Introduction

Hello dear reader!

By this time, you probably know me reasonably well from all the CVs I have sent you. It seems like those weren’t that interesting to you, which I totally understand. But at the same time, CV is not really able to fully communicate how I actually work and whether the things that I’m able to create could add value to DuckDuckGo. That’s why I have decided to show you a piece of my work by collecting and analyzing your data from the tracker-radar project. I know that this might be a little bit weird and annoying. But, I see working at DuckDuckGo as a great opportunity - far too great to give up right away. With that being said, if you decide to reject my application again, after seeing a piece of my work, I will fully accept that and I won’t reach out again. Thank you!

– Stanislav “Stano” Smatana

What happend in 2022? Analysis of tracker-radar data in time.

tldr; It seems like something interesting have happend some time between 2022 and 2023 in the world of website trackers. The nature of trackers have suddenly shifted and this shift seems to be connected with fingerprinting based on viewport characteristics. Do you know what could have happend? If you want to get right into results, jump to the section Analyzing Suspected Tracker Patterns in Time. If you want to see how I went over analyzing this data step by step - continue reading.

I’ve decided to download and analyze website tracking data collected by DuckDuckGo’s tracker-radar with following goals:

All of the data I’ve used in the analysis was extracted from domain json files availiable across all releases of . You can see the shape of the raw input data below. Each records corresponds to one type of API call in one resource of one domain, meaning that the overall grain of the table is release_tag (date), region, domain, resource, api call. The data contains following columns:

library(dplyr)
library(topicmodels)

api_calls_in_time <- read.csv("data/api_calls_by_release.csv") %>%
  filter(type == "Script") 

#converting release tags to dates
release_tag_to_date <- data.frame(release_tag = unique(api_calls_in_time$release_tag)) %>%
  separate(release_tag, into = c("year", "month", "day"), sep = "\\.", fill = "right",remove = F) %>%
  mutate(
    month = sprintf("%02d", as.integer(month)),
    day   = ifelse(is.na(day), "01", sprintf("%02d", as.integer(day))),
    date  = as.Date(paste(year, month, day, sep = "-"))
  ) %>%
  select(release_tag,date)

api_calls_in_time <- api_calls_in_time %>%
  inner_join(release_tag_to_date) %>%
  select(date,region,domain,rule,api,calls,fingerprinting)
  
head(api_calls_in_time)

Python scripts I’ve used to scrape tracker-radar together with the source code of this notebook and other artifacts are availiable here TODO.

NOTE: Python is my primary language, but I love to use R for exploratory data analysis, so that’s what I’m using here :)

AI Use During this Analysis

During this process I’ve used AI (GitHub Copilot) at times to complement my primary skills. I have used AI exactly in this way:

  • I have used AI to generate python scripts for scraping of , because I didn’t want to waste time implementing scraping and to focus on analysis.
  • I did not use AI to conduct the analysis of this data and create this notebook, because that’s the part that I enjoy, where I excel, and where I do a better job.
  • I have used AI to help interpret LDA topics (details in the next section), because my knowledge of web trackers is very limited :)

Characterizing API Calls - Latent Dirichlet Allocation

Fingerprinting is connected to certain Javascript API calls that can help create precise identification of a device or a user. My assumption is that there are certain types of trackers and that each tracker type is associated with certain API calls. In order to see what these suspected tracker types might be, I need first to identify API calls that tend to occur together - API call groups.

Nearly any clustering approach could be used to create some groups of API calls, but I have decided to use Latent Dirichlet Allocation (LDA, not to be confused with Linear Discriminant Analysis). While this model traditionally comes from the field of Natural Language Processing, its assumptions exactly match this setting - sparse count data (counts of api calls with only few appearing in each resource). Don’t be scared by the complicated name, it’s actually a fairly simple unsupervised generative model. Let me explain how it specifically works for this analysis.

Every statistical model assumes certain view of the world and then helps us derive new information based on this view. In this setting, LDA assumes that certain API calls tend to occur together. These co-ocuring calls can be grouped into larger call groups. Each call can be a member of multiple groups and this membership is soft. This means that each call is associated with each group (topic in LDA language) with certain probability between 0 and 1.

With groups established, script resources (documents in LDA language) are viewed as different mixes of groups. Each mix is characterized by resource to group associations between 0-1 that add up to 1. Groups, associations between resources and groups and associations between groups and calls are derived by LDA from data without supervision. The only required parameter is the number of groups (topics). I will describe selection of the number of groups later.

The diagram below illustrates how LDA models an example of 2 script resources. Each script is associated with call groups 1-4 and its group mix dictates the actual calls that appear in each script resource. For instance, example1.js is 70% group1 and 70% group2. Therefore, its API calls come mostly from group2 and then also from group1. This is a toy example, in real LDA, each document is usually associated with each topic to an extent.

What does LDA enable me to do?

In order to derive call groups I will use only data from the most recent release. Afterwards, I’ll apply the model to the full data spanning all tracker-radar releases. I do this because I assume that today we have the broadest spectrum of tracking techniques available and the past was equally or less diverse.

api_calls_recent <- 
  api_calls_in_time %>%
  filter(date == max(api_calls_in_time$date))

head(api_calls_recent)

One last thing I need to do in order to apply LDA is to determine the number of call groups (topics) - k. There are many methods of doing that. I have decided to use one based on coisne similarity (Cao et. al. 2009), which aims to create groups that are as distinct from one another as possible. This is helpful because LDA sometimes tends to create groups (topics) that are heavily overlapping. Presence of multiple overlapping groups that correspond roughly to the same concept creates noise that makes interpretation harder.

More formally, k, for which the model has groups with smallest average pairwise cosine similarity is selected as the winning k.

cosine_sim <- function(M) {
  # row norms
  norms <- sqrt(rowSums(M * M))
  
  # denominator via outer
  den <- outer(norms, norms, "*")
  
  # dot products
  dot <- M %*% t(M)
  
  # cosine similarity
  cos_sim <- dot / den
  cos_sim
}

avg_cos_sim <- function(M){
  csim <- cosine_sim(M)
  mean(csim[upper.tri(csim)])  
}

call_counts <- api_calls_recent %>%
  pivot_wider(names_from=api,values_from = calls,values_fill = 0) 

call_counts_mat <- call_counts %>% 
  select(-c(region,domain,rule,fingerprinting,date)) %>% 
  as.matrix()

#This takes a lot of time, just load the rds if winner was already calculated
if(file.exists("data/winning_lda.Rds")){
  winning_lda = readRDS("data/winning_lda.Rds")
}else
{
  csims <- rep(0,times=19)
  for(k in 2:20){
    cat(paste("Processing LDA ",k,"\n"))
    topic_model <- LDA(call_counts_mat,k)    
    csims[k-1] <- avg_cos_sim(topic_model@beta)
  }

  
  selected_k <- (2:20)[which.min(csims)]
  winning_lda = LDA(call_counts_mat,selected_k)
  
  
}

selected_k = ncol(winning_lda@gamma)

LDA with 4 topics was selected as the model with the lowest average cosine similarity between API call groups (topics). Next, I want to characterize each group (topic) and for that I’ll identify calls that are most characteristic for each group. This can be done by looking into the beta matrix that has 4 rows, one for each group, and as many columns as there are distinct call types. Each cell corresponds to probability (in log scale) of corresponding call being associated with a corresponding call group. Small excerpt of the matrix below:

assoc_mat <- winning_lda@beta[,1:3]
colnames(assoc_mat) <- winning_lda@terms[1:3]
rownames(assoc_mat) <- paste("Group",1:4)
assoc_mat
        HTMLCanvasElement.prototype.toDataURL Navigator.prototype.hardwareConcurrency Navigator.prototype.plugins
Group 1                            -46.927331                              -39.063618                  -27.901858
Group 2                             -9.249582                              -25.311132                  -20.326818
Group 3                            -31.002229                              -12.835772                  -31.452065
Group 4                             -5.318555                               -3.823308                   -3.639734

However, it is important to note that if a call has high probability of appearing in a given group, it doesn’t automatically mean that it is very characteristic of this group. It might be the case that it is a very common call that has equally high probability of appearing in each group.

Luckily, it is possible to use Bayes rule to calculate the probability of call being characteristic of a given group. This is done simply by normalizing the columns of the beta matrix into 0-1 range. I have used logsumexp and subtraction before applying exp. The same result could be achieved by exponentiating first and then normalizing using division by sum of probabilities. However, calculations in log scale are numerically more stable, so it makes sense to stay in the log scale as long as possible.

Next, I identify the most characteristic API calls for each group - API calls that have probability of being characteristic of a given group higher than 80%.

characteristic_terms <- function(lda_model){
  
  sums <- apply(lda_model@beta,2,logSumExp)
  p_distinctive <- exp(sweep(lda_model@beta,2,sums))  
  
  n_topics <- nrow(lda_model@beta)
  results <- list()
  
  for(i in 1:n_topics){
    ordering <- order(lda_model@beta[i,],decreasing = T)
    ordered_terms <- lda_model@terms[ordering]
    ordered_selection <- (distinctive[i,] > 0.8)[ordering]
    selected_ordered_terms <- ordered_terms[ordered_selection]
    ordered_ps <- (exp(lda_model@beta[i,])[ordering])[ordered_selection]      
    names(ordered_ps) <- selected_ordered_terms
    
    results[[i]] <- ordered_ps
  }
  
  results

}

characteristic_calls <- characteristic_terms(winning_lda)

Because my knowledge of web tracking methods is fairly limited, I have consulted AI at this stage. For each group, I have taken the characteristic API calls and asked AI (Copilot) these questions:

I have made sure to ask these questions without mentioning web tracking. Interestingly, for each group, the AI have immediately identified that tracking/fingerprinting might be at play. Answers helped me establish names and characterization of these API call groups.

The series of barplots below shows characteristic calls for each call group. The length of the bar corresponds to how frequently is given call associated with a given group.

Identity & Persistance API Call Group

Functions focused on persistante storage and high-entropy identifiers.

Browser Personality API Call Group

Calls focused on characterizing user’s browser. This is a broad group - limiting to top 15 most common calls.

Rendering & Viewing Surface API Call Group

Mostly properties of the viewport, rendering quirks and motion sensor.

Capability & Hardware API Call Group

Mostly functions that can be used to get broad information on hardware and device capabilities. Big group, showing only top 15.

API Call Groups and Suspected Tracking

Application of LDA above have helped to identify certain groups of calls that tend to co-occur. This does not mean, however, that all of them are indication of fingerprinting. In order to see which groups or group combinations might be associated with fingerprinting, I’m using logistic regression on resource-group associations, with outcome (predicted) variable being the fingerprinting score calculated by tracker-radar. I want to see which call groups, or their combinations are associated with high fingerprinting score.

Of course, the fingerprinting score itself is just a heuristic and might not be always correct. But this is ok for initial exploratory analysis to discover interesting hypotheses. To make things easier for interpretation, I have made two simplifications:

Remember that I’m not using classifier here to accurately predict the fingerprinting score. The main purpose is to see which call groups or call group combinations might be associated with high fingerprinting scores and therefore likely associated with trackers/fingerprinting. I’m also not doing the more common train-test-validation split, but using information criterion (AIC) for model selection. I’m not interested in how exactly this model would work in production - I’m just interested in choosing better model.

You can see the excerpt of data used for training below:

assoc_thresholds <- colQuantiles(lda_winner@gamma,probs = 0.7)
binary_activations <- lda_winner@gamma > assoc_thresholds
binary_activations_df <- as.data.frame(activations)
binary_activations_df$outcome <- call_counts$fingerprinting > 1

head(binary_activations_df)

I have tried 2 versions of logistic regression:

The code below fits these models and outputs their AIC score (lower is better).

pred_model_base <- glm(outcome ~ V1 + V2 + V3 + V4,binary_activations_df,family = "binomial")
pred_model_pairs <- glm(outcome ~ V1 + V2 + V3 + V4 + V1*V2 + V1*V3 + V1*V4 + V2*V3 + V2*V4 + V3*V4,binary_activations_df,family = "binomial")
data.frame(model=c("Baseline Model","Interaction Model"),AIC = c(AIC(pred_model_base),
AIC(pred_model_pairs)))

Interaction model is a clear winner. The difference in AIC value might seem small at the first sight. However, AIC is defined on a logarithmic scale, so this difference is actually very significant. Next, I have generated all possible combinations of activations to see which will be predicted by the model to have more than 50% probability of tracking behavior.

The table below shows the call group combinations associated with tracking/fingerprinting behavior (p>50%) ordered from most probable to the least probable. Acronym for each combination was created by taking first letters of each call group taht it consists of.

all_combs <- expand.grid(V1=c(T,F),V2=c(T,F),V3=c(T,F), V4 = c(T,F))
all_combs$Probability <- predict(pred_model_pairs,all_combs,type="response")


