1 Introduction

In the current world of high power data analytics and breadth of borrower data available for utilization, ensuring efficient borrower population segmentation and classification is central to the operating processes of any effective lending institution. Financial institutions and credit agencies must prioritize creation of accurate and detailed borrower profiles to accurately predict borrower outcome to ensure timely and complete repayment of loans as well as prevent fraudulent borrowing that will result in a complete financial loss for the institution.

Through detailed customer segmentation lending institutions and credit agencies can streamline and improve their repayment rates and overall profit by:

  • Identifying borrower populations with greater repayment probability for targeted reduced APR loan marketing
  • Improving prediction accuracy to identify which customers are likely to go into default and provide preemptive mitigation strategies such as refinancing or adjusted repayment plans
  • Develop better live spending limit adjustments to support borrower needs and repayment capabilities
  • Improve fraud detection to reduce the number of fraudulent application approvals

Loan default data was obtained from Applied Analytics through Case Studies Using SAS and R, Deepti Gupta for analysis. Each of the below outlined variables as included in the data set serves as a categorical data point captured to help classify borrowers and predict their repayment ability.

Variables
Variable Name Variable Type Details
Loan Status Categorical Status of bank loan default (Default vs Current)
Checking Amount Numeric Amount in borrower’s checking account
Term Numeric Loan term in months
Credit Score Numeric Borrower’s credit score
Gender Categorical Borrower’s gender
Marital Status Categorical Borrower’s marital status
Employment Status Categorical Borrower’s employment status
Amount Numeric Loan amount
Saving Amount Numeric Ammount in borrower’s saving account
Age Numeric Duration of borrower’s employment in months
Number of Credit Accounts Numeric Number of credit accounts in borrower’s name
Car Loan Categorical If borrower holds a car loan
Personal Loan Categorical If borrower holds a personal loan
Home Loan Categorical If borrower holds a home loan
Education Loan Categorical If borrower holds an education loan
Any Loan Categorical A feature variable measuring how many of the 4 defined loans held by the borrower (personal, home, education, or car)
Total Debt Numeric A feature variable measuring the total number of borrower’s debts (4 defined loans and Number of Credit Accounts)
raw.loan <- read.csv("https://nlepera.github.io/sta551/HW04/data/BankLoanDefaultDataset.csv")
raw.loan$Any_loan <- rowSums(raw.loan[c(7:10)] == 1)
raw.loan$Total_debt <- as.numeric(raw.loan$Any_loan) + as.numeric(raw.loan$No_of_credit_acc)
raw.loan$Loan_status <- factor(raw.loan$Default, levels = c(0:1), labels = c("Current","Default"))
raw.loan$Car_loan <- factor(raw.loan$Car_loan, levels = c(0:1), labels = c("No", "Yes"))
raw.loan$Personal_loan <- factor(raw.loan$Personal_loan, levels = c(0:1), labels = c("No", "Yes"))
raw.loan$Home_loan <- factor(raw.loan$Home_loan, levels = c(0:1), labels = c("No", "Yes"))
raw.loan$Education_loan <- factor(raw.loan$Education_loan, levels = c(0:1), labels = c("No", "Yes"))

1.1 EDA & Feature Variable Generation

In order to more efficiently utilize this data for borrower analysis, the number of variables needs to be reduced in a process called dimension reduction. The dimensions of this data set were reduced by first combining the variables Car Loan, Personal Loan, Home Loan, Education Loan and Number of Credit Accounts to create a new variable entitled Total Debt that captures the total count of loans and credit accounts for each borrower. This effectively reduces five variables into a single variable, with minimal loss of information.

loan <- raw.loan[-1] %>% 
  relocate(Loan_status, .before = Checking_amount) %>%
  relocate(Amount, .after = Loan_status) %>% 
  relocate(Term, .after = Amount) %>% 
  relocate(Total_debt, .after = Term) %>% 
  relocate(No_of_credit_acc, .before = Car_loan)

kable(head(loan), caption = "A breif glimpse at the borrower data as collected" ) %>% 
  kable_styling(full_width = FALSE) %>% 
  scroll_box(width ="100%")
A breif glimpse at the borrower data as collected
Loan_status Amount Term Total_debt Checking_amount Credit_score Gender Marital_status No_of_credit_acc Car_loan Personal_loan Home_loan Education_loan Emp_status Saving_amount Emp_duration Age Any_loan
Current 1536 15 2 988 796 Female Single 1 Yes No No No employed 3455 12 38 1
Current 947 15 2 458 813 Female Single 1 Yes No No No employed 3600 25 36 1
Current 1678 14 2 158 756 Female Single 1 No Yes No No employed 3093 43 34 1
Default 1804 25 2 300 737 Female Single 1 No No No Yes employed 2449 0 29 1
Default 1184 24 2 63 662 Female Single 1 No No No Yes unemployed 2867 4 30 1
Current 475 20 3 1071 828 Male Married 2 Yes No No No employed 3282 12 32 1

The distribution of the Total Debt feature variable is included for review.

par(mfrow = c(1,2))
hist(x = loan$Total_debt, 
     prob = TRUE,
     main = "Distribution of 
Borrower Total Debt",
     xlab = "Total Number of Debt Accounts")

plot(x = loan$Loan_status,
     y = loan$Total_debt,
     col = c("skyblue", "darkred"),
     main = "Total Debt by Loan Status
(Current vs Default)",
     xlab = "Borrower Loan Default Status
(Current vs Default)",
     ylab = "Total Number of Borrower Debt Accounts")

2 K Means Cluster Analysis (Full Data)

A rudimentary cluster analysis of the numeric variables (Loan status, Amount, Term, Checking amount, Credit score, Saving amount, Employment Duration, Age, Total debt) provides an initial glimpse at the borrower profile data, and underscores the need for further dimension reduction prior to cluster analysis.

When assessing fo the optional cluster number utilizing the silhouette method, it demonstrated that the optimal number of clusters is two. Such a small number of clusters indicates a poor cluster model fit and lacks predictive power. As demonstrated by the significant overlap in the two clusters, a high level cluster analysis of all numeric borrower profile data does not provide proper borrower segmentation for identification and loan default prediction.

Utilizing such a poorly fit model for various business purposes could result in ineffective targeted marketing, poor pre-approval models resulting in higher back end costs reviewing and rejecting poorly fit applicants, and overall poor risk analysis in initial borrower requests.

loan.cluster <- loan[, c(1:6, 15:17)]
loan.cluster$Loan_status <- as.numeric(loan.cluster$Loan_status)
loan.cluster.group <- kmeans(x = loan.cluster, centers = 2)
loan.clustID <- loan.cluster.group$cluster


plot1 <- fviz_nbclust(loan.cluster, FUN = hcut, method = "wss")
plot2 <- fviz_nbclust(loan.cluster, FUN = hcut, method = "silhouette")


gridExtra::grid.arrange(plot1,plot2)

clusplot(loan.cluster,
         loan.clustID,
         lines = 0,
         shade = T,
         color = T,
         labels = 1,
         plotchar = F,
         span = T)

3 Heirarchal Data Clustering - Agglomerative

The following variables were analyzed to determine the borrower’s repayment profile:

  • Amount in borrower’s saving account Saving_amount
  • Borrower’s credit score Credit_score
  • Loan amount Amount
  • Status of bank loan default (Default vs Current) Loan_status

Utilizing a ‘bottom-up’ approach the data was split into clusters starting with single data points and successively merging clusters. This allows for tuning of the cluster sizes without pre-determining cluster count. Therefore, overall borrower profiles can be created from the data without pre-determining segments allowing in improved borrower profiling and prediction.

All data was run both raw and scaled. Raw data did not present significant skew, and scaled data presented over fitting issues resulting in a best fit of two clusters. No missing values were identified in the data set, therefore no imputation was required.

heirarch.loans <- loan[, c("Checking_amount", "Amount", "Emp_duration", "Loan_status")]
heirarch.distance <- dist(heirarch.loans, method = "euclidean")
hcluster1 <- hclust(heirarch.distance, method = "complete")

3.1 Determining Cluster Count

By examining the current borrower data to determine both the number and dimensions of the clusters, the borrower profile clusters can be created to better align with the current borrower profile base. This will allow for improved accuracy in borrower outcome prediction over the life cycle of the loan. For example, as a loan progresses, if a borrower originally profiled as a high likelihood of repayment suddenly has a dip in employment duration (i.e. laid off), accurate and timely reassessment of the borrower profile will allow for rapid revocation of any pending lending offers or credit increases, as well as flag the borrower for targeted repayment and or refinancing programs to reduce risk of loan delinquency

plot3 <- fviz_nbclust(heirarch.loans, FUN = hcut, method = "wss")
plot4 <- fviz_nbclust(heirarch.loans, FUN = hcut, method = "silhouette")
gridExtra::grid.arrange(plot3, plot4)

3.2 Final Cluster analysis

Optimal clustering was indicated as 3 clusters via the silhouette method and was selected as the pre-defined number of clusters for hierarchical agglomerate cluster analysis. Unlike the clustering as seen in the initial k-means cluster analysis, by utilizing hierarchical agglomerate clustering, the clusters are created before the best fit number of clusters is selected. This unsupervised algorithm allows for the clustering to be determined directly from the data itself rather than being pre-assigned based on assumptions.

This hierarchical clustering is visually represented in the below dendrogram. The clusters as selected are highlighted visually with different color square overlays. Inside the bounds of each box are the borrowers falling into that cluster. A scatterplot is also included to demonstrate the mapping of these clusters as assigned by the hclust() function.

hcluster2 <- hcluster1
hCluster_group <- cutree(hcluster2, k = 3)
heirarch.loans$hCluster_group = hCluster_group

plot(hcluster2, cex = 0.6, labels = FALSE, hang = -1, xlab = "", main = "Dendogram of Borrower Profile Clustering")
par(lwd = 3)
rect.hclust(hcluster1, k = 3, border = 2:9)

par(lwd=1)
clusplot(heirarch.loans,
         hCluster_group,
         lines = 0,
         shade = T,
         color = T,
         labels = 1,
         plotchar = F,
         span = T,
         main = "Cluster Plot of Raw Borrower Data")

4 Principal Component Analysis (PCA) & Reducing dimensions

To reduce, the dimensions must be scaled. All data transformed via scale() function as log scaling not possible with employment duration as some duration = 0 months. (“Checking_amount”, “Amount”, “Emp_duration”, “Loan_status”)

pca.loans.scale <- heirarch.loans[,-5]

pca.loans.scale$Checking_amount <- scale(pca.loans.scale$Checking_amount)
pca.loans.scale$Amount <- scale(pca.loans.scale$Amount)
pca.loans.scale$Emp_duration <- scale(pca.loans.scale$Emp_duration)
pca.loans.scale$Loan_status <- scale(as.numeric(pca.loans.scale$Loan_status))


pca.loans <- prcomp(pca.loans.scale, center = TRUE, scale = TRUE)


kable(round(pca.loans$rotation, 2), caption = "Factor loading of Borrower Profile PCA") %>% 
  kable_styling()
Factor loading of Borrower Profile PCA
PC1 PC2 PC3 PC4
Checking_amount -0.65 -0.01 0.31 -0.69
Amount 0.30 0.61 0.73 0.04
Emp_duration -0.21 0.79 -0.57 -0.07
Loan_status 0.67 -0.03 -0.20 -0.72
kable(round(summary(pca.loans)$importance, 3), caption = "Importance of each componant of Borrower Profile PCA") %>% 
  kable_styling()
