Group 4 - Final Project

Introduction

As Regork enters the telecommunications market, understanding customer behavior is essential for long-term success. In this analysis, we focus on customer retention, using data from the customer_retention.csv file to build a predictive model. The goal is to accurately forecast whether a customer will leave in the future, enabling Regork to take proactive steps to retain valuable customers.

Customer retention is crucial in the telecommunications industry, as the cost of acquiring new customers often exceeds the cost of keeping existing ones. By developing a model that predicts customer churn, Regork can take targeted actions—such as offering promotions or incentives—based on these predictions. For example, marketing teams can use the model to tailor customer retention campaigns, while the finance team can better forecast company revenue and risk.

This report details the data analysis process, including necessary packages and data preparation, followed by an exploration of trends and relationships between predictors and the response variable. The focus of the analysis is on building and evaluating various machine learning models, including logistic regression, random forest, and decision trees, to identify the best approach for predicting customer churn.

The report provides insights that support model selection and offers recommendations for how Regork can leverage these findings to enhance customer retention strategies.

Packages Required

setwd("/Users/ezishr/Documents/CINCY/Fall 2024/BANA 4080/Final Project") # Local working directory
library(tidyverse)             # Data manipulation, visualization, reading data files
library(tidymodels)            # Models and recipes use purpose
library(rpart)                 # Tree-based models use purpose
library(baguette)              # Tools for bagging models
library(pdp)                   # Use for understanding model behavior
library(vip)                   # Feature importance
library(corrplot)              # Correlation matrices visualization
library(patchwork)             # Side-by-side or multipanel plots purpose
library(gridExtra)             # Arranging multiple plots in a grid
library(kernlab)               # Kernel-based machine learning algorithms

Data Preparation

1. Read data

First, we load the data from provided csv file and look at the NA count across every column:

data <- read_csv('customer_retention.csv')
data$SeniorCitizen <- as.character(data$SeniorCitizen)
colSums(is.na(data)) # 11 NAs in TotalCharges
          Gender    SeniorCitizen          Partner       Dependents 
               0                0                0                0 
          Tenure     PhoneService    MultipleLines  InternetService 
               0                0                0                0 
  OnlineSecurity     OnlineBackup DeviceProtection      TechSupport 
               0                0                0                0 
     StreamingTV  StreamingMovies         Contract PaperlessBilling 
               0                0                0                0 
   PaymentMethod   MonthlyCharges     TotalCharges           Status 
               0                0               11                0 

The TotalCharges column currently contains 11 missing values. When implementing models, we may consider removing this column due to its potential high multicollinearity with the MonthlyCharges column.

2. Add Churn Type

We categorized customers with Status = "Left" based on their contract types into three levels: Still Active (customers with Status = "Current"), Contract Fulfilled (customers who left after their contracts expired), and Early Termination (customers who left before their contracts ended).

# Convert Contract Into Months --------------------------------------------
sample_df <- data

sample_df <- sample_df %>% mutate(
    contract_months = case_when (
      Contract == 'Month-to-month' ~ Tenure,
      Contract == 'One year' ~ 12,
      Contract == 'Two year' ~ 24,
      TRUE ~ NA_real_ )
)


sample_df <- sample_df %>% mutate(
  contract_period = case_when (
    (Tenure <= contract_months) & (Contract != 'Month-to-month') ~ 1,
    (Tenure > contract_months) & (Contract != 'Month-to-month') & (Tenure%%contract_months == 0) ~ (Tenure / contract_months),
    (Tenure > contract_months) & (Contract != 'Month-to-month') & (Tenure%%contract_months != 0) ~ floor(Tenure / contract_months) + 1,
    TRUE ~ Tenure
  )
)

sample_df <- sample_df %>% mutate(
  total_time = case_when(
    (Contract == 'Month-to-month') ~ Tenure,
    (Contract != 'Month-to-month') ~ (contract_months * contract_period)
  )
)


sample_df <- sample_df %>% mutate(
  churn_case = case_when (
    (Status == 'Current') ~ 'Still Active',
    (Status == 'Left') & (total_time == Tenure) ~ 'Contract fulfilled',
    (Status == 'Left') & (total_time > Tenure) & (Contract != 'Month-to-month') ~ 'Early Termination',
    TRUE ~ 'Contract fulfilled'
  )
)

data <- sample_df %>% select(!total_time)
data$contract_period <- as.character(data$contract_period)

char_cols <- colnames(data)[sapply(data, is.character)]
char_cols <- char_cols[(char_cols!='Status') & (char_cols!='contract_period')]

3. Function: read unique values of each column

The function takes a dataset and a list of character columns as input. It prints the unique values of each specified column, displaying them line by line.

check_unique <- function(dataset, char_cols) {
  for (col in char_cols) {
    cat('\nUnique values for', col,':\n')
    print(unique(dataset[[col]]))
  }
}

Exploratory Data Analysis

Analysis Raw Dataset

1. Initial Look of Left Status

In this analysis, we begin by examining the counts of Status across various services and factors in the dataset.

plot_list <- list()
for (col in char_cols) {
  plot <- ggplot(data = data, aes(x = !!sym(col), fill = Status)) +
    geom_bar() +
    ylim(0, 7000) +
    geom_text(stat = 'count', aes(label = after_stat(count), vjust = -0.5, size = 0.1)) +
    labs(title = paste0('Count of Churn by ', col), y='Count')
  
  plot_list[[col]] <- plot
}

grid_plots <- grid.arrange(grobs = plot_list, nrow=6, ncol=3)

The Phone Service column has the highest count of Status = "Left", totaling 1,687. To understand this trend, we will analyze customers with Phone Service and Internet Service separately, along with their add-ons, to identify potential reasons for the high counts. Additionally, we will investigate whether customers with both services are more likely to leave and explore the factors contributing to their departure.

2. Demographic Look

Second, we have a look at demographic:

demographic_cols <- c('Gender', 'SeniorCitizen', 'Partner', 'Dependents')
demo <- data
demo <- demo %>% mutate(
  SeniorCitizen = case_when(
    SeniorCitizen == 0 ~ 'No',
    SeniorCitizen == 1 ~ 'Yes'
  )
)

## Charts demographics
for (col in demographic_cols) {
  plot <- ggplot(data = demo, aes(x = !!sym(col), fill = Status)) +
    geom_bar(position = "dodge") +
    labs(title = paste("Count of Status by", col), x = col, y = "Count") +
    theme_minimal() +
    scale_fill_brewer(palette = "Set2") +
    theme(legend.position = "bottom") +
    geom_text(
      stat = 'count',
      aes(label = after_stat(count)),
      position = position_dodge(width = 0.9),
      vjust = -0.5,
      size = 3)
  
  assign(paste0("plot_", col), plot)
}

plot_Dependents + plot_Gender + plot_Partner + plot_SeniorCitizen

Overall, there were more Current customers than those who left. However, customers without partners or dependents tended to leave at higher rates, which could be attributed to the charges for individuals living alone. We will conduct further analysis to explore this aspect. Regarding gender, there was no significant difference in the departure rates between males and females.

3. Churn Category Look

When preparing the dataset, we have classified the churn category into three: Still Active, Contract fulfilled, and Early Termination.

ggplot(data, aes(x = Contract)) +
    geom_bar(fill = "skyblue", color = "black") +
    geom_text(stat = "count", aes(label = after_stat(count)), vjust = -0.3, color = "black", size = 3.5) +
    facet_grid(churn_case ~ .) + 
    labs(title = "Churn Cases Count by Contract Type", x = "Contract Type", y = "Count")

As shown above, there are relatively few customers leaving due to Early Termination, suggesting that most customers tend to use the service until the end of their contract. However, the Month-to-month contract type has the highest churn rate, with approximately half of the customers in this category leaving. This could indicate that these customers may not have received the expected level of service or their specific needs and concerns were not addressed. We will conduct further analysis on this in the Monthly Charges and Churn Type section.

ggplot(data, aes(PaymentMethod, fill = churn_case)) + 
  geom_bar() +
  facet_wrap( ~Contract ) + 
  coord_flip() +
  labs(title = "Count of Payment Methods Across Contract Types and Churn Cases",
       x = "Payment Method",
       y = "Count",
       fill = "Churn Case"
       )

The analysis reveals that the payment methods Credit Bank and Bank Transfer maintain a consistent count across all contract types. This uniform distribution suggests that these payment methods are equally preferred by customers regardless of their chosen contract type. This consistency highlights the broad appeal and reliability of these options, making them essential for meeting customer expectations across the board. Additionally, the balanced usage of these methods provides operational benefits by simplifying resource allocation and planning.

The Month-to-month contract type displays an exceptionally high count for Electronic Check payments. This trend could signify that customers with shorter-term commitments prefer the flexibility and immediacy provided by this method.

Phone Service Analysis

1. General View

First, we get the data of customers having Phone Service, which means PhoneService != "No" and have a breif look at unique values across columns:

having_phone_service <- data[data$PhoneService != 'No', ]
having_phone_service <- having_phone_service[, !(colnames(having_phone_service) %in% c('contract_period', 'contract_months', 'contract_period'))]

char_cols <- colnames(having_phone_service)[sapply(having_phone_service, is_character)]
char_cols <- char_cols[char_cols!='PhoneService']

check_unique(having_phone_service, char_cols)

Unique values for Gender :
[1] "Male"   "Female"

Unique values for SeniorCitizen :
[1] "0" "1"

Unique values for Partner :
[1] "No"  "Yes"

Unique values for Dependents :
[1] "No"  "Yes"

Unique values for MultipleLines :
[1] "No"  "Yes"

Unique values for InternetService :
[1] "DSL"         "Fiber optic" "No"         

Unique values for OnlineSecurity :
[1] "Yes"                 "No"                  "No internet service"

Unique values for OnlineBackup :
[1] "No"                  "Yes"                 "No internet service"

Unique values for DeviceProtection :
[1] "Yes"                 "No"                  "No internet service"

Unique values for TechSupport :
[1] "No"                  "Yes"                 "No internet service"

Unique values for StreamingTV :
[1] "No"                  "Yes"                 "No internet service"

Unique values for StreamingMovies :
[1] "No"                  "Yes"                 "No internet service"

Unique values for Contract :
[1] "One year"       "Month-to-month" "Two year"      

Unique values for PaperlessBilling :
[1] "No"  "Yes"

Unique values for PaymentMethod :
[1] "Mailed check"              "Electronic check"         
[3] "Credit card (automatic)"   "Bank transfer (automatic)"

Unique values for Status :
[1] "Current" "Left"   

Unique values for churn_case :
[1] "Still Active"       "Contract fulfilled" "Early Termination" 
plot_list <- list()
for (col in char_cols) {
  plot <- ggplot(data = having_phone_service, aes(x = !!sym(col), fill = Status)) +
    geom_bar() +
    ylim(0, 7000) +
    geom_text(stat = 'count', aes(label = after_stat(count), vjust = -0.5, size = 0.1)) +
    labs(title = paste0('Count of Churn by ', col), y='Count')
  
  plot_list[[col]] <- plot
}
grid_plots <- grid.arrange(grobs = plot_list, nrow=5, ncol=4)

As seen above, customers with InternetService = "Fiber optic" and a Month-to-month contract type had the highest churn rates.

2. Charges across Contract Type and Internet Service

We will look more in the monthly chagres across the Contract Type and Internet Services.

ggplot(data = having_phone_service, aes(x = Contract, y = MonthlyCharges, fill = Status)) +
  geom_boxplot() +
  labs(title = "Monthly Charges In General by Contract Type and Churn Status",
       x = "Contract Type",
       y = "Monthly Charges")

Generally, regardless of the type of Internet Service, the median monthly charges for customers who have churned tend to be higher than those for active customers. This suggests that higher charges may be a contributing factor to customer churn, potentially indicating a perception of being overcharged.

have_internet <- having_phone_service[having_phone_service$InternetService != 'No',]

ggplot(data = have_internet, aes(x = Contract, y = MonthlyCharges, fill = Status)) +
  geom_boxplot() +
  facet_wrap(~ InternetService) +
  labs(title = "Monthly Charges by Contract Type, Churn Status, and Internet Service",
       x = "Contract Type",
       y = "Monthly Charges")

When examining specific Internet Services combined with Contract Type among customers using Phone Service, customers using Fiber optic typically face higher charges compared to those with DSL. However, the median charges for both churned and active customers across Contract Types are not significantly different. This suggests that customer churn may be influenced more by dissatisfaction with the service quality rather than pricing.

3. Charges across Contract Type, Multiple Lines, and Internet Service

# Boxplot of Monthly Charges by Contract and Status with NO InternetService
no_internet <- having_phone_service[having_phone_service$InternetService == 'No',]

ggplot(data = no_internet, aes(x = Contract, y = MonthlyCharges, fill = Status)) +
  geom_boxplot() +
  facet_wrap(~ MultipleLines, labeller = as_labeller(c("Yes" = "Has Multiple Lines", "No" = "No Multiple Lines"))) +
  labs(title = "Monthly Charges by Contract Type, Multiple Lines, and Churn Status - No Internet Service",
       x = "Contract Type",
       y = "Monthly Charges")

Churned customers in month-to-month contracts could be more price-sensitive. They might have initially chosen the month-to-month flexibility, attracted by low starting prices or discounts, but left when they found the pricing unsustainable or felt they were not getting value for their money.

no_internet_no_multipleLines <- no_internet[no_internet$MultipleLines == 'No',]

# Any SeniorCitizen with NO Internet & NO Multiplelines --------
senior_no_multiple <- no_internet_no_multipleLines[no_internet_no_multipleLines$SeniorCitizen == 1,]  # Subset for senior citizens with no multiple lines
non_senior_no_multiple <- no_internet_no_multipleLines[no_internet_no_multipleLines$SeniorCitizen == 0,]  # Subset for non-senior citizens with no multiple lines

# Boxplot of Monthly Charges by Status for Senior Citizens
plot3 <- ggplot(senior_no_multiple, aes(x = Contract, y = MonthlyCharges, fill = Status)) +
  geom_boxplot() +
  labs(title = "Monthly Charges by Status for Senior Citizens (No Internet & No Multiple Lines)",
       x = "Status", y = "Monthly Charges")

# Boxplot of Monthly Charges by Status for Non-Senior Citizens
plot4 <- ggplot(non_senior_no_multiple, aes(x = Contract, y = MonthlyCharges, fill = Status)) +
  geom_boxplot() +
  labs(title = "Monthly Charges by Status for Non-Senior Citizens (No Internet & No Multiple Lines)",
       x = "Status", y = "Monthly Charges")

plot3 + plot4

Among senior citizens, there is no churn for customers with one-year and two-year contract types when they do not use internet service or multiple lines for their phone service. This suggests that senior citizens who choose these contracts are likely more stable in their decisions, potentially due to the predictability and stability of long-term commitments. Additionally, the fact that they are not using internet service or multiple lines could indicate that they prefer simpler service plans that meet their basic needs, possibly at lower costs, which may increase their satisfaction and loyalty.

In contrast, non-senior citizens are exhibiting churn in all contract types (month-to-month, one-year, and two-year), indicating that they are less committed or more price-sensitive. They might be more likely to switch providers, possibly due to competitive offers or dissatisfaction with the service. The fact that churn occurs across all contract types for non-senior citizens suggests that factors like pricing, service features, or customer experience are significant drivers of churn, even when the charges across these customers appear similar.

Although the charges for non-senior citizens are similar across contract types, the churn rates differ significantly. This could indicate that the reason for churn may not be directly tied to price but possibly to factors like service satisfaction, perceived value, or flexibility.

4. Tenure Distribution

Now we look at the Tenure distribution across contract types and churn status:

# Plot tenure vs churn for customers with phone service (initial view) -----------
ggplot(data = having_phone_service, aes(x = Tenure, fill = Status)) +
  geom_histogram(binwidth = 1, alpha = 0.6) +
  facet_wrap(~Contract) +
  labs(title = "Tenure Distribution by Contract Type and Churn Status",
       x = "Tenure (Months)",
       y = "Count")

The tenure distribution by contract type and churn status shows that month-to-month customers tend to churn at lower tenures, mostly between 0-20 months. In contrast, one-year and two-year contract customers are more likely to stay longer, with most of the churn occurring towards the end of the tenure distribution, around 60+ months. This suggests that month-to-month contracts may attract more short-term customers, while longer contracts tend to retain customers for extended periods.

Internet Service Analysis

In this part, we will analyze the Internet Service and its add-ons. First, we get the data of customers having Internet Service, which means InternetService != "No":

having_internet_service <- data[data$InternetService != 'No', ]
having_internet_service <- having_internet_service[, !(colnames(having_internet_service) %in% c('contract_period', 'contract_months'))]

1. General View

Similar to the analysis of phone service, the churn patterns for customers with internet service across different demographics show comparable trends. This suggests that the company maintains a balanced approach, providing consistent service quality for both phone and internet services, without bias towards either.

char_cols <- colnames(having_internet_service)[sapply(having_internet_service, is_character)]
char_cols <- char_cols[char_cols != 'InternetService']
check_unique(having_internet_service, char_cols)

Unique values for Gender :
[1] "Female" "Male"  

Unique values for SeniorCitizen :
[1] "0" "1"

Unique values for Partner :
[1] "Yes" "No" 

Unique values for Dependents :
[1] "No"  "Yes"

Unique values for PhoneService :
[1] "No"  "Yes"

Unique values for MultipleLines :
[1] "No phone service" "No"               "Yes"             

Unique values for OnlineSecurity :
[1] "No"  "Yes"

Unique values for OnlineBackup :
[1] "Yes" "No" 

Unique values for DeviceProtection :
[1] "No"  "Yes"

Unique values for TechSupport :
[1] "No"  "Yes"

Unique values for StreamingTV :
[1] "No"  "Yes"

Unique values for StreamingMovies :
[1] "No"  "Yes"

Unique values for Contract :
[1] "Month-to-month" "One year"       "Two year"      

Unique values for PaperlessBilling :
[1] "Yes" "No" 

Unique values for PaymentMethod :
[1] "Electronic check"          "Mailed check"             
[3] "Bank transfer (automatic)" "Credit card (automatic)"  

Unique values for Status :
[1] "Current" "Left"   

Unique values for churn_case :
[1] "Still Active"       "Contract fulfilled" "Early Termination" 
plot_list <- list()
for (col in char_cols) {
  plot <- ggplot(data = having_internet_service, aes(x = !!sym(col), fill = churn_case)) +
    geom_bar() +
    labs(title = paste0("Count of Churn by ", col), y = 'Count', fill = "Churn Case")
  
  plot_list[[col]] <- plot
}
grid_plots <- grid.arrange(grobs = plot_list, nrow = 5, ncol = 4)

2. Charges across Contract Type and Internet Service

Next, we examine how monthly charges vary with different add-ons for customers with Internet Service. The logic behind this graph is to focus on customers who have Internet Service along with only one additional feature. For example, the first graph illustrates customers with Internet Service who have added only Online Security, without any other add-ons.

plot_list <- list()
add_ons <- c("OnlineSecurity", "OnlineBackup", "DeviceProtection", "TechSupport", "StreamingTV", "StreamingMovies")

for(add_on in add_ons) {
  non_add <- add_ons[add_ons != add_on]
  
  sample1 <- having_internet_service
  
  for (col in non_add) {
    sample1 <- sample1[sample1[[col]] == "No",]
  }
  
  plot <- ggplot(data = sample1, aes(x = MonthlyCharges, y = Contract, fill = !!sym(add_on))) +
    geom_boxplot() +
    facet_grid(InternetService ~ Status) +
    labs(title = paste0("Monthly Charges by Contract Type and ", add_on),
         x = "Monthly Charges",
         y = "Contract Type")
  
  plot_list[[add_on]] <- plot
}

grid_plots <- grid.arrange(grobs = plot_list, nrow=3, ncol=2)

plot_list <- list()

for(add_on in add_ons) {
  non_add <- add_ons[add_ons != add_on]
  
  sample1 <- having_internet_service
  
  for (col in non_add) {
    sample1 <- sample1[sample1[[col]] == "No",]
  }
  
  plot <- ggplot(data = sample1, aes(x = Contract, fill = !!sym(add_on))) +
    geom_bar(position = "dodge") +
    facet_grid(InternetService ~ Status) +
    labs(title = paste0("Count of Status by Contract Type and ", add_on),
         x = "Contract Type",
         y = "Count")
  
  plot_list[[add_on]] <- plot
}

grid_plots <- grid.arrange(grobs = plot_list, nrow=3, ncol=2)

From the grid plots above, it is evident that customers with the StreamingMovies and StreamingTV add-ons incur significantly higher charges. This group also exhibits a notably higher churn rate, suggesting that the increased costs may not align with customer expectations, prompting them to leave.

3. Tenure Distribution

ggplot(data = having_internet_service, aes(x = Tenure, fill = Status)) +
  geom_histogram(binwidth = 1, alpha = 0.6) +
  facet_wrap(~Contract) +
  labs(title = "Tenure Distribution by Contract Type and Churn Status",
       x = "Tenure (Months)",
       y = "Count",
       fill = "Status")

The similar tenure distribution across both internet and phone services suggests that customer retention patterns are consistent between the two. This implies that the factors influencing churn, such as pricing, service quality, or competition, are likely universal and not specific to either service.

Dependents and Partner Analysis

1. Initial Look

We first look at the churn_case count across demographic features. Generally, customers tent to churn when their contracts expired. However, customers that are SeniorCitizen are the least likey to churn, which might imply that they prefer stable service.

demographic_cols <- c('Gender', 'SeniorCitizen', 'Partner', 'Dependents')
demo <- data
demo <- demo %>% mutate(
  SeniorCitizen = case_when(
    SeniorCitizen == 0 ~ 'No',
    SeniorCitizen == 1 ~ 'Yes'
  )
)

## Charts demographics
for (col in demographic_cols) {
  plot <- ggplot(data = demo, aes(x = !!sym(col), fill = churn_case)) +
    geom_bar(position = "dodge") +
    labs(title = paste("Count of Status by", col), x = col, y = "Count") +
    theme_minimal() +
    scale_fill_brewer(palette = "Set2") +
    theme(legend.position = "bottom")
  
  assign(paste0("plot_", col), plot)
}

plot_Dependents + plot_Gender + plot_Partner + plot_SeniorCitizen

Now we look further into the monthly charges. We also extract data about customers having no dependents and customers having no partner to see if there is significant difference in charge for them.

no_dependents <- demo[demo$Dependents == 'No',]
has_dependents <- demo[demo$Dependents == 'Yes',]

no_partner <- demo[demo$Partner == 'No',]
has_partner <- demo[demo$Partner == 'Yes',]

2. Dependents Perspective

plot1 <- ggplot(no_dependents, aes(x = Status, y = MonthlyCharges, fill = Status)) +
  geom_boxplot() +
  labs(
    title = "Monthly Charges Distribution by Status (No Dependents)",
    x = "Status",
    y = "Monthly Charges"
  ) +
  theme_minimal()


plot2 <- ggplot(has_dependents, aes(x = Status, y = MonthlyCharges, fill = Status)) +
  geom_boxplot() +
  labs(
    title = "Monthly Charges Distribution by Status (Have Dependents)",
    x = "Status",
    y = "Monthly Charges"
  ) +
  theme_minimal()

plot1 + plot2

Insights:

  • Higher Charges for Customers Who Left:
    • The median Monthly Charges for those who have left is way higher compared to that of those who are still current.
    • This implies that higher monthly charges could be a cause for churning among the no-partner customers.
  • In demographic perspective, the monthly charge is not big different whether a customer has dependent or not. This suggests that having dependent has slight impact to the monthly charge.

3. Partner Perspective