all_combs %>%
  filter(Probability > 0.5) %>%
  arrange(desc(Probability)) %>%
  mutate(
    Acronym = paste0(ifelse(V1,"I",""),ifelse(V2,"BP",""),ifelse(V3,"R",""),ifelse(V4,"C","")),
    V1 = ifelse(V1,"Identity&Persistance",""),
    V2 = ifelse(V2,"Browser Personality",""),
    V3 = ifelse(V3,"Rendering&Viewport",""),
    V4 = ifelse(V4,"Capability","")
  ) %>%
  select(Acronym,V1,V2,V3,V4,Probability)

Analyzing Suspected Tracker Patterns in Time

Having established the API call groups and their combinations associated with fingerprinting, I can now use these combinations to see how the character of fingerprinting evolved in time. First, I’ll investigate overall development and then I’ll investigate the biggest players in tracking.

In order to do that, I had to do some data processing focused on annotation of each script resource by the call group combination it belongs to.

call_counts_in_time <-api_calls_in_time %>%
  pivot_wider(names_from=api,values_from = calls,values_fill = 0) 

call_counts_mat_in_time <- call_counts_in_time %>% 
  select(-c(date,region,domain,rule,fingerprinting)) %>% 
  as.matrix()

#Order columns in the same way as in LDA training
call_counts_mat_in_time <- call_counts_mat_in_time[,colnames(call_counts_mat)]

non_zero_row <- rowSums(call_counts_mat_in_time) > 0
call_counts_mat_in_time <- call_counts_mat_in_time[non_zero_row,]
call_counts_in_time_nonzero <- call_counts_in_time[non_zero_row,]

topic_posteriors <- posterior(lda_winner,call_counts_mat_in_time)
activations_in_time <- topic_posteriors$topics > assoc_thresholds
colnames(activations_in_time) <- c("V1","V2","V3","V4")

tracker_types_in_time <- activations_in_time %>% as.data.frame %>%
  mutate(
    tracker_type = case_when(
      V1 & V2 & V3 & V4 ~ "IBPRC",   # Identity + Browser Personality + Rendering + Capability
      V2 & V3 & V4      ~ "BPRC",    # Browser Personality + Rendering + Capability
      V2 & V4           ~ "BPC",     # Browser Personality + Capability
      V3 & V4           ~ "RC",      # Rendering + Capability
      V4                ~ "C",       # Capability only
      V1 & V2 & V3      ~ "IBPR",    # Identity + Browser Personality + Rendering
      V1 & V2 & V4      ~ "IBPC",    # Identity + Browser Personality + Capability
      V1 & V3 & V4      ~ "IRC",     # Identity + Rendering + Capability
      TRUE              ~ NA_character_
    )
  ) 

tracker_types_in_time$domain <- call_counts_in_time_nonzero$domain
tracker_types_in_time$date <- call_counts_in_time_nonzero$date

The plot below shows how the number of scripts associated with each pattern evolved over time. There are a couple of things to note here:

The plot below shows the same evolution of suspected tracker patterns in both absolute and relative terms (percentages) broken down for each major player. It is apparent that the same pattern seems to hold with nearly all major players. Especially Google, Akamai and Adobe seem to have sudden uptick in IBPR and RC, while Microsoft seems to always have had a significant part of its resources aligned with the RC pattern.

Finally, I’ve decided to zoom in into the grain of individual calls. I wanted to answer the question - how do the proportions of different API calls differ pre and post 2022? In order to identify changes that are significant, I’m using chi.square test over 2x2 contingency matrices. This means that for every call I create a matrix of the form

X Calls_of_function_f Other_calls
Before 22 n_f_pre22 n_other_pre22
After 22 n_f_post22 n_other_post22

And then apply chis.square test to derive significance p-values. I’ve chosen the significance threshold 0.01. However, because I’m performing a great number of these tests, the significance threshold needs to be divided by the number of tests so that results have expected error rate (this is called Bonferroni correction). Commentary of results is part of the next section - Conclusion.

period_api_calls <- api_calls_in_time %>% 
  select(-c(region,domain,rule)) %>%
  mutate(pre_2022 = date < as.Date("2022-1-1")) %>%
  group_by(pre_2022,api) %>%
  summarize(calls = sum(calls))

period_api_calls_wide <- period_api_calls %>%
  group_by(pre_2022) %>%
  mutate(period_total_calls = sum(calls)) %>%
  ungroup() %>%
  pivot_wider(names_from = pre_2022,values_from = c(calls,period_total_calls),values_fill = 0) %>%
  mutate(period_total_calls_TRUE = ifelse(period_total_calls_TRUE == 0,period_total_calls_TRUE[period_total_calls_TRUE > 0][1],period_total_calls_TRUE))


chi_square_calls <- function(df){
  calls_pre22 <- df$calls_TRUE
  non_calls_pre22 <- df$period_total_calls_TRUE - df$calls_TRUE
  calls_post22 <- df$calls_FALSE
  non_calls_post22 <- df$period_total_calls_FALSE - df$calls_FALSE
  
  all_data_mat <- cbind(calls_pre22,non_calls_pre22,calls_post22,non_calls_post22)
  apply(all_data_mat,1,function(row){
    chisq.test(rbind(
      c(row["calls_pre22"],row["non_calls_pre22"]),
      c(row["calls_post22"],row["non_calls_post22"])
    ))$p.value
  })
}

period_api_calls_wide$significant <- chi_square_calls(period_api_calls_wide) < 7.874016e-05 #bonferoni correction
period_api_calls_wide %>% 
  filter(significant) %>%
  select(api,calls_TRUE,calls_FALSE) %>%
  arrange(calls_TRUE) %>%
  rename(calls_before_2022 = calls_TRUE,calls_after_2022 = calls_FALSE)

Conclusion

It does seem like on top of the shift in call groups, there is a significant difference in proportions before 2022 and after 2022 for pretty much every individual API call. What’s more important some heavily used API calls are completely absent before 2022. This leads me to a couple of hypothesis explaining the sudden shift of tracking patterns in 2022:

I have a question for you, dear reader, what do you think is the most likely explanation? Feel free to share any ideas you have on my linkedin or via my e-mail .

