A Short Explanation About Confusion Matrix
Confusion Matrix (Ma trận nhầm lẫn, viết tắt là CM) là một tập hợp các tiêu chí nhằm đánh giá hiệu quả của một mô hình phân loại thường được sử dụng phổ biến. Đúng như tên gọi của nó, việc đọc hiểu ý nghĩa và ứng dụng kết quả của nó thường gây nhầm lẫn.
Một giải thích ấn tượng (và cũng ngắn gọn, dễ nhớ) là chúng ta xem xét tình huống sau. Hai “bệnh nhân” vì nghi ngờ mình mang thai nên đến bệnh viện chẩn đoán xem mình có mang thai hay không. Kết quả xét nghiệm có thể rơi vào bốn tình huống sau:

Bênh nhân được chấn đoán là có thai và thực tế đúng là như vậy. Tình huống này kết quả chấn đoán được gọi dương tính thật, hay TP (viết tắt của từ True Positive).
Bệnh nhân được chẩn đoán là có thai nhưng thực tế không phải vậy. Tình huống này được gọi là dương tính giả, kí hiệu là FP.
Bệnh nhân được chẩn đoán là không có thai nhưng thực tế người này đang mang bầu. Tình huống này được gọi là âm tính giả, kí hiệu là FN.
Bệnh nhân được chẩn đoán là không có thai và thực tế đúng như vậy. Tình huống này được gọi là âm tính thật, kí hiệu là TN.
Từ 4 tình huống cơ bản này của CM mà chúng ta có thể tính toán một loạt các tiêu chí khác như mức độ chính xác toàn cục Accuracy, độ nhạy Sensitivity. Chi tiết có thể xem ở đây.
Trong xét nghiệm y tế thì tình huống thứ 3 (còn gọi là sai sót loại 2) có thể gây ra hậu quả nặng nề hơn nhiều lần so với tình huống thứ 2 (sai sót loại 1).
Thực vậy, một bệnh nhân bị nhiếm HIV nhưng nếu anh ta được chẩn đoán sai là không nhiễm HIV (một kết quả âm tính giả) thì hậu quả là rất lớn: anh ta không có biện pháp phòng ngừa và có thể lây nhiễm cho nhiều người và đồng thời cũng bỏ qua các biện pháp điều trị kéo dài sự sống cho mình.
Còn một bệnh nhân không bị nhiễm HIV nhưng được chẩn đoán sai là nhiễm HIV (một kết quả dương tính giả) thì anh ta có thể được một cơ sở y tế khác kết luận ngược lại là “không bị nhiễm HIV” nếu anh ta khám - chữa trị ở nơi khác. Hậu quả khác là anh ta có thể uống khá nhiều các thuốc điều trị HIV khác nhau (thực tế anh ta không bị bệnh này) và chúng có thể có phản ứng phụ cũng như các tốn kém về tiền bạc khác.
Miêu tả chi tiết hơn về việc loại trừ phạm phải các sai sót này trong xét nghiệm y học chúng ta có thể tham khảo ở đây.
An Application of Confusion Matrix in Context of Credit Scoring / Classification
Trong bài toán phân loại và xếp hạng hồ sơ xin vay tín dụng từ một tổ chức tài chính như ngân hàng thì:
Một hồ sơ thực tế là xấu (Bad) nhưng mô hình phân loại mà ngân hàng sử dụng (như Logistic chẳng hạn) phân loại sai thành hồ sơ tốt (Good) dẫn đến quyết định cấp vốn vay cho khách hàng này thì ngân hàng gần như mất trắng vốn vì kết quả sai lầm từ mô hình phân loại.
Một hồ sơ thực tế là tốt nhưng mô hình phân loại sai thành xấu thì ngân hàng sẽ từ chối cấp khoản vay. Khi phạm phải sai lầm này ngân hàng đã bỏ lỡ một cơ hội kiếm lời trên khoản vay mà đáng lẽ ra nên cấp cho khách hàng này.
Như vậy là cũng như trong y tế, hậu quả phạm phải lỗi loại 2 là lớn hơn nhiều so với lỗi loại 1 đối với ngân hàng.
Do có (1) và (2) nên dẫn đến một hệ quả quan trọng sau:
- Khi lựa chọn mô hình phân loại thì một mô hình có mức độ chính xác toàn cục Accuracy cao lại có thể là mô hình tạo ra nhiều thiệt hại về mặt kinh tế hơn cho ngân hàng.
Nói cách khác Accuracy không phải là tiêu chí ưu tiên khi đánh giá và lựa chọn mô hình phân loại. Nếu được sử dụng, tiêu chí này có lẽ nên xếp cuối cùng trong danh sách.
Để minh họa, tạo một Data Frame gồm một cột là nhãn dự báo từ một mô hình phân loại nào đó, một cột là nhãn thực tế với hàm ý nhãn B là hồ sơ xấu, nhãn G là hồ sơ tốt. Có thể liên tưởng một cách hài hước rằng hồ sơ xấu có nhãn B là “bị nhiễm HIV”. Và đương nhiên mục tiêu của ngân hàng là loại càng nhiều hồ sơ có nhãn B này.
| B |
G |
| B |
B |
| B |
B |
| B |
G |
| G |
B |
| B |
G |
| G |
G |
| G |
B |
| B |
G |
| B |
B |
Nếu ngân hàng sử dụng kết quả của mô hình phân loại, ví dụ, để cho vay thì chỉ những nhãn nào là chữ G (hàm ý là hồ sơ tốt) sẽ được vay. Theo kết quả của mô hình phân loại, có 3 hồ sơ được duyệt vay như ta có thể thấy:
Bằng mắt ta có thể thấy trong số 3 hồ sơ được duyệt vay này thì: (1) chỉ có đúng duy nhất một hồ sơ thực tế là nhãn G và mô hình xếp loại đúng là G, (2) còn hai hồ sơ kia thực tế là nhãn B nhưng mô hình lại xếp nhầm thành nhãn G. Chúng ta có thể sử dụng hàm table() để thấy kết quả này rõ hơn:
##
## B G
## B 3 4
## G 2 1
Từ CM này chúng ta có thể tính toán một số tiêu chí đánh giá mô hình khác như mô tả dưới đây:

Trong tình huống của chúng ta thì:
- Accuracy = (3 + 1) / (3 + 4 + 2 + 1) = 40%.
- Sensitivity = 3 / (3 + 2) = 50%.
- Specificity = 1 / (4 + 1) = 20%.
Các kết quả trên (và nhiều tiêu chí khác) có thể được tình một cách khác bằng sử dụng hàm confusionMatrix() của gói caret:
## Confusion Matrix and Statistics
##
## Reference
## Prediction B G
## B 3 4
## G 2 1
##
## Accuracy : 0.4
## 95% CI : (0.1216, 0.7376)
## No Information Rate : 0.5
## P-Value [Acc > NIR] : 0.8281
##
## Kappa : -0.2
## Mcnemar's Test P-Value : 0.6831
##
## Sensitivity : 0.6000
## Specificity : 0.2000
## Pos Pred Value : 0.4286
## Neg Pred Value : 0.3333
## Prevalence : 0.5000
## Detection Rate : 0.3000
## Detection Prevalence : 0.7000
## Balanced Accuracy : 0.4000
##
## 'Positive' Class : B
##
Sensitivity chính là tiêu chí mà ngân hàng nên chú ý và tập trung nhiều hơn vì đây là thước đo đánh giá mức độ chính xác khi phân loại các hồ sơ có nhãn B - tức là các hồ sơ “nhiễm HIV”. Chẳng hạn, trong bối cảnh thu hẹp tín dụng do nền kinh tế suy thoái thì giữa các mô hình phân loại, ngân hàng nên chọn mô hình nào mà có Sensitivity cao.
Một lí do nữa cần lưu ý là tồn tại một quy tắc khó chịu sau: sự đánh đổi giữa các tiêu chí. Điều này có nghĩa là với một mô hình, nếu bằng cách thức nào đó mà chúng ta cố gắng nâng cao Sensitivity chẳng hạn thì một tiêu chí nào đó như Specificity (hoặc Accuracy) sẽ giảm.
Điều này được giải thích ngay sau đây.
Accuracy Trade-offs
Hầu hết các mô hình phân loại (bất kể chúng là mô hình Logistic hay các cách tiếp cận của Machine Learning) thì kết quả cuối cùng của mô hình vẫn là “xác suất xẩy ra sự kiện quan tâm” - tức là một con số bất kì nào đó nằm giữa 0 và 1.
Trong tình huống phân loại hồ sơ tín dụng, nếu mô hình Logistic cho ra kết quả rằng xác xuất khách hàng này rơi vào sự kiện mà ngân hàng quan tâm là “vỡ nợ” là 0.45 (xác suất này, các tài liệu và sách vở về phân loại hồ sơ tín dụng gọi bằng một cái tên mang tính “chuyên ngành” hơn là xác suất vỡ nợ PD viết tắt của từ Probability of Default). Trong tình huống này số phận của hồ sơ này hoàn toàn phụ thuộc vào ngưỡng (Threshold) được sử dụng để phân loại (hay dán nhãn cho hồ sơ):
- Nếu người làm mô hình quyết định rằng nếu PD này mà lớn hơn 0.4 thì hồ sơ này sẽ được dán nhãn Bad.
- Nhưng nếu chọn ngưỡng lỏng hơn là 0.5 chẳng hạn, thì hồ sơ này lại được dán nhãn là Good.
Hầu hết các phần mềm nếu để mặc định thì ngưỡng được chọn để dán nhãn luôn là 0.5. Để minh họa sự đánh đổi giữa các tiêu chí đo lường mức độ chính xác và ngưỡng được chọn chúng ta xét bộ số liệu German Credit.
library(magrittr)
# Sử dụng bộ dữ liệu GermanCredit:
data("GermanCredit")
# Viết hàm dán lại nhãn (tôi thích chữ in hoa hơn in thường) cho biến mục tiêu:
label_rename <- function(x) {
case_when(x == "Bad" ~ "BAD",
x == "Good" ~ "GOOD")
}
# Thực hiện một số thao tác tiền xử lí số liệu:
df <- GermanCredit %>%
rename(BAD = Class) %>%
mutate(BAD = as.character(BAD)) %>%
mutate(BAD = label_rename(BAD)) %>%
mutate(BAD = as.factor(BAD))
# Thực hiện phân chia dữ liệu:
set.seed(1)
id <- createDataPartition(y = df$BAD, p = 0.7, list = FALSE)
train <- df[id, ]
test <- df[-id, ]
# Thiết lập môi trường tinh chỉnh tham số và cross - validation:
set.seed(1)
train.control <- trainControl(method = "repeatedcv",
number = 5,
repeats = 5,
classProbs = TRUE,
allowParallel = TRUE,
summaryFunction = multiClassSummary)
# Xây dựng mô hình Logistic trên bộ dữ liệu train:
set.seed(1)
my_logit <- train(BAD ~.,
data = train,
method = "glm",
metric = "AUC",
trControl = train.control)
Như đã nói, R (cũng như các phần mềm khác) mặc định sử dụng ngưỡng 0.5 để dán nhãn. Điều này dẫn đến kết quả được thể hiện trên CM như sau:
## Confusion Matrix and Statistics
##
## Reference
## Prediction BAD GOOD
## BAD 41 35
## GOOD 49 175
##
## Accuracy : 0.72
## 95% CI : (0.6655, 0.7701)
## No Information Rate : 0.7
## P-Value [Acc > NIR] : 0.2456
##
## Kappa : 0.3023
## Mcnemar's Test P-Value : 0.1561
##
## Sensitivity : 0.4556
## Specificity : 0.8333
## Pos Pred Value : 0.5395
## Neg Pred Value : 0.7812
## Prevalence : 0.3000
## Detection Rate : 0.1367
## Detection Prevalence : 0.2533
## Balanced Accuracy : 0.6444
##
## 'Positive' Class : BAD
##
Nếu chúng ta muốn tính toán cụ thể PD thì:
Viết một hàm chuyển hóa từ xác suất vỡ nợ (PD) sang nhãn BAD hoặc GOOD với ngưỡng được chọn:
Với ngưỡng 0.5 thì kết quả trùng hợp với những gì chúng ta đã biết:
## Confusion Matrix and Statistics
##
## Reference
## Prediction BAD GOOD
## BAD 41 35
## GOOD 49 175
##
## Accuracy : 0.72
## 95% CI : (0.6655, 0.7701)
## No Information Rate : 0.7
## P-Value [Acc > NIR] : 0.2456
##
## Kappa : 0.3023
## Mcnemar's Test P-Value : 0.1561
##
## Sensitivity : 0.4556
## Specificity : 0.8333
## Pos Pred Value : 0.5395
## Neg Pred Value : 0.7812
## Prevalence : 0.3000
## Detection Rate : 0.1367
## Detection Prevalence : 0.2533
## Balanced Accuracy : 0.6444
##
## 'Positive' Class : BAD
##
Nhưng với ngưỡng chặt hơn (giảm từ 0.5 xuống 0.4) thì mức độ chính xác khi phân loại hồ sơ cho nhãn BAD (tức Sensitivity) tăng từ 45.56% lên 51.11%
## Confusion Matrix and Statistics
##
## Reference
## Prediction BAD GOOD
## BAD 46 45
## GOOD 44 165
##
## Accuracy : 0.7033
## 95% CI : (0.6481, 0.7545)
## No Information Rate : 0.7
## P-Value [Acc > NIR] : 0.4782
##
## Kappa : 0.2959
## Mcnemar's Test P-Value : 1.0000
##
## Sensitivity : 0.5111
## Specificity : 0.7857
## Pos Pred Value : 0.5055
## Neg Pred Value : 0.7895
## Prevalence : 0.3000
## Detection Rate : 0.1533
## Detection Prevalence : 0.3033
## Balanced Accuracy : 0.6484
##
## 'Positive' Class : BAD
##
Chúng ta có thể khảo sát toàn diện hơn một số tiêu chí đánh giá chất lượng của mô hình khi mà ngưỡng được chọn thay đổi. Để loại bỏ sự ngẫu nhiên và có thể tổng quát hóa kết quả chúng ta sẽ test kết quả trên 100 lần chọn mẫu và mẫu được thử nghiệm là 100 lấy ra từ test data mà chúng ta đã chuẩn bị ở trên.
Công việc này được thực hiện bằng một hàm như sau:
eval_fun2 <- function(thre) {
lapply(1:100, function(x) {
set.seed(x)
id <- createDataPartition(y = test$BAD, p = 100 / nrow(test), list = FALSE)
test_df <- test[id, ]
du_bao <- predict(my_logit, test_df, type = "prob") %>% pull(BAD)
du_bao <- case_when(du_bao >= thre ~ "BAD", du_bao < thre ~ "GOOD") %>% as.factor()
cm <- confusionMatrix(du_bao, test_df$BAD, positive = "BAD")
bg_gg <- cm$table %>%
as.vector() %>%
matrix(ncol = 4) %>%
as.data.frame()
names(bg_gg) <- c("TP", "FN", "FP", "TN")
kq <- c(cm$overall, cm$byClass)
ten <- kq %>% as.data.frame() %>% row.names()
kq %>%
as.vector() %>%
matrix(ncol = 18) %>%
as.data.frame() -> all_df
names(all_df) <- ten
all_df <- bind_cols(all_df, bg_gg)
return(all_df)
})
}
Sử dụng hàm này:
# Đánh giá sự biến đổi theo một loạt ngưỡng:
so_sanh_list <- lapply(seq(0.1, 0.90, by = 0.05), eval_fun2)
so_sanh_df <- do.call("bind_rows", so_sanh_list)
so_sanh_df %<>%
mutate(Threshold = lapply(seq(0.1, 0.90, by = 0.05), function(x) {rep(x, 100)}) %>% unlist())
names(so_sanh_df) <- names(so_sanh_df) %>% str_replace_all(" ", "")
# Chính xác chung:
so_sanh_df %>%
group_by(Threshold) %>%
summarise_each(funs(median), Accuracy) -> acc
Ngưỡng 0.55 thì Accuracy là lớn nhất và là và 74%:
## # A tibble: 1 x 2
## Threshold Accuracy
## <dbl> <dbl>
## 1 0.55 0.74
Nhưng ngưỡng mà phân loại hồ sơ xấu tốt nhất thì không phải 0.55 mà là 0.1:
## # A tibble: 1 x 2
## Threshold Sensitivity
## <dbl> <dbl>
## 1 0.1 0.867
Chúng ta có thể khảo sát đồng thời một số tiêu chí nhằm làm rõ hơn sự đánh đổi bằng công cụ hình ảnh:
theme_set(theme_minimal())
so_sanh_df %>%
group_by(Threshold) %>%
summarise_each(funs(median), Accuracy, Kappa, Sensitivity, Specificity) %>%
gather(Metric, b, -Threshold) %>%
ggplot(aes(Threshold, b, color = Metric)) +
geom_line() +
geom_point(size = 3) +
scale_y_continuous(labels = scales::percent) +
theme(panel.grid.minor = element_blank()) +
scale_x_continuous(breaks = seq(0.1, 0.9, by = 0.05)) +
geom_hline(yintercept = 0.7, color = "brown", size = 1) +
labs(y = "Accuracy Rate",
title = "Variation of Logistic Classifier's Metrics by Threshold",
subtitle = "Data Used: German Credit Data")