plot1 <- ggplot(no_partner, aes(x = Status, y = MonthlyCharges, fill = Status)) +
  geom_boxplot() +
  labs(
    title = "Monthly Charges Distribution by Status (No Partner)",
    x = "Status",
    y = "Monthly Charges"
  ) +
  theme_minimal()

plot2 <- ggplot(has_partner, aes(x = Status, y = MonthlyCharges, fill = Status)) +
  geom_boxplot() +
  labs(
    title = "Monthly Charges Distribution by Status (No Partner)",
    x = "Status",
    y = "Monthly Charges"
  ) +
  theme_minimal()

plot1 + plot2

Insights:

  • Higher Median Charges for Customers Who Left:
    • Similar to Dependents perspective above, the customers who have left had been monthly charged higher than those are still on service.
  • Customers without partners are paying higher monthly charges compared to those with partners. This insight suggests a potential need to revise pricing strategies to cater to solo customers and ensure equitable service offerings.

Business Implication:

  • Customers with no partner paying higher monthly charges are more likely to experience churn. Targeted discounts or personalized plans for this category could help in retaining customers.

Monthly Charges and Churn Type

ggplot(data, aes(x = churn_case, y = MonthlyCharges, fill = Contract)) +
  geom_boxplot() +
  labs(
    title = "Monthly Charges by Contract Type and Churn Case",
    x = "Churn Case",
    y = "Monthly Charges"
  ) +
  theme_minimal() +
  theme(legend.position = "right")

The average monthly charge for customers with a Month-to-month contract is slightly higher compared to other contract types. This likely reflects the comprehensive range of services utilized by these customers. Therefore, it is unlikely that monthly charges are a significant factor contributing to customer churn in this group.

Models Implementation

Logistic Regression

Since certain predictor variables are dependent on other columns (e.g., MultipleLines depends on PhoneService), it is important to cleanse these columns first to avoid Multicollinearity in logistic regression models. We will create custom processing functions for the InternetService and PhoneService columns, ensuring that each add-on for these services is grouped and combined into a single column. This will help prevent issues with multicollinearity and improve the model’s accuracy.

process_internet_features <- function(df) {
  new_data <- df %>% 
    mutate(
      internet_status = case_when(
        InternetService == "No" ~ "No Internet",
        
        InternetService == "DSL" &
          rowSums(across(c(OnlineSecurity, OnlineBackup, DeviceProtection), ~ .x == "No")) == 3 &
          rowSums(across(c(StreamingTV, StreamingMovies), ~ .x == "No")) == 2 &
          TechSupport == "No" ~ "DSL Basic",
        
        InternetService == "DSL" &
          rowSums(across(c(OnlineSecurity, OnlineBackup, DeviceProtection), ~ .x == "Yes")) > 0 &
          rowSums(across(c(StreamingTV, StreamingMovies), ~ .x == "Yes")) > 0 &
          TechSupport == "Yes" ~ "DSL Full",
        
        InternetService == "DSL" &
          rowSums(across(c(OnlineSecurity, OnlineBackup, DeviceProtection), ~ .x == "Yes")) > 0 &
          rowSums(across(c(StreamingTV, StreamingMovies), ~ .x == "Yes")) <= 0 &
          TechSupport != "Yes" ~ "DSL Security",
        
        InternetService == "DSL" &
          rowSums(across(c(OnlineSecurity, OnlineBackup, DeviceProtection), ~ .x == "Yes")) <= 0 &
          rowSums(across(c(StreamingTV, StreamingMovies), ~ .x == "Yes")) > 0 &
          TechSupport != "Yes" ~ "DSL Entertainment",
        
        InternetService == "DSL" &
          rowSums(across(c(OnlineSecurity, OnlineBackup, DeviceProtection), ~ .x == "Yes")) <= 0 &
          rowSums(across(c(StreamingTV, StreamingMovies), ~ .x == "Yes")) <= 0 &
          TechSupport == "Yes" ~ "DSL Support",
        
        InternetService == "DSL" &
          rowSums(across(c(OnlineSecurity, OnlineBackup, DeviceProtection), ~ .x == "Yes")) > 0 &
          rowSums(across(c(StreamingTV, StreamingMovies), ~ .x == "Yes")) <= 0 &
          TechSupport == "Yes" ~ "DSL Security & Support",
        
        InternetService == "DSL" &
          rowSums(across(c(OnlineSecurity, OnlineBackup, DeviceProtection), ~ .x == "Yes")) <= 0 &
          rowSums(across(c(StreamingTV, StreamingMovies), ~ .x == "Yes")) > 0 &
          TechSupport == "Yes" ~ "DSL Entertainment & Support",
        
        InternetService == "DSL" &
          rowSums(across(c(OnlineSecurity, OnlineBackup, DeviceProtection), ~ .x == "Yes")) > 0 &
          rowSums(across(c(StreamingTV, StreamingMovies), ~ .x == "Yes")) > 0 &
          TechSupport == "No" ~ "DSL Security & Entertainment",
        
        InternetService == "Fiber optic" &
          rowSums(across(c(OnlineSecurity, OnlineBackup, DeviceProtection), ~ .x == "No")) == 3 &
          rowSums(across(c(StreamingTV, StreamingMovies), ~ .x == "No")) == 2 &
          TechSupport == "No" ~ "Fiber optic Basic",
        
        InternetService == "Fiber optic" &
          rowSums(across(c(OnlineSecurity, OnlineBackup, DeviceProtection), ~ .x == "Yes")) > 0 &
          rowSums(across(c(StreamingTV, StreamingMovies), ~ .x == "Yes")) > 0 &
          TechSupport == "Yes" ~ "Fiber optic Full",
        
        InternetService == "Fiber optic" &
          rowSums(across(c(OnlineSecurity, OnlineBackup, DeviceProtection), ~ .x == "Yes")) > 0 &
          rowSums(across(c(StreamingTV, StreamingMovies), ~ .x == "Yes")) <= 0 &
          TechSupport != "Yes" ~ "Fiber optic Security",
        
        InternetService == "Fiber optic" &
          rowSums(across(c(OnlineSecurity, OnlineBackup, DeviceProtection), ~ .x == "Yes")) <= 0 &
          rowSums(across(c(StreamingTV, StreamingMovies), ~ .x == "Yes")) > 0 &
          TechSupport != "Yes" ~ "Fiber optic Entertainment",
        
        InternetService == "Fiber optic" &
          rowSums(across(c(OnlineSecurity, OnlineBackup, DeviceProtection), ~ .x == "Yes")) <= 0 &
          rowSums(across(c(StreamingTV, StreamingMovies), ~ .x == "Yes")) <= 0 &
          TechSupport == "Yes" ~ "Fiber optic Support",
        
        InternetService == "Fiber optic" &
          rowSums(across(c(OnlineSecurity, OnlineBackup, DeviceProtection), ~ .x == "Yes")) > 0 &
          rowSums(across(c(StreamingTV, StreamingMovies), ~ .x == "Yes")) <= 0 &
          TechSupport == "Yes" ~ "Fiber optic Security & Support",
        
        InternetService == "Fiber optic" &
          rowSums(across(c(OnlineSecurity, OnlineBackup, DeviceProtection), ~ .x == "Yes")) <= 0 &
          rowSums(across(c(StreamingTV, StreamingMovies), ~ .x == "Yes")) > 0 &
          TechSupport == "Yes" ~ "Fiber optic Entertainment & Support",
        
        InternetService == "Fiber optic" &
          rowSums(across(c(OnlineSecurity, OnlineBackup, DeviceProtection), ~ .x == "Yes")) > 0 &
          rowSums(across(c(StreamingTV, StreamingMovies), ~ .x == "Yes")) > 0 &
          TechSupport == "No" ~ "Fiber optic Security & Entertainment",
        
        TRUE ~ NA_character_
      )
    ) %>% select(-c(InternetService, OnlineSecurity, OnlineBackup, DeviceProtection, StreamingTV, StreamingMovies, TechSupport))
  
  return(new_data)
}

process_phone_feature <- function(df) {
  df %>% mutate (
    phone_status = case_when(
      PhoneService == "No" ~ "No",
      PhoneService == "Yes" & MultipleLines == "No" ~ "Single Line",
      PhoneService == "Yes" & MultipleLines == "Yes" ~ "Multiple Lines",
      TRUE ~ NA_character_
    )
  ) %>% select (-c(PhoneService, MultipleLines))
}

Now we apply the above processors to the dataset and remove the column TotalCharges since it has high correlation with MonthlyCharges, which might create multicollinearity. We also have a look at the first 5 rows:

df1 <- data %>% select(-c("contract_months", "contract_period", "churn_case", "TotalCharges"))
df2 <- process_internet_features(df1)
df2 <- process_phone_feature(df2)

head(df2, 5)

We create a Logistic Regression model with the Classification mode since the target variable Status has two categories: Current and Left. The model includes 5-fold cross-validation, a recipe for creating dummy variables for categorical columns, and normalizing the values for numerical columns. Additionally, a tuning grid is applied to optimize the model’s parameters.

# Convert Status to a factor and specify levels
df2$Status <- factor(df2$Status)

# Split data into training and testing sets
set.seed(123)
data_split <- initial_split(data = df2, prop = 0.7, strata = Status)
train <- training(data_split)
test <- testing(data_split)

# Set up cross-validation
set.seed(123)
logistic_kfolds <- vfold_cv(train, v = 5, strata = Status)

logistic_recipe <- recipe(Status ~ ., data = train) %>%
  step_dummy(all_nominal_predictors(), one_hot = TRUE) %>%
  step_YeoJohnson(all_numeric_predictors()) %>%
  step_normalize(all_numeric_predictors())

# Define logistic regression model
logistic_ridge_model <- logistic_reg(penalty = tune(), mixture = tune()) %>%
  set_engine("glmnet") %>%
  set_mode("classification")

# Create workflow
logistic_ridge_wf <- workflow() %>%
  add_recipe(logistic_recipe) %>%
  add_model(logistic_ridge_model)

# Create hyperparameter search grid
logistic_grid <- grid_regular(mixture(), penalty(c(-10,5)), levels = 10)

# Perform hyperparamter search
tuning_results <- logistic_ridge_wf %>%
  tune_grid(resamples = logistic_kfolds, grid = logistic_grid)

print(tuning_results %>%
  collect_metrics() %>%
  dplyr::filter(.metric == "roc_auc") %>%
  arrange(desc(mean)))

autoplot(tuning_results)

With such low regularization values (ranging from around 1e-07 to 1e-03), the model can fit the training data well, achieving a high ROC AUC score (around 0.84) during cross-validation. This suggests that the features in the dataset are highly informative, enabling the Logistic Regression model to effectively distinguish between the Status classes, even with minimal regularization.

Now, we choose the best from tuning search to apply on our test dataset and see top 10 important features:

best_hyperparameters <- select_best(tuning_results, metric = "roc_auc")

final_wf <- workflow() %>%
  add_recipe(logistic_recipe) %>%
  add_model(logistic_ridge_model) %>%
  finalize_workflow(best_hyperparameters)

final_fit <- final_wf %>%
  fit(data = train)

final_fit %>%
  extract_fit_parsnip() %>%
  vip()

Tenure appears to be the most influential factor in predicting customer churn. Following that, MonthlyCharges also plays a significant role in customer departures. This suggests that we should further investigate the current charges, especially in conjunction with the previous analysis of different services, to gain deeper insights into the factors driving churn.

Let have a look at ROC curve, which measures the model’s ability to distinguish between the two classes of Status:

final_fit %>% 
   predict(test, type = "prob") %>%
   mutate(truth = test$Status) %>%
   roc_curve(truth, .pred_Current) %>%
   autoplot()


print(final_fit %>%
        predict(test, type = "prob") %>%
        mutate(truth = test$Status) %>%
        roc_auc(truth, .pred_Current)
    )

print(final_fit %>%
    predict(test) %>%
    bind_cols(test %>% select(Status)) %>%
    conf_mat(truth = Status, estimate = .pred_class)
  )
          Truth
Prediction Current Left
   Current    1411  258
   Left        132  299
  • The curve’s proximity to the top-left corner indicates that the Logistic Regression model achieves high sensitivity while maintaining a low false positive rate at optimal thresholds.

  • The model’s AUC score of 0.85 (as previously calculated) further validates its effectiveness in distinguishing between classes, with values closer to 1 signifying excellent performance.

Decision Tree

Now we test the Decision Tree model with same training and testing dataset. To ensure consistency, the logistic_recipe was applied as a preprocessing step:

decision_tree_model <- decision_tree(mode = "classification") %>%
  set_engine("rpart")

decision_tree_fit <- workflow() %>%
  add_recipe(logistic_recipe) %>%
  add_model(decision_tree_model) %>%
  fit(data = train)

rpart.plot::rpart.plot(decision_tree_fit$fit$fit$fit)
Warning: Cannot retrieve the data used to build the model (model.frame: object '..y' not found).
To silence this warning:
    Call rpart.plot with roundint=FALSE,
    or rebuild the rpart model with model=TRUE.

The Decision Tree model identified Contract_Month.to.month as the key predictor of churn, prioritizing the flexibility of short-term contracts as a significant risk factor. This information is also seen when we analyzed the services. In contrast, the Logistic Regression model highlighted Tenure, suggesting that customers with longer relationships are less likely to churn.

This difference reflects the models’ approaches: the Decision Tree focuses on immediate splits in the data, while Logistic Regression emphasizes linear relationships. Together, these insights suggest targeting month-to-month customers and fostering long-term loyalty to reduce churn.

Now we do hyperparameters tuning in other to choose the best model. We choose cost_complexity, tree_depth, and min_n to tune:

decision_tree_model <- decision_tree(
  mode = "classification",
  cost_complexity = tune(),
  tree_depth = tune(),
  min_n = tune()
) %>%
  set_engine("rpart")

decision_tree_hyper_grid <- grid_regular(
  cost_complexity(),
  tree_depth(),
  min_n(),
  levels = 5
)

set.seed(123)
decision_tree_results <- tune_grid(decision_tree_model, logistic_recipe, resamples = logistic_kfolds, grid = decision_tree_hyper_grid)

show_best(decision_tree_results, metric = "roc_auc", n = 5)

The roc_auc score of this model is slightly lower than those from Logistic Regression. We continue to choose the best model from these tuning results and apply it on test dataset:

dt_best_model <- select_best(decision_tree_results, metric = 'roc_auc')

dt_final_wf <- workflow() %>%
  add_recipe(logistic_recipe) %>%
  add_model(decision_tree_model) %>%
  finalize_workflow(dt_best_model)

dt_final_fit <- dt_final_wf %>%
  fit(data = train)

dt_final_fit %>% 
   predict(test, type = "prob") %>%
   mutate(truth = test$Status) %>%
   roc_curve(truth, .pred_Current) %>%
   autoplot()


print(dt_final_fit %>%
        predict(test) %>%
        bind_cols(test %>% select(Status)) %>%
        conf_mat(truth = Status, estimate = .pred_class))
          Truth
Prediction Current Left
   Current    1365  262
   Left        178  295
print(dt_final_fit %>%
        predict(test, type = "prob") %>%
        mutate(truth = test$Status) %>%
        roc_auc(truth, .pred_Current)
    )

Compared with Logistic Regression model, the Decision Tree model performs slightly worse.

Random Forest

Now we create Random Forest model, which has a number of sub trees, to see if it performs better:

rf_mod <- rand_forest(mode = "classification") %>%
  set_engine("ranger")

rf_results <- fit_resamples(rf_mod, logistic_recipe, logistic_kfolds)

collect_metrics(rf_results)
rf_mod <- rand_forest(
  mode = "classification",
  trees = tune(),
  mtry = tune(),
  min_n = tune()
  ) %>%
  set_engine("ranger", importance = "impurity")

rf_hyper_grid <- grid_regular(
  trees(range = c(50, 800)),
  mtry(range = c(2, 39)),
  min_n(range = c(1, 20)),
  levels = 5
)

set.seed(123)
rf_results <- tune_grid(rf_mod, logistic_recipe, resamples = logistic_kfolds, grid = rf_hyper_grid)
show_best(rf_results, metric = "roc_auc")

Generally, the roc_auc scores across different models converges to 0.84, which is higher than Decision Tree model but still lower than the Logistic Regressions model. Now we choose the best model from these hyperparameters tuning and predict with our test dataset:

rf_best_hyperparameters <- select_best(rf_results, metric = "roc_auc")

final_rf_wf <- workflow() %>%
  add_recipe(logistic_recipe) %>%
  add_model(rf_mod) %>%
  finalize_workflow(rf_best_hyperparameters)

rf_final_fit <- final_rf_wf %>%
  fit(data = train)

rf_final_fit %>% 
   predict(test, type = "prob") %>%
   mutate(truth = test$Status) %>%
   roc_curve(truth, .pred_Current) %>%
   autoplot()


print(rf_final_fit %>%
        predict(test) %>%
        bind_cols(test %>% select(Status)) %>%
        conf_mat(truth = Status, estimate = .pred_class))
          Truth
Prediction Current Left
   Current    1468  362
   Left         75  195
print(rf_final_fit %>%
        predict(test, type = "prob") %>%
        mutate(truth = test$Status) %>%
        roc_auc(truth, .pred_Current)
    )

From the confusion matrix, we observe that the model performs significantly better at predicting the Current class compared to the Left class, with a higher number of true positives and fewer false negatives for Current. However, its performance in predicting the Left class is weaker, as indicated by the higher number of false negatives.

Summary

Key Findings

Customer Demographics and Churn Patterns

  • Customers without partners or dependents exhibited higher churn rates. This trend suggests that individual customers may find the service charges less justifiable, requiring tailored strategies to address their needs.
  • Senior citizens displayed higher retention, likely valuing stable and predictable service plans. In contrast, non-senior citizens were more price-sensitive and exhibited churn across all contract types.

Contract Types and Churn Behavior

  • Month-to-month contracts had the highest churn rate, primarily driven by short-tenure customers leaving within 0–20 months. These customers may prioritize flexibility and are more susceptible to pricing dissatisfaction or unmet service expectations.
  • Longer contracts (one-year and two-year) retained customers longer, with churn occurring toward the end of their tenure.

Phone and Internet Services: Common Patterns

  • Churn patterns across Phone and Internet Services demonstrated consistency, indicating that universal factors such as pricing, service quality, and competition drive customer behavior.
  • For both services, higher monthly charges were strongly associated with churn, with churned customers paying median charges higher than those who stayed.

Internet Service and Add-ons

  • Customers with StreamingMovies and StreamingTV add-ons faced significantly higher charges and exhibited higher churn rates. This highlights a potential misalignment between pricing and perceived value, prompting dissatisfaction among these customers.
  • Customers using only one add-on, such as Online Security, showed relatively stable charges and churn rates, suggesting that simpler service bundles could enhance satisfaction.

Dependents and Partners Analysis

  • Customers without partners paid higher monthly charges and were more likely to churn, emphasizing the need for equitable pricing for solo customers.
  • The presence of dependents had minimal impact on monthly charges but may slightly influence retention, as customers with dependents displayed a marginally higher likelihood of staying.

Business Implications and Recommendations

Reassess Pricing Strategies

  • Consider introducing targeted discounts or tailored plans for individual customers without partners to address their unique needs and improve retention.
  • Review the pricing structure for add-ons like StreamingMovies and StreamingTV to better align charges with customer expectations.

Enhance Value Proposition for Month-to-Month Contracts

  • For month-to-month customers, implement loyalty incentives or added-value features to reduce early churn and build long-term customer relationships.

Focus on Simplicity and Stability

  • Simplified service bundles with affordable pricing could enhance satisfaction among solo customers and senior citizens, ensuring that their needs are met without excessive costs.

Prioritize Customer Experience

  • Address non-price factors like service quality, perceived value, and responsiveness to customer concerns to mitigate churn across all demographics.
  • Utilize customer feedback to identify gaps in service and implement targeted improvements.

Models Implemented

Logistic Regression

  • To address multicollinearity, custom processing functions were created for the InternetService and PhoneService columns, ensuring each add-on was grouped into a single column.
  • A classification Logistic Regression model was implemented, targeting the Status variable (Current or Left). The model included:
    • 5-fold cross-validation.
    • A recipe for creating dummy variables for categorical columns and normalizing numerical columns.
    • A tuning grid to optimize parameters.
  • Performance:
    • Regularization values ranged from 1e-07 to 1e-03, enabling a good fit for the training data.
    • The model achieved a ROC AUC score of 0.84 during cross-validation, indicating high sensitivity and a low false positive rate.
    • Key Insights:
      • Tenure emerged as the most influential predictor of churn, followed by MonthlyCharges.
      • The AUC score of 0.85 validates the model’s strong ability to distinguish between classes, with performance close to excellent.
    • The ROC curve, nearing the top-left corner, highlights the model’s high sensitivity at optimal thresholds.

Decision Tree

  • The Decision Tree model was tested with the same training and testing datasets. The logistic_recipe preprocessing step was reused to maintain consistency.
  • Key Predictor:
    • The model identified Contract_Month.to.month as the most significant risk factor for churn, emphasizing the flexibility of short-term contracts.
  • Performance:
    • The ROC AUC score was slightly lower than Logistic Regression.
    • Insights into model behavior:
      • While Logistic Regression highlighted the importance of Tenure, the Decision Tree focused on splits like contract type.
      • Decision Trees excel in identifying immediate patterns but may lack the generalization capability of Logistic Regression.
  • Comparison:
    • Logistic Regression outperformed the Decision Tree, particularly in distinguishing between classes.

Random Forest

  • A Random Forest model was trained, with hyperparameter tuning applied to optimize performance.
  • Performance:
    • The model achieved an ROC AUC score of 0.84, higher than the Decision Tree but slightly lower than Logistic Regression.
    • Confusion Matrix:
      • The model performed better at predicting the Current class, with a high number of true positives and fewer false negatives.
      • Its performance in predicting the Left class was weaker, with more false negatives observed.

Comparative Insights

  • Logistic Regression emerged as the top-performing model, achieving the highest ROC AUC score and demonstrating superior sensitivity and generalization.
  • Decision Tree offered valuable insights into splits like Contract_Month.to.month but fell short in overall accuracy.
  • Random Forest provided robust predictions for the Current class but struggled with Left, reflecting its focus on overall prediction stability rather than class-specific performance.

Conclusion

This analysis underscores the importance of understanding customer behavior through demographic and service-related lenses. Pricing adjustments, tailored service plans, and a focus on enhancing customer experience can significantly improve retention rates. By addressing the specific needs of diverse customer groups, the company can build stronger relationships and reduce churn effectively.

