1 Subword Units

Neural network models use a fixed size vocabulary to handle natrual language. When the potentail vocabulary space is huge, especially for a neural machine translation (NMT) task, there will be too many unknown words to a model.

To deal with such challenge, Sennrich, Haddow, and Birch (2015) propose the idea to break up rare words into subword units for neural network modeling. The first segmentation approach is inspired by the byte pair encoding compression algorithm, or BPE in short.

1.1 Byte Pair Encoding

BPE is originally a data compression algorithm that iteratively replaces the most frequent pair of bytes in a sequence with single unused byte. By maintaining a mapping table of the new byte and the replaced old bytes, we can recover the original message from a compressed representation by reversing the encoding process.

The same idea can be applied at charater-level instead of byte-level on a given corpus, effectively constitute a subword segmentation algorithm.

The procedure to apply BPE to obtain subwords can be summarized as the followings:

  1. Extract word-level vocabulary as the initial vocabulary
  2. Represent the initial vocabulary at character-level for each word, served as the working vocabulary
  3. [Pairing] For each word in the working vocabulary, do character-level BPE and out of all words extract the most frequent pair concatenated as a new subword added into the subword vocabulary
  4. [Merging] Replace the most frequent pair with its single concatenated version in the working vocabulary
  5. Repeat step 3-4 for a given number of times, depending on a desirable subword vocabulary size
  6. The final vocabulary is the initial fullword vocabulary plus the subword vocabulary

Based on the procedure, essentially we do not consider paris across word boundaries to be a potential subword. Number of merge operations is the only hyperparameter for the algorithm.

Since the most frequent subwords will be merged early, common words will remain as one unique symbol in the vocabulary, leaving out rare words splitted into smaller units (subwords).

Here is a toy implementation of BPE applied on a given corpus:

import re
from collections import defaultdict


class BPE:

  def __init__(self, sents, min_cnt=3, verbose=False):
    self.verbose = verbose
    init_vocab = defaultdict(int)
    for sent in sents:
      words = re.split(r"\W+", sent)
      for w in words:
        if w != "":
          init_vocab[w] += 1
    # Create fullword vocabulary.
    self.word_vocab = {k: v for k, v in init_vocab.items() if v >= min_cnt}
    # Insert space between each char in a word for latter ease of merge operation.
    # We directly borrow the idea from https://www.aclweb.org/anthology/P16-1162.
    self.working_vocab = {" ".join(k): v for k, v in self.word_vocab.items()}
    self.subword_vocab = defaultdict(int)
    # Also build a character-level vocabulary as the base subwords.
    self.char_vocab = defaultdict(int)
    for sent in sents:
      for char in list(sent):
        self.char_vocab[char] += 1

  def _find_top_subword(self):
    subword_pairs = defaultdict(int)
    for w, cnt in self.working_vocab.items():
      subw = w.split()
      for i in range(len(subw) - 1):
        # Count bigrams.
        subword_pairs[subw[i], subw[i+1]] += cnt
    top_subw_pair = max(subword_pairs, key=subword_pairs.get)
    top_subw = "".join(top_subw_pair)
    self.subword_vocab[top_subw] = subword_pairs[top_subw_pair]
    if self.verbose:
      print("New subword added: {}".format(top_subw))
    return top_subw_pair

  def _merge(self, subw_pair):
    bigram = re.escape(" ".join(subw_pair))
    p = re.compile(r"(?<!\S)" + bigram + r"(?!\S)")
    self.working_vocab = {p.sub("".join(subw_pair), w): cnt for w, cnt in self.working_vocab.items()}
  
  def update_subword(self, n_merge=1):
    for n in range(n_merge):
      top_subw_pair = self._find_top_subword()
      self._merge(top_subw_pair)

Let’s use Shakespeare as the input text example to run the BPE algorithm. First we download the data from Google’s public hosting service:

# bash
mkdir -p data
import warnings
warnings.simplefilter(action="ignore", category=FutureWarning)

import os
import shutil
import tensorflow as tf

shakes_file = "data/shakespeare.txt"
if not os.path.exists(shakes_file):
  shakes_dl_path = tf.keras.utils.get_file(
    "shakespeare.txt",
    "https://storage.googleapis.com/download.tensorflow.org/data/shakespeare.txt")
  shutil.move(shakes_dl_path, shakes_file)
shakespeare = open(shakes_file, "rb").read().decode(encoding="utf-8")
shakespeare = shakespeare.lower().split("\n")

# Print the first few lines.
for sent in shakespeare[:20]:
  print(sent)
first citizen:
before we proceed any further, hear me speak.

all:
speak, speak.

first citizen:
you are all resolved rather to die than to famish?

all:
resolved. resolved.

first citizen:
first, you know caius marcius is chief enemy to the people.

all:
we know't, we know't.

first citizen:
let us kill him, and we'll have corn at our own price.
bpe = BPE(shakespeare, min_cnt=10)
print(len(bpe.word_vocab))
1872
print(list(bpe.word_vocab.items())[:5])  # Print some from fullword vocabulary.
[('first', 363), ('citizen', 100), ('before', 195), ('we', 938), ('proceed', 21)]
print(list(bpe.working_vocab.items())[:5])  # (For debug) Print some from the working vocab that we are going to perform the merge.
[('f i r s t', 363), ('c i t i z e n', 100), ('b e f o r e', 195), ('w e', 938), ('p r o c e e d', 21)]
bpe.update_subword(n_merge=100)  # Do merge update.
print(len(bpe.subword_vocab))
100
print(list(bpe.working_vocab.items())[:5])  # Check the working vocabulary after merge.
[('f ir st', 363), ('c it i z en', 100), ('be for e', 195), ('we', 938), ('p ro ce ed', 21)]
print(list(bpe.subword_vocab.items())[:5])  # Print some subwords generated by the first 100 merge operations.
[('th', 25186), ('ou', 11960), ('the', 11654), ('an', 11587), ('in', 9012)]

Each learned subword itself doesn’t need to have explicit meaning. It is their potential combinations with other subwords that reveal the actual context, which can be hopefully learned by neural network embeddings for the downstream task.

A modification of the BPE algorithm is to expand subword vocabulary by likelihood instead of the most frequent pair. That is, in each merge operation we choose the new subword that increases the likelihood of the training data the most.

1.2 Probablistic Subword Segmentation

Note that BPE is deterministic by a greedy replacement of adjacent symbols, while the actual subword segmentation is potentially ambiguous. A sequence can be represented in multiple subword sequences since even the same word can be segmented differently by different subwords.

Kudo (2018) propose an approach called “subword regularization” to handle this issue in a probablistic fashion. A new subword segmentation under this approach uses a unigram language model for generating subword segmentation with attached probability.

Denote \(X\) as a subword sequence of length \(n\)

\[ X = \begin{pmatrix} x_1 & x_2 & \dots & x_n\end{pmatrix}, \]

with independent subword probability \(P(x_i)\). The probability of the sequence hence is simply

\[ P(X) = \prod_{i=1}^n P(x_i), \]

with

\[ \sum_{x \in V}P(x) = 1, \]

where \(V\) is a pre-determined vocabulary set.

The most probable sequence (from all possible segmentation candidates) can be found by applying expectation-maximization with the Viterbi algorithm, which is designed for finding the most likely sequence of hidden states. Here the hidden states are subwords used in a sequence.

The vocabulary set \(V\) is indeed undertermined at the very beginning and cannot be solved simultaneously with the maximization task. A workaround is to provide a seed vocabulary as the initial (reasonably big enough) vocabulary, and shrink the seed vocabulary during training until a desired vocabulary size is reached.

To fully understand the unigram language model for segmentation, let’s examine the expectation-maximization and Viterbi algorithm accordingly.

1.2.1 Expectation-Maximization

Quoted from wiki page of EM:

In statistics, an expectation–maximization (EM) algorithm is an iterative method to find maximum likelihood or maximum a posteriori (MAP) estimates of parameters in statistical models, where the model depends on unobserved latent variables.

It’s better to understand the EM with a working example.

We use a working example of a Gaussian Mixture to illustrate the core idea of EM. A Gaussian Mixture model is more of a Hello-World example of EM. In its simplest illustration, assume we observe a set of data resulting from either one of two different Gaussians, how can we estimate parameters of these two Gaussians without knowing which Gaussian they came from?1

First create some toy data:

import numpy as np

np.random.seed(777)
size = 100

g1_mean = 3
g2_mean = 7
g1_std = 1
g2_std = 2

g1_data = np.random.normal(g1_mean, g1_std, size=size)
g2_data = np.random.normal(g2_mean, g2_std, size=size)
mixture_data = np.concatenate([g1_data, g2_data])

The following plot illustrates our toy data distribution. The resulting distribution is a equal combination of two Gaussians plotted as dotted curves.

# R
x <- py$mixture_data
hist(x, xlab="Data", main="Sample Data from a Gaussian Mixture",
     probability=TRUE, ylim=c(0, .45))
curve(dnorm(x, mean=py$g1_mean, sd=py$g1_std),
      col="blue", lty="dotted", add=TRUE, yaxt="n")
curve(dnorm(x, mean=py$g2_mean, sd=py$g2_std),
      col="red", lty="dotted", add=TRUE, yaxt="n")

To formulate th EM algorithm, we first identify what is the hidden state (unobserved/latent variable) in a given modeling task. In the Gaussain Mixture example, the hidden state (for each observed data point) is the indicator of the original Gaussian that generats the data.

The EM algorithm contains two step in one iteration:

  1. [Expectation Step] Formulate the expected maximum likelihood based on a probabilistic distribution of hidden state; that is, an expected maximum likelihood estimator (MLE) conditional on hidden states
  2. [Maximization Step] Figure out model parameters by maximizing the expected MLE

The solution to model parameters can be found (without even knowing the hidden state but only its expectation) after serveral iterations.

Here is a simple EM implementation for a Guassian Mixture model:

from scipy import stats


class GaussianMixtureEM:

  def __init__(self, data):
    self.data = data
    # Initialize hidden state probabilistic weight.
    # Note that there is no guarantee that g1 will correspond to the first half of the data.
    # The actual estimated assignment is determined by the final weights.
    self.g1_weights = np.random.uniform(size=len(data))
    self.g2_weights = 1 - self.g1_weights
    # Initialize parameter guessings.
    self.update_estimates()

  def update_estimates(self):
    self.g1_mean = self.estimate_mean(self.g1_weights)
    self.g2_mean = self.estimate_mean(self.g2_weights)
    self.g1_std = self.estimate_std(self.g1_mean, self.g1_weights)
    self.g2_std = self.estimate_std(self.g2_mean, self.g2_weights)

  def estimate_mean(self, weights):
      return np.sum(self.data * weights) / np.sum(weights)

  def estimate_std(self, mean, weights):
      variance = np.sum(weights * (self.data - mean)**2) / np.sum(weights)
      return np.sqrt(variance)

  def em(self, n_iter=10):
    for n in range(n_iter):
      g1_lik = stats.norm(self.g1_mean, self.g1_std).pdf(self.data)
      g2_lik = stats.norm(self.g2_mean, self.g2_std).pdf(self.data)
      self.g1_weights = g1_lik / (g1_lik + g2_lik)
      self.g2_weights = g2_lik / (g1_lik + g2_lik)
      self.update_estimates()


mix_model = GaussianMixtureEM(mixture_data)
mix_model.em(30)
true_params = (g1_mean, g2_mean, g1_std, g2_std)
mle_params = (
  np.mean(mixture_data[:size]),
  np.mean(mixture_data[size:]),
  np.std(mixture_data[:size]),
  np.std(mixture_data[size:])
)
# To ensure the ordering so we can have a nicely printed comparison.
em_params = np.concatenate([
  np.sort([mix_model.g1_mean, mix_model.g2_mean]),
  np.sort([mix_model.g1_std, mix_model.g2_std])
])


for i, (t, m, e) in enumerate(zip(true_params, mle_params, em_params)):
  if i == 0:
    print("True Value | MLE (Known Hidden States) | EM (Unknown Hidden States)")
  print("{:^11}|{:^27}|{:^27}".format(t, np.round(m, 4), np.round(e, 4)))
True Value | MLE (Known Hidden States) | EM (Unknown Hidden States)
     3     |          3.0071           |          2.9194           
     7     |          7.1051           |          7.0672           
     1     |          0.9586           |          0.9047           
     2     |          1.9048           |          1.8579           

As one can see, we are able to properly estimate the model parameters (in this case the first and second moments for two different Gaussians) without knowing the hidden variables (the indicator of which data point comes from which Gaussian).

1.2.2 Viterbi Algorithm

To apply EM to our subword segmentation problem, we can use the Viterbi algorithm in the maximization step to find out the most probable sequence of subwords.

Let’s use the Shakespeare text to illustrate the Viterbi algorithm. For a given text line we’d like to calculate the most probable subwords that can recover the text line. In order to achieve that we need to have a vocabulary for subwords that can attribute each subword to a probability. We can use BPE to build such vocabulary. For complete coverage we will also include character-level subwords into the vocabulary.

# Build a seeding subword vocabulary using BPE.
# For illustration purpose we didn't go for a huge seeding size.
# In actual application the seeding vocabulary should be much larger than what we have here.
bpe = BPE(shakespeare, min_cnt=10)
bpe.update_subword(n_merge=1000)
subword_cnt = {**bpe.char_vocab, **bpe.subword_vocab}
tot_cnt = np.sum(list(subword_cnt.values()))

# The initial estimate of subword probability.
subword_logp = {k: np.log(v / tot_cnt) for k, v in subword_cnt.items()}

A Viterbi procedure contains two steps: a forward step and a backward step. The forward step is to determine the most probable subword given each end-of-character position in a word. The backward step is to recover the entire subword sequence that maximizes the likelihood of the sequence.

The Forward Step

def viterbi_forward(word, subword_logp):
  """Forward step of Viterbi given a single word."""
  # Create storage array for best substring recorded at each end-of-character position.
  best_subw_slices =  [None] * (len(word) + 1)
  neg_loglik = np.zeros(len(word) + 1)
  # Search the best substring given every possible end of position along the word.
  for eow in range(1, len(word) + 1):
    # For every end-of-word position:
    neg_loglik[eow] = np.inf
    for bow in range(eow):
      # For every possible beginning-of-word position given the end-of-word position:
      subw = line[bow:eow]
      if subw in subword_logp:
        logp = subword_logp[subw]
        # Compute subword probability:
        # P(current segment) + P(the best segment just before the current segment).
        s = neg_loglik[bow] - logp
        if s < neg_loglik[eow]:
          neg_loglik[eow] = s
          best_subw_slices[eow] = (bow, eow)
  return neg_loglik, best_subw_slices

We demonstrate the segmentation given a single fullword:

# Take a text line from Shakespeare.
line = shakespeare[213]
words = line.split()

w = words[0]
print(w)  # Word for segmentation demo.
whereby
subw_scores, subw_slices = viterbi_forward(w, subword_logp)
print(subw_slices)  # Best segment indices at each subword end.
[None, (0, 1), (0, 2), (2, 3), (2, 4), (0, 5), (5, 6), (5, 7)]
subw = [w[idx[0]:idx[1]] for idx in subw_slices if idx is not None]
print(subw)  # Best segmented subword at each subword end.
['w', 'wh', 'e', 'er', 'where', 'b', 'by']

Let’s visualize the segmentation!

The number beneath the a subword is the corresponding negative log-likelihood for that subword up to the corresonding end-of-character position. The lower the better.

# R
library(ggplot2)
library(data.table)

subw_demo <- rbindlist(py$subw_slices)
setnames(subw_demo, c("start", "end"))
subw_demo <- subw_demo[, subw:=py$subw]
subw_demo <- rbind(list(0, 0, ""), subw_demo)  # To plot one more block at the top.
subw_demo <- rbind(subw_demo, list(0, 0, "_"))  # To plot one more block at the bottom.
subw_demo <- subw_demo[, subw:=factor(subw, levels=rev(subw))]
subw_demo <- subw_demo[, score:=c(py$subw_scores, 0)]

# To also plot the entire word by character.
w_df <- data.table(x=rep("", nrow(subw_demo) - 2), y=0:(nrow(subw_demo)-3) + .5,
                   label=unlist(strsplit(py$w, NULL)))

viterbi_demo_p <- ggplot(subw_demo, aes(x=subw, y=(start + end) / 2, label=subw)) +
  geom_linerange(aes(x=subw, ymin=start, ymax=end), size=1.1) +
  geom_text(vjust=1, size=7) +
  geom_text(data=subw_demo[2:(nrow(subw_demo) - 1)],
            aes(label=sprintf("(%.2f)", score)), vjust=3.5, size=3, color="darkgrey") +
  geom_text(data=w_df, aes(x=x, y=y, label=label), size=14) +
  geom_point(aes(x=subw, y=end), data=subw_demo[2:(nrow(subw_demo) - 1)], size=3) +
  scale_y_continuous("", breaks=0:(nrow(subw_demo)-2), sec.axis=dup_axis()) +
  theme(axis.title.y=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks.y=element_blank(),
        panel.grid.minor=element_blank()) +
  coord_flip()

viterbi_demo_p

We iterate over every position in a given word. At each end-of-character position, we determine the best segment by finding the one with highest likelihood given the current vocabulary.

For example, when we are at position 2, we compare the log-liklihood of (wh) and (w, h). The former is more likely so we record its negative log-likelihood along with the segment indices and then move onto the next position. Now at position 3 we compare (whe) with (w, he) and (wh, e) and again return the best segment. Notice that we don’t compare (w, h, e) since in the previous position we already rule (w, h) out due to its being inferior (less likely) to (wh).

The Backward Step

After the forward step is finished, we can track backward the path of segments that generates the highest overall likelihood.