Khi Threshold là 0.55 thì mức chính xác chung cho phân loại đúng cả hồ sơ tốt lẫn xấu là cao nhất. Vượt qua ngưỡng 0.55 này thì Accuracy lại giảm.
Sự đánh đổi sau đây là rõ ràng: khi ngưỡng được lựa chọn tăng thì khả năng phân loại chính xác hồ sơ tốt (Specificity) tăng nhưng đồng thời với đó là khả năng phân loại hồ sơ xấu (Sensitivity) lại giảm. Chúng ta có thể so sánh cụ thể luôn với một số ngưỡng:
## [[1]]
## Confusion Matrix and Statistics
##
## Reference
## Prediction BAD GOOD
## BAD 201 250
## GOOD 9 240
##
## Accuracy : 0.63
## 95% CI : (0.593, 0.6659)
## No Information Rate : 0.7
## P-Value [Acc > NIR] : 1
##
## Kappa : 0.3366
## Mcnemar's Test P-Value : <2e-16
##
## Sensitivity : 0.9571
## Specificity : 0.4898
## Pos Pred Value : 0.4457
## Neg Pred Value : 0.9639
## Prevalence : 0.3000
## Detection Rate : 0.2871
## Detection Prevalence : 0.6443
## Balanced Accuracy : 0.7235
##
## 'Positive' Class : BAD
##
##
## [[2]]
## Confusion Matrix and Statistics
##
## Reference
## Prediction BAD GOOD
## BAD 144 81
## GOOD 66 409
##
## Accuracy : 0.79
## 95% CI : (0.7579, 0.8196)
## No Information Rate : 0.7
## P-Value [Acc > NIR] : 5.016e-08
##
## Kappa : 0.51
## Mcnemar's Test P-Value : 0.2482
##
## Sensitivity : 0.6857
## Specificity : 0.8347
## Pos Pred Value : 0.6400
## Neg Pred Value : 0.8611
## Prevalence : 0.3000
## Detection Rate : 0.2057
## Detection Prevalence : 0.3214
## Balanced Accuracy : 0.7602
##
## 'Positive' Class : BAD
##
##
## [[3]]
## Confusion Matrix and Statistics
##
## Reference
## Prediction BAD GOOD
## BAD 98 37
## GOOD 112 453
##
## Accuracy : 0.7871
## 95% CI : (0.7549, 0.8169)
## No Information Rate : 0.7
## P-Value [Acc > NIR] : 1.310e-07
##
## Kappa : 0.4356
## Mcnemar's Test P-Value : 1.342e-09
##
## Sensitivity : 0.4667
## Specificity : 0.9245
## Pos Pred Value : 0.7259
## Neg Pred Value : 0.8018
## Prevalence : 0.3000
## Detection Rate : 0.1400
## Detection Prevalence : 0.1929
## Balanced Accuracy : 0.6956
##
## 'Positive' Class : BAD
##
Sự đánh đổi giữa các tiêu chí phân loại của mô hình còn được biểu diễn bằng một “ngôn ngữ” hình ảnh thường được sử dụng rất phổ biến là đường ROC (ROC Curve) và khái niệm diện tích nằm dưới đường cong AUC được mô tả chi tiết ở đây.
Theo quy ước, ROC là đường biểu diễn sự đánh đổi giữa khả tỉ lệ phân loại sai hồ sơ tốt (1 - Specificity) và tỉ lệ phân loại đúng hồ sơ xấu (Sensitivity) tương ứng với các ngưỡng khác nhau. Và diện tích nằm dưới đường cong ROC này được gọi là AUC.
Chúng ta có thể sử dụng một số hàm sẵn có của R để tính toán AUC và hình ảnh hóa cho ROC như sau:
## Area under the curve: 0.7172
Một mô hình phân loại nhị phân (biến được phân loại chỉ có hai nhãn) thì AUC càng lớn càng tốt. Nếu AUC = 0.5 thì khả năng dự báo của mô hình được gọi là tương với đoán ngẫu nhiên. Trong hầu hết các ứng dụng, mô hình có chất lượng phân loại chấp nhận được thì AUC nên tối thiếu là lớn hơn hoặc bằng 0.7.
Người ta còn có thể sử dụng một biến thể khác của AUC là hệ số GINI được tính theo công thức \(GINI = 2*AUC - 1\). Dưới đây là R codes cho biểu diễn ROC - đường cong làm cơ sở tính toán AUC và GINI:
sen_spec_df <- data_frame(TPR = my_auc$sensitivities, FPR = 1 - my_auc$specificities)
sen_spec_df %>%
ggplot(aes(x = FPR, ymin = 0, ymax = TPR))+
geom_polygon(aes(y = TPR), fill = "red", alpha = 0.3)+
geom_path(aes(y = TPR), col = "firebrick", size = 1.2) +
geom_abline(intercept = 0, slope = 1, color = "gray37", size = 1, linetype = "dashed") +
theme_bw() +
coord_equal() +
labs(x = "FPR (1 - Specificity)",
y = "TPR (Sensitivity)",
title = "Model Performance for Logistic Classifier based on Test Data",
subtitle = paste0("AUC Value: ", my_auc$auc %>% round(2)))

Optimal Threshold that Maximizes Commercial Bank’s Profit
Vì cái giá phải trả khi phạm sai lầm loại 2 là cao hơn nhiều so với cái giá phải trả khi phạm sai lầm loại 1 nên chúng ta có kết luận rằng:
- Accuracy không phải là tiêu chí lựa chọn mô hình hay căn cứ vào đó để đề xuất nên hay không nên sử dụng.
- Nếu chúng ta điều chỉnh ngưỡng phân loại hồ sơ theo hướng khắt khe hơn khi phân loại hồ sơ xấu thì ngân hàng đối diện với đồng thời hai vấn đề sau: một mặt, ngân hàng có thể từ chối nhiều hồ sơ xấu hơn nhưng mặt khác cũng buộc phải từ chối nhiều cơ hội kiếm lãi hơn khi bỏ lỡ mất khách hàng tiềm năng do khách hàng này thực sự tốt nhưng mô hình lại phân loại nhầm thành hồ sơ xấu.
Điều này gợi ý rằng chúng ta cần tìm một ngưỡng tối ưu sao cho tổng lợi ích thu được khi từ chối càng nhiều hồ sơ xấu và thiệt hại phải gánh khi bỏ lỡ mất cơ hội kiếm lời cho khách hàng tốt.
Có nhiều giải pháp có thể được áp dụng để tìm ngưỡng tối ưu này. Một trong những cách đó là chúng ta sử dụng mô phỏng Monte Carlo với các giả thiết, đơn giản như sau:
Nếu khách hàng tốt mà mô hình phân loại đúng là tốt thì ngân hàng sẽ thu được lãi 30% trên số tiền cho vay. Đây là những khách hàng thuộc tình huống TN (True Negative).
Nếu khách hàng mà xấu nhưng mô hình lại phân loại sai thành tốt thì ngân hàng sẽ mất trắng vốn khi cho vay khách hàng này. Đây là những khách hàng thuộc tình huống FN (False Negative).
Số tiền mỗi khách hàng được vay có phân phối tương tự như phân phối của hạn mức mà họ đề xuất trong credit application.
Trước hết chúng ta tính toán kết quả phân loại của mô hình Logistic với một loạt các ngưỡng khác nhau:
Viết hàm tính toán lợi nhuận dựa trên mô phỏng với các giả thiết như trên:
Sử dụng hàm mô phỏng lợi nhuận:
# Sử dụng hàm với 5000 lần mô phỏng, lãi suất 30%:
profit_simu1(data_from_model = df_th_15,
rate = 0.30,
so_lan_mo_phong = 5000) -> p1
profit_simu1(data_from_model = df_th_20,
rate = 0.30,
so_lan_mo_phong = 5000) -> p2
profit_simu1(data_from_model = df_th_30,
rate = 0.30,
so_lan_mo_phong = 5000) -> p3
profit_simu1(data_from_model = df_th_40,
rate = 0.30,
so_lan_mo_phong = 5000) -> p4
profit_simu1(data_from_model = df_th_50,
rate = 0.30,
so_lan_mo_phong = 5000) -> p5
profit_simu1(data_from_model = df_th_55,
rate = 0.30,
so_lan_mo_phong = 5000) -> p55
profit_simu1(data_from_model = df_th_60,
rate = 0.30,
so_lan_mo_phong = 5000) -> p6
profit_simu1(data_from_model = df_th_65,
rate = 0.30,
so_lan_mo_phong = 5000) -> p65
n <- length(p1)
# Tạo ra DF về lợi nhuận cho mục đích đánh giá và so sánh:
df_for_comp <- bind_rows(data_frame(Profit = p1, Threshold = rep("Thr = 0.15", n)),
data_frame(Profit = p2, Threshold = rep("Thr = 0.20", n)),
data_frame(Profit = p3, Threshold = rep("Thr = 0.30", n)),
data_frame(Profit = p4, Threshold = rep("Thr = 0.40", n)),
data_frame(Profit = p5, Threshold = rep("Thr = 0.50", n)),
data_frame(Profit = p55, Threshold = rep("Thr = 0.55", n)),
data_frame(Profit = p6, Threshold = rep("Thr = 0.60", n)),
data_frame(Profit = p65, Threshold = rep("Thr = 0.65", n)))
Có thể thấy với mục tiêu là tối đa hóa lợi nhuận thì ngân hàng nên sử dụng ngưỡng 0.2 cho mô hình phân loại Logistic. Cần lưu ý rằng tại ngưỡng tối đa hóa Accuracy (là 0.55) thì đây không phải là ngưỡng tối ưu hóa lợi nhuận:
| Thr = 0.20 |
1651468 |
1651459 |
1321225 |
2020524 |
94454 |
| Thr = 0.15 |
1590092 |
1590703 |
1250166 |
1891077 |
86018 |
| Thr = 0.30 |
887719 |
889162 |
254012 |
1245467 |
110141 |
| Thr = 0.40 |
734896 |
733167 |
257460 |
1218495 |
124541 |
| Thr = 0.50 |
515821 |
518282 |
39018 |
944545 |
130037 |
| Thr = 0.55 |
433742 |
437021 |
-64994 |
862653 |
133308 |
| Thr = 0.60 |
-137637 |
-136049 |
-610941 |
400872 |
140377 |
| Thr = 0.65 |
-407580 |
-407959 |
-872496 |
124419 |
142798 |
Nguyên nhân mà chúng ta đã biết là tại ngưỡng tối đa hóa Accuracy này thì cũng là ngưỡng mà tạo ra nhiều sai lầm loại 2 hơn như chúng ta có thể thấy:
## [[1]]
## Confusion Matrix and Statistics
##
## Reference
## Prediction BAD GOOD
## BAD 188 162
## GOOD 22 328
##
## Accuracy : 0.7371
## 95% CI : (0.7029, 0.7694)
## No Information Rate : 0.7
## P-Value [Acc > NIR] : 0.01685
##
## Kappa : 0.4743
## Mcnemar's Test P-Value : < 2e-16
##
## Sensitivity : 0.8952
## Specificity : 0.6694
## Pos Pred Value : 0.5371
## Neg Pred Value : 0.9371
## Prevalence : 0.3000
## Detection Rate : 0.2686
## Detection Prevalence : 0.5000
## Balanced Accuracy : 0.7823
##
## 'Positive' Class : BAD
##
##
## [[2]]
## Confusion Matrix and Statistics
##
## Reference
## Prediction BAD GOOD
## BAD 111 46
## GOOD 99 444
##
## Accuracy : 0.7929
## 95% CI : (0.7609, 0.8223)
## No Information Rate : 0.7
## P-Value [Acc > NIR] : 1.857e-08
##
## Kappa : 0.4685
## Mcnemar's Test P-Value : 1.572e-05
##
## Sensitivity : 0.5286
## Specificity : 0.9061
## Pos Pred Value : 0.7070
## Neg Pred Value : 0.8177
## Prevalence : 0.3000
## Detection Rate : 0.1586
## Detection Prevalence : 0.2243
## Balanced Accuracy : 0.7173
##
## 'Positive' Class : BAD
##
Các kết luận trên có thể tóm tắt theo một cách khác bởi công cụ hình ảnh:

Cũng có thể thấy lợi nhuận trung bình là một hàm bậc hai hình chữ U ngược:

Compare with Profit that Results from some Machine Learning Approaches
Các nghiên cứu đã chỉ ra rằng các cách tiếp cận của Machine Learning chính xác hơn Logistic khi dự báo PD. Điều này cũng hàm ý rằng sử dụng các cách tiếp cận của ML có thể cho kết quả lợi nhuận tốt hơn đối với ngân hàng.
Như đã biết ở mục trên, với Threshold = 0.2 thì ngân hàng sẽ đạt được mục tiêu tối đa hóa lợi nhuận và là 1651468 khi sử dụng Logistic.
Nếu sử dụng chính ngưỡng này và sử dụng một cách tiếp cận khác của ML thì lợi nhuận mô phỏng có hệ quả từ sử dụng một số cách tiếp cận của ML cho phân loại có thể cao hơn.
R codes dưới đây chúng ta huấn luyện đồng thời 5 mô hình ML và cả Logistic, trong đó Logistic đóng vai trò nhu base line để so sánh:
set.seed(1)
number <- 3
repeats <- 5
control <- trainControl(method = "repeatedcv",
number = number,
repeats = repeats,
classProbs = TRUE,
savePredictions = "final",
index = createResample(train$BAD, number*repeats),
summaryFunction = multiClassSummary,
allowParallel = TRUE)
# Use Parallel computing (I use 8 CPU cores for training ML Models):
library(doParallel)
registerDoParallel(cores = 8)
# Simultaneously train some machine learning models:
library(caretEnsemble)
set.seed(1)
my_models <- c("rf", "adaboost", "knn", "svmRadial", "nb", "glm")
system.time(model_list1 <- caretList(BAD ~.,
data = train,
trControl = control,
metric = "Accuracy",
methodList = my_models))
## user system elapsed
## 14.16 0.47 249.73
Plot dưới đây chỉ ra rằng, ví dụ, Random Forests chiếm ưu thế so với Logistic ở hầu hết các tiêu chí:
# Compare model performance based on 12 measures:
total_df %>%
select(-logLoss, -prAUC, -Resample) %>%
gather(a, b, -Model) %>%
ggplot(aes(Model, b, fill = Model, color = Model)) +
geom_boxplot(show.legend = FALSE, alpha = 0.3) +
facet_wrap(~ a, scales = "free") +
coord_flip() +
scale_y_continuous(labels = scales::percent) +
theme(plot.margin = unit(c(1, 1, 1, 1), "cm")) +
labs(x = NULL, y = NULL,
title = "Model Performance Based on 12 Criteria for Alternative ML Models",
subtitle = "Valdation Method Used: Cross-validation")

Chúng ta có thể phân tích chi tiết hơn AUC của hai mô hình trên bộ dữ liệu test:
## $Logistic
## Area under the curve: 0.7172
##
## $RF
## Area under the curve: 0.7571
Kết quả chỉ ra rằng AUC của RF cao hơn. Đây là một dấu hiệu cho thấy: nếu sử dụng RF cho phân loại thì sẽ đạt lợi nhuận “tối ưu” cao hơn. Chữ tối ưu ở đây được cho vào ngoặc kép với hàm ý rằng: chúng ta sử dụng ngưỡng tối ưu cho Logistic chứ chưa phải cho RF.
Chúng ta có thể vẽ ROC cho cả hai mô hình:
# Draft ROC curves:
results_list_roc <- list()
num_mod <- 1
for(the_roc in model_list_roc){
results_list_roc[[num_mod]] <-
data_frame(TPR = the_roc$sensitivities,
FPR = 1 - the_roc$specificities,
Model = names(model_list)[num_mod])
num_mod <- num_mod + 1
}
results_df_roc <- bind_rows(results_list_roc)
# Plot ROC curve for 2 models:
results_df_roc %>%
ggplot(aes(FPR, TPR, color = Model)) +
geom_line(size = 1) +
theme_bw() +
coord_equal() +
geom_abline(intercept = 0, slope = 1, color = "gray37", size = 1, linetype = "dashed") +
labs(x = "FPR (1 - Specificity)",
y = "TPR (Sensitivity)",
title = "Model Performance based on Test Data by Model Selected")

Tương tự chúng ta viết hàm đánh giá kết quả phân loại của RF trên 100 lần chọn mẫu, mỗi lần 100 quan sát:
eval_fun2_rf <- function(thre) {
lapply(1:100, function(x) {
set.seed(x)
id <- createDataPartition(y = test$BAD, p = 100 / nrow(test), list = FALSE)
test_df <- test[id, ]
du_bao <- predict(my_rf, test_df, type = "prob") %>% pull(BAD)
du_bao <- case_when(du_bao >= thre ~ "BAD", du_bao < thre ~ "GOOD") %>% as.factor()
cm <- confusionMatrix(du_bao, test_df$BAD, positive = "BAD")
bg_gg <- cm$table %>%
as.vector() %>%
matrix(ncol = 4) %>%
as.data.frame()
names(bg_gg) <- c("TP", "FN", "FP", "TN")
kq <- c(cm$overall, cm$byClass)
ten <- kq %>% as.data.frame() %>% row.names()
kq %>%
as.vector() %>%
matrix(ncol = 18) %>%
as.data.frame() -> all_df
names(all_df) <- ten
all_df <- bind_cols(all_df, bg_gg)
return(all_df)})
}
# Sử dụng hàm:
df_th_20_rf <- do.call("bind_rows", eval_fun2_rf(0.20)) %>% mutate(Thr = "Thre = 0.20")
So sánh lợi nhuận mô phỏng giữa hai cách tiếp cận là Logistic và RF chúng ta có thể thấy sử dụng RF mang lại nhiều lợi nhuận hơn cho ngân hàng:
profit_simu1(data_from_model = df_th_20, rate = 0.30,
so_lan_mo_phong = 5000) -> p2
profit_simu1(data_from_model = df_th_20_rf, rate = 0.30,
so_lan_mo_phong = 5000) -> p20_rf_profit
compare_rf_logit <- data_frame(Profit = c(p2, p20_rf_profit),
Model = rep(c("Logistic", "Random Forests"), each = 5000, times = 1))
compare_rf_logit %>%
ggplot(aes(x = Model, y = Profit / 1000, fill = Model, color = Model)) +
geom_boxplot(alpha = 0.3) +
labs(x = NULL, y = NULL,
title = "Simulated Profit Based Monte Carlo Method by Method Selected",
subtitle = "Threshold: 0.2")

Chi tiết hơn các về các tiêu chí thống kê của lợi nhuận giữa hai cách tiếp cận:
| Random Forests |
1914124 |
1913712 |
1608051 |
2231068 |
75646 |
| Logistic |
1651468 |
1651459 |
1321225 |
2020524 |
94454 |
Chú ý rằng lợi nhuận trung bình 1914124 này có thể tạm gọi là tối ưu. Vì ngưỡng tối ưu sử dụng cho mô phỏng là “mượn” từ ngưỡng tối ưu của Logistic. Có thể tồn tại một ngưỡng tối ưu cho RF và ngưỡng này có thể khác 0.2.
Optimal Threshold for Random Forests
Tương tự như đã làm với Logistic Model chúng ta có thể tìm ngưỡng tối ưu hóa lợi nhuận khi sử dụng RF. Ngưỡng tối ưu cho RF là trùng hợp với ngưỡng tối ưu của Logistic như ta có thể thấy:
# Tính toán kết quả phân loại của RF với một loạt ngưỡng:
df_th_15_rf <- do.call("bind_rows", eval_fun2_rf(0.15)) %>% mutate(Thr = "Thre = 0.15")
df_th_20_rf <- do.call("bind_rows", eval_fun2_rf(0.20)) %>% mutate(Thr = "Thre = 0.20")
df_th_30_rf <- do.call("bind_rows", eval_fun2_rf(0.30)) %>% mutate(Thr = "Thre = 0.30")
df_th_40_rf <- do.call("bind_rows", eval_fun2_rf(0.40)) %>% mutate(Thr = "Thre = 0.40")
df_th_50_rf <- do.call("bind_rows", eval_fun2_rf(0.50)) %>% mutate(Thr = "Thre = 0.50")
df_th_55_rf <- do.call("bind_rows", eval_fun2_rf(0.55)) %>% mutate(Thr = "Thre = 0.55")
df_th_60_rf <- do.call("bind_rows", eval_fun2_rf(0.60)) %>% mutate(Thr = "Thre = 0.60")
df_th_65_rf <- do.call("bind_rows", eval_fun2_rf(0.65)) %>% mutate(Thr = "Thre = 0.65")
# Tạo DF cho so sánh:
profit_simu1(data_from_model = df_th_15_rf, rate = 0.30, so_lan_mo_phong = 5000) -> p1_rf
profit_simu1(data_from_model = df_th_20_rf, rate = 0.30, so_lan_mo_phong = 5000) -> p2_rf
profit_simu1(data_from_model = df_th_30_rf, rate = 0.30, so_lan_mo_phong = 5000) -> p3_rf
profit_simu1(data_from_model = df_th_40_rf, rate = 0.30, so_lan_mo_phong = 5000) -> p4_rf
profit_simu1(data_from_model = df_th_50_rf, rate = 0.30, so_lan_mo_phong = 5000) -> p5_rf
profit_simu1(data_from_model = df_th_55_rf, rate = 0.30, so_lan_mo_phong = 5000) -> p55_rf
profit_simu1(data_from_model = df_th_60_rf, rate = 0.30, so_lan_mo_phong = 5000) -> p6_rf
profit_simu1(data_from_model = df_th_65_rf, rate = 0.30, so_lan_mo_phong = 5000) -> p65_rf
# Tạo ra DF về lợi nhuận cho mục đích đánh giá và so sánh:
df_for_comp <- bind_rows(data_frame(Profit = p1_rf, Threshold = rep("Thr = 0.15", n)),
data_frame(Profit = p2_rf, Threshold = rep("Thr = 0.20", n)),
data_frame(Profit = p3_rf, Threshold = rep("Thr = 0.30", n)),
data_frame(Profit = p4_rf, Threshold = rep("Thr = 0.40", n)),
data_frame(Profit = p5_rf, Threshold = rep("Thr = 0.50", n)),
data_frame(Profit = p55_rf, Threshold = rep("Thr = 0.55", n)),
data_frame(Profit = p6_rf, Threshold = rep("Thr = 0.60", n)),
data_frame(Profit = p65_rf, Threshold = rep("Thr = 0.65", n)))
Bằng công cụ hình ảnh chúng ta có thể thấy rằng biến đổi lợi nhuận có hai đỉnh nếu trục X chúng ta minh họa ngưỡng tối ưu. Một trong hai đỉnh đó ứng với ngưỡng tối ưu là 0.2 cho RF. Còn đỉnh kia có thể coi là một “cực đại địa phương” ứng với ngưỡng là 0.4:

Key Conclusions
Với những bằng chứng thực nghiệm từ bộ dữ liệu chúng ta có mấy kết luận sau:
Luôn có sự đánh đổi giữa các tiêu chí đánh giá mức độ chính xác của mô hình.
Là một tổ chức vì lợi nhuận, ngân hàng nên xây dựng mô hình của mình căn cứ vào mục tiêu này chứ không phải mục tiêu là Accuracy.
Tùy điều kiện và bối cảnh nền kinh tế cũng như chiến lược kinh doanh ngân hàng có thể điều chỉnh ngưỡng của mô hình phân loại cho phù hợp. Chẳng hạn, nếu định hướng là tối thiều hóa nợ xấu và an toàn vốn thì ngưỡng được chọn nên tối đa hóa Sensitivity - tức khả năng phân loại đúng hồ sơ xấu.
Lợi nhuận mô phỏng thu được từ việc sử dụng RF cao hơn so với khi sử dụng Logistic. Nếu chúng ta thực hiện tình chỉnh tham số cho RF thì lợi nhuận tối ưu có thể còn cao hơn nữa. Ngoài RF chúng ta còn có một số ứng viên khác có thể sử dụng cho mô hình phân loại (như Support Vector Machines) nhưng trong phạm vi bài viết này là chưa có thời gian khảo sát.
Trong thực tế thì một case là TN (khách hàng là tốt và mô hình phân loại đúng là tốt) khi cho vay vốn vẫn có một xác suất mất vốn nào đó. Ngược lại, một case là FN (khách hàng là xấu nhưng mô hình phân loại sai thành tốt) khi cho vay vốn thì ngân hàng vẫn có thể có khả năng thu hồi vốn vay dù với xác suất rất thấp. Do vậy, để có ước lượng chính xác hơn về ngưỡng tối ưu người làm mô hình cần tham khảo ý kiến chuyên gia hoặc căn cứ vào dữ liệu lịch sử của ngân hàng về tỉ lệ thu hồi vốn cũng như lãi của cả hai nhóm khách hàng này.
LS0tDQp0aXRsZTogIk1lYXN1cmluZyBNb2RlbCBQZXJmb3JtYW5jZSBpbiBDbGFzc2lmaWNhdGlvbiBNb2RlbHM6IEEgU2hvcnQgRXhwbGFuYXRpb24gQWJvdXQgQ29uZnVzaW9uIE1hdHJpeCBhbmQgSXRzIEFwcGxpY2F0aW9uIiANCnN1YnRpdGxlOiAiUiBmb3IgUGxlYXN1cmUiDQphdXRob3I6ICJOZ3V5ZW4gQ2hpIER1bmciDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6IA0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICAjIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGhpZ2hsaWdodDogcHlnbWVudHMNCiAgICAjIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdGhlbWU6ICJmbGF0bHkiDQogICAgdG9jOiBUUlVFDQogICAgdG9jX2Zsb2F0OiBUUlVFDQotLS0NCg0KYGBge3Igc2V0dXAsaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UpDQpgYGANCg0KIyBBIFNob3J0IEV4cGxhbmF0aW9uIEFib3V0IENvbmZ1c2lvbiBNYXRyaXgNCg0KQ29uZnVzaW9uIE1hdHJpeCAoTWEgdHLhuq1uIG5o4bqnbSBs4bqrbiwgdmnhur90IHThuq90IGzDoCBDTSkgbMOgIG3hu5l0IHThuq1wIGjhu6NwIGPDoWMgdGnDqnUgY2jDrSBuaOG6sW0gxJHDoW5oIGdpw6EgaGnhu4d1IHF14bqjIGPhu6dhIG3hu5l0IG3DtCBow6xuaCBwaMOibiBsb+G6oWkgdGjGsOG7nW5nIMSRxrDhu6NjIHPhu60gZOG7pW5nIHBo4buVIGJp4bq/bi4gxJDDum5nIG5oxrAgdMOqbiBn4buNaSBj4bunYSBuw7MsIHZp4buHYyDEkeG7jWMgaGnhu4N1IMO9IG5naMSpYSB2w6Ag4bupbmcgZOG7pW5nIGvhur90IHF14bqjIGPhu6dhIG7DsyB0aMaw4budbmcgZ8OieSBuaOG6p20gbOG6q24uIA0KDQpN4buZdCBnaeG6o2kgdGjDrWNoIOG6pW4gdMaw4bujbmcgKHbDoCBjxaluZyBuZ+G6r24gZ+G7jW4sIGThu4Ugbmjhu5spIGzDoCBjaMO6bmcgdGEgeGVtIHjDqXQgdMOsbmggaHXhu5FuZyBzYXUuIEhhaSAiYuG7h25oIG5ow6JuIiB2w6wgbmdoaSBuZ+G7nSBtw6xuaCBtYW5nIHRoYWkgbsOqbiDEkeG6v24gYuG7h25oIHZp4buHbiBjaOG6qW4gxJFvw6FuIHhlbSBtw6xuaCBjw7MgbWFuZyB0aGFpIGhheSBraMO0bmcuIEvhur90IHF14bqjIHjDqXQgbmdoaeG7h20gY8OzIHRo4buDIHLGoWkgdsOgbyBi4buRbiB0w6xuaCBodeG7kW5nIHNhdTogDQoNCg0KIVtdKEM6L1VzZXJzL1pib29rL0RvY3VtZW50cy9mYWxzZV9wb3NpdGl2ZS5wbmcpDQoNCjEuIELDqm5oIG5ow6JuIMSRxrDhu6NjIGNo4bqlbiDEkW/DoW4gbMOgIGPDsyB0aGFpIHbDoCB0aOG7sWMgdOG6vyDEkcO6bmcgbMOgIG5oxrAgduG6rXkuIFTDrG5oIGh14buRbmcgbsOgeSBr4bq/dCBxdeG6oyBjaOG6pW4gxJFvw6FuIMSRxrDhu6NjIGfhu41pICoqZMawxqFuZyB0w61uaCB0aOG6rXQqKiwgaGF5IFRQICh2aeG6v3QgdOG6r3QgY+G7p2EgdOG7qyBUcnVlIFBvc2l0aXZlKS4gDQoNCjIuIELhu4duaCBuaMOibiDEkcaw4bujYyBjaOG6qW4gxJFvw6FuIGzDoCBjw7MgdGhhaSBuaMawbmcgdGjhu7FjIHThur8ga2jDtG5nIHBo4bqjaSB24bqteS4gVMOsbmggaHXhu5FuZyBuw6B5IMSRxrDhu6NjIGfhu41pIGzDoCAqKmTGsMahbmcgdMOtbmggZ2nhuqMqKiwga8OtIGhp4buHdSBsw6AgRlAuIA0KDQozLiBC4buHbmggbmjDom4gxJHGsOG7o2MgY2jhuqluIMSRb8OhbiBsw6Aga2jDtG5nIGPDsyB0aGFpIG5oxrBuZyB0aOG7sWMgdOG6vyBuZ8aw4budaSBuw6B5IMSRYW5nIG1hbmcgYuG6p3UuIFTDrG5oIGh14buRbmcgbsOgeSDEkcaw4bujYyBn4buNaSBsw6AgKirDom0gdMOtbmggZ2nhuqMqKiwga8OtIGhp4buHdSBsw6AgRk4uIA0KDQo0LiBC4buHbmggbmjDom4gxJHGsOG7o2MgY2jhuqluIMSRb8OhbiBsw6Aga2jDtG5nIGPDsyB0aGFpIHbDoCB0aOG7sWMgdOG6vyDEkcO6bmcgbmjGsCB24bqteS4gVMOsbmggaHXhu5FuZyBuw6B5IMSRxrDhu6NjIGfhu41pIGzDoCAqKsOibSB0w61uaCB0aOG6rXQqKiwga8OtIGhp4buHdSBsw6AgVE4uIA0KDQpU4burIDQgdMOsbmggaHXhu5FuZyBjxqEgYuG6o24gbsOgeSBj4bunYSBDTSBtw6AgY2jDum5nIHRhIGPDsyB0aOG7gyB0w61uaCB0b8OhbiBt4buZdCBsb+G6oXQgY8OhYyB0acOqdSBjaMOtIGtow6FjIG5oxrAgbeG7qWMgxJHhu5kgY2jDrW5oIHjDoWMgdG/DoG4gY+G7pWMgQWNjdXJhY3ksIMSR4buZIG5o4bqheSBTZW5zaXRpdml0eS4gQ2hpIHRp4bq/dCBjw7MgdGjhu4MgeGVtIFvhu58gxJHDonldKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1NlbnNpdGl2aXR5X2FuZF9zcGVjaWZpY2l0eSkuIA0KDQpUcm9uZyB4w6l0IG5naGnhu4dtIHkgdOG6vyB0aMOsIHTDrG5oIGh14buRbmcgdGjhu6kgMyAoY8OybiBn4buNaSBsw6Agc2FpIHPDs3QgbG/huqFpIDIpIGPDsyB0aOG7gyBnw6J5IHJhIGjhuq11IHF14bqjIG7hurduZyBu4buBIGjGoW4gbmhp4buBdSBs4bqnbiBzbyB24bubaSB0w6xuaCBodeG7kW5nIHRo4bupIDIgKHNhaSBzw7N0IGxv4bqhaSAxKS4gDQoNClRo4buxYyB24bqteSwgbeG7mXQgYuG7h25oIG5ow6JuICoqYuG7iyBuaGnhur9tIEhJVioqIG5oxrBuZyBu4bq/dSBhbmggdGEgxJHGsOG7o2MgY2jhuqluIMSRb8OhbiBzYWkgbMOgICoqa2jDtG5nIG5oaeG7hW0gSElWKiogKG3hu5l0IGvhur90IHF14bqjIMOibSB0w61uaCBnaeG6oykgdGjDrCBo4bqtdSBxdeG6oyBsw6AgcuG6pXQgbOG7m246IGFuaCB0YSBraMO0bmcgY8OzIGJp4buHbiBwaMOhcCBwaMOybmcgbmfhu6thIHbDoCBjw7MgdGjhu4MgbMOieSBuaGnhu4VtIGNobyBuaGnhu4F1IG5nxrDhu51pIHbDoCDEkeG7k25nIHRo4budaSBjxaluZyBi4buPIHF1YSBjw6FjIGJp4buHbiBwaMOhcCDEkWnhu4F1IHRy4buLIGvDqW8gZMOgaSBz4buxIHPhu5FuZyBjaG8gbcOsbmguIA0KDQoNCkPDsm4gbeG7mXQgYuG7h25oIG5ow6JuICoqa2jDtG5nIGLhu4sgbmhp4buFbSBISVYqKiBuaMawbmcgxJHGsOG7o2MgY2jhuqluIMSRb8OhbiBzYWkgbMOgICoqbmhp4buFbSBISVYqKiAobeG7mXQga+G6v3QgcXXhuqMgZMawxqFuZyB0w61uaCBnaeG6oykgdGjDrCBhbmggdGEgY8OzIHRo4buDIMSRxrDhu6NjIG3hu5l0IGPGoSBz4bufIHkgdOG6vyBraMOhYyBr4bq/dCBsdeG6rW4gbmfGsOG7o2MgbOG6oWkgbMOgICJraMO0bmcgYuG7iyBuaGnhu4VtIEhJViIgbuG6v3UgYW5oIHRhIGtow6FtIC0gY2jhu69hIHRy4buLIOG7nyBuxqFpIGtow6FjLiBI4bqtdSBxdeG6oyBraMOhYyBsw6AgYW5oIHRhIGPDsyB0aOG7gyB14buRbmcga2jDoSBuaGnhu4F1IGPDoWMgdGh14buRYyDEkWnhu4F1IHRy4buLIEhJViBraMOhYyBuaGF1ICh0aOG7sWMgdOG6vyBhbmggdGEga2jDtG5nIGLhu4sgYuG7h25oIG7DoHkpIHbDoCBjaMO6bmcgY8OzIHRo4buDIGPDsyBwaOG6o24g4bupbmcgcGjhu6UgY8WpbmcgbmjGsCBjw6FjIHThu5FuIGvDqW0gduG7gSB0aeG7gW4gYuG6oWMga2jDoWMuIA0KDQpNacOqdSB04bqjIGNoaSB0aeG6v3QgaMahbiB24buBIHZp4buHYyBsb+G6oWkgdHLhu6sgcGjhuqFtIHBo4bqjaSBjw6FjIHNhaSBzw7N0IG7DoHkgdHJvbmcgeMOpdCBuZ2hp4buHbSB5IGjhu41jIGNow7puZyB0YSBjw7MgdGjhu4MgdGhhbSBraOG6o28gW+G7nyDEkcOieV0oaHR0cHM6Ly93d3cubGl2ZXNjaWVuY2UuY29tLzMyNzY3LXdoYXQtYXJlLWZhbHNlLXBvc2l0aXZlcy1hbmQtZmFsc2UtbmVnYXRpdmVzLmh0bWw/ZmJjbGlkPUl3QVIzS1Noa2Z1clNDaFhxSGhyWE5UNDBvNmpOS1hleEFKWE9vcDJwSnAzYXNfREhJSUJJUWxSSFVMOHcpLiANCg0KDQojIEFuIEFwcGxpY2F0aW9uIG9mIENvbmZ1c2lvbiBNYXRyaXggaW4gQ29udGV4dCBvZiBDcmVkaXQgU2NvcmluZyAvIENsYXNzaWZpY2F0aW9uDQoNClRyb25nIGLDoGkgdG/DoW4gcGjDom4gbG/huqFpIHbDoCB44bq/cCBo4bqhbmcgaOG7kyBzxqEgeGluIHZheSB0w61uIGThu6VuZyB04burIG3hu5l0IHThu5UgY2jhu6ljIHTDoGkgY2jDrW5oIG5oxrAgbmfDom4gaMOgbmcgdGjDrDogDQoNCjEuIE3hu5l0IGjhu5Mgc8ahIHRo4buxYyB04bq/IGzDoCB44bqldSAoQmFkKSBuaMawbmcgbcO0IGjDrG5oIHBow6JuIGxv4bqhaSBtw6AgbmfDom4gaMOgbmcgc+G7rSBk4bulbmcgKG5oxrAgTG9naXN0aWMgY2jhurNuZyBo4bqhbikgcGjDom4gbG/huqFpIHNhaSB0aMOgbmggaOG7kyBzxqEgdOG7kXQgKEdvb2QpIGThuqtuIMSR4bq/biBxdXnhur90IMSR4buLbmggY+G6pXAgduG7kW4gdmF5IGNobyBraMOhY2ggaMOgbmcgbsOgeSB0aMOsIG5nw6JuIGjDoG5nIGfhuqduIG5oxrAgbeG6pXQgdHLhuq9uZyB24buRbiB2w6wga+G6v3QgcXXhuqMgc2FpIGzhuqdtIHThu6sgbcO0IGjDrG5oIHBow6JuIGxv4bqhaS4gDQoNCjIuIE3hu5l0IGjhu5Mgc8ahIHRo4buxYyB04bq/IGzDoCB04buRdCBuaMawbmcgbcO0IGjDrG5oIHBow6JuIGxv4bqhaSBzYWkgdGjDoG5oIHjhuqV1IHRow6wgbmfDom4gaMOgbmcgc+G6vSB04burIGNo4buRaSBj4bqlcCBraG/huqNuIHZheS4gS2hpIHBo4bqhbSBwaOG6o2kgc2FpIGzhuqdtIG7DoHkgbmfDom4gaMOgbmcgxJHDoyBi4buPIGzhu6EgbeG7mXQgY8ahIGjhu5lpIGtp4bq/bSBs4budaSB0csOqbiBraG/huqNuIHZheSBtw6AgxJHDoW5nIGzhur0gcmEgbsOqbiBj4bqlcCBjaG8ga2jDoWNoIGjDoG5nIG7DoHkuIA0KDQpOaMawIHbhuq15IGzDoCBjxaluZyBuaMawIHRyb25nIHkgdOG6vywgaOG6rXUgcXXhuqMgcGjhuqFtIHBo4bqjaSBs4buXaSBsb+G6oWkgMiBsw6AgbOG7m24gaMahbiBuaGnhu4F1IHNvIHbhu5tpIGzhu5dpIGxv4bqhaSAxIMSR4buRaSB24bubaSBuZ8OibiBow6BuZy4gDQoNCkRvIGPDsyAoMSkgdsOgICgyKSBuw6puIGThuqtuIMSR4bq/biBt4buZdCBo4buHIHF14bqjIHF1YW4gdHLhu41uZyBzYXU6IA0KDQozLiBLaGkgbOG7sWEgY2jhu41uIG3DtCBow6xuaCBwaMOibiBsb+G6oWkgdGjDrCBt4buZdCBtw7QgaMOsbmggY8OzIG3hu6ljIMSR4buZIGNow61uaCB4w6FjIHRvw6BuIGPhu6VjIEFjY3VyYWN5IGNhbyBs4bqhaSBjw7MgdGjhu4MgbMOgIG3DtCBow6xuaCB04bqhbyByYSBuaGnhu4F1IHRoaeG7h3QgaOG6oWkgduG7gSBt4bq3dCBraW5oIHThur8gaMahbiBjaG8gbmfDom4gaMOgbmcuIA0KDQpOw7NpIGPDoWNoIGtow6FjIEFjY3VyYWN5IGtow7RuZyBwaOG6o2kgbMOgIHRpw6p1IGNow60gxrB1IHRpw6puIGtoaSDEkcOhbmggZ2nDoSB2w6AgbOG7sWEgY2jhu41uIG3DtCBow6xuaCBwaMOibiBsb+G6oWkuIE7hur91IMSRxrDhu6NjIHPhu60gZOG7pW5nLCB0acOqdSBjaMOtIG7DoHkgY8OzIGzhur0gbsOqbiB44bq/cCBjdeG7kWkgY8O5bmcgdHJvbmcgZGFuaCBzw6FjaC4NCg0KxJDhu4MgbWluaCBo4buNYSwgdOG6oW8gbeG7mXQgRGF0YSBGcmFtZSBn4buTbSBt4buZdCBj4buZdCBsw6AgbmjDo24gZOG7sSBiw6FvIHThu6sgbeG7mXQgbcO0IGjDrG5oIHBow6JuIGxv4bqhaSBuw6BvIMSRw7MsIG3hu5l0IGPhu5l0IGzDoCBuaMOjbiB0aOG7sWMgdOG6vyB24bubaSBow6BtIMO9IG5ow6NuIEIgbMOgIGjhu5Mgc8ahIHjhuqV1LCBuaMOjbiBHIGzDoCBo4buTIHPGoSB04buRdC4gQ8OzIHRo4buDIGxpw6puIHTGsOG7n25nIG3hu5l0IGPDoWNoIGjDoGkgaMaw4bubYyBy4bqxbmcgaOG7kyBzxqEgeOG6pXUgY8OzIG5ow6NuIEIgbMOgICJi4buLIG5oaeG7hW0gSElWIi4gVsOgIMSRxrDGoW5nIG5oacOqbiBt4bulYyB0acOqdSBj4bunYSBuZ8OibiBow6BuZyBsw6AgbG/huqFpIGPDoG5nIG5oaeG7gXUgaOG7kyBzxqEgY8OzIG5ow6NuIEIgbsOgeS4gDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMjkpDQpteV9kZiA8LSBkYXRhLmZyYW1lKGR1X2JhbyA9IGMoc2FtcGxlKGMoIkIiLCAiRyIpLCAxMCwgcmVwbGFjZSA9IFRSVUUpKSwgDQogICAgICAgICAgICAgICAgICAgIHRodWNfdGUgPSBjKHNhbXBsZShjKCJCIiwgIkciKSwgMTAsIHJlcGxhY2UgPSBUUlVFKSkpDQoNCmtuaXRyOjprYWJsZShteV9kZikNCmBgYA0KDQoNCk7hur91IG5nw6JuIGjDoG5nIHPhu60gZOG7pW5nIGvhur90IHF14bqjIGPhu6dhIG3DtCBow6xuaCBwaMOibiBsb+G6oWksIHbDrSBk4bulLCDEkeG7gyBjaG8gdmF5IHRow6wgY2jhu4kgbmjhu69uZyBuaMOjbiBuw6BvIGzDoCBjaOG7ryBHIChow6BtIMO9IGzDoCBo4buTIHPGoSB04buRdCkgc+G6vSDEkcaw4bujYyB2YXkuIFRoZW8ga+G6v3QgcXXhuqMgY+G7p2EgbcO0IGjDrG5oIHBow6JuIGxv4bqhaSwgY8OzIDMgaOG7kyBzxqEgxJHGsOG7o2MgZHV54buHdCB2YXkgbmjGsCB0YSBjw7MgdGjhu4MgdGjhuqV5Og0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbXlfZGYgJT4lIA0KICBmaWx0ZXIoZHVfYmFvID09ICJHIikgJT4lIA0KICBrbml0cjo6a2FibGUoKQ0KYGBgDQoNCkLhurFuZyBt4bqvdCB0YSBjw7MgdGjhu4MgdGjhuqV5IHRyb25nIHPhu5EgMyBo4buTIHPGoSDEkcaw4bujYyBkdXnhu4d0IHZheSBuw6B5IHRow6w6ICgxKSBjaOG7iSBjw7MgxJHDum5nIGR1eSBuaOG6pXQgbeG7mXQgaOG7kyBzxqEgdGjhu7FjIHThur8gbMOgIG5ow6NuIEcgdsOgIG3DtCBow6xuaCB44bq/cCBsb+G6oWkgxJHDum5nIGzDoCBHLCAoMikgY8OybiBoYWkgaOG7kyBzxqEga2lhIHRo4buxYyB04bq/IGzDoCBuaMOjbiBCIG5oxrBuZyBtw7QgaMOsbmggbOG6oWkgeOG6v3AgbmjhuqdtIHRow6BuaCBuaMOjbiBHLiBDaMO6bmcgdGEgY8OzIHRo4buDIHPhu60gZOG7pW5nIGjDoG0gKip0YWJsZSgpKiogxJHhu4MgdGjhuqV5IGvhur90IHF14bqjIG7DoHkgcsO1IGjGoW46IA0KDQpgYGB7cn0NCnRhYmxlKG15X2RmJGR1X2JhbywgbXlfZGYkdGh1Y190ZSkNCmBgYA0KDQpU4burIENNIG7DoHkgY2jDum5nIHRhIGPDsyB0aOG7gyB0w61uaCB0b8OhbiBt4buZdCBz4buRIHRpw6p1IGNow60gxJHDoW5oIGdpw6EgbcO0IGjDrG5oIGtow6FjIG5oxrAgbcO0IHThuqMgZMaw4bubaSDEkcOieTogDQoNCiFbXShDOi9Vc2Vycy9aYm9vay9Eb2N1bWVudHMvaW1hZ2UyLnBuZykgDQoNClRyb25nIHTDrG5oIGh14buRbmcgY+G7p2EgY2jDum5nIHRhIHRow6w6DQoNCjEuIEFjY3VyYWN5ID0gKDMgICsgMSkgLyAoMyArIDQgKyAyICsgMSkgPSA0MCUuIA0KMi4gU2Vuc2l0aXZpdHkgPSAzIC8gKDMgKyAyKSA9IDUwJS4gDQozLiBTcGVjaWZpY2l0eSA9IDEgLyAoNCArIDEpID0gMjAlLiANCg0KQ8OhYyBr4bq/dCBxdeG6oyB0csOqbiAodsOgIG5oaeG7gXUgdGnDqnUgY2jDrSBraMOhYykgY8OzIHRo4buDIMSRxrDhu6NjIHTDrG5oIG3hu5l0IGPDoWNoIGtow6FjIGLhurFuZyBz4butIGThu6VuZyBow6BtICoqY29uZnVzaW9uTWF0cml4KCkqKiBj4bunYSBnw7NpIGNhcmV0OiANCg0KYGBge3J9DQpsaWJyYXJ5KGNhcmV0KQ0KY29uZnVzaW9uTWF0cml4KG15X2RmJGR1X2JhbywgbXlfZGYkdGh1Y190ZSwgcG9zaXRpdmUgPSAiQiIpDQpgYGANCg0KU2Vuc2l0aXZpdHkgY2jDrW5oIGzDoCB0acOqdSBjaMOtIG3DoCBuZ8OibiBow6BuZyBuw6puIGNow7ogw70gdsOgIHThuq1wIHRydW5nIG5oaeG7gXUgaMahbiB2w6wgxJHDonkgbMOgIHRoxrDhu5tjIMSRbyDEkcOhbmggZ2nDoSBt4bupYyDEkeG7mSBjaMOtbmggeMOhYyBraGkgcGjDom4gbG/huqFpIGPDoWMgaOG7kyBzxqEgY8OzIG5ow6NuIEIgLSB04bupYyBsw6AgY8OhYyBo4buTIHPGoSAibmhp4buFbSBISVYiLiBDaOG6s25nIGjhuqFuLCB0cm9uZyBi4buRaSBj4bqjbmggdGh1IGjhurlwIHTDrW4gZOG7pW5nIGRvIG7hu4FuIGtpbmggdOG6vyBzdXkgdGhvw6FpIHRow6wgZ2nhu69hIGPDoWMgbcO0IGjDrG5oIHBow6JuIGxv4bqhaSwgbmfDom4gaMOgbmcgbsOqbiBjaOG7jW4gbcO0IGjDrG5oIG7DoG8gbcOgIGPDsyBTZW5zaXRpdml0eSBjYW8uDQoNCk3hu5l0IGzDrSBkbyBu4buvYSBj4bqnbiBsxrB1IMO9IGzDoCB04buTbiB04bqhaSBt4buZdCBxdXkgdOG6r2Mga2jDsyBjaOG7i3Ugc2F1OiAqKnPhu7EgxJHDoW5oIMSR4buVaSBnaeG7r2EgY8OhYyB0acOqdSBjaMOtKiouIMSQaeG7gXUgbsOgeSBjw7MgbmdoxKlhIGzDoCB24bubaSBt4buZdCBtw7QgaMOsbmgsIG7hur91IGLhurFuZyBjw6FjaCB0aOG7qWMgbsOgbyDEkcOzIG3DoCBjaMO6bmcgdGEgY+G7kSBn4bqvbmcgbsOibmcgY2FvIFNlbnNpdGl2aXR5IGNo4bqzbmcgaOG6oW4gdGjDrCBt4buZdCB0acOqdSBjaMOtIG7DoG8gxJHDsyBuaMawIFNwZWNpZmljaXR5IChob+G6t2MgQWNjdXJhY3kpIHPhur0gZ2nhuqNtLiANCg0KxJBp4buBdSBuw6B5IMSRxrDhu6NjIGdp4bqjaSB0aMOtY2ggbmdheSBzYXUgxJHDonkuIA0KDQojIEFjY3VyYWN5IFRyYWRlLW9mZnMNCg0KSOG6p3UgaOG6v3QgY8OhYyBtw7QgaMOsbmggcGjDom4gbG/huqFpIChi4bqldCBr4buDIGNow7puZyBsw6AgbcO0IGjDrG5oIExvZ2lzdGljIGhheSBjw6FjIGPDoWNoIHRp4bq/cCBj4bqtbiBj4bunYSBNYWNoaW5lIExlYXJuaW5nKSB0aMOsIGvhur90IHF14bqjIGN14buRaSBjw7luZyBj4bunYSBtw7QgaMOsbmggduG6q24gbMOgICJ4w6FjIHN14bqldCB44bqpeSByYSBz4buxIGtp4buHbiBxdWFuIHTDom0iIC0gdOG7qWMgbMOgIG3hu5l0IGNvbiBz4buRIGLhuqV0IGvDrCBuw6BvIMSRw7MgbuG6sW0gZ2nhu69hIDAgdsOgIDEuIA0KDQpUcm9uZyB0w6xuaCBodeG7kW5nIHBow6JuIGxv4bqhaSBo4buTIHPGoSB0w61uIGThu6VuZywgbuG6v3UgbcO0IGjDrG5oIExvZ2lzdGljIGNobyByYSBr4bq/dCBxdeG6oyBy4bqxbmcgeMOhYyB4deG6pXQga2jDoWNoIGjDoG5nIG7DoHkgcsahaSB2w6BvIHPhu7Ega2nhu4duIG3DoCBuZ8OibiBow6BuZyBxdWFuIHTDom0gbMOgICJ24buhIG7hu6MiIGzDoCAwLjQ1ICh4w6FjIHN14bqldCBuw6B5LCBjw6FjIHTDoGkgbGnhu4d1IHbDoCBzw6FjaCB24bufIHbhu4EgcGjDom4gbG/huqFpIGjhu5Mgc8ahIHTDrW4gZOG7pW5nIGfhu41pIGLhurFuZyBt4buZdCBjw6FpIHTDqm4gbWFuZyB0w61uaCAiY2h1ecOqbiBuZ8OgbmgiIGjGoW4gbMOgICoqeMOhYyBzdeG6pXQgduG7oSBu4bujIFBEKiogdmnhur90IHThuq90IGPhu6dhIHThu6sgUHJvYmFiaWxpdHkgb2YgRGVmYXVsdCkuIFRyb25nIHTDrG5oIGh14buRbmcgbsOgeSBz4buRIHBo4bqtbiBj4bunYSBo4buTIHPGoSBuw6B5IGhvw6BuIHRvw6BuIHBo4bulIHRodeG7mWMgdsOgbyBuZ8aw4buhbmcgKFRocmVzaG9sZCkgxJHGsOG7o2Mgc+G7rSBk4bulbmcgxJHhu4MgcGjDom4gbG/huqFpIChoYXkgZMOhbiBuaMOjbiBjaG8gaOG7kyBzxqEpOiANCg0KMS4gTuG6v3UgbmfGsOG7nWkgbMOgbSBtw7QgaMOsbmggcXV54bq/dCDEkeG7i25oIHLhurFuZyBu4bq/dSBQRCBuw6B5IG3DoCBs4bubbiBoxqFuIDAuNCB0aMOsIGjhu5Mgc8ahIG7DoHkgc+G6vSDEkcaw4bujYyBkw6FuIG5ow6NuIEJhZC4gDQoyLiBOaMawbmcgbuG6v3UgY2jhu41uIG5nxrDhu6FuZyBs4buPbmcgaMahbiBsw6AgMC41IGNo4bqzbmcgaOG6oW4sIHRow6wgaOG7kyBzxqEgbsOgeSBs4bqhaSDEkcaw4bujYyBkw6FuIG5ow6NuIGzDoCBHb29kLiANCg0KSOG6p3UgaOG6v3QgY8OhYyBwaOG6p24gbeG7gW0gbuG6v3UgxJHhu4MgbeG6t2MgxJHhu4tuaCB0aMOsIG5nxrDhu6FuZyDEkcaw4bujYyBjaOG7jW4gxJHhu4MgZMOhbiBuaMOjbiBsdcO0biBsw6AgMC41LiDEkOG7gyBtaW5oIGjhu41hIHPhu7EgxJHDoW5oIMSR4buVaSBnaeG7r2EgY8OhYyB0acOqdSBjaMOtIMSRbyBsxrDhu51uZyBt4bupYyDEkeG7mSBjaMOtbmggeMOhYyB2w6AgbmfGsOG7oW5nIMSRxrDhu6NjIGNo4buNbiBjaMO6bmcgdGEgeMOpdCBi4buZIHPhu5EgbGnhu4d1IFtHZXJtYW4gQ3JlZGl0XShodHRwczovL2FyY2hpdmUuaWNzLnVjaS5lZHUvbWwvZGF0YXNldHMvc3RhdGxvZysoZ2VybWFuK2NyZWRpdCtkYXRhKSkuICANCg0KYGBge3J9DQpsaWJyYXJ5KG1hZ3JpdHRyKQ0KIyBT4butIGThu6VuZyBi4buZIGThu68gbGnhu4d1IEdlcm1hbkNyZWRpdDogDQpkYXRhKCJHZXJtYW5DcmVkaXQiKQ0KDQojIFZp4bq/dCBow6BtIGTDoW4gbOG6oWkgbmjDo24gKHTDtGkgdGjDrWNoIGNo4buvIGluIGhvYSBoxqFuIGluIHRoxrDhu51uZykgY2hvIGJp4bq/biBt4bulYyB0acOqdTogDQoNCmxhYmVsX3JlbmFtZSA8LSBmdW5jdGlvbih4KSB7DQogIGNhc2Vfd2hlbih4ID09ICJCYWQiIH4gIkJBRCIsIA0KICAgICAgICAgICAgeCA9PSAiR29vZCIgfiAiR09PRCIpDQp9DQoNCiMgVGjhu7FjIGhp4buHbiBt4buZdCBz4buRIHRoYW8gdMOhYyB0aeG7gW4geOG7rSBsw60gc+G7kSBsaeG7h3U6IA0KDQpkZiA8LSBHZXJtYW5DcmVkaXQgJT4lIA0KICByZW5hbWUoQkFEID0gQ2xhc3MpICU+JSANCiAgbXV0YXRlKEJBRCA9IGFzLmNoYXJhY3RlcihCQUQpKSAlPiUgDQogIG11dGF0ZShCQUQgPSBsYWJlbF9yZW5hbWUoQkFEKSkgJT4lIA0KICBtdXRhdGUoQkFEID0gYXMuZmFjdG9yKEJBRCkpDQoNCiMgVGjhu7FjIGhp4buHbiBwaMOibiBjaGlhIGThu68gbGnhu4d1OiANCnNldC5zZWVkKDEpDQppZCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKHkgPSBkZiRCQUQsIHAgPSAwLjcsIGxpc3QgPSBGQUxTRSkNCnRyYWluIDwtIGRmW2lkLCBdDQp0ZXN0IDwtIGRmWy1pZCwgXQ0KDQojIFRoaeG6v3QgbOG6rXAgbcO0aSB0csaw4budbmcgdGluaCBjaOG7iW5oIHRoYW0gc+G7kSB2w6AgY3Jvc3MgLSB2YWxpZGF0aW9uOiANCg0Kc2V0LnNlZWQoMSkNCnRyYWluLmNvbnRyb2wgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1iZXIgPSA1LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwZWF0cyA9IDUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2xhc3NQcm9icyA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbGxvd1BhcmFsbGVsID0gVFJVRSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdW1tYXJ5RnVuY3Rpb24gPSBtdWx0aUNsYXNzU3VtbWFyeSkNCg0KIyBYw6J5IGThu7FuZyBtw7QgaMOsbmggTG9naXN0aWMgdHLDqm4gYuG7mSBk4buvIGxp4buHdSB0cmFpbjogDQpzZXQuc2VlZCgxKQ0KbXlfbG9naXQgPC0gdHJhaW4oQkFEIH4uLCANCiAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbiwgDQogICAgICAgICAgICAgICAgICBtZXRob2QgPSAiZ2xtIiwgDQogICAgICAgICAgICAgICAgICBtZXRyaWMgPSAiQVVDIiwgDQogICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSB0cmFpbi5jb250cm9sKQ0KYGBgDQoNCk5oxrAgxJHDoyBuw7NpLCBSIChjxaluZyBuaMawIGPDoWMgcGjhuqduIG3hu4FtIGtow6FjKSBt4bq3YyDEkeG7i25oIHPhu60gZOG7pW5nIG5nxrDhu6FuZyAwLjUgxJHhu4MgZMOhbiBuaMOjbi4gxJBp4buBdSBuw6B5IGThuqtuIMSR4bq/biBr4bq/dCBxdeG6oyDEkcaw4bujYyB0aOG7gyBoaeG7h24gdHLDqm4gQ00gbmjGsCBzYXU6IA0KDQpgYGB7cn0NCiMgTWFrZSBwcmVkaWN0aW9ucyBvbiB0ZXN0IGRhdGE6IA0KcHJlZF9jbGFzcyA8LSBwcmVkaWN0KG15X2xvZ2l0LCB0ZXN0ICU+JSBzZWxlY3QoLUJBRCksIHR5cGUgPSAicmF3IikNCg0KIyBDb25mdXNpb24gbWF0cml4OiANCmNvbmZ1c2lvbk1hdHJpeChwcmVkX2NsYXNzLCB0ZXN0JEJBRCwgcG9zaXRpdmUgPSAiQkFEIikNCmBgYA0KDQpO4bq/dSBjaMO6bmcgdGEgbXXhu5FuIHTDrW5oIHRvw6FuIGPhu6UgdGjhu4MgUEQgdGjDrDogDQoNCmBgYHtyfQ0KIyBNYWtlIHByZWRpY3Rpb24gb2YgcHJvYmFiaWxpdGllczogDQpwcmVkX3Byb2JfYmFkIDwtICBwcmVkaWN0KG15X2xvZ2l0LCB0ZXN0ICU+JSBzZWxlY3QoLUJBRCksIHR5cGUgPSAicHJvYiIpICU+JSBwdWxsKEJBRCkNCmBgYA0KDQpWaeG6v3QgbeG7mXQgaMOgbSBjaHV54buDbiBow7NhIHThu6sgeMOhYyBzdeG6pXQgduG7oSBu4bujIChQRCkgc2FuZyBuaMOjbiBCQUQgaG/hurdjIEdPT0QgduG7m2kgbmfGsOG7oW5nIMSRxrDhu6NjIGNo4buNbjogDQoNCmBgYHtyfQ0KIyBGdW5jdGlvbiBjb252ZXJ0cyBwcm9iYWJpbGl0eSB0byBjbGFzczogDQoNCmNvbnZlcnRfdG9fbGFiZWwgPC0gZnVuY3Rpb24odGhyZXNob2xkKSB7DQogIHkgPC0gY2FzZV93aGVuKHByZWRfcHJvYl9iYWQgPj0gdGhyZXNob2xkIH4gIkJBRCIsIFRSVUUgfiAiR09PRCIpDQogIHJldHVybihhcy5mYWN0b3IoeSkpDQp9DQpgYGANCg0KVuG7m2kgbmfGsOG7oW5nIDAuNSB0aMOsIGvhur90IHF14bqjIHRyw7luZyBo4bujcCB24bubaSBuaOG7r25nIGfDrCBjaMO6bmcgdGEgxJHDoyBiaeG6v3Q6IA0KDQpgYGB7cn0NCiMgQ29tcGFyZSByZXN1bHRzOiANCmNvbmZ1c2lvbk1hdHJpeChjb252ZXJ0X3RvX2xhYmVsKDAuNSksIHRlc3QkQkFELCBwb3NpdGl2ZSA9ICJCQUQiKQ0KYGBgDQoNCk5oxrBuZyB24bubaSBuZ8aw4buhbmcgY2jhurd0IGjGoW4gKGdp4bqjbSB04burIDAuNSB4deG7kW5nIDAuNCkgdGjDrCBt4bupYyDEkeG7mSBjaMOtbmggeMOhYyBraGkgcGjDom4gbG/huqFpIGjhu5Mgc8ahIGNobyBuaMOjbiBCQUQgKHThu6ljIFNlbnNpdGl2aXR5KSB0xINuZyB04burIDQ1LjU2JSBsw6puIDUxLjExJSANCg0KYGBge3J9DQpjb25mdXNpb25NYXRyaXgoY29udmVydF90b19sYWJlbCgwLjQpLCB0ZXN0JEJBRCwgcG9zaXRpdmUgPSAiQkFEIikNCmBgYA0KDQpDaMO6bmcgdGEgY8OzIHRo4buDIGto4bqjbyBzw6F0IHRvw6BuIGRp4buHbiBoxqFuIG3hu5l0IHPhu5EgdGnDqnUgY2jDrSDEkcOhbmggZ2nDoSBjaOG6pXQgbMaw4bujbmcgY+G7p2EgbcO0IGjDrG5oIGtoaSBtw6AgbmfGsOG7oW5nIMSRxrDhu6NjIGNo4buNbiB0aGF5IMSR4buVaS4gxJDhu4MgbG/huqFpIGLhu48gc+G7sSBuZ+G6q3Ugbmhpw6puIHbDoCBjw7MgdGjhu4MgdOG7lW5nIHF1w6F0IGjDs2Ega+G6v3QgcXXhuqMgY2jDum5nIHRhIHPhur0gdGVzdCBr4bq/dCBxdeG6oyB0csOqbiAxMDAgbOG6p24gY2jhu41uIG3huqt1IHbDoCBt4bqrdSDEkcaw4bujYyB0aOG7rSBuZ2hp4buHbSBsw6AgMTAwIGzhuqV5IHJhIHThu6sgdGVzdCBkYXRhIG3DoCBjaMO6bmcgdGEgxJHDoyBjaHXhuqluIGLhu4sg4bufIHRyw6puLiANCg0KQ8O0bmcgdmnhu4djIG7DoHkgxJHGsOG7o2MgdGjhu7FjIGhp4buHbiBi4bqxbmcgbeG7mXQgaMOgbSBuaMawIHNhdTogDQoNCmBgYHtyfQ0KZXZhbF9mdW4yIDwtIGZ1bmN0aW9uKHRocmUpIHsNCiAgbGFwcGx5KDE6MTAwLCBmdW5jdGlvbih4KSB7DQogICAgc2V0LnNlZWQoeCkNCiAgICBpZCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKHkgPSB0ZXN0JEJBRCwgcCA9IDEwMCAvIG5yb3codGVzdCksIGxpc3QgPSBGQUxTRSkNCiAgICB0ZXN0X2RmIDwtIHRlc3RbaWQsIF0NCiAgICANCiAgICBkdV9iYW8gPC0gcHJlZGljdChteV9sb2dpdCwgdGVzdF9kZiwgdHlwZSA9ICJwcm9iIikgJT4lIHB1bGwoQkFEKQ0KICAgIGR1X2JhbyA8LSBjYXNlX3doZW4oZHVfYmFvID49IHRocmUgfiAiQkFEIiwgZHVfYmFvIDwgdGhyZSB+ICJHT09EIikgJT4lIGFzLmZhY3RvcigpDQogICAgDQogICAgY20gPC0gY29uZnVzaW9uTWF0cml4KGR1X2JhbywgdGVzdF9kZiRCQUQsIHBvc2l0aXZlID0gIkJBRCIpDQogICAgDQogICAgYmdfZ2cgPC0gY20kdGFibGUgJT4lIA0KICAgICAgYXMudmVjdG9yKCkgJT4lIA0KICAgICAgbWF0cml4KG5jb2wgPSA0KSAlPiUgDQogICAgICBhcy5kYXRhLmZyYW1lKCkNCiAgICANCiAgICBuYW1lcyhiZ19nZykgPC0gYygiVFAiLCAiRk4iLCAiRlAiLCAiVE4iKSANCiAgICANCiAgICANCiAgICBrcSA8LSBjKGNtJG92ZXJhbGwsIGNtJGJ5Q2xhc3MpIA0KICAgIHRlbiA8LSBrcSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSByb3cubmFtZXMoKQ0KICAgIA0KICAgIGtxICU+JSANCiAgICAgIGFzLnZlY3RvcigpICU+JSANCiAgICAgIG1hdHJpeChuY29sID0gMTgpICU+JSANCiAgICAgIGFzLmRhdGEuZnJhbWUoKSAtPiBhbGxfZGYNCiAgICANCiAgICBuYW1lcyhhbGxfZGYpIDwtIHRlbg0KICAgIGFsbF9kZiA8LSBiaW5kX2NvbHMoYWxsX2RmLCBiZ19nZykNCiAgICByZXR1cm4oYWxsX2RmKQ0KICB9KQ0KfQ0KYGBgDQoNClPhu60gZOG7pW5nIGjDoG0gbsOgeTogDQoNCmBgYHtyfQ0KIyDEkMOhbmggZ2nDoSBz4buxIGJp4bq/biDEkeG7lWkgdGhlbyBt4buZdCBsb+G6oXQgbmfGsOG7oW5nOiANCnNvX3NhbmhfbGlzdCA8LSBsYXBwbHkoc2VxKDAuMSwgMC45MCwgYnkgPSAwLjA1KSwgZXZhbF9mdW4yKQ0Kc29fc2FuaF9kZiA8LSBkby5jYWxsKCJiaW5kX3Jvd3MiLCBzb19zYW5oX2xpc3QpIA0KDQpzb19zYW5oX2RmICU8PiUgDQogIG11dGF0ZShUaHJlc2hvbGQgPSBsYXBwbHkoc2VxKDAuMSwgMC45MCwgYnkgPSAwLjA1KSwgZnVuY3Rpb24oeCkge3JlcCh4LCAxMDApfSkgJT4lIHVubGlzdCgpKQ0KDQpuYW1lcyhzb19zYW5oX2RmKSA8LSBuYW1lcyhzb19zYW5oX2RmKSAlPiUgc3RyX3JlcGxhY2VfYWxsKCIgIiwgIiIpDQoNCiMgQ2jDrW5oIHjDoWMgY2h1bmc6IA0Kc29fc2FuaF9kZiAlPiUgDQogIGdyb3VwX2J5KFRocmVzaG9sZCkgJT4lIA0KICBzdW1tYXJpc2VfZWFjaChmdW5zKG1lZGlhbiksIEFjY3VyYWN5KSAtPiBhY2MNCmBgYA0KDQpOZ8aw4buhbmcgMC41NSB0aMOsIEFjY3VyYWN5IGzDoCBs4bubbiBuaOG6pXQgdsOgIGzDoCB2w6AgNzQlOiANCg0KYGBge3J9DQphY2MgJT4lIHNsaWNlKHdoaWNoLm1heCguJEFjY3VyYWN5KSkNCmBgYA0KDQpOaMawbmcgbmfGsOG7oW5nIG3DoCBwaMOibiBsb+G6oWkgaOG7kyBzxqEgeOG6pXUgdOG7kXQgbmjhuqV0IHRow6wga2jDtG5nIHBo4bqjaSAwLjU1IG3DoCBsw6AgMC4xOiANCg0KYGBge3J9DQpzb19zYW5oX2RmICU+JSANCiAgZ3JvdXBfYnkoVGhyZXNob2xkKSAlPiUgDQogIHN1bW1hcmlzZV9lYWNoKGZ1bnMobWVkaWFuKSwgU2Vuc2l0aXZpdHkpICU+JSANCiAgc2xpY2Uod2hpY2gubWF4KC4kU2Vuc2l0aXZpdHkpKQ0KICANCmBgYA0KDQpDaMO6bmcgdGEgY8OzIHRo4buDIGto4bqjbyBzw6F0IMSR4buTbmcgdGjhu51pIG3hu5l0IHPhu5EgdGnDqnUgY2jDrSBuaOG6sW0gbMOgbSByw7UgaMahbiBz4buxIMSRw6FuaCDEkeG7lWkgYuG6sW5nIGPDtG5nIGPhu6UgaMOsbmgg4bqjbmg6IA0KDQpgYGB7cn0NCnRoZW1lX3NldCh0aGVtZV9taW5pbWFsKCkpICANCg0Kc29fc2FuaF9kZiAlPiUgDQogIGdyb3VwX2J5KFRocmVzaG9sZCkgJT4lIA0KICBzdW1tYXJpc2VfZWFjaChmdW5zKG1lZGlhbiksIEFjY3VyYWN5LCBLYXBwYSwgU2Vuc2l0aXZpdHksIFNwZWNpZmljaXR5KSAlPiUgDQogIGdhdGhlcihNZXRyaWMsIGIsIC1UaHJlc2hvbGQpICU+JSANCiAgZ2dwbG90KGFlcyhUaHJlc2hvbGQsIGIsIGNvbG9yID0gTWV0cmljKSkgKyANCiAgZ2VvbV9saW5lKCkgKyANCiAgZ2VvbV9wb2ludChzaXplID0gMykgKyANCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCkgKyANCiAgdGhlbWUocGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSkgKyANCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLjEsIDAuOSwgYnkgPSAwLjA1KSkgKyANCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC43LCBjb2xvciA9ICJicm93biIsIHNpemUgPSAxKSArIA0KICBsYWJzKHkgPSAiQWNjdXJhY3kgUmF0ZSIsIA0KICAgICAgIHRpdGxlID0gIlZhcmlhdGlvbiBvZiBMb2dpc3RpYyBDbGFzc2lmaWVyJ3MgTWV0cmljcyBieSBUaHJlc2hvbGQiLCANCiAgICAgICBzdWJ0aXRsZSA9ICJEYXRhIFVzZWQ6IEdlcm1hbiBDcmVkaXQgRGF0YSIpDQpgYGANCg0KDQpLaGkgVGhyZXNob2xkIGzDoCAwLjU1IHRow6wgbeG7qWMgY2jDrW5oIHjDoWMgY2h1bmcgY2hvIHBow6JuIGxv4bqhaSDEkcO6bmcgY+G6oyBo4buTIHPGoSB04buRdCBs4bqrbiB44bqldSBsw6AgY2FvIG5o4bqldC4gVsaw4bujdCBxdWEgbmfGsOG7oW5nIDAuNTUgbsOgeSB0aMOsIEFjY3VyYWN5IGzhuqFpIGdp4bqjbS4gDQoNClPhu7EgxJHDoW5oIMSR4buVaSBzYXUgxJHDonkgbMOgIHLDtSByw6BuZzoga2hpIG5nxrDhu6FuZyDEkcaw4bujYyBs4buxYSBjaOG7jW4gdMSDbmcgdGjDrCBraOG6oyBuxINuZyBwaMOibiBsb+G6oWkgY2jDrW5oIHjDoWMgaOG7kyBzxqEgdOG7kXQgIChTcGVjaWZpY2l0eSkgdMSDbmcgbmjGsG5nIMSR4buTbmcgdGjhu51pIHbhu5tpIMSRw7MgbMOgIGto4bqjIG7Eg25nIHBow6JuIGxv4bqhaSBo4buTIHPGoSB44bqldSAoU2Vuc2l0aXZpdHkpIGzhuqFpIGdp4bqjbS4gQ2jDum5nIHRhIGPDsyB0aOG7gyBzbyBzw6FuaCBj4bulIHRo4buDIGx1w7RuIHbhu5tpIG3hu5l0IHPhu5EgbmfGsOG7oW5nOiANCg0KYGBge3J9DQojIFZp4bq/dCBow6BtOiANCm15X2NtX2NvbSA8LSBmdW5jdGlvbih0aHJlKSB7DQogIGR1X2JhbyA8LSBwcmVkaWN0KG15X2xvZ2l0LCB0cmFpbiAlPiUgc2VsZWN0KC1CQUQpLCB0eXBlID0gInByb2IiKSAlPiUgcHVsbChCQUQpDQogIGR1X2JhbyA8LSBjYXNlX3doZW4oZHVfYmFvID49IHRocmUgfiAiQkFEIiwgZHVfYmFvIDwgdGhyZSB+ICJHT09EIikgJT4lIGFzLmZhY3RvcigpDQogIGNtIDwtIGNvbmZ1c2lvbk1hdHJpeChkdV9iYW8sIHRyYWluJEJBRCkNCiAgcmV0dXJuKGNtKQ0KICANCn0NCg0KIyBT4butIGThu6VuZyBow6BtOiANCmxhcHBseShjKDAuMSwgMC40LCAwLjYpLCBteV9jbV9jb20pDQoNCmBgYA0KDQpT4buxIMSRw6FuaCDEkeG7lWkgZ2nhu69hIGPDoWMgdGnDqnUgY2jDrSBwaMOibiBsb+G6oWkgY+G7p2EgbcO0IGjDrG5oIGPDsm4gxJHGsOG7o2MgYmnhu4N1IGRp4buFbiBi4bqxbmcgbeG7mXQgIm5nw7RuIG5n4buvIiBow6xuaCDhuqNuaCB0aMaw4budbmcgxJHGsOG7o2Mgc+G7rSBk4bulbmcgcuG6pXQgcGjhu5UgYmnhur9uIGzDoCDEkcaw4budbmcgUk9DIChST0MgQ3VydmUpIHbDoCBraMOhaSBuaeG7h20gZGnhu4duIHTDrWNoIG7hurFtIGTGsOG7m2kgxJHGsOG7nW5nIGNvbmcgQVVDIMSRxrDhu6NjIG3DtCB04bqjIGNoaSB0aeG6v3QgW+G7nyDEkcOieV0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvUmVjZWl2ZXJfb3BlcmF0aW5nX2NoYXJhY3RlcmlzdGljKS4gDQoNClRoZW8gcXV5IMaw4bubYywgUk9DIGzDoCDEkcaw4budbmcgYmnhu4N1IGRp4buFbiBz4buxIMSRw6FuaCDEkeG7lWkgZ2nhu69hIGto4bqjIHThu4kgbOG7hyBwaMOibiBsb+G6oWkgc2FpIGjhu5Mgc8ahIHThu5F0ICgxIC0gU3BlY2lmaWNpdHkpIHbDoCB04buJIGzhu4cgcGjDom4gbG/huqFpIMSRw7puZyBo4buTIHPGoSB44bqldSAoU2Vuc2l0aXZpdHkpIHTGsMahbmcg4bupbmcgduG7m2kgY8OhYyBuZ8aw4buhbmcga2jDoWMgbmhhdS4gVsOgIGRp4buHbiB0w61jaCBu4bqxbSBkxrDhu5tpIMSRxrDhu51uZyBjb25nIFJPQyBuw6B5IMSRxrDhu6NjIGfhu41pIGzDoCBBVUMuIA0KDQpDaMO6bmcgdGEgY8OzIHRo4buDIHPhu60gZOG7pW5nIG3hu5l0IHPhu5EgaMOgbSBz4bq1biBjw7MgY+G7p2EgUiDEkeG7gyB0w61uaCB0b8OhbiBBVUMgdsOgIGjDrG5oIOG6o25oIGjDs2EgY2hvIFJPQyBuaMawIHNhdTogDQoNCg0KYGBge3J9DQojICBHw7NpIGNobyB0w61uaCB0b8OhbiBBVUM6IA0KbGlicmFyeShwUk9DKSANCg0KIyBWaeG6v3QgaMOgbSB0w61uaCBBVUM6IA0KdGVzdF9hdWMgPC0gZnVuY3Rpb24obW9kZWwsIGRhdGEpIHsNCiAgcm9jKGRhdGEkQkFELCBwcmVkaWN0KG1vZGVsLCB0ZXN0ICU+JSBzZWxlY3QoLUJBRCksIHR5cGUgPSAicHJvYiIpICU+JSBwdWxsKEJBRCkpDQp9DQoNCiMgU+G7rSBk4bulbmcgaMOgbSBuw6B5OiANCm15X2F1YyA8LSB0ZXN0X2F1YyhteV9sb2dpdCwgdGVzdCkNCm15X2F1YyRhdWMNCg0KYGBgDQoNCk3hu5l0IG3DtCBow6xuaCBwaMOibiBsb+G6oWkgbmjhu4sgcGjDom4gKGJp4bq/biDEkcaw4bujYyBwaMOibiBsb+G6oWkgY2jhu4kgY8OzIGhhaSBuaMOjbikgdGjDrCBBVUMgY8OgbmcgbOG7m24gY8OgbmcgdOG7kXQuIE7hur91IEFVQyA9IDAuNSB0aMOsIGto4bqjIG7Eg25nIGThu7EgYsOhbyBj4bunYSBtw7QgaMOsbmggxJHGsOG7o2MgZ+G7jWkgbMOgIHTGsMahbmcgduG7m2kgxJFvw6FuIG5n4bqrdSBuaGnDqm4uIFRyb25nIGjhuqd1IGjhur90IGPDoWMg4bupbmcgZOG7pW5nLCBtw7QgaMOsbmggY8OzIGNo4bqldCBsxrDhu6NuZyBwaMOibiBsb+G6oWkgY2jhuqVwIG5o4bqtbiDEkcaw4bujYyB0aMOsIEFVQyBuw6puIHThu5FpIHRoaeG6v3UgbMOgIGzhu5tuIGjGoW4gaG/hurdjIGLhurFuZyAwLjcuIA0KDQpOZ8aw4budaSB0YSBjw7JuIGPDsyB0aOG7gyBz4butIGThu6VuZyBt4buZdCBiaeG6v24gdGjhu4Mga2jDoWMgY+G7p2EgQVVDIGzDoCBo4buHIHPhu5EgR0lOSSDEkcaw4bujYyB0w61uaCB0aGVvIGPDtG5nIHRo4bupYyAkR0lOSSA9IDIqQVVDIC0gMSQuIETGsOG7m2kgxJHDonkgbMOgIFIgY29kZXMgY2hvIGJp4buDdSBkaeG7hW4gUk9DIC0gxJHGsOG7nW5nIGNvbmcgbMOgbSBjxqEgc+G7nyB0w61uaCB0b8OhbiBBVUMgdsOgIEdJTkk6IA0KDQpgYGB7cn0NCnNlbl9zcGVjX2RmIDwtIGRhdGFfZnJhbWUoVFBSID0gbXlfYXVjJHNlbnNpdGl2aXRpZXMsIEZQUiA9IDEgLSBteV9hdWMkc3BlY2lmaWNpdGllcykNCg0Kc2VuX3NwZWNfZGYgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBGUFIsIHltaW4gPSAwLCB5bWF4ID0gVFBSKSkrDQogIGdlb21fcG9seWdvbihhZXMoeSA9IFRQUiksIGZpbGwgPSAicmVkIiwgYWxwaGEgPSAwLjMpKw0KICBnZW9tX3BhdGgoYWVzKHkgPSBUUFIpLCBjb2wgPSAiZmlyZWJyaWNrIiwgc2l6ZSA9IDEuMikgKw0KICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGNvbG9yID0gImdyYXkzNyIsIHNpemUgPSAxLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArIA0KICB0aGVtZV9idygpICsNCiAgY29vcmRfZXF1YWwoKSArDQogIGxhYnMoeCA9ICJGUFIgKDEgLSBTcGVjaWZpY2l0eSkiLCANCiAgICAgICB5ID0gIlRQUiAoU2Vuc2l0aXZpdHkpIiwgDQogICAgICAgdGl0bGUgPSAiTW9kZWwgUGVyZm9ybWFuY2UgZm9yIExvZ2lzdGljIENsYXNzaWZpZXIgYmFzZWQgb24gVGVzdCBEYXRhIiwgDQogICAgICAgc3VidGl0bGUgPSBwYXN0ZTAoIkFVQyBWYWx1ZTogIiwgbXlfYXVjJGF1YyAlPiUgcm91bmQoMikpKQ0KDQpgYGANCg0KDQojIE9wdGltYWwgVGhyZXNob2xkIHRoYXQgTWF4aW1pemVzIENvbW1lcmNpYWwgQmFuaydzIFByb2ZpdA0KDQpWw6wgY8OhaSBnacOhIHBo4bqjaSB0cuG6oyBraGkgcGjhuqFtIHNhaSBs4bqnbSBsb+G6oWkgMiBsw6AgY2FvIGjGoW4gbmhp4buBdSBzbyB24bubaSBjw6FpIGdpw6EgcGjhuqNpIHRy4bqjIGtoaSBwaOG6oW0gc2FpIGzhuqdtIGxv4bqhaSAxIG7Dqm4gY2jDum5nIHRhIGPDsyBr4bq/dCBsdeG6rW4gcuG6sW5nOiANCg0KMS4gQWNjdXJhY3kga2jDtG5nIHBo4bqjaSBsw6AgdGnDqnUgY2jDrSBs4buxYSBjaOG7jW4gbcO0IGjDrG5oIGhheSBjxINuIGPhu6kgdsOgbyDEkcOzIMSR4buDIMSR4buBIHh14bqldCBuw6puIGhheSBraMO0bmcgbsOqbiBz4butIGThu6VuZy4gDQoyLiBO4bq/dSBjaMO6bmcgdGEgxJFp4buBdSBjaOG7iW5oIG5nxrDhu6FuZyBwaMOibiBsb+G6oWkgaOG7kyBzxqEgdGhlbyBoxrDhu5tuZyBraOG6r3Qga2hlIGjGoW4ga2hpIHBow6JuIGxv4bqhaSBo4buTIHPGoSB44bqldSB0aMOsIG5nw6JuIGjDoG5nIMSR4buRaSBkaeG7h24gduG7m2kgxJHhu5NuZyB0aOG7nWkgaGFpIHbhuqVuIMSR4buBIHNhdTogbeG7mXQgbeG6t3QsIG5nw6JuIGjDoG5nIGPDsyB0aOG7gyB04burIGNo4buRaSBuaGnhu4F1IGjhu5Mgc8ahIHjhuqV1IGjGoW4gbmjGsG5nIG3hurd0IGtow6FjIGPFqW5nIGJ14buZYyBwaOG6o2kgdOG7qyBjaOG7kWkgbmhp4buBdSBjxqEgaOG7mWkga2nhur9tIGzDo2kgaMahbiBraGkgYuG7jyBs4buhIG3huqV0IGtow6FjaCBow6BuZyB0aeG7gW0gbsSDbmcgZG8ga2jDoWNoIGjDoG5nIG7DoHkgdGjhu7FjIHPhu7EgdOG7kXQgbmjGsG5nIG3DtCBow6xuaCBs4bqhaSBwaMOibiBsb+G6oWkgbmjhuqdtIHRow6BuaCBo4buTIHPGoSB44bqldS4gDQoNCsSQaeG7gXUgbsOgeSBn4bujaSDDvSBy4bqxbmcgY2jDum5nIHRhIGPhuqduIHTDrG0gbeG7mXQgbmfGsOG7oW5nIHThu5FpIMawdSBzYW8gY2hvIHThu5VuZyBs4bujaSDDrWNoIHRodSDEkcaw4bujYyBraGkgdOG7qyBjaOG7kWkgY8Ogbmcgbmhp4buBdSBo4buTIHPGoSB44bqldSB2w6AgdGhp4buHdCBo4bqhaSBwaOG6o2kgZ8Ohbmgga2hpIGLhu48gbOG7oSBt4bqldCBjxqEgaOG7mWkga2nhur9tIGzhu51pIGNobyBraMOhY2ggaMOgbmcgdOG7kXQuIA0KDQpDw7Mgbmhp4buBdSBnaeG6o2kgcGjDoXAgY8OzIHRo4buDIMSRxrDhu6NjIMOhcCBk4bulbmcgxJHhu4MgdMOsbSBuZ8aw4buhbmcgdOG7kWkgxrB1IG7DoHkuIE3hu5l0IHRyb25nIG5o4buvbmcgY8OhY2ggxJHDsyBsw6AgY2jDum5nIHRhIHPhu60gZOG7pW5nIG3DtCBwaOG7j25nIE1vbnRlIENhcmxvIHbhu5tpIGPDoWMgZ2nhuqMgdGhp4bq/dCwgxJHGoW4gZ2nhuqNuIG5oxrAgc2F1OiANCg0KMS4gTuG6v3Uga2jDoWNoIGjDoG5nIHThu5F0IG3DoCBtw7QgaMOsbmggcGjDom4gbG/huqFpIMSRw7puZyBsw6AgdOG7kXQgdGjDrCBuZ8OibiBow6BuZyBz4bq9IHRodSDEkcaw4bujYyBsw6NpIDMwJSB0csOqbiBz4buRIHRp4buBbiBjaG8gdmF5LiDEkMOieSBsw6Agbmjhu69uZyBraMOhY2ggaMOgbmcgdGh14buZYyB0w6xuaCBodeG7kW5nIFROIChUcnVlIE5lZ2F0aXZlKS4gDQoNCjIuIE7hur91IGtow6FjaCBow6BuZyBtw6AgeOG6pXUgbmjGsG5nIG3DtCBow6xuaCBs4bqhaSBwaMOibiBsb+G6oWkgc2FpIHRow6BuaCB04buRdCB0aMOsIG5nw6JuIGjDoG5nIHPhur0gbeG6pXQgdHLhuq9uZyB24buRbiBraGkgY2hvIHZheSBraMOhY2ggaMOgbmcgbsOgeS4gxJDDonkgbMOgIG5o4buvbmcga2jDoWNoIGjDoG5nIHRodeG7mWMgdMOsbmggaHXhu5FuZyBGTiAoRmFsc2UgTmVnYXRpdmUpLiANCg0KMy4gU+G7kSB0aeG7gW4gbeG7l2kga2jDoWNoIGjDoG5nIMSRxrDhu6NjIHZheSBjw7MgcGjDom4gcGjhu5FpIHTGsMahbmcgdOG7sSBuaMawIHBow6JuIHBo4buRaSBj4bunYSBo4bqhbiBt4bupYyBtw6AgaOG7jSDEkeG7gSB4deG6pXQgdHJvbmcgY3JlZGl0IGFwcGxpY2F0aW9uLiANCg0KVHLGsOG7m2MgaOG6v3QgY2jDum5nIHRhIHTDrW5oIHRvw6FuIGvhur90IHF14bqjIHBow6JuIGxv4bqhaSBj4bunYSBtw7QgaMOsbmggTG9naXN0aWMgduG7m2kgbeG7mXQgbG/huqF0IGPDoWMgbmfGsOG7oW5nIGtow6FjIG5oYXU6IA0KDQpgYGB7cn0NCmRmX3RoXzE1IDwtIGRvLmNhbGwoImJpbmRfcm93cyIsIGV2YWxfZnVuMigwLjE1KSkgJT4lIG11dGF0ZShUaHIgPSAiVGhyZSA9IDAuMTUiKSANCmRmX3RoXzIwIDwtIGRvLmNhbGwoImJpbmRfcm93cyIsIGV2YWxfZnVuMigwLjIwKSkgJT4lIG11dGF0ZShUaHIgPSAiVGhyZSA9IDAuMjAiKQ0KZGZfdGhfMzAgPC0gZG8uY2FsbCgiYmluZF9yb3dzIiwgZXZhbF9mdW4yKDAuMzApKSAlPiUgbXV0YXRlKFRociA9ICJUaHJlID0gMC4zMCIpDQpkZl90aF80MCA8LSBkby5jYWxsKCJiaW5kX3Jvd3MiLCBldmFsX2Z1bjIoMC40MCkpICU+JSBtdXRhdGUoVGhyID0gIlRocmUgPSAwLjQwIikNCmRmX3RoXzUwIDwtIGRvLmNhbGwoImJpbmRfcm93cyIsIGV2YWxfZnVuMigwLjUwKSkgJT4lIG11dGF0ZShUaHIgPSAiVGhyZSA9IDAuNTAiKQ0KZGZfdGhfNTUgPC0gZG8uY2FsbCgiYmluZF9yb3dzIiwgZXZhbF9mdW4yKDAuNTUpKSAlPiUgbXV0YXRlKFRociA9ICJUaHJlID0gMC41NSIpDQpkZl90aF82MCA8LSBkby5jYWxsKCJiaW5kX3Jvd3MiLCBldmFsX2Z1bjIoMC42MCkpICU+JSBtdXRhdGUoVGhyID0gIlRocmUgPSAwLjYwIikNCmRmX3RoXzY1IDwtIGRvLmNhbGwoImJpbmRfcm93cyIsIGV2YWxfZnVuMigwLjY1KSkgJT4lIG11dGF0ZShUaHIgPSAiVGhyZSA9IDAuNjUiKQ0KDQpgYGANCg0KDQpWaeG6v3QgaMOgbSB0w61uaCB0b8OhbiBs4bujaSBuaHXhuq1uIGThu7FhIHRyw6puIG3DtCBwaOG7j25nIHbhu5tpIGPDoWMgZ2nhuqMgdGhp4bq/dCBuaMawIHRyw6puOiANCg0KYGBge3J9DQojIFZp4bq/dCBow6BtIG3DtCBwaOG7j25nIGzhu6NpIG5odeG6rW46IA0KDQpwcm9maXRfc2ltdTEgPC0gZnVuY3Rpb24oZGF0YV9mcm9tX21vZGVsLCByYXRlLCBzb19sYW5fbW9fcGhvbmcpIHsNCiAga2hvYW5fdmF5IDwtIEdlcm1hbkNyZWRpdCRBbW91bnQNCiAgbl92YXlfdG90IDwtIGRhdGFfZnJvbV9tb2RlbCRUTiAlPiUgc3VtKCkNCiAgbl92YXlfeGF1IDwtIGRhdGFfZnJvbV9tb2RlbCRGTiAlPiUgc3VtKCkNCiAgDQogIHNhcHBseSgxOnNvX2xhbl9tb19waG9uZywgZnVuY3Rpb24oeCkgew0KICAgIHNldC5zZWVkKHgpDQogICAgc29fdGllbl9jaG9fdmF5X3RvdCA8LSBzYW1wbGUoa2hvYW5fdmF5LCBuX3ZheV90b3QsIHJlcGxhY2UgPSBUUlVFKQ0KICAgIHNvX3RpZW5fY2hvX3ZheV94YXUgPC0gc2FtcGxlKGtob2FuX3ZheSwgbl92YXlfeGF1LCByZXBsYWNlID0gVFJVRSkNCiAgICBsb2lfbmh1YW4gPC0gc3VtKHJhdGUqc29fdGllbl9jaG9fdmF5X3RvdCkgLSBzdW0oc29fdGllbl9jaG9fdmF5X3hhdSkNCiAgICANCiAgfSkNCn0NCmBgYA0KDQpT4butIGThu6VuZyBow6BtIG3DtCBwaOG7j25nIGzhu6NpIG5odeG6rW46IA0KDQpgYGB7cn0NCiMgU+G7rSBk4bulbmcgaMOgbSB24bubaSA1MDAwIGzhuqduIG3DtCBwaOG7j25nLCBsw6NpIHN14bqldCAzMCU6IA0KcHJvZml0X3NpbXUxKGRhdGFfZnJvbV9tb2RlbCA9IGRmX3RoXzE1LCANCiAgICAgICAgICAgICByYXRlID0gMC4zMCwgDQogICAgICAgICAgICAgc29fbGFuX21vX3Bob25nID0gNTAwMCkgLT4gcDENCg0KcHJvZml0X3NpbXUxKGRhdGFfZnJvbV9tb2RlbCA9IGRmX3RoXzIwLCANCiAgICAgICAgICAgICByYXRlID0gMC4zMCwgDQogICAgICAgICAgICAgc29fbGFuX21vX3Bob25nID0gNTAwMCkgLT4gcDINCg0KcHJvZml0X3NpbXUxKGRhdGFfZnJvbV9tb2RlbCA9IGRmX3RoXzMwLCANCiAgICAgICAgICAgICByYXRlID0gMC4zMCwgDQogICAgICAgICAgICAgc29fbGFuX21vX3Bob25nID0gNTAwMCkgLT4gcDMNCg0KcHJvZml0X3NpbXUxKGRhdGFfZnJvbV9tb2RlbCA9IGRmX3RoXzQwLCANCiAgICAgICAgICAgICByYXRlID0gMC4zMCwgDQogICAgICAgICAgICAgc29fbGFuX21vX3Bob25nID0gNTAwMCkgLT4gcDQNCg0KcHJvZml0X3NpbXUxKGRhdGFfZnJvbV9tb2RlbCA9IGRmX3RoXzUwLCANCiAgICAgICAgICAgICByYXRlID0gMC4zMCwgDQogICAgICAgICAgICAgc29fbGFuX21vX3Bob25nID0gNTAwMCkgLT4gcDUNCg0KcHJvZml0X3NpbXUxKGRhdGFfZnJvbV9tb2RlbCA9IGRmX3RoXzU1LCANCiAgICAgICAgICAgICByYXRlID0gMC4zMCwgDQogICAgICAgICAgICAgc29fbGFuX21vX3Bob25nID0gNTAwMCkgLT4gcDU1DQoNCnByb2ZpdF9zaW11MShkYXRhX2Zyb21fbW9kZWwgPSBkZl90aF82MCwgDQogICAgICAgICAgICAgcmF0ZSA9IDAuMzAsIA0KICAgICAgICAgICAgIHNvX2xhbl9tb19waG9uZyA9IDUwMDApIC0+IHA2DQoNCnByb2ZpdF9zaW11MShkYXRhX2Zyb21fbW9kZWwgPSBkZl90aF82NSwgDQogICAgICAgICAgICAgcmF0ZSA9IDAuMzAsIA0KICAgICAgICAgICAgIHNvX2xhbl9tb19waG9uZyA9IDUwMDApIC0+IHA2NQ0KDQpuIDwtIGxlbmd0aChwMSkNCg0KDQojIFThuqFvIHJhIERGIHbhu4EgbOG7o2kgbmh14bqtbiBjaG8gbeG7pWMgxJHDrWNoIMSRw6FuaCBnacOhIHbDoCBzbyBzw6FuaDogDQpkZl9mb3JfY29tcCA8LSBiaW5kX3Jvd3MoZGF0YV9mcmFtZShQcm9maXQgPSBwMSwgVGhyZXNob2xkID0gcmVwKCJUaHIgPSAwLjE1IiwgbikpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhX2ZyYW1lKFByb2ZpdCA9IHAyLCBUaHJlc2hvbGQgPSByZXAoIlRociA9IDAuMjAiLCBuKSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfZnJhbWUoUHJvZml0ID0gcDMsIFRocmVzaG9sZCA9IHJlcCgiVGhyID0gMC4zMCIsIG4pKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgZGF0YV9mcmFtZShQcm9maXQgPSBwNCwgVGhyZXNob2xkID0gcmVwKCJUaHIgPSAwLjQwIiwgbikpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhX2ZyYW1lKFByb2ZpdCA9IHA1LCBUaHJlc2hvbGQgPSByZXAoIlRociA9IDAuNTAiLCBuKSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfZnJhbWUoUHJvZml0ID0gcDU1LCBUaHJlc2hvbGQgPSByZXAoIlRociA9IDAuNTUiLCBuKSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfZnJhbWUoUHJvZml0ID0gcDYsIFRocmVzaG9sZCA9IHJlcCgiVGhyID0gMC42MCIsIG4pKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgZGF0YV9mcmFtZShQcm9maXQgPSBwNjUsIFRocmVzaG9sZCA9IHJlcCgiVGhyID0gMC42NSIsIG4pKSkNCmBgYA0KDQpDw7MgdGjhu4MgdGjhuqV5IHbhu5tpIG3hu6VjIHRpw6p1IGzDoCB04buRaSDEkWEgaMOzYSBs4bujaSBuaHXhuq1uIHRow6wgbmfDom4gaMOgbmcgbsOqbiBz4butIGThu6VuZyBuZ8aw4buhbmcgMC4yIGNobyBtw7QgaMOsbmggcGjDom4gbG/huqFpIExvZ2lzdGljLiBD4bqnbiBsxrB1IMO9IHLhurFuZyB04bqhaSBuZ8aw4buhbmcgdOG7kWkgxJFhIGjDs2EgQWNjdXJhY3kgKGzDoCAwLjU1KSB0aMOsIMSRw6J5IGtow7RuZyBwaOG6o2kgbMOgIG5nxrDhu6FuZyB04buRaSDGsHUgaMOzYSBs4bujaSBuaHXhuq1uOiANCg0KYGBge3J9DQpkZl9mb3JfY29tcCAlPiUgDQogIGdyb3VwX2J5KFRocmVzaG9sZCkgJT4lIA0KICBzdW1tYXJpc2VfZWFjaChmdW5zKG1lYW4sIG1lZGlhbiwgbWluLCBtYXgsIHNkKSwgUHJvZml0KSAlPiUgDQogIGFycmFuZ2UoLW1lYW4pICU+JSANCiAgbXV0YXRlX2lmKGlzLm51bWVyaWMsIGZ1bmN0aW9uKHgpIHtyb3VuZCh4LCAwKX0pICU+JSANCiAga25pdHI6OmthYmxlKCkNCmBgYA0KDQoNCk5ndXnDqm4gbmjDom4gbcOgIGNow7puZyB0YSDEkcOjIGJp4bq/dCBsw6AgdOG6oWkgbmfGsOG7oW5nIHThu5FpIMSRYSBow7NhIEFjY3VyYWN5IG7DoHkgdGjDrCBjxaluZyBsw6AgbmfGsOG7oW5nIG3DoCB04bqhbyByYSBuaGnhu4F1IHNhaSBs4bqnbSBsb+G6oWkgMiBoxqFuIG5oxrAgY2jDum5nIHRhIGPDsyB0aOG7gyB0aOG6pXk6IA0KDQpgYGB7cn0NCmxhcHBseShjKDAuMiwgMC41NSksIG15X2NtX2NvbSkNCmBgYA0KDQoNCkPDoWMga+G6v3QgbHXhuq1uIHRyw6puIGPDsyB0aOG7gyB0w7NtIHThuq90IHRoZW8gbeG7mXQgY8OhY2gga2jDoWMgYuG7n2kgY8O0bmcgY+G7pSBow6xuaCDhuqNuaDogDQoNCmBgYHtyfQ0KIyBIw6xuaCDhuqNuaCBow7NhIGzhu6NpIG5odeG6rW4gdsOgIHBow6JuIHBo4buRaSBs4bujaSBuaHXhuq1uOiANCmRmX2Zvcl9jb21wICU+JSANCiAgZ2dwbG90KGFlcyhQcm9maXQgLyAxMDAwKSkgKyANCiAgZ2VvbV9kZW5zaXR5KGZpbGwgPSAicmVkIiwgY29sb3IgPSAicmVkIiwgYWxwaGEgPSAwLjMpICsgDQogIGdlb21faGlzdG9ncmFtKGFlcyh5ID0gLi5kZW5zaXR5Li4pLCBjb2xvciA9ICJibHVlIiwgZmlsbCA9ICJibHVlIiwgYWxwaGEgPSAwLjMpICsgDQogIGZhY2V0X3dyYXAofiBUaHJlc2hvbGQsIHNjYWxlcyA9ICJmcmVlIikgKyANCiAgbGFicyh4ID0gTlVMTCwgeSA9IE5VTEwsIA0KICAgICAgIHRpdGxlID0gIlNpbXVsYXRlZCBQcm9maXQgQmFzZWQgTW9udGUgQ2FybG8gTWV0aG9kIGJ5IFRocmVzaG9sZCBmb3IgTG9naXN0aWMgTW9kZWwiLCANCiAgICAgICBzdWJ0aXRsZSA9ICJEYXRhIFVzZWQ6IEdlcm1hbiBDcmVkaXQgRGF0YSIpIA0KYGBgDQoNCkPFqW5nIGPDsyB0aOG7gyB0aOG6pXkgbOG7o2kgbmh14bqtbiB0cnVuZyBiw6xuaCBsw6AgbeG7mXQgaMOgbSBi4bqtYyBoYWkgaMOsbmggY2jhu68gVSBuZ8aw4bujYzogDQoNCmBgYHtyfQ0KZGZfZm9yX2NvbXAgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBUaHJlc2hvbGQsIHkgPSBQcm9maXQgLyAxMDAwLCBmaWxsID0gVGhyZXNob2xkLCBjb2xvciA9IFRocmVzaG9sZCkpICsgDQogIGdlb21fYm94cGxvdChhbHBoYSA9IDAuMykgKyANCiAgbGFicyh4ID0gTlVMTCwgeSA9IE5VTEwsIA0KICAgICAgIHRpdGxlID0gIlNpbXVsYXRlZCBQcm9maXQgQmFzZWQgTW9udGUgQ2FybG8gTWV0aG9kIGJ5IFRocmVzaG9sZCBmb3IgTG9naXN0aWMgTW9kZWwiLCANCiAgICAgICBzdWJ0aXRsZSA9ICJEYXRhIFVzZWQ6IEdlcm1hbiBDcmVkaXQgRGF0YSIpDQpgYGANCg0KIyBDb21wYXJlIHdpdGggUHJvZml0IHRoYXQgUmVzdWx0cyBmcm9tIHNvbWUgTWFjaGluZSBMZWFybmluZyBBcHByb2FjaGVzDQoNCkPDoWMgbmdoacOqbiBj4bupdSDEkcOjIGNo4buJIHJhIHLhurFuZyBjw6FjIGPDoWNoIHRp4bq/cCBj4bqtbiBj4bunYSBNYWNoaW5lIExlYXJuaW5nIGNow61uaCB4w6FjIGjGoW4gTG9naXN0aWMga2hpIGThu7EgYsOhbyBQRC4gxJBp4buBdSBuw6B5IGPFqW5nIGjDoG0gw70gcuG6sW5nIHPhu60gZOG7pW5nIGPDoWMgY8OhY2ggdGnhur9wIGPhuq1uIGPhu6dhIE1MIGPDsyB0aOG7gyBjaG8ga+G6v3QgcXXhuqMgbOG7o2kgbmh14bqtbiB04buRdCBoxqFuIMSR4buRaSB24bubaSBuZ8OibiBow6BuZy4gDQoNCk5oxrAgxJHDoyBiaeG6v3Qg4bufIG3hu6VjIHRyw6puLCB24bubaSBUaHJlc2hvbGQgPSAwLjIgdGjDrCBuZ8OibiBow6BuZyBz4bq9IMSR4bqhdCDEkcaw4bujYyBt4bulYyB0acOqdSB04buRaSDEkWEgaMOzYSBs4bujaSBuaHXhuq1uIHbDoCBsw6AgMTY1MTQ2OCBraGkgc+G7rSBk4bulbmcgTG9naXN0aWMuIA0KDQpO4bq/dSBz4butIGThu6VuZyBjaMOtbmggbmfGsOG7oW5nIG7DoHkgdsOgIHPhu60gZOG7pW5nIG3hu5l0IGPDoWNoIHRp4bq/cCBj4bqtbiBraMOhYyBj4bunYSBNTCB0aMOsIGzhu6NpIG5odeG6rW4gbcO0IHBo4buPbmcgY8OzIGjhu4cgcXXhuqMgdOG7qyBz4butIGThu6VuZyBt4buZdCBz4buRIGPDoWNoIHRp4bq/cCBj4bqtbiBj4bunYSBNTCBjaG8gcGjDom4gbG/huqFpIGPDsyB0aOG7gyBjYW8gaMahbi4gDQoNClIgY29kZXMgZMaw4bubaSDEkcOieSBjaMO6bmcgdGEgaHXhuqVuIGx1eeG7h24gxJHhu5NuZyB0aOG7nWkgNSBtw7QgaMOsbmggTUwgdsOgIGPhuqMgTG9naXN0aWMsIHRyb25nIMSRw7MgTG9naXN0aWMgxJHDs25nIHZhaSB0csOyIG5odSBiYXNlIGxpbmUgxJHhu4Mgc28gc8Ohbmg6IA0KDQpgYGB7cn0NCnNldC5zZWVkKDEpDQpudW1iZXIgPC0gMw0KcmVwZWF0cyA8LSA1DQoNCmNvbnRyb2wgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwgDQogICAgICAgICAgICAgICAgICAgICAgICBudW1iZXIgPSBudW1iZXIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgcmVwZWF0cyA9IHJlcGVhdHMsIA0KICAgICAgICAgICAgICAgICAgICAgICAgY2xhc3NQcm9icyA9IFRSVUUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgc2F2ZVByZWRpY3Rpb25zID0gImZpbmFsIiwgDQogICAgICAgICAgICAgICAgICAgICAgICBpbmRleCA9IGNyZWF0ZVJlc2FtcGxlKHRyYWluJEJBRCwgbnVtYmVyKnJlcGVhdHMpLCANCiAgICAgICAgICAgICAgICAgICAgICAgIHN1bW1hcnlGdW5jdGlvbiA9IG11bHRpQ2xhc3NTdW1tYXJ5LCANCiAgICAgICAgICAgICAgICAgICAgICAgIGFsbG93UGFyYWxsZWwgPSBUUlVFKQ0KDQojIFVzZSBQYXJhbGxlbCBjb21wdXRpbmcgKEkgdXNlIDggQ1BVIGNvcmVzIGZvciB0cmFpbmluZyBNTCBNb2RlbHMpOiANCmxpYnJhcnkoZG9QYXJhbGxlbCkNCnJlZ2lzdGVyRG9QYXJhbGxlbChjb3JlcyA9IDgpDQoNCiMgU2ltdWx0YW5lb3VzbHkgdHJhaW4gc29tZSBtYWNoaW5lIGxlYXJuaW5nIG1vZGVsczogDQpsaWJyYXJ5KGNhcmV0RW5zZW1ibGUpDQpzZXQuc2VlZCgxKQ0KbXlfbW9kZWxzIDwtIGMoInJmIiwgImFkYWJvb3N0IiwgImtubiIsICJzdm1SYWRpYWwiLCAibmIiLCAiZ2xtIikNCg0Kc3lzdGVtLnRpbWUobW9kZWxfbGlzdDEgPC0gY2FyZXRMaXN0KEJBRCB+LiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGNvbnRyb2wsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0cmljID0gIkFjY3VyYWN5IiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kTGlzdCA9IG15X21vZGVscykpDQoNCiMgRXh0cmFjdCBhbGwgcmVzdWx0cyBmcm9tIE1MIG1vZGVscyBhbmQgY29tcGFyZSBiYXNlZCBvbiAxMiBtZWFzdXJlcyBmb3IgZXZhbHVhdGluZyBjbGFzc2lmaWNhdGlvbiBwZXJmb3JtYW5jZToNCg0KbGlzdF9vZl9yZXN1bHRzIDwtIGxhcHBseShteV9tb2RlbHMsIGZ1bmN0aW9uKHgpIHttb2RlbF9saXN0MVtbeF1dJHJlc2FtcGxlfSkNCg0KIyBDb252ZXJ0IHRvIGRhdGEgZnJhbWU6IA0KdG90YWxfZGYgPC0gZG8uY2FsbCgiYmluZF9yb3dzIiwgbGlzdF9vZl9yZXN1bHRzKQ0KdG90YWxfZGYgJTw+JSBtdXRhdGUoTW9kZWwgPSBsYXBwbHkobXlfbW9kZWxzLCBmdW5jdGlvbih4KSB7cmVwKHgsIG51bWJlcipyZXBlYXRzKX0pICU+JSB1bmxpc3QoKSkNCg0KYGBgDQoNClBsb3QgZMaw4bubaSDEkcOieSBjaOG7iSByYSBy4bqxbmcsIHbDrSBk4bulLCBSYW5kb20gRm9yZXN0cyBjaGnhur9tIMawdSB0aOG6vyBzbyB24bubaSBMb2dpc3RpYyDhu58gaOG6p3UgaOG6v3QgY8OhYyB0acOqdSBjaMOtOiANCg0KYGBge3J9DQojIENvbXBhcmUgbW9kZWwgcGVyZm9ybWFuY2UgYmFzZWQgb24gMTIgbWVhc3VyZXM6DQoNCnRvdGFsX2RmICU+JSANCiAgc2VsZWN0KC1sb2dMb3NzLCAtcHJBVUMsIC1SZXNhbXBsZSkgJT4lIA0KICBnYXRoZXIoYSwgYiwgLU1vZGVsKSAlPiUgDQogIGdncGxvdChhZXMoTW9kZWwsIGIsIGZpbGwgPSBNb2RlbCwgY29sb3IgPSBNb2RlbCkpICsgDQogIGdlb21fYm94cGxvdChzaG93LmxlZ2VuZCA9IEZBTFNFLCBhbHBoYSA9IDAuMykgKyANCiAgZmFjZXRfd3JhcCh+IGEsIHNjYWxlcyA9ICJmcmVlIikgKyANCiAgY29vcmRfZmxpcCgpICsgDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsgDQogIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChjKDEsIDEsIDEsIDEpLCAiY20iKSkgKyANCiAgbGFicyh4ID0gTlVMTCwgeSA9IE5VTEwsIA0KICAgICAgIHRpdGxlID0gIk1vZGVsIFBlcmZvcm1hbmNlIEJhc2VkIG9uIDEyIENyaXRlcmlhIGZvciBBbHRlcm5hdGl2ZSBNTCBNb2RlbHMiLCANCiAgICAgICBzdWJ0aXRsZSA9ICJWYWxkYXRpb24gTWV0aG9kIFVzZWQ6IENyb3NzLXZhbGlkYXRpb24iKQ0KYGBgDQoNCkNow7puZyB0YSBjw7MgdGjhu4MgcGjDom4gdMOtY2ggY2hpIHRp4bq/dCBoxqFuIEFVQyBj4bunYSBoYWkgbcO0IGjDrG5oIHRyw6puIGLhu5kgZOG7ryBsaeG7h3UgdGVzdDogDQoNCmBgYHtyfQ0KIyBDb21wYXJlIEFVQzogDQpteV9yZiA8LSBtb2RlbF9saXN0MSRyZg0KbW9kZWxfbGlzdCA8LSBsaXN0KExvZ2lzdGljID0gbXlfbG9naXQsIFJGID0gbXlfcmYpDQptb2RlbF9saXN0X3JvYyA8LSBtb2RlbF9saXN0ICU+JSBtYXAodGVzdF9hdWMsIGRhdGEgPSB0ZXN0KQ0KDQojIG1hcCgxOjIsIGZ1bmN0aW9uKHgpIHttb2RlbF9saXN0X3JvY1tbeF1dICU+JSAuW1siYXVjIl1dfSkNCm1vZGVsX2xpc3Rfcm9jICU+JSBtYXAoYXVjKQ0KYGBgDQoNCkvhur90IHF14bqjIGNo4buJIHJhIHLhurFuZyBBVUMgY+G7p2EgUkYgY2FvIGjGoW4uIMSQw6J5IGzDoCBt4buZdCBk4bqldSBoaeG7h3UgY2hvIHRo4bqleTogbuG6v3Ugc+G7rSBk4bulbmcgUkYgY2hvIHBow6JuIGxv4bqhaSB0aMOsIHPhur0gxJHhuqF0IGzhu6NpIG5odeG6rW4gInThu5FpIMawdSIgY2FvIGjGoW4uIENo4buvIHThu5FpIMawdSDhu58gxJHDonkgxJHGsOG7o2MgY2hvIHbDoG8gbmdv4bq3YyBrw6lwIHbhu5tpIGjDoG0gw70gcuG6sW5nOiBjaMO6bmcgdGEgc+G7rSBk4bulbmcgbmfGsOG7oW5nIHThu5FpIMawdSBjaG8gTG9naXN0aWMgY2jhu6kgY2jGsGEgcGjhuqNpIGNobyBSRi4gDQoNCkNow7puZyB0YSBjw7MgdGjhu4MgduG6vSBST0MgY2hvIGPhuqMgaGFpIG3DtCBow6xuaDogDQoNCmBgYHtyfQ0KIyBEcmFmdCBST0MgY3VydmVzOg0KDQpyZXN1bHRzX2xpc3Rfcm9jIDwtIGxpc3QoKQ0KbnVtX21vZCA8LSAxDQoNCmZvcih0aGVfcm9jIGluIG1vZGVsX2xpc3Rfcm9jKXsNCiAgcmVzdWx0c19saXN0X3JvY1tbbnVtX21vZF1dIDwtIA0KICBkYXRhX2ZyYW1lKFRQUiA9IHRoZV9yb2Mkc2Vuc2l0aXZpdGllcywNCiAgICAgICAgICAgICBGUFIgPSAxIC0gdGhlX3JvYyRzcGVjaWZpY2l0aWVzLA0KICAgICAgICAgICAgIE1vZGVsID0gbmFtZXMobW9kZWxfbGlzdClbbnVtX21vZF0pDQogIG51bV9tb2QgPC0gbnVtX21vZCArIDENCiAgfQ0KDQpyZXN1bHRzX2RmX3JvYyA8LSBiaW5kX3Jvd3MocmVzdWx0c19saXN0X3JvYykNCg0KIyBQbG90IFJPQyBjdXJ2ZSBmb3IgMiBtb2RlbHM6DQoNCnJlc3VsdHNfZGZfcm9jICU+JSANCiAgZ2dwbG90KGFlcyhGUFIsIFRQUiwgY29sb3IgPSBNb2RlbCkpICsNCiAgZ2VvbV9saW5lKHNpemUgPSAxKSArDQogIHRoZW1lX2J3KCkgKw0KICBjb29yZF9lcXVhbCgpICsNCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBjb2xvciA9ICJncmF5MzciLCBzaXplID0gMSwgbGluZXR5cGUgPSAiZGFzaGVkIikgKyANCiAgbGFicyh4ID0gIkZQUiAoMSAtIFNwZWNpZmljaXR5KSIsIA0KICAgICAgIHkgPSAiVFBSIChTZW5zaXRpdml0eSkiLCANCiAgICAgICB0aXRsZSA9ICJNb2RlbCBQZXJmb3JtYW5jZSBiYXNlZCBvbiBUZXN0IERhdGEgYnkgTW9kZWwgU2VsZWN0ZWQiKQ0KYGBgDQoNClTGsMahbmcgdOG7sSBjaMO6bmcgdGEgdmnhur90IGjDoG0gxJHDoW5oIGdpw6Ega+G6v3QgcXXhuqMgcGjDom4gbG/huqFpIGPhu6dhIFJGIHRyw6puIDEwMCBs4bqnbiBjaOG7jW4gbeG6q3UsIG3hu5dpIGzhuqduIDEwMCBxdWFuIHPDoXQ6IA0KDQpgYGB7cn0NCg0KZXZhbF9mdW4yX3JmIDwtIGZ1bmN0aW9uKHRocmUpIHsNCiAgbGFwcGx5KDE6MTAwLCBmdW5jdGlvbih4KSB7DQogIHNldC5zZWVkKHgpDQogIGlkIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oeSA9IHRlc3QkQkFELCBwID0gMTAwIC8gbnJvdyh0ZXN0KSwgbGlzdCA9IEZBTFNFKQ0KICB0ZXN0X2RmIDwtIHRlc3RbaWQsIF0NCg0KICBkdV9iYW8gPC0gcHJlZGljdChteV9yZiwgdGVzdF9kZiwgdHlwZSA9ICJwcm9iIikgJT4lIHB1bGwoQkFEKQ0KICBkdV9iYW8gPC0gY2FzZV93aGVuKGR1X2JhbyA+PSB0aHJlIH4gIkJBRCIsIGR1X2JhbyA8IHRocmUgfiAiR09PRCIpICU+JSBhcy5mYWN0b3IoKQ0KDQogIGNtIDwtIGNvbmZ1c2lvbk1hdHJpeChkdV9iYW8sIHRlc3RfZGYkQkFELCBwb3NpdGl2ZSA9ICJCQUQiKQ0KDQogIGJnX2dnIDwtIGNtJHRhYmxlICU+JSANCiAgYXMudmVjdG9yKCkgJT4lIA0KICBtYXRyaXgobmNvbCA9IDQpICU+JSANCiAgYXMuZGF0YS5mcmFtZSgpDQoNCiAgbmFtZXMoYmdfZ2cpIDwtIGMoIlRQIiwgIkZOIiwgIkZQIiwgIlROIikNCg0KICBrcSA8LSBjKGNtJG92ZXJhbGwsIGNtJGJ5Q2xhc3MpIA0KICB0ZW4gPC0ga3EgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgcm93Lm5hbWVzKCkNCg0KICBrcSAlPiUgDQogICAgYXMudmVjdG9yKCkgJT4lIA0KICAgIG1hdHJpeChuY29sID0gMTgpICU+JSANCiAgICBhcy5kYXRhLmZyYW1lKCkgLT4gYWxsX2RmDQoNCiAgbmFtZXMoYWxsX2RmKSA8LSB0ZW4NCiAgYWxsX2RmIDwtIGJpbmRfY29scyhhbGxfZGYsIGJnX2dnKQ0KICByZXR1cm4oYWxsX2RmKX0pDQp9DQoNCiMgU+G7rSBk4bulbmcgaMOgbTogDQpkZl90aF8yMF9yZiA8LSBkby5jYWxsKCJiaW5kX3Jvd3MiLCBldmFsX2Z1bjJfcmYoMC4yMCkpICU+JSBtdXRhdGUoVGhyID0gIlRocmUgPSAwLjIwIikNCmBgYA0KDQpTbyBzw6FuaCBs4bujaSBuaHXhuq1uIG3DtCBwaOG7j25nIGdp4buvYSBoYWkgY8OhY2ggdGnhur9wIGPhuq1uIGzDoCBMb2dpc3RpYyB2w6AgUkYgY2jDum5nIHRhIGPDsyB0aOG7gyB0aOG6pXkgc+G7rSBk4bulbmcgUkYgbWFuZyBs4bqhaSBuaGnhu4F1IGzhu6NpIG5odeG6rW4gaMahbiBjaG8gbmfDom4gaMOgbmc6IA0KDQpgYGB7cn0NCnByb2ZpdF9zaW11MShkYXRhX2Zyb21fbW9kZWwgPSBkZl90aF8yMCwgcmF0ZSA9IDAuMzAsIA0KICAgICAgICAgICAgIHNvX2xhbl9tb19waG9uZyA9IDUwMDApIC0+IHAyDQoNCnByb2ZpdF9zaW11MShkYXRhX2Zyb21fbW9kZWwgPSBkZl90aF8yMF9yZiwgcmF0ZSA9IDAuMzAsIA0KICAgICAgICAgICAgIHNvX2xhbl9tb19waG9uZyA9IDUwMDApIC0+IHAyMF9yZl9wcm9maXQNCg0KY29tcGFyZV9yZl9sb2dpdCA8LSBkYXRhX2ZyYW1lKFByb2ZpdCA9IGMocDIsIHAyMF9yZl9wcm9maXQpLCANCk1vZGVsID0gcmVwKGMoIkxvZ2lzdGljIiwgIlJhbmRvbSBGb3Jlc3RzIiksIGVhY2ggPSA1MDAwLCB0aW1lcyA9IDEpKQ0KDQpjb21wYXJlX3JmX2xvZ2l0ICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gTW9kZWwsIHkgPSBQcm9maXQgLyAxMDAwLCBmaWxsID0gTW9kZWwsIGNvbG9yID0gTW9kZWwpKSArIA0KICBnZW9tX2JveHBsb3QoYWxwaGEgPSAwLjMpICsgDQogIGxhYnMoeCA9IE5VTEwsIHkgPSBOVUxMLCANCiAgdGl0bGUgPSAiU2ltdWxhdGVkIFByb2ZpdCBCYXNlZCBNb250ZSBDYXJsbyBNZXRob2QgYnkgTWV0aG9kIFNlbGVjdGVkIiwgDQogIHN1YnRpdGxlID0gIlRocmVzaG9sZDogMC4yIikNCmBgYA0KDQpDaGkgdGnhur90IGjGoW4gY8OhYyB24buBIGPDoWMgdGnDqnUgY2jDrSB0aOG7kW5nIGvDqiBj4bunYSBs4bujaSBuaHXhuq1uIGdp4buvYSBoYWkgY8OhY2ggdGnhur9wIGPhuq1uOiANCg0KYGBge3J9DQpjb21wYXJlX3JmX2xvZ2l0ICU+JSANCiAgZ3JvdXBfYnkoTW9kZWwpICU+JSANCiAgc3VtbWFyaXNlX2VhY2goZnVucyhtZWFuLCBtZWRpYW4sIG1pbiwgbWF4LCBzZCksIFByb2ZpdCkgJT4lIA0KICBhcnJhbmdlKC1tZWFuKSAlPiUgDQogIG11dGF0ZV9pZihpcy5udW1lcmljLCBmdW5jdGlvbih4KSB7cm91bmQoeCwgMCl9KSAlPiUgDQogIGtuaXRyOjprYWJsZSgpDQpgYGANCg0KQ2jDuiDDvSBy4bqxbmcgbOG7o2kgbmh14bqtbiB0cnVuZyBiw6xuaCAxOTE0MTI0IG7DoHkgY8OzIHRo4buDIHThuqFtIGfhu41pIGzDoCB04buRaSDGsHUuIFbDrCBuZ8aw4buhbmcgdOG7kWkgxrB1IHPhu60gZOG7pW5nIGNobyBtw7QgcGjhu49uZyBsw6AgIm3GsOG7o24iIHThu6sgbmfGsOG7oW5nIHThu5FpIMawdSBj4bunYSBMb2dpc3RpYy4gQ8OzIHRo4buDIHThu5NuIHThuqFpIG3hu5l0IG5nxrDhu6FuZyB04buRaSDGsHUgY2hvIFJGIHbDoCBuZ8aw4buhbmcgbsOgeSBjw7MgdGjhu4Mga2jDoWMgMC4yLiAgDQoNCiMgT3B0aW1hbCBUaHJlc2hvbGQgZm9yIFJhbmRvbSBGb3Jlc3RzDQoNClTGsMahbmcgdOG7sSBuaMawIMSRw6MgbMOgbSB24bubaSBMb2dpc3RpYyBNb2RlbCBjaMO6bmcgdGEgY8OzIHRo4buDIHTDrG0gbmfGsOG7oW5nIHThu5FpIMawdSBow7NhIGzhu6NpIG5odeG6rW4ga2hpIHPhu60gZOG7pW5nIFJGLiBOZ8aw4buhbmcgdOG7kWkgxrB1IGNobyBSRiBsw6AgdHLDuW5nIGjhu6NwIHbhu5tpIG5nxrDhu6FuZyB04buRaSDGsHUgY+G7p2EgTG9naXN0aWMgbmjGsCB0YSBjw7MgdGjhu4MgdGjhuqV5OiANCg0KYGBge3J9DQojIFTDrW5oIHRvw6FuIGvhur90IHF14bqjIHBow6JuIGxv4bqhaSBj4bunYSBSRiB24bubaSBt4buZdCBsb+G6oXQgbmfGsOG7oW5nOiANCmRmX3RoXzE1X3JmIDwtIGRvLmNhbGwoImJpbmRfcm93cyIsIGV2YWxfZnVuMl9yZigwLjE1KSkgJT4lIG11dGF0ZShUaHIgPSAiVGhyZSA9IDAuMTUiKSANCmRmX3RoXzIwX3JmIDwtIGRvLmNhbGwoImJpbmRfcm93cyIsIGV2YWxfZnVuMl9yZigwLjIwKSkgJT4lIG11dGF0ZShUaHIgPSAiVGhyZSA9IDAuMjAiKQ0KZGZfdGhfMzBfcmYgPC0gZG8uY2FsbCgiYmluZF9yb3dzIiwgZXZhbF9mdW4yX3JmKDAuMzApKSAlPiUgbXV0YXRlKFRociA9ICJUaHJlID0gMC4zMCIpDQpkZl90aF80MF9yZiA8LSBkby5jYWxsKCJiaW5kX3Jvd3MiLCBldmFsX2Z1bjJfcmYoMC40MCkpICU+JSBtdXRhdGUoVGhyID0gIlRocmUgPSAwLjQwIikNCmRmX3RoXzUwX3JmIDwtIGRvLmNhbGwoImJpbmRfcm93cyIsIGV2YWxfZnVuMl9yZigwLjUwKSkgJT4lIG11dGF0ZShUaHIgPSAiVGhyZSA9IDAuNTAiKQ0KZGZfdGhfNTVfcmYgPC0gZG8uY2FsbCgiYmluZF9yb3dzIiwgZXZhbF9mdW4yX3JmKDAuNTUpKSAlPiUgbXV0YXRlKFRociA9ICJUaHJlID0gMC41NSIpDQpkZl90aF82MF9yZiA8LSBkby5jYWxsKCJiaW5kX3Jvd3MiLCBldmFsX2Z1bjJfcmYoMC42MCkpICU+JSBtdXRhdGUoVGhyID0gIlRocmUgPSAwLjYwIikNCmRmX3RoXzY1X3JmIDwtIGRvLmNhbGwoImJpbmRfcm93cyIsIGV2YWxfZnVuMl9yZigwLjY1KSkgJT4lIG11dGF0ZShUaHIgPSAiVGhyZSA9IDAuNjUiKQ0KDQojIFThuqFvIERGIGNobyBzbyBzw6FuaDogDQpwcm9maXRfc2ltdTEoZGF0YV9mcm9tX21vZGVsID0gZGZfdGhfMTVfcmYsIHJhdGUgPSAwLjMwLCBzb19sYW5fbW9fcGhvbmcgPSA1MDAwKSAtPiBwMV9yZg0KcHJvZml0X3NpbXUxKGRhdGFfZnJvbV9tb2RlbCA9IGRmX3RoXzIwX3JmLCByYXRlID0gMC4zMCwgc29fbGFuX21vX3Bob25nID0gNTAwMCkgLT4gcDJfcmYNCnByb2ZpdF9zaW11MShkYXRhX2Zyb21fbW9kZWwgPSBkZl90aF8zMF9yZiwgcmF0ZSA9IDAuMzAsIHNvX2xhbl9tb19waG9uZyA9IDUwMDApIC0+IHAzX3JmDQpwcm9maXRfc2ltdTEoZGF0YV9mcm9tX21vZGVsID0gZGZfdGhfNDBfcmYsIHJhdGUgPSAwLjMwLCBzb19sYW5fbW9fcGhvbmcgPSA1MDAwKSAtPiBwNF9yZg0KcHJvZml0X3NpbXUxKGRhdGFfZnJvbV9tb2RlbCA9IGRmX3RoXzUwX3JmLCByYXRlID0gMC4zMCwgc29fbGFuX21vX3Bob25nID0gNTAwMCkgLT4gcDVfcmYNCnByb2ZpdF9zaW11MShkYXRhX2Zyb21fbW9kZWwgPSBkZl90aF81NV9yZiwgcmF0ZSA9IDAuMzAsIHNvX2xhbl9tb19waG9uZyA9IDUwMDApIC0+IHA1NV9yZg0KcHJvZml0X3NpbXUxKGRhdGFfZnJvbV9tb2RlbCA9IGRmX3RoXzYwX3JmLCByYXRlID0gMC4zMCwgc29fbGFuX21vX3Bob25nID0gNTAwMCkgLT4gcDZfcmYNCnByb2ZpdF9zaW11MShkYXRhX2Zyb21fbW9kZWwgPSBkZl90aF82NV9yZiwgcmF0ZSA9IDAuMzAsIHNvX2xhbl9tb19waG9uZyA9IDUwMDApIC0+IHA2NV9yZg0KDQojIFThuqFvIHJhIERGIHbhu4EgbOG7o2kgbmh14bqtbiBjaG8gbeG7pWMgxJHDrWNoIMSRw6FuaCBnacOhIHbDoCBzbyBzw6FuaDogDQpkZl9mb3JfY29tcCA8LSBiaW5kX3Jvd3MoZGF0YV9mcmFtZShQcm9maXQgPSBwMV9yZiwgVGhyZXNob2xkID0gcmVwKCJUaHIgPSAwLjE1IiwgbikpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhX2ZyYW1lKFByb2ZpdCA9IHAyX3JmLCBUaHJlc2hvbGQgPSByZXAoIlRociA9IDAuMjAiLCBuKSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfZnJhbWUoUHJvZml0ID0gcDNfcmYsIFRocmVzaG9sZCA9IHJlcCgiVGhyID0gMC4zMCIsIG4pKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgZGF0YV9mcmFtZShQcm9maXQgPSBwNF9yZiwgVGhyZXNob2xkID0gcmVwKCJUaHIgPSAwLjQwIiwgbikpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhX2ZyYW1lKFByb2ZpdCA9IHA1X3JmLCBUaHJlc2hvbGQgPSByZXAoIlRociA9IDAuNTAiLCBuKSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfZnJhbWUoUHJvZml0ID0gcDU1X3JmLCBUaHJlc2hvbGQgPSByZXAoIlRociA9IDAuNTUiLCBuKSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfZnJhbWUoUHJvZml0ID0gcDZfcmYsIFRocmVzaG9sZCA9IHJlcCgiVGhyID0gMC42MCIsIG4pKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgZGF0YV9mcmFtZShQcm9maXQgPSBwNjVfcmYsIFRocmVzaG9sZCA9IHJlcCgiVGhyID0gMC42NSIsIG4pKSkNCmBgYA0KDQpC4bqxbmcgY8O0bmcgY+G7pSBow6xuaCDhuqNuaCBjaMO6bmcgdGEgY8OzIHRo4buDIHRo4bqleSBy4bqxbmcgYmnhur9uIMSR4buVaSBs4bujaSBuaHXhuq1uIGPDsyBoYWkgxJHhu4luaCBu4bq/dSB0cuG7pWMgWCBjaMO6bmcgdGEgbWluaCBo4buNYSBuZ8aw4buhbmcgdOG7kWkgxrB1LiBN4buZdCB0cm9uZyBoYWkgxJHhu4luaCDEkcOzIOG7qW5nIHbhu5tpIG5nxrDhu6FuZyB04buRaSDGsHUgbMOgIDAuMiBjaG8gUkYuIEPDsm4gxJHhu4luaCBraWEgY8OzIHRo4buDIGNvaSBsw6AgbeG7mXQgImPhu7FjIMSR4bqhaSDEkeG7i2EgcGjGsMahbmciIOG7qW5nIHbhu5tpIG5nxrDhu6FuZyBsw6AgMC40OiANCg0KYGBge3J9DQpkZl9mb3JfY29tcCAlPiUgDQogIGdncGxvdChhZXMoeCA9IFRocmVzaG9sZCwgeSA9IFByb2ZpdCAvIDEwMDAsIGZpbGwgPSBUaHJlc2hvbGQsIGNvbG9yID0gVGhyZXNob2xkKSkgKyANCiAgZ2VvbV9ib3hwbG90KGFscGhhID0gMC4zKSArIA0KICBsYWJzKHggPSBOVUxMLCB5ID0gTlVMTCwgDQogICAgICAgdGl0bGUgPSAiU2ltdWxhdGVkIFByb2ZpdCBCYXNlZCBNb250ZSBDYXJsbyBNZXRob2QgYnkgVGhyZXNob2xkIGZvciBSYW5kb20gRm9yZXN0cyIsIA0KICAgICAgIHN1YnRpdGxlID0gIkRhdGEgVXNlZDogR2VybWFuIENyZWRpdCBEYXRhIikNCmBgYA0KDQoNCiMgS2V5IENvbmNsdXNpb25zDQoNClbhu5tpIG5o4buvbmcgYuG6sW5nIGNo4bupbmcgdGjhu7FjIG5naGnhu4dtIHThu6sgYuG7mSBk4buvIGxp4buHdSBjaMO6bmcgdGEgY8OzIG3huqV5IGvhur90IGx14bqtbiBzYXU6DQoNCjEuIEx1w7RuIGPDsyBz4buxIMSRw6FuaCDEkeG7lWkgZ2nhu69hIGPDoWMgdGnDqnUgY2jDrSDEkcOhbmggZ2nDoSBt4bupYyDEkeG7mSBjaMOtbmggeMOhYyBj4bunYSBtw7QgaMOsbmguIA0KDQoyLiBMw6AgbeG7mXQgdOG7lSBjaOG7qWMgdsOsIGzhu6NpIG5odeG6rW4sIG5nw6JuIGjDoG5nIG7Dqm4geMOieSBk4buxbmcgbcO0IGjDrG5oIGPhu6dhIG3DrG5oIGPEg24gY+G7qSB2w6BvIG3hu6VjIHRpw6p1IG7DoHkgY2jhu6kga2jDtG5nIHBo4bqjaSBt4bulYyB0acOqdSBsw6AgQWNjdXJhY3kuIA0KDQozLiBUw7l5IMSRaeG7gXUga2nhu4duIHbDoCBi4buRaSBj4bqjbmggbuG7gW4ga2luaCB04bq/IGPFqW5nIG5oxrAgY2hp4bq/biBsxrDhu6NjIGtpbmggZG9hbmggbmfDom4gaMOgbmcgY8OzIHRo4buDIMSRaeG7gXUgY2jhu4luaCBuZ8aw4buhbmcgY+G7p2EgbcO0IGjDrG5oIHBow6JuIGxv4bqhaSBjaG8gcGjDuSBo4bujcC4gQ2jhurNuZyBo4bqhbiwgbuG6v3UgxJHhu4tuaCBoxrDhu5tuZyBsw6AgdOG7kWkgdGhp4buBdSBow7NhIG7hu6MgeOG6pXUgdsOgIGFuIHRvw6BuIHbhu5FuIHRow6wgbmfGsOG7oW5nIMSRxrDhu6NjIGNo4buNbiBuw6puIHThu5FpIMSRYSBow7NhIFNlbnNpdGl2aXR5IC0gdOG7qWMga2jhuqMgbsSDbmcgcGjDom4gbG/huqFpIMSRw7puZyBo4buTIHPGoSB44bqldS4gDQoNCjQuIEzhu6NpIG5odeG6rW4gbcO0IHBo4buPbmcgdGh1IMSRxrDhu6NjIHThu6sgdmnhu4djIHPhu60gZOG7pW5nIFJGIGNhbyBoxqFuIHNvIHbhu5tpIGtoaSBz4butIGThu6VuZyBMb2dpc3RpYy4gTuG6v3UgY2jDum5nIHRhIHRo4buxYyBoaeG7h24gdMOsbmggY2jhu4luaCB0aGFtIHPhu5EgY2hvIFJGIHRow6wgbOG7o2kgbmh14bqtbiB04buRaSDGsHUgY8OzIHRo4buDIGPDsm4gY2FvIGjGoW4gbuG7r2EuIE5nb8OgaSBSRiBjaMO6bmcgdGEgY8OybiBjw7MgbeG7mXQgc+G7kSDhu6luZyB2acOqbiBraMOhYyBjw7MgdGjhu4Mgc+G7rSBk4bulbmcgY2hvIG3DtCBow6xuaCBwaMOibiBsb+G6oWkgKG5oxrAgU3VwcG9ydCBWZWN0b3IgTWFjaGluZXMpIG5oxrBuZyB0cm9uZyBwaOG6oW0gdmkgYsOgaSB2aeG6v3QgbsOgeSBsw6AgY2jGsGEgY8OzIHRo4budaSBnaWFuIGto4bqjbyBzw6F0LiANCg0KNS4gVHJvbmcgdGjhu7FjIHThur8gdGjDrCBt4buZdCBjYXNlIGzDoCBUTiAoa2jDoWNoIGjDoG5nIGzDoCB04buRdCB2w6AgbcO0IGjDrG5oIHBow6JuIGxv4bqhaSDEkcO6bmcgbMOgIHThu5F0KSBraGkgY2hvIHZheSB24buRbiB24bqrbiBjw7MgbeG7mXQgeMOhYyBzdeG6pXQgbeG6pXQgduG7kW4gbsOgbyDEkcOzLiBOZ8aw4bujYyBs4bqhaSwgbeG7mXQgY2FzZSBsw6AgRk4gKGtow6FjaCBow6BuZyBsw6AgeOG6pXUgbmjGsG5nIG3DtCBow6xuaCBwaMOibiBsb+G6oWkgc2FpIHRow6BuaCB04buRdCkga2hpIGNobyB2YXkgduG7kW4gdGjDrCBuZ8OibiBow6BuZyB24bqrbiBjw7MgdGjhu4MgY8OzIGto4bqjIG7Eg25nIHRodSBo4buTaSB24buRbiB2YXkgZMO5IHbhu5tpIHjDoWMgc3XhuqV0IHLhuqV0IHRo4bqlcC4gRG8gduG6rXksIMSR4buDIGPDsyDGsOG7m2MgbMaw4bujbmcgY2jDrW5oIHjDoWMgaMahbiB24buBIG5nxrDhu6FuZyB04buRaSDGsHUgbmfGsOG7nWkgbMOgbSBtw7QgaMOsbmggY+G6p24gdGhhbSBraOG6o28gw70ga2nhur9uIGNodXnDqm4gZ2lhIGhv4bq3YyBjxINuIGPhu6kgdsOgbyBk4buvIGxp4buHdSBs4buLY2ggc+G7rSBj4bunYSBuZ8OibiBow6BuZyB24buBIHThu4kgbOG7hyB0aHUgaOG7k2kgduG7kW4gY8WpbmcgbmjGsCBsw6NpIGPhu6dhIGPhuqMgaGFpIG5ow7NtIGtow6FjaCBow6BuZyBuw6B5LiANCg0KDQoNCg0KDQoNCg0KDQo=