LS0tCnRpdGxlOiBGaW5hbCBQcm9qZWN0IC0gQ2hhdSBUcmluaCwgRWlybHlzIFZvLCBTYW5keSBDaHVuIERvbWluZ28Kb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKICBodG1sX2RvY3VtZW50OgogICAgdGhlbWU6IGNlcnVsZWFuCiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogMgotLS0KCiMgR3JvdXAgNCAtIEZpbmFsIFByb2plY3Qgey50YWJzZXR9CgojIyBJbnRyb2R1Y3Rpb24KCkFzIFJlZ29yayBlbnRlcnMgdGhlIHRlbGVjb21tdW5pY2F0aW9ucyBtYXJrZXQsIHVuZGVyc3RhbmRpbmcgY3VzdG9tZXIgYmVoYXZpb3IgaXMgZXNzZW50aWFsIGZvciBsb25nLXRlcm0gc3VjY2Vzcy4gSW4gdGhpcyBhbmFseXNpcywgd2UgZm9jdXMgb24gY3VzdG9tZXIgcmV0ZW50aW9uLCB1c2luZyBkYXRhIGZyb20gdGhlIGBjdXN0b21lcl9yZXRlbnRpb24uY3N2YCBmaWxlIHRvIGJ1aWxkIGEgcHJlZGljdGl2ZSBtb2RlbC4gVGhlIGdvYWwgaXMgdG8gYWNjdXJhdGVseSBmb3JlY2FzdCB3aGV0aGVyIGEgY3VzdG9tZXIgd2lsbCBsZWF2ZSBpbiB0aGUgZnV0dXJlLCBlbmFibGluZyBSZWdvcmsgdG8gdGFrZSBwcm9hY3RpdmUgc3RlcHMgdG8gcmV0YWluIHZhbHVhYmxlIGN1c3RvbWVycy4KCkN1c3RvbWVyIHJldGVudGlvbiBpcyBjcnVjaWFsIGluIHRoZSB0ZWxlY29tbXVuaWNhdGlvbnMgaW5kdXN0cnksIGFzIHRoZSBjb3N0IG9mIGFjcXVpcmluZyBuZXcgY3VzdG9tZXJzIG9mdGVuIGV4Y2VlZHMgdGhlIGNvc3Qgb2Yga2VlcGluZyBleGlzdGluZyBvbmVzLiBCeSBkZXZlbG9waW5nIGEgbW9kZWwgdGhhdCBwcmVkaWN0cyBjdXN0b21lciBjaHVybiwgUmVnb3JrIGNhbiB0YWtlIHRhcmdldGVkIGFjdGlvbnPigJRzdWNoIGFzIG9mZmVyaW5nIHByb21vdGlvbnMgb3IgaW5jZW50aXZlc+KAlGJhc2VkIG9uIHRoZXNlIHByZWRpY3Rpb25zLiBGb3IgZXhhbXBsZSwgbWFya2V0aW5nIHRlYW1zIGNhbiB1c2UgdGhlIG1vZGVsIHRvIHRhaWxvciBjdXN0b21lciByZXRlbnRpb24gY2FtcGFpZ25zLCB3aGlsZSB0aGUgZmluYW5jZSB0ZWFtIGNhbiBiZXR0ZXIgZm9yZWNhc3QgY29tcGFueSByZXZlbnVlIGFuZCByaXNrLgoKVGhpcyByZXBvcnQgZGV0YWlscyB0aGUgZGF0YSBhbmFseXNpcyBwcm9jZXNzLCBpbmNsdWRpbmcgbmVjZXNzYXJ5IHBhY2thZ2VzIGFuZCBkYXRhIHByZXBhcmF0aW9uLCBmb2xsb3dlZCBieSBhbiBleHBsb3JhdGlvbiBvZiB0cmVuZHMgYW5kIHJlbGF0aW9uc2hpcHMgYmV0d2VlbiBwcmVkaWN0b3JzIGFuZCB0aGUgcmVzcG9uc2UgdmFyaWFibGUuIFRoZSBmb2N1cyBvZiB0aGUgYW5hbHlzaXMgaXMgb24gYnVpbGRpbmcgYW5kIGV2YWx1YXRpbmcgdmFyaW91cyBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscywgaW5jbHVkaW5nIGxvZ2lzdGljIHJlZ3Jlc3Npb24sIHJhbmRvbSBmb3Jlc3QsIGFuZCBkZWNpc2lvbiB0cmVlcywgdG8gaWRlbnRpZnkgdGhlIGJlc3QgYXBwcm9hY2ggZm9yIHByZWRpY3RpbmcgY3VzdG9tZXIgY2h1cm4uCgpUaGUgcmVwb3J0IHByb3ZpZGVzIGluc2lnaHRzIHRoYXQgc3VwcG9ydCBtb2RlbCBzZWxlY3Rpb24gYW5kIG9mZmVycyByZWNvbW1lbmRhdGlvbnMgZm9yIGhvdyBSZWdvcmsgY2FuIGxldmVyYWdlIHRoZXNlIGZpbmRpbmdzIHRvIGVuaGFuY2UgY3VzdG9tZXIgcmV0ZW50aW9uIHN0cmF0ZWdpZXMuCgojIyBQYWNrYWdlcyBSZXF1aXJlZAoKYGBge3IsIHJlc3VsdHM9J2hpZGUnfQpzZXR3ZCgiL1VzZXJzL2V6aXNoci9Eb2N1bWVudHMvQ0lOQ1kvRmFsbCAyMDI0L0JBTkEgNDA4MC9GaW5hbCBQcm9qZWN0IikgIyBMb2NhbCB3b3JraW5nIGRpcmVjdG9yeQpsaWJyYXJ5KHRpZHl2ZXJzZSkgICAgICAgICAgICAgIyBEYXRhIG1hbmlwdWxhdGlvbiwgdmlzdWFsaXphdGlvbiwgcmVhZGluZyBkYXRhIGZpbGVzCmxpYnJhcnkodGlkeW1vZGVscykgICAgICAgICAgICAjIE1vZGVscyBhbmQgcmVjaXBlcyB1c2UgcHVycG9zZQpsaWJyYXJ5KHJwYXJ0KSAgICAgICAgICAgICAgICAgIyBUcmVlLWJhc2VkIG1vZGVscyB1c2UgcHVycG9zZQpsaWJyYXJ5KGJhZ3VldHRlKSAgICAgICAgICAgICAgIyBUb29scyBmb3IgYmFnZ2luZyBtb2RlbHMKbGlicmFyeShwZHApICAgICAgICAgICAgICAgICAgICMgVXNlIGZvciB1bmRlcnN0YW5kaW5nIG1vZGVsIGJlaGF2aW9yCmxpYnJhcnkodmlwKSAgICAgICAgICAgICAgICAgICAjIEZlYXR1cmUgaW1wb3J0YW5jZQpsaWJyYXJ5KGNvcnJwbG90KSAgICAgICAgICAgICAgIyBDb3JyZWxhdGlvbiBtYXRyaWNlcyB2aXN1YWxpemF0aW9uCmxpYnJhcnkocGF0Y2h3b3JrKSAgICAgICAgICAgICAjIFNpZGUtYnktc2lkZSBvciBtdWx0aXBhbmVsIHBsb3RzIHB1cnBvc2UKbGlicmFyeShncmlkRXh0cmEpICAgICAgICAgICAgICMgQXJyYW5naW5nIG11bHRpcGxlIHBsb3RzIGluIGEgZ3JpZApsaWJyYXJ5KGtlcm5sYWIpICAgICAgICAgICAgICAgIyBLZXJuZWwtYmFzZWQgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zCmBgYCAgIAoKIyMgRGF0YSBQcmVwYXJhdGlvbgoKKioxLiBSZWFkIGRhdGEqKgoKRmlyc3QsIHdlIGxvYWQgdGhlIGRhdGEgZnJvbSBwcm92aWRlZCBjc3YgZmlsZSBhbmQgbG9vayBhdCB0aGUgYE5BYCBjb3VudCBhY3Jvc3MgZXZlcnkgY29sdW1uOgoKYGBge3IsIHJlc3VsdHM9J2hpZGUnLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpkYXRhIDwtIHJlYWRfY3N2KCdjdXN0b21lcl9yZXRlbnRpb24uY3N2JykKZGF0YSRTZW5pb3JDaXRpemVuIDwtIGFzLmNoYXJhY3RlcihkYXRhJFNlbmlvckNpdGl6ZW4pCmBgYAoKYGBge3J9CmNvbFN1bXMoaXMubmEoZGF0YSkpICMgMTEgTkFzIGluIFRvdGFsQ2hhcmdlcwpgYGAKVGhlIGBUb3RhbENoYXJnZXNgIGNvbHVtbiBjdXJyZW50bHkgY29udGFpbnMgMTEgbWlzc2luZyB2YWx1ZXMuIFdoZW4gaW1wbGVtZW50aW5nIG1vZGVscywgd2UgbWF5IGNvbnNpZGVyIHJlbW92aW5nIHRoaXMgY29sdW1uIGR1ZSB0byBpdHMgcG90ZW50aWFsIGhpZ2ggbXVsdGljb2xsaW5lYXJpdHkgd2l0aCB0aGUgYE1vbnRobHlDaGFyZ2VzYCBjb2x1bW4uCgoqKjIuIEFkZCBDaHVybiBUeXBlKioKCldlIGNhdGVnb3JpemVkIGN1c3RvbWVycyB3aXRoIGBTdGF0dXNgID0gYCJMZWZ0ImAgYmFzZWQgb24gdGhlaXIgY29udHJhY3QgdHlwZXMgaW50byB0aHJlZSBsZXZlbHM6IFN0aWxsIEFjdGl2ZSAoY3VzdG9tZXJzIHdpdGggYFN0YXR1c2AgPSBgIkN1cnJlbnQiYCksIENvbnRyYWN0IEZ1bGZpbGxlZCAoY3VzdG9tZXJzIHdobyBsZWZ0IGFmdGVyIHRoZWlyIGNvbnRyYWN0cyBleHBpcmVkKSwgYW5kIEVhcmx5IFRlcm1pbmF0aW9uIChjdXN0b21lcnMgd2hvIGxlZnQgYmVmb3JlIHRoZWlyIGNvbnRyYWN0cyBlbmRlZCkuCgpgYGB7cn0KIyBDb252ZXJ0IENvbnRyYWN0IEludG8gTW9udGhzIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCnNhbXBsZV9kZiA8LSBkYXRhCgpzYW1wbGVfZGYgPC0gc2FtcGxlX2RmICU+JSBtdXRhdGUoCiAgICBjb250cmFjdF9tb250aHMgPSBjYXNlX3doZW4gKAogICAgICBDb250cmFjdCA9PSAnTW9udGgtdG8tbW9udGgnIH4gVGVudXJlLAogICAgICBDb250cmFjdCA9PSAnT25lIHllYXInIH4gMTIsCiAgICAgIENvbnRyYWN0ID09ICdUd28geWVhcicgfiAyNCwKICAgICAgVFJVRSB+IE5BX3JlYWxfICkKKQoKCnNhbXBsZV9kZiA8LSBzYW1wbGVfZGYgJT4lIG11dGF0ZSgKICBjb250cmFjdF9wZXJpb2QgPSBjYXNlX3doZW4gKAogICAgKFRlbnVyZSA8PSBjb250cmFjdF9tb250aHMpICYgKENvbnRyYWN0ICE9ICdNb250aC10by1tb250aCcpIH4gMSwKICAgIChUZW51cmUgPiBjb250cmFjdF9tb250aHMpICYgKENvbnRyYWN0ICE9ICdNb250aC10by1tb250aCcpICYgKFRlbnVyZSUlY29udHJhY3RfbW9udGhzID09IDApIH4gKFRlbnVyZSAvIGNvbnRyYWN0X21vbnRocyksCiAgICAoVGVudXJlID4gY29udHJhY3RfbW9udGhzKSAmIChDb250cmFjdCAhPSAnTW9udGgtdG8tbW9udGgnKSAmIChUZW51cmUlJWNvbnRyYWN0X21vbnRocyAhPSAwKSB+IGZsb29yKFRlbnVyZSAvIGNvbnRyYWN0X21vbnRocykgKyAxLAogICAgVFJVRSB+IFRlbnVyZQogICkKKQoKc2FtcGxlX2RmIDwtIHNhbXBsZV9kZiAlPiUgbXV0YXRlKAogIHRvdGFsX3RpbWUgPSBjYXNlX3doZW4oCiAgICAoQ29udHJhY3QgPT0gJ01vbnRoLXRvLW1vbnRoJykgfiBUZW51cmUsCiAgICAoQ29udHJhY3QgIT0gJ01vbnRoLXRvLW1vbnRoJykgfiAoY29udHJhY3RfbW9udGhzICogY29udHJhY3RfcGVyaW9kKQogICkKKQoKCnNhbXBsZV9kZiA8LSBzYW1wbGVfZGYgJT4lIG11dGF0ZSgKICBjaHVybl9jYXNlID0gY2FzZV93aGVuICgKICAgIChTdGF0dXMgPT0gJ0N1cnJlbnQnKSB+ICdTdGlsbCBBY3RpdmUnLAogICAgKFN0YXR1cyA9PSAnTGVmdCcpICYgKHRvdGFsX3RpbWUgPT0gVGVudXJlKSB+ICdDb250cmFjdCBmdWxmaWxsZWQnLAogICAgKFN0YXR1cyA9PSAnTGVmdCcpICYgKHRvdGFsX3RpbWUgPiBUZW51cmUpICYgKENvbnRyYWN0ICE9ICdNb250aC10by1tb250aCcpIH4gJ0Vhcmx5IFRlcm1pbmF0aW9uJywKICAgIFRSVUUgfiAnQ29udHJhY3QgZnVsZmlsbGVkJwogICkKKQoKZGF0YSA8LSBzYW1wbGVfZGYgJT4lIHNlbGVjdCghdG90YWxfdGltZSkKZGF0YSRjb250cmFjdF9wZXJpb2QgPC0gYXMuY2hhcmFjdGVyKGRhdGEkY29udHJhY3RfcGVyaW9kKQoKY2hhcl9jb2xzIDwtIGNvbG5hbWVzKGRhdGEpW3NhcHBseShkYXRhLCBpcy5jaGFyYWN0ZXIpXQpjaGFyX2NvbHMgPC0gY2hhcl9jb2xzWyhjaGFyX2NvbHMhPSdTdGF0dXMnKSAmIChjaGFyX2NvbHMhPSdjb250cmFjdF9wZXJpb2QnKV0KYGBgCgoqKjMuIEZ1bmN0aW9uOiByZWFkIHVuaXF1ZSB2YWx1ZXMgb2YgZWFjaCBjb2x1bW4qKgoKVGhlIGZ1bmN0aW9uIHRha2VzIGEgZGF0YXNldCBhbmQgYSBsaXN0IG9mIGNoYXJhY3RlciBjb2x1bW5zIGFzIGlucHV0LiBJdCBwcmludHMgdGhlIHVuaXF1ZSB2YWx1ZXMgb2YgZWFjaCBzcGVjaWZpZWQgY29sdW1uLCBkaXNwbGF5aW5nIHRoZW0gbGluZSBieSBsaW5lLgoKYGBge3J9CmNoZWNrX3VuaXF1ZSA8LSBmdW5jdGlvbihkYXRhc2V0LCBjaGFyX2NvbHMpIHsKICBmb3IgKGNvbCBpbiBjaGFyX2NvbHMpIHsKICAgIGNhdCgnXG5VbmlxdWUgdmFsdWVzIGZvcicsIGNvbCwnOlxuJykKICAgIHByaW50KHVuaXF1ZShkYXRhc2V0W1tjb2xdXSkpCiAgfQp9CmBgYAoKIyMgRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpcyB7LnRhYnNldH0KCiMjIyBBbmFseXNpcyBSYXcgRGF0YXNldAoKKioxLiBJbml0aWFsIExvb2sgb2YgTGVmdCBTdGF0dXMqKgoKSW4gdGhpcyBhbmFseXNpcywgd2UgYmVnaW4gYnkgZXhhbWluaW5nIHRoZSBjb3VudHMgb2YgYFN0YXR1c2AgYWNyb3NzIHZhcmlvdXMgc2VydmljZXMgYW5kIGZhY3RvcnMgaW4gdGhlIGRhdGFzZXQuCgpgYGB7ciwgZmlnLndpZHRoPTI1LCBmaWcuaGVpZ2h0PTIwfQpwbG90X2xpc3QgPC0gbGlzdCgpCmZvciAoY29sIGluIGNoYXJfY29scykgewogIHBsb3QgPC0gZ2dwbG90KGRhdGEgPSBkYXRhLCBhZXMoeCA9ICEhc3ltKGNvbCksIGZpbGwgPSBTdGF0dXMpKSArCiAgICBnZW9tX2JhcigpICsKICAgIHlsaW0oMCwgNzAwMCkgKwogICAgZ2VvbV90ZXh0KHN0YXQgPSAnY291bnQnLCBhZXMobGFiZWwgPSBhZnRlcl9zdGF0KGNvdW50KSwgdmp1c3QgPSAtMC41LCBzaXplID0gMC4xKSkgKwogICAgbGFicyh0aXRsZSA9IHBhc3RlMCgnQ291bnQgb2YgQ2h1cm4gYnkgJywgY29sKSwgeT0nQ291bnQnKQogIAogIHBsb3RfbGlzdFtbY29sXV0gPC0gcGxvdAp9CgpncmlkX3Bsb3RzIDwtIGdyaWQuYXJyYW5nZShncm9icyA9IHBsb3RfbGlzdCwgbnJvdz02LCBuY29sPTMpCmBgYAoKVGhlIGBQaG9uZSBTZXJ2aWNlYCBjb2x1bW4gaGFzIHRoZSBoaWdoZXN0IGNvdW50IG9mIGBTdGF0dXNgID0gYCJMZWZ0ImAsIHRvdGFsaW5nIDEsNjg3LiBUbyB1bmRlcnN0YW5kIHRoaXMgdHJlbmQsIHdlIHdpbGwgYW5hbHl6ZSBjdXN0b21lcnMgd2l0aCBgUGhvbmUgU2VydmljZWAgYW5kIGBJbnRlcm5ldCBTZXJ2aWNlYCBzZXBhcmF0ZWx5LCBhbG9uZyB3aXRoIHRoZWlyIGFkZC1vbnMsIHRvIGlkZW50aWZ5IHBvdGVudGlhbCByZWFzb25zIGZvciB0aGUgaGlnaCBjb3VudHMuIEFkZGl0aW9uYWxseSwgd2Ugd2lsbCBpbnZlc3RpZ2F0ZSB3aGV0aGVyIGN1c3RvbWVycyB3aXRoIGJvdGggc2VydmljZXMgYXJlIG1vcmUgbGlrZWx5IHRvIGxlYXZlIGFuZCBleHBsb3JlIHRoZSBmYWN0b3JzIGNvbnRyaWJ1dGluZyB0byB0aGVpciBkZXBhcnR1cmUuCgoqKjIuIERlbW9ncmFwaGljIExvb2sqKgoKU2Vjb25kLCB3ZSBoYXZlIGEgbG9vayBhdCBkZW1vZ3JhcGhpYzoKCmBgYHtyLCBmaWcuaGVpZ2h0PTEwfQpkZW1vZ3JhcGhpY19jb2xzIDwtIGMoJ0dlbmRlcicsICdTZW5pb3JDaXRpemVuJywgJ1BhcnRuZXInLCAnRGVwZW5kZW50cycpCmRlbW8gPC0gZGF0YQpkZW1vIDwtIGRlbW8gJT4lIG11dGF0ZSgKICBTZW5pb3JDaXRpemVuID0gY2FzZV93aGVuKAogICAgU2VuaW9yQ2l0aXplbiA9PSAwIH4gJ05vJywKICAgIFNlbmlvckNpdGl6ZW4gPT0gMSB+ICdZZXMnCiAgKQopCgojIyBDaGFydHMgZGVtb2dyYXBoaWNzCmZvciAoY29sIGluIGRlbW9ncmFwaGljX2NvbHMpIHsKICBwbG90IDwtIGdncGxvdChkYXRhID0gZGVtbywgYWVzKHggPSAhIXN5bShjb2wpLCBmaWxsID0gU3RhdHVzKSkgKwogICAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZG9kZ2UiKSArCiAgICBsYWJzKHRpdGxlID0gcGFzdGUoIkNvdW50IG9mIFN0YXR1cyBieSIsIGNvbCksIHggPSBjb2wsIHkgPSAiQ291bnQiKSArCiAgICB0aGVtZV9taW5pbWFsKCkgKwogICAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJTZXQyIikgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpICsKICAgIGdlb21fdGV4dCgKICAgICAgc3RhdCA9ICdjb3VudCcsCiAgICAgIGFlcyhsYWJlbCA9IGFmdGVyX3N0YXQoY291bnQpKSwKICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSh3aWR0aCA9IDAuOSksCiAgICAgIHZqdXN0ID0gLTAuNSwKICAgICAgc2l6ZSA9IDMpCiAgCiAgYXNzaWduKHBhc3RlMCgicGxvdF8iLCBjb2wpLCBwbG90KQp9CgpwbG90X0RlcGVuZGVudHMgKyBwbG90X0dlbmRlciArIHBsb3RfUGFydG5lciArIHBsb3RfU2VuaW9yQ2l0aXplbgpgYGAKT3ZlcmFsbCwgdGhlcmUgd2VyZSBtb3JlIGBDdXJyZW50YCBjdXN0b21lcnMgdGhhbiB0aG9zZSB3aG8gbGVmdC4gSG93ZXZlciwgY3VzdG9tZXJzIHdpdGhvdXQgcGFydG5lcnMgb3IgZGVwZW5kZW50cyB0ZW5kZWQgdG8gbGVhdmUgYXQgaGlnaGVyIHJhdGVzLCB3aGljaCBjb3VsZCBiZSBhdHRyaWJ1dGVkIHRvIHRoZSBjaGFyZ2VzIGZvciBpbmRpdmlkdWFscyBsaXZpbmcgYWxvbmUuIFdlIHdpbGwgY29uZHVjdCBmdXJ0aGVyIGFuYWx5c2lzIHRvIGV4cGxvcmUgdGhpcyBhc3BlY3QuIFJlZ2FyZGluZyBnZW5kZXIsIHRoZXJlIHdhcyBubyBzaWduaWZpY2FudCBkaWZmZXJlbmNlIGluIHRoZSBkZXBhcnR1cmUgcmF0ZXMgYmV0d2VlbiBtYWxlcyBhbmQgZmVtYWxlcy4KCioqMy4gQ2h1cm4gQ2F0ZWdvcnkgTG9vayoqCgpXaGVuIHByZXBhcmluZyB0aGUgZGF0YXNldCwgd2UgaGF2ZSBjbGFzc2lmaWVkIHRoZSBjaHVybiBjYXRlZ29yeSBpbnRvIHRocmVlOiBTdGlsbCBBY3RpdmUsIENvbnRyYWN0IGZ1bGZpbGxlZCwgYW5kIEVhcmx5IFRlcm1pbmF0aW9uLgoKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0xMH0KZ2dwbG90KGRhdGEsIGFlcyh4ID0gQ29udHJhY3QpKSArCiAgICBnZW9tX2JhcihmaWxsID0gInNreWJsdWUiLCBjb2xvciA9ICJibGFjayIpICsKICAgIGdlb21fdGV4dChzdGF0ID0gImNvdW50IiwgYWVzKGxhYmVsID0gYWZ0ZXJfc3RhdChjb3VudCkpLCB2anVzdCA9IC0wLjMsIGNvbG9yID0gImJsYWNrIiwgc2l6ZSA9IDMuNSkgKwogICAgZmFjZXRfZ3JpZChjaHVybl9jYXNlIH4gLikgKyAKICAgIGxhYnModGl0bGUgPSAiQ2h1cm4gQ2FzZXMgQ291bnQgYnkgQ29udHJhY3QgVHlwZSIsIHggPSAiQ29udHJhY3QgVHlwZSIsIHkgPSAiQ291bnQiKQpgYGAKQXMgc2hvd24gYWJvdmUsIHRoZXJlIGFyZSByZWxhdGl2ZWx5IGZldyBjdXN0b21lcnMgbGVhdmluZyBkdWUgdG8gYEVhcmx5IFRlcm1pbmF0aW9uYCwgc3VnZ2VzdGluZyB0aGF0IG1vc3QgY3VzdG9tZXJzIHRlbmQgdG8gdXNlIHRoZSBzZXJ2aWNlIHVudGlsIHRoZSBlbmQgb2YgdGhlaXIgY29udHJhY3QuIEhvd2V2ZXIsIHRoZSBgTW9udGgtdG8tbW9udGhgIGNvbnRyYWN0IHR5cGUgaGFzIHRoZSBoaWdoZXN0IGNodXJuIHJhdGUsIHdpdGggYXBwcm94aW1hdGVseSBoYWxmIG9mIHRoZSBjdXN0b21lcnMgaW4gdGhpcyBjYXRlZ29yeSBsZWF2aW5nLiBUaGlzIGNvdWxkIGluZGljYXRlIHRoYXQgdGhlc2UgY3VzdG9tZXJzIG1heSBub3QgaGF2ZSByZWNlaXZlZCB0aGUgZXhwZWN0ZWQgbGV2ZWwgb2Ygc2VydmljZSBvciB0aGVpciBzcGVjaWZpYyBuZWVkcyBhbmQgY29uY2VybnMgd2VyZSBub3QgYWRkcmVzc2VkLiBXZSB3aWxsIGNvbmR1Y3QgZnVydGhlciBhbmFseXNpcyBvbiB0aGlzIGluIHRoZSAqKipNb250aGx5IENoYXJnZXMgYW5kIENodXJuIFR5cGUqKiogc2VjdGlvbi4KCmBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9NX0KZ2dwbG90KGRhdGEsIGFlcyhQYXltZW50TWV0aG9kLCBmaWxsID0gY2h1cm5fY2FzZSkpICsgCiAgZ2VvbV9iYXIoKSArCiAgZmFjZXRfd3JhcCggfkNvbnRyYWN0ICkgKyAKICBjb29yZF9mbGlwKCkgKwogIGxhYnModGl0bGUgPSAiQ291bnQgb2YgUGF5bWVudCBNZXRob2RzIEFjcm9zcyBDb250cmFjdCBUeXBlcyBhbmQgQ2h1cm4gQ2FzZXMiLAogICAgICAgeCA9ICJQYXltZW50IE1ldGhvZCIsCiAgICAgICB5ID0gIkNvdW50IiwKICAgICAgIGZpbGwgPSAiQ2h1cm4gQ2FzZSIKICAgICAgICkKYGBgCgpUaGUgYW5hbHlzaXMgcmV2ZWFscyB0aGF0IHRoZSBwYXltZW50IG1ldGhvZHMgYENyZWRpdCBCYW5rYCBhbmQgYEJhbmsgVHJhbnNmZXJgIG1haW50YWluIGEgY29uc2lzdGVudCBjb3VudCBhY3Jvc3MgYWxsIGNvbnRyYWN0IHR5cGVzLiBUaGlzIHVuaWZvcm0gZGlzdHJpYnV0aW9uIHN1Z2dlc3RzIHRoYXQgdGhlc2UgcGF5bWVudCBtZXRob2RzIGFyZSBlcXVhbGx5IHByZWZlcnJlZCBieSBjdXN0b21lcnMgcmVnYXJkbGVzcyBvZiB0aGVpciBjaG9zZW4gY29udHJhY3QgdHlwZS4gVGhpcyBjb25zaXN0ZW5jeSBoaWdobGlnaHRzIHRoZSBicm9hZCBhcHBlYWwgYW5kIHJlbGlhYmlsaXR5IG9mIHRoZXNlIG9wdGlvbnMsIG1ha2luZyB0aGVtIGVzc2VudGlhbCBmb3IgbWVldGluZyBjdXN0b21lciBleHBlY3RhdGlvbnMgYWNyb3NzIHRoZSBib2FyZC4gQWRkaXRpb25hbGx5LCB0aGUgYmFsYW5jZWQgdXNhZ2Ugb2YgdGhlc2UgbWV0aG9kcyBwcm92aWRlcyBvcGVyYXRpb25hbCBiZW5lZml0cyBieSBzaW1wbGlmeWluZyByZXNvdXJjZSBhbGxvY2F0aW9uIGFuZCBwbGFubmluZy4KClRoZSBgTW9udGgtdG8tbW9udGhgIGNvbnRyYWN0IHR5cGUgZGlzcGxheXMgYW4gZXhjZXB0aW9uYWxseSBoaWdoIGNvdW50IGZvciBFbGVjdHJvbmljIENoZWNrIHBheW1lbnRzLiBUaGlzIHRyZW5kIGNvdWxkIHNpZ25pZnkgdGhhdCBjdXN0b21lcnMgd2l0aCBzaG9ydGVyLXRlcm0gY29tbWl0bWVudHMgcHJlZmVyIHRoZSBmbGV4aWJpbGl0eSBhbmQgaW1tZWRpYWN5IHByb3ZpZGVkIGJ5IHRoaXMgbWV0aG9kLgoKIyMjIFBob25lIFNlcnZpY2UgQW5hbHlzaXMKCioqMS4gR2VuZXJhbCBWaWV3KioKCkZpcnN0LCB3ZSBnZXQgdGhlIGRhdGEgb2YgY3VzdG9tZXJzIGhhdmluZyBQaG9uZSBTZXJ2aWNlLCB3aGljaCBtZWFucyBgUGhvbmVTZXJ2aWNlICE9ICJObyJgIGFuZCBoYXZlIGEgYnJlaWYgbG9vayBhdCB1bmlxdWUgdmFsdWVzIGFjcm9zcyBjb2x1bW5zOgoKYGBge3J9CmhhdmluZ19waG9uZV9zZXJ2aWNlIDwtIGRhdGFbZGF0YSRQaG9uZVNlcnZpY2UgIT0gJ05vJywgXQpoYXZpbmdfcGhvbmVfc2VydmljZSA8LSBoYXZpbmdfcGhvbmVfc2VydmljZVssICEoY29sbmFtZXMoaGF2aW5nX3Bob25lX3NlcnZpY2UpICVpbiUgYygnY29udHJhY3RfcGVyaW9kJywgJ2NvbnRyYWN0X21vbnRocycsICdjb250cmFjdF9wZXJpb2QnKSldCgpjaGFyX2NvbHMgPC0gY29sbmFtZXMoaGF2aW5nX3Bob25lX3NlcnZpY2UpW3NhcHBseShoYXZpbmdfcGhvbmVfc2VydmljZSwgaXNfY2hhcmFjdGVyKV0KY2hhcl9jb2xzIDwtIGNoYXJfY29sc1tjaGFyX2NvbHMhPSdQaG9uZVNlcnZpY2UnXQoKY2hlY2tfdW5pcXVlKGhhdmluZ19waG9uZV9zZXJ2aWNlLCBjaGFyX2NvbHMpCmBgYAoKYGBge3IsIGZpZy53aWR0aD0yNSwgZmlnLmhlaWdodD0yMH0KcGxvdF9saXN0IDwtIGxpc3QoKQpmb3IgKGNvbCBpbiBjaGFyX2NvbHMpIHsKICBwbG90IDwtIGdncGxvdChkYXRhID0gaGF2aW5nX3Bob25lX3NlcnZpY2UsIGFlcyh4ID0gISFzeW0oY29sKSwgZmlsbCA9IFN0YXR1cykpICsKICAgIGdlb21fYmFyKCkgKwogICAgeWxpbSgwLCA3MDAwKSArCiAgICBnZW9tX3RleHQoc3RhdCA9ICdjb3VudCcsIGFlcyhsYWJlbCA9IGFmdGVyX3N0YXQoY291bnQpLCB2anVzdCA9IC0wLjUsIHNpemUgPSAwLjEpKSArCiAgICBsYWJzKHRpdGxlID0gcGFzdGUwKCdDb3VudCBvZiBDaHVybiBieSAnLCBjb2wpLCB5PSdDb3VudCcpCiAgCiAgcGxvdF9saXN0W1tjb2xdXSA8LSBwbG90Cn0KZ3JpZF9wbG90cyA8LSBncmlkLmFycmFuZ2UoZ3JvYnMgPSBwbG90X2xpc3QsIG5yb3c9NSwgbmNvbD00KQpgYGAKQXMgc2VlbiBhYm92ZSwgY3VzdG9tZXJzIHdpdGggYEludGVybmV0U2VydmljZWAgPSBgIkZpYmVyIG9wdGljImAgYW5kIGEgYE1vbnRoLXRvLW1vbnRoYCBjb250cmFjdCB0eXBlIGhhZCB0aGUgaGlnaGVzdCBjaHVybiByYXRlcy4KCioqMi4gQ2hhcmdlcyBhY3Jvc3MgQ29udHJhY3QgVHlwZSBhbmQgSW50ZXJuZXQgU2VydmljZSAqKgoKV2Ugd2lsbCBsb29rIG1vcmUgaW4gdGhlIG1vbnRobHkgY2hhZ3JlcyBhY3Jvc3MgdGhlIENvbnRyYWN0IFR5cGUgYW5kIEludGVybmV0IFNlcnZpY2VzLiAKCmBgYHtyLCBmaWcud2lkdGg9OCxmaWcuaGVpZ2h0PTV9CmdncGxvdChkYXRhID0gaGF2aW5nX3Bob25lX3NlcnZpY2UsIGFlcyh4ID0gQ29udHJhY3QsIHkgPSBNb250aGx5Q2hhcmdlcywgZmlsbCA9IFN0YXR1cykpICsKICBnZW9tX2JveHBsb3QoKSArCiAgbGFicyh0aXRsZSA9ICJNb250aGx5IENoYXJnZXMgSW4gR2VuZXJhbCBieSBDb250cmFjdCBUeXBlIGFuZCBDaHVybiBTdGF0dXMiLAogICAgICAgeCA9ICJDb250cmFjdCBUeXBlIiwKICAgICAgIHkgPSAiTW9udGhseSBDaGFyZ2VzIikKYGBgCkdlbmVyYWxseSwgcmVnYXJkbGVzcyBvZiB0aGUgdHlwZSBvZiBJbnRlcm5ldCBTZXJ2aWNlLCB0aGUgbWVkaWFuIG1vbnRobHkgY2hhcmdlcyBmb3IgY3VzdG9tZXJzIHdobyBoYXZlIGNodXJuZWQgdGVuZCB0byBiZSBoaWdoZXIgdGhhbiB0aG9zZSBmb3IgYWN0aXZlIGN1c3RvbWVycy4gVGhpcyBzdWdnZXN0cyB0aGF0IGhpZ2hlciBjaGFyZ2VzIG1heSBiZSBhIGNvbnRyaWJ1dGluZyBmYWN0b3IgdG8gY3VzdG9tZXIgY2h1cm4sIHBvdGVudGlhbGx5IGluZGljYXRpbmcgYSBwZXJjZXB0aW9uIG9mIGJlaW5nIG92ZXJjaGFyZ2VkLgoKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTV9CmhhdmVfaW50ZXJuZXQgPC0gaGF2aW5nX3Bob25lX3NlcnZpY2VbaGF2aW5nX3Bob25lX3NlcnZpY2UkSW50ZXJuZXRTZXJ2aWNlICE9ICdObycsXQoKZ2dwbG90KGRhdGEgPSBoYXZlX2ludGVybmV0LCBhZXMoeCA9IENvbnRyYWN0LCB5ID0gTW9udGhseUNoYXJnZXMsIGZpbGwgPSBTdGF0dXMpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIGZhY2V0X3dyYXAofiBJbnRlcm5ldFNlcnZpY2UpICsKICBsYWJzKHRpdGxlID0gIk1vbnRobHkgQ2hhcmdlcyBieSBDb250cmFjdCBUeXBlLCBDaHVybiBTdGF0dXMsIGFuZCBJbnRlcm5ldCBTZXJ2aWNlIiwKICAgICAgIHggPSAiQ29udHJhY3QgVHlwZSIsCiAgICAgICB5ID0gIk1vbnRobHkgQ2hhcmdlcyIpCmBgYApXaGVuIGV4YW1pbmluZyBzcGVjaWZpYyBJbnRlcm5ldCBTZXJ2aWNlcyBjb21iaW5lZCB3aXRoIENvbnRyYWN0IFR5cGUgYW1vbmcgY3VzdG9tZXJzIHVzaW5nIFBob25lIFNlcnZpY2UsIGN1c3RvbWVycyB1c2luZyBgRmliZXIgb3B0aWNgIHR5cGljYWxseSBmYWNlIGhpZ2hlciBjaGFyZ2VzIGNvbXBhcmVkIHRvIHRob3NlIHdpdGggYERTTGAuIEhvd2V2ZXIsIHRoZSBtZWRpYW4gY2hhcmdlcyBmb3IgYm90aCBjaHVybmVkIGFuZCBhY3RpdmUgY3VzdG9tZXJzIGFjcm9zcyBDb250cmFjdCBUeXBlcyBhcmUgbm90IHNpZ25pZmljYW50bHkgZGlmZmVyZW50LiBUaGlzIHN1Z2dlc3RzIHRoYXQgY3VzdG9tZXIgY2h1cm4gbWF5IGJlIGluZmx1ZW5jZWQgbW9yZSBieSBkaXNzYXRpc2ZhY3Rpb24gd2l0aCB0aGUgc2VydmljZSBxdWFsaXR5IHJhdGhlciB0aGFuIHByaWNpbmcuCgoqKjMuIENoYXJnZXMgYWNyb3NzIENvbnRyYWN0IFR5cGUsIE11bHRpcGxlIExpbmVzLCBhbmQgSW50ZXJuZXQgU2VydmljZSoqCgpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTV9CiMgQm94cGxvdCBvZiBNb250aGx5IENoYXJnZXMgYnkgQ29udHJhY3QgYW5kIFN0YXR1cyB3aXRoIE5PIEludGVybmV0U2VydmljZQpub19pbnRlcm5ldCA8LSBoYXZpbmdfcGhvbmVfc2VydmljZVtoYXZpbmdfcGhvbmVfc2VydmljZSRJbnRlcm5ldFNlcnZpY2UgPT0gJ05vJyxdCgpnZ3Bsb3QoZGF0YSA9IG5vX2ludGVybmV0LCBhZXMoeCA9IENvbnRyYWN0LCB5ID0gTW9udGhseUNoYXJnZXMsIGZpbGwgPSBTdGF0dXMpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIGZhY2V0X3dyYXAofiBNdWx0aXBsZUxpbmVzLCBsYWJlbGxlciA9IGFzX2xhYmVsbGVyKGMoIlllcyIgPSAiSGFzIE11bHRpcGxlIExpbmVzIiwgIk5vIiA9ICJObyBNdWx0aXBsZSBMaW5lcyIpKSkgKwogIGxhYnModGl0bGUgPSAiTW9udGhseSBDaGFyZ2VzIGJ5IENvbnRyYWN0IFR5cGUsIE11bHRpcGxlIExpbmVzLCBhbmQgQ2h1cm4gU3RhdHVzIC0gTm8gSW50ZXJuZXQgU2VydmljZSIsCiAgICAgICB4ID0gIkNvbnRyYWN0IFR5cGUiLAogICAgICAgeSA9ICJNb250aGx5IENoYXJnZXMiKQpgYGAKQ2h1cm5lZCBjdXN0b21lcnMgaW4gbW9udGgtdG8tbW9udGggY29udHJhY3RzIGNvdWxkIGJlIG1vcmUgcHJpY2Utc2Vuc2l0aXZlLiBUaGV5IG1pZ2h0IGhhdmUgaW5pdGlhbGx5IGNob3NlbiB0aGUgbW9udGgtdG8tbW9udGggZmxleGliaWxpdHksIGF0dHJhY3RlZCBieSBsb3cgc3RhcnRpbmcgcHJpY2VzIG9yIGRpc2NvdW50cywgYnV0IGxlZnQgd2hlbiB0aGV5IGZvdW5kIHRoZSBwcmljaW5nIHVuc3VzdGFpbmFibGUgb3IgZmVsdCB0aGV5IHdlcmUgbm90IGdldHRpbmcgdmFsdWUgZm9yIHRoZWlyIG1vbmV5LgoKYGBge3IsIGZpZy53aWR0aD0xNSwgZmlnLmhlaWdodD0xMH0Kbm9faW50ZXJuZXRfbm9fbXVsdGlwbGVMaW5lcyA8LSBub19pbnRlcm5ldFtub19pbnRlcm5ldCRNdWx0aXBsZUxpbmVzID09ICdObycsXQoKIyBBbnkgU2VuaW9yQ2l0aXplbiB3aXRoIE5PIEludGVybmV0ICYgTk8gTXVsdGlwbGVsaW5lcyAtLS0tLS0tLQpzZW5pb3Jfbm9fbXVsdGlwbGUgPC0gbm9faW50ZXJuZXRfbm9fbXVsdGlwbGVMaW5lc1tub19pbnRlcm5ldF9ub19tdWx0aXBsZUxpbmVzJFNlbmlvckNpdGl6ZW4gPT0gMSxdICAjIFN1YnNldCBmb3Igc2VuaW9yIGNpdGl6ZW5zIHdpdGggbm8gbXVsdGlwbGUgbGluZXMKbm9uX3Nlbmlvcl9ub19tdWx0aXBsZSA8LSBub19pbnRlcm5ldF9ub19tdWx0aXBsZUxpbmVzW25vX2ludGVybmV0X25vX211bHRpcGxlTGluZXMkU2VuaW9yQ2l0aXplbiA9PSAwLF0gICMgU3Vic2V0IGZvciBub24tc2VuaW9yIGNpdGl6ZW5zIHdpdGggbm8gbXVsdGlwbGUgbGluZXMKCiMgQm94cGxvdCBvZiBNb250aGx5IENoYXJnZXMgYnkgU3RhdHVzIGZvciBTZW5pb3IgQ2l0aXplbnMKcGxvdDMgPC0gZ2dwbG90KHNlbmlvcl9ub19tdWx0aXBsZSwgYWVzKHggPSBDb250cmFjdCwgeSA9IE1vbnRobHlDaGFyZ2VzLCBmaWxsID0gU3RhdHVzKSkgKwogIGdlb21fYm94cGxvdCgpICsKICBsYWJzKHRpdGxlID0gIk1vbnRobHkgQ2hhcmdlcyBieSBTdGF0dXMgZm9yIFNlbmlvciBDaXRpemVucyAoTm8gSW50ZXJuZXQgJiBObyBNdWx0aXBsZSBMaW5lcykiLAogICAgICAgeCA9ICJTdGF0dXMiLCB5ID0gIk1vbnRobHkgQ2hhcmdlcyIpCgojIEJveHBsb3Qgb2YgTW9udGhseSBDaGFyZ2VzIGJ5IFN0YXR1cyBmb3IgTm9uLVNlbmlvciBDaXRpemVucwpwbG90NCA8LSBnZ3Bsb3Qobm9uX3Nlbmlvcl9ub19tdWx0aXBsZSwgYWVzKHggPSBDb250cmFjdCwgeSA9IE1vbnRobHlDaGFyZ2VzLCBmaWxsID0gU3RhdHVzKSkgKwogIGdlb21fYm94cGxvdCgpICsKICBsYWJzKHRpdGxlID0gIk1vbnRobHkgQ2hhcmdlcyBieSBTdGF0dXMgZm9yIE5vbi1TZW5pb3IgQ2l0aXplbnMgKE5vIEludGVybmV0ICYgTm8gTXVsdGlwbGUgTGluZXMpIiwKICAgICAgIHggPSAiU3RhdHVzIiwgeSA9ICJNb250aGx5IENoYXJnZXMiKQoKcGxvdDMgKyBwbG90NApgYGAKQW1vbmcgc2VuaW9yIGNpdGl6ZW5zLCB0aGVyZSBpcyBubyBjaHVybiBmb3IgY3VzdG9tZXJzIHdpdGggb25lLXllYXIgYW5kIHR3by15ZWFyIGNvbnRyYWN0IHR5cGVzIHdoZW4gdGhleSBkbyBub3QgdXNlIGludGVybmV0IHNlcnZpY2Ugb3IgbXVsdGlwbGUgbGluZXMgZm9yIHRoZWlyIHBob25lIHNlcnZpY2UuIFRoaXMgc3VnZ2VzdHMgdGhhdCBzZW5pb3IgY2l0aXplbnMgd2hvIGNob29zZSB0aGVzZSBjb250cmFjdHMgYXJlIGxpa2VseSBtb3JlIHN0YWJsZSBpbiB0aGVpciBkZWNpc2lvbnMsIHBvdGVudGlhbGx5IGR1ZSB0byB0aGUgcHJlZGljdGFiaWxpdHkgYW5kIHN0YWJpbGl0eSBvZiBsb25nLXRlcm0gY29tbWl0bWVudHMuIEFkZGl0aW9uYWxseSwgdGhlIGZhY3QgdGhhdCB0aGV5IGFyZSBub3QgdXNpbmcgaW50ZXJuZXQgc2VydmljZSBvciBtdWx0aXBsZSBsaW5lcyBjb3VsZCBpbmRpY2F0ZSB0aGF0IHRoZXkgcHJlZmVyIHNpbXBsZXIgc2VydmljZSBwbGFucyB0aGF0IG1lZXQgdGhlaXIgYmFzaWMgbmVlZHMsIHBvc3NpYmx5IGF0IGxvd2VyIGNvc3RzLCB3aGljaCBtYXkgaW5jcmVhc2UgdGhlaXIgc2F0aXNmYWN0aW9uIGFuZCBsb3lhbHR5LgoKSW4gY29udHJhc3QsIG5vbi1zZW5pb3IgY2l0aXplbnMgYXJlIGV4aGliaXRpbmcgY2h1cm4gaW4gYWxsIGNvbnRyYWN0IHR5cGVzIChtb250aC10by1tb250aCwgb25lLXllYXIsIGFuZCB0d28teWVhciksIGluZGljYXRpbmcgdGhhdCB0aGV5IGFyZSBsZXNzIGNvbW1pdHRlZCBvciBtb3JlIHByaWNlLXNlbnNpdGl2ZS4gVGhleSBtaWdodCBiZSBtb3JlIGxpa2VseSB0byBzd2l0Y2ggcHJvdmlkZXJzLCBwb3NzaWJseSBkdWUgdG8gY29tcGV0aXRpdmUgb2ZmZXJzIG9yIGRpc3NhdGlzZmFjdGlvbiB3aXRoIHRoZSBzZXJ2aWNlLiBUaGUgZmFjdCB0aGF0IGNodXJuIG9jY3VycyBhY3Jvc3MgYWxsIGNvbnRyYWN0IHR5cGVzIGZvciBub24tc2VuaW9yIGNpdGl6ZW5zIHN1Z2dlc3RzIHRoYXQgZmFjdG9ycyBsaWtlIHByaWNpbmcsIHNlcnZpY2UgZmVhdHVyZXMsIG9yIGN1c3RvbWVyIGV4cGVyaWVuY2UgYXJlIHNpZ25pZmljYW50IGRyaXZlcnMgb2YgY2h1cm4sIGV2ZW4gd2hlbiB0aGUgY2hhcmdlcyBhY3Jvc3MgdGhlc2UgY3VzdG9tZXJzIGFwcGVhciBzaW1pbGFyLgoKQWx0aG91Z2ggdGhlIGNoYXJnZXMgZm9yIG5vbi1zZW5pb3IgY2l0aXplbnMgYXJlIHNpbWlsYXIgYWNyb3NzIGNvbnRyYWN0IHR5cGVzLCB0aGUgY2h1cm4gcmF0ZXMgZGlmZmVyIHNpZ25pZmljYW50bHkuIFRoaXMgY291bGQgaW5kaWNhdGUgdGhhdCB0aGUgcmVhc29uIGZvciBjaHVybiBtYXkgbm90IGJlIGRpcmVjdGx5IHRpZWQgdG8gcHJpY2UgYnV0IHBvc3NpYmx5IHRvIGZhY3RvcnMgbGlrZSBzZXJ2aWNlIHNhdGlzZmFjdGlvbiwgcGVyY2VpdmVkIHZhbHVlLCBvciBmbGV4aWJpbGl0eS4KCioqNC4gVGVudXJlIERpc3RyaWJ1dGlvbioqCgpOb3cgd2UgbG9vayBhdCB0aGUgYFRlbnVyZWAgZGlzdHJpYnV0aW9uIGFjcm9zcyBjb250cmFjdCB0eXBlcyBhbmQgY2h1cm4gc3RhdHVzOgoKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTV9CiMgUGxvdCB0ZW51cmUgdnMgY2h1cm4gZm9yIGN1c3RvbWVycyB3aXRoIHBob25lIHNlcnZpY2UgKGluaXRpYWwgdmlldykgLS0tLS0tLS0tLS0KZ2dwbG90KGRhdGEgPSBoYXZpbmdfcGhvbmVfc2VydmljZSwgYWVzKHggPSBUZW51cmUsIGZpbGwgPSBTdGF0dXMpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxLCBhbHBoYSA9IDAuNikgKwogIGZhY2V0X3dyYXAofkNvbnRyYWN0KSArCiAgbGFicyh0aXRsZSA9ICJUZW51cmUgRGlzdHJpYnV0aW9uIGJ5IENvbnRyYWN0IFR5cGUgYW5kIENodXJuIFN0YXR1cyIsCiAgICAgICB4ID0gIlRlbnVyZSAoTW9udGhzKSIsCiAgICAgICB5ID0gIkNvdW50IikKYGBgClRoZSB0ZW51cmUgZGlzdHJpYnV0aW9uIGJ5IGNvbnRyYWN0IHR5cGUgYW5kIGNodXJuIHN0YXR1cyBzaG93cyB0aGF0IG1vbnRoLXRvLW1vbnRoIGN1c3RvbWVycyB0ZW5kIHRvIGNodXJuIGF0IGxvd2VyIHRlbnVyZXMsIG1vc3RseSBiZXR3ZWVuIDAtMjAgbW9udGhzLiBJbiBjb250cmFzdCwgb25lLXllYXIgYW5kIHR3by15ZWFyIGNvbnRyYWN0IGN1c3RvbWVycyBhcmUgbW9yZSBsaWtlbHkgdG8gc3RheSBsb25nZXIsIHdpdGggbW9zdCBvZiB0aGUgY2h1cm4gb2NjdXJyaW5nIHRvd2FyZHMgdGhlIGVuZCBvZiB0aGUgdGVudXJlIGRpc3RyaWJ1dGlvbiwgYXJvdW5kIDYwKyBtb250aHMuIFRoaXMgc3VnZ2VzdHMgdGhhdCBtb250aC10by1tb250aCBjb250cmFjdHMgbWF5IGF0dHJhY3QgbW9yZSBzaG9ydC10ZXJtIGN1c3RvbWVycywgd2hpbGUgbG9uZ2VyIGNvbnRyYWN0cyB0ZW5kIHRvIHJldGFpbiBjdXN0b21lcnMgZm9yIGV4dGVuZGVkIHBlcmlvZHMuCgojIyMgSW50ZXJuZXQgU2VydmljZSBBbmFseXNpcwoKSW4gdGhpcyBwYXJ0LCB3ZSB3aWxsIGFuYWx5emUgdGhlIEludGVybmV0IFNlcnZpY2UgYW5kIGl0cyBhZGQtb25zLiBGaXJzdCwgd2UgZ2V0IHRoZSBkYXRhIG9mIGN1c3RvbWVycyBoYXZpbmcgSW50ZXJuZXQgU2VydmljZSwgd2hpY2ggbWVhbnMgYEludGVybmV0U2VydmljZSAhPSAiTm8iYDoKCmBgYHtyfQpoYXZpbmdfaW50ZXJuZXRfc2VydmljZSA8LSBkYXRhW2RhdGEkSW50ZXJuZXRTZXJ2aWNlICE9ICdObycsIF0KaGF2aW5nX2ludGVybmV0X3NlcnZpY2UgPC0gaGF2aW5nX2ludGVybmV0X3NlcnZpY2VbLCAhKGNvbG5hbWVzKGhhdmluZ19pbnRlcm5ldF9zZXJ2aWNlKSAlaW4lIGMoJ2NvbnRyYWN0X3BlcmlvZCcsICdjb250cmFjdF9tb250aHMnKSldCmBgYAoKKioxLiBHZW5lcmFsIFZpZXcqKgoKU2ltaWxhciB0byB0aGUgYW5hbHlzaXMgb2YgcGhvbmUgc2VydmljZSwgdGhlIGNodXJuIHBhdHRlcm5zIGZvciBjdXN0b21lcnMgd2l0aCBpbnRlcm5ldCBzZXJ2aWNlIGFjcm9zcyBkaWZmZXJlbnQgZGVtb2dyYXBoaWNzIHNob3cgY29tcGFyYWJsZSB0cmVuZHMuIFRoaXMgc3VnZ2VzdHMgdGhhdCB0aGUgY29tcGFueSBtYWludGFpbnMgYSBiYWxhbmNlZCBhcHByb2FjaCwgcHJvdmlkaW5nIGNvbnNpc3RlbnQgc2VydmljZSBxdWFsaXR5IGZvciBib3RoIHBob25lIGFuZCBpbnRlcm5ldCBzZXJ2aWNlcywgd2l0aG91dCBiaWFzIHRvd2FyZHMgZWl0aGVyLgoKYGBge3IsIGZpZy53aWR0aD0yMCwgZmlnLmhlaWdodD0xNX0KY2hhcl9jb2xzIDwtIGNvbG5hbWVzKGhhdmluZ19pbnRlcm5ldF9zZXJ2aWNlKVtzYXBwbHkoaGF2aW5nX2ludGVybmV0X3NlcnZpY2UsIGlzX2NoYXJhY3RlcildCmNoYXJfY29scyA8LSBjaGFyX2NvbHNbY2hhcl9jb2xzICE9ICdJbnRlcm5ldFNlcnZpY2UnXQpjaGVja191bmlxdWUoaGF2aW5nX2ludGVybmV0X3NlcnZpY2UsIGNoYXJfY29scykKCgpwbG90X2xpc3QgPC0gbGlzdCgpCmZvciAoY29sIGluIGNoYXJfY29scykgewogIHBsb3QgPC0gZ2dwbG90KGRhdGEgPSBoYXZpbmdfaW50ZXJuZXRfc2VydmljZSwgYWVzKHggPSAhIXN5bShjb2wpLCBmaWxsID0gY2h1cm5fY2FzZSkpICsKICAgIGdlb21fYmFyKCkgKwogICAgbGFicyh0aXRsZSA9IHBhc3RlMCgiQ291bnQgb2YgQ2h1cm4gYnkgIiwgY29sKSwgeSA9ICdDb3VudCcsIGZpbGwgPSAiQ2h1cm4gQ2FzZSIpCiAgCiAgcGxvdF9saXN0W1tjb2xdXSA8LSBwbG90Cn0KZ3JpZF9wbG90cyA8LSBncmlkLmFycmFuZ2UoZ3JvYnMgPSBwbG90X2xpc3QsIG5yb3cgPSA1LCBuY29sID0gNCkKYGBgCgoqKjIuIENoYXJnZXMgYWNyb3NzIENvbnRyYWN0IFR5cGUgYW5kIEludGVybmV0IFNlcnZpY2UqKgoKTmV4dCwgd2UgZXhhbWluZSBob3cgbW9udGhseSBjaGFyZ2VzIHZhcnkgd2l0aCBkaWZmZXJlbnQgYWRkLW9ucyBmb3IgY3VzdG9tZXJzIHdpdGggYEludGVybmV0IFNlcnZpY2VgLiBUaGUgbG9naWMgYmVoaW5kIHRoaXMgZ3JhcGggaXMgdG8gZm9jdXMgb24gY3VzdG9tZXJzIHdobyBoYXZlIEludGVybmV0IFNlcnZpY2UgYWxvbmcgd2l0aCBvbmx5IG9uZSBhZGRpdGlvbmFsIGZlYXR1cmUuIEZvciBleGFtcGxlLCB0aGUgZmlyc3QgZ3JhcGggaWxsdXN0cmF0ZXMgY3VzdG9tZXJzIHdpdGggSW50ZXJuZXQgU2VydmljZSB3aG8gaGF2ZSBhZGRlZCBvbmx5IE9ubGluZSBTZWN1cml0eSwgd2l0aG91dCBhbnkgb3RoZXIgYWRkLW9ucy4KCmBgYHtyLCBmaWcud2lkdGg9MjAsIGZpZy5oZWlnaHQ9MjB9CnBsb3RfbGlzdCA8LSBsaXN0KCkKYWRkX29ucyA8LSBjKCJPbmxpbmVTZWN1cml0eSIsICJPbmxpbmVCYWNrdXAiLCAiRGV2aWNlUHJvdGVjdGlvbiIsICJUZWNoU3VwcG9ydCIsICJTdHJlYW1pbmdUViIsICJTdHJlYW1pbmdNb3ZpZXMiKQoKZm9yKGFkZF9vbiBpbiBhZGRfb25zKSB7CiAgbm9uX2FkZCA8LSBhZGRfb25zW2FkZF9vbnMgIT0gYWRkX29uXQogIAogIHNhbXBsZTEgPC0gaGF2aW5nX2ludGVybmV0X3NlcnZpY2UKICAKICBmb3IgKGNvbCBpbiBub25fYWRkKSB7CiAgICBzYW1wbGUxIDwtIHNhbXBsZTFbc2FtcGxlMVtbY29sXV0gPT0gIk5vIixdCiAgfQogIAogIHBsb3QgPC0gZ2dwbG90KGRhdGEgPSBzYW1wbGUxLCBhZXMoeCA9IE1vbnRobHlDaGFyZ2VzLCB5ID0gQ29udHJhY3QsIGZpbGwgPSAhIXN5bShhZGRfb24pKSkgKwogICAgZ2VvbV9ib3hwbG90KCkgKwogICAgZmFjZXRfZ3JpZChJbnRlcm5ldFNlcnZpY2UgfiBTdGF0dXMpICsKICAgIGxhYnModGl0bGUgPSBwYXN0ZTAoIk1vbnRobHkgQ2hhcmdlcyBieSBDb250cmFjdCBUeXBlIGFuZCAiLCBhZGRfb24pLAogICAgICAgICB4ID0gIk1vbnRobHkgQ2hhcmdlcyIsCiAgICAgICAgIHkgPSAiQ29udHJhY3QgVHlwZSIpCiAgCiAgcGxvdF9saXN0W1thZGRfb25dXSA8LSBwbG90Cn0KCmdyaWRfcGxvdHMgPC0gZ3JpZC5hcnJhbmdlKGdyb2JzID0gcGxvdF9saXN0LCBucm93PTMsIG5jb2w9MikKYGBgCgpgYGB7ciwgZmlnLmhlaWdodD0yMCwgZmlnLndpZHRoPTIwfQpwbG90X2xpc3QgPC0gbGlzdCgpCgpmb3IoYWRkX29uIGluIGFkZF9vbnMpIHsKICBub25fYWRkIDwtIGFkZF9vbnNbYWRkX29ucyAhPSBhZGRfb25dCiAgCiAgc2FtcGxlMSA8LSBoYXZpbmdfaW50ZXJuZXRfc2VydmljZQogIAogIGZvciAoY29sIGluIG5vbl9hZGQpIHsKICAgIHNhbXBsZTEgPC0gc2FtcGxlMVtzYW1wbGUxW1tjb2xdXSA9PSAiTm8iLF0KICB9CiAgCiAgcGxvdCA8LSBnZ3Bsb3QoZGF0YSA9IHNhbXBsZTEsIGFlcyh4ID0gQ29udHJhY3QsIGZpbGwgPSAhIXN5bShhZGRfb24pKSkgKwogICAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZG9kZ2UiKSArCiAgICBmYWNldF9ncmlkKEludGVybmV0U2VydmljZSB+IFN0YXR1cykgKwogICAgbGFicyh0aXRsZSA9IHBhc3RlMCgiQ291bnQgb2YgU3RhdHVzIGJ5IENvbnRyYWN0IFR5cGUgYW5kICIsIGFkZF9vbiksCiAgICAgICAgIHggPSAiQ29udHJhY3QgVHlwZSIsCiAgICAgICAgIHkgPSAiQ291bnQiKQogIAogIHBsb3RfbGlzdFtbYWRkX29uXV0gPC0gcGxvdAp9CgpncmlkX3Bsb3RzIDwtIGdyaWQuYXJyYW5nZShncm9icyA9IHBsb3RfbGlzdCwgbnJvdz0zLCBuY29sPTIpCmBgYApGcm9tIHRoZSBncmlkIHBsb3RzIGFib3ZlLCBpdCBpcyBldmlkZW50IHRoYXQgY3VzdG9tZXJzIHdpdGggdGhlIGBTdHJlYW1pbmdNb3ZpZXNgIGFuZCBgU3RyZWFtaW5nVFZgIGFkZC1vbnMgaW5jdXIgc2lnbmlmaWNhbnRseSBoaWdoZXIgY2hhcmdlcy4gVGhpcyBncm91cCBhbHNvIGV4aGliaXRzIGEgbm90YWJseSBoaWdoZXIgY2h1cm4gcmF0ZSwgc3VnZ2VzdGluZyB0aGF0IHRoZSBpbmNyZWFzZWQgY29zdHMgbWF5IG5vdCBhbGlnbiB3aXRoIGN1c3RvbWVyIGV4cGVjdGF0aW9ucywgcHJvbXB0aW5nIHRoZW0gdG8gbGVhdmUuCgoqKjMuIFRlbnVyZSBEaXN0cmlidXRpb24qKgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBoYXZpbmdfaW50ZXJuZXRfc2VydmljZSwgYWVzKHggPSBUZW51cmUsIGZpbGwgPSBTdGF0dXMpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxLCBhbHBoYSA9IDAuNikgKwogIGZhY2V0X3dyYXAofkNvbnRyYWN0KSArCiAgbGFicyh0aXRsZSA9ICJUZW51cmUgRGlzdHJpYnV0aW9uIGJ5IENvbnRyYWN0IFR5cGUgYW5kIENodXJuIFN0YXR1cyIsCiAgICAgICB4ID0gIlRlbnVyZSAoTW9udGhzKSIsCiAgICAgICB5ID0gIkNvdW50IiwKICAgICAgIGZpbGwgPSAiU3RhdHVzIikKYGBgClRoZSBzaW1pbGFyIHRlbnVyZSBkaXN0cmlidXRpb24gYWNyb3NzIGJvdGggaW50ZXJuZXQgYW5kIHBob25lIHNlcnZpY2VzIHN1Z2dlc3RzIHRoYXQgY3VzdG9tZXIgcmV0ZW50aW9uIHBhdHRlcm5zIGFyZSBjb25zaXN0ZW50IGJldHdlZW4gdGhlIHR3by4gVGhpcyBpbXBsaWVzIHRoYXQgdGhlIGZhY3RvcnMgaW5mbHVlbmNpbmcgY2h1cm4sIHN1Y2ggYXMgcHJpY2luZywgc2VydmljZSBxdWFsaXR5LCBvciBjb21wZXRpdGlvbiwgYXJlIGxpa2VseSB1bml2ZXJzYWwgYW5kIG5vdCBzcGVjaWZpYyB0byBlaXRoZXIgc2VydmljZS4KCiMjIyBEZXBlbmRlbnRzIGFuZCBQYXJ0bmVyIEFuYWx5c2lzCgoqKjEuIEluaXRpYWwgTG9vayoqCgpXZSBmaXJzdCBsb29rIGF0IHRoZSBgY2h1cm5fY2FzZWAgY291bnQgYWNyb3NzIGRlbW9ncmFwaGljIGZlYXR1cmVzLiBHZW5lcmFsbHksIGN1c3RvbWVycyB0ZW50IHRvIGNodXJuIHdoZW4gdGhlaXIgY29udHJhY3RzIGV4cGlyZWQuIEhvd2V2ZXIsIGN1c3RvbWVycyB0aGF0IGFyZSBgU2VuaW9yQ2l0aXplbmAgYXJlIHRoZSBsZWFzdCBsaWtleSB0byBjaHVybiwgd2hpY2ggbWlnaHQgaW1wbHkgdGhhdCB0aGV5IHByZWZlciBzdGFibGUgc2VydmljZS4gCgpgYGB7ciwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSAxMH0KZGVtb2dyYXBoaWNfY29scyA8LSBjKCdHZW5kZXInLCAnU2VuaW9yQ2l0aXplbicsICdQYXJ0bmVyJywgJ0RlcGVuZGVudHMnKQpkZW1vIDwtIGRhdGEKZGVtbyA8LSBkZW1vICU+JSBtdXRhdGUoCiAgU2VuaW9yQ2l0aXplbiA9IGNhc2Vfd2hlbigKICAgIFNlbmlvckNpdGl6ZW4gPT0gMCB+ICdObycsCiAgICBTZW5pb3JDaXRpemVuID09IDEgfiAnWWVzJwogICkKKQoKIyMgQ2hhcnRzIGRlbW9ncmFwaGljcwpmb3IgKGNvbCBpbiBkZW1vZ3JhcGhpY19jb2xzKSB7CiAgcGxvdCA8LSBnZ3Bsb3QoZGF0YSA9IGRlbW8sIGFlcyh4ID0gISFzeW0oY29sKSwgZmlsbCA9IGNodXJuX2Nhc2UpKSArCiAgICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJkb2RnZSIpICsKICAgIGxhYnModGl0bGUgPSBwYXN0ZSgiQ291bnQgb2YgU3RhdHVzIGJ5IiwgY29sKSwgeCA9IGNvbCwgeSA9ICJDb3VudCIpICsKICAgIHRoZW1lX21pbmltYWwoKSArCiAgICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlNldDIiKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikKICAKICBhc3NpZ24ocGFzdGUwKCJwbG90XyIsIGNvbCksIHBsb3QpCn0KCnBsb3RfRGVwZW5kZW50cyArIHBsb3RfR2VuZGVyICsgcGxvdF9QYXJ0bmVyICsgcGxvdF9TZW5pb3JDaXRpemVuCgpgYGAKCk5vdyB3ZSBsb29rIGZ1cnRoZXIgaW50byB0aGUgbW9udGhseSBjaGFyZ2VzLiBXZSBhbHNvIGV4dHJhY3QgZGF0YSBhYm91dCBjdXN0b21lcnMgaGF2aW5nIG5vIGRlcGVuZGVudHMgYW5kIGN1c3RvbWVycyBoYXZpbmcgbm8gcGFydG5lciB0byBzZWUgaWYgdGhlcmUgaXMgc2lnbmlmaWNhbnQgZGlmZmVyZW5jZSBpbiBjaGFyZ2UgZm9yIHRoZW0uIAoKYGBge3J9Cm5vX2RlcGVuZGVudHMgPC0gZGVtb1tkZW1vJERlcGVuZGVudHMgPT0gJ05vJyxdCmhhc19kZXBlbmRlbnRzIDwtIGRlbW9bZGVtbyREZXBlbmRlbnRzID09ICdZZXMnLF0KCm5vX3BhcnRuZXIgPC0gZGVtb1tkZW1vJFBhcnRuZXIgPT0gJ05vJyxdCmhhc19wYXJ0bmVyIDwtIGRlbW9bZGVtbyRQYXJ0bmVyID09ICdZZXMnLF0KYGBgCgoqKjIuIERlcGVuZGVudHMgUGVyc3BlY3RpdmUqKgoKYGBge3IsIGZpZy53aWR0aCA9IDEyfQpwbG90MSA8LSBnZ3Bsb3Qobm9fZGVwZW5kZW50cywgYWVzKHggPSBTdGF0dXMsIHkgPSBNb250aGx5Q2hhcmdlcywgZmlsbCA9IFN0YXR1cykpICsKICBnZW9tX2JveHBsb3QoKSArCiAgbGFicygKICAgIHRpdGxlID0gIk1vbnRobHkgQ2hhcmdlcyBEaXN0cmlidXRpb24gYnkgU3RhdHVzIChObyBEZXBlbmRlbnRzKSIsCiAgICB4ID0gIlN0YXR1cyIsCiAgICB5ID0gIk1vbnRobHkgQ2hhcmdlcyIKICApICsKICB0aGVtZV9taW5pbWFsKCkKCgpwbG90MiA8LSBnZ3Bsb3QoaGFzX2RlcGVuZGVudHMsIGFlcyh4ID0gU3RhdHVzLCB5ID0gTW9udGhseUNoYXJnZXMsIGZpbGwgPSBTdGF0dXMpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJNb250aGx5IENoYXJnZXMgRGlzdHJpYnV0aW9uIGJ5IFN0YXR1cyAoSGF2ZSBEZXBlbmRlbnRzKSIsCiAgICB4ID0gIlN0YXR1cyIsCiAgICB5ID0gIk1vbnRobHkgQ2hhcmdlcyIKICApICsKICB0aGVtZV9taW5pbWFsKCkKCnBsb3QxICsgcGxvdDIKYGBgCioqKkluc2lnaHRzOioqKgoKLSBIaWdoZXIgQ2hhcmdlcyBmb3IgQ3VzdG9tZXJzIFdobyBgTGVmdGA6CiAgLSBUaGUgbWVkaWFuIE1vbnRobHkgQ2hhcmdlcyBmb3IgdGhvc2Ugd2hvIGhhdmUgbGVmdCBpcyB3YXkgaGlnaGVyIGNvbXBhcmVkIHRvIHRoYXQgb2YgdGhvc2Ugd2hvIGFyZSBzdGlsbCBjdXJyZW50LgogIC0gVGhpcyBpbXBsaWVzIHRoYXQgaGlnaGVyIG1vbnRobHkgY2hhcmdlcyBjb3VsZCBiZSBhIGNhdXNlIGZvciBjaHVybmluZyBhbW9uZyB0aGUgbm8tcGFydG5lciBjdXN0b21lcnMuCgotIEluIGRlbW9ncmFwaGljIHBlcnNwZWN0aXZlLCB0aGUgbW9udGhseSBjaGFyZ2UgaXMgbm90IGJpZyBkaWZmZXJlbnQgd2hldGhlciBhIGN1c3RvbWVyIGhhcyBkZXBlbmRlbnQgb3Igbm90LiBUaGlzIHN1Z2dlc3RzIHRoYXQgaGF2aW5nIGRlcGVuZGVudCBoYXMgc2xpZ2h0IGltcGFjdCB0byB0aGUgbW9udGhseSBjaGFyZ2UuCgoqKjMuIFBhcnRuZXIgUGVyc3BlY3RpdmUqKgoKYGBge3IsIGZpZy53aWR0aD0xMH0KcGxvdDEgPC0gZ2dwbG90KG5vX3BhcnRuZXIsIGFlcyh4ID0gU3RhdHVzLCB5ID0gTW9udGhseUNoYXJnZXMsIGZpbGwgPSBTdGF0dXMpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJNb250aGx5IENoYXJnZXMgRGlzdHJpYnV0aW9uIGJ5IFN0YXR1cyAoTm8gUGFydG5lcikiLAogICAgeCA9ICJTdGF0dXMiLAogICAgeSA9ICJNb250aGx5IENoYXJnZXMiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpCgpwbG90MiA8LSBnZ3Bsb3QoaGFzX3BhcnRuZXIsIGFlcyh4ID0gU3RhdHVzLCB5ID0gTW9udGhseUNoYXJnZXMsIGZpbGwgPSBTdGF0dXMpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJNb250aGx5IENoYXJnZXMgRGlzdHJpYnV0aW9uIGJ5IFN0YXR1cyAoTm8gUGFydG5lcikiLAogICAgeCA9ICJTdGF0dXMiLAogICAgeSA9ICJNb250aGx5IENoYXJnZXMiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpCgpwbG90MSArIHBsb3QyCmBgYAoqKipJbnNpZ2h0czoqKioKCi0gSGlnaGVyIE1lZGlhbiBDaGFyZ2VzIGZvciBDdXN0b21lcnMgV2hvIExlZnQ6CiAgLSBTaW1pbGFyIHRvIGBEZXBlbmRlbnRzYCBwZXJzcGVjdGl2ZSBhYm92ZSwgdGhlIGN1c3RvbWVycyB3aG8gaGF2ZSBsZWZ0IGhhZCBiZWVuIG1vbnRobHkgY2hhcmdlZCBoaWdoZXIgdGhhbiB0aG9zZSBhcmUgc3RpbGwgb24gc2VydmljZS4gCiAgCi0gQ3VzdG9tZXJzIHdpdGhvdXQgcGFydG5lcnMgYXJlIHBheWluZyBoaWdoZXIgbW9udGhseSBjaGFyZ2VzIGNvbXBhcmVkIHRvIHRob3NlIHdpdGggcGFydG5lcnMuIFRoaXMgaW5zaWdodCBzdWdnZXN0cyBhIHBvdGVudGlhbCBuZWVkIHRvIHJldmlzZSBwcmljaW5nIHN0cmF0ZWdpZXMgdG8gY2F0ZXIgdG8gc29sbyBjdXN0b21lcnMgYW5kIGVuc3VyZSBlcXVpdGFibGUgc2VydmljZSBvZmZlcmluZ3MuCgoqKkJ1c2luZXNzIEltcGxpY2F0aW9uOioqCgotIEN1c3RvbWVycyB3aXRoIG5vIHBhcnRuZXIgcGF5aW5nIGhpZ2hlciBtb250aGx5IGNoYXJnZXMgYXJlIG1vcmUgbGlrZWx5IHRvIGV4cGVyaWVuY2UgY2h1cm4uIFRhcmdldGVkIGRpc2NvdW50cyBvciBwZXJzb25hbGl6ZWQgcGxhbnMgZm9yIHRoaXMgY2F0ZWdvcnkgY291bGQgaGVscCBpbiByZXRhaW5pbmcgY3VzdG9tZXJzLgoKIyMjIE1vbnRobHkgQ2hhcmdlcyBhbmQgQ2h1cm4gVHlwZQoKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTh9CmdncGxvdChkYXRhLCBhZXMoeCA9IGNodXJuX2Nhc2UsIHkgPSBNb250aGx5Q2hhcmdlcywgZmlsbCA9IENvbnRyYWN0KSkgKwogIGdlb21fYm94cGxvdCgpICsKICBsYWJzKAogICAgdGl0bGUgPSAiTW9udGhseSBDaGFyZ2VzIGJ5IENvbnRyYWN0IFR5cGUgYW5kIENodXJuIENhc2UiLAogICAgeCA9ICJDaHVybiBDYXNlIiwKICAgIHkgPSAiTW9udGhseSBDaGFyZ2VzIgogICkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikKYGBgClRoZSBhdmVyYWdlIG1vbnRobHkgY2hhcmdlIGZvciBjdXN0b21lcnMgd2l0aCBhIGBNb250aC10by1tb250aGAgY29udHJhY3QgaXMgc2xpZ2h0bHkgaGlnaGVyIGNvbXBhcmVkIHRvIG90aGVyIGNvbnRyYWN0IHR5cGVzLiBUaGlzIGxpa2VseSByZWZsZWN0cyB0aGUgY29tcHJlaGVuc2l2ZSByYW5nZSBvZiBzZXJ2aWNlcyB1dGlsaXplZCBieSB0aGVzZSBjdXN0b21lcnMuIFRoZXJlZm9yZSwgaXQgaXMgdW5saWtlbHkgdGhhdCBtb250aGx5IGNoYXJnZXMgYXJlIGEgc2lnbmlmaWNhbnQgZmFjdG9yIGNvbnRyaWJ1dGluZyB0byBjdXN0b21lciBjaHVybiBpbiB0aGlzIGdyb3VwLgoKCiMjIE1vZGVscyBJbXBsZW1lbnRhdGlvbiB7LnRhYnNldH0KCiMjIyBMb2dpc3RpYyBSZWdyZXNzaW9uCgpTaW5jZSBjZXJ0YWluIHByZWRpY3RvciB2YXJpYWJsZXMgYXJlIGRlcGVuZGVudCBvbiBvdGhlciBjb2x1bW5zIChlLmcuLCBgTXVsdGlwbGVMaW5lc2AgZGVwZW5kcyBvbiBgUGhvbmVTZXJ2aWNlYCksIGl0IGlzIGltcG9ydGFudCB0byBjbGVhbnNlIHRoZXNlIGNvbHVtbnMgZmlyc3QgdG8gYXZvaWQgKioqTXVsdGljb2xsaW5lYXJpdHkqKiogaW4gbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbHMuIFdlIHdpbGwgY3JlYXRlIGN1c3RvbSBwcm9jZXNzaW5nIGZ1bmN0aW9ucyBmb3IgdGhlIGBJbnRlcm5ldFNlcnZpY2VgIGFuZCBgUGhvbmVTZXJ2aWNlYCBjb2x1bW5zLCBlbnN1cmluZyB0aGF0IGVhY2ggYWRkLW9uIGZvciB0aGVzZSBzZXJ2aWNlcyBpcyBncm91cGVkIGFuZCBjb21iaW5lZCBpbnRvIGEgc2luZ2xlIGNvbHVtbi4gVGhpcyB3aWxsIGhlbHAgcHJldmVudCBpc3N1ZXMgd2l0aCBtdWx0aWNvbGxpbmVhcml0eSBhbmQgaW1wcm92ZSB0aGUgbW9kZWwncyBhY2N1cmFjeS4KCmBgYHtyfQpwcm9jZXNzX2ludGVybmV0X2ZlYXR1cmVzIDwtIGZ1bmN0aW9uKGRmKSB7CiAgbmV3X2RhdGEgPC0gZGYgJT4lIAogICAgbXV0YXRlKAogICAgICBpbnRlcm5ldF9zdGF0dXMgPSBjYXNlX3doZW4oCiAgICAgICAgSW50ZXJuZXRTZXJ2aWNlID09ICJObyIgfiAiTm8gSW50ZXJuZXQiLAogICAgICAgIAogICAgICAgIEludGVybmV0U2VydmljZSA9PSAiRFNMIiAmCiAgICAgICAgICByb3dTdW1zKGFjcm9zcyhjKE9ubGluZVNlY3VyaXR5LCBPbmxpbmVCYWNrdXAsIERldmljZVByb3RlY3Rpb24pLCB+IC54ID09ICJObyIpKSA9PSAzICYKICAgICAgICAgIHJvd1N1bXMoYWNyb3NzKGMoU3RyZWFtaW5nVFYsIFN0cmVhbWluZ01vdmllcyksIH4gLnggPT0gIk5vIikpID09IDIgJgogICAgICAgICAgVGVjaFN1cHBvcnQgPT0gIk5vIiB+ICJEU0wgQmFzaWMiLAogICAgICAgIAogICAgICAgIEludGVybmV0U2VydmljZSA9PSAiRFNMIiAmCiAgICAgICAgICByb3dTdW1zKGFjcm9zcyhjKE9ubGluZVNlY3VyaXR5LCBPbmxpbmVCYWNrdXAsIERldmljZVByb3RlY3Rpb24pLCB+IC54ID09ICJZZXMiKSkgPiAwICYKICAgICAgICAgIHJvd1N1bXMoYWNyb3NzKGMoU3RyZWFtaW5nVFYsIFN0cmVhbWluZ01vdmllcyksIH4gLnggPT0gIlllcyIpKSA+IDAgJgogICAgICAgICAgVGVjaFN1cHBvcnQgPT0gIlllcyIgfiAiRFNMIEZ1bGwiLAogICAgICAgIAogICAgICAgIEludGVybmV0U2VydmljZSA9PSAiRFNMIiAmCiAgICAgICAgICByb3dTdW1zKGFjcm9zcyhjKE9ubGluZVNlY3VyaXR5LCBPbmxpbmVCYWNrdXAsIERldmljZVByb3RlY3Rpb24pLCB+IC54ID09ICJZZXMiKSkgPiAwICYKICAgICAgICAgIHJvd1N1bXMoYWNyb3NzKGMoU3RyZWFtaW5nVFYsIFN0cmVhbWluZ01vdmllcyksIH4gLnggPT0gIlllcyIpKSA8PSAwICYKICAgICAgICAgIFRlY2hTdXBwb3J0ICE9ICJZZXMiIH4gIkRTTCBTZWN1cml0eSIsCiAgICAgICAgCiAgICAgICAgSW50ZXJuZXRTZXJ2aWNlID09ICJEU0wiICYKICAgICAgICAgIHJvd1N1bXMoYWNyb3NzKGMoT25saW5lU2VjdXJpdHksIE9ubGluZUJhY2t1cCwgRGV2aWNlUHJvdGVjdGlvbiksIH4gLnggPT0gIlllcyIpKSA8PSAwICYKICAgICAgICAgIHJvd1N1bXMoYWNyb3NzKGMoU3RyZWFtaW5nVFYsIFN0cmVhbWluZ01vdmllcyksIH4gLnggPT0gIlllcyIpKSA+IDAgJgogICAgICAgICAgVGVjaFN1cHBvcnQgIT0gIlllcyIgfiAiRFNMIEVudGVydGFpbm1lbnQiLAogICAgICAgIAogICAgICAgIEludGVybmV0U2VydmljZSA9PSAiRFNMIiAmCiAgICAgICAgICByb3dTdW1zKGFjcm9zcyhjKE9ubGluZVNlY3VyaXR5LCBPbmxpbmVCYWNrdXAsIERldmljZVByb3RlY3Rpb24pLCB+IC54ID09ICJZZXMiKSkgPD0gMCAmCiAgICAgICAgICByb3dTdW1zKGFjcm9zcyhjKFN0cmVhbWluZ1RWLCBTdHJlYW1pbmdNb3ZpZXMpLCB+IC54ID09ICJZZXMiKSkgPD0gMCAmCiAgICAgICAgICBUZWNoU3VwcG9ydCA9PSAiWWVzIiB+ICJEU0wgU3VwcG9ydCIsCiAgICAgICAgCiAgICAgICAgSW50ZXJuZXRTZXJ2aWNlID09ICJEU0wiICYKICAgICAgICAgIHJvd1N1bXMoYWNyb3NzKGMoT25saW5lU2VjdXJpdHksIE9ubGluZUJhY2t1cCwgRGV2aWNlUHJvdGVjdGlvbiksIH4gLnggPT0gIlllcyIpKSA+IDAgJgogICAgICAgICAgcm93U3VtcyhhY3Jvc3MoYyhTdHJlYW1pbmdUViwgU3RyZWFtaW5nTW92aWVzKSwgfiAueCA9PSAiWWVzIikpIDw9IDAgJgogICAgICAgICAgVGVjaFN1cHBvcnQgPT0gIlllcyIgfiAiRFNMIFNlY3VyaXR5ICYgU3VwcG9ydCIsCiAgICAgICAgCiAgICAgICAgSW50ZXJuZXRTZXJ2aWNlID09ICJEU0wiICYKICAgICAgICAgIHJvd1N1bXMoYWNyb3NzKGMoT25saW5lU2VjdXJpdHksIE9ubGluZUJhY2t1cCwgRGV2aWNlUHJvdGVjdGlvbiksIH4gLnggPT0gIlllcyIpKSA8PSAwICYKICAgICAgICAgIHJvd1N1bXMoYWNyb3NzKGMoU3RyZWFtaW5nVFYsIFN0cmVhbWluZ01vdmllcyksIH4gLnggPT0gIlllcyIpKSA+IDAgJgogICAgICAgICAgVGVjaFN1cHBvcnQgPT0gIlllcyIgfiAiRFNMIEVudGVydGFpbm1lbnQgJiBTdXBwb3J0IiwKICAgICAgICAKICAgICAgICBJbnRlcm5ldFNlcnZpY2UgPT0gIkRTTCIgJgogICAgICAgICAgcm93U3VtcyhhY3Jvc3MoYyhPbmxpbmVTZWN1cml0eSwgT25saW5lQmFja3VwLCBEZXZpY2VQcm90ZWN0aW9uKSwgfiAueCA9PSAiWWVzIikpID4gMCAmCiAgICAgICAgICByb3dTdW1zKGFjcm9zcyhjKFN0cmVhbWluZ1RWLCBTdHJlYW1pbmdNb3ZpZXMpLCB+IC54ID09ICJZZXMiKSkgPiAwICYKICAgICAgICAgIFRlY2hTdXBwb3J0ID09ICJObyIgfiAiRFNMIFNlY3VyaXR5ICYgRW50ZXJ0YWlubWVudCIsCiAgICAgICAgCiAgICAgICAgSW50ZXJuZXRTZXJ2aWNlID09ICJGaWJlciBvcHRpYyIgJgogICAgICAgICAgcm93U3VtcyhhY3Jvc3MoYyhPbmxpbmVTZWN1cml0eSwgT25saW5lQmFja3VwLCBEZXZpY2VQcm90ZWN0aW9uKSwgfiAueCA9PSAiTm8iKSkgPT0gMyAmCiAgICAgICAgICByb3dTdW1zKGFjcm9zcyhjKFN0cmVhbWluZ1RWLCBTdHJlYW1pbmdNb3ZpZXMpLCB+IC54ID09ICJObyIpKSA9PSAyICYKICAgICAgICAgIFRlY2hTdXBwb3J0ID09ICJObyIgfiAiRmliZXIgb3B0aWMgQmFzaWMiLAogICAgICAgIAogICAgICAgIEludGVybmV0U2VydmljZSA9PSAiRmliZXIgb3B0aWMiICYKICAgICAgICAgIHJvd1N1bXMoYWNyb3NzKGMoT25saW5lU2VjdXJpdHksIE9ubGluZUJhY2t1cCwgRGV2aWNlUHJvdGVjdGlvbiksIH4gLnggPT0gIlllcyIpKSA+IDAgJgogICAgICAgICAgcm93U3VtcyhhY3Jvc3MoYyhTdHJlYW1pbmdUViwgU3RyZWFtaW5nTW92aWVzKSwgfiAueCA9PSAiWWVzIikpID4gMCAmCiAgICAgICAgICBUZWNoU3VwcG9ydCA9PSAiWWVzIiB+ICJGaWJlciBvcHRpYyBGdWxsIiwKICAgICAgICAKICAgICAgICBJbnRlcm5ldFNlcnZpY2UgPT0gIkZpYmVyIG9wdGljIiAmCiAgICAgICAgICByb3dTdW1zKGFjcm9zcyhjKE9ubGluZVNlY3VyaXR5LCBPbmxpbmVCYWNrdXAsIERldmljZVByb3RlY3Rpb24pLCB+IC54ID09ICJZZXMiKSkgPiAwICYKICAgICAgICAgIHJvd1N1bXMoYWNyb3NzKGMoU3RyZWFtaW5nVFYsIFN0cmVhbWluZ01vdmllcyksIH4gLnggPT0gIlllcyIpKSA8PSAwICYKICAgICAgICAgIFRlY2hTdXBwb3J0ICE9ICJZZXMiIH4gIkZpYmVyIG9wdGljIFNlY3VyaXR5IiwKICAgICAgICAKICAgICAgICBJbnRlcm5ldFNlcnZpY2UgPT0gIkZpYmVyIG9wdGljIiAmCiAgICAgICAgICByb3dTdW1zKGFjcm9zcyhjKE9ubGluZVNlY3VyaXR5LCBPbmxpbmVCYWNrdXAsIERldmljZVByb3RlY3Rpb24pLCB+IC54ID09ICJZZXMiKSkgPD0gMCAmCiAgICAgICAgICByb3dTdW1zKGFjcm9zcyhjKFN0cmVhbWluZ1RWLCBTdHJlYW1pbmdNb3ZpZXMpLCB+IC54ID09ICJZZXMiKSkgPiAwICYKICAgICAgICAgIFRlY2hTdXBwb3J0ICE9ICJZZXMiIH4gIkZpYmVyIG9wdGljIEVudGVydGFpbm1lbnQiLAogICAgICAgIAogICAgICAgIEludGVybmV0U2VydmljZSA9PSAiRmliZXIgb3B0aWMiICYKICAgICAgICAgIHJvd1N1bXMoYWNyb3NzKGMoT25saW5lU2VjdXJpdHksIE9ubGluZUJhY2t1cCwgRGV2aWNlUHJvdGVjdGlvbiksIH4gLnggPT0gIlllcyIpKSA8PSAwICYKICAgICAgICAgIHJvd1N1bXMoYWNyb3NzKGMoU3RyZWFtaW5nVFYsIFN0cmVhbWluZ01vdmllcyksIH4gLnggPT0gIlllcyIpKSA8PSAwICYKICAgICAgICAgIFRlY2hTdXBwb3J0ID09ICJZZXMiIH4gIkZpYmVyIG9wdGljIFN1cHBvcnQiLAogICAgICAgIAogICAgICAgIEludGVybmV0U2VydmljZSA9PSAiRmliZXIgb3B0aWMiICYKICAgICAgICAgIHJvd1N1bXMoYWNyb3NzKGMoT25saW5lU2VjdXJpdHksIE9ubGluZUJhY2t1cCwgRGV2aWNlUHJvdGVjdGlvbiksIH4gLnggPT0gIlllcyIpKSA+IDAgJgogICAgICAgICAgcm93U3VtcyhhY3Jvc3MoYyhTdHJlYW1pbmdUViwgU3RyZWFtaW5nTW92aWVzKSwgfiAueCA9PSAiWWVzIikpIDw9IDAgJgogICAgICAgICAgVGVjaFN1cHBvcnQgPT0gIlllcyIgfiAiRmliZXIgb3B0aWMgU2VjdXJpdHkgJiBTdXBwb3J0IiwKICAgICAgICAKICAgICAgICBJbnRlcm5ldFNlcnZpY2UgPT0gIkZpYmVyIG9wdGljIiAmCiAgICAgICAgICByb3dTdW1zKGFjcm9zcyhjKE9ubGluZVNlY3VyaXR5LCBPbmxpbmVCYWNrdXAsIERldmljZVByb3RlY3Rpb24pLCB+IC54ID09ICJZZXMiKSkgPD0gMCAmCiAgICAgICAgICByb3dTdW1zKGFjcm9zcyhjKFN0cmVhbWluZ1RWLCBTdHJlYW1pbmdNb3ZpZXMpLCB+IC54ID09ICJZZXMiKSkgPiAwICYKICAgICAgICAgIFRlY2hTdXBwb3J0ID09ICJZZXMiIH4gIkZpYmVyIG9wdGljIEVudGVydGFpbm1lbnQgJiBTdXBwb3J0IiwKICAgICAgICAKICAgICAgICBJbnRlcm5ldFNlcnZpY2UgPT0gIkZpYmVyIG9wdGljIiAmCiAgICAgICAgICByb3dTdW1zKGFjcm9zcyhjKE9ubGluZVNlY3VyaXR5LCBPbmxpbmVCYWNrdXAsIERldmljZVByb3RlY3Rpb24pLCB+IC54ID09ICJZZXMiKSkgPiAwICYKICAgICAgICAgIHJvd1N1bXMoYWNyb3NzKGMoU3RyZWFtaW5nVFYsIFN0cmVhbWluZ01vdmllcyksIH4gLnggPT0gIlllcyIpKSA+IDAgJgogICAgICAgICAgVGVjaFN1cHBvcnQgPT0gIk5vIiB+ICJGaWJlciBvcHRpYyBTZWN1cml0eSAmIEVudGVydGFpbm1lbnQiLAogICAgICAgIAogICAgICAgIFRSVUUgfiBOQV9jaGFyYWN0ZXJfCiAgICAgICkKICAgICkgJT4lIHNlbGVjdCgtYyhJbnRlcm5ldFNlcnZpY2UsIE9ubGluZVNlY3VyaXR5LCBPbmxpbmVCYWNrdXAsIERldmljZVByb3RlY3Rpb24sIFN0cmVhbWluZ1RWLCBTdHJlYW1pbmdNb3ZpZXMsIFRlY2hTdXBwb3J0KSkKICAKICByZXR1cm4obmV3X2RhdGEpCn0KCnByb2Nlc3NfcGhvbmVfZmVhdHVyZSA8LSBmdW5jdGlvbihkZikgewogIGRmICU+JSBtdXRhdGUgKAogICAgcGhvbmVfc3RhdHVzID0gY2FzZV93aGVuKAogICAgICBQaG9uZVNlcnZpY2UgPT0gIk5vIiB+ICJObyIsCiAgICAgIFBob25lU2VydmljZSA9PSAiWWVzIiAmIE11bHRpcGxlTGluZXMgPT0gIk5vIiB+ICJTaW5nbGUgTGluZSIsCiAgICAgIFBob25lU2VydmljZSA9PSAiWWVzIiAmIE11bHRpcGxlTGluZXMgPT0gIlllcyIgfiAiTXVsdGlwbGUgTGluZXMiLAogICAgICBUUlVFIH4gTkFfY2hhcmFjdGVyXwogICAgKQogICkgJT4lIHNlbGVjdCAoLWMoUGhvbmVTZXJ2aWNlLCBNdWx0aXBsZUxpbmVzKSkKfQpgYGAKCk5vdyB3ZSBhcHBseSB0aGUgYWJvdmUgcHJvY2Vzc29ycyB0byB0aGUgZGF0YXNldCBhbmQgcmVtb3ZlIHRoZSBjb2x1bW4gYFRvdGFsQ2hhcmdlc2Agc2luY2UgaXQgaGFzIGhpZ2ggY29ycmVsYXRpb24gd2l0aCBgTW9udGhseUNoYXJnZXNgLCB3aGljaCBtaWdodCBjcmVhdGUgbXVsdGljb2xsaW5lYXJpdHkuIFdlIGFsc28gaGF2ZSBhIGxvb2sgYXQgdGhlIGZpcnN0IDUgcm93czoKCmBgYHtyfQpkZjEgPC0gZGF0YSAlPiUgc2VsZWN0KC1jKCJjb250cmFjdF9tb250aHMiLCAiY29udHJhY3RfcGVyaW9kIiwgImNodXJuX2Nhc2UiLCAiVG90YWxDaGFyZ2VzIikpCmRmMiA8LSBwcm9jZXNzX2ludGVybmV0X2ZlYXR1cmVzKGRmMSkKZGYyIDwtIHByb2Nlc3NfcGhvbmVfZmVhdHVyZShkZjIpCgpoZWFkKGRmMiwgNSkKYGBgCgpXZSBjcmVhdGUgYSAqKipMb2dpc3RpYyBSZWdyZXNzaW9uKioqIG1vZGVsIHdpdGggdGhlICoqKkNsYXNzaWZpY2F0aW9uKioqIG1vZGUgc2luY2UgdGhlIHRhcmdldCB2YXJpYWJsZSBTdGF0dXMgaGFzIHR3byBjYXRlZ29yaWVzOiBgQ3VycmVudGAgYW5kIGBMZWZ0LmAgVGhlIG1vZGVsIGluY2x1ZGVzIDUtZm9sZCBjcm9zcy12YWxpZGF0aW9uLCBhIHJlY2lwZSBmb3IgY3JlYXRpbmcgZHVtbXkgdmFyaWFibGVzIGZvciBjYXRlZ29yaWNhbCBjb2x1bW5zLCBhbmQgbm9ybWFsaXppbmcgdGhlIHZhbHVlcyBmb3IgbnVtZXJpY2FsIGNvbHVtbnMuIEFkZGl0aW9uYWxseSwgYSB0dW5pbmcgZ3JpZCBpcyBhcHBsaWVkIHRvIG9wdGltaXplIHRoZSBtb2RlbCdzIHBhcmFtZXRlcnMuCgpgYGB7cn0KIyBDb252ZXJ0IFN0YXR1cyB0byBhIGZhY3RvciBhbmQgc3BlY2lmeSBsZXZlbHMKZGYyJFN0YXR1cyA8LSBmYWN0b3IoZGYyJFN0YXR1cykKCiMgU3BsaXQgZGF0YSBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0aW5nIHNldHMKc2V0LnNlZWQoMTIzKQpkYXRhX3NwbGl0IDwtIGluaXRpYWxfc3BsaXQoZGF0YSA9IGRmMiwgcHJvcCA9IDAuNywgc3RyYXRhID0gU3RhdHVzKQp0cmFpbiA8LSB0cmFpbmluZyhkYXRhX3NwbGl0KQp0ZXN0IDwtIHRlc3RpbmcoZGF0YV9zcGxpdCkKCiMgU2V0IHVwIGNyb3NzLXZhbGlkYXRpb24Kc2V0LnNlZWQoMTIzKQpsb2dpc3RpY19rZm9sZHMgPC0gdmZvbGRfY3YodHJhaW4sIHYgPSA1LCBzdHJhdGEgPSBTdGF0dXMpCgpsb2dpc3RpY19yZWNpcGUgPC0gcmVjaXBlKFN0YXR1cyB+IC4sIGRhdGEgPSB0cmFpbikgJT4lCiAgc3RlcF9kdW1teShhbGxfbm9taW5hbF9wcmVkaWN0b3JzKCksIG9uZV9ob3QgPSBUUlVFKSAlPiUKICBzdGVwX1llb0pvaG5zb24oYWxsX251bWVyaWNfcHJlZGljdG9ycygpKSAlPiUKICBzdGVwX25vcm1hbGl6ZShhbGxfbnVtZXJpY19wcmVkaWN0b3JzKCkpCgojIERlZmluZSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsCmxvZ2lzdGljX3JpZGdlX21vZGVsIDwtIGxvZ2lzdGljX3JlZyhwZW5hbHR5ID0gdHVuZSgpLCBtaXh0dXJlID0gdHVuZSgpKSAlPiUKICBzZXRfZW5naW5lKCJnbG1uZXQiKSAlPiUKICBzZXRfbW9kZSgiY2xhc3NpZmljYXRpb24iKQoKIyBDcmVhdGUgd29ya2Zsb3cKbG9naXN0aWNfcmlkZ2Vfd2YgPC0gd29ya2Zsb3coKSAlPiUKICBhZGRfcmVjaXBlKGxvZ2lzdGljX3JlY2lwZSkgJT4lCiAgYWRkX21vZGVsKGxvZ2lzdGljX3JpZGdlX21vZGVsKQoKIyBDcmVhdGUgaHlwZXJwYXJhbWV0ZXIgc2VhcmNoIGdyaWQKbG9naXN0aWNfZ3JpZCA8LSBncmlkX3JlZ3VsYXIobWl4dHVyZSgpLCBwZW5hbHR5KGMoLTEwLDUpKSwgbGV2ZWxzID0gMTApCgojIFBlcmZvcm0gaHlwZXJwYXJhbXRlciBzZWFyY2gKdHVuaW5nX3Jlc3VsdHMgPC0gbG9naXN0aWNfcmlkZ2Vfd2YgJT4lCiAgdHVuZV9ncmlkKHJlc2FtcGxlcyA9IGxvZ2lzdGljX2tmb2xkcywgZ3JpZCA9IGxvZ2lzdGljX2dyaWQpCgpwcmludCh0dW5pbmdfcmVzdWx0cyAlPiUKICBjb2xsZWN0X21ldHJpY3MoKSAlPiUKICBkcGx5cjo6ZmlsdGVyKC5tZXRyaWMgPT0gInJvY19hdWMiKSAlPiUKICBhcnJhbmdlKGRlc2MobWVhbikpKQoKYXV0b3Bsb3QodHVuaW5nX3Jlc3VsdHMpCmBgYAoKV2l0aCBzdWNoIGxvdyByZWd1bGFyaXphdGlvbiB2YWx1ZXMgKHJhbmdpbmcgZnJvbSBhcm91bmQgMWUtMDcgdG8gMWUtMDMpLCB0aGUgbW9kZWwgY2FuIGZpdCB0aGUgdHJhaW5pbmcgZGF0YSB3ZWxsLCBhY2hpZXZpbmcgYSBoaWdoIFJPQyBBVUMgc2NvcmUgKGFyb3VuZCAwLjg0KSBkdXJpbmcgY3Jvc3MtdmFsaWRhdGlvbi4gVGhpcyBzdWdnZXN0cyB0aGF0IHRoZSBmZWF0dXJlcyBpbiB0aGUgZGF0YXNldCBhcmUgaGlnaGx5IGluZm9ybWF0aXZlLCBlbmFibGluZyB0aGUgTG9naXN0aWMgUmVncmVzc2lvbiBtb2RlbCB0byBlZmZlY3RpdmVseSBkaXN0aW5ndWlzaCBiZXR3ZWVuIHRoZSBgU3RhdHVzYCBjbGFzc2VzLCBldmVuIHdpdGggbWluaW1hbCByZWd1bGFyaXphdGlvbi4KCk5vdywgd2UgY2hvb3NlIHRoZSBiZXN0IGZyb20gdHVuaW5nIHNlYXJjaCB0byBhcHBseSBvbiBvdXIgdGVzdCBkYXRhc2V0IGFuZCBzZWUgdG9wIDEwIGltcG9ydGFudCBmZWF0dXJlczoKCmBgYHtyfQpiZXN0X2h5cGVycGFyYW1ldGVycyA8LSBzZWxlY3RfYmVzdCh0dW5pbmdfcmVzdWx0cywgbWV0cmljID0gInJvY19hdWMiKQoKZmluYWxfd2YgPC0gd29ya2Zsb3coKSAlPiUKICBhZGRfcmVjaXBlKGxvZ2lzdGljX3JlY2lwZSkgJT4lCiAgYWRkX21vZGVsKGxvZ2lzdGljX3JpZGdlX21vZGVsKSAlPiUKICBmaW5hbGl6ZV93b3JrZmxvdyhiZXN0X2h5cGVycGFyYW1ldGVycykKCmZpbmFsX2ZpdCA8LSBmaW5hbF93ZiAlPiUKICBmaXQoZGF0YSA9IHRyYWluKQoKZmluYWxfZml0ICU+JQogIGV4dHJhY3RfZml0X3BhcnNuaXAoKSAlPiUKICB2aXAoKQpgYGAKYFRlbnVyZWAgYXBwZWFycyB0byBiZSB0aGUgbW9zdCBpbmZsdWVudGlhbCBmYWN0b3IgaW4gcHJlZGljdGluZyBjdXN0b21lciBjaHVybi4gRm9sbG93aW5nIHRoYXQsIGBNb250aGx5Q2hhcmdlc2AgYWxzbyBwbGF5cyBhIHNpZ25pZmljYW50IHJvbGUgaW4gY3VzdG9tZXIgZGVwYXJ0dXJlcy4gVGhpcyBzdWdnZXN0cyB0aGF0IHdlIHNob3VsZCBmdXJ0aGVyIGludmVzdGlnYXRlIHRoZSBjdXJyZW50IGNoYXJnZXMsIGVzcGVjaWFsbHkgaW4gY29uanVuY3Rpb24gd2l0aCB0aGUgcHJldmlvdXMgYW5hbHlzaXMgb2YgZGlmZmVyZW50IHNlcnZpY2VzLCB0byBnYWluIGRlZXBlciBpbnNpZ2h0cyBpbnRvIHRoZSBmYWN0b3JzIGRyaXZpbmcgY2h1cm4uCgpMZXQgaGF2ZSBhIGxvb2sgYXQgUk9DIGN1cnZlLCB3aGljaCBtZWFzdXJlcyB0aGUgbW9kZWwncyBhYmlsaXR5IHRvIGRpc3Rpbmd1aXNoIGJldHdlZW4gdGhlIHR3byBjbGFzc2VzIG9mIGBTdGF0dXNgOgoKYGBge3J9CmZpbmFsX2ZpdCAlPiUgCiAgIHByZWRpY3QodGVzdCwgdHlwZSA9ICJwcm9iIikgJT4lCiAgIG11dGF0ZSh0cnV0aCA9IHRlc3QkU3RhdHVzKSAlPiUKICAgcm9jX2N1cnZlKHRydXRoLCAucHJlZF9DdXJyZW50KSAlPiUKICAgYXV0b3Bsb3QoKQoKcHJpbnQoZmluYWxfZml0ICU+JQogICAgICAgIHByZWRpY3QodGVzdCwgdHlwZSA9ICJwcm9iIikgJT4lCiAgICAgICAgbXV0YXRlKHRydXRoID0gdGVzdCRTdGF0dXMpICU+JQogICAgICAgIHJvY19hdWModHJ1dGgsIC5wcmVkX0N1cnJlbnQpCiAgICApCgpwcmludChmaW5hbF9maXQgJT4lCiAgICBwcmVkaWN0KHRlc3QpICU+JQogICAgYmluZF9jb2xzKHRlc3QgJT4lIHNlbGVjdChTdGF0dXMpKSAlPiUKICAgIGNvbmZfbWF0KHRydXRoID0gU3RhdHVzLCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzKQogICkKYGBgCi0gVGhlIGN1cnZl4oCZcyBwcm94aW1pdHkgdG8gdGhlIHRvcC1sZWZ0IGNvcm5lciBpbmRpY2F0ZXMgdGhhdCB0aGUgTG9naXN0aWMgUmVncmVzc2lvbiBtb2RlbCBhY2hpZXZlcyBoaWdoIHNlbnNpdGl2aXR5IHdoaWxlIG1haW50YWluaW5nIGEgbG93IGZhbHNlIHBvc2l0aXZlIHJhdGUgYXQgb3B0aW1hbCB0aHJlc2hvbGRzLgoKLSBUaGUgbW9kZWzigJlzIEFVQyBzY29yZSBvZiAwLjg1IChhcyBwcmV2aW91c2x5IGNhbGN1bGF0ZWQpIGZ1cnRoZXIgdmFsaWRhdGVzIGl0cyBlZmZlY3RpdmVuZXNzIGluIGRpc3Rpbmd1aXNoaW5nIGJldHdlZW4gY2xhc3Nlcywgd2l0aCB2YWx1ZXMgY2xvc2VyIHRvIDEgc2lnbmlmeWluZyBleGNlbGxlbnQgcGVyZm9ybWFuY2UuCgojIyMgRGVjaXNpb24gVHJlZQoKTm93IHdlIHRlc3QgdGhlIERlY2lzaW9uIFRyZWUgbW9kZWwgd2l0aCBzYW1lIHRyYWluaW5nIGFuZCB0ZXN0aW5nIGRhdGFzZXQuIFRvIGVuc3VyZSBjb25zaXN0ZW5jeSwgdGhlIGBsb2dpc3RpY19yZWNpcGVgIHdhcyBhcHBsaWVkIGFzIGEgcHJlcHJvY2Vzc2luZyBzdGVwOgoKYGBge3J9CmRlY2lzaW9uX3RyZWVfbW9kZWwgPC0gZGVjaXNpb25fdHJlZShtb2RlID0gImNsYXNzaWZpY2F0aW9uIikgJT4lCiAgc2V0X2VuZ2luZSgicnBhcnQiKQoKZGVjaXNpb25fdHJlZV9maXQgPC0gd29ya2Zsb3coKSAlPiUKICBhZGRfcmVjaXBlKGxvZ2lzdGljX3JlY2lwZSkgJT4lCiAgYWRkX21vZGVsKGRlY2lzaW9uX3RyZWVfbW9kZWwpICU+JQogIGZpdChkYXRhID0gdHJhaW4pCgpycGFydC5wbG90OjpycGFydC5wbG90KGRlY2lzaW9uX3RyZWVfZml0JGZpdCRmaXQkZml0KQpgYGAKVGhlIERlY2lzaW9uIFRyZWUgbW9kZWwgaWRlbnRpZmllZCBgQ29udHJhY3RfTW9udGgudG8ubW9udGhgIGFzIHRoZSBrZXkgcHJlZGljdG9yIG9mIGNodXJuLCBwcmlvcml0aXppbmcgdGhlIGZsZXhpYmlsaXR5IG9mIHNob3J0LXRlcm0gY29udHJhY3RzIGFzIGEgc2lnbmlmaWNhbnQgcmlzayBmYWN0b3IuIFRoaXMgaW5mb3JtYXRpb24gaXMgYWxzbyBzZWVuIHdoZW4gd2UgYW5hbHl6ZWQgdGhlIHNlcnZpY2VzLiBJbiBjb250cmFzdCwgdGhlIExvZ2lzdGljIFJlZ3Jlc3Npb24gbW9kZWwgaGlnaGxpZ2h0ZWQgYFRlbnVyZWAsIHN1Z2dlc3RpbmcgdGhhdCBjdXN0b21lcnMgd2l0aCBsb25nZXIgcmVsYXRpb25zaGlwcyBhcmUgbGVzcyBsaWtlbHkgdG8gY2h1cm4uCgpUaGlzIGRpZmZlcmVuY2UgcmVmbGVjdHMgdGhlIG1vZGVscycgYXBwcm9hY2hlczogdGhlIERlY2lzaW9uIFRyZWUgZm9jdXNlcyBvbiBpbW1lZGlhdGUgc3BsaXRzIGluIHRoZSBkYXRhLCB3aGlsZSBMb2dpc3RpYyBSZWdyZXNzaW9uIGVtcGhhc2l6ZXMgbGluZWFyIHJlbGF0aW9uc2hpcHMuIFRvZ2V0aGVyLCB0aGVzZSBpbnNpZ2h0cyBzdWdnZXN0IHRhcmdldGluZyBtb250aC10by1tb250aCBjdXN0b21lcnMgYW5kIGZvc3RlcmluZyBsb25nLXRlcm0gbG95YWx0eSB0byByZWR1Y2UgY2h1cm4uCgpOb3cgd2UgZG8gaHlwZXJwYXJhbWV0ZXJzIHR1bmluZyBpbiBvdGhlciB0byBjaG9vc2UgdGhlIGJlc3QgbW9kZWwuIFdlIGNob29zZSBgY29zdF9jb21wbGV4aXR5YCwgYHRyZWVfZGVwdGhgLCBhbmQgYG1pbl9uYCB0byB0dW5lOgoKYGBge3J9CmRlY2lzaW9uX3RyZWVfbW9kZWwgPC0gZGVjaXNpb25fdHJlZSgKICBtb2RlID0gImNsYXNzaWZpY2F0aW9uIiwKICBjb3N0X2NvbXBsZXhpdHkgPSB0dW5lKCksCiAgdHJlZV9kZXB0aCA9IHR1bmUoKSwKICBtaW5fbiA9IHR1bmUoKQopICU+JQogIHNldF9lbmdpbmUoInJwYXJ0IikKCmRlY2lzaW9uX3RyZWVfaHlwZXJfZ3JpZCA8LSBncmlkX3JlZ3VsYXIoCiAgY29zdF9jb21wbGV4aXR5KCksCiAgdHJlZV9kZXB0aCgpLAogIG1pbl9uKCksCiAgbGV2ZWxzID0gNQopCgpzZXQuc2VlZCgxMjMpCmRlY2lzaW9uX3RyZWVfcmVzdWx0cyA8LSB0dW5lX2dyaWQoZGVjaXNpb25fdHJlZV9tb2RlbCwgbG9naXN0aWNfcmVjaXBlLCByZXNhbXBsZXMgPSBsb2dpc3RpY19rZm9sZHMsIGdyaWQgPSBkZWNpc2lvbl90cmVlX2h5cGVyX2dyaWQpCgpzaG93X2Jlc3QoZGVjaXNpb25fdHJlZV9yZXN1bHRzLCBtZXRyaWMgPSAicm9jX2F1YyIsIG4gPSA1KQpgYGAKClRoZSBgcm9jX2F1Y2Agc2NvcmUgb2YgdGhpcyBtb2RlbCBpcyBzbGlnaHRseSBsb3dlciB0aGFuIHRob3NlIGZyb20gTG9naXN0aWMgUmVncmVzc2lvbi4gV2UgY29udGludWUgdG8gY2hvb3NlIHRoZSBiZXN0IG1vZGVsIGZyb20gdGhlc2UgdHVuaW5nIHJlc3VsdHMgYW5kIGFwcGx5IGl0IG9uIGB0ZXN0YCBkYXRhc2V0OgoKYGBge3J9CmR0X2Jlc3RfbW9kZWwgPC0gc2VsZWN0X2Jlc3QoZGVjaXNpb25fdHJlZV9yZXN1bHRzLCBtZXRyaWMgPSAncm9jX2F1YycpCgpkdF9maW5hbF93ZiA8LSB3b3JrZmxvdygpICU+JQogIGFkZF9yZWNpcGUobG9naXN0aWNfcmVjaXBlKSAlPiUKICBhZGRfbW9kZWwoZGVjaXNpb25fdHJlZV9tb2RlbCkgJT4lCiAgZmluYWxpemVfd29ya2Zsb3coZHRfYmVzdF9tb2RlbCkKCmR0X2ZpbmFsX2ZpdCA8LSBkdF9maW5hbF93ZiAlPiUKICBmaXQoZGF0YSA9IHRyYWluKQoKZHRfZmluYWxfZml0ICU+JSAKICAgcHJlZGljdCh0ZXN0LCB0eXBlID0gInByb2IiKSAlPiUKICAgbXV0YXRlKHRydXRoID0gdGVzdCRTdGF0dXMpICU+JQogICByb2NfY3VydmUodHJ1dGgsIC5wcmVkX0N1cnJlbnQpICU+JQogICBhdXRvcGxvdCgpCgpwcmludChkdF9maW5hbF9maXQgJT4lCiAgICAgICAgcHJlZGljdCh0ZXN0KSAlPiUKICAgICAgICBiaW5kX2NvbHModGVzdCAlPiUgc2VsZWN0KFN0YXR1cykpICU+JQogICAgICAgIGNvbmZfbWF0KHRydXRoID0gU3RhdHVzLCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzKSkKCnByaW50KGR0X2ZpbmFsX2ZpdCAlPiUKICAgICAgICBwcmVkaWN0KHRlc3QsIHR5cGUgPSAicHJvYiIpICU+JQogICAgICAgIG11dGF0ZSh0cnV0aCA9IHRlc3QkU3RhdHVzKSAlPiUKICAgICAgICByb2NfYXVjKHRydXRoLCAucHJlZF9DdXJyZW50KQogICAgKQpgYGAKQ29tcGFyZWQgd2l0aCBMb2dpc3RpYyBSZWdyZXNzaW9uIG1vZGVsLCB0aGUgRGVjaXNpb24gVHJlZSBtb2RlbCBwZXJmb3JtcyBzbGlnaHRseSB3b3JzZS4gCgojIyMgUmFuZG9tIEZvcmVzdAoKTm93IHdlIGNyZWF0ZSBSYW5kb20gRm9yZXN0IG1vZGVsLCB3aGljaCBoYXMgYSBudW1iZXIgb2Ygc3ViIHRyZWVzLCB0byBzZWUgaWYgaXQgcGVyZm9ybXMgYmV0dGVyOgoKYGBge3J9CnJmX21vZCA8LSByYW5kX2ZvcmVzdChtb2RlID0gImNsYXNzaWZpY2F0aW9uIikgJT4lCiAgc2V0X2VuZ2luZSgicmFuZ2VyIikKCnJmX3Jlc3VsdHMgPC0gZml0X3Jlc2FtcGxlcyhyZl9tb2QsIGxvZ2lzdGljX3JlY2lwZSwgbG9naXN0aWNfa2ZvbGRzKQoKY29sbGVjdF9tZXRyaWNzKHJmX3Jlc3VsdHMpCmBgYAoKYGBge3IsIHdhcm5pbmc9RkFMU0V9CnJmX21vZCA8LSByYW5kX2ZvcmVzdCgKICBtb2RlID0gImNsYXNzaWZpY2F0aW9uIiwKICB0cmVlcyA9IHR1bmUoKSwKICBtdHJ5ID0gdHVuZSgpLAogIG1pbl9uID0gdHVuZSgpCiAgKSAlPiUKICBzZXRfZW5naW5lKCJyYW5nZXIiLCBpbXBvcnRhbmNlID0gImltcHVyaXR5IikKCnJmX2h5cGVyX2dyaWQgPC0gZ3JpZF9yZWd1bGFyKAogIHRyZWVzKHJhbmdlID0gYyg1MCwgODAwKSksCiAgbXRyeShyYW5nZSA9IGMoMiwgMzkpKSwKICBtaW5fbihyYW5nZSA9IGMoMSwgMjApKSwKICBsZXZlbHMgPSA1CikKCnNldC5zZWVkKDEyMykKcmZfcmVzdWx0cyA8LSB0dW5lX2dyaWQocmZfbW9kLCBsb2dpc3RpY19yZWNpcGUsIHJlc2FtcGxlcyA9IGxvZ2lzdGljX2tmb2xkcywgZ3JpZCA9IHJmX2h5cGVyX2dyaWQpCnNob3dfYmVzdChyZl9yZXN1bHRzLCBtZXRyaWMgPSAicm9jX2F1YyIpCmBgYAoKR2VuZXJhbGx5LCB0aGUgYHJvY19hdWNgIHNjb3JlcyBhY3Jvc3MgZGlmZmVyZW50IG1vZGVscyBjb252ZXJnZXMgdG8gMC44NCwgd2hpY2ggaXMgaGlnaGVyIHRoYW4gRGVjaXNpb24gVHJlZSBtb2RlbCBidXQgc3RpbGwgbG93ZXIgdGhhbiB0aGUgTG9naXN0aWMgUmVncmVzc2lvbnMgbW9kZWwuIE5vdyB3ZSBjaG9vc2UgdGhlIGJlc3QgbW9kZWwgZnJvbSB0aGVzZSBoeXBlcnBhcmFtZXRlcnMgdHVuaW5nIGFuZCBwcmVkaWN0IHdpdGggb3VyIGB0ZXN0YCBkYXRhc2V0OgoKYGBge3J9CnJmX2Jlc3RfaHlwZXJwYXJhbWV0ZXJzIDwtIHNlbGVjdF9iZXN0KHJmX3Jlc3VsdHMsIG1ldHJpYyA9ICJyb2NfYXVjIikKCmZpbmFsX3JmX3dmIDwtIHdvcmtmbG93KCkgJT4lCiAgYWRkX3JlY2lwZShsb2dpc3RpY19yZWNpcGUpICU+JQogIGFkZF9tb2RlbChyZl9tb2QpICU+JQogIGZpbmFsaXplX3dvcmtmbG93KHJmX2Jlc3RfaHlwZXJwYXJhbWV0ZXJzKQoKcmZfZmluYWxfZml0IDwtIGZpbmFsX3JmX3dmICU+JQogIGZpdChkYXRhID0gdHJhaW4pCgpyZl9maW5hbF9maXQgJT4lIAogICBwcmVkaWN0KHRlc3QsIHR5cGUgPSAicHJvYiIpICU+JQogICBtdXRhdGUodHJ1dGggPSB0ZXN0JFN0YXR1cykgJT4lCiAgIHJvY19jdXJ2ZSh0cnV0aCwgLnByZWRfQ3VycmVudCkgJT4lCiAgIGF1dG9wbG90KCkKCnByaW50KHJmX2ZpbmFsX2ZpdCAlPiUKICAgICAgICBwcmVkaWN0KHRlc3QpICU+JQogICAgICAgIGJpbmRfY29scyh0ZXN0ICU+JSBzZWxlY3QoU3RhdHVzKSkgJT4lCiAgICAgICAgY29uZl9tYXQodHJ1dGggPSBTdGF0dXMsIGVzdGltYXRlID0gLnByZWRfY2xhc3MpKQoKcHJpbnQocmZfZmluYWxfZml0ICU+JQogICAgICAgIHByZWRpY3QodGVzdCwgdHlwZSA9ICJwcm9iIikgJT4lCiAgICAgICAgbXV0YXRlKHRydXRoID0gdGVzdCRTdGF0dXMpICU+JQogICAgICAgIHJvY19hdWModHJ1dGgsIC5wcmVkX0N1cnJlbnQpCiAgICApCmBgYAoKRnJvbSB0aGUgY29uZnVzaW9uIG1hdHJpeCwgd2Ugb2JzZXJ2ZSB0aGF0IHRoZSBtb2RlbCBwZXJmb3JtcyBzaWduaWZpY2FudGx5IGJldHRlciBhdCBwcmVkaWN0aW5nIHRoZSBgQ3VycmVudGAgY2xhc3MgY29tcGFyZWQgdG8gdGhlIGBMZWZ0YCBjbGFzcywgd2l0aCBhIGhpZ2hlciBudW1iZXIgb2YgdHJ1ZSBwb3NpdGl2ZXMgYW5kIGZld2VyIGZhbHNlIG5lZ2F0aXZlcyBmb3IgYEN1cnJlbnRgLiBIb3dldmVyLCBpdHMgcGVyZm9ybWFuY2UgaW4gcHJlZGljdGluZyB0aGUgYExlZnRgIGNsYXNzIGlzIHdlYWtlciwgYXMgaW5kaWNhdGVkIGJ5IHRoZSBoaWdoZXIgbnVtYmVyIG9mIGZhbHNlIG5lZ2F0aXZlcy4KCiMjIFN1bW1hcnkgey50YWJzZXR9CgojIyMgS2V5IEZpbmRpbmdzCgojIyMjIEN1c3RvbWVyIERlbW9ncmFwaGljcyBhbmQgQ2h1cm4gUGF0dGVybnMKCi0gQ3VzdG9tZXJzIHdpdGhvdXQgcGFydG5lcnMgb3IgZGVwZW5kZW50cyBleGhpYml0ZWQgaGlnaGVyIGNodXJuIHJhdGVzLiBUaGlzIHRyZW5kIHN1Z2dlc3RzIHRoYXQgaW5kaXZpZHVhbCBjdXN0b21lcnMgbWF5IGZpbmQgdGhlIHNlcnZpY2UgY2hhcmdlcyBsZXNzIGp1c3RpZmlhYmxlLCByZXF1aXJpbmcgdGFpbG9yZWQgc3RyYXRlZ2llcyB0byBhZGRyZXNzIHRoZWlyIG5lZWRzLiAgCi0gU2VuaW9yIGNpdGl6ZW5zIGRpc3BsYXllZCBoaWdoZXIgcmV0ZW50aW9uLCBsaWtlbHkgdmFsdWluZyBzdGFibGUgYW5kIHByZWRpY3RhYmxlIHNlcnZpY2UgcGxhbnMuIEluIGNvbnRyYXN0LCBub24tc2VuaW9yIGNpdGl6ZW5zIHdlcmUgbW9yZSBwcmljZS1zZW5zaXRpdmUgYW5kIGV4aGliaXRlZCBjaHVybiBhY3Jvc3MgYWxsIGNvbnRyYWN0IHR5cGVzLgoKIyMjIyBDb250cmFjdCBUeXBlcyBhbmQgQ2h1cm4gQmVoYXZpb3IKCi0gTW9udGgtdG8tbW9udGggY29udHJhY3RzIGhhZCB0aGUgaGlnaGVzdCBjaHVybiByYXRlLCBwcmltYXJpbHkgZHJpdmVuIGJ5IHNob3J0LXRlbnVyZSBjdXN0b21lcnMgbGVhdmluZyB3aXRoaW4gMOKAkzIwIG1vbnRocy4gVGhlc2UgY3VzdG9tZXJzIG1heSBwcmlvcml0aXplIGZsZXhpYmlsaXR5IGFuZCBhcmUgbW9yZSBzdXNjZXB0aWJsZSB0byBwcmljaW5nIGRpc3NhdGlzZmFjdGlvbiBvciB1bm1ldCBzZXJ2aWNlIGV4cGVjdGF0aW9ucy4gIAotIExvbmdlciBjb250cmFjdHMgKG9uZS15ZWFyIGFuZCB0d28teWVhcikgcmV0YWluZWQgY3VzdG9tZXJzIGxvbmdlciwgd2l0aCBjaHVybiBvY2N1cnJpbmcgdG93YXJkIHRoZSBlbmQgb2YgdGhlaXIgdGVudXJlLgoKIyMjIyBQaG9uZSBhbmQgSW50ZXJuZXQgU2VydmljZXM6IENvbW1vbiBQYXR0ZXJucwoKLSBDaHVybiBwYXR0ZXJucyBhY3Jvc3MgUGhvbmUgYW5kIEludGVybmV0IFNlcnZpY2VzIGRlbW9uc3RyYXRlZCBjb25zaXN0ZW5jeSwgaW5kaWNhdGluZyB0aGF0IHVuaXZlcnNhbCBmYWN0b3JzIHN1Y2ggYXMgcHJpY2luZywgc2VydmljZSBxdWFsaXR5LCBhbmQgY29tcGV0aXRpb24gZHJpdmUgY3VzdG9tZXIgYmVoYXZpb3IuICAKLSBGb3IgYm90aCBzZXJ2aWNlcywgaGlnaGVyIG1vbnRobHkgY2hhcmdlcyB3ZXJlIHN0cm9uZ2x5IGFzc29jaWF0ZWQgd2l0aCBjaHVybiwgd2l0aCBjaHVybmVkIGN1c3RvbWVycyBwYXlpbmcgbWVkaWFuIGNoYXJnZXMgaGlnaGVyIHRoYW4gdGhvc2Ugd2hvIHN0YXllZC4KCiMjIyMgSW50ZXJuZXQgU2VydmljZSBhbmQgQWRkLW9ucwoKLSBDdXN0b21lcnMgd2l0aCAqKlN0cmVhbWluZ01vdmllcyoqIGFuZCAqKlN0cmVhbWluZ1RWKiogYWRkLW9ucyBmYWNlZCBzaWduaWZpY2FudGx5IGhpZ2hlciBjaGFyZ2VzIGFuZCBleGhpYml0ZWQgaGlnaGVyIGNodXJuIHJhdGVzLiBUaGlzIGhpZ2hsaWdodHMgYSBwb3RlbnRpYWwgbWlzYWxpZ25tZW50IGJldHdlZW4gcHJpY2luZyBhbmQgcGVyY2VpdmVkIHZhbHVlLCBwcm9tcHRpbmcgZGlzc2F0aXNmYWN0aW9uIGFtb25nIHRoZXNlIGN1c3RvbWVycy4gIAotIEN1c3RvbWVycyB1c2luZyBvbmx5IG9uZSBhZGQtb24sIHN1Y2ggYXMgKipPbmxpbmUgU2VjdXJpdHkqKiwgc2hvd2VkIHJlbGF0aXZlbHkgc3RhYmxlIGNoYXJnZXMgYW5kIGNodXJuIHJhdGVzLCBzdWdnZXN0aW5nIHRoYXQgc2ltcGxlciBzZXJ2aWNlIGJ1bmRsZXMgY291bGQgZW5oYW5jZSBzYXRpc2ZhY3Rpb24uCgojIyMjIERlcGVuZGVudHMgYW5kIFBhcnRuZXJzIEFuYWx5c2lzCgotIEN1c3RvbWVycyB3aXRob3V0IHBhcnRuZXJzIHBhaWQgaGlnaGVyIG1vbnRobHkgY2hhcmdlcyBhbmQgd2VyZSBtb3JlIGxpa2VseSB0byBjaHVybiwgZW1waGFzaXppbmcgdGhlIG5lZWQgZm9yIGVxdWl0YWJsZSBwcmljaW5nIGZvciBzb2xvIGN1c3RvbWVycy4gIAotIFRoZSBwcmVzZW5jZSBvZiBkZXBlbmRlbnRzIGhhZCBtaW5pbWFsIGltcGFjdCBvbiBtb250aGx5IGNoYXJnZXMgYnV0IG1heSBzbGlnaHRseSBpbmZsdWVuY2UgcmV0ZW50aW9uLCBhcyBjdXN0b21lcnMgd2l0aCBkZXBlbmRlbnRzIGRpc3BsYXllZCBhIG1hcmdpbmFsbHkgaGlnaGVyIGxpa2VsaWhvb2Qgb2Ygc3RheWluZy4KCiMjIyMgU2VuaW9yIENpdGl6ZW4gUmV0ZW50aW9uIFRyZW5kcwoKLSBTZW5pb3IgY2l0aXplbnMsIGVzcGVjaWFsbHkgdGhvc2Ugb24gbG9uZy10ZXJtIGNvbnRyYWN0cyB3aXRoIHNpbXBsZSBzZXJ2aWNlIHBsYW5zLCBkZW1vbnN0cmF0ZWQgc3Ryb25nIHJldGVudGlvbiwgbGlrZWx5IHZhbHVpbmcgcHJlZGljdGFiaWxpdHkgYW5kIGNvc3Qgc3RhYmlsaXR5LgoKIyMjIEJ1c2luZXNzIEltcGxpY2F0aW9ucyBhbmQgUmVjb21tZW5kYXRpb25zCgojIyMjIFJlYXNzZXNzIFByaWNpbmcgU3RyYXRlZ2llcwoKLSBDb25zaWRlciBpbnRyb2R1Y2luZyB0YXJnZXRlZCBkaXNjb3VudHMgb3IgdGFpbG9yZWQgcGxhbnMgZm9yIGluZGl2aWR1YWwgY3VzdG9tZXJzIHdpdGhvdXQgcGFydG5lcnMgdG8gYWRkcmVzcyB0aGVpciB1bmlxdWUgbmVlZHMgYW5kIGltcHJvdmUgcmV0ZW50aW9uLiAgCi0gUmV2aWV3IHRoZSBwcmljaW5nIHN0cnVjdHVyZSBmb3IgYWRkLW9ucyBsaWtlICoqU3RyZWFtaW5nTW92aWVzKiogYW5kICoqU3RyZWFtaW5nVFYqKiB0byBiZXR0ZXIgYWxpZ24gY2hhcmdlcyB3aXRoIGN1c3RvbWVyIGV4cGVjdGF0aW9ucy4KCiMjIyMgRW5oYW5jZSBWYWx1ZSBQcm9wb3NpdGlvbiBmb3IgTW9udGgtdG8tTW9udGggQ29udHJhY3RzCgotIEZvciBtb250aC10by1tb250aCBjdXN0b21lcnMsIGltcGxlbWVudCBsb3lhbHR5IGluY2VudGl2ZXMgb3IgYWRkZWQtdmFsdWUgZmVhdHVyZXMgdG8gcmVkdWNlIGVhcmx5IGNodXJuIGFuZCBidWlsZCBsb25nLXRlcm0gY3VzdG9tZXIgcmVsYXRpb25zaGlwcy4KCiMjIyMgRm9jdXMgb24gU2ltcGxpY2l0eSBhbmQgU3RhYmlsaXR5CgotIFNpbXBsaWZpZWQgc2VydmljZSBidW5kbGVzIHdpdGggYWZmb3JkYWJsZSBwcmljaW5nIGNvdWxkIGVuaGFuY2Ugc2F0aXNmYWN0aW9uIGFtb25nIHNvbG8gY3VzdG9tZXJzIGFuZCBzZW5pb3IgY2l0aXplbnMsIGVuc3VyaW5nIHRoYXQgdGhlaXIgbmVlZHMgYXJlIG1ldCB3aXRob3V0IGV4Y2Vzc2l2ZSBjb3N0cy4KCiMjIyMgUHJpb3JpdGl6ZSBDdXN0b21lciBFeHBlcmllbmNlCgotIEFkZHJlc3Mgbm9uLXByaWNlIGZhY3RvcnMgbGlrZSBzZXJ2aWNlIHF1YWxpdHksIHBlcmNlaXZlZCB2YWx1ZSwgYW5kIHJlc3BvbnNpdmVuZXNzIHRvIGN1c3RvbWVyIGNvbmNlcm5zIHRvIG1pdGlnYXRlIGNodXJuIGFjcm9zcyBhbGwgZGVtb2dyYXBoaWNzLiAgCi0gVXRpbGl6ZSBjdXN0b21lciBmZWVkYmFjayB0byBpZGVudGlmeSBnYXBzIGluIHNlcnZpY2UgYW5kIGltcGxlbWVudCB0YXJnZXRlZCBpbXByb3ZlbWVudHMuCgoKCiMjIyBNb2RlbHMgSW1wbGVtZW50ZWQKCiMjIyMgTG9naXN0aWMgUmVncmVzc2lvbgoKLSBUbyBhZGRyZXNzICoqbXVsdGljb2xsaW5lYXJpdHkqKiwgY3VzdG9tIHByb2Nlc3NpbmcgZnVuY3Rpb25zIHdlcmUgY3JlYXRlZCBmb3IgdGhlIGBJbnRlcm5ldFNlcnZpY2VgIGFuZCBgUGhvbmVTZXJ2aWNlYCBjb2x1bW5zLCBlbnN1cmluZyBlYWNoIGFkZC1vbiB3YXMgZ3JvdXBlZCBpbnRvIGEgc2luZ2xlIGNvbHVtbi4gIAotIEEgKipjbGFzc2lmaWNhdGlvbiBMb2dpc3RpYyBSZWdyZXNzaW9uIG1vZGVsKiogd2FzIGltcGxlbWVudGVkLCB0YXJnZXRpbmcgdGhlIGBTdGF0dXNgIHZhcmlhYmxlIChDdXJyZW50IG9yIExlZnQpLiBUaGUgbW9kZWwgaW5jbHVkZWQ6CiAgLSAqKjUtZm9sZCBjcm9zcy12YWxpZGF0aW9uKiouCiAgLSBBICoqcmVjaXBlKiogZm9yIGNyZWF0aW5nIGR1bW15IHZhcmlhYmxlcyBmb3IgY2F0ZWdvcmljYWwgY29sdW1ucyBhbmQgbm9ybWFsaXppbmcgbnVtZXJpY2FsIGNvbHVtbnMuCiAgLSBBICoqdHVuaW5nIGdyaWQqKiB0byBvcHRpbWl6ZSBwYXJhbWV0ZXJzLiAgCgotICoqUGVyZm9ybWFuY2UqKjoKICAtICoqUmVndWxhcml6YXRpb24gdmFsdWVzKiogcmFuZ2VkIGZyb20gMWUtMDcgdG8gMWUtMDMsIGVuYWJsaW5nIGEgZ29vZCBmaXQgZm9yIHRoZSB0cmFpbmluZyBkYXRhLgogIC0gVGhlIG1vZGVsIGFjaGlldmVkIGEgKipST0MgQVVDIHNjb3JlIG9mIDAuODQqKiBkdXJpbmcgY3Jvc3MtdmFsaWRhdGlvbiwgaW5kaWNhdGluZyBoaWdoIHNlbnNpdGl2aXR5IGFuZCBhIGxvdyBmYWxzZSBwb3NpdGl2ZSByYXRlLiAgCiAgLSAqKktleSBJbnNpZ2h0cyoqOgogICAgLSBgVGVudXJlYCBlbWVyZ2VkIGFzIHRoZSBtb3N0IGluZmx1ZW50aWFsIHByZWRpY3RvciBvZiBjaHVybiwgZm9sbG93ZWQgYnkgYE1vbnRobHlDaGFyZ2VzYC4gIAogICAgLSBUaGUgQVVDIHNjb3JlIG9mICoqMC44NSoqIHZhbGlkYXRlcyB0aGUgbW9kZWzigJlzIHN0cm9uZyBhYmlsaXR5IHRvIGRpc3Rpbmd1aXNoIGJldHdlZW4gY2xhc3Nlcywgd2l0aCBwZXJmb3JtYW5jZSBjbG9zZSB0byBleGNlbGxlbnQuICAKICAtIFRoZSBST0MgY3VydmUsIG5lYXJpbmcgdGhlIHRvcC1sZWZ0IGNvcm5lciwgaGlnaGxpZ2h0cyB0aGUgbW9kZWwncyBoaWdoIHNlbnNpdGl2aXR5IGF0IG9wdGltYWwgdGhyZXNob2xkcy4KCiMjIyMgRGVjaXNpb24gVHJlZQoKLSBUaGUgKipEZWNpc2lvbiBUcmVlIG1vZGVsKiogd2FzIHRlc3RlZCB3aXRoIHRoZSBzYW1lIHRyYWluaW5nIGFuZCB0ZXN0aW5nIGRhdGFzZXRzLiBUaGUgYGxvZ2lzdGljX3JlY2lwZWAgcHJlcHJvY2Vzc2luZyBzdGVwIHdhcyByZXVzZWQgdG8gbWFpbnRhaW4gY29uc2lzdGVuY3kuICAKLSAqKktleSBQcmVkaWN0b3IqKjoKICAtIFRoZSBtb2RlbCBpZGVudGlmaWVkIGBDb250cmFjdF9Nb250aC50by5tb250aGAgYXMgdGhlIG1vc3Qgc2lnbmlmaWNhbnQgcmlzayBmYWN0b3IgZm9yIGNodXJuLCBlbXBoYXNpemluZyB0aGUgZmxleGliaWxpdHkgb2Ygc2hvcnQtdGVybSBjb250cmFjdHMuICAKLSAqKlBlcmZvcm1hbmNlKio6CiAgLSBUaGUgUk9DIEFVQyBzY29yZSB3YXMgc2xpZ2h0bHkgbG93ZXIgdGhhbiBMb2dpc3RpYyBSZWdyZXNzaW9uLiAgCiAgLSBJbnNpZ2h0cyBpbnRvIG1vZGVsIGJlaGF2aW9yOgogICAgLSBXaGlsZSBMb2dpc3RpYyBSZWdyZXNzaW9uIGhpZ2hsaWdodGVkIHRoZSBpbXBvcnRhbmNlIG9mIGBUZW51cmVgLCB0aGUgRGVjaXNpb24gVHJlZSBmb2N1c2VkIG9uIHNwbGl0cyBsaWtlIGNvbnRyYWN0IHR5cGUuICAKICAgIC0gRGVjaXNpb24gVHJlZXMgZXhjZWwgaW4gaWRlbnRpZnlpbmcgaW1tZWRpYXRlIHBhdHRlcm5zIGJ1dCBtYXkgbGFjayB0aGUgZ2VuZXJhbGl6YXRpb24gY2FwYWJpbGl0eSBvZiBMb2dpc3RpYyBSZWdyZXNzaW9uLiAgCi0gKipDb21wYXJpc29uKio6CiAgLSBMb2dpc3RpYyBSZWdyZXNzaW9uIG91dHBlcmZvcm1lZCB0aGUgRGVjaXNpb24gVHJlZSwgcGFydGljdWxhcmx5IGluIGRpc3Rpbmd1aXNoaW5nIGJldHdlZW4gY2xhc3Nlcy4KCiMjIyMgUmFuZG9tIEZvcmVzdAoKLSBBICoqUmFuZG9tIEZvcmVzdCBtb2RlbCoqIHdhcyB0cmFpbmVkLCB3aXRoIGh5cGVycGFyYW1ldGVyIHR1bmluZyBhcHBsaWVkIHRvIG9wdGltaXplIHBlcmZvcm1hbmNlLiAgCi0gKipQZXJmb3JtYW5jZSoqOgogIC0gVGhlIG1vZGVsIGFjaGlldmVkIGFuICoqUk9DIEFVQyBzY29yZSBvZiAwLjg0KiosIGhpZ2hlciB0aGFuIHRoZSBEZWNpc2lvbiBUcmVlIGJ1dCBzbGlnaHRseSBsb3dlciB0aGFuIExvZ2lzdGljIFJlZ3Jlc3Npb24uICAKICAtICoqQ29uZnVzaW9uIE1hdHJpeCoqOgogICAgLSBUaGUgbW9kZWwgcGVyZm9ybWVkIGJldHRlciBhdCBwcmVkaWN0aW5nIHRoZSBgQ3VycmVudGAgY2xhc3MsIHdpdGggYSBoaWdoIG51bWJlciBvZiB0cnVlIHBvc2l0aXZlcyBhbmQgZmV3ZXIgZmFsc2UgbmVnYXRpdmVzLiAgCiAgICAtIEl0cyBwZXJmb3JtYW5jZSBpbiBwcmVkaWN0aW5nIHRoZSBgTGVmdGAgY2xhc3Mgd2FzIHdlYWtlciwgd2l0aCBtb3JlIGZhbHNlIG5lZ2F0aXZlcyBvYnNlcnZlZC4gIAoKIyMjIyBDb21wYXJhdGl2ZSBJbnNpZ2h0cwoKLSAqKkxvZ2lzdGljIFJlZ3Jlc3Npb24qKiBlbWVyZ2VkIGFzIHRoZSB0b3AtcGVyZm9ybWluZyBtb2RlbCwgYWNoaWV2aW5nIHRoZSBoaWdoZXN0IFJPQyBBVUMgc2NvcmUgYW5kIGRlbW9uc3RyYXRpbmcgc3VwZXJpb3Igc2Vuc2l0aXZpdHkgYW5kIGdlbmVyYWxpemF0aW9uLiAgCi0gKipEZWNpc2lvbiBUcmVlKiogb2ZmZXJlZCB2YWx1YWJsZSBpbnNpZ2h0cyBpbnRvIHNwbGl0cyBsaWtlIGBDb250cmFjdF9Nb250aC50by5tb250aGAgYnV0IGZlbGwgc2hvcnQgaW4gb3ZlcmFsbCBhY2N1cmFjeS4gIAotICoqUmFuZG9tIEZvcmVzdCoqIHByb3ZpZGVkIHJvYnVzdCBwcmVkaWN0aW9ucyBmb3IgdGhlIGBDdXJyZW50YCBjbGFzcyBidXQgc3RydWdnbGVkIHdpdGggYExlZnRgLCByZWZsZWN0aW5nIGl0cyBmb2N1cyBvbiBvdmVyYWxsIHByZWRpY3Rpb24gc3RhYmlsaXR5IHJhdGhlciB0aGFuIGNsYXNzLXNwZWNpZmljIHBlcmZvcm1hbmNlLgoKIyMjIENvbmNsdXNpb24KVGhpcyBhbmFseXNpcyB1bmRlcnNjb3JlcyB0aGUgaW1wb3J0YW5jZSBvZiB1bmRlcnN0YW5kaW5nIGN1c3RvbWVyIGJlaGF2aW9yIHRocm91Z2ggZGVtb2dyYXBoaWMgYW5kIHNlcnZpY2UtcmVsYXRlZCBsZW5zZXMuIFByaWNpbmcgYWRqdXN0bWVudHMsIHRhaWxvcmVkIHNlcnZpY2UgcGxhbnMsIGFuZCBhIGZvY3VzIG9uIGVuaGFuY2luZyBjdXN0b21lciBleHBlcmllbmNlIGNhbiBzaWduaWZpY2FudGx5IGltcHJvdmUgcmV0ZW50aW9uIHJhdGVzLiBCeSBhZGRyZXNzaW5nIHRoZSBzcGVjaWZpYyBuZWVkcyBvZiBkaXZlcnNlIGN1c3RvbWVyIGdyb3VwcywgdGhlIGNvbXBhbnkgY2FuIGJ1aWxkIHN0cm9uZ2VyIHJlbGF0aW9uc2hpcHMgYW5kIHJlZHVjZSBjaHVybiBlZmZlY3RpdmVseS4KCgoKCgoKCgo=