Importance of each componant of Borrower Profile PCA
PC1 PC2 PC3 PC4
Standard deviation 1.243 1.009 0.949 0.733
Proportion of Variance 0.386 0.254 0.225 0.134
Cumulative Proportion 0.386 0.641 0.866 1.000

As demonstrated by the above PCA tables, the first three principal components account for 86.6% of the variation in the borrower profile data. The equation for each principal component is included below:

\[PC_1= -0.65[Checking.amount] + 0.30[Loan.amount] - 0.21[Employment.duration] + 0.67[Loan.status]\] \[PC_2= -0.01[Checking.amount] + 0.61[Loan.amount] + 0.79[Employment.duration] - 0.03[Loan.status]\] \[PC_3= 0.31[Checking.amount] + 0.73[Loan.amount] - 0.57[Employment.duration] - 0.20[Loan.status]\]

PC1 most impacted by checking amount and loan status with light impact from loan amount and employment duration, we will call this measure Immediate Financial Status Index PC2 most impacted by loan amount and employment duration with near 0 impact from checking amount and loan status, we will call this measure Repayment Security Index

PC3 will be dropped ass PC2 and PC3 have same largest influence variables (loan amount and employment duration), but PC2 has a greater proportion of variance than PC3.

4.1 Determne Best Number of Clusters

The same process was utilized to determine the best fit number of clusters for the PCA variables, again outlining 3 clusters as the best fit. Despite this best fit silhouette prediction, four clusters were utilized to reduce overall cluster overlap.

pca.cluster <- data.frame(Imed_fin_stat_PC1 = pca.loans$x[,1],
                          Repay_sec_PC2 = pca.loans$x[,2])

plot5 <- fviz_nbclust(pca.cluster, FUN = hcut, method = "wss")
plot6 <- fviz_nbclust(pca.cluster, FUN = hcut, method = "silhouette")
gridExtra::grid.arrange(plot5, plot6)

pca.distance <- dist(pca.cluster, method = "euclidean")
pca.clust <- hclust(pca.distance, method = "complete")
pca.cluster.group <- cutree(pca.clust, 4)

heirarch.loans$pcaCluster = as.character(pca.cluster.group)

clusplot(pca.cluster,
         pca.cluster.group,
         lines = 0,
         shade = T,
         color = T,
         labels = 1,
         xlab = "Immediate Financial Status Index",
         ylab = "Repayment Security Index",
         plotchar = F,
         span = T,
         main="Cluster Plot of PCA Components")

In reviewing the principal component variables are found to improve borrower profile predictive capabilities through dimension reduction. By combining four variables into two component analysis variables, the data can be more easily manipulated for borrower outcome predictions at the time of lending and increased targeted marketing for customers indicated to have a strong financial status index and repayment security index. This allows for borrower segmentation into four clusters:

  • High immediate financial status & High Repayment Security
  • High Immediate Financial Status & Low Repayment Security
  • Low Immediate Financial Status & High Repayment Security
  • Low Immediate Financial Status & Low Repayment Security

These four clusters that comprise the borrower profile can allow for a targeted customer based approach. Those with high financial status and high repayment security can be targeted for low interest rate loans with a higher down payment marketing to entice further borrowing from a low risk borrower. Where as those with low immediate financial status & high repayment security can be targeted for micro to medium sized loans with minimal to no down payments to entice further borrowing from a low risk borrower that may not have the funds for a traditional high down payment loan.

5 Outlier analysis

Outlier identification can provide further support in both effective targeted marketing, appropriate borrower application denials, and fraudulent borrower application identification. Outliers to the borrower profile groups should be manually reviewed to determine if the borrower is at risk for adjusting one of their principal component classifications (ex: moving from high immediate financial status to low, etc), if the borrower data is potentially fraudulent, or if the borrower data indicates a borrower that requires potential account termination and/or future borrower application denial for lack of repayment ability.

Additionally, properly identifying outliers allows for removal of outliers prior to PCA or hierarchical agglomerate clustering will result in increased borrower repayment profile classification and improved predictive capacity.

5.1 Local Outlier Factor (LOF) Score

A local outlier factor (LOF) score is calculated utilizing the below equation to compare a data point’s local reachability density (LRD) of the nearest k neighbors to point \(A_i\) for \(i = 1 , 2, ... , n\).

\[ LOF(A_i) = \frac{\frac{\sum_{i = A_j \in N_kA_i} {LRD_k(A_j)}}{||N_k(A_i)||}} {LRD_k(A_i)} \]

The LOF factor acts as an easily filterable scale variable to quickly identify outlier values. An LOF > 1 indicates a potential outlier, with the greater values for LOF indiciating more extreme outliers.

lof.pca.cluster <- lof(pca.cluster, minPts = 50)
pca.cluster$LOF <- lof.pca.cluster

pander::pander(summary(lof.pca.cluster), caption = "Summary statistics of LOF scores for Immediate Financial Status Index & Repayment Security Index")
Summary statistics of LOF scores for Immediate Financial Status Index & Repayment Security Index
Min. 1st Qu. Median Mean 3rd Qu. Max.
0.9571 0.9939 1.017 1.064 1.079 2.315

Based on the summary statistics for the calculated LOF scores for the PCA feature variables the outlier cutoff was selected as an LOF value of 1.8. This value was selected as the cutoff to ensure less than 1% of the dataset is identified as an outlier, leading to an outlier flagging of 1 in every 100 borrowers. Selecting an LOF value of 1.7 resulted in > 1% outlier flagging. This 1% outlier flagging will scale well to the lender or credit agency’s operational constraints until the need for hyperparameter (k) tuning is required.

plot(x = pca.cluster$Imed_fin_stat_PC1,
     y = pca.cluster$Repay_sec_PC2,
     pch = "x",
     cex = 0.5,
     xlab = "Immediate Financial Status Index",
     ylab = "Repayment Security Index",
     main="Outlier Identification Amongst PCA Components")
points(pca.cluster,
       cex = ((lof.pca.cluster - 1)*1.5),
       pch = 21,
       col = "hotpink")
text(pca.cluster[lof.pca.cluster > 1.8,],
     labels = round(lof.pca.cluster, 1)[lof.pca.cluster >1.8],
     pos = 2,
     cex = 0.6,
     col = "darkred")

kable(filter(pca.cluster, LOF > 1.8), caption = "listing of all LOF scores > 1.8") %>% 
  kable_styling()
listing of all LOF scores > 1.8
Imed_fin_stat_PC1 Repay_sec_PC2 LOF
-2.481105 -2.2764407 2.009484
3.396802 0.6592382 1.882003
-3.034711 -0.0338831 1.888966
-2.282020 -2.8414007 2.315474
2.926977 2.3990462 1.973623

6 Conclusions

Overall proper borrower profile segmentation will allow for improved loan default prediction models, improved identification of fraudulent pre-approval applications, and improved targeted marketing to drive up borrowing rates from borrowers with a high repayment profile. Overall this borrower segmentation and classification may also be used for predictive analysis regarding borrower pre-approval determinations. Overall borrower population segmentation remains a highly effective tool for managing and predicting overall loan outcomes.

7 References

Data source:

Applied Analytics through Case Studies Using SAS and R, Deepti Gupta by APress, ISBN - 978-1-4842-3525-6 Accessed via: https://pengdsci.github.io/datasets/LoanData2/BankLoanDefaultDataset.csv