LS0tCnRpdGxlOiAiV2hhdCBoYXBwZW5kIGluIHdlYiB0cmFja2luZyBhcm91bmQgMjAyMj8iCmF1dGhvcjogU3RhbmlzbGF2IFNtYXRhbmEKbWFpbDogc3RhbmlzbGF2c21hdGFuYUBnbWFpbC5jb20Kb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgdGhlbWU6IGpvdXJuYWwKICAgIHRvYzogeWVzCi0tLQojIyMgSW50cm9kdWN0aW9uCgpIZWxsbyBkZWFyIHJlYWRlciEKCkJ5IHRoaXMgdGltZSwgeW91IHByb2JhYmx5IGtub3cgbWUgcmVhc29uYWJseSB3ZWxsIGZyb20gYWxsIHRoZSBDVnMgSSBoYXZlIHNlbnQgeW91LiBJdCBzZWVtcyBsaWtlIHRob3NlIHdlcmVuJ3QgdGhhdCBpbnRlcmVzdGluZyB0byB5b3UsIHdoaWNoIEkgdG90YWxseSB1bmRlcnN0YW5kLiBCdXQgYXQgdGhlIHNhbWUgdGltZSwgQ1YgaXMgbm90IHJlYWxseSBhYmxlIHRvIGZ1bGx5IGNvbW11bmljYXRlIGhvdyBJIGFjdHVhbGx5IHdvcmsgYW5kIHdoZXRoZXIgdGhlIHRoaW5ncyB0aGF0IEknbSBhYmxlIHRvIGNyZWF0ZSBjb3VsZCBhZGQgdmFsdWUgdG8gRHVja0R1Y2tHby4gVGhhdCdzIHdoeSBJIGhhdmUgZGVjaWRlZCB0byBzaG93IHlvdSBhIHBpZWNlIG9mIG15IHdvcmsgYnkgY29sbGVjdGluZyBhbmQgYW5hbHl6aW5nIHlvdXIgZGF0YSBmcm9tIHRoZSB0cmFja2VyLXJhZGFyIHByb2plY3QuIEkga25vdyB0aGF0IHRoaXMgbWlnaHQgYmUgYSBsaXR0bGUgYml0IHdlaXJkIGFuZCBhbm5veWluZy4gQnV0LCBJIHNlZSB3b3JraW5nIGF0IER1Y2tEdWNrR28gYXMgYSBncmVhdCBvcHBvcnR1bml0eSAtIGZhciB0b28gZ3JlYXQgdG8gZ2l2ZSB1cCByaWdodCBhd2F5LiBXaXRoIHRoYXQgYmVpbmcgc2FpZCwgaWYgeW91IGRlY2lkZSB0byByZWplY3QgbXkgYXBwbGljYXRpb24gYWdhaW4sIGFmdGVyIHNlZWluZyBhIHBpZWNlIG9mIG15IHdvcmssIEkgd2lsbCBmdWxseSBhY2NlcHQgdGhhdCBhbmQgSSB3b24ndCByZWFjaCBvdXQgYWdhaW4uIFRoYW5rIHlvdSEKCi0tIFN0YW5pc2xhdiAiU3Rhbm8iIFNtYXRhbmEKCiMjIyBXaGF0IGhhcHBlbmQgaW4gMjAyMj8gQW5hbHlzaXMgb2YgdHJhY2tlci1yYWRhciBkYXRhIGluIHRpbWUuCgoqKnRsZHI7KiogSXQgc2VlbXMgbGlrZSBzb21ldGhpbmcgaW50ZXJlc3RpbmcgaGF2ZSBoYXBwZW5kIHNvbWUgdGltZSBiZXR3ZWVuIDIwMjIgYW5kIDIwMjMgaW4gdGhlIHdvcmxkIG9mIHdlYnNpdGUgdHJhY2tlcnMuIFRoZSBuYXR1cmUgb2YgdHJhY2tlcnMgaGF2ZSBzdWRkZW5seSBzaGlmdGVkIGFuZCB0aGlzIHNoaWZ0IHNlZW1zIHRvIGJlIGNvbm5lY3RlZCB3aXRoIGZpbmdlcnByaW50aW5nIGJhc2VkIG9uIHZpZXdwb3J0IGNoYXJhY3RlcmlzdGljcy4gIERvIHlvdSBrbm93IHdoYXQgY291bGQgaGF2ZSBoYXBwZW5kPyBJZiB5b3Ugd2FudCB0byBnZXQgcmlnaHQgaW50byByZXN1bHRzLCBqdW1wIHRvIHRoZSBzZWN0aW9uIFtBbmFseXppbmcgU3VzcGVjdGVkIFRyYWNrZXIgUGF0dGVybnMgaW4gVGltZV0uIElmIHlvdSB3YW50IHRvIHNlZSBob3cgSSB3ZW50IG92ZXIgYW5hbHl6aW5nIHRoaXMgZGF0YSBzdGVwIGJ5IHN0ZXAgLSBjb250aW51ZSByZWFkaW5nLgoKSSd2ZSBkZWNpZGVkIHRvIGRvd25sb2FkIGFuZCBhbmFseXplIHdlYnNpdGUgdHJhY2tpbmcgZGF0YSBjb2xsZWN0ZWQgYnkgRHVja0R1Y2tHbydzIHRyYWNrZXItcmFkYXIgd2l0aCBmb2xsb3dpbmcgZ29hbHM6CgoqIENoYXJhY3Rlcml6ZSBwb3RlbnRpYWwgdHJhY2tlcnMgYmFzZWQgb24gY29tYmluYXRpb25zIG9mIEFQSSBjYWxscyB0aGV5IGFyZSBwZXJmb3JtaW5nLgoqIFVuZGVyc3RhbmQgaG93IGNoYXJhY3RlciBvZiB0cmFja2VycyBkaWZmZXJzIGFjcm9zcyBiaWcgcGxheWVycyBhbmQgaG93IGl0IGV2b2x2ZWQgaW4gdGltZS4KCkFsbCBvZiB0aGUgZGF0YSBJJ3ZlIHVzZWQgaW4gdGhlIGFuYWx5c2lzIHdhcyBleHRyYWN0ZWQgZnJvbSBkb21haW4ganNvbiBmaWxlcyBhdmFpbGlhYmxlIGFjcm9zcyBhbGwgcmVsZWFzZXMgb2YgW10oaHR0cHM6Ly9naXRodWIuY29tL2R1Y2tkdWNrZ28vdHJhY2tlci1yYWRhcikuIFlvdSBjYW4gc2VlIHRoZSBzaGFwZSBvZiB0aGUgcmF3IGlucHV0IGRhdGEgYmVsb3cuIEVhY2ggcmVjb3JkcyBjb3JyZXNwb25kcyB0byBvbmUgdHlwZSBvZiBBUEkgY2FsbCBpbiBvbmUgcmVzb3VyY2Ugb2Ygb25lIGRvbWFpbiwgbWVhbmluZyB0aGF0IHRoZSBvdmVyYWxsIGdyYWluIG9mIHRoZSB0YWJsZSBpcyByZWxlYXNlX3RhZyAoZGF0ZSksIHJlZ2lvbiwgZG9tYWluLCByZXNvdXJjZSwgYXBpIGNhbGwuIFRoZSBkYXRhIGNvbnRhaW5zIGZvbGxvd2luZyBjb2x1bW5zOgoKKiAqKmRhdGUqKiAtIGRhdGUgb2YgZGF0YSBjb2xsZWN0aW9uLCBleHRyYWN0ZWQgZnJvbSByZWxlYXNlX3RhZwoqICoqcmVnaW9uKiogLSByZWdpb24gaW4gd2hpY2ggdGhpcyByZWNvcmQgd2FzIGNvbGxlY3RlZCAoY29ycmVzcG9uZHMgdG8gc3ViZm9sZGVycyBvZiBkb21haW4gZm9sZGVyKQoqICoqcnVsZSoqIC0gcmVnZXggaWRlbnRpZnlpbmcgdGhlIHJlc291cmNlCiogKiphcGkqKiAtIGFwaSBmdW5jdGlvbiAgCiogKipjYWxscyoqIC0gbnVtYmVyIG9mIGNhbGxzIHRvIHRoZSBhcGkgZnVuY3Rpb24KKiAqKmZpbmdlcnByaW50aW5nKiogLSBmaW5nZXJwcmludGluZyBzY29yZSBkZXJpdmVkIGluIHRyYWNrZXItcmFkYXIgZm9yIHRoZSByZXNvdXJjZQoKCmBgYHtyfQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRvcGljbW9kZWxzKQpsaWJyYXJ5KHRpZHlyKQoKYXBpX2NhbGxzX2luX3RpbWUgPC0gcmVhZC5jc3YoImRhdGEvYXBpX2NhbGxzX2J5X3JlbGVhc2UuY3N2IikgJT4lCiAgZmlsdGVyKHR5cGUgPT0gIlNjcmlwdCIpIAoKI2NvbnZlcnRpbmcgcmVsZWFzZSB0YWdzIHRvIGRhdGVzCnJlbGVhc2VfdGFnX3RvX2RhdGUgPC0gZGF0YS5mcmFtZShyZWxlYXNlX3RhZyA9IHVuaXF1ZShhcGlfY2FsbHNfaW5fdGltZSRyZWxlYXNlX3RhZykpICU+JQogIHNlcGFyYXRlKHJlbGVhc2VfdGFnLCBpbnRvID0gYygieWVhciIsICJtb250aCIsICJkYXkiKSwgc2VwID0gIlxcLiIsIGZpbGwgPSAicmlnaHQiLHJlbW92ZSA9IEYpICU+JQogIG11dGF0ZSgKICAgIG1vbnRoID0gc3ByaW50ZigiJTAyZCIsIGFzLmludGVnZXIobW9udGgpKSwKICAgIGRheSAgID0gaWZlbHNlKGlzLm5hKGRheSksICIwMSIsIHNwcmludGYoIiUwMmQiLCBhcy5pbnRlZ2VyKGRheSkpKSwKICAgIGRhdGUgID0gYXMuRGF0ZShwYXN0ZSh5ZWFyLCBtb250aCwgZGF5LCBzZXAgPSAiLSIpKQogICkgJT4lCiAgc2VsZWN0KHJlbGVhc2VfdGFnLGRhdGUpCgphcGlfY2FsbHNfaW5fdGltZSA8LSBhcGlfY2FsbHNfaW5fdGltZSAlPiUKICBpbm5lcl9qb2luKHJlbGVhc2VfdGFnX3RvX2RhdGUpICU+JQogIHNlbGVjdChkYXRlLHJlZ2lvbixkb21haW4scnVsZSxhcGksY2FsbHMsZmluZ2VycHJpbnRpbmcpCiAgCmhlYWQoYXBpX2NhbGxzX2luX3RpbWUpCmBgYAoKUHl0aG9uIHNjcmlwdHMgSSd2ZSB1c2VkIHRvIHNjcmFwZSB0cmFja2VyLXJhZGFyIHRvZ2V0aGVyIHdpdGggdGhlIHNvdXJjZSBjb2RlIG9mIHRoaXMgbm90ZWJvb2sgYW5kIG90aGVyIGFydGlmYWN0cyBhcmUgYXZhaWxpYWJsZSBoZXJlIFRPRE8uCgpOT1RFOiBQeXRob24gaXMgbXkgcHJpbWFyeSBsYW5ndWFnZSwgYnV0IEkgbG92ZSB0byB1c2UgUiBmb3IgZXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcywgc28gdGhhdCdzIHdoYXQgSSdtIHVzaW5nIGhlcmUgOikgCgojIyMjIEFJIFVzZSBEdXJpbmcgdGhpcyBBbmFseXNpcwpEdXJpbmcgdGhpcyBwcm9jZXNzIEkndmUgdXNlZCBBSSAoR2l0SHViIENvcGlsb3QpIGF0IHRpbWVzIHRvIGNvbXBsZW1lbnQgbXkgcHJpbWFyeSBza2lsbHMuIEkgaGF2ZSB1c2VkIEFJIGV4YWN0bHkgaW4gdGhpcyB3YXk6CgoqIEkgKipoYXZlIHVzZWQgQUkqKiB0byBnZW5lcmF0ZSBweXRob24gc2NyaXB0cyBmb3Igc2NyYXBpbmcgb2YgW10oaHR0cHM6Ly9naXRodWIuY29tL2R1Y2tkdWNrZ28vdHJhY2tlci1yYWRhciksIGJlY2F1c2UgSSBkaWRuJ3Qgd2FudCB0byB3YXN0ZSB0aW1lIGltcGxlbWVudGluZyBzY3JhcGluZyBhbmQgdG8gZm9jdXMgb24gYW5hbHlzaXMuCiogSSBkaWQgKipub3QgdXNlKiogQUkgdG8gY29uZHVjdCB0aGUgYW5hbHlzaXMgb2YgdGhpcyBkYXRhIGFuZCBjcmVhdGUgdGhpcyBub3RlYm9vaywgYmVjYXVzZSB0aGF0J3MgdGhlIHBhcnQgdGhhdCBJIGVuam95LCB3aGVyZSBJIGV4Y2VsLCBhbmQgd2hlcmUgSSBkbyBhIGJldHRlciBqb2IuCiogSSAqKmhhdmUgdXNlZCBBSSoqIHRvIGhlbHAgaW50ZXJwcmV0IExEQSB0b3BpY3MgKGRldGFpbHMgaW4gdGhlIG5leHQgc2VjdGlvbiksIGJlY2F1c2UgbXkga25vd2xlZGdlIG9mIHdlYiB0cmFja2VycyBpcyB2ZXJ5IGxpbWl0ZWQgOikgCgoKIyMjIENoYXJhY3Rlcml6aW5nIEFQSSBDYWxscyAtIExhdGVudCBEaXJpY2hsZXQgQWxsb2NhdGlvbgoKRmluZ2VycHJpbnRpbmcgaXMgY29ubmVjdGVkIHRvIGNlcnRhaW4gSmF2YXNjcmlwdCBBUEkgY2FsbHMgdGhhdCBjYW4gaGVscCBjcmVhdGUgcHJlY2lzZSBpZGVudGlmaWNhdGlvbiBvZiBhIGRldmljZSBvciBhIHVzZXIuIE15IGFzc3VtcHRpb24gaXMgdGhhdCB0aGVyZSBhcmUgY2VydGFpbiB0eXBlcyBvZiB0cmFja2VycyBhbmQgdGhhdCBlYWNoIHRyYWNrZXIgdHlwZSBpcyBhc3NvY2lhdGVkIHdpdGggY2VydGFpbiBBUEkgY2FsbHMuIEluIG9yZGVyIHRvIHNlZSB3aGF0IHRoZXNlIHN1c3BlY3RlZCB0cmFja2VyIHR5cGVzIG1pZ2h0IGJlLCBJIG5lZWQgZmlyc3QgdG8gaWRlbnRpZnkgQVBJIGNhbGxzIHRoYXQgdGVuZCB0byBvY2N1ciB0b2dldGhlciAtIEFQSSBjYWxsIGdyb3Vwcy4KCk5lYXJseSBhbnkgY2x1c3RlcmluZyBhcHByb2FjaCBjb3VsZCBiZSB1c2VkIHRvIGNyZWF0ZSBzb21lIGdyb3VwcyBvZiBBUEkgY2FsbHMsIGJ1dCBJIGhhdmUgZGVjaWRlZCB0byB1c2UgKkxhdGVudCBEaXJpY2hsZXQgQWxsb2NhdGlvbiogKExEQSwgbm90IHRvIGJlIGNvbmZ1c2VkIHdpdGggTGluZWFyIERpc2NyaW1pbmFudCBBbmFseXNpcykuIFdoaWxlIHRoaXMgbW9kZWwgdHJhZGl0aW9uYWxseSBjb21lcyBmcm9tIHRoZSBmaWVsZCBvZiBOYXR1cmFsIExhbmd1YWdlIFByb2Nlc3NpbmcsIGl0cyBhc3N1bXB0aW9ucyBleGFjdGx5IG1hdGNoIHRoaXMgc2V0dGluZyAtIHNwYXJzZSBjb3VudCBkYXRhIChjb3VudHMgb2YgYXBpIGNhbGxzIHdpdGggb25seSBmZXcgYXBwZWFyaW5nIGluIGVhY2ggcmVzb3VyY2UpLiBEb24ndCBiZSBzY2FyZWQgYnkgdGhlIGNvbXBsaWNhdGVkIG5hbWUsIGl0J3MgYWN0dWFsbHkgYSBmYWlybHkgc2ltcGxlIHVuc3VwZXJ2aXNlZCBnZW5lcmF0aXZlIG1vZGVsLiBMZXQgbWUgZXhwbGFpbiBob3cgaXQgc3BlY2lmaWNhbGx5IHdvcmtzIGZvciB0aGlzIGFuYWx5c2lzLgoKRXZlcnkgc3RhdGlzdGljYWwgbW9kZWwgYXNzdW1lcyBjZXJ0YWluIHZpZXcgb2YgdGhlIHdvcmxkIGFuZCB0aGVuIGhlbHBzIHVzIGRlcml2ZSBuZXcgaW5mb3JtYXRpb24gYmFzZWQgb24gdGhpcyB2aWV3LiBJbiB0aGlzIHNldHRpbmcsIExEQSBhc3N1bWVzIHRoYXQgY2VydGFpbiBBUEkgY2FsbHMgdGVuZCB0byBvY2N1ciB0b2dldGhlci4gVGhlc2UgY28tb2N1cmluZyBjYWxscyBjYW4gYmUgZ3JvdXBlZCBpbnRvIGxhcmdlciBjYWxsIGdyb3Vwcy4gRWFjaCBjYWxsIGNhbiBiZSBhIG1lbWJlciBvZiBtdWx0aXBsZSBncm91cHMgYW5kIHRoaXMgbWVtYmVyc2hpcCBpcyBzb2Z0LiBUaGlzIG1lYW5zIHRoYXQgZWFjaCBjYWxsIGlzIGFzc29jaWF0ZWQgd2l0aCBlYWNoIGdyb3VwICh0b3BpYyBpbiBMREEgbGFuZ3VhZ2UpIHdpdGggY2VydGFpbiBwcm9iYWJpbGl0eSBiZXR3ZWVuIDAgYW5kIDEuIAoKV2l0aCBncm91cHMgZXN0YWJsaXNoZWQsIHNjcmlwdCByZXNvdXJjZXMgKGRvY3VtZW50cyBpbiBMREEgbGFuZ3VhZ2UpIGFyZSB2aWV3ZWQgYXMgZGlmZmVyZW50IG1peGVzIG9mIGdyb3Vwcy4gRWFjaCBtaXggaXMgY2hhcmFjdGVyaXplZCBieSByZXNvdXJjZSB0byBncm91cCBhc3NvY2lhdGlvbnMgYmV0d2VlbiAwLTEgdGhhdCBhZGQgdXAgdG8gMS4gR3JvdXBzLCBhc3NvY2lhdGlvbnMgYmV0d2VlbiByZXNvdXJjZXMgYW5kIGdyb3VwcyBhbmQgYXNzb2NpYXRpb25zIGJldHdlZW4gZ3JvdXBzIGFuZCBjYWxscyBhcmUgZGVyaXZlZCBieSBMREEgZnJvbSBkYXRhIHdpdGhvdXQgc3VwZXJ2aXNpb24uIFRoZSBvbmx5IHJlcXVpcmVkIHBhcmFtZXRlciBpcyB0aGUgbnVtYmVyIG9mIGdyb3VwcyAodG9waWNzKS4gSSB3aWxsIGRlc2NyaWJlIHNlbGVjdGlvbiBvZiB0aGUgbnVtYmVyIG9mIGdyb3VwcyBsYXRlci4gCgpUaGUgZGlhZ3JhbSBiZWxvdyBpbGx1c3RyYXRlcyBob3cgTERBIG1vZGVscyBhbiBleGFtcGxlIG9mIDIgc2NyaXB0IHJlc291cmNlcy4gRWFjaCBzY3JpcHQgaXMgYXNzb2NpYXRlZCB3aXRoIGNhbGwgZ3JvdXBzIDEtNCBhbmQgaXRzIGdyb3VwIG1peCBkaWN0YXRlcyB0aGUgYWN0dWFsIGNhbGxzIHRoYXQgYXBwZWFyIGluIGVhY2ggc2NyaXB0IHJlc291cmNlLiBGb3IgaW5zdGFuY2UsIGV4YW1wbGUxLmpzIGlzIDcwJSBncm91cDEgYW5kIDcwJSBncm91cDIuIFRoZXJlZm9yZSwgaXRzIEFQSSBjYWxscyBjb21lIG1vc3RseSBmcm9tIGdyb3VwMiBhbmQgdGhlbiBhbHNvIGZyb20gZ3JvdXAxLiBUaGlzIGlzIGEgdG95IGV4YW1wbGUsIGluIHJlYWwgTERBLCBlYWNoIGRvY3VtZW50IGlzIHVzdWFsbHkgYXNzb2NpYXRlZCB3aXRoIGVhY2ggdG9waWMgdG8gYW4gZXh0ZW50LgoKV2hhdCBkb2VzIExEQSBlbmFibGUgbWUgdG8gZG8/CgoqIFJlZHVjZSBkaW1lbnNpb25hbGl0eSBpbiBhbiBpbnRlcnByZXRhYmxlIHdheS4gSW5zdGVhZCBvZiBjaGFyYWN0ZXJpemluZyByZXNvdXJjZXMgYnkgYWxsIGFwaSBjYWxsIHR5cGVzLCBJIGNhbiBjaGFyYWN0ZXJpemUgdGhlbSBieSB3aG9sZSBjYWxsIGdyb3Vwcy4KKiBVbmRlcnN0YW5kIHdoaWNoIEFQSSBjYWxscyB0ZW5kIHRvIGNvLW9jY3VyIGFuZCB1bmRlcnN0YW5kIHdoYXQgd2hhdCB0aGVzZSBncm91cHMgb2YgY28tb2NjdXJpbmcgY2FsbHMgbWlnaHQgcmVwcmVzZW50LgoKCiFbXShmaWcvbGRhX2Zvcl9hcGlfY2FsbHMuanBlZykKCkluIG9yZGVyIHRvIGRlcml2ZSBjYWxsIGdyb3VwcyBJIHdpbGwgdXNlIG9ubHkgZGF0YSBmcm9tIHRoZSBtb3N0IHJlY2VudCByZWxlYXNlLiBBZnRlcndhcmRzLCBJJ2xsIGFwcGx5IHRoZSBtb2RlbCB0byB0aGUgZnVsbCBkYXRhIHNwYW5uaW5nIGFsbCB0cmFja2VyLXJhZGFyIHJlbGVhc2VzLiBJIGRvIHRoaXMgYmVjYXVzZSBJIGFzc3VtZSB0aGF0IHRvZGF5IHdlIGhhdmUgdGhlIGJyb2FkZXN0IHNwZWN0cnVtIG9mIHRyYWNraW5nIHRlY2huaXF1ZXMgYXZhaWxhYmxlIGFuZCB0aGUgcGFzdCB3YXMgZXF1YWxseSBvciBsZXNzIGRpdmVyc2UuCgoKYGBge3J9CmFwaV9jYWxsc19yZWNlbnQgPC0gCiAgYXBpX2NhbGxzX2luX3RpbWUgJT4lCiAgZmlsdGVyKGRhdGUgPT0gbWF4KGFwaV9jYWxsc19pbl90aW1lJGRhdGUpKQoKaGVhZChhcGlfY2FsbHNfcmVjZW50KQpgYGAKCk9uZSBsYXN0IHRoaW5nIEkgbmVlZCB0byBkbyBpbiBvcmRlciB0byBhcHBseSBMREEgaXMgdG8gZGV0ZXJtaW5lIHRoZSBudW1iZXIgb2YgY2FsbCBncm91cHMgKHRvcGljcykgLSBrLiBUaGVyZSBhcmUgbWFueSBtZXRob2RzIG9mIGRvaW5nIHRoYXQuIEkgaGF2ZSBkZWNpZGVkIHRvIHVzZSBvbmUgYmFzZWQgb24gY29pc25lIHNpbWlsYXJpdHkgKENhbyBldC4gYWwuIDIwMDkpLCB3aGljaCBhaW1zIHRvIGNyZWF0ZSBncm91cHMgdGhhdCBhcmUgYXMgZGlzdGluY3QgZnJvbSBvbmUgYW5vdGhlciBhcyBwb3NzaWJsZS4gVGhpcyBpcyBoZWxwZnVsIGJlY2F1c2UgTERBIHNvbWV0aW1lcyB0ZW5kcyB0byBjcmVhdGUgZ3JvdXBzICh0b3BpY3MpIHRoYXQgYXJlIGhlYXZpbHkgb3ZlcmxhcHBpbmcuIFByZXNlbmNlIG9mIG11bHRpcGxlIG92ZXJsYXBwaW5nIGdyb3VwcyB0aGF0IGNvcnJlc3BvbmQgcm91Z2hseSB0byB0aGUgc2FtZSBjb25jZXB0IGNyZWF0ZXMgbm9pc2UgdGhhdCBtYWtlcyBpbnRlcnByZXRhdGlvbiBoYXJkZXIuCgpNb3JlIGZvcm1hbGx5LCBrLCBmb3Igd2hpY2ggdGhlIG1vZGVsIGhhcyBncm91cHMgd2l0aCBzbWFsbGVzdCBhdmVyYWdlIHBhaXJ3aXNlIGNvc2luZSBzaW1pbGFyaXR5IGlzIHNlbGVjdGVkIGFzIHRoZSB3aW5uaW5nIGsuCgpgYGB7cn0KY29zaW5lX3NpbSA8LSBmdW5jdGlvbihNKSB7CiAgIyByb3cgbm9ybXMKICBub3JtcyA8LSBzcXJ0KHJvd1N1bXMoTSAqIE0pKQogIAogICMgZGVub21pbmF0b3IgdmlhIG91dGVyCiAgZGVuIDwtIG91dGVyKG5vcm1zLCBub3JtcywgIioiKQogIAogICMgZG90IHByb2R1Y3RzCiAgZG90IDwtIE0gJSolIHQoTSkKICAKICAjIGNvc2luZSBzaW1pbGFyaXR5CiAgY29zX3NpbSA8LSBkb3QgLyBkZW4KICBjb3Nfc2ltCn0KCmF2Z19jb3Nfc2ltIDwtIGZ1bmN0aW9uKE0pewogIGNzaW0gPC0gY29zaW5lX3NpbShNKQogIG1lYW4oY3NpbVt1cHBlci50cmkoY3NpbSldKSAgCn0KCmNhbGxfY291bnRzIDwtIGFwaV9jYWxsc19yZWNlbnQgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbT1hcGksdmFsdWVzX2Zyb20gPSBjYWxscyx2YWx1ZXNfZmlsbCA9IDApIAoKY2FsbF9jb3VudHNfbWF0IDwtIGNhbGxfY291bnRzICU+JSAKICBzZWxlY3QoLWMocmVnaW9uLGRvbWFpbixydWxlLGZpbmdlcnByaW50aW5nLGRhdGUpKSAlPiUgCiAgYXMubWF0cml4KCkKCiNUaGlzIHRha2VzIGEgbG90IG9mIHRpbWUsIGp1c3QgbG9hZCB0aGUgcmRzIGlmIHdpbm5lciB3YXMgYWxyZWFkeSBjYWxjdWxhdGVkCmlmKGZpbGUuZXhpc3RzKCJkYXRhL3dpbm5pbmdfbGRhLlJkcyIpKXsKICB3aW5uaW5nX2xkYSA9IHJlYWRSRFMoImRhdGEvd2lubmluZ19sZGEuUmRzIikKfWVsc2UKewogIGNzaW1zIDwtIHJlcCgwLHRpbWVzPTE5KQogIGZvcihrIGluIDI6MjApewogICAgY2F0KHBhc3RlKCJQcm9jZXNzaW5nIExEQSAiLGssIlxuIikpCiAgICB0b3BpY19tb2RlbCA8LSBMREEoY2FsbF9jb3VudHNfbWF0LGspICAgIAogICAgY3NpbXNbay0xXSA8LSBhdmdfY29zX3NpbSh0b3BpY19tb2RlbEBiZXRhKQogIH0KCiAgCiAgc2VsZWN0ZWRfayA8LSAoMjoyMClbd2hpY2gubWluKGNzaW1zKV0KICB3aW5uaW5nX2xkYSA9IExEQShjYWxsX2NvdW50c19tYXQsc2VsZWN0ZWRfaykKICAKICAKfQoKc2VsZWN0ZWRfayA9IG5jb2wod2lubmluZ19sZGFAZ2FtbWEpCgpgYGAKCkxEQSB3aXRoIDQgdG9waWNzIHdhcyBzZWxlY3RlZCBhcyB0aGUgbW9kZWwgd2l0aCB0aGUgbG93ZXN0IGF2ZXJhZ2UgY29zaW5lIHNpbWlsYXJpdHkgYmV0d2VlbiBBUEkgY2FsbCBncm91cHMgKHRvcGljcykuIE5leHQsIEkgd2FudCB0byBjaGFyYWN0ZXJpemUgZWFjaCBncm91cCAodG9waWMpIGFuZCBmb3IgdGhhdCBJJ2xsIGlkZW50aWZ5IGNhbGxzIHRoYXQgYXJlIG1vc3QgY2hhcmFjdGVyaXN0aWMgZm9yIGVhY2ggZ3JvdXAuIFRoaXMgY2FuIGJlIGRvbmUgYnkgbG9va2luZyBpbnRvIHRoZSBiZXRhIG1hdHJpeCB0aGF0IGhhcyA0IHJvd3MsIG9uZSBmb3IgZWFjaCBncm91cCwgIGFuZCBhcyBtYW55IGNvbHVtbnMgYXMgdGhlcmUgYXJlIGRpc3RpbmN0IGNhbGwgdHlwZXMuIEVhY2ggY2VsbCBjb3JyZXNwb25kcyB0byBwcm9iYWJpbGl0eSAoaW4gbG9nIHNjYWxlKSBvZiBjb3JyZXNwb25kaW5nIGNhbGwgYmVpbmcgYXNzb2NpYXRlZCB3aXRoIGEgY29ycmVzcG9uZGluZyBjYWxsIGdyb3VwLiBTbWFsbCBleGNlcnB0IG9mIHRoZSBtYXRyaXggYmVsb3c6CgpgYGB7cn0gCmFzc29jX21hdCA8LSB3aW5uaW5nX2xkYUBiZXRhWywxOjNdCmNvbG5hbWVzKGFzc29jX21hdCkgPC0gd2lubmluZ19sZGFAdGVybXNbMTozXQpyb3duYW1lcyhhc3NvY19tYXQpIDwtIHBhc3RlKCJHcm91cCIsMTo0KQphc3NvY19tYXQKYGBgCkhvd2V2ZXIsIGl0IGlzIGltcG9ydGFudCB0byBub3RlIHRoYXQgaWYgYSBjYWxsIGhhcyBoaWdoIHByb2JhYmlsaXR5IG9mIGFwcGVhcmluZyBpbiBhIGdpdmVuIGdyb3VwLCBpdCBkb2Vzbid0IGF1dG9tYXRpY2FsbHkgbWVhbiB0aGF0IGl0IGlzIHZlcnkgY2hhcmFjdGVyaXN0aWMgb2YgdGhpcyBncm91cC4gSXQgbWlnaHQgYmUgdGhlIGNhc2UgdGhhdCBpdCBpcyBhIHZlcnkgY29tbW9uIGNhbGwgdGhhdCBoYXMgZXF1YWxseSBoaWdoIHByb2JhYmlsaXR5IG9mIGFwcGVhcmluZyBpbiBlYWNoIGdyb3VwLiAKCkx1Y2tpbHksIGl0IGlzIHBvc3NpYmxlIHRvIHVzZSBCYXllcyBydWxlIHRvIGNhbGN1bGF0ZSB0aGUgcHJvYmFiaWxpdHkgb2YgY2FsbCBiZWluZyAqKmNoYXJhY3RlcmlzdGljKiogb2YgYSBnaXZlbiBncm91cC4gVGhpcyBpcyBkb25lIHNpbXBseSBieSBub3JtYWxpemluZyB0aGUgY29sdW1ucyBvZiB0aGUgYmV0YSBtYXRyaXggaW50byAwLTEgcmFuZ2UuIEkgaGF2ZSB1c2VkIGxvZ3N1bWV4cCBhbmQgc3VidHJhY3Rpb24gYmVmb3JlIGFwcGx5aW5nIGV4cC4gVGhlIHNhbWUgcmVzdWx0IGNvdWxkIGJlIGFjaGlldmVkIGJ5IGV4cG9uZW50aWF0aW5nIGZpcnN0IGFuZCB0aGVuIG5vcm1hbGl6aW5nIHVzaW5nIGRpdmlzaW9uIGJ5IHN1bSBvZiBwcm9iYWJpbGl0aWVzLiBIb3dldmVyLCBjYWxjdWxhdGlvbnMgaW4gbG9nIHNjYWxlIGFyZSBudW1lcmljYWxseSBtb3JlIHN0YWJsZSwgc28gaXQgbWFrZXMgc2Vuc2UgdG8gc3RheSBpbiB0aGUgbG9nIHNjYWxlIGFzIGxvbmcgYXMgcG9zc2libGUuCgpOZXh0LCBJIGlkZW50aWZ5IHRoZSBtb3N0IGNoYXJhY3RlcmlzdGljIEFQSSBjYWxscyBmb3IgZWFjaCBncm91cCAtIEFQSSBjYWxscyB0aGF0IGhhdmUgcHJvYmFiaWxpdHkgb2YgYmVpbmcgY2hhcmFjdGVyaXN0aWMgb2YgYSBnaXZlbiBncm91cCBoaWdoZXIgdGhhbiA4MCUuCgpgYGB7cn0KbGlicmFyeShtYXRyaXhTdGF0cykKY2hhcmFjdGVyaXN0aWNfdGVybXMgPC0gZnVuY3Rpb24obGRhX21vZGVsKXsKICAKICBzdW1zIDwtIGFwcGx5KGxkYV9tb2RlbEBiZXRhLDIsbG9nU3VtRXhwKQogIHBfZGlzdGluY3RpdmUgPC0gZXhwKHN3ZWVwKGxkYV9tb2RlbEBiZXRhLDIsc3VtcykpICAKICAKICBuX3RvcGljcyA8LSBucm93KGxkYV9tb2RlbEBiZXRhKQogIHJlc3VsdHMgPC0gbGlzdCgpCiAgCiAgZm9yKGkgaW4gMTpuX3RvcGljcyl7CiAgICBvcmRlcmluZyA8LSBvcmRlcihsZGFfbW9kZWxAYmV0YVtpLF0sZGVjcmVhc2luZyA9IFQpCiAgICBvcmRlcmVkX3Rlcm1zIDwtIGxkYV9tb2RlbEB0ZXJtc1tvcmRlcmluZ10KICAgIG9yZGVyZWRfc2VsZWN0aW9uIDwtIChwX2Rpc3RpbmN0aXZlW2ksXSA+IDAuOClbb3JkZXJpbmddCiAgICBzZWxlY3RlZF9vcmRlcmVkX3Rlcm1zIDwtIG9yZGVyZWRfdGVybXNbb3JkZXJlZF9zZWxlY3Rpb25dCiAgICBvcmRlcmVkX3BzIDwtIChleHAobGRhX21vZGVsQGJldGFbaSxdKVtvcmRlcmluZ10pW29yZGVyZWRfc2VsZWN0aW9uXSAgICAgIAogICAgbmFtZXMob3JkZXJlZF9wcykgPC0gc2VsZWN0ZWRfb3JkZXJlZF90ZXJtcwogICAgCiAgICByZXN1bHRzW1tpXV0gPC0gb3JkZXJlZF9wcwogIH0KICAKICByZXN1bHRzCgp9CgpjaGFyYWN0ZXJpc3RpY19jYWxscyA8LSBjaGFyYWN0ZXJpc3RpY190ZXJtcyh3aW5uaW5nX2xkYSkKCmBgYAoKQmVjYXVzZSBteSBrbm93bGVkZ2Ugb2Ygd2ViIHRyYWNraW5nIG1ldGhvZHMgaXMgZmFpcmx5IGxpbWl0ZWQsIEkgaGF2ZSBjb25zdWx0ZWQgQUkgYXQgdGhpcyBzdGFnZS4gRm9yIGVhY2ggZ3JvdXAsIEkgaGF2ZSB0YWtlbiB0aGUgY2hhcmFjdGVyaXN0aWMgQVBJIGNhbGxzIGFuZCBhc2tlZCBBSSAoQ29waWxvdCkgdGhlc2UgcXVlc3Rpb25zOgoKKiAiV2hhdCB0aGVzZSBqYXZhc2NyaXB0IGZ1bmN0aW9uIGNhbGxzIGhhdmUgaW4gY29tbW9uPyIKKiAiSG93IHdvdWxkIHlvdSBjaGFyYWN0ZXJpemUgdGhpcyBncm91cD8iIAoqICJXaGF0IG5hbWUgd291bGQgeW91IGdpdmUgdG8gdGhpcyBncm91cCBvZiBmdW5jdGlvbnM/IgoKSSBoYXZlIG1hZGUgc3VyZSB0byBhc2sgdGhlc2UgcXVlc3Rpb25zIHdpdGhvdXQgbWVudGlvbmluZyB3ZWIgdHJhY2tpbmcuIEludGVyZXN0aW5nbHksIGZvciBlYWNoIGdyb3VwLCB0aGUgQUkgaGF2ZSBpbW1lZGlhdGVseSBpZGVudGlmaWVkIHRoYXQgdHJhY2tpbmcvZmluZ2VycHJpbnRpbmcgbWlnaHQgYmUgYXQgcGxheS4gQW5zd2VycyBoZWxwZWQgbWUgZXN0YWJsaXNoIG5hbWVzIGFuZCBjaGFyYWN0ZXJpemF0aW9uIG9mIHRoZXNlIEFQSSBjYWxsIGdyb3Vwcy4KClRoZSBzZXJpZXMgb2YgYmFycGxvdHMgYmVsb3cgc2hvd3MgY2hhcmFjdGVyaXN0aWMgY2FsbHMgZm9yIGVhY2ggY2FsbCBncm91cC4gVGhlIGxlbmd0aCBvZiB0aGUgYmFyIGNvcnJlc3BvbmRzIHRvIGhvdyBmcmVxdWVudGx5IGlzIGdpdmVuIGNhbGwgYXNzb2NpYXRlZCB3aXRoIGEgZ2l2ZW4gZ3JvdXAuIAoKIyMjIyBJZGVudGl0eSAmIFBlcnNpc3RhbmNlIEFQSSBDYWxsIEdyb3VwCkZ1bmN0aW9ucyBmb2N1c2VkIG9uIHBlcnNpc3RhbnRlIHN0b3JhZ2UgYW5kIGhpZ2gtZW50cm9weSBpZGVudGlmaWVycy4KYGBge3IsIGVjaG89Rn0Kb2xkX3BhciA8LSBwYXIoKQpwYXIobWFyID0gYyg1LjEsIDEzLCA0LjEsIDIuMSkpCmJhcnBsb3QoY2hhcmFjdGVyaXN0aWNfY2FsbHNbWzFdXSxuYW1lcy5hcmcgPSBuYW1lcyhjaGFyYWN0ZXJpc3RpY19jYWxsc1tbMV1dKSxsYXM9MixjZXgubmFtZXM9MC42LGhvcml6ID0gVCx4bGFiPSJQcm9iYWJpbGl0eSBvZiBBc3NvY2lhdGlvbiIpCnBhcihvbGRfcGFyKQoKYGBgCiMjIyMgQnJvd3NlciBQZXJzb25hbGl0eSBBUEkgQ2FsbCBHcm91cApDYWxscyBmb2N1c2VkIG9uIGNoYXJhY3Rlcml6aW5nIHVzZXIncyBicm93c2VyLiBUaGlzIGlzIGEgYnJvYWQgZ3JvdXAgLSBsaW1pdGluZyB0byB0b3AgMTUgbW9zdCBjb21tb24gY2FsbHMuCgpgYGB7ciwgZWNobz1GfQpsaW1pdGVkX3NldCA8LSBjaGFyYWN0ZXJpc3RpY19jYWxsc1tbMl1dWzE6MTVdCm9sZF9wYXIgPC0gcGFyKCkKcGFyKG1hciA9IGMoNS4xLCAxMywgNC4xLCAyLjEpKQpiYXJwbG90KGxpbWl0ZWRfc2V0LG5hbWVzLmFyZyA9IG5hbWVzKGxpbWl0ZWRfc2V0KSxsYXM9MixjZXgubmFtZXM9MC42LGhvcml6ID0gVCx4bGFiPSJQcm9iYWJpbGl0eSBvZiBBc3NvY2lhdGlvbiIpCnBhcihvbGRfcGFyKQpgYGAKIyMjIyBSZW5kZXJpbmcgJiBWaWV3aW5nIFN1cmZhY2UgQVBJIENhbGwgR3JvdXAKTW9zdGx5IHByb3BlcnRpZXMgb2YgdGhlIHZpZXdwb3J0LCByZW5kZXJpbmcgcXVpcmtzIGFuZCBtb3Rpb24gc2Vuc29yLgoKYGBge3IsIGVjaG89Rn0Kb2xkX3BhciA8LSBwYXIoKQpwYXIobWFyID0gYyg1LjEsIDEzLCA0LjEsIDIuMSkpCmJhcnBsb3QoY2hhcmFjdGVyaXN0aWNfY2FsbHNbWzNdXSxuYW1lcy5hcmcgPSBuYW1lcyhjaGFyYWN0ZXJpc3RpY19jYWxsc1tbM11dKSxsYXM9MixjZXgubmFtZXM9MC42LGhvcml6ID0gVCx4bGFiPSJQcm9iYWJpbGl0eSBvZiBBc3NvY2lhdGlvbiIpCnBhcihvbGRfcGFyKQpgYGAKIyMjIyBDYXBhYmlsaXR5ICYgSGFyZHdhcmUgQVBJIENhbGwgR3JvdXAKTW9zdGx5IGZ1bmN0aW9ucyB0aGF0IGNhbiBiZSB1c2VkIHRvIGdldCBicm9hZCBpbmZvcm1hdGlvbiBvbiBoYXJkd2FyZSBhbmQgZGV2aWNlIGNhcGFiaWxpdGllcy4gQmlnIGdyb3VwLCBzaG93aW5nIG9ubHkgdG9wIDE1LgoKYGBge3IsIGVjaG89Rn0KbGltaXRlZF9zZXQgPC0gY2hhcmFjdGVyaXN0aWNfY2FsbHNbWzRdXVsxOjE1XQpvbGRfcGFyIDwtIHBhcigpCnBhcihtYXIgPSBjKDUuMSwgMTMsIDQuMSwgMi4xKSkKYmFycGxvdChsaW1pdGVkX3NldCxuYW1lcy5hcmcgPSBuYW1lcyhsaW1pdGVkX3NldCksbGFzPTIsY2V4Lm5hbWVzPTAuNixob3JpeiA9IFQseGxhYj0iUHJvYmFiaWxpdHkgb2YgQXNzb2NpYXRpb24iKQpwYXIob2xkX3BhcikKYGBgCgojIyMgQVBJIENhbGwgR3JvdXBzIGFuZCBTdXNwZWN0ZWQgVHJhY2tpbmcKCkFwcGxpY2F0aW9uIG9mIExEQSBhYm92ZSBoYXZlIGhlbHBlZCB0byBpZGVudGlmeSBjZXJ0YWluIGdyb3VwcyBvZiBjYWxscyB0aGF0IHRlbmQgdG8gY28tb2NjdXIuIFRoaXMgZG9lcyBub3QgbWVhbiwgaG93ZXZlciwgdGhhdCBhbGwgb2YgdGhlbSBhcmUgaW5kaWNhdGlvbiBvZiBmaW5nZXJwcmludGluZy4gSW4gb3JkZXIgdG8gc2VlIHdoaWNoIGdyb3VwcyBvciBncm91cCBjb21iaW5hdGlvbnMgbWlnaHQgYmUgYXNzb2NpYXRlZCB3aXRoIGZpbmdlcnByaW50aW5nLCBJJ20gdXNpbmcgbG9naXN0aWMgcmVncmVzc2lvbiBvbiByZXNvdXJjZS1ncm91cCBhc3NvY2lhdGlvbnMsIHdpdGggb3V0Y29tZSAocHJlZGljdGVkKSB2YXJpYWJsZSBiZWluZyB0aGUgZmluZ2VycHJpbnRpbmcgc2NvcmUgY2FsY3VsYXRlZCBieSB0cmFja2VyLXJhZGFyLiBJIHdhbnQgdG8gc2VlIHdoaWNoIGNhbGwgZ3JvdXBzLCBvciB0aGVpciBjb21iaW5hdGlvbnMgYXJlIGFzc29jaWF0ZWQgd2l0aCBoaWdoIGZpbmdlcnByaW50aW5nIHNjb3JlLgoKT2YgY291cnNlLCB0aGUgZmluZ2VycHJpbnRpbmcgc2NvcmUgaXRzZWxmIGlzIGp1c3QgYSBoZXVyaXN0aWMgYW5kIG1pZ2h0IG5vdCBiZSBhbHdheXMgY29ycmVjdC4gQnV0IHRoaXMgaXMgb2sgZm9yIGluaXRpYWwgZXhwbG9yYXRvcnkgYW5hbHlzaXMgdG8gZGlzY292ZXIgaW50ZXJlc3RpbmcgaHlwb3RoZXNlcy4gVG8gbWFrZSB0aGluZ3MgZWFzaWVyIGZvciBpbnRlcnByZXRhdGlvbiwgSSBoYXZlIG1hZGUgdHdvIHNpbXBsaWZpY2F0aW9uczoKCiogSSBoYXZlIGJpbmFyaXplZCBhc3NvY2lhdGlvbnMgYmV0d2VlbiBjYWxsIGdyb3VwcyBhbmQgc2NyaXB0IHJlc291cmNlcy4gQ2FsbCBncm91cCBpcyBhc3NvY2lhdGVkIHdpdGggYSBnaXZlbiBzY3JpcHQgcmVzb3VyY2UsIGlmIGl0cyBhc3NvY2lhdGlvbiBwcm9iYWJpbGl0eSB3aXRoIGdpdmVuIGdyb3VwIGlzIGdyZWF0ZXIgdGhhbiB3aGF0IHdvdWxkIGJlIGV4cGVjdGVkIGluIDcwJSBvZiBjYXNlcy4KKiBJbnN0ZWFkIG9mIHByZWRpY3RpbmcgZmluZ2VycHJpbnRpbmcgc2NvcmUgb2YgMSAtIDMsIEknbSBwcmVkaWN0aW5nIGJpbmFyeSBvdXRjb21lIG9mIGZpbmdlcnByaW50aW5nIHNjb3JlIGJlaW5nIDEgdnMgMiBvciAzLgoKUmVtZW1iZXIgdGhhdCBJJ20gbm90IHVzaW5nIGNsYXNzaWZpZXIgaGVyZSB0byBhY2N1cmF0ZWx5IHByZWRpY3QgdGhlIGZpbmdlcnByaW50aW5nIHNjb3JlLiBUaGUgbWFpbiBwdXJwb3NlIGlzIHRvIHNlZSB3aGljaCBjYWxsIGdyb3VwcyBvciBjYWxsIGdyb3VwIGNvbWJpbmF0aW9ucyBtaWdodCBiZSBhc3NvY2lhdGVkIHdpdGggaGlnaCBmaW5nZXJwcmludGluZyBzY29yZXMgYW5kIHRoZXJlZm9yZSBsaWtlbHkgYXNzb2NpYXRlZCB3aXRoIHRyYWNrZXJzL2ZpbmdlcnByaW50aW5nLiBJJ20gYWxzbyBub3QgZG9pbmcgdGhlIG1vcmUgY29tbW9uIHRyYWluLXRlc3QtdmFsaWRhdGlvbiBzcGxpdCwgYnV0IHVzaW5nIGluZm9ybWF0aW9uIGNyaXRlcmlvbiAoQUlDKSBmb3IgbW9kZWwgc2VsZWN0aW9uLiBJJ20gbm90IGludGVyZXN0ZWQgaW4gaG93IGV4YWN0bHkgdGhpcyBtb2RlbCB3b3VsZCB3b3JrIGluIHByb2R1Y3Rpb24gLSBJJ20ganVzdCBpbnRlcmVzdGVkIGluIGNob29zaW5nIGJldHRlciBtb2RlbC4KCllvdSBjYW4gc2VlIHRoZSBleGNlcnB0IG9mIGRhdGEgdXNlZCBmb3IgdHJhaW5pbmcgYmVsb3c6CmBgYHtyfQphc3NvY190aHJlc2hvbGRzIDwtIGNvbFF1YW50aWxlcyh3aW5uaW5nX2xkYUBnYW1tYSxwcm9icyA9IDAuNykKYmluYXJ5X2FjdGl2YXRpb25zIDwtIHdpbm5pbmdfbGRhQGdhbW1hID4gYXNzb2NfdGhyZXNob2xkcwpiaW5hcnlfYWN0aXZhdGlvbnNfZGYgPC0gYXMuZGF0YS5mcmFtZShiaW5hcnlfYWN0aXZhdGlvbnMpCmJpbmFyeV9hY3RpdmF0aW9uc19kZiRvdXRjb21lIDwtIGNhbGxfY291bnRzJGZpbmdlcnByaW50aW5nID4gMQoKaGVhZChiaW5hcnlfYWN0aXZhdGlvbnNfZGYpCmBgYAoKSSBoYXZlIHRyaWVkIDIgdmVyc2lvbnMgb2YgbG9naXN0aWMgcmVncmVzc2lvbjoKCiogUmVncmVzc2luZyBvbiBlYWNoIHZhcmlhYmxlLCBidXQgbm90IG9uIHRoZWlyIGNvbWJpbmF0aW9ucwoqIFJlZ3Jlc3Npb24gb24gZWFjaCB2YXJpYWJsZSBhbmQgZWFjaCBwYWlyd2lzZSB2YXJpYWJsZSBjb21iaW5hdGlvbiAocGFpciBpbnRlcmFjdGlvbnMpCgpUaGUgY29kZSBiZWxvdyBmaXRzIHRoZXNlIG1vZGVscyBhbmQgb3V0cHV0cyB0aGVpciBBSUMgc2NvcmUgKGxvd2VyIGlzIGJldHRlcikuCgpgYGB7cn0KcHJlZF9tb2RlbF9iYXNlIDwtIGdsbShvdXRjb21lIH4gVjEgKyBWMiArIFYzICsgVjQsYmluYXJ5X2FjdGl2YXRpb25zX2RmLGZhbWlseSA9ICJiaW5vbWlhbCIpCnByZWRfbW9kZWxfcGFpcnMgPC0gZ2xtKG91dGNvbWUgfiBWMSArIFYyICsgVjMgKyBWNCArIFYxKlYyICsgVjEqVjMgKyBWMSpWNCArIFYyKlYzICsgVjIqVjQgKyBWMypWNCxiaW5hcnlfYWN0aXZhdGlvbnNfZGYsZmFtaWx5ID0gImJpbm9taWFsIikKZGF0YS5mcmFtZShtb2RlbD1jKCJCYXNlbGluZSBNb2RlbCIsIkludGVyYWN0aW9uIE1vZGVsIiksQUlDID0gYyhBSUMocHJlZF9tb2RlbF9iYXNlKSwKQUlDKHByZWRfbW9kZWxfcGFpcnMpKSkKYGBgCgoKSW50ZXJhY3Rpb24gbW9kZWwgaXMgYSBjbGVhciB3aW5uZXIuIFRoZSBkaWZmZXJlbmNlIGluIEFJQyB2YWx1ZSBtaWdodCBzZWVtIHNtYWxsIGF0IHRoZSBmaXJzdCBzaWdodC4gSG93ZXZlciwgQUlDIGlzIGRlZmluZWQgb24gYSBsb2dhcml0aG1pYyBzY2FsZSwgc28gdGhpcyBkaWZmZXJlbmNlIGlzIGFjdHVhbGx5IHZlcnkgc2lnbmlmaWNhbnQuIE5leHQsIEkgaGF2ZSBnZW5lcmF0ZWQgYWxsIHBvc3NpYmxlIGNvbWJpbmF0aW9ucyBvZiBhY3RpdmF0aW9ucyB0byBzZWUgd2hpY2ggd2lsbCBiZSBwcmVkaWN0ZWQgYnkgdGhlIG1vZGVsIHRvIGhhdmUgbW9yZSB0aGFuIDUwJSBwcm9iYWJpbGl0eSBvZiB0cmFja2luZyBiZWhhdmlvci4gCgpUaGUgdGFibGUgYmVsb3cgc2hvd3MgdGhlIGNhbGwgZ3JvdXAgY29tYmluYXRpb25zIGFzc29jaWF0ZWQgd2l0aCB0cmFja2luZy9maW5nZXJwcmludGluZyBiZWhhdmlvciAocD41MCUpIG9yZGVyZWQgZnJvbSBtb3N0IHByb2JhYmxlIHRvIHRoZSBsZWFzdCBwcm9iYWJsZS4gQWNyb255bSBmb3IgZWFjaCBjb21iaW5hdGlvbiB3YXMgY3JlYXRlZCBieSB0YWtpbmcgZmlyc3QgbGV0dGVycyBvZiBlYWNoIGNhbGwgZ3JvdXAgdGFodCBpdCBjb25zaXN0cyBvZi4gCgoKYGBge3J9CmFsbF9jb21icyA8LSBleHBhbmQuZ3JpZChWMT1jKFQsRiksVjI9YyhULEYpLFYzPWMoVCxGKSwgVjQgPSBjKFQsRikpCmFsbF9jb21icyRQcm9iYWJpbGl0eSA8LSBwcmVkaWN0KHByZWRfbW9kZWxfcGFpcnMsYWxsX2NvbWJzLHR5cGU9InJlc3BvbnNlIikKCgphbGxfY29tYnMgJT4lCiAgZmlsdGVyKFByb2JhYmlsaXR5ID4gMC41KSAlPiUKICBhcnJhbmdlKGRlc2MoUHJvYmFiaWxpdHkpKSAlPiUKICBtdXRhdGUoCiAgICBBY3JvbnltID0gcGFzdGUwKGlmZWxzZShWMSwiSSIsIiIpLGlmZWxzZShWMiwiQlAiLCIiKSxpZmVsc2UoVjMsIlIiLCIiKSxpZmVsc2UoVjQsIkMiLCIiKSksCiAgICBWMSA9IGlmZWxzZShWMSwiSWRlbnRpdHkmUGVyc2lzdGFuY2UiLCIiKSwKICAgIFYyID0gaWZlbHNlKFYyLCJCcm93c2VyIFBlcnNvbmFsaXR5IiwiIiksCiAgICBWMyA9IGlmZWxzZShWMywiUmVuZGVyaW5nJlZpZXdwb3J0IiwiIiksCiAgICBWNCA9IGlmZWxzZShWNCwiQ2FwYWJpbGl0eSIsIiIpCiAgKSAlPiUKICBzZWxlY3QoQWNyb255bSxWMSxWMixWMyxWNCxQcm9iYWJpbGl0eSkKYGBgCgoKIyMjIEFuYWx5emluZyBTdXNwZWN0ZWQgVHJhY2tlciBQYXR0ZXJucyBpbiBUaW1lCgpIYXZpbmcgZXN0YWJsaXNoZWQgdGhlIEFQSSBjYWxsIGdyb3VwcyBhbmQgdGhlaXIgY29tYmluYXRpb25zIGFzc29jaWF0ZWQgd2l0aCBmaW5nZXJwcmludGluZywgSSBjYW4gbm93IHVzZSB0aGVzZSBjb21iaW5hdGlvbnMgdG8gc2VlIGhvdyB0aGUgY2hhcmFjdGVyIG9mIGZpbmdlcnByaW50aW5nIGV2b2x2ZWQgaW4gdGltZS4gRmlyc3QsIEknbGwgaW52ZXN0aWdhdGUgb3ZlcmFsbCBkZXZlbG9wbWVudCBhbmQgdGhlbiBJJ2xsIGludmVzdGlnYXRlIHRoZSBiaWdnZXN0IHBsYXllcnMgaW4gdHJhY2tpbmcuCgpJbiBvcmRlciB0byBkbyB0aGF0LCBJIGhhZCB0byBkbyBzb21lIGRhdGEgcHJvY2Vzc2luZyBmb2N1c2VkIG9uIGFubm90YXRpb24gb2YgZWFjaCBzY3JpcHQgcmVzb3VyY2UgYnkgdGhlIGNhbGwgZ3JvdXAgY29tYmluYXRpb24gaXQgYmVsb25ncyB0by4KCmBgYHtyfQpjYWxsX2NvdW50c19pbl90aW1lIDwtYXBpX2NhbGxzX2luX3RpbWUgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbT1hcGksdmFsdWVzX2Zyb20gPSBjYWxscyx2YWx1ZXNfZmlsbCA9IDApIAoKY2FsbF9jb3VudHNfbWF0X2luX3RpbWUgPC0gY2FsbF9jb3VudHNfaW5fdGltZSAlPiUgCiAgc2VsZWN0KC1jKGRhdGUscmVnaW9uLGRvbWFpbixydWxlLGZpbmdlcnByaW50aW5nKSkgJT4lIAogIGFzLm1hdHJpeCgpCgojT3JkZXIgY29sdW1ucyBpbiB0aGUgc2FtZSB3YXkgYXMgaW4gTERBIHRyYWluaW5nCmNhbGxfY291bnRzX21hdF9pbl90aW1lIDwtIGNhbGxfY291bnRzX21hdF9pbl90aW1lWyxjb2xuYW1lcyhjYWxsX2NvdW50c19tYXQpXQoKbm9uX3plcm9fcm93IDwtIHJvd1N1bXMoY2FsbF9jb3VudHNfbWF0X2luX3RpbWUpID4gMApjYWxsX2NvdW50c19tYXRfaW5fdGltZSA8LSBjYWxsX2NvdW50c19tYXRfaW5fdGltZVtub25femVyb19yb3csXQpjYWxsX2NvdW50c19pbl90aW1lX25vbnplcm8gPC0gY2FsbF9jb3VudHNfaW5fdGltZVtub25femVyb19yb3csXQoKdG9waWNfcG9zdGVyaW9ycyA8LSBwb3N0ZXJpb3Iod2lubmluZ19sZGEsY2FsbF9jb3VudHNfbWF0X2luX3RpbWUpCmFjdGl2YXRpb25zX2luX3RpbWUgPC0gdG9waWNfcG9zdGVyaW9ycyR0b3BpY3MgPiBhc3NvY190aHJlc2hvbGRzCmNvbG5hbWVzKGFjdGl2YXRpb25zX2luX3RpbWUpIDwtIGMoIlYxIiwiVjIiLCJWMyIsIlY0IikKCnRyYWNrZXJfdHlwZXNfaW5fdGltZSA8LSBhY3RpdmF0aW9uc19pbl90aW1lICU+JSBhcy5kYXRhLmZyYW1lICU+JQogIG11dGF0ZSgKICAgIHRyYWNrZXJfdHlwZSA9IGNhc2Vfd2hlbigKICAgICAgVjEgJiBWMiAmIFYzICYgVjQgfiAiSUJQUkMiLCAgICMgSWRlbnRpdHkgKyBCcm93c2VyIFBlcnNvbmFsaXR5ICsgUmVuZGVyaW5nICsgQ2FwYWJpbGl0eQogICAgICBWMiAmIFYzICYgVjQgICAgICB+ICJCUFJDIiwgICAgIyBCcm93c2VyIFBlcnNvbmFsaXR5ICsgUmVuZGVyaW5nICsgQ2FwYWJpbGl0eQogICAgICBWMiAmIFY0ICAgICAgICAgICB+ICJCUEMiLCAgICAgIyBCcm93c2VyIFBlcnNvbmFsaXR5ICsgQ2FwYWJpbGl0eQogICAgICBWMyAmIFY0ICAgICAgICAgICB+ICJSQyIsICAgICAgIyBSZW5kZXJpbmcgKyBDYXBhYmlsaXR5CiAgICAgIFY0ICAgICAgICAgICAgICAgIH4gIkMiLCAgICAgICAjIENhcGFiaWxpdHkgb25seQogICAgICBWMSAmIFYyICYgVjMgICAgICB+ICJJQlBSIiwgICAgIyBJZGVudGl0eSArIEJyb3dzZXIgUGVyc29uYWxpdHkgKyBSZW5kZXJpbmcKICAgICAgVjEgJiBWMiAmIFY0ICAgICAgfiAiSUJQQyIsICAgICMgSWRlbnRpdHkgKyBCcm93c2VyIFBlcnNvbmFsaXR5ICsgQ2FwYWJpbGl0eQogICAgICBWMSAmIFYzICYgVjQgICAgICB+ICJJUkMiLCAgICAgIyBJZGVudGl0eSArIFJlbmRlcmluZyArIENhcGFiaWxpdHkKICAgICAgVFJVRSAgICAgICAgICAgICAgfiBOQV9jaGFyYWN0ZXJfCiAgICApCiAgKSAKCnRyYWNrZXJfdHlwZXNfaW5fdGltZSRkb21haW4gPC0gY2FsbF9jb3VudHNfaW5fdGltZV9ub256ZXJvJGRvbWFpbgp0cmFja2VyX3R5cGVzX2luX3RpbWUkZGF0ZSA8LSBjYWxsX2NvdW50c19pbl90aW1lX25vbnplcm8kZGF0ZQoKCnRyYWNrZXJfdHlwZXNfZmlsdGVyZWRfaW5fdGltZSA8LSB0cmFja2VyX3R5cGVzX2luX3RpbWUgJT4lCiAgZmlsdGVyKCFpcy5uYSh0cmFja2VyX3R5cGUpKQpgYGAKCgpUaGUgcGxvdCBiZWxvdyBzaG93cyBob3cgdGhlIG51bWJlciBvZiBzY3JpcHRzIGFzc29jaWF0ZWQgd2l0aCBlYWNoIHBhdHRlcm4gZXZvbHZlZCBvdmVyIHRpbWUuIFRoZXJlIGFyZSBhIGNvdXBsZSBvZiB0aGluZ3MgdG8gbm90ZSBoZXJlOgoKKiBPdXQgb2YgdGhlIDEwIHBhdHRlcm5zIHN1Z2dlc3RpbmcgdHJhY2tpbmcgYWNjb3JkaW5nIHRvIHRoZSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsLCBvbmx5IDUgYXJlIGFjdHVhbGx5IHJlYWxseSBvY2N1cnJpbmcgaW4gdGhlIHdpbGQuIFRoaXMgaXMgZXhwZWN0ZWQgYmVoYXZpb3IgLSBzdGF0aXN0aWNhbCBtb2RlbHMgY2FuIGJlIHVzZWQgZm9yIHNwZWN1bGF0aW9uIG92ZXIgZmljdGlvbmFsIHNjZW5hcmlvcy4gSSBoYXZlIGxpbWl0ZWQgZnVydGhlciBhbmFseXNpcyB0byB0aGVzZSByZWFsIHBhdHRlcm5zOgogICogQlBDIC0gQnJvd3NlciBQZXJzb25hbGl0eSBhbmQgQ2FwYWJpbGl0eSAKICAqIEJQUkMgLSBCcm93c2VyIFBlcnNvbmFsaXR5LCBSZW5kZXJpbmcgYW5kIENhcGFiaWxpdHkgCiAgKiBDIC0gQ2FwYWJpbGl0eQogICogSUJQUiAtIElkZW50aXR5LCBCcm93c2VyIFBlcnNvbmFsaXR5IGFuZCBSZW5kZXJpbmcmVmlld3BvcnQKKiBUaGUgcGxvdCBjbGVhcmx5IHNob3dzIHRoYXQgdGhlIHByZWRvbWluYW50IHBhdHRlcm4gaXMgQ2FwYWJpbGl0eS1vbmx5IGZvbGxvd2VkIGJ5IHRoZSBjb21iaW5hdGlvbiBvZiBCcm93c2VyIFBlcnNvbmFpdHkgYW5kIENhcGFiaWxpdHkuCiogSG93ZXZlciwgYXQgc29tZSB0aW1lICoqYmV0d2VlbiAyMDIyIGFuZCAyMDIzIHRoZXJlJ3MgYSBzdWRkZW4gdXB0aWNrIGluIHR3byBwYXR0ZXJucyB3aXRoIGZ1bmN0aW9ucyBrbm93biB0byBiZSBpbnZvbHZlZCBpbiAgdmlld3BvcnQgZmluZ2VycHJpbnRpbmcgLSBJQlBSIGFuZCBlc3BlY2lhbGx5IFJDKiouCgpgYGB7ciwgZmlnLndpZHRoPTEwfQpsaWJyYXJ5KGdncGxvdDIpCnRyYWNrZXJfdHlwZXNfZmlsdGVyZWRfaW5fdGltZSAlPiUKICBncm91cF9ieShkYXRlLHRyYWNrZXJfdHlwZSkgJT4lCiAgc3VtbWFyaXNlKG49bigpKSAlPiUKICBnZ3Bsb3QoYWVzKHg9ZGF0ZSx5PW4sZmlsbD10cmFja2VyX3R5cGUpKSArIAogIGdlb21fYXJlYSgpICsKICB5bGFiKCJOdW1iZXIgb2YgU2NyaXB0IFJlc291cmNlcyIpICsKICB4bGFiKCJUaW1lIikgKwogIGxhYnMoZmlsbD0iU3VzcGVjdGVkIFRyYWNrZXIgVHlwZSIpICsKICBnZ3RpdGxlKCJFdm9sdXRpb24gb2YgU3VzcGVjdGVkIFRyYWNrZXIgUGF0dGVybiBQb3B1bGFyaXR5IGluIFRpbWUiKQpgYGAKClRoZSBwbG90IGJlbG93IHNob3dzIHRoZSBzYW1lIGV2b2x1dGlvbiBvZiBzdXNwZWN0ZWQgdHJhY2tlciBwYXR0ZXJucyBpbiBib3RoIGFic29sdXRlIGFuZCByZWxhdGl2ZSB0ZXJtcyAocGVyY2VudGFnZXMpIGJyb2tlbiBkb3duIGZvciBlYWNoIG1ham9yIHBsYXllci4KSXQgaXMgYXBwYXJlbnQgdGhhdCB0aGUgc2FtZSBwYXR0ZXJuIHNlZW1zIHRvIGhvbGQgd2l0aCBuZWFybHkgYWxsIG1ham9yIHBsYXllcnMuIEVzcGVjaWFsbHkgR29vZ2xlLCBBa2FtYWkgYW5kIEFkb2JlIHNlZW0gdG8gaGF2ZSBzdWRkZW4gdXB0aWNrIGluIElCUFIgYW5kIFJDLCB3aGlsZSBNaWNyb3NvZnQgc2VlbXMgdG8gYWx3YXlzIGhhdmUgaGFkIGEgc2lnbmlmaWNhbnQgcGFydCBvZiBpdHMgcmVzb3VyY2VzIGFsaWduZWQgd2l0aCB0aGUgUkMgcGF0dGVybi4KCmBgYHtyLCBmaWcuaGVpZ2h0PTE1fQpsaWJyYXJ5KHBhdGNod29yaykKCmRvbWFpbl9vd25lcnNfaW5fdGltZSA8LSByZWFkLmNzdigiL1VzZXJzL3N0YW5pc2xhdi5zbWF0YW5hL0RvY3VtZW50cy9QZXJzb25hbCBQcm9qZWN0cy90cmFja2VyLXJhZGFyL293bmVyX2RvbWFpbnNfYnlfcmVsZWFzZS5jc3YiKSAlPiUKICBzZXBhcmF0ZShyZWxlYXNlX3RhZywgaW50byA9IGMoInllYXIiLCAibW9udGgiLCAiZGF5IiksIHNlcCA9ICJcXC4iLCBmaWxsID0gInJpZ2h0IikgJT4lCiAgbXV0YXRlKAogICAgbW9udGggPSBzcHJpbnRmKCIlMDJkIiwgYXMuaW50ZWdlcihtb250aCkpLAogICAgZGF5ICAgPSBpZmVsc2UoaXMubmEoZGF5KSwgIjAxIiwgc3ByaW50ZigiJTAyZCIsIGFzLmludGVnZXIoZGF5KSkpLAogICAgZGF0ZSAgPSBhcy5EYXRlKHBhc3RlKHllYXIsIG1vbnRoLCBkYXksIHNlcCA9ICItIikpCiAgKQoKdG9wX293bmVyc19pbl90aW1lIDwtIGRvbWFpbl9vd25lcnNfaW5fdGltZSAlPiUgCiAgZ3JvdXBfYnkoZGF0ZSxvd25lcikgJT4lCiAgc3VtbWFyaXNlKG49bigpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgZ3JvdXBfYnkoZGF0ZSkgJT4lCiAgdG9wX24oMykgJT4lCiAgYCRgKG93bmVyKSAlPiUKICB1bmlxdWUKCmdpZ2FudHNfaW5fdGltZSA8LSB0cmFja2VyX3R5cGVzX2ZpbHRlcmVkX2luX3RpbWUgJT4lCiAgaW5uZXJfam9pbihkb21haW5fb3duZXJzX2luX3RpbWUgJT4lIGZpbHRlcihvd25lciAlaW4lIHRvcF9vd25lcnNfaW5fdGltZSkpICU+JQogIGdyb3VwX2J5KGRhdGUsb3duZXIsdHJhY2tlcl90eXBlKSAlPiUKICBzdW1tYXJpemUobj1uKCkpCgphYnNvbHV0ZSA8LSBnaWdhbnRzX2luX3RpbWUgJT4lCiAgZ2dwbG90KGFlcyh4PWRhdGUseT1uLGZpbGw9dHJhY2tlcl90eXBlKSkgKwogIGdlb21fYXJlYSgpICsKICBmYWNldF93cmFwKH5vd25lcikgKyAKICB4bGFiKCJUaW1lIikgKyAKICB5bGFiKCJOdW1iZXIgb2YgU2NyaXB0IFJlc291cmNlcyIpICsKICBnZ3RpdGxlKCJBYnNvbHV0ZSBEZXZlbG9wbWVudCBvZiBTdXNwZWN0ZWQgVHJhY2tlciBUeXBlcyBpbiBUaW1lIikKCgpyZWxhdGl2ZSA8LSBnaWdhbnRzX2luX3RpbWUgJT4lCiAgZ2dwbG90KGFlcyh4PWRhdGUseT1uLGZpbGw9dHJhY2tlcl90eXBlKSkgKwogIGdlb21fYXJlYShwb3NpdGlvbj0iZmlsbCIpICsKICBmYWNldF93cmFwKH5vd25lcikgKyAKICB4bGFiKCJUaW1lIikgKyAKICB5bGFiKCJQcm9wb3J0aW9uIG9mIFNjcmlwdCBSZXNvdXJjZXMiKSArCiAgZ2d0aXRsZSgiUmVsYXRpdmUgRGV2ZWxvcG1lbnQgb2YgU3VzcGVjdGVkIFRyYWNrZXIgVHlwZXMgaW4gVGltZSIpCgphYnNvbHV0ZSAvIHJlbGF0aXZlCgoKCmBgYApGaW5hbGx5LCBJJ3ZlIGRlY2lkZWQgdG8gem9vbSBpbiBpbnRvIHRoZSBncmFpbiBvZiBpbmRpdmlkdWFsIGNhbGxzLiBJIHdhbnRlZCB0byBhbnN3ZXIgdGhlIHF1ZXN0aW9uIC0gaG93IGRvIHRoZSBwcm9wb3J0aW9ucyBvZiBkaWZmZXJlbnQgQVBJIGNhbGxzIGRpZmZlciBwcmUgYW5kIHBvc3QgMjAyMj8gSW4gb3JkZXIgdG8gaWRlbnRpZnkgY2hhbmdlcyB0aGF0IGFyZSBzaWduaWZpY2FudCwgSSdtIHVzaW5nIGNoaS5zcXVhcmUgdGVzdCBvdmVyIDJ4MiBjb250aW5nZW5jeSBtYXRyaWNlcy4gVGhpcyBtZWFucyB0aGF0IGZvciBldmVyeSBjYWxsIEkgY3JlYXRlIGEgbWF0cml4IG9mIHRoZSBmb3JtCgoKYGBge3IsZWNobz1GfQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KGthYmxlRXh0cmEpCgprYWJsZShkYXRhLmZyYW1lKAogICJYIiA9IGMoIkJlZm9yZSAyMiIsIkFmdGVyIDIyIiksCiAgIkNhbGxzX29mX2Z1bmN0aW9uX2YiID0gYygibl9mX3ByZTIyIiwibl9mX3Bvc3QyMiIpLAogICJPdGhlcl9jYWxscyI9IGMoIm5fb3RoZXJfcHJlMjIiLCJuX290aGVyX3Bvc3QyMiIpKSwgZm9ybWF0ID0gImh0bWwiLCB0YWJsZS5hdHRyID0gInN0eWxlPSd3aWR0aDo1MCU7JyIpICU+JSAKICBrYWJsZV9zdHlsaW5nKCkKYGBgCgpBbmQgdGhlbiBhcHBseSBjaGlzLnNxdWFyZSB0ZXN0IHRvIGRlcml2ZSBzaWduaWZpY2FuY2UgcC12YWx1ZXMuIEkndmUgY2hvc2VuIHRoZSBzaWduaWZpY2FuY2UgdGhyZXNob2xkIDAuMDEuIEhvd2V2ZXIsIGJlY2F1c2UgSSdtIHBlcmZvcm1pbmcgYSBncmVhdCBudW1iZXIgb2YgdGhlc2UgdGVzdHMsIHRoZSBzaWduaWZpY2FuY2UgdGhyZXNob2xkIG5lZWRzIHRvIGJlIGRpdmlkZWQgYnkgdGhlIG51bWJlciBvZiB0ZXN0cyBzbyB0aGF0IHJlc3VsdHMgaGF2ZSBleHBlY3RlZCBlcnJvciByYXRlICh0aGlzIGlzIGNhbGxlZCBCb25mZXJyb25pIGNvcnJlY3Rpb24pLiAgQ29tbWVudGFyeSBvZiByZXN1bHRzIGlzIHBhcnQgb2YgdGhlIG5leHQgc2VjdGlvbiAtIENvbmNsdXNpb24uCgpgYGB7cn0KcGVyaW9kX2FwaV9jYWxscyA8LSBhcGlfY2FsbHNfaW5fdGltZSAlPiUgCiAgc2VsZWN0KC1jKHJlZ2lvbixkb21haW4scnVsZSkpICU+JQogIG11dGF0ZShwcmVfMjAyMiA9IGRhdGUgPCBhcy5EYXRlKCIyMDIyLTEtMSIpKSAlPiUKICBncm91cF9ieShwcmVfMjAyMixhcGkpICU+JQogIHN1bW1hcml6ZShjYWxscyA9IHN1bShjYWxscykpCgpwZXJpb2RfYXBpX2NhbGxzX3dpZGUgPC0gcGVyaW9kX2FwaV9jYWxscyAlPiUKICBncm91cF9ieShwcmVfMjAyMikgJT4lCiAgbXV0YXRlKHBlcmlvZF90b3RhbF9jYWxscyA9IHN1bShjYWxscykpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gcHJlXzIwMjIsdmFsdWVzX2Zyb20gPSBjKGNhbGxzLHBlcmlvZF90b3RhbF9jYWxscyksdmFsdWVzX2ZpbGwgPSAwKSAlPiUKICBtdXRhdGUocGVyaW9kX3RvdGFsX2NhbGxzX1RSVUUgPSBpZmVsc2UocGVyaW9kX3RvdGFsX2NhbGxzX1RSVUUgPT0gMCxwZXJpb2RfdG90YWxfY2FsbHNfVFJVRVtwZXJpb2RfdG90YWxfY2FsbHNfVFJVRSA+IDBdWzFdLHBlcmlvZF90b3RhbF9jYWxsc19UUlVFKSkKCgpjaGlfc3F1YXJlX2NhbGxzIDwtIGZ1bmN0aW9uKGRmKXsKICBjYWxsc19wcmUyMiA8LSBkZiRjYWxsc19UUlVFCiAgbm9uX2NhbGxzX3ByZTIyIDwtIGRmJHBlcmlvZF90b3RhbF9jYWxsc19UUlVFIC0gZGYkY2FsbHNfVFJVRQogIGNhbGxzX3Bvc3QyMiA8LSBkZiRjYWxsc19GQUxTRQogIG5vbl9jYWxsc19wb3N0MjIgPC0gZGYkcGVyaW9kX3RvdGFsX2NhbGxzX0ZBTFNFIC0gZGYkY2FsbHNfRkFMU0UKICAKICBhbGxfZGF0YV9tYXQgPC0gY2JpbmQoY2FsbHNfcHJlMjIsbm9uX2NhbGxzX3ByZTIyLGNhbGxzX3Bvc3QyMixub25fY2FsbHNfcG9zdDIyKQogIGFwcGx5KGFsbF9kYXRhX21hdCwxLGZ1bmN0aW9uKHJvdyl7CiAgICBjaGlzcS50ZXN0KHJiaW5kKAogICAgICBjKHJvd1siY2FsbHNfcHJlMjIiXSxyb3dbIm5vbl9jYWxsc19wcmUyMiJdKSwKICAgICAgYyhyb3dbImNhbGxzX3Bvc3QyMiJdLHJvd1sibm9uX2NhbGxzX3Bvc3QyMiJdKQogICAgKSkkcC52YWx1ZQogIH0pCn0KCnBlcmlvZF9hcGlfY2FsbHNfd2lkZSRzaWduaWZpY2FudCA8LSBjaGlfc3F1YXJlX2NhbGxzKHBlcmlvZF9hcGlfY2FsbHNfd2lkZSkgPCA3Ljg3NDAxNmUtMDUgI2JvbmZlcm9uaSBjb3JyZWN0aW9uCnBlcmlvZF9hcGlfY2FsbHNfd2lkZSAlPiUgCiAgZmlsdGVyKHNpZ25pZmljYW50KSAlPiUKICBzZWxlY3QoYXBpLGNhbGxzX1RSVUUsY2FsbHNfRkFMU0UpICU+JQogIGFycmFuZ2UoY2FsbHNfVFJVRSkgJT4lCiAgcmVuYW1lKGNhbGxzX2JlZm9yZV8yMDIyID0gY2FsbHNfVFJVRSxjYWxsc19hZnRlcl8yMDIyID0gY2FsbHNfRkFMU0UpCgpgYGAKCiMjIyBDb25jbHVzaW9uCgpJdCBkb2VzIHNlZW0gbGlrZSBvbiB0b3Agb2YgdGhlIHNoaWZ0IGluIGNhbGwgZ3JvdXBzLCB0aGVyZSBpcyBhIHNpZ25pZmljYW50IGRpZmZlcmVuY2UgaW4gcHJvcG9ydGlvbnMgYmVmb3JlIDIwMjIgYW5kIGFmdGVyIDIwMjIgZm9yIHByZXR0eSBtdWNoIGV2ZXJ5IGluZGl2aWR1YWwgQVBJIGNhbGwuIFdoYXQncyBtb3JlIGltcG9ydGFudCBzb21lIGhlYXZpbHkgdXNlZCBBUEkgY2FsbHMgYXJlIGNvbXBsZXRlbHkgYWJzZW50IGJlZm9yZSAyMDIyLiBUaGlzIGxlYWRzIG1lIHRvIGEgY291cGxlIG9mIGh5cG90aGVzaXMgZXhwbGFpbmluZyB0aGUgc3VkZGVuIHNoaWZ0IG9mIHRyYWNraW5nIHBhdHRlcm5zIGluIDIwMjI6CgoqIFRoZXJlIHdhcyBhIHRydWUgY2hhbmdlIG9mIGhvdyBjb21wYW5pZXMgYXJlIGRvaW5nIGZpbmdlcnByaW50aW5nIG1vdGl2YXRlZCBieSBuZXcgYXBwcm9hY2hlcyBvciBjaGFuZ2UgaW4gKGxlZ2lzbGF0aXZlPykgZW52aXJvbm1lbnQuCiogU29tZSBBUEkgY2FsbHMgdXNlZnVsIGZvciBmaW5nZXJwcmludGluZyB3ZXJlIG5vdCBhdmFpbGFibGUgaW4gSlMgYmVmb3JlIDIwMjIuIFRoZWlyIGFkZGl0aW9uIG1vdGl2YXRlZCBjb21wYW5pZXMgdG8gaW5jbHVkZSB0aGVtIGluIHRoZWlyIHRyYWNraW5nIGFwcHJvYWNoZXMuCiogVGhlIHdheSB0cmFja2VyLXJhZGFyIGNvbGxlY3RzIGRhdGEgaGF2ZSBjaGFuZ2VkIGFuZCB0aGVyZSdzIG5vIHJlYWwgY2hhbmdlIGluIGJlaGF2aW9yIG9mIG9yZ2FuaXphdGlvbnMgdGhhdCBkbyBmaW5nZXJwcmludGluZy90cmFja2luZy4KKiBPciBhbnkgY29tYmluYXRpb24gb2YgYWJvdmUuCgpJIGhhdmUgYSBxdWVzdGlvbiBmb3IgeW91LCBkZWFyIHJlYWRlciwgd2hhdCBkbyB5b3UgdGhpbmsgaXMgdGhlIG1vc3QgbGlrZWx5IGV4cGxhbmF0aW9uPyBGZWVsIGZyZWUgdG8gc2hhcmUgYW55IGlkZWFzIHlvdSBoYXZlIG9uIG15IFtsaW5rZWRpbl0oaHR0cHM6Ly93d3cubGlua2VkaW4uY29tL2luL3N0YW5pc2xhdnNtYXRhbmEvKSBvciB2aWEgbXkgZS1tYWlsIHN0YW5pc2xhdnNtYXRhbmFAZ21haWwuY29tLgo=