def viterbi_backward(word, subw_slices):
  """Backward step of Viterbi to return the best path."""
  subwords = []
  subword_slices = []
  next_slices = subw_slices[-1]
  while next_slices is not None:
    subw = word[next_slices[0]:next_slices[1]]
    subwords.append(subw)
    subword_slices.append(next_slices)
    next_slices = subw_slices[next_slices[0]]
  subwords.reverse()
  return subwords, subword_slices


# Test with a single word.
best_subw, best_slices = viterbi_backward(w, subw_slices)
print(best_subw)
['where', 'by']

This is easily understandable if we also visualize the backward step:

# R
seg <- rbindlist(py$best_slices)
setnames(seg, c("yend", "y"))
seg <- seg[, x:=match(rev(py$best_subw), rev(subw_demo$subw))]
seg <- seg[, xend:=x]
seg <- seg[, subw:=""]  # Required to bypass ggplot data checking.

viterbi_demo_p +
  geom_segment(aes(x=x, xend=xend, y=y, yend=yend), data=seg,
               col="red", arrow=arrow(length=unit(0.03, "npc")), size=1.5)

Now combining the forward and backward steps for a complete segmentation of a text line:

def viterbi(line):
  out = []
  words = line.split()
  for w in words:
    subw_scores, subw_slices = viterbi_forward(w, subword_logp)
    subw, subw_idx = viterbi_backward(w, subw_slices)
    out.extend(subw)
  return out

print(line)  # Input sentence.
whereby they live: and though that all at once,
print(viterbi(line))  # Output subword sequence.
['where', 'by', 'th', 'ey', 'live:', 'an', 'd', 'thoug', 'h', 'th', 'at', 'al', 'l', 'at', 'once,']

1.2.3 EM with Viterbi

Now we know the idea of EM, and we know how to find the optimal segment path by Viterbi, we can put them together to forumlate the optimization task of our probablistic subword segmentation.

Here is the complete procedure:

  1. Initialize a large seeding subword vocabulary from the training corpus
  2. [Expectation] Estimate each subword probability by the corresponding frequency counts in the vocabulary
  3. [Maximization] Use Viterbi to segment the corpus, returning the optimal segments
  4. Compute the loss of each new subword from optimal segments
  5. Shrink the vocabulary size by dropping the subwords with top X% smallest losses
  6. Repeat step 2 to 5 until the vocabulary size reaches a desired number

The loss of a subword in step 4 is the reduction in overall training corpus segment likelihood if that subword is removed from the current vocabulary.

In the above example we use BPE to generate the initial vocabulary. Another common choice is to the suffix array algorithm. to generate the common substrings.

2 SentencePiece

Kudo and Richardson (2018) propose a language-agnostic subword segmentation algorithm called SentencePiece. SentencePiece is a powerful and efficient open-sourced unsupervised langugae tokenizer that can help build end-to-end vocabulary for neural networks. In this section we will walk-through the idea of SentencePiece along with demo examples.

2.1 Model Characteristics

In this section we briefly summarize several useful characteristics about the segmentation model.

2.1.1 Language-Agnostic Handle

SentencePiece is langauge independent becuase under the hood it simply treats a given sentence as a sequence of Unicode characters. And most written langauge in the world we speak can be encoded in Unicode. This is particualrly useful for non-whitespace-segmented languages such as Chinese, Korean and Japanese.

Technically speaking, in SentencePiece whitespace is also treated as just another normal symbol. To achieve this, a meta symbol (U+2581) is used to replace the whitespace at the very beginning.

The entire vocabulary is built from subwords directly based on raw text input so there is also no need to do pre-processing to provide a fullword initial vocabulary. This bypasses the need for any language-specific preprocessing logic, resulting in a unified framework for natural langauge segmentation.

2.1.2 Lossless Tokenization

Because of the special (or rather, non-special) treatment of whitespace as a normal symbol, SentencePiece is able to acheive lossless tokenization. That is, the entire raw text can be recovered from its segmented tokens with zero ambiguity.

The reason why mosty of the other tokenization algorithm won’t be able to achieve this is because of the pre-tokenization stage that use whitespace to pre-process the corpus into a working vocabulary. The subword segmentation is then operating on the working vocabulary instead of the raw text lines in the corpus.

Notice that in our toy example in the previous section we also relies on a pre-tokenization before the subword segmentation kicks in.

2.1.3 Self-Contained Integer Mapping

Yet another goodness about SentencePiece is its flexible cabapility to generate the integer mappings that is required for neural network modeling in the embedding lookup subtask. It can handle all the following meta symbols:

  • bos: beginning of word
  • eos: ending of word
  • unk: out-of-vocabulary word
  • pad: padding for fixed-length output, especially useful in recurrent neural network modeling

Once the model is trained, the mappings are self-contained in the model file and can be used to encode/decode raw text/integer sequence on the fly.

2.2 Command Line Usage

spm_train --version
sentencepiece 0.1.82

SentencePiece is written in C++ with both a command line tool and a Python wrapper. There are a handful of options that can be used to setup the model configuration.

# bash
spm_train --help
sentencepiece

Usage: spm_train [options] files

   --accept_language (comma-separated list of languages this model can accept)  type: string  default: 
   --add_dummy_prefix (Add dummy whitespace at the beginning of text)  type: bool  default: true
   --bos_id (Override BOS (<s>) id. Set -1 to disable BOS.)  type: int32  default: 1
   --bos_piece (Override BOS (<s>) piece.)  type: string  default: <s>
   --character_coverage (character coverage to determine the minimum symbols)  type: double  default: 0.9995
   --control_symbols (comma separated list of control symbols)  type: string  default: 
   --eos_id (Override EOS (</s>) id. Set -1 to disable EOS.)  type: int32  default: 2
   --eos_piece (Override EOS (</s>) piece.)  type: string  default: </s>
   --hard_vocab_limit (If set to false, --vocab_size is considered as a soft limit.)  type: bool  default: true
   --input (comma separated list of input sentences)  type: string  default: 
   --input_format (Input format. Supported format is `text` or `tsv`.)  type: string  default: 
   --input_sentence_size (maximum size of sentences the trainer loads)  type: int32  default: 0
   --max_sentence_length (maximum length of sentence in byte)  type: int32  default: 4192
   --max_sentencepiece_length (maximum length of sentence piece)  type: int32  default: 16
   --model_prefix (output model prefix)  type: string  default: 
   --model_type (model algorithm: unigram, bpe, word or char)  type: string  default: unigram
   --normalization_rule_name (Normalization rule name. Choose from nfkc or identity)  type: string  default: nmt_nfkc
   --normalization_rule_tsv (Normalization rule TSV file. )  type: string  default: 
   --num_sub_iterations (number of EM sub-iterations)  type: int32  default: 2
   --num_threads (number of threads for training)  type: int32  default: 16
   --pad_id (Override PAD (<pad>) id. Set -1 to disable PAD.)  type: int32  default: -1
   --pad_piece (Override PAD (<pad>) piece.)  type: string  default: <pad>
   --remove_extra_whitespaces (Removes leading, trailing, and duplicate internal whitespace)  type: bool  default: true
   --seed_sentencepiece_size (the size of seed sentencepieces)  type: int32  default: 1000000
   --self_test_sample_size (the size of self test samples)  type: int32  default: 0
   --shrinking_factor (Keeps top shrinking_factor pieces with respect to the loss)  type: double  default: 0.75
   --shuffle_input_sentence (Randomly sample input sentences in advance. Valid when --input_sentence_size > 0)  type: bool  default: true
   --split_by_number (split tokens by numbers (0-9))  type: bool  default: true
   --split_by_unicode_script (use Unicode script to split sentence pieces)  type: bool  default: true
   --split_by_whitespace (use a white space to split sentence pieces)  type: bool  default: true
   --treat_whitespace_as_suffix (treat whitespace marker as suffix instead of prefix.)  type: bool  default: false
   --unk_id (Override UNK (<unk>) id.)  type: int32  default: 0
   --unk_piece (Override UNK (<unk>) piece.)  type: string  default: <unk>
   --unk_surface (Dummy surface string for <unk>. In decoding <unk> is decoded to `unk_surface`.)  type: string  default:  ⁇ 
   --use_all_vocab (If set to true, use all tokens as vocab. Valid for word/char models.)  type: bool  default: false
   --user_defined_symbols (comma separated list of user defined symbols)  type: string  default: 
   --vocab_size (vocabulary size)  type: int32  default: 8000

SentencePiece supports not only the unigram langauge model we just discussed in the previous section, it also supports BPE algorithm, and two naive tokenizer one for word-level the other for character-level.

Several options are relevant to the unigram model:

  • --seed_sentencepiece_size=1000000: This is the initial seeding vocabulary for the EM-Viterbi algorithm to start with
  • --shrinking_factor=0.75: This is by how much the seeding vocabulary is reduced based on the loss-ranking dropout procedure
  • --num_sub_iterations=2: This is the hyperparameter for the EM-Viterbi algorithm

2.3 Python Usage

The command line tool is very concise. But since we usually train the model for a downstream neural network model that is developed in Python, we will turn our focus into SentencePiece’s Python wrapper. It turns out that it is not much different. We will still use the command line option style to control the behavior of the tool.

Let’s train a small vocabulary using the Shakespeare:

import sentencepiece as spm

spm_args = "--input=data/shakespeare.txt"
spm_args += " --model_prefix=m"
spm_args += " --vocab_size=1000"
spm_args += " --model_type=unigram"
spm.SentencePieceTrainer.Train(spm_args)

2.3.1 Vocabulary Inspection

The resulting .model file comes with a vocabulary file .vocab that can be used for investigation. (It is not required for segmentation since the vocab is self-contained in the model file already.)

The .vocab file contains all final subwords sorted in descending order for its log-likelihood.

# R
# We use R here for the sake of a nicely formatted table done by Rmd notebook. :)
vocab <- fread("m.vocab", header=FALSE, encoding="UTF-8")
setnames(vocab, c("subword", "logp"))
vocab <- vocab[, p:=round(exp(logp), 5)]
vocab[]
# The summation of all subwords should approximate unity.
sum(exp(vocab$logp)[-(1:3)])
[1] 0.9914569

Note the common presence of the special symbol (U+2581) that is used to encode the original whitespace. Usually a subword with this symbol means it is a legitimate fullword, or at least a subword that is the prefix rather than the suffix part of a fullword.

2.3.2 Encoding-Decoding

Once trained, we can load the model with a single line:

sp = spm.SentencePieceProcessor()
sp.Load("m.model")

Here are the general usage of the encoding/decoding APIs:

subword_pieces = sp.EncodeAsPieces("This is a test.")
print(subword_pieces)
['▁This', '▁is', '▁a', '▁t', 'est', '.']
print("".join(subword_pieces).replace("\u2581", " "))  # Recover the whitespace.
 This is a test.
subword_ids = sp.EncodeAsIds("This is a test.")
print(subword_ids)  # Convert sentence to integer sequence.
[343, 38, 15, 211, 153, 7]
print(sp.DecodePieces(subword_pieces))  # Lossless Detokenization from subwords.
This is a test.
print(sp.DecodeIds(subword_ids))  # Lossless Detokenization from subword ids.
This is a test.
# By default the first 3 ids are token for unknown, bos and eos.
for i in range(3):
  print(sp.IdToPiece(i))
<unk>
<s>
</s>
# The word "test" is not common enough in Shakespeare.
# So it is not part of our small vocabulary.
print(sp.PieceToId("test"))  # Map to integer representing unknown.
0

2.3.3 Subword Sampling

Remember that the unigram model for segmentation is probabilistic, which means that one sentence can be represented by more than one sequence of segments (or subwords). By default the most probable segmentation is used when we are doing encoding-decoding task with SentencePiece. But we can also generate segments using sampling based on the subword probability in our vocabulary:

for _ in range(5):
  print(sp.SampleEncodeAsPieces("This is a test", nbest_size=-1, alpha=.1))
['▁Th', 'is', '▁', 'is', '▁', 'a', '▁', 't', 'e', 'st']
['▁T', 'h', 'i', 's', '▁is', '▁a', '▁', 't', 'e', 's', 't']
['▁This', '▁', 'is', '▁a', '▁', 'te', 's', 't']
['▁This', '▁', 'i', 's', '▁a', '▁', 't', 'est']
['▁This', '▁', 'is', '▁a', '▁', 't', 'e', 's', 't']

Or we can simply generate the top 5 most probable segmentations:

for seg in sp.NBestEncodeAsPieces("This is a test", 5):
  print(seg)
['▁This', '▁is', '▁a', '▁t', 'est']
['▁This', '▁is', '▁a', '▁', 't', 'est']
['▁This', '▁is', '▁a', '▁', 'te', 'st']
['▁This', '▁is', '▁a', '▁t', 'e', 'st']
['▁This', '▁is', '▁', 'a', '▁t', 'est']

3 Exercise: Chinese Words Segmentation

In this section we demonstrate the segmentation on a non-whitespace-separated langauge: traditional chinese. We will use a subset of wikipedia articles (from Wikimedia Downloads). We use the WikiExtractor tool to process the raw dump, and OpenCC to convert the texts into traditional chinese (Taiwan standard).

# Prepare zh-wiki dumps
OUTDIR=data/zhwiki_texts
if [ ! -d $OUTDIR ]
then
  # Download the data if not found locally.
  WIKIDUMP=zhwiki-latest-pages-articles1.xml-p1p162886
  if [ ! -f data/"$WIKIDUMP" ]
  then
    wget https://dumps.wikimedia.org/zhwiki/latest/$WIKIDUMP.bz2
    mv $WIKIDUMP.bz2 data
    bzip2 -d -k $WIKIDUMP.bz2
  fi
  # Extract clean text from xml dump.
  WikiExtractor.py --json --no_templates --processes 4 \
    -o $OUTDIR --min_text_length 10 data/$WIKIDUMP
fi
import jsonlines

infile = "data/zhwiki_texts/AA/wiki_00"
def parse_wiki_sent(infile):
  # Simple sentence delimiter.
  _DELIMITER_HALF = ["!", "\?"]
  _ZH_DELIMITER_FULL = ["。", "!", "?"]
  _GENERAL_DELIMITER = ["\n"]
  _DELIMITER = _DELIMITER_HALF + _ZH_DELIMITER_FULL + _GENERAL_DELIMITER
  split_pat = r'{}'.format('|'.join(_DELIMITER))

  # Read paragraphs.
  paragraphs = []
  with jsonlines.open(infile) as jlines:
    for j in jlines:
      paragraphs.append(j["text"])

  # Break by newline.
  lines = []
  for p in paragraphs:
    lines.extend(p.lower().split("\n"))

  # Further break by sentence. We use a naive approach here just for simplicity.
  sents = []
  for l in lines:
    if len(l) >= 20:
      sents.extend(re.split(split_pat, l))

  return sents

sents = parse_wiki_sent(infile)

# Show raw cleaned texts.
for s in sents[:3]:
  print(s + "\n")
数学是利用符号语言研究數量、结构、变化以及空间等概念的一門学科,从某种角度看屬於形式科學的一種

數學透過抽象化和邏輯推理的使用,由計數、計算、量度和對物體形狀及運動的觀察而產生

數學家們拓展這些概念,為了公式化新的猜想以及從選定的公理及定義中建立起嚴謹推導出的定理
# Write out sentence files from processed wiki dumps.
indir = "data/zhwiki_texts"
outfile = "data/zhwiki_sents.txt"

if not os.path.exists(outfile):
  with open(outfile, "w") as f:
    for root, dirs, leafs in os.walk(indir):
      for l in leafs:
        infile = os.path.join(root, l)
        if os.path.isfile(infile):
          sents = parse_wiki_sent(infile)
          for s in sents:
            if s != "":
              f.write(s + "\n")
# Bash
# Convert to traditional chinese in Taiwan standard.
if [ ! -f "data/tw_zhwiki_sents.txt" ]
then
  opencc -i data/zhwiki_sents.txt -o data/tw_zhwiki_sents.txt -c s2twp.json
fi
head -n5 data/tw_zhwiki_sents.txt
基督宗教歷史,指基督宗教、基督教世界及各宗派教會的歷史,從公元一世紀耶穌及其門徒的時代開始,直到現代
基督宗教於公元一世紀中葉發源於羅馬帝國統治的黎凡特地區
起先是被壓迫的宗教,但很快從耶路撒冷傳播到整個近東,包括亞蘭,亞述,美索不達米亞,腓尼基,小亞細亞,約旦和埃及等地
在301年成為亞美尼亞國教,319年成為喬治亞國教,325年成為阿克蘇姆帝國國教,380年成為羅馬帝國國教
公元431年以弗所公會議後,聶斯脫利派分離,形成東方亞述教會
import sentencepiece as spm

# Skip training if the model file is already there, to save notebook rendering time.
if not os.path.exists("twzh.model"):
  spm_args = "--input=data/tw_zhwiki_sents.txt"
  spm_args += " --model_prefix=twzh"
  spm_args += " --vocab_size=30000"
  spm_args += " --model_type=unigram"
  spm.SentencePieceTrainer.Train(spm_args)
twzh_model = spm.SentencePieceProcessor()
twzh_model.Load("twzh.model")
True
twzh_model.EncodeAsPieces("光復香港,時代革命")
['▁', '光復', '香港', ',', '時代', '革命']
twzh_model.EncodeAsPieces("我們與惡的距離")
['▁我們', '與', '惡', '的距離']
twzh_model.EncodeAsPieces("所有的模型都是錯的,但有些是有用的")
['▁', '所有的', '模型', '都是', '錯', '的', ',', '但', '有些', '是', '有用的']

The result is not perfect, but considering the effort we put it is quite good already. For a bigger corpus that may be more representative to the downstream application, the unsupervised tokenization is a very convenient way to quickly dive into the modeling phase with little pre-processing effort of the traditional NLP tasks.