LS0tDQp0aXRsZTogIkNhbiB5b3UgcGF5IG1lIGJhY2s/IDo8aW1nIHNyYz1cImh0dHBzOi8vbmxlcGVyYS5naXRodWIuaW8vc3RhNTUxL0hXMDEvaW1nL3Blbmd1aW5fY3V0ZS5wbmdcIiBzdHlsZT1cImZsb2F0OiByaWdodDsgd2lkdGg6IDEyJVwiLz4iDQpzdWJ0aXRsZTogIkxvYW4gRGVmYXVsdCBCb3Jyb3dlciBEYXRhIENsdXN0ZXJpbmcgJiBBbmFseXNpcyINCmF1dGhvcjoNCi0gbmFtZTogTmF0YWxpZSBMZVBlcmENCiAgYWZmaWxpYXRpb246IFdlc3QgQ2hlc3RlciBVbml2ZXJzaXR5IHwgU1RBNTUxIC0gSFcgMDQNCmRhdGU6ICIxMiBEZWMgMjAyNCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICB0b2NfZmxvYXQ6IHllcw0KICAgIHRvY19jb2xsYXBzZTogeWVzDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlDQogICAgdGhlbWU6IHJlYWRhYmxlDQogICAgZmlnX2FsaWduOiBjZW50ZXINCiAgICBkZl9wcmludDoga2FibGUNCi0tLQ0KDQpgYGB7Y3NzLCBlY2hvID0gRkFMU0V9DQpoMS50aXRsZSB7ICAvKiBUaXRsZSAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgb2YgdGhlIHJlcG9ydCB0aXRsZSAqLw0KICBmb250LXdlaWdodDpib2xkOw0KICBjb2xvcjogZGFya21hZ2VudGEgOw0KfQ0KaDEuc3VidGl0bGUgeyAgLyogVGl0bGUgLSBmb250IHNwZWNpZmljYXRpb25zIG9mIHRoZSByZXBvcnQgdGl0bGUgKi8NCiAgZm9udC13ZWlnaHQ6Ym9sZDsNCiAgY29sb3I6IGRhcmttYWdlbnRhIDsNCn0NCmg0LmF1dGhvciB7IC8qIEhlYWRlciA0IC0gZm9udCBzcGVjaWZpY2F0aW9ucyBmb3IgYXV0aG9ycyAgKi8NCiAgZm9udC1mYW1pbHk6IHN5c3RlbS11aTsNCiAgY29sb3I6IG5hdnk7DQp9DQpoNC5kYXRlIHsgLyogSGVhZGVyIDQgLSBmb250IHNwZWNpZmljYXRpb25zIGZvciB0aGUgZGF0ZSAgKi8NCiAgZm9udC1mYW1pbHk6IHN5c3RlbS11aTsNCiAgY29sb3I6IG5hdnk7DQp9DQpoMSB7IC8qIEhlYWRlciAxIC0gZm9udCBzcGVjaWZpY2F0aW9ucyBmb3IgbGV2ZWwgMSBzZWN0aW9uIHRpdGxlICAqLw0KICAgIGZvbnQtd2VpZ2h0OmJvbGQ7DQogICAgY29sb3I6IG5hdnk7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCn0NCmgyIHsgLyogSGVhZGVyIDIgLSBmb250IHNwZWNpZmljYXRpb25zIGZvciBsZXZlbCAyIHNlY3Rpb24gdGl0bGUgKi8NCiAgICBmb250LXdlaWdodDpib2xkOw0KICAgIGNvbG9yOiBuYXZ5Ow0KICAgIHRleHQtYWxpZ246IGxlZnQ7DQp9DQoNCmgzIHsgLyogSGVhZGVyIDMgLSBmb250IHNwZWNpZmljYXRpb25zIG9mIGxldmVsIDMgc2VjdGlvbiB0aXRsZSAgKi8NCiAgICBmb250LXdlaWdodDpib2xkOw0KICAgIGNvbG9yOiBuYXZ5Ow0KICAgIHRleHQtYWxpZ246IGxlZnQ7DQp9DQoNCmg0IHsgLyogSGVhZGVyIDQgLSBmb250IHNwZWNpZmljYXRpb25zIG9mIGxldmVsIDQgc2VjdGlvbiB0aXRsZSAgKi8NCiAgICBjb2xvcjogZGFya3JlZDsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KDQpib2R5IHsNCiAgYmFja2dyb3VuZC1jb2xvcjp3aGl0ZTsNCn0NCg0KLmhpZ2hsaWdodG1lIHsgDQogIGJhY2tncm91bmQtY29sb3I6eWVsbG93OyANCn0NCg0KcCB7IA0KICBiYWNrZ3JvdW5kLWNvbG9yOndoaXRlOyANCn0NCg0KaDUgew0KICBjb2xvcjogbmF2eTsNCn0NCg0KLmlmcmFtZSB7DQogIHRleHQtYWxpZ246IGNlbnRlcjsNCn0NCg0KYTpsaW5rIHsNCiAgY29sb3I6IGRhcmttYWdlbnRhOw0KfQ0KDQouZmlnbGFiZWwgew0KICB0ZXh0LWFsaWduOiBjZW50ZXI7DQogIGNvbG9yOiBkYXJrc2xhdGVncmF5Ow0KICBmb250LXdlaWdodDogYm9sZDsNCiAgZm9udC1zaXplOiAxODsNCn0NCg0KLnRkMSB7DQogIGZvbnQtd2VpZ2h0OiBib2xkOw0KfQ0KDQp0aCwgdGQgew0KICBib3JkZXItYm90dG9tOiAxcHggc29saWQgI2RkZDsNCiAgdGV4dC1hbGlnbjogbGVmdDsNCn0NCg0KdHI6aG92ZXIge2JhY2tncm91bmQtY29sb3I6IGNvcmFsO30NCmBgYA0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmlmICghcmVxdWlyZSgiZHBseXIiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoImRwbHlyIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoImRwbHlyIikNCn0NCg0KaWYgKCFyZXF1aXJlKCJwbHlyIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJwbHlyIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoInBseXIiKQ0KfQ0KDQppZiAoIXJlcXVpcmUoInN0cmluZ3IiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoInN0cmluZ3IiKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgic3RyaW5nciIpDQp9DQoNCmlmICghcmVxdWlyZSgicGxvdGx5IikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJwbG90bHkiKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgicGxvdGx5IikNCn0NCg0KaWYgKCFyZXF1aXJlKCJwYW5kb2MiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoInBhbmRvYyIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJwYW5kb2MiKQ0KfQ0KDQppZiAoIXJlcXVpcmUoImdyaWRFeHRyYSIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygiZ3JpZEV4dHJhIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoImdyaWRFeHRyYSIpDQp9DQoNCmlmICghcmVxdWlyZSgiZ3JpZCIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygiZ3JpZCIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJncmlkIikNCn0NCmlmICghcmVxdWlyZSgicmFzdGVyIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJyYXN0ZXIiKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgicmFzdGVyIikNCn0NCmlmICghcmVxdWlyZSgiZGJzY2FuIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJkYnNjYW4iKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgiZGJzY2FuIikNCn0NCmlmICghcmVxdWlyZSgicFJPQyIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygicFJPQyIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJwUk9DIikNCn0NCmlmICghcmVxdWlyZSgiZ2dyaWRnZXMiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoImdncmlkZ2VzIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoImdncmlkZ2VzIikNCn0NCmlmICghcmVxdWlyZSgia25pdHIiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoImtuaXRyIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoImtuaXRyIikNCn0NCmlmICghcmVxdWlyZSgiR0dhbGx5IikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJHR2FsbHkiKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgiR0dhbGx5IikNCn0NCmlmICghcmVxdWlyZSgiZ2dwbG90MiIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygiZ2dwbG90MiIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJnZ2xwb3QyIikNCn0NCmlmICghcmVxdWlyZSgiY2x1c3RlciIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygiY2x1c3RlciIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJjbHVzdGVyIikNCn0NCmlmICghcmVxdWlyZSgia2FibGVFeHRyYSIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygia2FibGVFeHRyYSIsIGRlcGVuZGVuY2llcyA9IFRSVUUpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJrYWJsZUV4dHJhIikNCn0NCmlmICghcmVxdWlyZSgiZm9yY2F0cyIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygiZm9yY2F0cyIsIGRlcGVuZGVuY2llcyA9IFRSVUUpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJmb3JjYXRzIikNCn0NCmlmICghcmVxdWlyZSgicnBhcnQiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoInJwYXJ0IiwgZGVwZW5kZW5jaWVzID0gVFJVRSkgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoInJwYXJ0IikNCn0NCmlmICghcmVxdWlyZSgicnBhcnQucGxvdCIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygicnBhcnQucGxvdCIsIGRlcGVuZGVuY2llcyA9IFRSVUUpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJycGFydC5wbG90IikNCn0NCmlmICghcmVxdWlyZSgibWV0YW4iKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoIm1ldGFuIiwgZGVwZW5kZW5jaWVzID0gVFJVRSkgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoIm1ldGFuIikNCn0NCiBpZiAoIXJlcXVpcmUoImZhY3RvZXh0cmEiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygiZmFjdG9leHRyYSIsIGRlcGVuZGVuY2llcyA9IFRSVUUpDQogICBsaWJyYXJ5KCJmYWN0b2V4dHJhIikNCiB9DQoNCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEZBTFNFLCAgIA0KICAgICAgICAgICAgICAgICAgICAgIHJlc3VsdCA9IFRSVUUsICAgDQogICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIGNvbW1lbnQgPSBOQSwNCiAgICAgICAgICAgICAgICAgICAgICBmaWcuYWxpZ24gPSAnY2VudGVyJykNCg0Kb3B0aW9ucyhEVC5vcHRpb25zID0gbGlzdChwYWdlTGVuZ3RoID0gNSwgc2Nyb2xsWCA9IFRSVUUpKQ0KYGBgDQoNCg0KIyBJbnRyb2R1Y3Rpb24NCg0KSW4gdGhlIGN1cnJlbnQgd29ybGQgb2YgaGlnaCBwb3dlciBkYXRhIGFuYWx5dGljcyBhbmQgYnJlYWR0aCBvZiBib3Jyb3dlciBkYXRhIGF2YWlsYWJsZSBmb3IgdXRpbGl6YXRpb24sIGVuc3VyaW5nIGVmZmljaWVudCBib3Jyb3dlciBwb3B1bGF0aW9uIHNlZ21lbnRhdGlvbiBhbmQgY2xhc3NpZmljYXRpb24gaXMgY2VudHJhbCB0byB0aGUgb3BlcmF0aW5nIHByb2Nlc3NlcyBvZiBhbnkgZWZmZWN0aXZlIGxlbmRpbmcgaW5zdGl0dXRpb24uIEZpbmFuY2lhbCBpbnN0aXR1dGlvbnMgYW5kIGNyZWRpdCBhZ2VuY2llcyBtdXN0IHByaW9yaXRpemUgY3JlYXRpb24gb2YgYWNjdXJhdGUgYW5kIGRldGFpbGVkIGJvcnJvd2VyIHByb2ZpbGVzIHRvIGFjY3VyYXRlbHkgcHJlZGljdCBib3Jyb3dlciBvdXRjb21lIHRvIGVuc3VyZSB0aW1lbHkgYW5kIGNvbXBsZXRlIHJlcGF5bWVudCBvZiBsb2FucyBhcyB3ZWxsIGFzIHByZXZlbnQgZnJhdWR1bGVudCBib3Jyb3dpbmcgdGhhdCB3aWxsIHJlc3VsdCBpbiBhIGNvbXBsZXRlIGZpbmFuY2lhbCBsb3NzIGZvciB0aGUgaW5zdGl0dXRpb24uICAgDQoNCg0KVGhyb3VnaCBkZXRhaWxlZCBjdXN0b21lciBzZWdtZW50YXRpb24gbGVuZGluZyBpbnN0aXR1dGlvbnMgYW5kIGNyZWRpdCBhZ2VuY2llcyBjYW4gc3RyZWFtbGluZSBhbmQgaW1wcm92ZSB0aGVpciByZXBheW1lbnQgcmF0ZXMgYW5kIG92ZXJhbGwgcHJvZml0IGJ5Og0KDQogIC0gSWRlbnRpZnlpbmcgYm9ycm93ZXIgcG9wdWxhdGlvbnMgd2l0aCBncmVhdGVyIHJlcGF5bWVudCBwcm9iYWJpbGl0eSBmb3IgdGFyZ2V0ZWQgcmVkdWNlZCBBUFIgbG9hbiBtYXJrZXRpbmcNCiAgLSBJbXByb3ZpbmcgcHJlZGljdGlvbiBhY2N1cmFjeSB0byBpZGVudGlmeSB3aGljaCBjdXN0b21lcnMgYXJlIGxpa2VseSB0byBnbyBpbnRvIGRlZmF1bHQgYW5kIHByb3ZpZGUgcHJlZW1wdGl2ZSBtaXRpZ2F0aW9uIHN0cmF0ZWdpZXMgc3VjaCBhcyByZWZpbmFuY2luZyBvciBhZGp1c3RlZCByZXBheW1lbnQgcGxhbnMNCiAgLSBEZXZlbG9wIGJldHRlciBsaXZlIHNwZW5kaW5nIGxpbWl0IGFkanVzdG1lbnRzIHRvIHN1cHBvcnQgYm9ycm93ZXIgbmVlZHMgYW5kIHJlcGF5bWVudCBjYXBhYmlsaXRpZXMNCiAgLSBJbXByb3ZlIGZyYXVkIGRldGVjdGlvbiB0byByZWR1Y2UgdGhlIG51bWJlciBvZiBmcmF1ZHVsZW50IGFwcGxpY2F0aW9uIGFwcHJvdmFscw0KDQpMb2FuIGRlZmF1bHQgZGF0YSB3YXMgb2J0YWluZWQgZnJvbSA8YSBocmVmPSJodHRwczovL3Blbmdkc2NpLmdpdGh1Yi5pby9kYXRhc2V0cy9Mb2FuRGF0YTIvQmFua0xvYW5EZWZhdWx0RGF0YXNldC5jc3YiPkFwcGxpZWQgQW5hbHl0aWNzIHRocm91Z2ggQ2FzZSBTdHVkaWVzIFVzaW5nIFNBUyBhbmQgUiwgRGVlcHRpIEd1cHRhPC9hPiBmb3IgYW5hbHlzaXMuIEVhY2ggb2YgdGhlIGJlbG93IG91dGxpbmVkIHZhcmlhYmxlcyBhcyBpbmNsdWRlZCBpbiB0aGUgZGF0YSBzZXQgc2VydmVzIGFzIGEgY2F0ZWdvcmljYWwgZGF0YSBwb2ludCBjYXB0dXJlZCB0byBoZWxwIGNsYXNzaWZ5IGJvcnJvd2VycyBhbmQgcHJlZGljdCB0aGVpciByZXBheW1lbnQgYWJpbGl0eS4gDQoNCjxoNT5WYXJpYWJsZXM8L2g1Pg0KDQo8dGFibGUgc3R5bGU9IndpZHRoOjEwMCUiPg0KPHRoZWFkPjx0cj4NCjx0aD5WYXJpYWJsZSBOYW1lPC90aD4NCjx0aD5WYXJpYWJsZSBUeXBlPC90aD4NCjx0aD5EZXRhaWxzPC90aD4NCjwvdHI+PC90aGVhZD4NCjx0cj48dGQgY2xhc3MgPSAidGQxIj5Mb2FuIFN0YXR1czwvdGQ+PHRkPkNhdGVnb3JpY2FsPC90ZD48dGQ+U3RhdHVzIG9mIGJhbmsgbG9hbiBkZWZhdWx0IChEZWZhdWx0IHZzIEN1cnJlbnQpPC90ZD48L3RyPg0KPHRyPjx0ZCBjbGFzcyA9ICJ0ZDEiPkNoZWNraW5nIEFtb3VudDwvdGQ+PHRkPk51bWVyaWM8L3RkPjx0ZD5BbW91bnQgaW4gYm9ycm93ZXIncyBjaGVja2luZyBhY2NvdW50PC90ZD4NCjx0cj48dGQgY2xhc3MgPSAidGQxIj5UZXJtPC90ZD48dGQ+TnVtZXJpYzwvdGQ+PHRkPkxvYW4gdGVybSBpbiBtb250aHM8L3RkPjwvdHI+DQo8dHI+PHRkIGNsYXNzID0gInRkMSI+Q3JlZGl0IFNjb3JlPC90ZD48dGQ+TnVtZXJpYzwvdGQ+PHRkPkJvcnJvd2VyJ3MgY3JlZGl0IHNjb3JlPC90ZD48L3RyPg0KPHRyPjx0ZCBjbGFzcyA9ICJ0ZDEiPkdlbmRlcjwvdGQ+PHRkPkNhdGVnb3JpY2FsPC90ZD48dGQ+Qm9ycm93ZXIncyBnZW5kZXI8L3RkPjwvdHI+DQo8dHI+PHRkIGNsYXNzID0gInRkMSI+TWFyaXRhbCBTdGF0dXM8L3RkPjx0ZD5DYXRlZ29yaWNhbDwvdGQ+PHRkPkJvcnJvd2VyJ3MgbWFyaXRhbCBzdGF0dXM8L3RkPjwvdHI+DQo8dHI+PHRkIGNsYXNzID0gInRkMSI+RW1wbG95bWVudCBTdGF0dXM8L3RkPjx0ZD5DYXRlZ29yaWNhbDwvdGQ+PHRkPkJvcnJvd2VyJ3MgZW1wbG95bWVudCBzdGF0dXM8L3RkPjwvdHI+DQo8dHI+PHRkIGNsYXNzID0gInRkMSI+QW1vdW50PC90ZD48dGQ+TnVtZXJpYzwvdGQ+PHRkPkxvYW4gYW1vdW50PC90ZD48L3RyPg0KPHRyPjx0ZCBjbGFzcyA9ICJ0ZDEiPlNhdmluZyBBbW91bnQ8L3RkPjx0ZD5OdW1lcmljPC90ZD48dGQ+QW1tb3VudCBpbiBib3Jyb3dlcidzIHNhdmluZyBhY2NvdW50PC90ZD48L3RyPg0KPHRyPjx0ZCBjbGFzcyA9ICJ0ZDEiPkFnZTwvdGQ+PHRkPk51bWVyaWM8L3RkPjx0ZD5EdXJhdGlvbiBvZiBib3Jyb3dlcidzIGVtcGxveW1lbnQgaW4gbW9udGhzPC90ZD48L3RyPg0KPHRyPjx0ZCBjbGFzcyA9ICJ0ZDEiPk51bWJlciBvZiBDcmVkaXQgQWNjb3VudHM8L3RkPjx0ZD5OdW1lcmljPC90ZD48dGQ+TnVtYmVyIG9mIGNyZWRpdCBhY2NvdW50cyBpbiBib3Jyb3dlcidzIG5hbWU8L3RkPjwvdHI+DQo8dHI+PHRkIGNsYXNzID0gInRkMSI+Q2FyIExvYW48L3RkPjx0ZD5DYXRlZ29yaWNhbDwvdGQ+PHRkPklmIGJvcnJvd2VyIGhvbGRzIGEgY2FyIGxvYW48L3RkPjwvdHI+DQo8dHI+PHRkIGNsYXNzID0gInRkMSI+UGVyc29uYWwgTG9hbjwvdGQ+PHRkPkNhdGVnb3JpY2FsPC90ZD48dGQ+SWYgYm9ycm93ZXIgaG9sZHMgYSBwZXJzb25hbCBsb2FuPC90ZD48L3RyPg0KPHRyPjx0ZCBjbGFzcyA9ICJ0ZDEiPkhvbWUgTG9hbjwvdGQ+PHRkPkNhdGVnb3JpY2FsPC90ZD48dGQ+SWYgYm9ycm93ZXIgaG9sZHMgYSBob21lIGxvYW48L3RkPjwvdHI+DQo8dHI+PHRkIGNsYXNzID0gInRkMSI+RWR1Y2F0aW9uIExvYW48L3RkPjx0ZD5DYXRlZ29yaWNhbDwvdGQ+PHRkPklmIGJvcnJvd2VyIGhvbGRzIGFuIGVkdWNhdGlvbiBsb2FuPC90ZD48L3RyPg0KPHRyPjx0ZCBjbGFzcyA9ICJ0ZDEiPkFueSBMb2FuPC90ZD48dGQ+Q2F0ZWdvcmljYWw8L3RkPjx0ZD5BIGZlYXR1cmUgdmFyaWFibGUgbWVhc3VyaW5nIGhvdyBtYW55IG9mIHRoZSA0IGRlZmluZWQgbG9hbnMgaGVsZCBieSB0aGUgYm9ycm93ZXIgKHBlcnNvbmFsLCBob21lLCBlZHVjYXRpb24sIG9yIGNhcik8L3RkPjwvdHI+DQo8dHI+PHRkIGNsYXNzID0gInRkMSI+VG90YWwgRGVidDwvdGQ+PHRkPk51bWVyaWM8L3RkPjx0ZD5BIGZlYXR1cmUgdmFyaWFibGUgbWVhc3VyaW5nIHRoZSB0b3RhbCBudW1iZXIgb2YgYm9ycm93ZXIncyBkZWJ0cyAoNCBkZWZpbmVkIGxvYW5zIGFuZCBOdW1iZXIgb2YgQ3JlZGl0IEFjY291bnRzKTwvdGQ+PC90cj4NCjwvdGFibGU+DQoNCmBgYHtyfQ0KcmF3LmxvYW4gPC0gcmVhZC5jc3YoImh0dHBzOi8vbmxlcGVyYS5naXRodWIuaW8vc3RhNTUxL0hXMDQvZGF0YS9CYW5rTG9hbkRlZmF1bHREYXRhc2V0LmNzdiIpDQpyYXcubG9hbiRBbnlfbG9hbiA8LSByb3dTdW1zKHJhdy5sb2FuW2MoNzoxMCldID09IDEpDQpyYXcubG9hbiRUb3RhbF9kZWJ0IDwtIGFzLm51bWVyaWMocmF3LmxvYW4kQW55X2xvYW4pICsgYXMubnVtZXJpYyhyYXcubG9hbiROb19vZl9jcmVkaXRfYWNjKQ0KcmF3LmxvYW4kTG9hbl9zdGF0dXMgPC0gZmFjdG9yKHJhdy5sb2FuJERlZmF1bHQsIGxldmVscyA9IGMoMDoxKSwgbGFiZWxzID0gYygiQ3VycmVudCIsIkRlZmF1bHQiKSkNCnJhdy5sb2FuJENhcl9sb2FuIDwtIGZhY3RvcihyYXcubG9hbiRDYXJfbG9hbiwgbGV2ZWxzID0gYygwOjEpLCBsYWJlbHMgPSBjKCJObyIsICJZZXMiKSkNCnJhdy5sb2FuJFBlcnNvbmFsX2xvYW4gPC0gZmFjdG9yKHJhdy5sb2FuJFBlcnNvbmFsX2xvYW4sIGxldmVscyA9IGMoMDoxKSwgbGFiZWxzID0gYygiTm8iLCAiWWVzIikpDQpyYXcubG9hbiRIb21lX2xvYW4gPC0gZmFjdG9yKHJhdy5sb2FuJEhvbWVfbG9hbiwgbGV2ZWxzID0gYygwOjEpLCBsYWJlbHMgPSBjKCJObyIsICJZZXMiKSkNCnJhdy5sb2FuJEVkdWNhdGlvbl9sb2FuIDwtIGZhY3RvcihyYXcubG9hbiRFZHVjYXRpb25fbG9hbiwgbGV2ZWxzID0gYygwOjEpLCBsYWJlbHMgPSBjKCJObyIsICJZZXMiKSkNCg0KYGBgDQoNCiMjIEVEQSAmIEZlYXR1cmUgVmFyaWFibGUgR2VuZXJhdGlvbg0KDQpJbiBvcmRlciB0byBtb3JlIGVmZmljaWVudGx5IHV0aWxpemUgdGhpcyBkYXRhIGZvciBib3Jyb3dlciBhbmFseXNpcywgdGhlIG51bWJlciBvZiB2YXJpYWJsZXMgbmVlZHMgdG8gYmUgcmVkdWNlZCBpbiBhIHByb2Nlc3MgY2FsbGVkIGRpbWVuc2lvbiByZWR1Y3Rpb24uICBUaGUgZGltZW5zaW9ucyBvZiB0aGlzIGRhdGEgc2V0IHdlcmUgcmVkdWNlZCBieSBmaXJzdCBjb21iaW5pbmcgdGhlIHZhcmlhYmxlcyBgQ2FyIExvYW5gLCBgUGVyc29uYWwgTG9hbmAsIGBIb21lIExvYW5gLCBgRWR1Y2F0aW9uIExvYW5gIGFuZCBgTnVtYmVyIG9mIENyZWRpdCBBY2NvdW50c2AgdG8gY3JlYXRlIGEgbmV3IHZhcmlhYmxlIGVudGl0bGVkIGBUb3RhbCBEZWJ0YCB0aGF0IGNhcHR1cmVzIHRoZSB0b3RhbCBjb3VudCBvZiBsb2FucyBhbmQgY3JlZGl0IGFjY291bnRzIGZvciBlYWNoIGJvcnJvd2VyLiAgVGhpcyBlZmZlY3RpdmVseSByZWR1Y2VzIGZpdmUgdmFyaWFibGVzIGludG8gYSBzaW5nbGUgdmFyaWFibGUsIHdpdGggbWluaW1hbCBsb3NzIG9mIGluZm9ybWF0aW9uLiANCg0KYGBge3J9DQpsb2FuIDwtIHJhdy5sb2FuWy0xXSAlPiUgDQogIHJlbG9jYXRlKExvYW5fc3RhdHVzLCAuYmVmb3JlID0gQ2hlY2tpbmdfYW1vdW50KSAlPiUNCiAgcmVsb2NhdGUoQW1vdW50LCAuYWZ0ZXIgPSBMb2FuX3N0YXR1cykgJT4lIA0KICByZWxvY2F0ZShUZXJtLCAuYWZ0ZXIgPSBBbW91bnQpICU+JSANCiAgcmVsb2NhdGUoVG90YWxfZGVidCwgLmFmdGVyID0gVGVybSkgJT4lIA0KICByZWxvY2F0ZShOb19vZl9jcmVkaXRfYWNjLCAuYmVmb3JlID0gQ2FyX2xvYW4pDQoNCmthYmxlKGhlYWQobG9hbiksIGNhcHRpb24gPSAiQSBicmVpZiBnbGltcHNlIGF0IHRoZSBib3Jyb3dlciBkYXRhIGFzIGNvbGxlY3RlZCIgKSAlPiUgDQogIGthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEZBTFNFKSAlPiUgDQogIHNjcm9sbF9ib3god2lkdGggPSIxMDAlIikNCmBgYA0KDQoNClRoZSBkaXN0cmlidXRpb24gb2YgdGhlIGBUb3RhbCBEZWJ0YCBmZWF0dXJlIHZhcmlhYmxlIGlzIGluY2x1ZGVkIGZvciByZXZpZXcuDQoNCmBgYHtyLCBmaWcud2lkdGg9OX0NCg0KcGFyKG1mcm93ID0gYygxLDIpKQ0KaGlzdCh4ID0gbG9hbiRUb3RhbF9kZWJ0LCANCiAgICAgcHJvYiA9IFRSVUUsDQogICAgIG1haW4gPSAiRGlzdHJpYnV0aW9uIG9mIA0KQm9ycm93ZXIgVG90YWwgRGVidCIsDQogICAgIHhsYWIgPSAiVG90YWwgTnVtYmVyIG9mIERlYnQgQWNjb3VudHMiKQ0KDQpwbG90KHggPSBsb2FuJExvYW5fc3RhdHVzLA0KICAgICB5ID0gbG9hbiRUb3RhbF9kZWJ0LA0KICAgICBjb2wgPSBjKCJza3libHVlIiwgImRhcmtyZWQiKSwNCiAgICAgbWFpbiA9ICJUb3RhbCBEZWJ0IGJ5IExvYW4gU3RhdHVzDQooQ3VycmVudCB2cyBEZWZhdWx0KSIsDQogICAgIHhsYWIgPSAiQm9ycm93ZXIgTG9hbiBEZWZhdWx0IFN0YXR1cw0KKEN1cnJlbnQgdnMgRGVmYXVsdCkiLA0KICAgICB5bGFiID0gIlRvdGFsIE51bWJlciBvZiBCb3Jyb3dlciBEZWJ0IEFjY291bnRzIikNCmBgYA0KDQoNCg0KDQojIEsgTWVhbnMgQ2x1c3RlciBBbmFseXNpcyAoRnVsbCBEYXRhKQ0KDQpBIHJ1ZGltZW50YXJ5IGNsdXN0ZXIgYW5hbHlzaXMgb2YgdGhlIG51bWVyaWMgdmFyaWFibGVzIChMb2FuIHN0YXR1cywgQW1vdW50LCBUZXJtLCBDaGVja2luZyBhbW91bnQsIENyZWRpdCBzY29yZSwgU2F2aW5nIGFtb3VudCwgRW1wbG95bWVudCBEdXJhdGlvbiwgQWdlLCBUb3RhbCBkZWJ0KSBwcm92aWRlcyBhbiBpbml0aWFsIGdsaW1wc2UgYXQgdGhlIGJvcnJvd2VyIHByb2ZpbGUgZGF0YSwgYW5kIHVuZGVyc2NvcmVzIHRoZSBuZWVkIGZvciBmdXJ0aGVyIGRpbWVuc2lvbiByZWR1Y3Rpb24gcHJpb3IgdG8gY2x1c3RlciBhbmFseXNpcy4gIA0KDQpXaGVuIGFzc2Vzc2luZyBmbyB0aGUgb3B0aW9uYWwgY2x1c3RlciBudW1iZXIgdXRpbGl6aW5nIHRoZSBzaWxob3VldHRlIG1ldGhvZCwgaXQgZGVtb25zdHJhdGVkIHRoYXQgdGhlIG9wdGltYWwgbnVtYmVyIG9mIGNsdXN0ZXJzIGlzIHR3by4gIFN1Y2ggYSBzbWFsbCBudW1iZXIgb2YgY2x1c3RlcnMgaW5kaWNhdGVzIGEgcG9vciBjbHVzdGVyIG1vZGVsIGZpdCBhbmQgbGFja3MgcHJlZGljdGl2ZSBwb3dlci4gIEFzIGRlbW9uc3RyYXRlZCBieSB0aGUgc2lnbmlmaWNhbnQgb3ZlcmxhcCBpbiB0aGUgdHdvIGNsdXN0ZXJzLCBhIGhpZ2ggbGV2ZWwgY2x1c3RlciBhbmFseXNpcyBvZiBhbGwgbnVtZXJpYyBib3Jyb3dlciBwcm9maWxlIGRhdGEgZG9lcyBub3QgcHJvdmlkZSBwcm9wZXIgYm9ycm93ZXIgc2VnbWVudGF0aW9uIGZvciBpZGVudGlmaWNhdGlvbiBhbmQgbG9hbiBkZWZhdWx0IHByZWRpY3Rpb24uDQoNClV0aWxpemluZyBzdWNoIGEgcG9vcmx5IGZpdCBtb2RlbCBmb3IgdmFyaW91cyBidXNpbmVzcyBwdXJwb3NlcyBjb3VsZCByZXN1bHQgaW4gaW5lZmZlY3RpdmUgdGFyZ2V0ZWQgbWFya2V0aW5nLCBwb29yIHByZS1hcHByb3ZhbCBtb2RlbHMgcmVzdWx0aW5nIGluIGhpZ2hlciBiYWNrIGVuZCBjb3N0cyByZXZpZXdpbmcgYW5kIHJlamVjdGluZyBwb29ybHkgZml0IGFwcGxpY2FudHMsIGFuZCBvdmVyYWxsIHBvb3IgcmlzayBhbmFseXNpcyBpbiBpbml0aWFsIGJvcnJvd2VyIHJlcXVlc3RzLiANCg0KDQpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD05fQ0KbG9hbi5jbHVzdGVyIDwtIGxvYW5bLCBjKDE6NiwgMTU6MTcpXQ0KbG9hbi5jbHVzdGVyJExvYW5fc3RhdHVzIDwtIGFzLm51bWVyaWMobG9hbi5jbHVzdGVyJExvYW5fc3RhdHVzKQ0KbG9hbi5jbHVzdGVyLmdyb3VwIDwtIGttZWFucyh4ID0gbG9hbi5jbHVzdGVyLCBjZW50ZXJzID0gMikNCmxvYW4uY2x1c3RJRCA8LSBsb2FuLmNsdXN0ZXIuZ3JvdXAkY2x1c3Rlcg0KDQoNCnBsb3QxIDwtIGZ2aXpfbmJjbHVzdChsb2FuLmNsdXN0ZXIsIEZVTiA9IGhjdXQsIG1ldGhvZCA9ICJ3c3MiKQ0KcGxvdDIgPC0gZnZpel9uYmNsdXN0KGxvYW4uY2x1c3RlciwgRlVOID0gaGN1dCwgbWV0aG9kID0gInNpbGhvdWV0dGUiKQ0KDQoNCmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKHBsb3QxLHBsb3QyKQ0KDQpjbHVzcGxvdChsb2FuLmNsdXN0ZXIsDQogICAgICAgICBsb2FuLmNsdXN0SUQsDQogICAgICAgICBsaW5lcyA9IDAsDQogICAgICAgICBzaGFkZSA9IFQsDQogICAgICAgICBjb2xvciA9IFQsDQogICAgICAgICBsYWJlbHMgPSAxLA0KICAgICAgICAgcGxvdGNoYXIgPSBGLA0KICAgICAgICAgc3BhbiA9IFQpDQpgYGANCg0KDQoNCiMgSGVpcmFyY2hhbCBEYXRhIENsdXN0ZXJpbmcgLSBBZ2dsb21lcmF0aXZlDQoNClRoZSBmb2xsb3dpbmcgdmFyaWFibGVzIHdlcmUgYW5hbHl6ZWQgdG8gZGV0ZXJtaW5lIHRoZSBib3Jyb3dlcidzIHJlcGF5bWVudCBwcm9maWxlOg0KDQogIC0gQW1vdW50IGluIGJvcnJvd2VyJ3Mgc2F2aW5nIGFjY291bnQgYFNhdmluZ19hbW91bnRgDQogIC0gQm9ycm93ZXIncyBjcmVkaXQgc2NvcmUgYENyZWRpdF9zY29yZWANCiAgLSBMb2FuIGFtb3VudCBgQW1vdW50YA0KICAtIFN0YXR1cyBvZiBiYW5rIGxvYW4gZGVmYXVsdCAoRGVmYXVsdCB2cyBDdXJyZW50KSBgTG9hbl9zdGF0dXNgDQogIA0KDQpVdGlsaXppbmcgYSAnYm90dG9tLXVwJyBhcHByb2FjaCB0aGUgZGF0YSB3YXMgc3BsaXQgaW50byBjbHVzdGVycyBzdGFydGluZyB3aXRoIHNpbmdsZSBkYXRhIHBvaW50cyBhbmQgc3VjY2Vzc2l2ZWx5IG1lcmdpbmcgY2x1c3RlcnMuIFRoaXMgYWxsb3dzIGZvciB0dW5pbmcgb2YgdGhlIGNsdXN0ZXIgc2l6ZXMgd2l0aG91dCBwcmUtZGV0ZXJtaW5pbmcgY2x1c3RlciBjb3VudC4gIFRoZXJlZm9yZSwgb3ZlcmFsbCBib3Jyb3dlciBwcm9maWxlcyBjYW4gYmUgY3JlYXRlZCBmcm9tIHRoZSBkYXRhIHdpdGhvdXQgcHJlLWRldGVybWluaW5nIHNlZ21lbnRzIGFsbG93aW5nIGluIGltcHJvdmVkIGJvcnJvd2VyIHByb2ZpbGluZyBhbmQgcHJlZGljdGlvbi4NCg0KDQpBbGwgZGF0YSB3YXMgcnVuIGJvdGggcmF3IGFuZCBzY2FsZWQuICBSYXcgZGF0YSBkaWQgbm90IHByZXNlbnQgc2lnbmlmaWNhbnQgc2tldywgYW5kIHNjYWxlZCBkYXRhIHByZXNlbnRlZCBvdmVyIGZpdHRpbmcgaXNzdWVzIHJlc3VsdGluZyBpbiBhIGJlc3QgZml0IG9mIHR3byBjbHVzdGVycy4gTm8gbWlzc2luZyB2YWx1ZXMgd2VyZSBpZGVudGlmaWVkIGluIHRoZSBkYXRhIHNldCwgdGhlcmVmb3JlIG5vIGltcHV0YXRpb24gd2FzIHJlcXVpcmVkLiANCg0KYGBge3J9DQpoZWlyYXJjaC5sb2FucyA8LSBsb2FuWywgYygiQ2hlY2tpbmdfYW1vdW50IiwgIkFtb3VudCIsICJFbXBfZHVyYXRpb24iLCAiTG9hbl9zdGF0dXMiKV0NCmhlaXJhcmNoLmRpc3RhbmNlIDwtIGRpc3QoaGVpcmFyY2gubG9hbnMsIG1ldGhvZCA9ICJldWNsaWRlYW4iKQ0KaGNsdXN0ZXIxIDwtIGhjbHVzdChoZWlyYXJjaC5kaXN0YW5jZSwgbWV0aG9kID0gImNvbXBsZXRlIikNCmBgYA0KDQojIyBEZXRlcm1pbmluZyBDbHVzdGVyIENvdW50DQoNCkJ5IGV4YW1pbmluZyB0aGUgY3VycmVudCBib3Jyb3dlciBkYXRhIHRvIGRldGVybWluZSBib3RoIHRoZSBudW1iZXIgYW5kIGRpbWVuc2lvbnMgb2YgdGhlIGNsdXN0ZXJzLCB0aGUgYm9ycm93ZXIgcHJvZmlsZSBjbHVzdGVycyBjYW4gYmUgY3JlYXRlZCB0byBiZXR0ZXIgYWxpZ24gd2l0aCB0aGUgY3VycmVudCBib3Jyb3dlciBwcm9maWxlIGJhc2UuICBUaGlzIHdpbGwgYWxsb3cgZm9yIGltcHJvdmVkIGFjY3VyYWN5IGluIGJvcnJvd2VyIG91dGNvbWUgcHJlZGljdGlvbiBvdmVyIHRoZSBsaWZlIGN5Y2xlIG9mIHRoZSBsb2FuLiAgRm9yIGV4YW1wbGUsIGFzIGEgbG9hbiBwcm9ncmVzc2VzLCBpZiBhIGJvcnJvd2VyIG9yaWdpbmFsbHkgcHJvZmlsZWQgYXMgYSBoaWdoIGxpa2VsaWhvb2Qgb2YgcmVwYXltZW50IHN1ZGRlbmx5IGhhcyBhIGRpcCBpbiBlbXBsb3ltZW50IGR1cmF0aW9uIChpLmUuIGxhaWQgb2ZmKSwgYWNjdXJhdGUgYW5kIHRpbWVseSByZWFzc2Vzc21lbnQgb2YgdGhlIGJvcnJvd2VyIHByb2ZpbGUgd2lsbCBhbGxvdyBmb3IgcmFwaWQgcmV2b2NhdGlvbiBvZiBhbnkgcGVuZGluZyBsZW5kaW5nIG9mZmVycyBvciBjcmVkaXQgaW5jcmVhc2VzLCBhcyB3ZWxsIGFzIGZsYWcgdGhlIGJvcnJvd2VyIGZvciB0YXJnZXRlZCByZXBheW1lbnQgYW5kIG9yIHJlZmluYW5jaW5nIHByb2dyYW1zIHRvIHJlZHVjZSByaXNrIG9mIGxvYW4gZGVsaW5xdWVuY3kgDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTksIGZpZy53aWR0aD03fQ0KcGxvdDMgPC0gZnZpel9uYmNsdXN0KGhlaXJhcmNoLmxvYW5zLCBGVU4gPSBoY3V0LCBtZXRob2QgPSAid3NzIikNCnBsb3Q0IDwtIGZ2aXpfbmJjbHVzdChoZWlyYXJjaC5sb2FucywgRlVOID0gaGN1dCwgbWV0aG9kID0gInNpbGhvdWV0dGUiKQ0KZ3JpZEV4dHJhOjpncmlkLmFycmFuZ2UocGxvdDMsIHBsb3Q0KQ0KYGBgDQoNCg0KDQojIyBGaW5hbCBDbHVzdGVyIGFuYWx5c2lzDQoNCk9wdGltYWwgY2x1c3RlcmluZyB3YXMgaW5kaWNhdGVkIGFzIDMgY2x1c3RlcnMgdmlhIHRoZSBzaWxob3VldHRlIG1ldGhvZCBhbmQgd2FzIHNlbGVjdGVkIGFzIHRoZSBwcmUtZGVmaW5lZCBudW1iZXIgb2YgY2x1c3RlcnMgZm9yIGhpZXJhcmNoaWNhbCBhZ2dsb21lcmF0ZSBjbHVzdGVyIGFuYWx5c2lzLiAgVW5saWtlIHRoZSBjbHVzdGVyaW5nIGFzIHNlZW4gaW4gdGhlIGluaXRpYWwgay1tZWFucyBjbHVzdGVyIGFuYWx5c2lzLCBieSB1dGlsaXppbmcgaGllcmFyY2hpY2FsIGFnZ2xvbWVyYXRlIGNsdXN0ZXJpbmcsIHRoZSBjbHVzdGVycyBhcmUgY3JlYXRlZCBiZWZvcmUgdGhlIGJlc3QgZml0IG51bWJlciBvZiBjbHVzdGVycyBpcyBzZWxlY3RlZC4gIFRoaXMgdW5zdXBlcnZpc2VkIGFsZ29yaXRobSBhbGxvd3MgZm9yIHRoZSBjbHVzdGVyaW5nIHRvIGJlIGRldGVybWluZWQgZGlyZWN0bHkgZnJvbSB0aGUgZGF0YSBpdHNlbGYgcmF0aGVyIHRoYW4gYmVpbmcgcHJlLWFzc2lnbmVkIGJhc2VkIG9uIGFzc3VtcHRpb25zLg0KDQpUaGlzIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIGlzIHZpc3VhbGx5IHJlcHJlc2VudGVkIGluIHRoZSBiZWxvdyBkZW5kcm9ncmFtLiAgVGhlIGNsdXN0ZXJzIGFzIHNlbGVjdGVkIGFyZSBoaWdobGlnaHRlZCB2aXN1YWxseSB3aXRoIGRpZmZlcmVudCBjb2xvciBzcXVhcmUgb3ZlcmxheXMuIEluc2lkZSB0aGUgYm91bmRzIG9mIGVhY2ggYm94IGFyZSB0aGUgYm9ycm93ZXJzIGZhbGxpbmcgaW50byB0aGF0IGNsdXN0ZXIuICBBIHNjYXR0ZXJwbG90IGlzIGFsc28gaW5jbHVkZWQgdG8gZGVtb25zdHJhdGUgdGhlIG1hcHBpbmcgb2YgdGhlc2UgY2x1c3RlcnMgYXMgYXNzaWduZWQgYnkgdGhlIGhjbHVzdCgpIGZ1bmN0aW9uLiANCg0KYGBge3IsIGZpZy53aWR0aD05fQ0KDQpoY2x1c3RlcjIgPC0gaGNsdXN0ZXIxDQpoQ2x1c3Rlcl9ncm91cCA8LSBjdXRyZWUoaGNsdXN0ZXIyLCBrID0gMykNCmhlaXJhcmNoLmxvYW5zJGhDbHVzdGVyX2dyb3VwID0gaENsdXN0ZXJfZ3JvdXANCg0KcGxvdChoY2x1c3RlcjIsIGNleCA9IDAuNiwgbGFiZWxzID0gRkFMU0UsIGhhbmcgPSAtMSwgeGxhYiA9ICIiLCBtYWluID0gIkRlbmRvZ3JhbSBvZiBCb3Jyb3dlciBQcm9maWxlIENsdXN0ZXJpbmciKQ0KcGFyKGx3ZCA9IDMpDQpyZWN0LmhjbHVzdChoY2x1c3RlcjEsIGsgPSAzLCBib3JkZXIgPSAyOjkpDQoNCnBhcihsd2Q9MSkNCmNsdXNwbG90KGhlaXJhcmNoLmxvYW5zLA0KICAgICAgICAgaENsdXN0ZXJfZ3JvdXAsDQogICAgICAgICBsaW5lcyA9IDAsDQogICAgICAgICBzaGFkZSA9IFQsDQogICAgICAgICBjb2xvciA9IFQsDQogICAgICAgICBsYWJlbHMgPSAxLA0KICAgICAgICAgcGxvdGNoYXIgPSBGLA0KICAgICAgICAgc3BhbiA9IFQsDQogICAgICAgICBtYWluID0gIkNsdXN0ZXIgUGxvdCBvZiBSYXcgQm9ycm93ZXIgRGF0YSIpDQoNCg0KYGBgDQoNCg0KDQoNCiMgUHJpbmNpcGFsIENvbXBvbmVudCBBbmFseXNpcyAoUENBKSAmIFJlZHVjaW5nIGRpbWVuc2lvbnMNCg0KVG8gcmVkdWNlLCB0aGUgZGltZW5zaW9ucyBtdXN0IGJlIHNjYWxlZC4gQWxsIGRhdGEgdHJhbnNmb3JtZWQgdmlhIGBzY2FsZSgpYCBmdW5jdGlvbiBhcyBsb2cgc2NhbGluZyBub3QgcG9zc2libGUgd2l0aCBlbXBsb3ltZW50IGR1cmF0aW9uIGFzIHNvbWUgZHVyYXRpb24gPSAwIG1vbnRocy4gKCJDaGVja2luZ19hbW91bnQiLCAiQW1vdW50IiwgIkVtcF9kdXJhdGlvbiIsICJMb2FuX3N0YXR1cyIpDQoNCmBgYHtyfQ0KcGNhLmxvYW5zLnNjYWxlIDwtIGhlaXJhcmNoLmxvYW5zWywtNV0NCg0KcGNhLmxvYW5zLnNjYWxlJENoZWNraW5nX2Ftb3VudCA8LSBzY2FsZShwY2EubG9hbnMuc2NhbGUkQ2hlY2tpbmdfYW1vdW50KQ0KcGNhLmxvYW5zLnNjYWxlJEFtb3VudCA8LSBzY2FsZShwY2EubG9hbnMuc2NhbGUkQW1vdW50KQ0KcGNhLmxvYW5zLnNjYWxlJEVtcF9kdXJhdGlvbiA8LSBzY2FsZShwY2EubG9hbnMuc2NhbGUkRW1wX2R1cmF0aW9uKQ0KcGNhLmxvYW5zLnNjYWxlJExvYW5fc3RhdHVzIDwtIHNjYWxlKGFzLm51bWVyaWMocGNhLmxvYW5zLnNjYWxlJExvYW5fc3RhdHVzKSkNCg0KDQpwY2EubG9hbnMgPC0gcHJjb21wKHBjYS5sb2Fucy5zY2FsZSwgY2VudGVyID0gVFJVRSwgc2NhbGUgPSBUUlVFKQ0KDQoNCmthYmxlKHJvdW5kKHBjYS5sb2FucyRyb3RhdGlvbiwgMiksIGNhcHRpb24gPSAiRmFjdG9yIGxvYWRpbmcgb2YgQm9ycm93ZXIgUHJvZmlsZSBQQ0EiKSAlPiUgDQogIGthYmxlX3N0eWxpbmcoKQ0Ka2FibGUocm91bmQoc3VtbWFyeShwY2EubG9hbnMpJGltcG9ydGFuY2UsIDMpLCBjYXB0aW9uID0gIkltcG9ydGFuY2Ugb2YgZWFjaCBjb21wb25hbnQgb2YgQm9ycm93ZXIgUHJvZmlsZSBQQ0EiKSAlPiUgDQogIGthYmxlX3N0eWxpbmcoKQ0KYGBgDQoNCg0KQXMgZGVtb25zdHJhdGVkIGJ5IHRoZSBhYm92ZSBQQ0EgdGFibGVzLCB0aGUgZmlyc3QgdGhyZWUgcHJpbmNpcGFsIGNvbXBvbmVudHMgYWNjb3VudCBmb3IgODYuNiUgb2YgdGhlIHZhcmlhdGlvbiBpbiB0aGUgYm9ycm93ZXIgcHJvZmlsZSBkYXRhLiAgVGhlIGVxdWF0aW9uIGZvciBlYWNoIHByaW5jaXBhbCBjb21wb25lbnQgaXMgaW5jbHVkZWQgYmVsb3c6IA0KDQokJFBDXzE9IC0wLjY1W0NoZWNraW5nLmFtb3VudF0gKyAwLjMwW0xvYW4uYW1vdW50XSAtIDAuMjFbRW1wbG95bWVudC5kdXJhdGlvbl0gKyAwLjY3W0xvYW4uc3RhdHVzXSQkDQokJFBDXzI9IC0wLjAxW0NoZWNraW5nLmFtb3VudF0gKyAwLjYxW0xvYW4uYW1vdW50XSArIDAuNzlbRW1wbG95bWVudC5kdXJhdGlvbl0gLSAwLjAzW0xvYW4uc3RhdHVzXSQkDQokJFBDXzM9IDAuMzFbQ2hlY2tpbmcuYW1vdW50XSArIDAuNzNbTG9hbi5hbW91bnRdIC0gMC41N1tFbXBsb3ltZW50LmR1cmF0aW9uXSAtIDAuMjBbTG9hbi5zdGF0dXNdJCQNCg0KUEMxIG1vc3QgaW1wYWN0ZWQgYnkgY2hlY2tpbmcgYW1vdW50IGFuZCBsb2FuIHN0YXR1cyB3aXRoIGxpZ2h0IGltcGFjdCBmcm9tIGxvYW4gYW1vdW50IGFuZCBlbXBsb3ltZW50IGR1cmF0aW9uLCB3ZSB3aWxsIGNhbGwgdGhpcyBtZWFzdXJlIEltbWVkaWF0ZSBGaW5hbmNpYWwgU3RhdHVzIEluZGV4DQpQQzIgbW9zdCBpbXBhY3RlZCBieSBsb2FuIGFtb3VudCBhbmQgZW1wbG95bWVudCBkdXJhdGlvbiB3aXRoIG5lYXIgMCBpbXBhY3QgZnJvbSBjaGVja2luZyBhbW91bnQgYW5kIGxvYW4gc3RhdHVzLCB3ZSB3aWxsIGNhbGwgdGhpcyBtZWFzdXJlIFJlcGF5bWVudCBTZWN1cml0eSBJbmRleCANCg0KUEMzIHdpbGwgYmUgZHJvcHBlZCBhc3MgUEMyIGFuZCBQQzMgaGF2ZSBzYW1lIGxhcmdlc3QgaW5mbHVlbmNlIHZhcmlhYmxlcyAobG9hbiBhbW91bnQgYW5kIGVtcGxveW1lbnQgZHVyYXRpb24pLCBidXQgUEMyIGhhcyBhIGdyZWF0ZXIgcHJvcG9ydGlvbiBvZiB2YXJpYW5jZSB0aGFuIFBDMy4gDQoNCiMjIERldGVybW5lIEJlc3QgTnVtYmVyIG9mIENsdXN0ZXJzDQoNClRoZSBzYW1lIHByb2Nlc3Mgd2FzIHV0aWxpemVkIHRvIGRldGVybWluZSB0aGUgYmVzdCBmaXQgbnVtYmVyIG9mIGNsdXN0ZXJzIGZvciB0aGUgUENBIHZhcmlhYmxlcywgYWdhaW4gb3V0bGluaW5nIDMgY2x1c3RlcnMgYXMgdGhlIGJlc3QgZml0LiAgRGVzcGl0ZSB0aGlzIGJlc3QgZml0IHNpbGhvdWV0dGUgcHJlZGljdGlvbiwgZm91ciBjbHVzdGVycyB3ZXJlIHV0aWxpemVkIHRvIHJlZHVjZSBvdmVyYWxsIGNsdXN0ZXIgb3ZlcmxhcC4gDQoNCmBgYHtyfQ0KDQpwY2EuY2x1c3RlciA8LSBkYXRhLmZyYW1lKEltZWRfZmluX3N0YXRfUEMxID0gcGNhLmxvYW5zJHhbLDFdLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBSZXBheV9zZWNfUEMyID0gcGNhLmxvYW5zJHhbLDJdKQ0KDQpwbG90NSA8LSBmdml6X25iY2x1c3QocGNhLmNsdXN0ZXIsIEZVTiA9IGhjdXQsIG1ldGhvZCA9ICJ3c3MiKQ0KcGxvdDYgPC0gZnZpel9uYmNsdXN0KHBjYS5jbHVzdGVyLCBGVU4gPSBoY3V0LCBtZXRob2QgPSAic2lsaG91ZXR0ZSIpDQpncmlkRXh0cmE6OmdyaWQuYXJyYW5nZShwbG90NSwgcGxvdDYpDQoNCmBgYA0KDQpgYGB7cn0NCg0KDQpwY2EuZGlzdGFuY2UgPC0gZGlzdChwY2EuY2x1c3RlciwgbWV0aG9kID0gImV1Y2xpZGVhbiIpDQpwY2EuY2x1c3QgPC0gaGNsdXN0KHBjYS5kaXN0YW5jZSwgbWV0aG9kID0gImNvbXBsZXRlIikNCnBjYS5jbHVzdGVyLmdyb3VwIDwtIGN1dHJlZShwY2EuY2x1c3QsIDQpDQoNCmhlaXJhcmNoLmxvYW5zJHBjYUNsdXN0ZXIgPSBhcy5jaGFyYWN0ZXIocGNhLmNsdXN0ZXIuZ3JvdXApDQoNCmNsdXNwbG90KHBjYS5jbHVzdGVyLA0KICAgICAgICAgcGNhLmNsdXN0ZXIuZ3JvdXAsDQogICAgICAgICBsaW5lcyA9IDAsDQogICAgICAgICBzaGFkZSA9IFQsDQogICAgICAgICBjb2xvciA9IFQsDQogICAgICAgICBsYWJlbHMgPSAxLA0KICAgICAgICAgeGxhYiA9ICJJbW1lZGlhdGUgRmluYW5jaWFsIFN0YXR1cyBJbmRleCIsDQogICAgICAgICB5bGFiID0gIlJlcGF5bWVudCBTZWN1cml0eSBJbmRleCIsDQogICAgICAgICBwbG90Y2hhciA9IEYsDQogICAgICAgICBzcGFuID0gVCwNCiAgICAgICAgIG1haW49IkNsdXN0ZXIgUGxvdCBvZiBQQ0EgQ29tcG9uZW50cyIpDQoNCmBgYA0KDQpJbiByZXZpZXdpbmcgdGhlIHByaW5jaXBhbCBjb21wb25lbnQgdmFyaWFibGVzIGFyZSBmb3VuZCB0byBpbXByb3ZlIGJvcnJvd2VyIHByb2ZpbGUgcHJlZGljdGl2ZSBjYXBhYmlsaXRpZXMgdGhyb3VnaCBkaW1lbnNpb24gcmVkdWN0aW9uLiAgQnkgY29tYmluaW5nIGZvdXIgdmFyaWFibGVzIGludG8gdHdvIGNvbXBvbmVudCBhbmFseXNpcyB2YXJpYWJsZXMsIHRoZSBkYXRhIGNhbiBiZSBtb3JlIGVhc2lseSBtYW5pcHVsYXRlZCBmb3IgYm9ycm93ZXIgb3V0Y29tZSBwcmVkaWN0aW9ucyBhdCB0aGUgdGltZSBvZiBsZW5kaW5nIGFuZCBpbmNyZWFzZWQgdGFyZ2V0ZWQgbWFya2V0aW5nIGZvciBjdXN0b21lcnMgaW5kaWNhdGVkIHRvIGhhdmUgYSBzdHJvbmcgZmluYW5jaWFsIHN0YXR1cyBpbmRleCBhbmQgcmVwYXltZW50IHNlY3VyaXR5IGluZGV4LiAgIFRoaXMgYWxsb3dzIGZvciBib3Jyb3dlciBzZWdtZW50YXRpb24gaW50byBmb3VyIGNsdXN0ZXJzOg0KDQogIC0gSGlnaCBpbW1lZGlhdGUgZmluYW5jaWFsIHN0YXR1cyAmIEhpZ2ggUmVwYXltZW50IFNlY3VyaXR5DQogIC0gSGlnaCBJbW1lZGlhdGUgRmluYW5jaWFsIFN0YXR1cyAmIExvdyBSZXBheW1lbnQgU2VjdXJpdHkNCiAgLSBMb3cgSW1tZWRpYXRlIEZpbmFuY2lhbCBTdGF0dXMgJiBIaWdoIFJlcGF5bWVudCBTZWN1cml0eQ0KICAtIExvdyBJbW1lZGlhdGUgRmluYW5jaWFsIFN0YXR1cyAmIExvdyBSZXBheW1lbnQgU2VjdXJpdHkNCiAgDQoNClRoZXNlIGZvdXIgY2x1c3RlcnMgdGhhdCBjb21wcmlzZSB0aGUgYm9ycm93ZXIgcHJvZmlsZSBjYW4gYWxsb3cgZm9yIGEgdGFyZ2V0ZWQgY3VzdG9tZXIgYmFzZWQgYXBwcm9hY2guICBUaG9zZSB3aXRoIGhpZ2ggZmluYW5jaWFsIHN0YXR1cyBhbmQgaGlnaCByZXBheW1lbnQgc2VjdXJpdHkgY2FuIGJlIHRhcmdldGVkIGZvciBsb3cgaW50ZXJlc3QgcmF0ZSBsb2FucyB3aXRoIGEgaGlnaGVyIGRvd24gcGF5bWVudCBtYXJrZXRpbmcgdG8gZW50aWNlIGZ1cnRoZXIgYm9ycm93aW5nIGZyb20gYSBsb3cgcmlzayBib3Jyb3dlci4gIFdoZXJlIGFzIHRob3NlIHdpdGggbG93IGltbWVkaWF0ZSBmaW5hbmNpYWwgc3RhdHVzICYgaGlnaCByZXBheW1lbnQgc2VjdXJpdHkgY2FuIGJlIHRhcmdldGVkIGZvciBtaWNybyB0byBtZWRpdW0gc2l6ZWQgbG9hbnMgd2l0aCBtaW5pbWFsIHRvIG5vIGRvd24gcGF5bWVudHMgdG8gZW50aWNlIGZ1cnRoZXIgYm9ycm93aW5nIGZyb20gYSBsb3cgcmlzayBib3Jyb3dlciB0aGF0IG1heSBub3QgaGF2ZSB0aGUgZnVuZHMgZm9yIGEgdHJhZGl0aW9uYWwgaGlnaCBkb3duIHBheW1lbnQgbG9hbi4gDQoNCiMgT3V0bGllciBhbmFseXNpcw0KDQpPdXRsaWVyIGlkZW50aWZpY2F0aW9uIGNhbiBwcm92aWRlIGZ1cnRoZXIgc3VwcG9ydCBpbiBib3RoIGVmZmVjdGl2ZSB0YXJnZXRlZCBtYXJrZXRpbmcsIGFwcHJvcHJpYXRlIGJvcnJvd2VyIGFwcGxpY2F0aW9uIGRlbmlhbHMsIGFuZCBmcmF1ZHVsZW50IGJvcnJvd2VyIGFwcGxpY2F0aW9uIGlkZW50aWZpY2F0aW9uLiBPdXRsaWVycyB0byB0aGUgYm9ycm93ZXIgcHJvZmlsZSBncm91cHMgc2hvdWxkIGJlIG1hbnVhbGx5IHJldmlld2VkIHRvIGRldGVybWluZSBpZiB0aGUgYm9ycm93ZXIgaXMgYXQgcmlzayBmb3IgYWRqdXN0aW5nIG9uZSBvZiB0aGVpciBwcmluY2lwYWwgY29tcG9uZW50IGNsYXNzaWZpY2F0aW9ucyAoZXg6IG1vdmluZyBmcm9tIGhpZ2ggaW1tZWRpYXRlIGZpbmFuY2lhbCBzdGF0dXMgdG8gbG93LCBldGMpLCBpZiB0aGUgYm9ycm93ZXIgZGF0YSBpcyBwb3RlbnRpYWxseSBmcmF1ZHVsZW50LCBvciBpZiB0aGUgYm9ycm93ZXIgZGF0YSBpbmRpY2F0ZXMgYSBib3Jyb3dlciB0aGF0IHJlcXVpcmVzIHBvdGVudGlhbCBhY2NvdW50IHRlcm1pbmF0aW9uIGFuZC9vciBmdXR1cmUgYm9ycm93ZXIgYXBwbGljYXRpb24gZGVuaWFsIGZvciBsYWNrIG9mIHJlcGF5bWVudCBhYmlsaXR5LiAgDQoNCkFkZGl0aW9uYWxseSwgcHJvcGVybHkgaWRlbnRpZnlpbmcgb3V0bGllcnMgYWxsb3dzIGZvciByZW1vdmFsIG9mIG91dGxpZXJzIHByaW9yIHRvIFBDQSBvciBoaWVyYXJjaGljYWwgYWdnbG9tZXJhdGUgY2x1c3RlcmluZyB3aWxsIHJlc3VsdCBpbiBpbmNyZWFzZWQgYm9ycm93ZXIgcmVwYXltZW50IHByb2ZpbGUgY2xhc3NpZmljYXRpb24gYW5kIGltcHJvdmVkIHByZWRpY3RpdmUgY2FwYWNpdHkuDQoNCiMjIExvY2FsIE91dGxpZXIgRmFjdG9yIChMT0YpIFNjb3JlDQoNCkEgbG9jYWwgb3V0bGllciBmYWN0b3IgKExPRikgc2NvcmUgaXMgY2FsY3VsYXRlZCB1dGlsaXppbmcgdGhlIGJlbG93IGVxdWF0aW9uIHRvIGNvbXBhcmUgYSBkYXRhIHBvaW50J3MgbG9jYWwgcmVhY2hhYmlsaXR5IGRlbnNpdHkgKExSRCkgb2YgdGhlIG5lYXJlc3QgayBuZWlnaGJvcnMgdG8gcG9pbnQgJEFfaSQgZm9yICRpID0gMSAsIDIsIC4uLiAsIG4kLiANCg0KJCQNCkxPRihBX2kpID0gXGZyYWN7XGZyYWN7XHN1bV97aSA9IEFfaiBcaW4gTl9rQV9pfSB7TFJEX2soQV9qKX19e3x8Tl9rKEFfaSl8fH19IHtMUkRfayhBX2kpfQ0KJCQNCg0KVGhlIExPRiBmYWN0b3IgYWN0cyBhcyBhbiBlYXNpbHkgZmlsdGVyYWJsZSBzY2FsZSB2YXJpYWJsZSB0byBxdWlja2x5IGlkZW50aWZ5IG91dGxpZXIgdmFsdWVzLiAgQW4gTE9GID4gMSBpbmRpY2F0ZXMgYSBwb3RlbnRpYWwgb3V0bGllciwgd2l0aCB0aGUgZ3JlYXRlciB2YWx1ZXMgZm9yIExPRiBpbmRpY2lhdGluZyBtb3JlIGV4dHJlbWUgb3V0bGllcnMuIA0KDQpgYGB7cn0NCg0KbG9mLnBjYS5jbHVzdGVyIDwtIGxvZihwY2EuY2x1c3RlciwgbWluUHRzID0gNTApDQpwY2EuY2x1c3RlciRMT0YgPC0gbG9mLnBjYS5jbHVzdGVyDQoNCnBhbmRlcjo6cGFuZGVyKHN1bW1hcnkobG9mLnBjYS5jbHVzdGVyKSwgY2FwdGlvbiA9ICJTdW1tYXJ5IHN0YXRpc3RpY3Mgb2YgTE9GIHNjb3JlcyBmb3IgSW1tZWRpYXRlIEZpbmFuY2lhbCBTdGF0dXMgSW5kZXggJiBSZXBheW1lbnQgU2VjdXJpdHkgSW5kZXgiKQ0KYGBgDQoNCkJhc2VkIG9uIHRoZSBzdW1tYXJ5IHN0YXRpc3RpY3MgZm9yIHRoZSBjYWxjdWxhdGVkIExPRiBzY29yZXMgZm9yIHRoZSBQQ0EgZmVhdHVyZSB2YXJpYWJsZXMgdGhlIG91dGxpZXIgY3V0b2ZmIHdhcyBzZWxlY3RlZCBhcyBhbiBMT0YgdmFsdWUgb2YgMS44LiAgVGhpcyB2YWx1ZSB3YXMgc2VsZWN0ZWQgYXMgdGhlIGN1dG9mZiB0byBlbnN1cmUgbGVzcyB0aGFuIDElIG9mIHRoZSBkYXRhc2V0IGlzIGlkZW50aWZpZWQgYXMgYW4gb3V0bGllciwgbGVhZGluZyB0byBhbiBvdXRsaWVyIGZsYWdnaW5nIG9mIDEgaW4gZXZlcnkgMTAwIGJvcnJvd2Vycy4gU2VsZWN0aW5nIGFuIExPRiB2YWx1ZSBvZiAxLjcgcmVzdWx0ZWQgaW4gPiAxJSBvdXRsaWVyIGZsYWdnaW5nLiBUaGlzIDElIG91dGxpZXIgZmxhZ2dpbmcgd2lsbCBzY2FsZSB3ZWxsIHRvIHRoZSBsZW5kZXIgb3IgY3JlZGl0IGFnZW5jeSdzIG9wZXJhdGlvbmFsIGNvbnN0cmFpbnRzIHVudGlsIHRoZSBuZWVkIGZvciBoeXBlcnBhcmFtZXRlciAoaykgdHVuaW5nIGlzIHJlcXVpcmVkLiANCg0KYGBge3J9DQpwbG90KHggPSBwY2EuY2x1c3RlciRJbWVkX2Zpbl9zdGF0X1BDMSwNCiAgICAgeSA9IHBjYS5jbHVzdGVyJFJlcGF5X3NlY19QQzIsDQogICAgIHBjaCA9ICJ4IiwNCiAgICAgY2V4ID0gMC41LA0KICAgICB4bGFiID0gIkltbWVkaWF0ZSBGaW5hbmNpYWwgU3RhdHVzIEluZGV4IiwNCiAgICAgeWxhYiA9ICJSZXBheW1lbnQgU2VjdXJpdHkgSW5kZXgiLA0KICAgICBtYWluPSJPdXRsaWVyIElkZW50aWZpY2F0aW9uIEFtb25nc3QgUENBIENvbXBvbmVudHMiKQ0KcG9pbnRzKHBjYS5jbHVzdGVyLA0KICAgICAgIGNleCA9ICgobG9mLnBjYS5jbHVzdGVyIC0gMSkqMS41KSwNCiAgICAgICBwY2ggPSAyMSwNCiAgICAgICBjb2wgPSAiaG90cGluayIpDQp0ZXh0KHBjYS5jbHVzdGVyW2xvZi5wY2EuY2x1c3RlciA+IDEuOCxdLA0KICAgICBsYWJlbHMgPSByb3VuZChsb2YucGNhLmNsdXN0ZXIsIDEpW2xvZi5wY2EuY2x1c3RlciA+MS44XSwNCiAgICAgcG9zID0gMiwNCiAgICAgY2V4ID0gMC42LA0KICAgICBjb2wgPSAiZGFya3JlZCIpDQoNCmthYmxlKGZpbHRlcihwY2EuY2x1c3RlciwgTE9GID4gMS44KSwgY2FwdGlvbiA9ICJsaXN0aW5nIG9mIGFsbCBMT0Ygc2NvcmVzID4gMS44IikgJT4lIA0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQojIENvbmNsdXNpb25zDQoNCk92ZXJhbGwgcHJvcGVyIGJvcnJvd2VyIHByb2ZpbGUgc2VnbWVudGF0aW9uIHdpbGwgYWxsb3cgZm9yIGltcHJvdmVkIGxvYW4gZGVmYXVsdCBwcmVkaWN0aW9uIG1vZGVscywgaW1wcm92ZWQgaWRlbnRpZmljYXRpb24gb2YgZnJhdWR1bGVudCBwcmUtYXBwcm92YWwgYXBwbGljYXRpb25zLCBhbmQgaW1wcm92ZWQgdGFyZ2V0ZWQgbWFya2V0aW5nIHRvIGRyaXZlIHVwIGJvcnJvd2luZyByYXRlcyBmcm9tIGJvcnJvd2VycyB3aXRoIGEgaGlnaCByZXBheW1lbnQgcHJvZmlsZS4gIE92ZXJhbGwgdGhpcyBib3Jyb3dlciBzZWdtZW50YXRpb24gYW5kIGNsYXNzaWZpY2F0aW9uIG1heSBhbHNvIGJlIHVzZWQgZm9yIHByZWRpY3RpdmUgYW5hbHlzaXMgcmVnYXJkaW5nIGJvcnJvd2VyIHByZS1hcHByb3ZhbCBkZXRlcm1pbmF0aW9ucy4gIE92ZXJhbGwgYm9ycm93ZXIgcG9wdWxhdGlvbiBzZWdtZW50YXRpb24gcmVtYWlucyBhIGhpZ2hseSBlZmZlY3RpdmUgdG9vbCBmb3IgbWFuYWdpbmcgYW5kIHByZWRpY3Rpbmcgb3ZlcmFsbCBsb2FuIG91dGNvbWVzLg0KDQoNCiMgUmVmZXJlbmNlcw0KDQpEYXRhIHNvdXJjZToNCg0KQXBwbGllZCBBbmFseXRpY3MgdGhyb3VnaCBDYXNlIFN0dWRpZXMgVXNpbmcgU0FTIGFuZCBSLCBEZWVwdGkgR3VwdGEgYnkgQVByZXNzLCBJU0JOIC0gOTc4LTEtNDg0Mi0zNTI1LTYNCkFjY2Vzc2VkIHZpYTogPGEgaHJlZj0iaHR0cHM6Ly9wZW5nZHNjaS5naXRodWIuaW8vZGF0YXNldHMvTG9hbkRhdGEyL0JhbmtMb2FuRGVmYXVsdERhdGFzZXQuY3N2Ij5odHRwczovL3Blbmdkc2NpLmdpdGh1Yi5pby9kYXRhc2V0cy9Mb2FuRGF0YTIvQmFua0xvYW5EZWZhdWx0RGF0YXNldC5jc3Y8L2E+