4 References

Kudo, Taku. 2018. “Subword Regularization: Improving Neural Network Translation Models with Multiple Subword Candidates.” arXiv Preprint arXiv:1804.10959.

Kudo, Taku, and John Richardson. 2018. “Sentencepiece: A Simple and Language Independent Subword Tokenizer and Detokenizer for Neural Text Processing.” arXiv Preprint arXiv:1808.06226.

Sennrich, Rico, Barry Haddow, and Alexandra Birch. 2015. “Neural Machine Translation of Rare Words with Subword Units.” arXiv Preprint arXiv:1508.07909.


  1. We borrow the example from this stack-overflow thread, with a modification to use a general initial guess by randomizing the hidden state weights.↩︎

LS0tCnRpdGxlOiAiT24gU3Vid29yZCBVbml0cyIKc3VidGl0bGU6ICJTZWdtZW50YXRpb24gZm9yIE5hdHVyYWwgTGFuZ3VhZ2UgTW9kZWxpbmciCmF1dGhvcjoKLSBuYW1lOiBLeWxlIENodW5nCiAgYWZmaWxpYXRpb246CmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVkICViICVZJylgIExhc3QgVXBkYXRlZCAoMDQgQXVnIDIwMTkgRmlyc3QgVXBsb2FkZWQpIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazogCiAgICBoaWdobGlnaHQ6IHB5Z21lbnRzCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcwogICAgdGhlbWU6IHBhcGVyCiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAzCiAgICB0b2NfZmxvYXQ6IHllcwogICAgaW5jbHVkZXM6CiAgICAgIGluX2hlYWRlcjogL3RtcC9tZXRhX2hlYWRlci5odG1sCiAgY29kZV9kb3dubG9hZDogdHJ1ZQpiaWJsaW9ncmFwaHk6IHN1YndvcmRfdW5pdHMuYmliCmRlc2NyaXB0aW9uOiB0ZXN0CmFic3RyYWN0OiB8CiAgTGFuZ2F1Z2Ugc2VnbWVudGF0aW9uIG9yIHRva2VuaXphdGlvbiBpcyB0aGUgdmVyeSBmaXJzdCBzdGVwIHRvd2FyZCBhIG5hdHVyYWwgbGFuZ3VhZ2UgdW5kZXJzdGFuZGluZyAoTkxVKSB0YXNrLiBUaGUgc3RhdGUtb2YtdGhlLWFydCBOTFUgbW9kZWwgdXN1YWxseSBpbnZvbHZlcyBuZXVyYWwgbmV0d29ya3Mgd2l0aCB3b3JkIGVtYmVkZGluZ3MgYXMgdGhlIHdvcmtob3JzZSB0byBlbmNvZGUgcmF3IHRleHQgb250byB2ZWN0b3Igc3BhY2UuIEEgZml4ZWQgdm9jYWJ1bGFyeSBpcyBwcmUtZGV0ZXJtaW5lZCBpbiBvcmRlciB0byBmYWNpbGl0YXRlIHN1Y2ggc2V0dXAuIFdpdGggdGhlIGNoYWxsZW5nZSBvZiBvdXQtb2Ytdm9jYWJ1bGFyeSBpc3N1ZSBhbmQgbm9uLXdoaXRlc3BhY2UtZGVsaW1pdGVkIGxhbmd1YWdlLCB3ZSB1c2Ugc3Vid29yZCB1bml0cyB0byBmdXJ0aGVyIGRlY29tcG9zZSByYXcgdGV4dHMgaW50byBzdWJzdHJpbmdzLiBJbiB0aGlzIG5vdGVib29rIHdlIHN1bW1hcml6ZSB0aGUgdGVjaG5pcXVlIG9mIHN1YndvcmQgc2VnbWVudGF0aW9uIGluIGRldGFpbHMgd2l0aCBQeXRob24gY29kaW5nIGV4YW1wbGVzLiBXZSBhbHNvIHByb3ZpZGUgYSBnZW5lcmFsIHVzYWdlIHdhbGstdGhyb3VnaCBmb3IgR29vZ2xlJ3Mgb3BlbiBzb3VyY2UgbGlicmFyeSAqU2VudGVuY2VQaWVjZSosIGEgcG93ZXJmdWwgbGFuZ3VhZ2UtYWdub3N0aWMgdW5zdXBlcnZpc2VkIHN1YndvcmQgc2VnbWVudGF0aW9uIHRvb2wuCi0tLQo8IS0tRm9yIGNvbnRyb2xpbmcgY29kZSBmb2xkaW5nIGJ5IGNodW5rLi0tPgo8c2NyaXB0IHNyYz0iLi4vLi4vc2l0ZV9saWJzL3V0aWxzL2hpZGVfb3V0cHV0LmpzIj48L3NjcmlwdD4KCjwhLS1Gb3IgZXF1YXRpb24gcmVmZXJlbmNlIGluIFJtZC4tLT4KPHNjcmlwdCB0eXBlPSJ0ZXh0L3gtbWF0aGpheC1jb25maWciPgpNYXRoSmF4Lkh1Yi5Db25maWcoewogIFRlWDogeyBlcXVhdGlvbk51bWJlcnM6IHsgYXV0b051bWJlcjogIkFNUyIgfSB9Cn0pOwo8L3NjcmlwdD4KCmBgYHtyIG1ldGEsIGluY2x1ZGU9RkFMU0V9Cm1ldGFfaGVhZGVyX2ZpbGUgPC0gZmlsZSgiL3RtcC9tZXRhX2hlYWRlci5odG1sIikKCiMgQWRkIG9wZW4gZ3JhcGggbWV0YS4KbWV0YSA8LSBjKAogICAgJzxtZXRhIG5hbWU9ImF1dGhvciIgY29udGVudD0iS3lsZSBDaHVuZyI+JywKICAgICc8bWV0YSBwcm9wZXJ0eT0ib2c6dGl0bGUiIGNvbnRlbnQ9Ik9uIFN1YndvcmQgVW5pdHM6IFNlZ21lbnRhdGlvbiBmb3IgTmF0dXJhbCBMYW5ndWFnZSBNb2RlbGluZyI+JywKICAgICc8bWV0YSBwcm9wZXJ0eT0ib2c6dHlwZSIgY29udGVudD0iYXJ0aWNsZSI+JywKICAgICc8bWV0YSBwcm9wZXJ0eT0ib2c6dXJsIiBjb250ZW50PSJodHRwczovL2V2ZXJkYXJrLmdpdGh1Yi5pby9rOS9uYXR1cmFsX2xhbmd1YWdlX3VuZGVyc3RhbmRpbmcvc3Vid29yZF91bml0cy9zdWJ3b3JkX3VuaXRzLm5iLmh0bWwiPicsCiAgICAnPG1ldGEgcHJvcGVydHk9Im9nOmltYWdlIiBjb250ZW50PSJodHRwczovL2V2ZXJkYXJrLmdpdGh1Yi5pby9rOS9hc3NldHMvdml0ZXJiaS5wbmciPicsCiAgICAnPG1ldGEgcHJvcGVydHk9Im9nOmRlc2NyaXB0aW9uIiBjb250ZW50PSJBIGRhdGEgc2NpZW5jZSBub3RlYm9vayBhYm91dCBuYXR1cmFsIGxhbmd1YWdlIHVuZGVyc3RhbmRpbmcuIj4nCikKY29udGVudHMgPC0gbWV0YQoKIyBBZGQgR2l0aHViIGNvcm5lci4KZ2l0aHViX2Nvcm5lcl9zdmcgPC0gIi4uLy4uL2Fzc2V0cy9naXRodWJfY29ybmVyLmh0bWwiCmdpdGh1Yl9jb3JuZXJfY29uZiA8LSBsaXN0KGdpdGh1Yl9saW5rPSJodHRwczovL2dpdGh1Yi5jb20vZXZlcmRhcmsvazkvdHJlZS9tYXN0ZXIvbmF0dXJhbF9sYW5ndWFnZV91bmRlcnN0YW5kaW5nL3N1YndvcmRfdW5pdHMiKQpjb250ZW50cyA8LSBjKGNvbnRlbnRzLCBzdHJpbmdyOjpzdHJfaW50ZXJwKHJlYWRMaW5lcyhnaXRodWJfY29ybmVyX3N2ZyksIGdpdGh1Yl9jb3JuZXJfY29uZikpCndyaXRlTGluZXMoY29udGVudHMsIG1ldGFfaGVhZGVyX2ZpbGUpCgpjbG9zZShtZXRhX2hlYWRlcl9maWxlKQpgYGAKCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQpsaWJyYXJ5KHJldGljdWxhdGUpCnIgPC0gdHJ5KHVzZV9weXRob24oU3lzLmdldGVudigiUFlUSE9OX1BBVEgiKSwgcmVxdWlyZWQ9VFJVRSksIHNpbGVudD1UUlVFKQppZiAoIGlzKHIsICJ0cnktZXJyb3IiKSApIHsKICByIDwtIHRyeSh1c2VfdmlydHVhbGVudihTeXMuZ2V0ZW52KCJQWVRIT05fUEFUSCIpLCByZXF1aXJlZD1UUlVFKSwgc2lsZW50PVRSVUUpCiAgaWYgKCBpcyhyLCAidHJ5LWVycm9yIikgKSB1c2VfY29uZGFlbnYoU3lzLmdldGVudigiUFlUSE9OX1BBVEgiKSwgcmVxdWlyZWQ9VFJVRSkKfQpgYGAKCiMgU3Vid29yZCBVbml0cwoKTmV1cmFsIG5ldHdvcmsgbW9kZWxzIHVzZSBhIGZpeGVkIHNpemUgdm9jYWJ1bGFyeSB0byBoYW5kbGUgbmF0cnVhbCBsYW5ndWFnZS4KV2hlbiB0aGUgcG90ZW50YWlsIHZvY2FidWxhcnkgc3BhY2UgaXMgaHVnZSwKZXNwZWNpYWxseSBmb3IgYSBuZXVyYWwgbWFjaGluZSB0cmFuc2xhdGlvbiAoTk1UKSB0YXNrLAp0aGVyZSB3aWxsIGJlIHRvbyBtYW55IHVua25vd24gd29yZHMgdG8gYSBtb2RlbC4KClRvIGRlYWwgd2l0aCBzdWNoIGNoYWxsZW5nZSwKQHNlbm5yaWNoMjAxNW5ldXJhbCBwcm9wb3NlIHRoZSBpZGVhIHRvIGJyZWFrIHVwIHJhcmUgd29yZHMgaW50byBzdWJ3b3JkIHVuaXRzIGZvciBuZXVyYWwgbmV0d29yayBtb2RlbGluZy4KVGhlIGZpcnN0IHNlZ21lbnRhdGlvbiBhcHByb2FjaCBpcyBpbnNwaXJlZCBieSB0aGUgWypieXRlIHBhaXIgZW5jb2RpbmcqXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9CeXRlX3BhaXJfZW5jb2RpbmcpIGNvbXByZXNzaW9uIGFsZ29yaXRobSwKb3IgQlBFIGluIHNob3J0LgoKIyMgQnl0ZSBQYWlyIEVuY29kaW5nCgpCUEUgaXMgb3JpZ2luYWxseSBhIGRhdGEgY29tcHJlc3Npb24gYWxnb3JpdGhtIHRoYXQgaXRlcmF0aXZlbHkgcmVwbGFjZXMgdGhlIG1vc3QgZnJlcXVlbnQgcGFpciBvZiBieXRlcyBpbiBhIHNlcXVlbmNlIHdpdGggc2luZ2xlIHVudXNlZCBieXRlLgpCeSBtYWludGFpbmluZyBhIG1hcHBpbmcgdGFibGUgb2YgdGhlIG5ldyBieXRlIGFuZCB0aGUgcmVwbGFjZWQgb2xkIGJ5dGVzLAp3ZSBjYW4gcmVjb3ZlciB0aGUgb3JpZ2luYWwgbWVzc2FnZSBmcm9tIGEgY29tcHJlc3NlZCByZXByZXNlbnRhdGlvbiBieSByZXZlcnNpbmcgdGhlIGVuY29kaW5nIHByb2Nlc3MuCgpUaGUgc2FtZSBpZGVhIGNhbiBiZSBhcHBsaWVkIGF0IGNoYXJhdGVyLWxldmVsIGluc3RlYWQgb2YgYnl0ZS1sZXZlbCBvbiBhIGdpdmVuIGNvcnB1cywKZWZmZWN0aXZlbHkgY29uc3RpdHV0ZSBhIHN1YndvcmQgc2VnbWVudGF0aW9uIGFsZ29yaXRobS4KClRoZSBwcm9jZWR1cmUgdG8gYXBwbHkgQlBFIHRvIG9idGFpbiBzdWJ3b3JkcyBjYW4gYmUgc3VtbWFyaXplZCBhcyB0aGUgZm9sbG93aW5nczoKCjEuIEV4dHJhY3Qgd29yZC1sZXZlbCB2b2NhYnVsYXJ5IGFzIHRoZSBpbml0aWFsIHZvY2FidWxhcnkKMi4gUmVwcmVzZW50IHRoZSBpbml0aWFsIHZvY2FidWxhcnkgYXQgY2hhcmFjdGVyLWxldmVsIGZvciBlYWNoIHdvcmQsIHNlcnZlZCBhcyB0aGUgd29ya2luZyB2b2NhYnVsYXJ5CjMuIFtQYWlyaW5nXSBGb3IgZWFjaCB3b3JkIGluIHRoZSB3b3JraW5nIHZvY2FidWxhcnksIGRvIGNoYXJhY3Rlci1sZXZlbCBCUEUgYW5kIG91dCBvZiBhbGwgd29yZHMgZXh0cmFjdCB0aGUgbW9zdCBmcmVxdWVudCBwYWlyIGNvbmNhdGVuYXRlZCBhcyBhIG5ldyBzdWJ3b3JkIGFkZGVkIGludG8gdGhlIHN1YndvcmQgdm9jYWJ1bGFyeQo0LiBbTWVyZ2luZ10gUmVwbGFjZSB0aGUgbW9zdCBmcmVxdWVudCBwYWlyIHdpdGggaXRzIHNpbmdsZSBjb25jYXRlbmF0ZWQgdmVyc2lvbiBpbiB0aGUgd29ya2luZyB2b2NhYnVsYXJ5CjUuIFJlcGVhdCBzdGVwIDMtNCBmb3IgYSBnaXZlbiBudW1iZXIgb2YgdGltZXMsIGRlcGVuZGluZyBvbiBhIGRlc2lyYWJsZSBzdWJ3b3JkIHZvY2FidWxhcnkgc2l6ZQo2LiBUaGUgZmluYWwgdm9jYWJ1bGFyeSBpcyB0aGUgaW5pdGlhbCBmdWxsd29yZCB2b2NhYnVsYXJ5IHBsdXMgdGhlIHN1YndvcmQgdm9jYWJ1bGFyeQoKQmFzZWQgb24gdGhlIHByb2NlZHVyZSwKZXNzZW50aWFsbHkgd2UgZG8gbm90IGNvbnNpZGVyIHBhcmlzIGFjcm9zcyB3b3JkIGJvdW5kYXJpZXMgdG8gYmUgYSBwb3RlbnRpYWwgc3Vid29yZC4KTnVtYmVyIG9mIG1lcmdlIG9wZXJhdGlvbnMgaXMgdGhlIG9ubHkgaHlwZXJwYXJhbWV0ZXIgZm9yIHRoZSBhbGdvcml0aG0uCgpTaW5jZSB0aGUgbW9zdCBmcmVxdWVudCBzdWJ3b3JkcyB3aWxsIGJlIG1lcmdlZCBlYXJseSwKY29tbW9uIHdvcmRzIHdpbGwgcmVtYWluIGFzIG9uZSB1bmlxdWUgc3ltYm9sIGluIHRoZSB2b2NhYnVsYXJ5LApsZWF2aW5nIG91dCByYXJlIHdvcmRzIHNwbGl0dGVkIGludG8gc21hbGxlciB1bml0cyAoc3Vid29yZHMpLgoKSGVyZSBpcyBhIHRveSBpbXBsZW1lbnRhdGlvbiBvZiBCUEUgYXBwbGllZCBvbiBhIGdpdmVuIGNvcnB1czoKCmBgYHtweXRob24gYnBlfQppbXBvcnQgcmUKZnJvbSBjb2xsZWN0aW9ucyBpbXBvcnQgZGVmYXVsdGRpY3QKCgpjbGFzcyBCUEU6CgogIGRlZiBfX2luaXRfXyhzZWxmLCBzZW50cywgbWluX2NudD0zLCB2ZXJib3NlPUZhbHNlKToKICAgIHNlbGYudmVyYm9zZSA9IHZlcmJvc2UKICAgIGluaXRfdm9jYWIgPSBkZWZhdWx0ZGljdChpbnQpCiAgICBmb3Igc2VudCBpbiBzZW50czoKICAgICAgd29yZHMgPSByZS5zcGxpdChyIlxXKyIsIHNlbnQpCiAgICAgIGZvciB3IGluIHdvcmRzOgogICAgICAgIGlmIHcgIT0gIiI6CiAgICAgICAgICBpbml0X3ZvY2FiW3ddICs9IDEKICAgICMgQ3JlYXRlIGZ1bGx3b3JkIHZvY2FidWxhcnkuCiAgICBzZWxmLndvcmRfdm9jYWIgPSB7azogdiBmb3IgaywgdiBpbiBpbml0X3ZvY2FiLml0ZW1zKCkgaWYgdiA+PSBtaW5fY250fQogICAgIyBJbnNlcnQgc3BhY2UgYmV0d2VlbiBlYWNoIGNoYXIgaW4gYSB3b3JkIGZvciBsYXR0ZXIgZWFzZSBvZiBtZXJnZSBvcGVyYXRpb24uCiAgICAjIFdlIGRpcmVjdGx5IGJvcnJvdyB0aGUgaWRlYSBmcm9tIGh0dHBzOi8vd3d3LmFjbHdlYi5vcmcvYW50aG9sb2d5L1AxNi0xMTYyLgogICAgc2VsZi53b3JraW5nX3ZvY2FiID0geyIgIi5qb2luKGspOiB2IGZvciBrLCB2IGluIHNlbGYud29yZF92b2NhYi5pdGVtcygpfQogICAgc2VsZi5zdWJ3b3JkX3ZvY2FiID0gZGVmYXVsdGRpY3QoaW50KQogICAgIyBBbHNvIGJ1aWxkIGEgY2hhcmFjdGVyLWxldmVsIHZvY2FidWxhcnkgYXMgdGhlIGJhc2Ugc3Vid29yZHMuCiAgICBzZWxmLmNoYXJfdm9jYWIgPSBkZWZhdWx0ZGljdChpbnQpCiAgICBmb3Igc2VudCBpbiBzZW50czoKICAgICAgZm9yIGNoYXIgaW4gbGlzdChzZW50KToKICAgICAgICBzZWxmLmNoYXJfdm9jYWJbY2hhcl0gKz0gMQoKICBkZWYgX2ZpbmRfdG9wX3N1YndvcmQoc2VsZik6CiAgICBzdWJ3b3JkX3BhaXJzID0gZGVmYXVsdGRpY3QoaW50KQogICAgZm9yIHcsIGNudCBpbiBzZWxmLndvcmtpbmdfdm9jYWIuaXRlbXMoKToKICAgICAgc3VidyA9IHcuc3BsaXQoKQogICAgICBmb3IgaSBpbiByYW5nZShsZW4oc3VidykgLSAxKToKICAgICAgICAjIENvdW50IGJpZ3JhbXMuCiAgICAgICAgc3Vid29yZF9wYWlyc1tzdWJ3W2ldLCBzdWJ3W2krMV1dICs9IGNudAogICAgdG9wX3N1YndfcGFpciA9IG1heChzdWJ3b3JkX3BhaXJzLCBrZXk9c3Vid29yZF9wYWlycy5nZXQpCiAgICB0b3Bfc3VidyA9ICIiLmpvaW4odG9wX3N1YndfcGFpcikKICAgIHNlbGYuc3Vid29yZF92b2NhYlt0b3Bfc3Vid10gPSBzdWJ3b3JkX3BhaXJzW3RvcF9zdWJ3X3BhaXJdCiAgICBpZiBzZWxmLnZlcmJvc2U6CiAgICAgIHByaW50KCJOZXcgc3Vid29yZCBhZGRlZDoge30iLmZvcm1hdCh0b3Bfc3VidykpCiAgICByZXR1cm4gdG9wX3N1YndfcGFpcgoKICBkZWYgX21lcmdlKHNlbGYsIHN1YndfcGFpcik6CiAgICBiaWdyYW0gPSByZS5lc2NhcGUoIiAiLmpvaW4oc3Vid19wYWlyKSkKICAgIHAgPSByZS5jb21waWxlKHIiKD88IVxTKSIgKyBiaWdyYW0gKyByIig/IVxTKSIpCiAgICBzZWxmLndvcmtpbmdfdm9jYWIgPSB7cC5zdWIoIiIuam9pbihzdWJ3X3BhaXIpLCB3KTogY250IGZvciB3LCBjbnQgaW4gc2VsZi53b3JraW5nX3ZvY2FiLml0ZW1zKCl9CiAgCiAgZGVmIHVwZGF0ZV9zdWJ3b3JkKHNlbGYsIG5fbWVyZ2U9MSk6CiAgICBmb3IgbiBpbiByYW5nZShuX21lcmdlKToKICAgICAgdG9wX3N1YndfcGFpciA9IHNlbGYuX2ZpbmRfdG9wX3N1YndvcmQoKQogICAgICBzZWxmLl9tZXJnZSh0b3Bfc3Vid19wYWlyKQpgYGAKCkxldCdzIHVzZSBTaGFrZXNwZWFyZSBhcyB0aGUgaW5wdXQgdGV4dCBleGFtcGxlIHRvIHJ1biB0aGUgQlBFIGFsZ29yaXRobS4KRmlyc3Qgd2UgZG93bmxvYWQgdGhlIGRhdGEgZnJvbSBHb29nbGUncyBwdWJsaWMgaG9zdGluZyBzZXJ2aWNlOgoKYGBge2Jhc2ggbWtkaXJ9CiMgYmFzaApta2RpciAtcCBkYXRhCmBgYAoKYGBge3B5dGhvbiBicGVfZGxfc2hha2VzcGVhcmV9CmltcG9ydCB3YXJuaW5ncwp3YXJuaW5ncy5zaW1wbGVmaWx0ZXIoYWN0aW9uPSJpZ25vcmUiLCBjYXRlZ29yeT1GdXR1cmVXYXJuaW5nKQoKaW1wb3J0IG9zCmltcG9ydCBzaHV0aWwKaW1wb3J0IHRlbnNvcmZsb3cgYXMgdGYKCnNoYWtlc19maWxlID0gImRhdGEvc2hha2VzcGVhcmUudHh0IgppZiBub3Qgb3MucGF0aC5leGlzdHMoc2hha2VzX2ZpbGUpOgogIHNoYWtlc19kbF9wYXRoID0gdGYua2VyYXMudXRpbHMuZ2V0X2ZpbGUoCiAgICAic2hha2VzcGVhcmUudHh0IiwKICAgICJodHRwczovL3N0b3JhZ2UuZ29vZ2xlYXBpcy5jb20vZG93bmxvYWQudGVuc29yZmxvdy5vcmcvZGF0YS9zaGFrZXNwZWFyZS50eHQiKQogIHNodXRpbC5tb3ZlKHNoYWtlc19kbF9wYXRoLCBzaGFrZXNfZmlsZSkKc2hha2VzcGVhcmUgPSBvcGVuKHNoYWtlc19maWxlLCAicmIiKS5yZWFkKCkuZGVjb2RlKGVuY29kaW5nPSJ1dGYtOCIpCnNoYWtlc3BlYXJlID0gc2hha2VzcGVhcmUubG93ZXIoKS5zcGxpdCgiXG4iKQoKIyBQcmludCB0aGUgZmlyc3QgZmV3IGxpbmVzLgpmb3Igc2VudCBpbiBzaGFrZXNwZWFyZVs6MjBdOgogIHByaW50KHNlbnQpCmBgYAoKYGBge3B5dGhvbiBicGVfc2hha2VzcGVhcmV9CmJwZSA9IEJQRShzaGFrZXNwZWFyZSwgbWluX2NudD0xMCkKcHJpbnQobGVuKGJwZS53b3JkX3ZvY2FiKSkKCnByaW50KGxpc3QoYnBlLndvcmRfdm9jYWIuaXRlbXMoKSlbOjVdKSAgIyBQcmludCBzb21lIGZyb20gZnVsbHdvcmQgdm9jYWJ1bGFyeS4KCnByaW50KGxpc3QoYnBlLndvcmtpbmdfdm9jYWIuaXRlbXMoKSlbOjVdKSAgIyAoRm9yIGRlYnVnKSBQcmludCBzb21lIGZyb20gdGhlIHdvcmtpbmcgdm9jYWIgdGhhdCB3ZSBhcmUgZ29pbmcgdG8gcGVyZm9ybSB0aGUgbWVyZ2UuCgpicGUudXBkYXRlX3N1YndvcmQobl9tZXJnZT0xMDApICAjIERvIG1lcmdlIHVwZGF0ZS4KcHJpbnQobGVuKGJwZS5zdWJ3b3JkX3ZvY2FiKSkKCnByaW50KGxpc3QoYnBlLndvcmtpbmdfdm9jYWIuaXRlbXMoKSlbOjVdKSAgIyBDaGVjayB0aGUgd29ya2luZyB2b2NhYnVsYXJ5IGFmdGVyIG1lcmdlLgoKcHJpbnQobGlzdChicGUuc3Vid29yZF92b2NhYi5pdGVtcygpKVs6NV0pICAjIFByaW50IHNvbWUgc3Vid29yZHMgZ2VuZXJhdGVkIGJ5IHRoZSBmaXJzdCAxMDAgbWVyZ2Ugb3BlcmF0aW9ucy4KYGBgCgpFYWNoIGxlYXJuZWQgc3Vid29yZCBpdHNlbGYgZG9lc24ndCBuZWVkIHRvIGhhdmUgZXhwbGljaXQgbWVhbmluZy4KSXQgaXMgdGhlaXIgcG90ZW50aWFsIGNvbWJpbmF0aW9ucyB3aXRoIG90aGVyIHN1YndvcmRzIHRoYXQgcmV2ZWFsIHRoZSBhY3R1YWwgY29udGV4dCwKd2hpY2ggY2FuIGJlIGhvcGVmdWxseSBsZWFybmVkIGJ5IG5ldXJhbCBuZXR3b3JrIGVtYmVkZGluZ3MgZm9yIHRoZSBkb3duc3RyZWFtIHRhc2suCgpBIG1vZGlmaWNhdGlvbiBvZiB0aGUgQlBFIGFsZ29yaXRobSBpcyB0byBleHBhbmQgc3Vid29yZCB2b2NhYnVsYXJ5IGJ5IGxpa2VsaWhvb2QgaW5zdGVhZCBvZiB0aGUgbW9zdCBmcmVxdWVudCBwYWlyLgpUaGF0IGlzLAppbiBlYWNoIG1lcmdlIG9wZXJhdGlvbiB3ZSBjaG9vc2UgdGhlIG5ldyBzdWJ3b3JkIHRoYXQgaW5jcmVhc2VzIHRoZSBsaWtlbGlob29kIG9mIHRoZSB0cmFpbmluZyBkYXRhIHRoZSBtb3N0LgoKIyMgIFByb2JhYmxpc3RpYyBTdWJ3b3JkIFNlZ21lbnRhdGlvbgoKTm90ZSB0aGF0IEJQRSBpcyAqZGV0ZXJtaW5pc3RpYyogYnkgYSBncmVlZHkgcmVwbGFjZW1lbnQgb2YgYWRqYWNlbnQgc3ltYm9scywKd2hpbGUgdGhlIGFjdHVhbCBzdWJ3b3JkIHNlZ21lbnRhdGlvbiBpcyBwb3RlbnRpYWxseSBhbWJpZ3VvdXMuCkEgc2VxdWVuY2UgY2FuIGJlIHJlcHJlc2VudGVkIGluIG11bHRpcGxlIHN1YndvcmQgc2VxdWVuY2VzIHNpbmNlIGV2ZW4gdGhlIHNhbWUgd29yZCBjYW4gYmUgc2VnbWVudGVkIGRpZmZlcmVudGx5IGJ5IGRpZmZlcmVudCBzdWJ3b3Jkcy4KCkBrdWRvMjAxOHN1YndvcmQgcHJvcG9zZSBhbiBhcHByb2FjaCBjYWxsZWQgInN1YndvcmQgcmVndWxhcml6YXRpb24iIHRvIGhhbmRsZSB0aGlzIGlzc3VlIGluIGEgcHJvYmFibGlzdGljIGZhc2hpb24uCkEgbmV3IHN1YndvcmQgc2VnbWVudGF0aW9uIHVuZGVyIHRoaXMgYXBwcm9hY2ggdXNlcyBhICp1bmlncmFtIGxhbmd1YWdlIG1vZGVsKiBmb3IgZ2VuZXJhdGluZyBzdWJ3b3JkIHNlZ21lbnRhdGlvbiB3aXRoIGF0dGFjaGVkIHByb2JhYmlsaXR5LgoKRGVub3RlICRYJCBhcyBhIHN1YndvcmQgc2VxdWVuY2Ugb2YgbGVuZ3RoICRuJAoKJCQKWCA9IFxiZWdpbntwbWF0cml4fSB4XzEgJiB4XzIgJiBcZG90cyAmIHhfblxlbmR7cG1hdHJpeH0sCiQkCgp3aXRoICppbmRlcGVuZGVudCogc3Vid29yZCBwcm9iYWJpbGl0eSAkUCh4X2kpJC4KVGhlIHByb2JhYmlsaXR5IG9mIHRoZSBzZXF1ZW5jZSBoZW5jZSBpcyBzaW1wbHkKCiQkClAoWCkgPSBccHJvZF97aT0xfV5uIFAoeF9pKSwKJCQKCndpdGgKCiQkClxzdW1fe3ggXGluIFZ9UCh4KSA9IDEsCiQkCgp3aGVyZSAkViQgaXMgYSBwcmUtZGV0ZXJtaW5lZCB2b2NhYnVsYXJ5IHNldC4KClRoZSBtb3N0IHByb2JhYmxlIHNlcXVlbmNlIChmcm9tIGFsbCBwb3NzaWJsZSBzZWdtZW50YXRpb24gY2FuZGlkYXRlcykgY2FuIGJlIGZvdW5kIGJ5IGFwcGx5aW5nIFtleHBlY3RhdGlvbi1tYXhpbWl6YXRpb25dKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0V4cGVjdGF0aW9uJUUyJTgwJTkzbWF4aW1pemF0aW9uX2FsZ29yaXRobSkgd2l0aCB0aGUgW1ZpdGVyYmkgYWxnb3JpdGhtXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9WaXRlcmJpX2FsZ29yaXRobSksCndoaWNoIGlzIGRlc2lnbmVkIGZvciBmaW5kaW5nIHRoZSBtb3N0IGxpa2VseSBzZXF1ZW5jZSBvZiBoaWRkZW4gc3RhdGVzLgpIZXJlIHRoZSBoaWRkZW4gc3RhdGVzIGFyZSBzdWJ3b3JkcyB1c2VkIGluIGEgc2VxdWVuY2UuCgpUaGUgdm9jYWJ1bGFyeSBzZXQgJFYkIGlzIGluZGVlZCB1bmRlcnRlcm1pbmVkIGF0IHRoZSB2ZXJ5IGJlZ2lubmluZyBhbmQgY2Fubm90IGJlIHNvbHZlZCBzaW11bHRhbmVvdXNseSB3aXRoIHRoZSBtYXhpbWl6YXRpb24gdGFzay4KQSB3b3JrYXJvdW5kIGlzIHRvIHByb3ZpZGUgYSBzZWVkIHZvY2FidWxhcnkgYXMgdGhlIGluaXRpYWwgKHJlYXNvbmFibHkgYmlnIGVub3VnaCkgdm9jYWJ1bGFyeSwKYW5kIHNocmluayB0aGUgc2VlZCB2b2NhYnVsYXJ5IGR1cmluZyB0cmFpbmluZyB1bnRpbCBhIGRlc2lyZWQgdm9jYWJ1bGFyeSBzaXplIGlzIHJlYWNoZWQuCgpUbyBmdWxseSB1bmRlcnN0YW5kIHRoZSB1bmlncmFtIGxhbmd1YWdlIG1vZGVsIGZvciBzZWdtZW50YXRpb24sCmxldCdzIGV4YW1pbmUgdGhlIGV4cGVjdGF0aW9uLW1heGltaXphdGlvbiBhbmQgVml0ZXJiaSBhbGdvcml0aG0gYWNjb3JkaW5nbHkuCgojIyMgRXhwZWN0YXRpb24tTWF4aW1pemF0aW9uCgpRdW90ZWQgZnJvbSBbd2lraSBwYWdlIG9mIEVNXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9FeHBlY3RhdGlvbiVFMiU4MCU5M21heGltaXphdGlvbl9hbGdvcml0aG0pOgoKPgpJbiBzdGF0aXN0aWNzLCBhbiBleHBlY3RhdGlvbuKAk21heGltaXphdGlvbiAoRU0pIGFsZ29yaXRobSBpcyBhbiBpdGVyYXRpdmUgbWV0aG9kIHRvIGZpbmQgbWF4aW11bSBsaWtlbGlob29kIG9yIG1heGltdW0gYSBwb3N0ZXJpb3JpIChNQVApIGVzdGltYXRlcyBvZiBwYXJhbWV0ZXJzIGluIHN0YXRpc3RpY2FsIG1vZGVscywgd2hlcmUgdGhlIG1vZGVsIGRlcGVuZHMgb24gdW5vYnNlcnZlZCBsYXRlbnQgdmFyaWFibGVzLgoKSXQncyBiZXR0ZXIgdG8gdW5kZXJzdGFuZCB0aGUgRU0gd2l0aCBhIHdvcmtpbmcgZXhhbXBsZS4KCldlIHVzZSBhIHdvcmtpbmcgZXhhbXBsZSBvZiBhIEdhdXNzaWFuIE1peHR1cmUgdG8gaWxsdXN0cmF0ZSB0aGUgY29yZSBpZGVhIG9mIEVNLgpBIEdhdXNzaWFuIE1peHR1cmUgbW9kZWwgaXMgbW9yZSBvZiBhIEhlbGxvLVdvcmxkIGV4YW1wbGUgb2YgRU0uCkluIGl0cyBzaW1wbGVzdCBpbGx1c3RyYXRpb24sCmFzc3VtZSB3ZSBvYnNlcnZlIGEgc2V0IG9mIGRhdGEgcmVzdWx0aW5nIGZyb20gZWl0aGVyIG9uZSBvZiB0d28gZGlmZmVyZW50IEdhdXNzaWFucywKaG93IGNhbiB3ZSBlc3RpbWF0ZSBwYXJhbWV0ZXJzIG9mIHRoZXNlIHR3byBHYXVzc2lhbnMgd2l0aG91dCBrbm93aW5nIHdoaWNoIEdhdXNzaWFuIHRoZXkgY2FtZSBmcm9tP15bV2UgYm9ycm93IHRoZSBleGFtcGxlIGZyb20gW3RoaXMgc3RhY2stb3ZlcmZsb3cgdGhyZWFkXShodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8xMTgwODA3NC93aGF0LWlzLWFuLWludHVpdGl2ZS1leHBsYW5hdGlvbi1vZi10aGUtZXhwZWN0YXRpb24tbWF4aW1pemF0aW9uLXRlY2huaXF1ZS80MzU2MTMzOSM0MzU2MTMzOSksIHdpdGggYSBtb2RpZmljYXRpb24gdG8gdXNlIGEgZ2VuZXJhbCBpbml0aWFsIGd1ZXNzIGJ5IHJhbmRvbWl6aW5nIHRoZSBoaWRkZW4gc3RhdGUgd2VpZ2h0cy5dCgpGaXJzdCBjcmVhdGUgc29tZSB0b3kgZGF0YToKCmBgYHtweXRob24gZW1fdG95X2RhdGF9CmltcG9ydCBudW1weSBhcyBucAoKbnAucmFuZG9tLnNlZWQoNzc3KQpzaXplID0gMTAwCgpnMV9tZWFuID0gMwpnMl9tZWFuID0gNwpnMV9zdGQgPSAxCmcyX3N0ZCA9IDIKCmcxX2RhdGEgPSBucC5yYW5kb20ubm9ybWFsKGcxX21lYW4sIGcxX3N0ZCwgc2l6ZT1zaXplKQpnMl9kYXRhID0gbnAucmFuZG9tLm5vcm1hbChnMl9tZWFuLCBnMl9zdGQsIHNpemU9c2l6ZSkKbWl4dHVyZV9kYXRhID0gbnAuY29uY2F0ZW5hdGUoW2cxX2RhdGEsIGcyX2RhdGFdKQpgYGAKClRoZSBmb2xsb3dpbmcgcGxvdCBpbGx1c3RyYXRlcyBvdXIgdG95IGRhdGEgZGlzdHJpYnV0aW9uLgpUaGUgcmVzdWx0aW5nIGRpc3RyaWJ1dGlvbiBpcyBhIGVxdWFsIGNvbWJpbmF0aW9uIG9mIHR3byBHYXVzc2lhbnMgcGxvdHRlZCBhcyBkb3R0ZWQgY3VydmVzLgoKYGBge3IgZW1fdG95X2RhdGFfaGlzdCwgcmVzdWx0cz0iaG9sZCJ9CiMgUgp4IDwtIHB5JG1peHR1cmVfZGF0YQpoaXN0KHgsIHhsYWI9IkRhdGEiLCBtYWluPSJTYW1wbGUgRGF0YSBmcm9tIGEgR2F1c3NpYW4gTWl4dHVyZSIsCiAgICAgcHJvYmFiaWxpdHk9VFJVRSwgeWxpbT1jKDAsIC40NSkpCmN1cnZlKGRub3JtKHgsIG1lYW49cHkkZzFfbWVhbiwgc2Q9cHkkZzFfc3RkKSwKICAgICAgY29sPSJibHVlIiwgbHR5PSJkb3R0ZWQiLCBhZGQ9VFJVRSwgeWF4dD0ibiIpCmN1cnZlKGRub3JtKHgsIG1lYW49cHkkZzJfbWVhbiwgc2Q9cHkkZzJfc3RkKSwKICAgICAgY29sPSJyZWQiLCBsdHk9ImRvdHRlZCIsIGFkZD1UUlVFLCB5YXh0PSJuIikKYGBgCgpUbyBmb3JtdWxhdGUgdGggRU0gYWxnb3JpdGhtLAp3ZSBmaXJzdCBpZGVudGlmeSB3aGF0IGlzIHRoZSBoaWRkZW4gc3RhdGUgKHVub2JzZXJ2ZWQvbGF0ZW50IHZhcmlhYmxlKSBpbiBhIGdpdmVuIG1vZGVsaW5nIHRhc2suCkluIHRoZSBHYXVzc2FpbiBNaXh0dXJlIGV4YW1wbGUsCnRoZSBoaWRkZW4gc3RhdGUgKGZvciBlYWNoIG9ic2VydmVkIGRhdGEgcG9pbnQpIGlzIHRoZSBpbmRpY2F0b3Igb2YgdGhlIG9yaWdpbmFsIEdhdXNzaWFuIHRoYXQgZ2VuZXJhdHMgdGhlIGRhdGEuCgpUaGUgRU0gYWxnb3JpdGhtIGNvbnRhaW5zIHR3byBzdGVwIGluIG9uZSBpdGVyYXRpb246CgoxLiBbRXhwZWN0YXRpb24gU3RlcF0gRm9ybXVsYXRlIHRoZSAqZXhwZWN0ZWQqIG1heGltdW0gbGlrZWxpaG9vZCBiYXNlZCBvbiBhIHByb2JhYmlsaXN0aWMgZGlzdHJpYnV0aW9uIG9mIGhpZGRlbiBzdGF0ZTsgdGhhdCBpcywgYW4gZXhwZWN0ZWQgbWF4aW11bSBsaWtlbGlob29kIGVzdGltYXRvciAoTUxFKSBjb25kaXRpb25hbCBvbiBoaWRkZW4gc3RhdGVzCjIuIFtNYXhpbWl6YXRpb24gU3RlcF0gRmlndXJlIG91dCBtb2RlbCBwYXJhbWV0ZXJzIGJ5IG1heGltaXppbmcgdGhlIGV4cGVjdGVkIE1MRQoKVGhlIHNvbHV0aW9uIHRvIG1vZGVsIHBhcmFtZXRlcnMgY2FuIGJlIGZvdW5kICh3aXRob3V0IGV2ZW4ga25vd2luZyB0aGUgaGlkZGVuIHN0YXRlIGJ1dCBvbmx5IGl0cyBleHBlY3RhdGlvbikgYWZ0ZXIgc2VydmVyYWwgaXRlcmF0aW9ucy4KCkhlcmUgaXMgYSBzaW1wbGUgRU0gaW1wbGVtZW50YXRpb24gZm9yIGEgR3Vhc3NpYW4gTWl4dHVyZSBtb2RlbDoKCmBgYHtweXRob24gZW19CmZyb20gc2NpcHkgaW1wb3J0IHN0YXRzCgoKY2xhc3MgR2F1c3NpYW5NaXh0dXJlRU06CgogIGRlZiBfX2luaXRfXyhzZWxmLCBkYXRhKToKICAgIHNlbGYuZGF0YSA9IGRhdGEKICAgICMgSW5pdGlhbGl6ZSBoaWRkZW4gc3RhdGUgcHJvYmFiaWxpc3RpYyB3ZWlnaHQuCiAgICAjIE5vdGUgdGhhdCB0aGVyZSBpcyBubyBndWFyYW50ZWUgdGhhdCBnMSB3aWxsIGNvcnJlc3BvbmQgdG8gdGhlIGZpcnN0IGhhbGYgb2YgdGhlIGRhdGEuCiAgICAjIFRoZSBhY3R1YWwgZXN0aW1hdGVkIGFzc2lnbm1lbnQgaXMgZGV0ZXJtaW5lZCBieSB0aGUgZmluYWwgd2VpZ2h0cy4KICAgIHNlbGYuZzFfd2VpZ2h0cyA9IG5wLnJhbmRvbS51bmlmb3JtKHNpemU9bGVuKGRhdGEpKQogICAgc2VsZi5nMl93ZWlnaHRzID0gMSAtIHNlbGYuZzFfd2VpZ2h0cwogICAgIyBJbml0aWFsaXplIHBhcmFtZXRlciBndWVzc2luZ3MuCiAgICBzZWxmLnVwZGF0ZV9lc3RpbWF0ZXMoKQoKICBkZWYgdXBkYXRlX2VzdGltYXRlcyhzZWxmKToKICAgIHNlbGYuZzFfbWVhbiA9IHNlbGYuZXN0aW1hdGVfbWVhbihzZWxmLmcxX3dlaWdodHMpCiAgICBzZWxmLmcyX21lYW4gPSBzZWxmLmVzdGltYXRlX21lYW4oc2VsZi5nMl93ZWlnaHRzKQogICAgc2VsZi5nMV9zdGQgPSBzZWxmLmVzdGltYXRlX3N0ZChzZWxmLmcxX21lYW4sIHNlbGYuZzFfd2VpZ2h0cykKICAgIHNlbGYuZzJfc3RkID0gc2VsZi5lc3RpbWF0ZV9zdGQoc2VsZi5nMl9tZWFuLCBzZWxmLmcyX3dlaWdodHMpCgogIGRlZiBlc3RpbWF0ZV9tZWFuKHNlbGYsIHdlaWdodHMpOgogICAgICByZXR1cm4gbnAuc3VtKHNlbGYuZGF0YSAqIHdlaWdodHMpIC8gbnAuc3VtKHdlaWdodHMpCgogIGRlZiBlc3RpbWF0ZV9zdGQoc2VsZiwgbWVhbiwgd2VpZ2h0cyk6CiAgICAgIHZhcmlhbmNlID0gbnAuc3VtKHdlaWdodHMgKiAoc2VsZi5kYXRhIC0gbWVhbikqKjIpIC8gbnAuc3VtKHdlaWdodHMpCiAgICAgIHJldHVybiBucC5zcXJ0KHZhcmlhbmNlKQoKICBkZWYgZW0oc2VsZiwgbl9pdGVyPTEwKToKICAgIGZvciBuIGluIHJhbmdlKG5faXRlcik6CiAgICAgIGcxX2xpayA9IHN0YXRzLm5vcm0oc2VsZi5nMV9tZWFuLCBzZWxmLmcxX3N0ZCkucGRmKHNlbGYuZGF0YSkKICAgICAgZzJfbGlrID0gc3RhdHMubm9ybShzZWxmLmcyX21lYW4sIHNlbGYuZzJfc3RkKS5wZGYoc2VsZi5kYXRhKQogICAgICBzZWxmLmcxX3dlaWdodHMgPSBnMV9saWsgLyAoZzFfbGlrICsgZzJfbGlrKQogICAgICBzZWxmLmcyX3dlaWdodHMgPSBnMl9saWsgLyAoZzFfbGlrICsgZzJfbGlrKQogICAgICBzZWxmLnVwZGF0ZV9lc3RpbWF0ZXMoKQoKCm1peF9tb2RlbCA9IEdhdXNzaWFuTWl4dHVyZUVNKG1peHR1cmVfZGF0YSkKbWl4X21vZGVsLmVtKDMwKQpgYGAKCmBgYHtweXRob24gZW1fcmVzdWx0fQp0cnVlX3BhcmFtcyA9IChnMV9tZWFuLCBnMl9tZWFuLCBnMV9zdGQsIGcyX3N0ZCkKbWxlX3BhcmFtcyA9ICgKICBucC5tZWFuKG1peHR1cmVfZGF0YVs6c2l6ZV0pLAogIG5wLm1lYW4obWl4dHVyZV9kYXRhW3NpemU6XSksCiAgbnAuc3RkKG1peHR1cmVfZGF0YVs6c2l6ZV0pLAogIG5wLnN0ZChtaXh0dXJlX2RhdGFbc2l6ZTpdKQopCiMgVG8gZW5zdXJlIHRoZSBvcmRlcmluZyBzbyB3ZSBjYW4gaGF2ZSBhIG5pY2VseSBwcmludGVkIGNvbXBhcmlzb24uCmVtX3BhcmFtcyA9IG5wLmNvbmNhdGVuYXRlKFsKICBucC5zb3J0KFttaXhfbW9kZWwuZzFfbWVhbiwgbWl4X21vZGVsLmcyX21lYW5dKSwKICBucC5zb3J0KFttaXhfbW9kZWwuZzFfc3RkLCBtaXhfbW9kZWwuZzJfc3RkXSkKXSkKCgpmb3IgaSwgKHQsIG0sIGUpIGluIGVudW1lcmF0ZSh6aXAodHJ1ZV9wYXJhbXMsIG1sZV9wYXJhbXMsIGVtX3BhcmFtcykpOgogIGlmIGkgPT0gMDoKICAgIHByaW50KCJUcnVlIFZhbHVlIHwgTUxFIChLbm93biBIaWRkZW4gU3RhdGVzKSB8IEVNIChVbmtub3duIEhpZGRlbiBTdGF0ZXMpIikKICBwcmludCgiezpeMTF9fHs6XjI3fXx7Ol4yN30iLmZvcm1hdCh0LCBucC5yb3VuZChtLCA0KSwgbnAucm91bmQoZSwgNCkpKQpgYGAKCkFzIG9uZSBjYW4gc2VlLAp3ZSBhcmUgYWJsZSB0byBwcm9wZXJseSBlc3RpbWF0ZSB0aGUgbW9kZWwgcGFyYW1ldGVycyAoaW4gdGhpcyBjYXNlIHRoZSBmaXJzdCBhbmQgc2Vjb25kIG1vbWVudHMgZm9yIHR3byBkaWZmZXJlbnQgR2F1c3NpYW5zKSB3aXRob3V0IGtub3dpbmcgdGhlIGhpZGRlbiB2YXJpYWJsZXMgKHRoZSBpbmRpY2F0b3Igb2Ygd2hpY2ggZGF0YSBwb2ludCBjb21lcyBmcm9tIHdoaWNoIEdhdXNzaWFuKS4KCiMjIyBWaXRlcmJpIEFsZ29yaXRobQoKVG8gYXBwbHkgRU0gdG8gb3VyIHN1YndvcmQgc2VnbWVudGF0aW9uIHByb2JsZW0sCndlIGNhbiB1c2UgdGhlIFZpdGVyYmkgYWxnb3JpdGhtIGluIHRoZSBtYXhpbWl6YXRpb24gc3RlcCB0byBmaW5kIG91dCB0aGUgbW9zdCBwcm9iYWJsZSBzZXF1ZW5jZSBvZiBzdWJ3b3Jkcy4KCkxldCdzIHVzZSB0aGUgU2hha2VzcGVhcmUgdGV4dCB0byBpbGx1c3RyYXRlIHRoZSBWaXRlcmJpIGFsZ29yaXRobS4KRm9yIGEgZ2l2ZW4gdGV4dCBsaW5lIHdlJ2QgbGlrZSB0byBjYWxjdWxhdGUgdGhlIG1vc3QgcHJvYmFibGUgc3Vid29yZHMgdGhhdCBjYW4gcmVjb3ZlciB0aGUgdGV4dCBsaW5lLgpJbiBvcmRlciB0byBhY2hpZXZlIHRoYXQgd2UgbmVlZCB0byBoYXZlIGEgdm9jYWJ1bGFyeSBmb3Igc3Vid29yZHMgdGhhdCBjYW4gYXR0cmlidXRlIGVhY2ggc3Vid29yZCB0byBhIHByb2JhYmlsaXR5LgpXZSBjYW4gdXNlIEJQRSB0byBidWlsZCBzdWNoIHZvY2FidWxhcnkuCkZvciBjb21wbGV0ZSBjb3ZlcmFnZSB3ZSB3aWxsIGFsc28gaW5jbHVkZSBjaGFyYWN0ZXItbGV2ZWwgc3Vid29yZHMgaW50byB0aGUgdm9jYWJ1bGFyeS4KCmBgYHtweXRob24gdml0ZXJiaV9zZWVkfQojIEJ1aWxkIGEgc2VlZGluZyBzdWJ3b3JkIHZvY2FidWxhcnkgdXNpbmcgQlBFLgojIEZvciBpbGx1c3RyYXRpb24gcHVycG9zZSB3ZSBkaWRuJ3QgZ28gZm9yIGEgaHVnZSBzZWVkaW5nIHNpemUuCiMgSW4gYWN0dWFsIGFwcGxpY2F0aW9uIHRoZSBzZWVkaW5nIHZvY2FidWxhcnkgc2hvdWxkIGJlIG11Y2ggbGFyZ2VyIHRoYW4gd2hhdCB3ZSBoYXZlIGhlcmUuCmJwZSA9IEJQRShzaGFrZXNwZWFyZSwgbWluX2NudD0xMCkKYnBlLnVwZGF0ZV9zdWJ3b3JkKG5fbWVyZ2U9MTAwMCkKc3Vid29yZF9jbnQgPSB7KipicGUuY2hhcl92b2NhYiwgKipicGUuc3Vid29yZF92b2NhYn0KdG90X2NudCA9IG5wLnN1bShsaXN0KHN1YndvcmRfY250LnZhbHVlcygpKSkKCiMgVGhlIGluaXRpYWwgZXN0aW1hdGUgb2Ygc3Vid29yZCBwcm9iYWJpbGl0eS4Kc3Vid29yZF9sb2dwID0ge2s6IG5wLmxvZyh2IC8gdG90X2NudCkgZm9yIGssIHYgaW4gc3Vid29yZF9jbnQuaXRlbXMoKX0KYGBgCgpBIFZpdGVyYmkgcHJvY2VkdXJlIGNvbnRhaW5zIHR3byBzdGVwczoKYSBmb3J3YXJkIHN0ZXAgYW5kIGEgYmFja3dhcmQgc3RlcC4KVGhlIGZvcndhcmQgc3RlcCBpcyB0byBkZXRlcm1pbmUgdGhlIG1vc3QgcHJvYmFibGUgc3Vid29yZCBnaXZlbiBlYWNoIGVuZC1vZi1jaGFyYWN0ZXIgcG9zaXRpb24gaW4gYSB3b3JkLgpUaGUgYmFja3dhcmQgc3RlcCBpcyB0byByZWNvdmVyIHRoZSBlbnRpcmUgc3Vid29yZCBzZXF1ZW5jZSB0aGF0IG1heGltaXplcyB0aGUgbGlrZWxpaG9vZCBvZiB0aGUgc2VxdWVuY2UuCgojIyMjIFRoZSBGb3J3YXJkIFN0ZXAgey19CgpgYGB7cHl0aG9uIHZpdGVyYmlfZm9yd2FyZH0KZGVmIHZpdGVyYmlfZm9yd2FyZCh3b3JkLCBzdWJ3b3JkX2xvZ3ApOgogICIiIkZvcndhcmQgc3RlcCBvZiBWaXRlcmJpIGdpdmVuIGEgc2luZ2xlIHdvcmQuIiIiCiAgIyBDcmVhdGUgc3RvcmFnZSBhcnJheSBmb3IgYmVzdCBzdWJzdHJpbmcgcmVjb3JkZWQgYXQgZWFjaCBlbmQtb2YtY2hhcmFjdGVyIHBvc2l0aW9uLgogIGJlc3Rfc3Vid19zbGljZXMgPSAgW05vbmVdICogKGxlbih3b3JkKSArIDEpCiAgbmVnX2xvZ2xpayA9IG5wLnplcm9zKGxlbih3b3JkKSArIDEpCiAgIyBTZWFyY2ggdGhlIGJlc3Qgc3Vic3RyaW5nIGdpdmVuIGV2ZXJ5IHBvc3NpYmxlIGVuZCBvZiBwb3NpdGlvbiBhbG9uZyB0aGUgd29yZC4KICBmb3IgZW93IGluIHJhbmdlKDEsIGxlbih3b3JkKSArIDEpOgogICAgIyBGb3IgZXZlcnkgZW5kLW9mLXdvcmQgcG9zaXRpb246CiAgICBuZWdfbG9nbGlrW2Vvd10gPSBucC5pbmYKICAgIGZvciBib3cgaW4gcmFuZ2UoZW93KToKICAgICAgIyBGb3IgZXZlcnkgcG9zc2libGUgYmVnaW5uaW5nLW9mLXdvcmQgcG9zaXRpb24gZ2l2ZW4gdGhlIGVuZC1vZi13b3JkIHBvc2l0aW9uOgogICAgICBzdWJ3ID0gbGluZVtib3c6ZW93XQogICAgICBpZiBzdWJ3IGluIHN1YndvcmRfbG9ncDoKICAgICAgICBsb2dwID0gc3Vid29yZF9sb2dwW3N1YnddCiAgICAgICAgIyBDb21wdXRlIHN1YndvcmQgcHJvYmFiaWxpdHk6CiAgICAgICAgIyBQKGN1cnJlbnQgc2VnbWVudCkgKyBQKHRoZSBiZXN0IHNlZ21lbnQganVzdCBiZWZvcmUgdGhlIGN1cnJlbnQgc2VnbWVudCkuCiAgICAgICAgcyA9IG5lZ19sb2dsaWtbYm93XSAtIGxvZ3AKICAgICAgICBpZiBzIDwgbmVnX2xvZ2xpa1tlb3ddOgogICAgICAgICAgbmVnX2xvZ2xpa1tlb3ddID0gcwogICAgICAgICAgYmVzdF9zdWJ3X3NsaWNlc1tlb3ddID0gKGJvdywgZW93KQogIHJldHVybiBuZWdfbG9nbGlrLCBiZXN0X3N1Yndfc2xpY2VzCmBgYAoKV2UgZGVtb25zdHJhdGUgdGhlIHNlZ21lbnRhdGlvbiBnaXZlbiBhIHNpbmdsZSBmdWxsd29yZDoKCmBgYHtweXRob24gdml0ZXJiaV9mb3J3YXJkX2RlbW99CiMgVGFrZSBhIHRleHQgbGluZSBmcm9tIFNoYWtlc3BlYXJlLgpsaW5lID0gc2hha2VzcGVhcmVbMjEzXQp3b3JkcyA9IGxpbmUuc3BsaXQoKQoKdyA9IHdvcmRzWzBdCnByaW50KHcpICAjIFdvcmQgZm9yIHNlZ21lbnRhdGlvbiBkZW1vLgoKc3Vid19zY29yZXMsIHN1Yndfc2xpY2VzID0gdml0ZXJiaV9mb3J3YXJkKHcsIHN1YndvcmRfbG9ncCkKcHJpbnQoc3Vid19zbGljZXMpICAjIEJlc3Qgc2VnbWVudCBpbmRpY2VzIGF0IGVhY2ggc3Vid29yZCBlbmQuCgpzdWJ3ID0gW3dbaWR4WzBdOmlkeFsxXV0gZm9yIGlkeCBpbiBzdWJ3X3NsaWNlcyBpZiBpZHggaXMgbm90IE5vbmVdCnByaW50KHN1YncpICAjIEJlc3Qgc2VnbWVudGVkIHN1YndvcmQgYXQgZWFjaCBzdWJ3b3JkIGVuZC4KYGBgCgpMZXQncyB2aXN1YWxpemUgdGhlIHNlZ21lbnRhdGlvbiEKClRoZSBudW1iZXIgYmVuZWF0aCB0aGUgYSBzdWJ3b3JkIGlzIHRoZSBjb3JyZXNwb25kaW5nIG5lZ2F0aXZlIGxvZy1saWtlbGlob29kIGZvciB0aGF0IHN1YndvcmQgdXAgdG8gdGhlIGNvcnJlc29uZGluZyBlbmQtb2YtY2hhcmFjdGVyIHBvc2l0aW9uLgpUaGUgbG93ZXIgdGhlIGJldHRlci4KCjxkaXYgY2xhc3M9ImZvbGQgcyI+CmBgYHtyIHZpdGVyYmlfc2VnbWVudF9wbG90LCByZXN1bHRzPSJob2xkIn0KIyBSCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShkYXRhLnRhYmxlKQoKc3Vid19kZW1vIDwtIHJiaW5kbGlzdChweSRzdWJ3X3NsaWNlcykKc2V0bmFtZXMoc3Vid19kZW1vLCBjKCJzdGFydCIsICJlbmQiKSkKc3Vid19kZW1vIDwtIHN1YndfZGVtb1ssIHN1Ync6PXB5JHN1YnddCnN1YndfZGVtbyA8LSByYmluZChsaXN0KDAsIDAsICIiKSwgc3Vid19kZW1vKSAgIyBUbyBwbG90IG9uZSBtb3JlIGJsb2NrIGF0IHRoZSB0b3AuCnN1YndfZGVtbyA8LSByYmluZChzdWJ3X2RlbW8sIGxpc3QoMCwgMCwgIl8iKSkgICMgVG8gcGxvdCBvbmUgbW9yZSBibG9jayBhdCB0aGUgYm90dG9tLgpzdWJ3X2RlbW8gPC0gc3Vid19kZW1vWywgc3Vidzo9ZmFjdG9yKHN1YncsIGxldmVscz1yZXYoc3VidykpXQpzdWJ3X2RlbW8gPC0gc3Vid19kZW1vWywgc2NvcmU6PWMocHkkc3Vid19zY29yZXMsIDApXQoKIyBUbyBhbHNvIHBsb3QgdGhlIGVudGlyZSB3b3JkIGJ5IGNoYXJhY3Rlci4Kd19kZiA8LSBkYXRhLnRhYmxlKHg9cmVwKCIiLCBucm93KHN1YndfZGVtbykgLSAyKSwgeT0wOihucm93KHN1YndfZGVtbyktMykgKyAuNSwKICAgICAgICAgICAgICAgICAgIGxhYmVsPXVubGlzdChzdHJzcGxpdChweSR3LCBOVUxMKSkpCgp2aXRlcmJpX2RlbW9fcCA8LSBnZ3Bsb3Qoc3Vid19kZW1vLCBhZXMoeD1zdWJ3LCB5PShzdGFydCArIGVuZCkgLyAyLCBsYWJlbD1zdWJ3KSkgKwogIGdlb21fbGluZXJhbmdlKGFlcyh4PXN1YncsIHltaW49c3RhcnQsIHltYXg9ZW5kKSwgc2l6ZT0xLjEpICsKICBnZW9tX3RleHQodmp1c3Q9MSwgc2l6ZT03KSArCiAgZ2VvbV90ZXh0KGRhdGE9c3Vid19kZW1vWzI6KG5yb3coc3Vid19kZW1vKSAtIDEpXSwKICAgICAgICAgICAgYWVzKGxhYmVsPXNwcmludGYoIiglLjJmKSIsIHNjb3JlKSksIHZqdXN0PTMuNSwgc2l6ZT0zLCBjb2xvcj0iZGFya2dyZXkiKSArCiAgZ2VvbV90ZXh0KGRhdGE9d19kZiwgYWVzKHg9eCwgeT15LCBsYWJlbD1sYWJlbCksIHNpemU9MTQpICsKICBnZW9tX3BvaW50KGFlcyh4PXN1YncsIHk9ZW5kKSwgZGF0YT1zdWJ3X2RlbW9bMjoobnJvdyhzdWJ3X2RlbW8pIC0gMSldLCBzaXplPTMpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoIiIsIGJyZWFrcz0wOihucm93KHN1YndfZGVtbyktMiksIHNlYy5heGlzPWR1cF9heGlzKCkpICsKICB0aGVtZShheGlzLnRpdGxlLnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmdyaWQubWlub3I9ZWxlbWVudF9ibGFuaygpKSArCiAgY29vcmRfZmxpcCgpCgp2aXRlcmJpX2RlbW9fcApgYGAKPC9kaXY+CgpXZSBpdGVyYXRlIG92ZXIgZXZlcnkgcG9zaXRpb24gaW4gYSBnaXZlbiB3b3JkLgpBdCBlYWNoIGVuZC1vZi1jaGFyYWN0ZXIgcG9zaXRpb24sCndlIGRldGVybWluZSB0aGUgYmVzdCBzZWdtZW50IGJ5IGZpbmRpbmcgdGhlIG9uZSB3aXRoIGhpZ2hlc3QgbGlrZWxpaG9vZCBnaXZlbiB0aGUgY3VycmVudCB2b2NhYnVsYXJ5LgoKRm9yIGV4YW1wbGUsCndoZW4gd2UgYXJlIGF0IHBvc2l0aW9uIDIsCndlIGNvbXBhcmUgdGhlIGxvZy1saWtsaWhvb2Qgb2YgYCh3aClgIGFuZCBgKHcsIGgpYC4KVGhlIGZvcm1lciBpcyBtb3JlIGxpa2VseSBzbyB3ZSByZWNvcmQgaXRzIG5lZ2F0aXZlIGxvZy1saWtlbGlob29kIGFsb25nIHdpdGggdGhlIHNlZ21lbnQgaW5kaWNlcyBhbmQgdGhlbiBtb3ZlIG9udG8gdGhlIG5leHQgcG9zaXRpb24uCk5vdyBhdCBwb3NpdGlvbiAzIHdlIGNvbXBhcmUgYCh3aGUpYCB3aXRoIGAodywgaGUpYCBhbmQgYCh3aCwgZSlgIGFuZCBhZ2FpbiByZXR1cm4gdGhlIGJlc3Qgc2VnbWVudC4KTm90aWNlIHRoYXQgd2UgZG9uJ3QgY29tcGFyZSBgKHcsIGgsIGUpYCBzaW5jZSBpbiB0aGUgcHJldmlvdXMgcG9zaXRpb24gd2UgYWxyZWFkeSBydWxlIGAodywgaClgIG91dCBkdWUgdG8gaXRzIGJlaW5nIGluZmVyaW9yIChsZXNzIGxpa2VseSkgdG8gYCh3aClgLgoKIyMjIyBUaGUgQmFja3dhcmQgU3RlcCB7LX0KCkFmdGVyIHRoZSBmb3J3YXJkIHN0ZXAgaXMgZmluaXNoZWQsCndlIGNhbiB0cmFjayBiYWNrd2FyZCB0aGUgcGF0aCBvZiBzZWdtZW50cyB0aGF0IGdlbmVyYXRlcyB0aGUgaGlnaGVzdCBvdmVyYWxsIGxpa2VsaWhvb2QuCgpgYGB7cHl0aG9uIHZpdGVyYmlfYmFja3dhcmR9CmRlZiB2aXRlcmJpX2JhY2t3YXJkKHdvcmQsIHN1Yndfc2xpY2VzKToKICAiIiJCYWNrd2FyZCBzdGVwIG9mIFZpdGVyYmkgdG8gcmV0dXJuIHRoZSBiZXN0IHBhdGguIiIiCiAgc3Vid29yZHMgPSBbXQogIHN1YndvcmRfc2xpY2VzID0gW10KICBuZXh0X3NsaWNlcyA9IHN1Yndfc2xpY2VzWy0xXQogIHdoaWxlIG5leHRfc2xpY2VzIGlzIG5vdCBOb25lOgogICAgc3VidyA9IHdvcmRbbmV4dF9zbGljZXNbMF06bmV4dF9zbGljZXNbMV1dCiAgICBzdWJ3b3Jkcy5hcHBlbmQoc3VidykKICAgIHN1YndvcmRfc2xpY2VzLmFwcGVuZChuZXh0X3NsaWNlcykKICAgIG5leHRfc2xpY2VzID0gc3Vid19zbGljZXNbbmV4dF9zbGljZXNbMF1dCiAgc3Vid29yZHMucmV2ZXJzZSgpCiAgcmV0dXJuIHN1YndvcmRzLCBzdWJ3b3JkX3NsaWNlcwoKCiMgVGVzdCB3aXRoIGEgc2luZ2xlIHdvcmQuCmJlc3Rfc3VidywgYmVzdF9zbGljZXMgPSB2aXRlcmJpX2JhY2t3YXJkKHcsIHN1Yndfc2xpY2VzKQpwcmludChiZXN0X3N1YncpCmBgYAoKVGhpcyBpcyBlYXNpbHkgdW5kZXJzdGFuZGFibGUgaWYgd2UgYWxzbyB2aXN1YWxpemUgdGhlIGJhY2t3YXJkIHN0ZXA6Cgo8ZGl2IGNsYXNzPSJmb2xkIHMiPgpgYGB7ciB2aXRld3JiaV9iYWNrd2FyZF9wbG90fQojIFIKc2VnIDwtIHJiaW5kbGlzdChweSRiZXN0X3NsaWNlcykKc2V0bmFtZXMoc2VnLCBjKCJ5ZW5kIiwgInkiKSkKc2VnIDwtIHNlZ1ssIHg6PW1hdGNoKHJldihweSRiZXN0X3N1YncpLCByZXYoc3Vid19kZW1vJHN1YncpKV0Kc2VnIDwtIHNlZ1ssIHhlbmQ6PXhdCnNlZyA8LSBzZWdbLCBzdWJ3Oj0iIl0gICMgUmVxdWlyZWQgdG8gYnlwYXNzIGdncGxvdCBkYXRhIGNoZWNraW5nLgoKdml0ZXJiaV9kZW1vX3AgKwogIGdlb21fc2VnbWVudChhZXMoeD14LCB4ZW5kPXhlbmQsIHk9eSwgeWVuZD15ZW5kKSwgZGF0YT1zZWcsCiAgICAgICAgICAgICAgIGNvbD0icmVkIiwgYXJyb3c9YXJyb3cobGVuZ3RoPXVuaXQoMC4wMywgIm5wYyIpKSwgc2l6ZT0xLjUpCmBgYAo8L2Rpdj4KCk5vdyBjb21iaW5pbmcgdGhlIGZvcndhcmQgYW5kIGJhY2t3YXJkIHN0ZXBzIGZvciBhIGNvbXBsZXRlIHNlZ21lbnRhdGlvbiBvZiBhIHRleHQgbGluZToKCmBgYHtweXRob24gdml0ZXJiaX0KZGVmIHZpdGVyYmkobGluZSk6CiAgb3V0ID0gW10KICB3b3JkcyA9IGxpbmUuc3BsaXQoKQogIGZvciB3IGluIHdvcmRzOgogICAgc3Vid19zY29yZXMsIHN1Yndfc2xpY2VzID0gdml0ZXJiaV9mb3J3YXJkKHcsIHN1YndvcmRfbG9ncCkKICAgIHN1YncsIHN1YndfaWR4ID0gdml0ZXJiaV9iYWNrd2FyZCh3LCBzdWJ3X3NsaWNlcykKICAgIG91dC5leHRlbmQoc3VidykKICByZXR1cm4gb3V0CgpwcmludChsaW5lKSAgIyBJbnB1dCBzZW50ZW5jZS4KcHJpbnQodml0ZXJiaShsaW5lKSkgICMgT3V0cHV0IHN1YndvcmQgc2VxdWVuY2UuCmBgYAoKIyMjIEVNIHdpdGggVml0ZXJiaQoKTm93IHdlIGtub3cgdGhlIGlkZWEgb2YgRU0sCmFuZCB3ZSBrbm93IGhvdyB0byBmaW5kIHRoZSBvcHRpbWFsIHNlZ21lbnQgcGF0aCBieSBWaXRlcmJpLAp3ZSBjYW4gcHV0IHRoZW0gdG9nZXRoZXIgdG8gZm9ydW1sYXRlIHRoZSBvcHRpbWl6YXRpb24gdGFzayBvZiBvdXIgcHJvYmFibGlzdGljIHN1YndvcmQgc2VnbWVudGF0aW9uLgoKSGVyZSBpcyB0aGUgY29tcGxldGUgcHJvY2VkdXJlOgoKMS4gSW5pdGlhbGl6ZSBhIGxhcmdlIHNlZWRpbmcgc3Vid29yZCB2b2NhYnVsYXJ5IGZyb20gdGhlIHRyYWluaW5nIGNvcnB1cwoyLiBbRXhwZWN0YXRpb25dIEVzdGltYXRlIGVhY2ggc3Vid29yZCBwcm9iYWJpbGl0eSBieSB0aGUgY29ycmVzcG9uZGluZyBmcmVxdWVuY3kgY291bnRzIGluIHRoZSB2b2NhYnVsYXJ5CjMuIFtNYXhpbWl6YXRpb25dIFVzZSBWaXRlcmJpIHRvIHNlZ21lbnQgdGhlIGNvcnB1cywgcmV0dXJuaW5nIHRoZSBvcHRpbWFsIHNlZ21lbnRzCjQuIENvbXB1dGUgdGhlIGxvc3Mgb2YgZWFjaCBuZXcgc3Vid29yZCBmcm9tIG9wdGltYWwgc2VnbWVudHMKNS4gU2hyaW5rIHRoZSB2b2NhYnVsYXJ5IHNpemUgYnkgZHJvcHBpbmcgdGhlIHN1YndvcmRzIHdpdGggdG9wIFglIHNtYWxsZXN0IGxvc3Nlcwo2LiBSZXBlYXQgc3RlcCAyIHRvIDUgdW50aWwgdGhlIHZvY2FidWxhcnkgc2l6ZSByZWFjaGVzIGEgZGVzaXJlZCBudW1iZXIKClRoZSBsb3NzIG9mIGEgc3Vid29yZCBpbiBzdGVwIDQgaXMgdGhlIHJlZHVjdGlvbiBpbiBvdmVyYWxsIHRyYWluaW5nIGNvcnB1cyBzZWdtZW50IGxpa2VsaWhvb2QgaWYgdGhhdCBzdWJ3b3JkIGlzIHJlbW92ZWQgZnJvbSB0aGUgY3VycmVudCB2b2NhYnVsYXJ5LgoKSW4gdGhlIGFib3ZlIGV4YW1wbGUgd2UgdXNlIEJQRSB0byBnZW5lcmF0ZSB0aGUgaW5pdGlhbCB2b2NhYnVsYXJ5LgpBbm90aGVyIGNvbW1vbiBjaG9pY2UgaXMgdG8gdGhlIFtzdWZmaXggYXJyYXldKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1N1ZmZpeF9hcnJheSkgYWxnb3JpdGhtLiB0byBnZW5lcmF0ZSB0aGUgY29tbW9uIHN1YnN0cmluZ3MuCgojIFNlbnRlbmNlUGllY2UKCkBrdWRvMjAxOHNlbnRlbmNlcGllY2UgcHJvcG9zZSBhIGxhbmd1YWdlLWFnbm9zdGljIHN1YndvcmQgc2VnbWVudGF0aW9uIGFsZ29yaXRobSBjYWxsZWQgW1NlbnRlbmNlUGllY2VdKGh0dHBzOi8vZ2l0aHViLmNvbS9nb29nbGUvc2VudGVuY2VwaWVjZSkuClNlbnRlbmNlUGllY2UgaXMgYSBwb3dlcmZ1bCBhbmQgZWZmaWNpZW50IG9wZW4tc291cmNlZCB1bnN1cGVydmlzZWQgbGFuZ3VnYWUgdG9rZW5pemVyIHRoYXQgY2FuIGhlbHAgYnVpbGQgZW5kLXRvLWVuZCB2b2NhYnVsYXJ5IGZvciBuZXVyYWwgbmV0d29ya3MuCkluIHRoaXMgc2VjdGlvbiB3ZSB3aWxsIHdhbGstdGhyb3VnaCB0aGUgaWRlYSBvZiBTZW50ZW5jZVBpZWNlIGFsb25nIHdpdGggZGVtbyBleGFtcGxlcy4KCiMjIE1vZGVsIENoYXJhY3RlcmlzdGljcwoKSW4gdGhpcyBzZWN0aW9uIHdlIGJyaWVmbHkgc3VtbWFyaXplIHNldmVyYWwgdXNlZnVsIGNoYXJhY3RlcmlzdGljcyBhYm91dCB0aGUgc2VnbWVudGF0aW9uIG1vZGVsLgoKIyMjIExhbmd1YWdlLUFnbm9zdGljIEhhbmRsZQoKU2VudGVuY2VQaWVjZSBpcyBsYW5nYXVnZSBpbmRlcGVuZGVudCBiZWN1YXNlIHVuZGVyIHRoZSBob29kIGl0IHNpbXBseSB0cmVhdHMgYSBnaXZlbiBzZW50ZW5jZSBhcyBhIHNlcXVlbmNlIG9mICpVbmljb2RlKiBjaGFyYWN0ZXJzLgpBbmQgbW9zdCB3cml0dGVuIGxhbmdhdWdlIGluIHRoZSB3b3JsZCB3ZSBzcGVhayBjYW4gYmUgZW5jb2RlZCBpbiBVbmljb2RlLgpUaGlzIGlzIHBhcnRpY3VhbHJseSB1c2VmdWwgZm9yIG5vbi13aGl0ZXNwYWNlLXNlZ21lbnRlZCBsYW5ndWFnZXMgc3VjaCBhcyBDaGluZXNlLApLb3JlYW4gYW5kIEphcGFuZXNlLgoKVGVjaG5pY2FsbHkgc3BlYWtpbmcsCmluIFNlbnRlbmNlUGllY2Ugd2hpdGVzcGFjZSBpcyBhbHNvIHRyZWF0ZWQgYXMganVzdCBhbm90aGVyIG5vcm1hbCBzeW1ib2wuClRvIGFjaGlldmUgdGhpcywKYSBtZXRhIHN5bWJvbCBg4paBYCAoVSsyNTgxKSBpcyB1c2VkIHRvIHJlcGxhY2UgdGhlIHdoaXRlc3BhY2UgYXQgdGhlIHZlcnkgYmVnaW5uaW5nLgoKVGhlIGVudGlyZSB2b2NhYnVsYXJ5IGlzIGJ1aWx0IGZyb20gc3Vid29yZHMgZGlyZWN0bHkgYmFzZWQgb24gcmF3IHRleHQgaW5wdXQgc28gdGhlcmUgaXMgYWxzbyBubyBuZWVkIHRvIGRvIHByZS1wcm9jZXNzaW5nIHRvIHByb3ZpZGUgYSBmdWxsd29yZCBpbml0aWFsIHZvY2FidWxhcnkuClRoaXMgYnlwYXNzZXMgdGhlIG5lZWQgZm9yIGFueSBsYW5ndWFnZS1zcGVjaWZpYyBwcmVwcm9jZXNzaW5nIGxvZ2ljLApyZXN1bHRpbmcgaW4gYSB1bmlmaWVkIGZyYW1ld29yayBmb3IgbmF0dXJhbCBsYW5nYXVnZSBzZWdtZW50YXRpb24uCgojIyMgTG9zc2xlc3MgVG9rZW5pemF0aW9uCgpCZWNhdXNlIG9mIHRoZSBzcGVjaWFsIChvciByYXRoZXIsIG5vbi1zcGVjaWFsKSB0cmVhdG1lbnQgb2Ygd2hpdGVzcGFjZSBhcyBhIG5vcm1hbCBzeW1ib2wsClNlbnRlbmNlUGllY2UgaXMgYWJsZSB0byBhY2hlaXZlIGxvc3NsZXNzIHRva2VuaXphdGlvbi4KVGhhdCBpcywKdGhlIGVudGlyZSByYXcgdGV4dCBjYW4gYmUgcmVjb3ZlcmVkIGZyb20gaXRzIHNlZ21lbnRlZCB0b2tlbnMgd2l0aCB6ZXJvIGFtYmlndWl0eS4KClRoZSByZWFzb24gd2h5IG1vc3R5IG9mIHRoZSBvdGhlciB0b2tlbml6YXRpb24gYWxnb3JpdGhtIHdvbid0IGJlIGFibGUgdG8gYWNoaWV2ZSB0aGlzIGlzIGJlY2F1c2Ugb2YgdGhlIHByZS10b2tlbml6YXRpb24gc3RhZ2UgdGhhdCB1c2Ugd2hpdGVzcGFjZSB0byBwcmUtcHJvY2VzcyB0aGUgY29ycHVzIGludG8gYSB3b3JraW5nIHZvY2FidWxhcnkuClRoZSBzdWJ3b3JkIHNlZ21lbnRhdGlvbiBpcyB0aGVuIG9wZXJhdGluZyBvbiB0aGUgd29ya2luZyB2b2NhYnVsYXJ5IGluc3RlYWQgb2YgdGhlIHJhdyB0ZXh0IGxpbmVzIGluIHRoZSBjb3JwdXMuCgpOb3RpY2UgdGhhdCBpbiBvdXIgdG95IGV4YW1wbGUgaW4gdGhlIHByZXZpb3VzIHNlY3Rpb24gd2UgYWxzbyByZWxpZXMgb24gYSBwcmUtdG9rZW5pemF0aW9uIGJlZm9yZSB0aGUgc3Vid29yZCBzZWdtZW50YXRpb24ga2lja3MgaW4uCgojIyMgU2VsZi1Db250YWluZWQgSW50ZWdlciBNYXBwaW5nCgpZZXQgYW5vdGhlciBnb29kbmVzcyBhYm91dCBTZW50ZW5jZVBpZWNlIGlzIGl0cyBmbGV4aWJsZSBjYWJhcGlsaXR5IHRvIGdlbmVyYXRlIHRoZSBpbnRlZ2VyIG1hcHBpbmdzIHRoYXQgaXMgcmVxdWlyZWQgZm9yIG5ldXJhbCBuZXR3b3JrIG1vZGVsaW5nIGluIHRoZSBlbWJlZGRpbmcgbG9va3VwIHN1YnRhc2suCkl0IGNhbiBoYW5kbGUgYWxsIHRoZSBmb2xsb3dpbmcgbWV0YSBzeW1ib2xzOgoKKyBgYm9zYDogYmVnaW5uaW5nIG9mIHdvcmQKKyBgZW9zYDogZW5kaW5nIG9mIHdvcmQKKyBgdW5rYDogb3V0LW9mLXZvY2FidWxhcnkgd29yZAorIGBwYWRgOiBwYWRkaW5nIGZvciBmaXhlZC1sZW5ndGggb3V0cHV0LCBlc3BlY2lhbGx5IHVzZWZ1bCBpbiByZWN1cnJlbnQgbmV1cmFsIG5ldHdvcmsgbW9kZWxpbmcKCk9uY2UgdGhlIG1vZGVsIGlzIHRyYWluZWQsCnRoZSBtYXBwaW5ncyBhcmUgc2VsZi1jb250YWluZWQgaW4gdGhlIG1vZGVsIGZpbGUgYW5kIGNhbiBiZSB1c2VkIHRvIGVuY29kZS9kZWNvZGUgcmF3IHRleHQvaW50ZWdlciBzZXF1ZW5jZSBvbiB0aGUgZmx5LgoKIyMgQ29tbWFuZCBMaW5lIFVzYWdlCgpgYGB7YmFzaCBzcG1fdmVyc2lvbn0Kc3BtX3RyYWluIC0tdmVyc2lvbgpgYGAKClNlbnRlbmNlUGllY2UgaXMgd3JpdHRlbiBpbiBDKysgd2l0aCBib3RoIGEgY29tbWFuZCBsaW5lIHRvb2wgYW5kIGEgUHl0aG9uIHdyYXBwZXIuClRoZXJlIGFyZSBhIGhhbmRmdWwgb2Ygb3B0aW9ucyB0aGF0IGNhbiBiZSB1c2VkIHRvIHNldHVwIHRoZSBtb2RlbCBjb25maWd1cmF0aW9uLgoKYGBge2Jhc2ggc3BtX3VzYWdlfQojIGJhc2gKc3BtX3RyYWluIC0taGVscApgYGAKClNlbnRlbmNlUGllY2Ugc3VwcG9ydHMgbm90IG9ubHkgdGhlIHVuaWdyYW0gbGFuZ2F1Z2UgbW9kZWwgd2UganVzdCBkaXNjdXNzZWQgaW4gdGhlIHByZXZpb3VzIHNlY3Rpb24sCml0IGFsc28gc3VwcG9ydHMgQlBFIGFsZ29yaXRobSwKYW5kIHR3byBuYWl2ZSB0b2tlbml6ZXIgb25lIGZvciB3b3JkLWxldmVsIHRoZSBvdGhlciBmb3IgY2hhcmFjdGVyLWxldmVsLgoKU2V2ZXJhbCBvcHRpb25zIGFyZSByZWxldmFudCB0byB0aGUgdW5pZ3JhbSBtb2RlbDoKCisgYC0tc2VlZF9zZW50ZW5jZXBpZWNlX3NpemU9MTAwMDAwMGA6IFRoaXMgaXMgdGhlIGluaXRpYWwgc2VlZGluZyB2b2NhYnVsYXJ5IGZvciB0aGUgRU0tVml0ZXJiaSBhbGdvcml0aG0gdG8gc3RhcnQgd2l0aAorIGAtLXNocmlua2luZ19mYWN0b3I9MC43NWA6IFRoaXMgaXMgYnkgaG93IG11Y2ggdGhlIHNlZWRpbmcgdm9jYWJ1bGFyeSBpcyByZWR1Y2VkIGJhc2VkIG9uIHRoZSBsb3NzLXJhbmtpbmcgZHJvcG91dCBwcm9jZWR1cmUKKyBgLS1udW1fc3ViX2l0ZXJhdGlvbnM9MmA6IFRoaXMgaXMgdGhlIGh5cGVycGFyYW1ldGVyIGZvciB0aGUgRU0tVml0ZXJiaSBhbGdvcml0aG0KCiMjIFB5dGhvbiBVc2FnZQoKVGhlIGNvbW1hbmQgbGluZSB0b29sIGlzIHZlcnkgY29uY2lzZS4KQnV0IHNpbmNlIHdlIHVzdWFsbHkgdHJhaW4gdGhlIG1vZGVsIGZvciBhIGRvd25zdHJlYW0gbmV1cmFsIG5ldHdvcmsgbW9kZWwgdGhhdCBpcyBkZXZlbG9wZWQgaW4gUHl0aG9uLAp3ZSB3aWxsIHR1cm4gb3VyIGZvY3VzIGludG8gU2VudGVuY2VQaWVjZSdzIFB5dGhvbiB3cmFwcGVyLgpJdCB0dXJucyBvdXQgdGhhdCBpdCBpcyBub3QgbXVjaCBkaWZmZXJlbnQuCldlIHdpbGwgc3RpbGwgdXNlIHRoZSBjb21tYW5kIGxpbmUgb3B0aW9uIHN0eWxlIHRvIGNvbnRyb2wgdGhlIGJlaGF2aW9yIG9mIHRoZSB0b29sLgoKTGV0J3MgdHJhaW4gYSBzbWFsbCB2b2NhYnVsYXJ5IHVzaW5nIHRoZSBTaGFrZXNwZWFyZToKCmBgYHtweXRob24gc3BtX3VuaWdyYW0sIHJlc3VsdHM9ImhpZGUifQppbXBvcnQgc2VudGVuY2VwaWVjZSBhcyBzcG0KCnNwbV9hcmdzID0gIi0taW5wdXQ9ZGF0YS9zaGFrZXNwZWFyZS50eHQiCnNwbV9hcmdzICs9ICIgLS1tb2RlbF9wcmVmaXg9bSIKc3BtX2FyZ3MgKz0gIiAtLXZvY2FiX3NpemU9MTAwMCIKc3BtX2FyZ3MgKz0gIiAtLW1vZGVsX3R5cGU9dW5pZ3JhbSIKc3BtLlNlbnRlbmNlUGllY2VUcmFpbmVyLlRyYWluKHNwbV9hcmdzKQpgYGAKCiMjIyBWb2NhYnVsYXJ5IEluc3BlY3Rpb24KClRoZSByZXN1bHRpbmcgYC5tb2RlbGAgZmlsZSBjb21lcyB3aXRoIGEgdm9jYWJ1bGFyeSBmaWxlIGAudm9jYWJgIHRoYXQgY2FuIGJlIHVzZWQgZm9yIGludmVzdGlnYXRpb24uCihJdCBpcyBub3QgcmVxdWlyZWQgZm9yIHNlZ21lbnRhdGlvbiBzaW5jZSB0aGUgdm9jYWIgaXMgc2VsZi1jb250YWluZWQgaW4gdGhlIG1vZGVsIGZpbGUgYWxyZWFkeS4pCgpUaGUgYC52b2NhYmAgZmlsZSBjb250YWlucyBhbGwgZmluYWwgc3Vid29yZHMgc29ydGVkIGluIGRlc2NlbmRpbmcgb3JkZXIgZm9yIGl0cyBsb2ctbGlrZWxpaG9vZC4KCmBgYHtyIHNwbV92b2NhYn0KIyBSCiMgV2UgdXNlIFIgaGVyZSBmb3IgdGhlIHNha2Ugb2YgYSBuaWNlbHkgZm9ybWF0dGVkIHRhYmxlIGRvbmUgYnkgUm1kIG5vdGVib29rLiA6KQp2b2NhYiA8LSBmcmVhZCgibS52b2NhYiIsIGhlYWRlcj1GQUxTRSwgZW5jb2Rpbmc9IlVURi04IikKc2V0bmFtZXModm9jYWIsIGMoInN1YndvcmQiLCAibG9ncCIpKQp2b2NhYiA8LSB2b2NhYlssIHA6PXJvdW5kKGV4cChsb2dwKSwgNSldCnZvY2FiW10KYGBgCgpgYGB7ciBzcG1fY2hlY2tfcH0KIyBUaGUgc3VtbWF0aW9uIG9mIGFsbCBzdWJ3b3JkcyBzaG91bGQgYXBwcm94aW1hdGUgdW5pdHkuCnN1bShleHAodm9jYWIkbG9ncClbLSgxOjMpXSkKYGBgCgpOb3RlIHRoZSBjb21tb24gcHJlc2VuY2Ugb2YgdGhlIHNwZWNpYWwgc3ltYm9sIChVKzI1ODEpIHRoYXQgaXMgdXNlZCB0byBlbmNvZGUgdGhlIG9yaWdpbmFsIHdoaXRlc3BhY2UuClVzdWFsbHkgYSBzdWJ3b3JkIHdpdGggdGhpcyBzeW1ib2wgbWVhbnMgaXQgaXMgYSBsZWdpdGltYXRlIGZ1bGx3b3JkLApvciBhdCBsZWFzdCBhIHN1YndvcmQgdGhhdCBpcyB0aGUgcHJlZml4IHJhdGhlciB0aGFuIHRoZSBzdWZmaXggcGFydCBvZiBhIGZ1bGx3b3JkLgoKIyMjIEVuY29kaW5nLURlY29kaW5nCgpPbmNlIHRyYWluZWQsCndlIGNhbiBsb2FkIHRoZSBtb2RlbCB3aXRoIGEgc2luZ2xlIGxpbmU6CgpgYGB7cHl0aG9uIHNwbV9sb2FkX21vZGVsLCByZXN1bHRzPSJoaWRlIn0Kc3AgPSBzcG0uU2VudGVuY2VQaWVjZVByb2Nlc3NvcigpCnNwLkxvYWQoIm0ubW9kZWwiKQpgYGAKCkhlcmUgYXJlIHRoZSBnZW5lcmFsIHVzYWdlIG9mIHRoZSBlbmNvZGluZy9kZWNvZGluZyBBUElzOgoKYGBge3B5dGhvbiBzcG1fdGVzdF9lbmNvZGVfZGVjb2RlfQpzdWJ3b3JkX3BpZWNlcyA9IHNwLkVuY29kZUFzUGllY2VzKCJUaGlzIGlzIGEgdGVzdC4iKQpwcmludChzdWJ3b3JkX3BpZWNlcykKCnByaW50KCIiLmpvaW4oc3Vid29yZF9waWVjZXMpLnJlcGxhY2UoIlx1MjU4MSIsICIgIikpICAjIFJlY292ZXIgdGhlIHdoaXRlc3BhY2UuCgpzdWJ3b3JkX2lkcyA9IHNwLkVuY29kZUFzSWRzKCJUaGlzIGlzIGEgdGVzdC4iKQpwcmludChzdWJ3b3JkX2lkcykgICMgQ29udmVydCBzZW50ZW5jZSB0byBpbnRlZ2VyIHNlcXVlbmNlLgoKcHJpbnQoc3AuRGVjb2RlUGllY2VzKHN1YndvcmRfcGllY2VzKSkgICMgTG9zc2xlc3MgRGV0b2tlbml6YXRpb24gZnJvbSBzdWJ3b3Jkcy4KcHJpbnQoc3AuRGVjb2RlSWRzKHN1YndvcmRfaWRzKSkgICMgTG9zc2xlc3MgRGV0b2tlbml6YXRpb24gZnJvbSBzdWJ3b3JkIGlkcy4KYGBgCgpgYGB7cHl0aG9uIHNwbV9tZXRhX3N5bWJvbH0KIyBCeSBkZWZhdWx0IHRoZSBmaXJzdCAzIGlkcyBhcmUgdG9rZW4gZm9yIHVua25vd24sIGJvcyBhbmQgZW9zLgpmb3IgaSBpbiByYW5nZSgzKToKICBwcmludChzcC5JZFRvUGllY2UoaSkpCmBgYAoKYGBge3B5dGhvbiBzcG1fdGVzdF9vb3Z9CiMgVGhlIHdvcmQgInRlc3QiIGlzIG5vdCBjb21tb24gZW5vdWdoIGluIFNoYWtlc3BlYXJlLgojIFNvIGl0IGlzIG5vdCBwYXJ0IG9mIG91ciBzbWFsbCB2b2NhYnVsYXJ5LgpwcmludChzcC5QaWVjZVRvSWQoInRlc3QiKSkgICMgTWFwIHRvIGludGVnZXIgcmVwcmVzZW50aW5nIHVua25vd24uCmBgYAoKIyMjIFN1YndvcmQgU2FtcGxpbmcKClJlbWVtYmVyIHRoYXQgdGhlIHVuaWdyYW0gbW9kZWwgZm9yIHNlZ21lbnRhdGlvbiBpcyBwcm9iYWJpbGlzdGljLAp3aGljaCBtZWFucyB0aGF0IG9uZSBzZW50ZW5jZSBjYW4gYmUgcmVwcmVzZW50ZWQgYnkgbW9yZSB0aGFuIG9uZSBzZXF1ZW5jZSBvZiBzZWdtZW50cyAob3Igc3Vid29yZHMpLgpCeSBkZWZhdWx0IHRoZSBtb3N0IHByb2JhYmxlIHNlZ21lbnRhdGlvbiBpcyB1c2VkIHdoZW4gd2UgYXJlIGRvaW5nIGVuY29kaW5nLWRlY29kaW5nIHRhc2sgd2l0aCBTZW50ZW5jZVBpZWNlLgpCdXQgd2UgY2FuIGFsc28gZ2VuZXJhdGUgc2VnbWVudHMgdXNpbmcgc2FtcGxpbmcgYmFzZWQgb24gdGhlIHN1YndvcmQgcHJvYmFiaWxpdHkgaW4gb3VyIHZvY2FidWxhcnk6CgpgYGB7cHl0aG9uIHNwbV9zdWJ3b3JkX3NhbXBsaW5nfQpmb3IgXyBpbiByYW5nZSg1KToKICBwcmludChzcC5TYW1wbGVFbmNvZGVBc1BpZWNlcygiVGhpcyBpcyBhIHRlc3QiLCBuYmVzdF9zaXplPS0xLCBhbHBoYT0uMSkpCmBgYAoKT3Igd2UgY2FuIHNpbXBseSBnZW5lcmF0ZSB0aGUgdG9wIDUgbW9zdCBwcm9iYWJsZSBzZWdtZW50YXRpb25zOgoKYGBge3B5dGhvbiBzcG1fc3Vid29yZF90b3BufQpmb3Igc2VnIGluIHNwLk5CZXN0RW5jb2RlQXNQaWVjZXMoIlRoaXMgaXMgYSB0ZXN0IiwgNSk6CiAgcHJpbnQoc2VnKQpgYGAKCiMgRXhlcmNpc2U6IENoaW5lc2UgV29yZHMgU2VnbWVudGF0aW9uCgpJbiB0aGlzIHNlY3Rpb24gd2UgZGVtb25zdHJhdGUgdGhlIHNlZ21lbnRhdGlvbiBvbiBhIG5vbi13aGl0ZXNwYWNlLXNlcGFyYXRlZCBsYW5nYXVnZToKdHJhZGl0aW9uYWwgY2hpbmVzZS4KV2Ugd2lsbCB1c2UgYSBzdWJzZXQgb2Ygd2lraXBlZGlhIGFydGljbGVzIChmcm9tIFtXaWtpbWVkaWEgRG93bmxvYWRzXShodHRwczovL2R1bXBzLndpa2ltZWRpYS5vcmcvKSkuCldlIHVzZSB0aGUgW1dpa2lFeHRyYWN0b3JdKGh0dHBzOi8vZ2l0aHViLmNvbS9hdHRhcmRpL3dpa2lleHRyYWN0b3IpIHRvb2wgdG8gcHJvY2VzcyB0aGUgcmF3IGR1bXAsCmFuZCBbT3BlbkNDXShodHRwczovL2dpdGh1Yi5jb20vQllWb2lkL09wZW5DQykgdG8gY29udmVydCB0aGUgdGV4dHMgaW50byB0cmFkaXRpb25hbCBjaGluZXNlIChUYWl3YW4gc3RhbmRhcmQpLgoKYGBge2Jhc2ggZG93bmxvYWRfemh3aWtpfQojIFByZXBhcmUgemgtd2lraSBkdW1wcwpPVVRESVI9ZGF0YS96aHdpa2lfdGV4dHMKaWYgWyAhIC1kICRPVVRESVIgXQp0aGVuCiAgIyBEb3dubG9hZCB0aGUgZGF0YSBpZiBub3QgZm91bmQgbG9jYWxseS4KICBXSUtJRFVNUD16aHdpa2ktbGF0ZXN0LXBhZ2VzLWFydGljbGVzMS54bWwtcDFwMTYyODg2CiAgaWYgWyAhIC1mIGRhdGEvIiRXSUtJRFVNUCIgXQogIHRoZW4KICAgIHdnZXQgaHR0cHM6Ly9kdW1wcy53aWtpbWVkaWEub3JnL3pod2lraS9sYXRlc3QvJFdJS0lEVU1QLmJ6MgogICAgbXYgJFdJS0lEVU1QLmJ6MiBkYXRhCiAgICBiemlwMiAtZCAtayAkV0lLSURVTVAuYnoyCiAgZmkKICAjIEV4dHJhY3QgY2xlYW4gdGV4dCBmcm9tIHhtbCBkdW1wLgogIFdpa2lFeHRyYWN0b3IucHkgLS1qc29uIC0tbm9fdGVtcGxhdGVzIC0tcHJvY2Vzc2VzIDQgXAogICAgLW8gJE9VVERJUiAtLW1pbl90ZXh0X2xlbmd0aCAxMCBkYXRhLyRXSUtJRFVNUApmaQpgYGAKCmBgYHtweXRob24gcGFyc2Vfemh3aWtpX2R1bXB9CmltcG9ydCBqc29ubGluZXMKCmluZmlsZSA9ICJkYXRhL3pod2lraV90ZXh0cy9BQS93aWtpXzAwIgpkZWYgcGFyc2Vfd2lraV9zZW50KGluZmlsZSk6CiAgIyBTaW1wbGUgc2VudGVuY2UgZGVsaW1pdGVyLgogIF9ERUxJTUlURVJfSEFMRiA9IFsiISIsICJcPyJdCiAgX1pIX0RFTElNSVRFUl9GVUxMID0gWyLjgIIiLCAi77yBIiwgIu+8nyJdCiAgX0dFTkVSQUxfREVMSU1JVEVSID0gWyJcbiJdCiAgX0RFTElNSVRFUiA9IF9ERUxJTUlURVJfSEFMRiArIF9aSF9ERUxJTUlURVJfRlVMTCArIF9HRU5FUkFMX0RFTElNSVRFUgogIHNwbGl0X3BhdCA9IHIne30nLmZvcm1hdCgnfCcuam9pbihfREVMSU1JVEVSKSkKCiAgIyBSZWFkIHBhcmFncmFwaHMuCiAgcGFyYWdyYXBocyA9IFtdCiAgd2l0aCBqc29ubGluZXMub3BlbihpbmZpbGUpIGFzIGpsaW5lczoKICAgIGZvciBqIGluIGpsaW5lczoKICAgICAgcGFyYWdyYXBocy5hcHBlbmQoalsidGV4dCJdKQoKICAjIEJyZWFrIGJ5IG5ld2xpbmUuCiAgbGluZXMgPSBbXQogIGZvciBwIGluIHBhcmFncmFwaHM6CiAgICBsaW5lcy5leHRlbmQocC5sb3dlcigpLnNwbGl0KCJcbiIpKQoKICAjIEZ1cnRoZXIgYnJlYWsgYnkgc2VudGVuY2UuIFdlIHVzZSBhIG5haXZlIGFwcHJvYWNoIGhlcmUganVzdCBmb3Igc2ltcGxpY2l0eS4KICBzZW50cyA9IFtdCiAgZm9yIGwgaW4gbGluZXM6CiAgICBpZiBsZW4obCkgPj0gMjA6CiAgICAgIHNlbnRzLmV4dGVuZChyZS5zcGxpdChzcGxpdF9wYXQsIGwpKQoKICByZXR1cm4gc2VudHMKCnNlbnRzID0gcGFyc2Vfd2lraV9zZW50KGluZmlsZSkKCiMgU2hvdyByYXcgY2xlYW5lZCB0ZXh0cy4KZm9yIHMgaW4gc2VudHNbOjNdOgogIHByaW50KHMgKyAiXG4iKQpgYGAKCmBgYHtweXRob24gd3JpdGVvdXRfd2lraV9zZW50fQojIFdyaXRlIG91dCBzZW50ZW5jZSBmaWxlcyBmcm9tIHByb2Nlc3NlZCB3aWtpIGR1bXBzLgppbmRpciA9ICJkYXRhL3pod2lraV90ZXh0cyIKb3V0ZmlsZSA9ICJkYXRhL3pod2lraV9zZW50cy50eHQiCgppZiBub3Qgb3MucGF0aC5leGlzdHMob3V0ZmlsZSk6CiAgd2l0aCBvcGVuKG91dGZpbGUsICJ3IikgYXMgZjoKICAgIGZvciByb290LCBkaXJzLCBsZWFmcyBpbiBvcy53YWxrKGluZGlyKToKICAgICAgZm9yIGwgaW4gbGVhZnM6CiAgICAgICAgaW5maWxlID0gb3MucGF0aC5qb2luKHJvb3QsIGwpCiAgICAgICAgaWYgb3MucGF0aC5pc2ZpbGUoaW5maWxlKToKICAgICAgICAgIHNlbnRzID0gcGFyc2Vfd2lraV9zZW50KGluZmlsZSkKICAgICAgICAgIGZvciBzIGluIHNlbnRzOgogICAgICAgICAgICBpZiBzICE9ICIiOgogICAgICAgICAgICAgIGYud3JpdGUocyArICJcbiIpCmBgYAoKYGBge2Jhc2ggb3BlbmNjfQojIEJhc2gKIyBDb252ZXJ0IHRvIHRyYWRpdGlvbmFsIGNoaW5lc2UgaW4gVGFpd2FuIHN0YW5kYXJkLgppZiBbICEgLWYgImRhdGEvdHdfemh3aWtpX3NlbnRzLnR4dCIgXQp0aGVuCiAgb3BlbmNjIC1pIGRhdGEvemh3aWtpX3NlbnRzLnR4dCAtbyBkYXRhL3R3X3pod2lraV9zZW50cy50eHQgLWMgczJ0d3AuanNvbgpmaQpgYGAKCmBgYHtiYXNoIHR3X3pod2lraV9zZW50X2hlYWR9CmhlYWQgLW41IGRhdGEvdHdfemh3aWtpX3NlbnRzLnR4dApgYGAKCmBgYHtweXRob24gdHJhaW5fdHd6aF9zcG19CmltcG9ydCBzZW50ZW5jZXBpZWNlIGFzIHNwbQoKIyBTa2lwIHRyYWluaW5nIGlmIHRoZSBtb2RlbCBmaWxlIGlzIGFscmVhZHkgdGhlcmUsIHRvIHNhdmUgbm90ZWJvb2sgcmVuZGVyaW5nIHRpbWUuCmlmIG5vdCBvcy5wYXRoLmV4aXN0cygidHd6aC5tb2RlbCIpOgogIHNwbV9hcmdzID0gIi0taW5wdXQ9ZGF0YS90d196aHdpa2lfc2VudHMudHh0IgogIHNwbV9hcmdzICs9ICIgLS1tb2RlbF9wcmVmaXg9dHd6aCIKICBzcG1fYXJncyArPSAiIC0tdm9jYWJfc2l6ZT0zMDAwMCIKICBzcG1fYXJncyArPSAiIC0tbW9kZWxfdHlwZT11bmlncmFtIgogIHNwbS5TZW50ZW5jZVBpZWNlVHJhaW5lci5UcmFpbihzcG1fYXJncykKYGBgCgpgYGB7cHl0aG9uIHR3emhfZGVtb30KdHd6aF9tb2RlbCA9IHNwbS5TZW50ZW5jZVBpZWNlUHJvY2Vzc29yKCkKdHd6aF9tb2RlbC5Mb2FkKCJ0d3poLm1vZGVsIikKCnR3emhfbW9kZWwuRW5jb2RlQXNQaWVjZXMoIuWFieW+qemmmea4r++8jOaZguS7o+mdqeWRvSIpCgp0d3poX21vZGVsLkVuY29kZUFzUGllY2VzKCLmiJHlgJHoiIfmg6HnmoTot53pm6IiKQoKdHd6aF9tb2RlbC5FbmNvZGVBc1BpZWNlcygi5omA5pyJ55qE5qih5Z6L6YO95piv6Yyv55qE77yM5L2G5pyJ5Lqb5piv5pyJ55So55qEIikKYGBgCgpUaGUgcmVzdWx0IGlzIG5vdCBwZXJmZWN0LApidXQgY29uc2lkZXJpbmcgdGhlIGVmZm9ydCB3ZSBwdXQgaXQgaXMgcXVpdGUgZ29vZCBhbHJlYWR5LgpGb3IgYSBiaWdnZXIgY29ycHVzIHRoYXQgbWF5IGJlIG1vcmUgcmVwcmVzZW50YXRpdmUgdG8gdGhlIGRvd25zdHJlYW0gYXBwbGljYXRpb24sCnRoZSB1bnN1cGVydmlzZWQgdG9rZW5pemF0aW9uIGlzIGEgdmVyeSBjb252ZW5pZW50IHdheSB0byBxdWlja2x5IGRpdmUgaW50byB0aGUgbW9kZWxpbmcgcGhhc2Ugd2l0aCBsaXR0bGUgcHJlLXByb2Nlc3NpbmcgZWZmb3J0IG9mIHRoZSB0cmFkaXRpb25hbCBOTFAgdGFza3MuCgojIFJlZmVyZW5jZXMK