
Motivations
Customer Segments (hoặc rộng hơn là Market Segmentation) cho phép công ti có thể sử dụng hiệu quả hơn các nguồn lực hạn chế (thời gian, tài chính) để phục vụ những mục tiêu của tổ chức như tăng doanh thu, tăng lợi nhuận, giữ chân các khách hàng quan trọng cũng như thực hiện các chiến dịch quảng cáo (marketing campaign) hiệu quả hơn dựa trên những hiểu biết về hành vi (behavior), thói quen (habits) và sở thích (preferences) của khách hàng.
Method and Data Used
Thuật toán được sử dụng cho Customer Segmentation là K-means Clustering với các inputs đầu vào là RFM mô tả hành vi của khách hàng với định nghĩa như sau:
- R (Recency) là khoảng thời gian gần đây nhất mà khách hàng mua hàng hoặc sử dụng dịch vụ (hoặc mua hàng).
- F (Frequency) là tần suất mua hàng/sử dụng dịch vụ.
- M (Monetary) là số tiền mà khách hàng mua hàng hóa/dịch vụ.
F và M là những inputs được tính trong một khoảng thời gian khảo sát nhất định (1 năm, một tháng, hoặc 1 quý). Riêng R thì phụ thuộc vào mốc thời gian lựa chọn của người làm mô hình và không ảnh hưởng đến kết quả của mô hình.
RFM thường được tính toán từ cơ sở dữ liệu khách hàng và có thể rất lớn, nhất là các công ti thương mại điện tử, bán hàng qua mạng. Các công ti viễn thông (như Viettel) hoặc Game Online (như VinaGame). Trong post này chúng ta sẽ sử dụng một phần dữ liệu (chỉ hơn 530.000 giao dịch) được cung cấp bởi một công ti thương mại điện tử và có thể download ở đây. Trước hết đọc dữ liệu và xem qua:
561197 |
22120 |
WELCOME WOODEN BLOCK LETTERS |
4 |
542174 |
21326 |
AGED GLASS SILVER T-LIGHT HOLDER |
36 |
553009 |
23191 |
BUNDLE OF 3 RETRO NOTE BOOKS |
2 |
576837 |
84661B |
BLACK SQUARE TABLE CLOCK |
1 |
539836 |
85232D |
SET/3 DECOUPAGE STACKING TINS |
1 |
556187 |
47590A |
BLUE HAPPY BIRTHDAY BUNTING |
3 |
578664 |
23267 |
SET OF 4 SANTA PLACE SETTINGS |
2 |
577358 |
21240 |
BLUE POLKADOT CUP |
1 |
575324 |
23130 |
MISTLETOE HEART WREATH GREEN |
4 |
554512 |
21390 |
FILIGRIS HEART WITH BUTTERFLY |
1 |
9/20/2011 11:32 |
1.85 |
13767 |
United Kingdom |
3/28/2011 11:34 |
1.25 |
17841 |
United Kingdom |
10/12/2011 12:54 |
2.55 |
12691 |
France |
6/23/2011 11:38 |
0.42 |
13268 |
United Kingdom |
12/15/2010 15:50 |
8.50 |
17980 |
United Kingdom |
5/27/2011 12:41 |
8.50 |
16275 |
United Kingdom |
12/7/2011 14:01 |
1.65 |
16426 |
United Kingdom |
8/1/2011 11:23 |
2.10 |
14408 |
United Kingdom |
6/23/2011 18:44 |
1.25 |
15023 |
United Kingdom |
7/7/2011 16:30 |
4.13 |
NA |
United Kingdom |
Các tên biến số là rất dễ hiểu. Ví dụ InvoiceNo là mã hóa đơn, StockCode là mã hàng hóa và là hàng hóa gì thì được miêu tả ở Description.
Chúng ta cũng nên đánh giá sơ bộ chất lượng dữ liệu. Trước hết là tỉ lệ dữ liệu trống (Missing Data):
0 |
0 |
0.2683107 |
0 |
0 |
0 |
24.92669 |
0 |
Gần 27% các mã hàng không có mô tả. Tương tự, khoảng 25% khách hàng không xác định được ID. Với bài toán phân khúc khách hàng sử dụng RFM thì dữ liệu thiếu ở các biến số này không quan trọng.
Các đại lượng ở bộ dữ liệu này - nếu là biến định lượng thì sẽ phải là số không âm. Do vậy chúng ta cũng cần check xem có tồn tại của các số âm hay không. Nếu có thì nguyên nhân có thể là lỗi dữ liệu hoặc có sai sót gì đó thuộc hệ thống cơ sở dữ liệu và các case là số âm này chúng ta phải loại ra khỏi mẫu phân tích:
## Quantity UnitPrice
## 1.9604768 0.4644691
Do chúng ta cần thời gian thực để tính R nên InvoiceDate cần chuyển về thời gian thực đồng thời tạo ra một số cột biến phục vụ cho những phân tích xa hơn nếu cần:
library(lubridate)
my_df %>%
mutate(time_ymd_hm = mdy_hm(InvoiceDate),
time_hour = hour(time_ymd_hm),
time_min = minute(time_ymd_hm),
w_day = wday(time_ymd_hm, label = TRUE, abbr = TRUE),
time_mon = month(time_ymd_hm, label = TRUE, abbr = TRUE),
time_ymd = InvoiceDate %>% str_split(" ", simplify = TRUE) %>% data.frame() %>% pull(X1) %>% mdy) -> my_df
Exploratory Data Analysis
Bước phân tích này được thực hiện nhằm tìm kiếm một số insights. Ví dụ, lượng bán ra (chính xác đến từng phút) có hai thời điểm tăng đột biến trên 70.000. Đây là những bất thường:

Lượng bán ra theo ngày có xu hướng tăng ở giai đoạn cuối. Mặt khác có hai ngày có lượng bán ra bất thường (điều này có thể dự báo được từ trước):

Từ Figure 2 chúng ta cũng thấy rằng có một bất thường khác: có hai thời điểm mà dữ liệu không được liên tiếp với khoảng thời gian dài. Nguyên nhân có thể là công ti bán hàng online này ngừng hoạt động trong một khoảng thời gian nào đó, hoặc hệ thống cơ sở dữ liệu của nó ngừng hoạt động. Cụ thể từ ngày 2010-12-22 đến 2011-01-03 - tức là 12 ngày liền dữ liệu là không có:
2011-01-04 |
8639 |
2010-12-23 |
12 |
Chúng ta cũng có thể khảo sát doanh thu bằng tiền một cách tương tự:


Doanh thu cả theo số lượng bán và tiền ở 4 tháng cuối năm cao hơn hẳn các tháng còn lại trong năm:


Có hơn 4000 mã hàng được bán nhưng doanh thu từ chúng lại phân bố không đều như ta có thể thấy:

Một lần nữa chúng ta có thể thấy sự hiện diện của nguyên lí 80 - 20: 80% doanh thu của hãng bán hàng online này đến từ 827 mã hàng (tương ứng với 20.59% mã hàng):
## [1] 0.2059776
Chúng ta có thể xem một số mặt hàng mang lại nhiều doanh thu nhất:
DOTCOM POSTAGE |
206248.77 |
0.0193358 |
0.0193358 |
REGENCY CAKESTAND 3 TIER |
174484.74 |
0.0163579 |
0.0356937 |
PAPER CRAFT , LITTLE BIRDIE |
168469.60 |
0.0157940 |
0.0514877 |
WHITE HANGING HEART T-LIGHT HOLDER |
106292.77 |
0.0099649 |
0.0614526 |
PARTY BUNTING |
99504.33 |
0.0093285 |
0.0707812 |
JUMBO BAG RED RETROSPOT |
94340.05 |
0.0088444 |
0.0796255 |
Công ti có thể sử dụng thông tin này phục vụ cho những mục đích kinh doanh của mình. Chẳng hạn công ti có thể ưu tiên ship hàng cho những đơn hàng có trong danh sách 827 mã hàng trên hoặc ưu tiên chuẩn bị hàng tồn kho cho những mã hàng này. Nói cách khác cần tập trung mảng logistics (hậu cần - kho bãi - vận chuyển) cho những mã hàng mang lại đến 80% doanh thu cho công ti.
Customer Segments
Sử dụng thuật toán K-means Clusterning cho Customer Segments theo RFM. Chọn thời điểm (chính xác đến phút) là 2011-12-31 24:59 để tính R. Chú ý rằng việc chọn thời điểm không ảnh hưởng đến kết quả phân tích:
12346 |
77183.60 |
348 |
1 |
12347 |
4310.00 |
24 |
182 |
12348 |
1797.24 |
97 |
31 |
12349 |
1757.55 |
41 |
73 |
12350 |
334.40 |
332 |
17 |
12352 |
2506.04 |
58 |
85 |
Vì RFM được đo bằng các thước đo khác nhau nên chúng ta cần rescaling các biến số này:
Thuật toán đòi hỏi phải chọn K - hay số cụm. K tối ưu được lựa chọn căn cứ vào nhiều phương pháp. Trong post này K tối ưu được lựa chọn dựa trên Elbow Method:
set.seed(29)
wss <- sapply(1:10,
function(k){kmeans(final_df_scaled %>% sample_frac(0.2),
k, nstart = 30)$tot.withinss})
u <- data.frame(k = 1:10, WSS = wss)
u %>%
ggplot(aes(k, WSS)) +
geom_line() +
geom_point() +
geom_point(data = u %>% filter(k == 3), color = "red", size = 3) +
scale_x_continuous(breaks = seq(1, 10, by = 1)) +
labs(title = "Figure 7: The Optimal Number of Clusters, Elbow Method", x = "Number of Clusters (K)") +
theme(panel.grid.minor = element_blank())

Từ Figure 7 chúng ta thấy rằng bắt đầu từ K = 3 trở đi thì WSS giảm với tốc độ rất chậm. Điều này ngụ ý rằng K tối ưu nên là 3. Chúng ta phân cụm khách hàng với K = 3 như sau:
Group 2 |
3308 |
54 |
165 |
Group 1 |
799 |
176 |
41 |
Group 3 |
607 |
316 |
24 |
Căn cứ vào kết quả của thuật toán K-Means Clustering thì các khách hàng sẽ được phân thành 3 nhóm sau:
Group 2 là nhóm chi tiêu rất nhiều, rất thường xuyên mua hàng và Recency bé nhất. Nhóm này được gọi là Champions. Cách thức “chăm sóc” các khách hàng này được mô tả kĩ ở đây. Bình quân mỗi khách hàng thuộc nhóm này chi tiêu 3308 bảng Anh.
Group 1 là thuộc diện được đặt tên là Loyal Customers. Đây là nhóm khách hàng có tiềm năng chuyển thành Champions nếu công ti biết thực hiện những chiến lược chăm sóc khách hàng, quảng bá phù hợp.
Group 3 là nhóm mang lại ít doanh thu/lợi nhuận nhất cho công ti: chi tiêu của họ chỉ khoảng 1/6 so với nhóm 2.
Trước khi có thể sử dụng những Insights ở trên để phục vụ cho các chiến lược kinh doanh của công ti chúng ta nên thận trọng: thuật toán K-means Clustering rất nhạy cảm với Outliers. Mặc dù dữ liệu đã được scaling nhằm giảm thiểu ảnh hưởng của các Outliers này nhưng điều đó cũng không đảm bảo rằng kết quả không bị méo mó biến dạng bởi các bất thường này. Để rõ ràng hơn chúng ta xem, ví dụ, money của khách hàng:

Figure 8 cho thấy một số khách hàng chi tiêu đặc biệt lớn. Họ có thể không phải là khách hàng cá nhân mà có thể là dạng một cửa hàng nhỏ mua rồi bán lại. Tương tự là tần suất mua hàng (Figure 9, đơn vị trên trục Y là 1000):

Do thuật toán K-Means Clustering rất nhạy cảm với các bất thường nên chúng ta có thể tách ra nhóm khách hàng bất thường này để phân tích riêng và chỉ giữ lại các quan sát không phải là outliers. Trước hết viết một hàm dán nhãn cho các quan sát bất thường. Ở đây các quan sát bất thường được định nghĩa là lớn hơn hoặc bé hơn trung bình cộng với (hoặc trừ) ba lần độ lệch chuẩn:
Thực hiện lại K-means Clustering. Trước hết là tìm K tối ưu:
set.seed(29)
wss <- sapply(1:10,
function(k){kmeans(final_df_normal_scaled %>% select(-CustomerID) %>% sample_frac(0.2),
k, nstart = 30)$tot.withinss})
u <- data.frame(k = 1:10, WSS = wss)
u %>%
ggplot(aes(k, WSS)) +
geom_line() +
geom_point() +
geom_point(data = u %>% filter(k == 4), color = "red", size = 3) +
scale_x_continuous(breaks = seq(1, 10, by = 1)) +
labs(title = "Figure 10: The Optimal Number of Clusters, Elbow Method",
subtitle = "Outliers are are removed from sample.",
x = "Number of Clusters (K)") +
theme(panel.grid.minor = element_blank())

Như vậy sau khi loại các Outliers thì K tối ưu là 4. Chạy lại K-means Clustering với số cụm được chọn này và thực hiện lại các phân tích như trên:
Sau khi loại Outliers thì khách hàng sẽ được phân thành 4 nhóm với các hành vi chi tiêu (dựa trên R, F, M) như sau:
Group 4 |
2668 |
41 |
136 |
Group 2 |
999 |
103 |
51 |
Group 3 |
726 |
214 |
35 |
Group 1 |
566 |
331 |
24 |
Tính toán tỉ trọng về doanh thu từ các nhóm khách hàng này:
final_df_clustered %>%
group_by(Group) %>%
summarise_each(funs(sum, mean, median, min, max, sd, n()), money) %>%
ungroup() %>%
mutate(per_sale = round(100*sum / sum(sum), 2)) -> sale_group
library(ggthemes)
sale_group %>%
ggplot(aes(reorder(Group, per_sale), per_sale, fill = Group, color = Group)) +
geom_col(width = 0.5, show.legend = FALSE) +
coord_flip() +
geom_text(aes(label = paste(per_sale, paste0(paste0("(", "%")), ")")),
hjust = -0.05, color = "white", size = 5) +
scale_y_continuous(limits = c(0, 90), expand = c(0.01, 0)) +
scale_fill_tableau() +
scale_color_tableau() +
theme(axis.title.x = element_blank(),
axis.text.x = element_blank(),
axis.ticks.x = element_blank()) +
theme(panel.grid.major = element_blank()) +
theme(panel.grid.minor = element_blank()) +
labs(x = NULL, title = "Figure 11: Share of Sales by Customer Group")

Group 4 chiếm 49.1% tổng số khách hàng và là nhóm mang lại gần 75% doanh thu cho công ti:
sale_group %>%
select(Group, n) %>%
mutate(total = sum(n)) %>%
mutate(label = 100*n / total) %>%
mutate(label = paste(round(label, 1), "%")) %>%
ggplot(aes(Group, n, fill = Group, color = Group)) +
geom_col(width = 0.5, show.legend = FALSE) +
geom_text(aes(label = label), color = "white", vjust = 1.4, size = 5) +
scale_fill_tableau() +
scale_color_tableau() +
theme(panel.grid.minor = element_blank()) +
theme(panel.grid.major.x = element_blank()) +
labs(x = NULL, y = NULL, title = "Figure 12: Number of Customers by Group")

Group Labelling
Bài toán đặt ra ở đây là một khách hàng có hành vi tiêu dùng được mô tả bằng R, F và M biết trước thì khách hàng này sẽ được xếp vào nhóm nào?. Có nhiều cách tiếp cận/mô hình có thể được áp dụng cho bài toán này và một trong những cách đó là sử dụng các thuật toán phân loại, ví dụ, Random Forest:
## note: only 2 unique complexity parameters in default grid. Truncating the grid to 2 .
Với RF đã được huấn luyện trên bộ dữ liệu train, giả sử có một quan sát mới với các đặc điểm về tiêu dùng M = 1757.55, R = 41, F = 73 như sau:
Thì thuật toán RF sẽ dự báo quan sát này thuộc nhóm 4:
## [1] "Group 4"
Chúng ta co thể đánh giá chất lượng dự báo của RF:
## Confusion Matrix and Statistics
##
## Reference
## Prediction Group 1 Group 2 Group 3 Group 4
## Group 1 99 0 0 0
## Group 2 0 217 0 1
## Group 3 0 0 123 0
## Group 4 0 0 0 424
##
## Overall Statistics
##
## Accuracy : 0.9988
## 95% CI : (0.9936, 1)
## No Information Rate : 0.4919
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.9983
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: Group 1 Class: Group 2 Class: Group 3
## Sensitivity 1.0000 1.0000 1.0000
## Specificity 1.0000 0.9985 1.0000
## Pos Pred Value 1.0000 0.9954 1.0000
## Neg Pred Value 1.0000 1.0000 1.0000
## Prevalence 0.1146 0.2512 0.1424
## Detection Rate 0.1146 0.2512 0.1424
## Detection Prevalence 0.1146 0.2523 0.1424
## Balanced Accuracy 1.0000 0.9992 1.0000
## Class: Group 4
## Sensitivity 0.9976
## Specificity 1.0000
## Pos Pred Value 1.0000
## Neg Pred Value 0.9977
## Prevalence 0.4919
## Detection Rate 0.4907
## Detection Prevalence 0.4907
## Balanced Accuracy 0.9988
Kết quả này chỉ ra rằng RF có khả năng phân loại - hay chỉ ra một khách hàng cụ thể với RFM đã biết sẽ thuộc về nhóm nào. Chất lượng dán nhãn cho khách hàng có thể được kiểm tra bằng cách đặt câu hỏi Phải chăng cách phân loại khách hàng từ RF sẽ tạo ra các nhóm khách hàng có các đặt điểm về hành vi chi tiêu là rất gần với hành vi chi tiêu đã được khảo sát?. Nói cách khác,nếu trung bình R, F và M của nhóm khách hàng được dự báo từ RF mà không khác biệt (so với trung bình R, F và M của nhóm khách hàng đã được phân cụm từ K-means Clustering) thì có nghĩa là cách dán nhãn cho khách hàng từ RF là phù hợp. Chúng ta có thể kiểm tra như sau:
Group 1 |
453 |
330 |
22 |
Group 2 |
1018 |
103 |
53 |
Group 3 |
505 |
217 |
29 |
Group 4 |
2753 |
42 |
147 |
Group 1 |
594 |
331 |
24 |
Group 2 |
995 |
103 |
51 |
Group 3 |
782 |
213 |
36 |
Group 4 |
2647 |
41 |
133 |
Summary
Post này giới thiệu một trong những ứng dụng cơ bản nhất của Customer Segmentation (hoặc rộng hơn là Market Segmentation) với thuật toán K-means Clustering bằng ngôn ngữ R. Những vẫn đề sau chưa được đề cập (hi vọng sẽ được trình bày trong các post sau):
Những phương pháp tìm K tối ưu bằng Average Silhouette Method và Gap Statistic Method.
Một số thuật toán phân cụm khác có thể thay thế cho K-means Clustering, như Hierarchical Clustering.
Một số thuật toán phân cụm khác áp dụng cho cả categorical lẫn numeric data. Hạn chế của K-means Clustering là thuật toán này chỉ nhận Inputs là biến số (numeric data).
References
- Chapman, C., & Feit, E. M. (2019). R for marketing research and analytics. New York, NY: Springer.
- Chen, D., Sain, S. L., & Guo, K. (2012). Data mining for the online retail industry: A case study of RFM model-based customer segmentation using data mining. Journal of Database Marketing & Customer Strategy Management, 19(3), 197-208.
- Khajvand, M., & Tarokh, M. J. (2011). Estimating customer future value of different customer segments based on adapted RFM model in retail banking context. Procedia Computer Science, 3, 1327-1332.
- Shmueli, G., Bruce, P. C., Yahav, I., Patel, N. R., & Lichtendahl Jr, K. C. (2017). Data mining for business analytics: concepts, techniques, and applications in R. John Wiley & Sons.
- Zakrzewska, D., & Murlewski, J. (2005, September). Clustering algorithms for bank customer segmentation. In 5th International Conference on Intelligent Systems Design and Applications (ISDA’05) (pp. 197-202). IEEE.
LS0tDQp0aXRsZTogIk1hcmtldCBTZWdtZW50YXRpb246IEstbWVhbnMgQ2x1c3RlcmluZyBBbGdvcml0aG0iIA0Kc3VidGl0bGU6ICJEYXRhIFNjaWVuY2UgZm9yIE1hcmtldGluZyBTZXJpZXMiDQphdXRob3I6ICJOZ3V5ZW4gQ2hpIER1bmciDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6IA0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICAjY29kZV9mb2xkaW5nOiBoaWRlDQogICAgaGlnaGxpZ2h0OiB6ZW5idXJuDQogICAgIyBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRoZW1lOiAiZmxhdGx5Ig0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19mbG9hdDogVFJVRQ0KLS0tDQoNCmBgYHtyIHNldHVwLGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDYpDQpgYGANCg0KIVtdKEM6XFVzZXJzXEFkbWluXERlc2t0b3BccDIuanBlZykNCg0KIyBNb3RpdmF0aW9ucw0KDQpDdXN0b21lciBTZWdtZW50cyAoaG/hurdjIHLhu5luZyBoxqFuIGzDoCBNYXJrZXQgU2VnbWVudGF0aW9uKSBjaG8gcGjDqXAgY8O0bmcgdGkgY8OzIHRo4buDIHPhu60gZOG7pW5nIGhp4buHdSBxdeG6oyBoxqFuIGPDoWMgbmd14buTbiBs4buxYyBo4bqhbiBjaOG6vyAodGjhu51pIGdpYW4sIHTDoGkgY2jDrW5oKSDEkeG7gyBwaOG7pWMgduG7pSBuaOG7r25nIG3hu6VjIHRpw6p1IGPhu6dhIHThu5UgY2jhu6ljIG5oxrAgdMSDbmcgZG9hbmggdGh1LCB0xINuZyBs4bujaSBuaHXhuq1uLCBnaeG7ryBjaMOibiBjw6FjIGtow6FjaCBow6BuZyBxdWFuIHRy4buNbmcgY8WpbmcgbmjGsCB0aOG7sWMgaGnhu4duIGPDoWMgY2hp4bq/biBk4buLY2ggcXXhuqNuZyBjw6FvIChtYXJrZXRpbmcgY2FtcGFpZ24pIGhp4buHdSBxdeG6oyBoxqFuIGThu7FhIHRyw6puIG5o4buvbmcgaGnhu4N1IGJp4bq/dCB24buBIGjDoG5oIHZpIChiZWhhdmlvciksIHRow7NpIHF1ZW4gKGhhYml0cykgdsOgIHPhu58gdGjDrWNoIChwcmVmZXJlbmNlcykgY+G7p2Ega2jDoWNoIGjDoG5nLiANCg0KDQojIE1ldGhvZCBhbmQgRGF0YSBVc2VkDQoNClRodeG6rXQgdG/DoW4gxJHGsOG7o2Mgc+G7rSBk4bulbmcgY2hvIEN1c3RvbWVyIFNlZ21lbnRhdGlvbiBsw6AgSy1tZWFucyBDbHVzdGVyaW5nIHbhu5tpIGPDoWMgaW5wdXRzIMSR4bqndSB2w6BvIGzDoCBSRk0gbcO0IHThuqMgaMOgbmggdmkgY+G7p2Ega2jDoWNoIGjDoG5nIHbhu5tpIMSR4buLbmggbmdoxKlhIG5oxrAgc2F1OiANCg0KLSAqKlIgKFJlY2VuY3kpKiogbMOgIGtob+G6o25nIHRo4budaSBnaWFuIGfhuqduIMSRw6J5IG5o4bqldCBtw6Aga2jDoWNoIGjDoG5nIG11YSBow6BuZyBob+G6t2Mgc+G7rSBk4bulbmcgZOG7i2NoIHbhu6UgKGhv4bq3YyBtdWEgaMOgbmcpLiANCi0gKipGIChGcmVxdWVuY3kpKiogbMOgIHThuqduIHN14bqldCBtdWEgaMOgbmcvc+G7rSBk4bulbmcgZOG7i2NoIHbhu6UuIA0KLSAqKk0gKE1vbmV0YXJ5KSoqIGzDoCBz4buRIHRp4buBbiBtw6Aga2jDoWNoIGjDoG5nIG11YSBow6BuZyBow7NhL2Thu4tjaCB24bulLiANCg0KRiB2w6AgTSBsw6Agbmjhu69uZyBpbnB1dHMgxJHGsOG7o2MgdMOtbmggdHJvbmcgbeG7mXQga2hv4bqjbmcgdGjhu51pIGdpYW4ga2jhuqNvIHPDoXQgbmjhuqV0IMSR4buLbmggKDEgbsSDbSwgbeG7mXQgdGjDoW5nLCBob+G6t2MgMSBxdcO9KS4gUmnDqm5nIFIgdGjDrCBwaOG7pSB0aHXhu5ljIHbDoG8gbeG7kWMgdGjhu51pIGdpYW4gbOG7sWEgY2jhu41uIGPhu6dhIG5nxrDhu51pIGzDoG0gbcO0IGjDrG5oIHbDoCBraMO0bmcg4bqjbmggaMaw4bufbmcgxJHhur9uIGvhur90IHF14bqjIGPhu6dhIG3DtCBow6xuaC4gDQoNClJGTSB0aMaw4budbmcgxJHGsOG7o2MgdMOtbmggdG/DoW4gdOG7qyBjxqEgc+G7nyBk4buvIGxp4buHdSBraMOhY2ggaMOgbmcgdsOgIGPDsyB0aOG7gyBy4bqldCBs4bubbiwgbmjhuqV0IGzDoCBjw6FjIGPDtG5nIHRpIHRoxrDGoW5nIG3huqFpIMSRaeG7h24gdOG7rSwgYsOhbiBow6BuZyBxdWEgbeG6oW5nLiBDw6FjIGPDtG5nIHRpIHZp4buFbiB0aMO0bmcgKG5oxrAgVmlldHRlbCkgaG/hurdjIEdhbWUgT25saW5lIChuaMawIFZpbmFHYW1lKS4gVHJvbmcgcG9zdCBuw6B5IGNow7puZyB0YSBz4bq9IHPhu60gZOG7pW5nIG3hu5l0IHBo4bqnbiBk4buvIGxp4buHdSAoY2jhu4kgaMahbiA1MzAuMDAwIGdpYW8gZOG7i2NoKSDEkcaw4bujYyBjdW5nIGPhuqVwIGLhu59pIG3hu5l0IGPDtG5nIHRpIHRoxrDGoW5nIG3huqFpIMSRaeG7h24gdOG7rSB2w6AgY8OzIHRo4buDIGRvd25sb2FkIFvhu58gxJHDonldKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vY2FycmllMS9lY29tbWVyY2UtZGF0YSkuIFRyxrDhu5tjIGjhur90IMSR4buNYyBk4buvIGxp4buHdSB2w6AgeGVtIHF1YTogDQoNCmBgYHtyfQ0KIyDEkOG7jWMgZOG7ryBsaeG7h3U6IA0Kcm0obGlzdCA9IGxzKCkpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkobWFncml0dHIpDQpsaWJyYXJ5KGtuaXRyKQ0KbXlfZGYgPC0gcmVhZF9jc3YoIkQ6XFxrYWdnbGVfZGF0YVxcdzEwX1JGTV9tb2RlbFxcZGF0YS5jc3YiKQ0KDQojIFhlbSAxMCBnaWFvIGThu4tjaCBi4bqldCBrw6wgdOG7qyBj4buZdCAxIMSR4bq/biBj4buZdCA0OiANCnNldC5zZWVkKDI5KQ0KbXlfZGYgJT4lIA0KICBzYW1wbGVfbigxMCkgJT4lIA0KICBzZWxlY3QoMTo0KSAlPiUgDQogIGthYmxlKCkNCg0KIyBU4burIGPhu5l0IDUgxJHhur9uIDg6IA0KbXlfZGYgJT4lIA0KICBzYW1wbGVfbigxMCkgJT4lIA0KICBzZWxlY3QoNTo4KSAlPiUgDQogIGthYmxlKCkNCmBgYA0KDQpDw6FjIHTDqm4gYmnhur9uIHPhu5EgbMOgIHLhuqV0IGThu4UgaGnhu4N1LiBWw60gZOG7pSAqKkludm9pY2VObyoqIGzDoCBtw6MgaMOzYSDEkcahbiwgKipTdG9ja0NvZGUqKiBsw6AgbcOjIGjDoG5nIGjDs2EgdsOgIGzDoCBow6BuZyBow7NhIGfDrCB0aMOsIMSRxrDhu6NjIG1pw6p1IHThuqMg4bufICoqRGVzY3JpcHRpb24qKi4gDQoNCkNow7puZyB0YSBjxaluZyBuw6puIMSRw6FuaCBnacOhIHPGoSBi4buZIGNo4bqldCBsxrDhu6NuZyBk4buvIGxp4buHdS4gVHLGsOG7m2MgaOG6v3QgbMOgIHThu4kgbOG7hyBk4buvIGxp4buHdSB0cuG7kW5nIChNaXNzaW5nIERhdGEpOiANCg0KYGBge3J9DQojIEjDoG0gxJHDoW5oIGdpw6EgZOG7ryBsaeG7h3UgdHLhu5FuZzogDQpuYV9yYXRlIDwtIGZ1bmN0aW9uKHgpIHsxMDAqc3VtKGlzLm5hKHgpKSAvIGxlbmd0aCh4KX0NCg0KIyBT4butIGThu6VuZyBow6BtOiANCm15X2RmICU+JQ0KICBzdW1tYXJpc2VfYWxsKG5hX3JhdGUpICU+JSANCiAga2FibGUoKQ0KYGBgDQoNCkfhuqduIDI3JSBjw6FjIG3DoyBow6BuZyBraMO0bmcgY8OzIG3DtCB04bqjLiBUxrDGoW5nIHThu7EsIGtob+G6o25nIDI1JSBraMOhY2ggaMOgbmcga2jDtG5nIHjDoWMgxJHhu4tuaCDEkcaw4bujYyBJRC4gVuG7m2kgYsOgaSB0b8OhbiBwaMOibiBraMO6YyBraMOhY2ggaMOgbmcgc+G7rSBk4bulbmcgUkZNIHRow6wgZOG7ryBsaeG7h3UgdGhp4bq/dSDhu58gY8OhYyBiaeG6v24gc+G7kSBuw6B5IGtow7RuZyBxdWFuIHRy4buNbmcuIA0KDQpDw6FjIMSR4bqhaSBsxrDhu6NuZyDhu58gYuG7mSBk4buvIGxp4buHdSAgbsOgeSAtIG7hur91IGzDoCBiaeG6v24gxJHhu4tuaCBsxrDhu6NuZyB0aMOsIHPhur0gcGjhuqNpIGzDoCBz4buRIGtow7RuZyDDom0uIERvIHbhuq15IGNow7puZyB0YSBjxaluZyBj4bqnbiBjaGVjayB4ZW0gY8OzIHThu5NuIHThuqFpIGPhu6dhIGPDoWMgc+G7kSDDom0gaGF5IGtow7RuZy4gTuG6v3UgY8OzIHRow6wgbmd1ecOqbiBuaMOibiBjw7MgdGjhu4MgbMOgIGzhu5dpIGThu68gbGnhu4d1IGhv4bq3YyBjw7Mgc2FpIHPDs3QgZ8OsIMSRw7MgdGh14buZYyBo4buHIHRo4buRbmcgY8ahIHPhu58gZOG7ryBsaeG7h3UgdsOgIGPDoWMgY2FzZSBsw6Agc+G7kSDDom0gbsOgeSBjaMO6bmcgdGEgcGjhuqNpIGxv4bqhaSByYSBraOG7j2kgbeG6q3UgcGjDom4gdMOtY2g6IA0KDQoNCmBgYHtyfQ0KIyBIw6BtIMSRw6FuaCBnacOhIHThu4kgbOG7hyBz4buRIMOibTogDQpuZWdhdGl2ZV9kZWN0ZWN0IDwtIGZ1bmN0aW9uKHgpIHsxMDAqc3VtKHggPD0gMCkgLyBsZW5ndGgoeCl9DQoNCiMgQ8OzIHRo4buDIHRo4bqleSBy4bqxbmcgMS45NiUgdsOgIDAuNDYlIGPDoWMgY2FzZSDDom0gbsOgeSBsw6Aga2jDtG5nIGjhu6NwIGzDrTogDQpzYXBwbHkobXlfZGYgJT4lIHNlbGVjdChRdWFudGl0eSwgVW5pdFByaWNlKSwgbmVnYXRpdmVfZGVjdGVjdCkNCg0KIyBMb+G6oWkgY8OhYyBjYXNlIMOibSBuw6B5OiANCm15X2RmICU+JSANCiAgZmlsdGVyKFF1YW50aXR5ID4gMCwgVW5pdFByaWNlID4gMCkgLT4gbXlfZGYNCmBgYA0KDQpEbyBjaMO6bmcgdGEgY+G6p24gdGjhu51pIGdpYW4gdGjhu7FjIMSR4buDIHTDrW5oIFIgbsOqbiBJbnZvaWNlRGF0ZSBj4bqnbiBjaHV54buDbiB24buBIHRo4budaSBnaWFuIHRo4buxYyDEkeG7k25nIHRo4budaSB04bqhbyByYSBt4buZdCBz4buRIGPhu5l0IGJp4bq/biBwaOG7pWMgduG7pSBjaG8gbmjhu69uZyBwaMOibiB0w61jaCB4YSBoxqFuIG7hur91IGPhuqduOiANCg0KYGBge3J9DQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCg0KbXlfZGYgJT4lIA0KICBtdXRhdGUodGltZV95bWRfaG0gPSBtZHlfaG0oSW52b2ljZURhdGUpLCANCiAgICAgICAgIHRpbWVfaG91ciA9IGhvdXIodGltZV95bWRfaG0pLCANCiAgICAgICAgIHRpbWVfbWluID0gbWludXRlKHRpbWVfeW1kX2htKSwgDQogICAgICAgICB3X2RheSA9IHdkYXkodGltZV95bWRfaG0sIGxhYmVsID0gVFJVRSwgYWJiciA9IFRSVUUpLCANCiAgICAgICAgIHRpbWVfbW9uID0gbW9udGgodGltZV95bWRfaG0sIGxhYmVsID0gVFJVRSwgYWJiciA9IFRSVUUpLCANCiAgICAgICAgIHRpbWVfeW1kID0gSW52b2ljZURhdGUgJT4lIHN0cl9zcGxpdCgiICIsIHNpbXBsaWZ5ID0gVFJVRSkgJT4lIGRhdGEuZnJhbWUoKSAlPiUgcHVsbChYMSkgJT4lIG1keSkgLT4gbXlfZGYNCmBgYA0KDQojIEV4cGxvcmF0b3J5IERhdGEgQW5hbHlzaXMNCg0KQsaw4bubYyBwaMOibiB0w61jaCBuw6B5IMSRxrDhu6NjIHRo4buxYyBoaeG7h24gbmjhurFtIHTDrG0ga2nhur9tIG3hu5l0IHPhu5EgaW5zaWdodHMuIFbDrSBk4bulLCBsxrDhu6NuZyBiw6FuIHJhIChjaMOtbmggeMOhYyDEkeG6v24gdOG7q25nIHBow7p0KSBjw7MgaGFpIHRo4budaSDEkWnhu4NtIHTEg25nIMSR4buZdCBiaeG6v24gdHLDqm4gNzAuMDAwLiDEkMOieSBsw6Agbmjhu69uZyBi4bqldCB0aMaw4budbmc6IA0KDQoNCmBgYHtyfQ0KbGlicmFyeShocmJydGhlbWVzKQ0KdGhlbWVfc2V0KHRoZW1lX21vZGVybl9yYygpKQ0KDQpteV9kZiAlPiUgDQogIGdyb3VwX2J5KHRpbWVfeW1kX2htKSAlPiUgDQogIHN1bW1hcmlzZShzYWxlcyA9IHN1bShRdWFudGl0eSkpICU+JSANCiAgdW5ncm91cCgpIC0+IHNhbGVzX2J5VGltZV9obQ0KDQpzYWxlc19ieVRpbWVfaG0gJT4lIA0KICBnZ3Bsb3QoYWVzKHRpbWVfeW1kX2htLCBzYWxlcykpICsgDQogIGdlb21fbGluZSgpICsgDQogIGxhYnModGl0bGUgPSAiRmlndXJlIDE6IFVuaXQgU2FsZXMgYnkgTWluIiwgeCA9ICJUaW1lIiwgeSA9ICJRdWFudGl0eSIpDQogIA0KYGBgDQoNCkzGsOG7o25nIGLDoW4gcmEgdGhlbyBuZ8OgeSBjw7MgeHUgaMaw4bubbmcgdMSDbmcg4bufIGdpYWkgxJFv4bqhbiBjdeG7kWkuIE3hurd0IGtow6FjIGPDsyBoYWkgbmfDoHkgY8OzIGzGsOG7o25nIGLDoW4gcmEgYuG6pXQgdGjGsOG7nW5nICjEkWnhu4F1IG7DoHkgY8OzIHRo4buDIGThu7EgYsOhbyDEkcaw4bujYyB04burIHRyxrDhu5tjKTogDQoNCmBgYHtyfQ0KbXlfZGYgJT4lIA0KICBncm91cF9ieSh0aW1lX3ltZCkgJT4lIA0KICBzdW1tYXJpc2Uoc2FsZXMgPSBzdW0oUXVhbnRpdHkpKSAlPiUgDQogIHVuZ3JvdXAoKSAtPiBzYWxlc19ieVRpbWUNCg0Kc2FsZXNfYnlUaW1lICU+JSANCiAgZ2dwbG90KGFlcyh0aW1lX3ltZCwgc2FsZXMpKSArIA0KICBnZW9tX2xpbmUoKSArIA0KICBnZW9tX3BvaW50KGNvbG9yID0gImZpcmVicmljayIpICsgDQogIGxhYnModGl0bGUgPSAiRmlndXJlIDI6IFVuaXQgU2FsZXMgYnkgRGF5IiwgeCA9ICJUaW1lIiwgeSA9ICJRdWFudGl0eSIpDQoNCmBgYA0KDQpU4burIEZpZ3VyZSAyIGNow7puZyB0YSBjxaluZyB0aOG6pXkgcuG6sW5nIGPDsyBt4buZdCBi4bqldCB0aMaw4budbmcga2jDoWM6IGPDsyBoYWkgdGjhu51pIMSRaeG7g20gbcOgIGThu68gbGnhu4d1IGtow7RuZyDEkcaw4bujYyBsacOqbiB0aeG6v3AgduG7m2kga2hv4bqjbmcgdGjhu51pIGdpYW4gZMOgaS4gTmd1ecOqbiBuaMOibiBjw7MgdGjhu4MgbMOgIGPDtG5nIHRpIGLDoW4gaMOgbmcgb25saW5lIG7DoHkgbmfhu6tuZyBob+G6oXQgxJHhu5luZyB0cm9uZyBt4buZdCBraG/huqNuZyB0aOG7nWkgZ2lhbiBuw6BvIMSRw7MsIGhv4bq3YyBo4buHIHRo4buRbmcgY8ahIHPhu58gZOG7ryBsaeG7h3UgY+G7p2EgbsOzIG5n4burbmcgaG/huqF0IMSR4buZbmcuIEPhu6UgdGjhu4MgdOG7qyBuZ8OgeSAyMDEwLTEyLTIyIMSR4bq/biAyMDExLTAxLTAzIC0gdOG7qWMgbMOgIDEyIG5nw6B5IGxp4buBbiBk4buvIGxp4buHdSBsw6Aga2jDtG5nIGPDszogDQoNCmBgYHtyfQ0Kc2FsZXNfYnlUaW1lICU+JSANCiAgbXV0YXRlKGxhZzEgPSBsYWcodGltZV95bWQsIG4gPSAxTCkpICU+JSANCiAgbXV0YXRlKGR1cmF0aW9uX2RhdGUgPSB0aW1lX3ltZCAtIGxhZzEpICU+JSANCiAgbXV0YXRlKGR1cmF0aW9uX2RhdGUgPSBhcy5udW1lcmljKGR1cmF0aW9uX2RhdGUpKSAlPiUgDQogIHNsaWNlKHdoaWNoLm1heChkdXJhdGlvbl9kYXRlKSkgJT4lIA0KICBrYWJsZSgpDQoNCmBgYA0KDQoNCkNow7puZyB0YSBjxaluZyBjw7MgdGjhu4Mga2jhuqNvIHPDoXQgZG9hbmggdGh1IGLhurFuZyB0aeG7gW4gbeG7mXQgY8OhY2ggdMawxqFuZyB04buxOiANCg0KDQpgYGB7cn0NCm15X2RmICU+JSANCiAgbXV0YXRlKG1vbmV5ID0gUXVhbnRpdHkqVW5pdFByaWNlKSAtPiBteV9kZg0KDQpteV9kZiAlPiUgDQogIGdyb3VwX2J5KHRpbWVfeW1kX2htKSAlPiUgDQogIHN1bW1hcmlzZShtb25leVNhbGVzID0gc3VtKG1vbmV5KSkgJT4lIA0KICB1bmdyb3VwKCkgLT4gc2FsZXNfYnlUaW1lX2htX21vbmV5DQoNCnNhbGVzX2J5VGltZV9obV9tb25leSAlPiUgDQogIGdncGxvdChhZXModGltZV95bWRfaG0sIG1vbmV5U2FsZXMpKSArIA0KICBnZW9tX2xpbmUoKSArIA0KICBsYWJzKHRpdGxlID0gIkZpZ3VyZSAzOiBNb25ldGFyeSBTYWxlcyBieSBNaW4iLCB4ID0gIlRpbWUiLCB5ID0gIiIpDQoNCm15X2RmICU+JSANCiAgZ3JvdXBfYnkodGltZV95bWQpICU+JSANCiAgc3VtbWFyaXNlKG1vbmV5U2FsZXMgPSBzdW0obW9uZXkpKSAlPiUgDQogIHVuZ3JvdXAoKSAtPiBzYWxlc19ieVRpbWVfbW9uZXkNCg0Kc2FsZXNfYnlUaW1lX21vbmV5ICU+JSANCiAgZ2dwbG90KGFlcyh0aW1lX3ltZCwgbW9uZXlTYWxlcykpICsgDQogIGdlb21fbGluZSgpICsgDQogIGdlb21fcG9pbnQoY29sb3IgPSAiZmlyZWJyaWNrIikgKyANCiAgbGFicyh0aXRsZSA9ICJGaWd1cmUgNDogTW9uZXRhcnkgU2FsZXMgYnkgRGF5IiwgeCA9ICJUaW1lIiwgeSA9ICIiKQ0KICANCmBgYA0KDQpEb2FuaCB0aHUgY+G6oyB0aGVvIHPhu5EgbMaw4bujbmcgYsOhbiB2w6AgdGnhu4FuIOG7nyA0IHRow6FuZyBjdeG7kWkgbsSDbSBjYW8gaMahbiBo4bqzbiBjw6FjIHRow6FuZyBjw7JuIGzhuqFpIHRyb25nIG7Eg206IA0KDQoNCmBgYHtyfQ0KbXlfZGYgJT4lIA0KICBncm91cF9ieSh0aW1lX21vbikgJT4lIA0KICBzdW1tYXJpc2VfZWFjaChmdW5zKHN1bSksIFF1YW50aXR5KSAlPiUgDQogIG11dGF0ZShRdWFudGl0eSA9IFF1YW50aXR5IC8gMTAwMCkgJT4lIA0KICBnZ3Bsb3QoYWVzKHRpbWVfbW9uLCBRdWFudGl0eSkpICsgDQogIGdlb21fY29sKCkgKyANCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9ibGFuaygpKSArIA0KICBsYWJzKHRpdGxlID0gIkZpZ3VyZSA1OiBVbml0IFNhbGVzIGluIHRob3VzYW5kcyBieSBNb250aCIsIHggPSAiVGltZSIsIHkgPSAiUXVhbmxpdHkiKSArIA0KICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCA4MDApKQ0KDQpteV9kZiAlPiUgDQogIGdyb3VwX2J5KHRpbWVfbW9uKSAlPiUgDQogIHN1bW1hcmlzZV9lYWNoKGZ1bnMoc3VtKSwgbW9uZXkpICU+JSANCiAgbXV0YXRlKG1vbmV5ID0gbW9uZXkgLyAxMDAwKSAlPiUgDQogIGdncGxvdChhZXModGltZV9tb24sIG1vbmV5KSkgKyANCiAgZ2VvbV9jb2woKSArIA0KICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsgDQogIGxhYnModGl0bGUgPSAiRmlndXJlIDY6IE1vbmV0YXJ5IFNhbGVzIGluIHRob3VzYW5kcyBieSBNb250aCIsIHggPSAiVGltZSIsIHkgPSAiIikNCiAgDQpgYGANCg0KQ8OzIGjGoW4gNDAwMCBtw6MgaMOgbmcgxJHGsOG7o2MgYsOhbiBuaMawbmcgZG9hbmggdGh1IHThu6sgY2jDum5nIGzhuqFpIHBow6JuIGLhu5Ega2jDtG5nIMSR4buBdSBuaMawIHRhIGPDsyB0aOG7gyB0aOG6pXk6IA0KDQpgYGB7cn0NCm15X2RmICU+JSANCiAgZ3JvdXBfYnkoRGVzY3JpcHRpb24pICU+JSANCiAgc3VtbWFyaXNlKHNhbGVzID0gc3VtKG1vbmV5KSkgJT4lIA0KICB1bmdyb3VwKCkgJT4lIA0KICBhcnJhbmdlKC1zYWxlcykgJT4lIA0KICBtdXRhdGUoRGVzY3JpcHRpb24gPSBmYWN0b3IoRGVzY3JpcHRpb24sIGxldmVscyA9IERlc2NyaXB0aW9uKSkgJT4lIA0KICBtdXRhdGUodG90YWwgPSBzdW0oc2FsZXMpKSAlPiUgDQogIG11dGF0ZShtb25leV9wZXJjZW50ID0gc2FsZXMgLyB0b3RhbCkgJT4lIA0KICBtdXRhdGUoY3VtX21vbmV5ID0gY3Vtc3VtKG1vbmV5X3BlcmNlbnQpKSAtPiBtb25leVNhbGVzX0l0ZW0NCg0KbW9uZXlTYWxlc19JdGVtICU+JSANCiAgZ2dwbG90KGFlcyhEZXNjcmlwdGlvbiwgc2FsZXMpKSArIA0KICBnZW9tX2NvbCgpICsgDQogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfYmxhbmsoKSkgKyANCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCkpICsgDQogIGxhYnModGl0bGUgPSAiRmlndXJlIDY6IE1vbmV5IFNhbGVzIGJ5IFByb2R1Y3QiLCB4ID0gIiIsIHkgPSAiIikNCiAgDQpgYGANCg0KTeG7mXQgbOG6p24gbuG7r2EgY2jDum5nIHRhIGPDsyB0aOG7gyB0aOG6pXkgc+G7sSBoaeG7h24gZGnhu4duIGPhu6dhICoqbmd1ecOqbiBsw60gODAgLSAyMCoqOiA4MCUgZG9hbmggdGh1IGPhu6dhIGjDo25nIGLDoW4gaMOgbmcgb25saW5lIG7DoHkgxJHhur9uIHThu6sgODI3IG3DoyBow6BuZyAodMawxqFuZyDhu6luZyB24bubaSAyMC41OSUgbcOjIGjDoG5nKTogDQoNCmBgYHtyfQ0KbW9uZXlTYWxlc19JdGVtICU+JSANCiAgZmlsdGVyKGN1bV9tb25leSA8PSAwLjgpIC0+IHRvcDgwX3NhbGVzDQoNCnRvcDgwX3NhbGVzICU+JSBucm93KCkgLyBucm93KG1vbmV5U2FsZXNfSXRlbSkNCmBgYA0KDQpDaMO6bmcgdGEgY8OzIHRo4buDIHhlbSBt4buZdCBz4buRIG3hurd0IGjDoG5nIG1hbmcgbOG6oWkgbmhp4buBdSBkb2FuaCB0aHUgbmjhuqV0OiANCg0KYGBge3J9DQptb25leVNhbGVzX0l0ZW0gJT4lIA0KICBzZWxlY3QoLXRvdGFsKSAlPiUgDQogIGhlYWQoKSAlPiUgDQogIGthYmxlKCkNCmBgYA0KDQpDw7RuZyB0aSBjw7MgdGjhu4Mgc+G7rSBk4bulbmcgdGjDtG5nIHRpbiBuw6B5IHBo4bulYyB24bulIGNobyBuaOG7r25nIG3hu6VjIMSRw61jaCBraW5oIGRvYW5oIGPhu6dhIG3DrG5oLiBDaOG6s25nIGjhuqFuIGPDtG5nIHRpIGPDsyB0aOG7gyDGsHUgdGnDqm4gc2hpcCBow6BuZyBjaG8gbmjhu69uZyDEkcahbiBow6BuZyBjw7MgdHJvbmcgZGFuaCBzw6FjaCA4MjcgbcOjIGjDoG5nIHRyw6puIGhv4bq3YyDGsHUgdGnDqm4gY2h14bqpbiBi4buLIGjDoG5nIHThu5NuIGtobyBjaG8gbmjhu69uZyBtw6MgaMOgbmcgbsOgeS4gTsOzaSBjw6FjaCBraMOhYyBj4bqnbiB04bqtcCB0cnVuZyBt4bqjbmcgbG9naXN0aWNzICho4bqtdSBj4bqnbiAtIGtobyBiw6NpIC0gduG6rW4gY2h1eeG7g24pIGNobyBuaOG7r25nIG3DoyBow6BuZyBtYW5nIGzhuqFpIMSR4bq/biA4MCUgZG9hbmggdGh1IGNobyBjw7RuZyB0aS4gDQoNCiMgQ3VzdG9tZXIgU2VnbWVudHMNCg0KU+G7rSBk4bulbmcgdGh14bqtdCB0b8OhbiBLLW1lYW5zIENsdXN0ZXJuaW5nIGNobyAgQ3VzdG9tZXIgU2VnbWVudHMgdGhlbyBSRk0uIENo4buNbiB0aOG7nWkgxJFp4buDbSAoY2jDrW5oIHjDoWMgxJHhur9uIHBow7p0KSBsw6AgMjAxMS0xMi0zMSAyNDo1OSDEkeG7gyB0w61uaCBSLiBDaMO6IMO9IHLhurFuZyB2aeG7h2MgY2jhu41uIHRo4budaSDEkWnhu4NtIGtow7RuZyDhuqNuaCBoxrDhu59uZyDEkeG6v24ga+G6v3QgcXXhuqMgcGjDom4gdMOtY2g6IA0KDQpgYGB7cn0NCiMgS2hv4bqjbmcgdGjhu51pIGdpYW4gdOG7qyB0aOG7nWkgxJFp4buDbSBraMOhY2ggaMOgbmcgZ2lhbyBk4buLY2ggDQojIMSR4bq/biAyMDExLTEyLTMxIGzDoG0gdHLDsm4gxJHhur9uIG5nw6B5OiANCnkgPC0gYXMuZHVyYXRpb24oeW1kX2htKCIyMDExLTEyLTMxIDI0OjU5IikgLSBteV9kZiR0aW1lX3ltZF9obSkgJT4lIGFzLm51bWVyaWMoKQ0KeSA8LSByb3VuZCh5IC8gKDM2MDAqMjQpLCAwKQ0KDQojIFThuqFvIHRow6ptIGJp4bq/biBSZWNlbmN5OiANCm15X2RmICU+JSBtdXRhdGUocmVjZW5jeSA9IHkpIC0+IG15X2RmDQoNCiMgU+G7kSB0aeG7gW4gbcOgIG3hu5l0IGtow6FjaCBow6BuZyDEkcOjIG11YSAodMOtbmggTSk6IA0KbXlfZGYgJT4lIA0KICBncm91cF9ieShDdXN0b21lcklEKSAlPiUgDQogIHN1bW1hcmlzZV9lYWNoKGZ1bnMoc3VtKSwgbW9uZXkpICU+JSANCiAgdW5ncm91cCgpIC0+IGRmX21vbmV5DQoNCiMgVMOtbmggUjogDQpteV9kZiAlPiUgDQogIGdyb3VwX2J5KEN1c3RvbWVySUQpICU+JSANCiAgc3VtbWFyaXNlX2VhY2goZnVucyhtaW4pLCByZWNlbmN5KSAlPiUgDQogIHVuZ3JvdXAoKSAtPiBkZl9yZWNlbmN5DQoNCiMgVMOtbmggRjogDQpteV9kZiAlPiUgDQogIGdyb3VwX2J5KEN1c3RvbWVySUQpICU+JSANCiAgY291bnQoKSAlPiUgDQogIHVuZ3JvdXAoKSAlPiUgDQogIHJlbmFtZShmcmVxID0gbikgLT4gZGZfZnJlcQ0KDQojIEThu68gbGnhu4d1IHPhu60gZOG7pW5nIGNobyBwaMOibiB0w61jaDogDQoNCmRmX21vbmV5ICU+JSANCiAgZnVsbF9qb2luKGRmX3JlY2VuY3ksIGJ5ID0gIkN1c3RvbWVySUQiKSAlPiUgDQogIGZ1bGxfam9pbihkZl9mcmVxLCBieSA9ICJDdXN0b21lcklEIikgJT4lIA0KICBtdXRhdGUoQ3VzdG9tZXJJRCA9IGFzLmNoYXJhY3RlcihDdXN0b21lcklEKSkgLT4gZmluYWxfZGYNCg0KIyBYZW0gcXVhIGThu68gbGnhu4d1IHPhu60gZOG7pW5nIGNobyBwaMOibiB0w61jaCBLLW1lYW5zIENsdXN0ZXJpbmc6IA0KZmluYWxfZGYgJT4lIA0KICBoZWFkKCkgJT4lIA0KICBrYWJsZSgpDQoNCmBgYA0KDQpWw6wgUkZNIMSRxrDhu6NjIMSRbyBi4bqxbmcgY8OhYyB0aMaw4bubYyDEkW8ga2jDoWMgbmhhdSBuw6puIGNow7puZyB0YSBj4bqnbiByZXNjYWxpbmcgY8OhYyBiaeG6v24gc+G7kSBuw6B5OiANCg0KYGBge3J9DQpmaW5hbF9kZiAlPiUgDQogIG11dGF0ZV9pZihpcy5udW1lcmljLCBmdW5jdGlvbih4KSB7KHggLSBtaW4oeCkpIC8gKG1heCh4KSAtIG1pbih4KSl9KSAlPiUgDQogIHNlbGVjdCgtQ3VzdG9tZXJJRCkgLT4gZmluYWxfZGZfc2NhbGVkDQpgYGANCg0KDQpUaHXhuq10IHRvw6FuIMSRw7JpIGjhu49pIHBo4bqjaSBjaOG7jW4gSyAtIGhheSBz4buRIGPhu6VtLiBLIHThu5FpIMawdSDEkcaw4bujYyBs4buxYSBjaOG7jW4gY8SDbiBj4bupIHbDoG8gbmhp4buBdSBwaMawxqFuZyBwaMOhcC4gVHJvbmcgcG9zdCBuw6B5IEsgdOG7kWkgxrB1IMSRxrDhu6NjIGzhu7FhIGNo4buNbiBk4buxYSB0csOqbiBbRWxib3cgTWV0aG9kXShodHRwczovL2JsLm9ja3Mub3JnL3JwZ292ZS8wMDYwZmYzYjY1NjYxOGU5MTM2Yik6IA0KDQpgYGB7cn0NCnNldC5zZWVkKDI5KQ0Kd3NzIDwtIHNhcHBseSgxOjEwLCANCiAgICAgICAgICAgICAgZnVuY3Rpb24oayl7a21lYW5zKGZpbmFsX2RmX3NjYWxlZCAlPiUgc2FtcGxlX2ZyYWMoMC4yKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrLCBuc3RhcnQgPSAzMCkkdG90LndpdGhpbnNzfSkNCg0KDQp1IDwtIGRhdGEuZnJhbWUoayA9IDE6MTAsIFdTUyA9IHdzcykNCg0KdSAlPiUgDQogIGdncGxvdChhZXMoaywgV1NTKSkgKyANCiAgZ2VvbV9saW5lKCkgKyANCiAgZ2VvbV9wb2ludCgpICsgDQogIGdlb21fcG9pbnQoZGF0YSA9IHUgJT4lIGZpbHRlcihrID09IDMpLCBjb2xvciA9ICJyZWQiLCBzaXplID0gMykgKyANCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgxLCAxMCwgYnkgPSAxKSkgKyANCiAgbGFicyh0aXRsZSA9ICJGaWd1cmUgNzogVGhlIE9wdGltYWwgTnVtYmVyIG9mIENsdXN0ZXJzLCBFbGJvdyBNZXRob2QiLCB4ID0gIk51bWJlciBvZiBDbHVzdGVycyAoSykiKSArIA0KICB0aGVtZShwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpKQ0KYGBgDQoNClThu6sgRmlndXJlIDcgY2jDum5nIHRhIHRo4bqleSBy4bqxbmcgYuG6r3QgxJHhuqd1IHThu6sgSyA9IDMgdHLhu58gxJFpIHRow6wgV1NTIGdp4bqjbSB24bubaSB04buRYyDEkeG7mSBy4bqldCBjaOG6rW0uIMSQaeG7gXUgbsOgeSBuZ+G7pSDDvSBy4bqxbmcgSyB04buRaSDGsHUgbsOqbiBsw6AgMy4gQ2jDum5nIHRhIHBow6JuIGPhu6VtIGtow6FjaCBow6BuZyB24bubaSBLID0gMyBuaMawIHNhdTogDQoNCmBgYHtyfQ0KIyBQaMOibiBj4bulbSB24bubaSBrID0gMzogDQpzZXQuc2VlZCgxMjMpDQprbS5yZXMgPC0ga21lYW5zKGZpbmFsX2RmX3NjYWxlZCwgMywgbnN0YXJ0ID0gMzApDQoNCiMgU+G7rSBk4bulbmcga+G6v3QgcXXhuqMgcGjDom4gY+G7pW06IA0KZmluYWxfZGYgJT4lIA0KICBtdXRhdGUoR3JvdXAgPSBrbS5yZXMkY2x1c3RlcikgJT4lIA0KICBtdXRhdGUoR3JvdXAgPSBwYXN0ZSgiR3JvdXAiLCBHcm91cCkpIC0+IGZpbmFsX2RmX2NsdXN0ZXJlZA0KDQoNCiMgQ2jDom4gZHVuZyBj4bunYSBuaMOzbSBraMOhY2ggaMOgbmcgxJHGsOG7o2MgbcO0IHThuqMgcXVhIGJhIHRpw6p1IGNow60gRlJNOiANCmZpbmFsX2RmX2NsdXN0ZXJlZCAlPiUgDQogIGdyb3VwX2J5KEdyb3VwKSAlPiUgDQogIHN1bW1hcmlzZV9lYWNoKGZ1bnMobWVhbiksIG1vbmV5LCByZWNlbmN5LCBmcmVxKSAlPiUgDQogIHVuZ3JvdXAoKSAlPiUgDQogIG11dGF0ZV9pZihpcy5udW1lcmljLCBmdW5jdGlvbih4KSB7cm91bmQoeCwgMCl9KSAlPiUgDQogIGFycmFuZ2UoLW1vbmV5KSAlPiUgDQogIGthYmxlKCkNCg0KYGBgDQoNCkPEg24gY+G7qSB2w6BvIGvhur90IHF14bqjIGPhu6dhIHRodeG6rXQgdG/DoW4gSy1NZWFucyBDbHVzdGVyaW5nIHRow6wgY8OhYyBraMOhY2ggaMOgbmcgc+G6vSDEkcaw4bujYyBwaMOibiB0aMOgbmggMyBuaMOzbSBzYXU6IA0KDQotICoqR3JvdXAgMioqIGzDoCBuaMOzbSBjaGkgdGnDqnUgcuG6pXQgbmhp4buBdSwgcuG6pXQgdGjGsOG7nW5nIHh1ecOqbiBtdWEgaMOgbmcgdsOgIFJlY2VuY3kgYsOpIG5o4bqldC4gTmjDs20gbsOgeSDEkcaw4bujYyBn4buNaSBsw6AgKkNoYW1waW9ucyouIEPDoWNoIHRo4bupYyAiY2jEg20gc8OzYyIgY8OhYyBraMOhY2ggaMOgbmcgbsOgeSDEkcaw4bujYyBtw7QgdOG6oyBrxKkgW+G7nyDEkcOieV0oaHR0cHM6Ly93d3cucHV0bGVyLmNvbS9yZm0tYW5hbHlzaXMvKS4gQsOsbmggcXXDom4gbeG7l2kga2jDoWNoIGjDoG5nIHRodeG7mWMgbmjDs20gbsOgeSBjaGkgdGnDqnUgMzMwOCBi4bqjbmcgQW5oLiANCg0KLSAqKkdyb3VwIDEqKiBsw6AgdGh14buZYyBkaeG7h24gxJHGsOG7o2MgxJHhurd0IHTDqm4gbMOgICpMb3lhbCBDdXN0b21lcnMqLiDEkMOieSBsw6AgbmjDs20ga2jDoWNoIGjDoG5nIGPDsyB0aeG7gW0gbsSDbmcgY2h1eeG7g24gdGjDoG5oIENoYW1waW9ucyBu4bq/dSBjw7RuZyB0aSBiaeG6v3QgdGjhu7FjIGhp4buHbiBuaOG7r25nIGNoaeG6v24gbMaw4bujYyBjaMSDbSBzw7NjIGtow6FjaCBow6BuZywgcXXhuqNuZyBiw6EgcGjDuSBo4bujcC4gDQoNCi0gKipHcm91cCAzKiogbMOgIG5ow7NtIG1hbmcgbOG6oWkgw610IGRvYW5oIHRodS9s4bujaSBuaHXhuq1uIG5o4bqldCBjaG8gY8O0bmcgdGk6IGNoaSB0acOqdSBj4bunYSBo4buNIGNo4buJIGtob+G6o25nIDEvNiBzbyB24bubaSBuaMOzbSAyLiANCg0KVHLGsOG7m2Mga2hpIGPDsyB0aOG7gyBz4butIGThu6VuZyBuaOG7r25nIEluc2lnaHRzIOG7nyB0csOqbiDEkeG7gyBwaOG7pWMgduG7pSBjaG8gY8OhYyBjaGnhur9uIGzGsOG7o2Mga2luaCBkb2FuaCBj4bunYSBjw7RuZyB0aSBjaMO6bmcgdGEgbsOqbiB0aOG6rW4gdHLhu41uZzogdGh14bqtdCB0b8OhbiBLLW1lYW5zIENsdXN0ZXJpbmcgcuG6pXQgbmjhuqF5IGPhuqNtIHbhu5tpIE91dGxpZXJzLiBN4bq3YyBkw7kgZOG7ryBsaeG7h3UgxJHDoyDEkcaw4bujYyBzY2FsaW5nIG5o4bqxbSBnaeG6o20gdGhp4buDdSDhuqNuaCBoxrDhu59uZyBj4bunYSBjw6FjIE91dGxpZXJzIG7DoHkgbmjGsG5nIMSRaeG7gXUgxJHDsyBjxaluZyBraMO0bmcgxJHhuqNtIGLhuqNvIHLhurFuZyBr4bq/dCBxdeG6oyBraMO0bmcgYuG7iyBtw6lvIG3DsyBiaeG6v24gZOG6oW5nIGLhu59pIGPDoWMgYuG6pXQgdGjGsOG7nW5nIG7DoHkuIMSQ4buDIHLDtSByw6BuZyBoxqFuIGNow7puZyB0YSB4ZW0sIHbDrSBk4bulLCBtb25leSBj4bunYSBraMOhY2ggaMOgbmc6IA0KDQoNCmBgYHtyfQ0KZmluYWxfZGYgJT4lIA0KICBnZ3Bsb3QoYWVzKEN1c3RvbWVySUQsIG1vbmV5KSkgKyANCiAgZ2VvbV9jb2woKSArIA0KICB0aGVtZShwYW5lbC5ncmlkLm1pbm9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsgDQogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfYmxhbmsoKSkgKyANCiAgdGhlbWUocGFuZWwuZ3JpZC5taW5vci55ID0gZWxlbWVudF9ibGFuaygpKSArIA0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSkgKyANCiAgbGFicyh0aXRsZSA9ICJGaWd1cmUgODogU3BlbmRpbmcgYnkgQ3VzdG9tZXIiLCB5ID0gIiIpDQpgYGANCg0KRmlndXJlIDggY2hvIHRo4bqleSBt4buZdCBz4buRIGtow6FjaCBow6BuZyBjaGkgdGnDqnUgxJHhurdjIGJp4buHdCBs4bubbi4gSOG7jSBjw7MgdGjhu4Mga2jDtG5nIHBo4bqjaSBsw6Aga2jDoWNoIGjDoG5nIGPDoSBuaMOibiBtw6AgY8OzIHRo4buDIGzDoCBk4bqhbmcgbeG7mXQgY+G7rWEgaMOgbmcgbmjhu48gbXVhIHLhu5NpIGLDoW4gbOG6oWkuIFTGsMahbmcgdOG7sSBsw6AgdOG6p24gc3XhuqV0IG11YSBow6BuZyAoRmlndXJlIDksIMSRxqFuIHbhu4sgdHLDqm4gdHLhu6VjIFkgbMOgIDEwMDApOiANCg0KDQpgYGB7cn0NCmZpbmFsX2RmICU+JSANCiAgbXV0YXRlKGZyZXEgPSBmcmVxIC8gMTAwMCkgJT4lIA0KICBnZ3Bsb3QoYWVzKEN1c3RvbWVySUQsIGZyZXEpKSArIA0KICBnZW9tX2NvbCgpICsgDQogIHRoZW1lKHBhbmVsLmdyaWQubWlub3IueCA9IGVsZW1lbnRfYmxhbmsoKSkgKyANCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9ibGFuaygpKSArIA0KICB0aGVtZShwYW5lbC5ncmlkLm1pbm9yLnkgPSBlbGVtZW50X2JsYW5rKCkpICsgDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpKSArIA0KICBsYWJzKHRpdGxlID0gIkZpZ3VyZSA5OiBGcmVxdWVuY3kgYnkgQ3VzdG9tZXIiLCB5ID0gIiIpDQpgYGANCg0KDQpEbyB0aHXhuq10IHRvw6FuIEstTWVhbnMgQ2x1c3RlcmluZyBy4bqldCBuaOG6oXkgY+G6o20gduG7m2kgY8OhYyBi4bqldCB0aMaw4budbmcgbsOqbiBjaMO6bmcgdGEgY8OzIHRo4buDIHTDoWNoIHJhIG5ow7NtIGtow6FjaCBow6BuZyBi4bqldCB0aMaw4budbmcgbsOgeSDEkeG7gyBwaMOibiB0w61jaCByacOqbmcgdsOgIGNo4buJIGdp4buvIGzhuqFpIGPDoWMgcXVhbiBzw6F0IGtow7RuZyBwaOG6o2kgbMOgIG91dGxpZXJzLiBUcsaw4bubYyBo4bq/dCB2aeG6v3QgbeG7mXQgaMOgbSBkw6FuIG5ow6NuIGNobyBjw6FjIHF1YW4gc8OhdCBi4bqldCB0aMaw4budbmcuIOG7niDEkcOieSBjw6FjIHF1YW4gc8OhdCBi4bqldCB0aMaw4budbmcgxJHGsOG7o2MgxJHhu4tuaCBuZ2jEqWEgbMOgIGzhu5tuIGjGoW4gaG/hurdjIGLDqSBoxqFuIHRydW5nIGLDrG5oIGPhu5luZyB24bubaSAoaG/hurdjIHRy4burKSBiYSBs4bqnbiDEkeG7mSBs4buHY2ggY2h14bqpbjogDQoNCmBgYHtyfQ0KIyBIw6BtIHjDoWMgbWluaCBPdXRsaWVyOiANCm91dGxpZXJfbGFiZWwgPC0gZnVuY3Rpb24oeCkgew0KICBhIDwtIG1lYW4oeCkNCiAgYiA8LSBzZCh4KQ0KICB0aDEgPC0gYSAtIDMqYg0KICB0aDIgPC0gYSArIDMqYg0KICB5IDwtIGNhc2Vfd2hlbih4ID49IHRoMSAmIHggPD0gdGgyIH4gIk5vcm1hbCIsIFRSVUUgfiAiT3V0bGllciIpDQogIHJldHVybih5KQ0KICANCn0NCg0KIyBDaOG7iSBz4butIGThu6VuZyBOb3JtYWwgT2JzZXJ2YXRpb24gY2hvIHRodeG6rXQgdG/DoW4gSy1tZWFucyBDbHVzdGVyaW5nOiANCg0KZmluYWxfZGYgJT4lIA0KICBtdXRhdGUobm9yX21vbmV5ID0gb3V0bGllcl9sYWJlbChtb25leSksIG5vcl9mcmVxID0gb3V0bGllcl9sYWJlbChmcmVxKSkgJT4lIA0KICBmaWx0ZXIobm9yX21vbmV5ID09ICJOb3JtYWwiLCBub3JfZnJlcSA9PSAiTm9ybWFsIikgJT4lIA0KICBzZWxlY3QoMTo0KSAtPiBmaW5hbF9kZl9ub3JtYWwNCg0KZmluYWxfZGZfbm9ybWFsICU+JSANCiAgbXV0YXRlX2lmKGlzLm51bWVyaWMsIGZ1bmN0aW9uKHgpIHsoeCAtIG1pbih4KSkgLyAobWF4KHgpIC0gbWluKHgpKX0pIC0+IGZpbmFsX2RmX25vcm1hbF9zY2FsZWQNCg0KYGBgDQoNClRo4buxYyBoaeG7h24gbOG6oWkgSy1tZWFucyBDbHVzdGVyaW5nLiBUcsaw4bubYyBo4bq/dCBsw6AgdMOsbSBLIHThu5FpIMawdTogDQoNCg0KYGBge3J9DQpzZXQuc2VlZCgyOSkNCndzcyA8LSBzYXBwbHkoMToxMCwgDQogICAgICAgICAgICAgIGZ1bmN0aW9uKGspe2ttZWFucyhmaW5hbF9kZl9ub3JtYWxfc2NhbGVkICU+JSBzZWxlY3QoLUN1c3RvbWVySUQpICU+JSBzYW1wbGVfZnJhYygwLjIpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGssIG5zdGFydCA9IDMwKSR0b3Qud2l0aGluc3N9KQ0KDQoNCnUgPC0gZGF0YS5mcmFtZShrID0gMToxMCwgV1NTID0gd3NzKQ0KDQp1ICU+JSANCiAgZ2dwbG90KGFlcyhrLCBXU1MpKSArIA0KICBnZW9tX2xpbmUoKSArIA0KICBnZW9tX3BvaW50KCkgKyANCiAgZ2VvbV9wb2ludChkYXRhID0gdSAlPiUgZmlsdGVyKGsgPT0gNCksIGNvbG9yID0gInJlZCIsIHNpemUgPSAzKSArIA0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDEsIDEwLCBieSA9IDEpKSArIA0KICBsYWJzKHRpdGxlID0gIkZpZ3VyZSAxMDogVGhlIE9wdGltYWwgTnVtYmVyIG9mIENsdXN0ZXJzLCBFbGJvdyBNZXRob2QiLCANCiAgICAgICBzdWJ0aXRsZSA9ICJPdXRsaWVycyBhcmUgYXJlIHJlbW92ZWQgZnJvbSBzYW1wbGUuIiwgDQogICAgICAgeCA9ICJOdW1iZXIgb2YgQ2x1c3RlcnMgKEspIikgKyANCiAgdGhlbWUocGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSkNCmBgYA0KDQoNCk5oxrAgduG6rXkgc2F1IGtoaSBsb+G6oWkgY8OhYyBPdXRsaWVycyB0aMOsIEsgdOG7kWkgxrB1IGzDoCA0LiBDaOG6oXkgbOG6oWkgSy1tZWFucyBDbHVzdGVyaW5nIHbhu5tpIHPhu5EgY+G7pW0gxJHGsOG7o2MgY2jhu41uIG7DoHkgdsOgIHRo4buxYyBoaeG7h24gbOG6oWkgY8OhYyBwaMOibiB0w61jaCBuaMawIHRyw6puOiANCg0KDQpgYGB7cn0NCiMgUGjDom4gY+G7pW0gduG7m2kgayA9IDQ6IA0Kc2V0LnNlZWQoMTIzKQ0Ka20ucmVzNCA8LSBrbWVhbnMoZmluYWxfZGZfbm9ybWFsX3NjYWxlZCAlPiUgc2VsZWN0KC1DdXN0b21lcklEKSwgNCwgbnN0YXJ0ID0gMzApDQoNCiMgU+G7rSBk4bulbmcga+G6v3QgcXXhuqMgcGjDom4gY+G7pW06IA0KZmluYWxfZGZfbm9ybWFsICU+JSANCiAgbXV0YXRlKEdyb3VwID0ga20ucmVzNCRjbHVzdGVyKSAlPiUgDQogIG11dGF0ZShHcm91cCA9IHBhc3RlKCJHcm91cCIsIEdyb3VwKSkgLT4gZmluYWxfZGZfY2x1c3RlcmVkDQoNCmBgYA0KDQpTYXUga2hpIGxv4bqhaSBPdXRsaWVycyB0aMOsIGtow6FjaCBow6BuZyBz4bq9IMSRxrDhu6NjIHBow6JuIHRow6BuaCA0IG5ow7NtIHbhu5tpIGPDoWMgaMOgbmggdmkgY2hpIHRpw6p1IChk4buxYSB0csOqbiBSLCBGLCBNKSBuaMawIHNhdTogDQoNCmBgYHtyfQ0KIyBDaMOibiBkdW5nIGPhu6dhIG5ow7NtIGtow6FjaCBow6BuZyDEkcaw4bujYyBtw7QgdOG6oyBxdWEgYmEgdGnDqnUgY2jDrSBGUk06IA0KZmluYWxfZGZfY2x1c3RlcmVkICU+JSANCiAgZ3JvdXBfYnkoR3JvdXApICU+JSANCiAgc3VtbWFyaXNlX2VhY2goZnVucyhtZWFuKSwgbW9uZXksIHJlY2VuY3ksIGZyZXEpICU+JSANCiAgdW5ncm91cCgpICU+JSANCiAgbXV0YXRlX2lmKGlzLm51bWVyaWMsIGZ1bmN0aW9uKHgpIHtyb3VuZCh4LCAwKX0pICU+JSANCiAgYXJyYW5nZSgtbW9uZXkpICU+JSANCiAga2FibGUoKQ0KYGBgDQoNClTDrW5oIHRvw6FuIHThu4kgdHLhu41uZyB24buBIGRvYW5oIHRodSB04burIGPDoWMgbmjDs20ga2jDoWNoIGjDoG5nIG7DoHk6IA0KDQpgYGB7cn0NCmZpbmFsX2RmX2NsdXN0ZXJlZCAlPiUgDQogIGdyb3VwX2J5KEdyb3VwKSAlPiUgDQogIHN1bW1hcmlzZV9lYWNoKGZ1bnMoc3VtLCBtZWFuLCBtZWRpYW4sIG1pbiwgbWF4LCBzZCwgbigpKSwgbW9uZXkpICU+JSANCiAgdW5ncm91cCgpICU+JSANCiAgbXV0YXRlKHBlcl9zYWxlID0gcm91bmQoMTAwKnN1bSAvIHN1bShzdW0pLCAyKSkgLT4gc2FsZV9ncm91cA0KDQoNCmxpYnJhcnkoZ2d0aGVtZXMpDQoNCnNhbGVfZ3JvdXAgJT4lIA0KICBnZ3Bsb3QoYWVzKHJlb3JkZXIoR3JvdXAsIHBlcl9zYWxlKSwgcGVyX3NhbGUsIGZpbGwgPSBHcm91cCwgY29sb3IgPSBHcm91cCkpICsgDQogIGdlb21fY29sKHdpZHRoID0gMC41LCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArIA0KICBjb29yZF9mbGlwKCkgKyANCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHBhc3RlKHBlcl9zYWxlLCBwYXN0ZTAocGFzdGUwKCIoIiwgIiUiKSksICIpIikpLCANCiAgICAgICAgICAgIGhqdXN0ID0gLTAuMDUsIGNvbG9yID0gIndoaXRlIiwgc2l6ZSA9IDUpICsgDQogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDkwKSwgZXhwYW5kID0gYygwLjAxLCAwKSkgKyANCiAgc2NhbGVfZmlsbF90YWJsZWF1KCkgKyANCiAgc2NhbGVfY29sb3JfdGFibGVhdSgpICsgDQogIHRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSkgKyANCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSkgKyANCiAgdGhlbWUocGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSkgKyANCiAgbGFicyh4ID0gTlVMTCwgdGl0bGUgPSAiRmlndXJlIDExOiBTaGFyZSBvZiBTYWxlcyBieSBDdXN0b21lciBHcm91cCIpDQoNCmBgYA0KDQoNCkdyb3VwIDQgY2hp4bq/bSA0OS4xJSB04buVbmcgc+G7kSBraMOhY2ggaMOgbmcgdsOgIGzDoCBuaMOzbSBtYW5nIGzhuqFpIGfhuqduIDc1JSBkb2FuaCB0aHUgY2hvIGPDtG5nIHRpOiANCg0KYGBge3J9DQpzYWxlX2dyb3VwICU+JSANCiAgc2VsZWN0KEdyb3VwLCBuKSAlPiUgDQogIG11dGF0ZSh0b3RhbCA9IHN1bShuKSkgJT4lIA0KICBtdXRhdGUobGFiZWwgPSAxMDAqbiAvIHRvdGFsKSAlPiUgDQogIG11dGF0ZShsYWJlbCA9IHBhc3RlKHJvdW5kKGxhYmVsLCAxKSwgIiUiKSkgJT4lIA0KICBnZ3Bsb3QoYWVzKEdyb3VwLCBuLCBmaWxsID0gR3JvdXAsIGNvbG9yID0gR3JvdXApKSArIA0KICBnZW9tX2NvbCh3aWR0aCA9IDAuNSwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKyANCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IGxhYmVsKSwgY29sb3IgPSAid2hpdGUiLCB2anVzdCA9IDEuNCwgc2l6ZSA9IDUpICsgDQogIHNjYWxlX2ZpbGxfdGFibGVhdSgpICsgDQogIHNjYWxlX2NvbG9yX3RhYmxlYXUoKSArIA0KICB0aGVtZShwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpKSArIA0KICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsgDQogIGxhYnMoeCA9IE5VTEwsIHkgPSBOVUxMLCB0aXRsZSA9ICJGaWd1cmUgMTI6IE51bWJlciBvZiBDdXN0b21lcnMgYnkgR3JvdXAiKQ0KICANCmBgYA0KDQoNCiMgR3JvdXAgTGFiZWxsaW5nDQoNCkLDoGkgdG/DoW4gxJHhurd0IHJhIOG7nyDEkcOieSBsw6AgKipt4buZdCBraMOhY2ggaMOgbmcgY8OzIGjDoG5oIHZpIHRpw6p1IGTDuW5nIMSRxrDhu6NjIG3DtCB04bqjIGLhurFuZyBSLCBGIHbDoCBNIGJp4bq/dCB0csaw4bubYyB0aMOsIGtow6FjaCBow6BuZyBuw6B5IHPhur0gxJHGsOG7o2MgeOG6v3AgdsOgbyBuaMOzbSBuw6BvPyoqLiBDw7Mgbmhp4buBdSBjw6FjaCB0aeG6v3AgY+G6rW4vbcO0IGjDrG5oIGPDsyB0aOG7gyDEkcaw4bujYyDDoXAgZOG7pW5nIGNobyBiw6BpIHRvw6FuIG7DoHkgdsOgIG3hu5l0IHRyb25nIG5o4buvbmcgY8OhY2ggxJHDsyBsw6Agc+G7rSBk4bulbmcgY8OhYyB0aHXhuq10IHRvw6FuIHBow6JuIGxv4bqhaSwgdsOtIGThu6UsIFJhbmRvbSBGb3Jlc3Q6IA0KDQoNCmBgYHtyfQ0KIyBDaHXhuqluIGLhu4sgZOG7ryBsaeG7h3U6IA0KZGZfZm9yTUwgPC0gZmluYWxfZGZfY2x1c3RlcmVkICU+JSANCiAgc2VsZWN0KC0gQ3VzdG9tZXJJRCkgJT4lIA0KICBtdXRhdGUoR3JvdXAgPSBhcy5mYWN0b3IoR3JvdXApKQ0KDQojIENoaWEgZOG7ryBsaeG7h3UgdGhlbyB04buJIGzhu4cgODAgLSAyMDoNCg0KbGlicmFyeShjYXJldCkNCnNldC5zZWVkKDEpDQppZCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGRmX2Zvck1MJEdyb3VwLCBwID0gMC44LCBsaXN0ID0gRkFMU0UpDQpkZl90cmFpbiA8LSBkZl9mb3JNTFtpZCwgXQ0KZGZfdGVzdCA8LSBkZl9mb3JNTFstaWQsIF0NCg0KIyBIdeG6pW4gbHV54buHbiBSYW5kb20gRm9yZXN0OiANCnNldC5zZWVkKDEpDQpteV9yZiA8LSB0cmFpbihHcm91cCB+LiwgbWV0aG9kID0gInJmIiwgZGF0YSA9IGRmX3RyYWluKQ0KDQpgYGANCg0KVuG7m2kgUkYgxJHDoyDEkcaw4bujYyBodeG6pW4gbHV54buHbiB0csOqbiBi4buZIGThu68gbGnhu4d1IHRyYWluLCBnaeG6oyBz4butIGPDsyBt4buZdCBxdWFuIHPDoXQgbeG7m2kgduG7m2kgY8OhYyDEkeG6t2MgxJFp4buDbSB24buBIHRpw6p1IGTDuW5nIE0gPSAxNzU3LjU1LCBSID0gNDEsIEYgPSA3MyBuaMawIHNhdTogIA0KYGBge3J9DQojIE3hu5l0IHF1YW4gc8OhdCBj4bulIHRo4buDOiANCmN1czEgPC0gZGZfdGVzdCAlPiUgDQogIHNsaWNlKDEpICU+JSANCiAgc2VsZWN0KC1Hcm91cCkNCg0KY3VzMSAlPiUgDQogIGthYmxlKCkNCg0KYGBgDQoNClRow6wgdGh14bqtdCB0b8OhbiBSRiBz4bq9IGThu7EgYsOhbyBxdWFuIHPDoXQgbsOgeSB0aHXhu5ljIG5ow7NtIDQ6IA0KDQpgYGB7cn0NCnByZWRpY3QobXlfcmYsIGN1czEpICU+JSBhcy5jaGFyYWN0ZXIoKQ0KYGBgDQoNCkNow7puZyB0YSBjbyB0aOG7gyDEkcOhbmggZ2nDoSBjaOG6pXQgbMaw4bujbmcgZOG7sSBiw6FvIGPhu6dhIFJGOiANCg0KDQpgYGB7cn0NCiMgVGjhu7FjIGhp4buHbiBk4buxIGLDoW86IA0KcHJlZCA8LSBwcmVkaWN0KG15X3JmLCBkZl90ZXN0KQ0KDQojIEvhur90IHF14bqjDQpjb25mdXNpb25NYXRyaXgocHJlZCwgZGZfdGVzdCRHcm91cCkNCmBgYA0KDQpL4bq/dCBxdeG6oyBuw6B5IGNo4buJIHJhIHLhurFuZyBSRiBjw7Mga2jhuqMgbsSDbmcgcGjDom4gbG/huqFpIC0gaGF5IGNo4buJIHJhIG3hu5l0IGtow6FjaCBow6BuZyBj4bulIHRo4buDIHbhu5tpIFJGTSDEkcOjIGJp4bq/dCBz4bq9IHRodeG7mWMgduG7gSBuaMOzbSBuw6BvLiBDaOG6pXQgbMaw4bujbmcgZMOhbiBuaMOjbiBjaG8ga2jDoWNoIGjDoG5nIGPDsyB0aOG7gyDEkcaw4bujYyBraeG7g20gdHJhIGLhurFuZyBjw6FjaCDEkeG6t3QgY8OidSBo4buPaSAqUGjhuqNpIGNoxINuZyBjw6FjaCBwaMOibiBsb+G6oWkga2jDoWNoIGjDoG5nIHThu6sgUkYgc+G6vSB04bqhbyByYSBjw6FjIG5ow7NtIGtow6FjaCBow6BuZyBjw7MgY8OhYyDEkeG6t3QgxJFp4buDbSB24buBIGjDoG5oIHZpIGNoaSB0acOqdSBsw6AgcuG6pXQgZ+G6p24gduG7m2kgaMOgbmggdmkgY2hpIHRpw6p1IMSRw6MgxJHGsOG7o2Mga2jhuqNvIHPDoXQ/Ki4gTsOzaSBjw6FjaCBraMOhYyxu4bq/dSB0cnVuZyBiw6xuaCBSLCBGIHbDoCBNIGPhu6dhIG5ow7NtIGtow6FjaCBow6BuZyDEkcaw4bujYyBk4buxIGLDoW8gdOG7qyBSRiBtw6Aga2jDtG5nIGtow6FjIGJp4buHdCAoc28gduG7m2kgdHJ1bmcgYsOsbmggUiwgRiB2w6AgTSBj4bunYSBuaMOzbSBraMOhY2ggaMOgbmcgxJHDoyDEkcaw4bujYyBwaMOibiBj4bulbSB04burIEstbWVhbnMgQ2x1c3RlcmluZykgdGjDrCBjw7MgbmdoxKlhIGzDoCBjw6FjaCBkw6FuIG5ow6NuIGNobyBraMOhY2ggaMOgbmcgdOG7qyBSRiBsw6AgcGjDuSBo4bujcC4gQ2jDum5nIHRhIGPDsyB0aOG7gyBraeG7g20gdHJhIG5oxrAgc2F1OiANCg0KDQpgYGB7cn0NCg0KIyBUcnVuZyBiw6xuaCBj4bunYSBSLCBGIHbDoCBNIGPhu6dhIG5ow7NtIGtow6FjaCBow6BuZyDEkcaw4bujYyBk4buxIGLDoW8gdOG7qyBSRjogDQpkZl90ZXN0ICU+JSANCiAgbXV0YXRlKEdyb3VwUHJlZGljdGVkID0gcHJlZCkgJT4lIA0KICBncm91cF9ieShHcm91cFByZWRpY3RlZCkgJT4lIA0KICBzdW1tYXJpc2VfZWFjaChmdW5zKG1lYW4pLCBtb25leSwgcmVjZW5jeSwgZnJlcSkgJT4lIA0KICBtdXRhdGVfaWYoaXMubnVtZXJpYywgZnVuY3Rpb24oeCkge3JvdW5kKHgsIDApfSkgJT4lIA0KICBrYWJsZSgpDQoNCiMgU28gduG7m2kgdHJ1bmcgYsOsbmggY+G7p2EgUiwgRiB2w6AgTSBj4bunYSBuaMOzbSBraMOhY2ggaMOgbmcgdOG7qyBLLW1lYW5zIENsdXN0ZXJpbmc6IA0KZGZfdHJhaW4gJT4lIA0KICBncm91cF9ieShHcm91cCkgJT4lIA0KICBzdW1tYXJpc2VfZWFjaChmdW5zKG1lYW4pLCBtb25leSwgcmVjZW5jeSwgZnJlcSkgJT4lIA0KICBtdXRhdGVfaWYoaXMubnVtZXJpYywgZnVuY3Rpb24oeCkge3JvdW5kKHgsIDApfSkgJT4lIA0KICBrYWJsZSgpDQoNCiAgDQpgYGANCg0KDQojIFN1bW1hcnkNCg0KUG9zdCBuw6B5IGdp4bubaSB0aGnhu4d1IG3hu5l0IHRyb25nIG5o4buvbmcg4bupbmcgZOG7pW5nIGPGoSBi4bqjbiBuaOG6pXQgY+G7p2EgQ3VzdG9tZXIgU2VnbWVudGF0aW9uIChob+G6t2MgcuG7mW5nIGjGoW4gbMOgIE1hcmtldCBTZWdtZW50YXRpb24pIHbhu5tpIHRodeG6rXQgdG/DoW4gSy1tZWFucyBDbHVzdGVyaW5nIGLhurFuZyBuZ8O0biBuZ+G7ryBSLiBOaOG7r25nIHbhuqtuIMSR4buBIHNhdSBjaMawYSDEkcaw4bujYyDEkeG7gSBj4bqtcCAoaGkgduG7jW5nIHPhur0gxJHGsOG7o2MgdHLDrG5oIGLDoHkgdHJvbmcgY8OhYyBwb3N0IHNhdSk6IA0KDQotIE5o4buvbmcgcGjGsMahbmcgcGjDoXAgdMOsbSBLIHThu5FpIMawdSBi4bqxbmcgW0F2ZXJhZ2UgU2lsaG91ZXR0ZSBNZXRob2RdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1NpbGhvdWV0dGVfKGNsdXN0ZXJpbmcpKSB2w6AgW0dhcCBTdGF0aXN0aWMgTWV0aG9kXShodHRwczovL3N0YXR3ZWIuc3RhbmZvcmQuZWR1L35nd2FsdGhlci9nYXApLiANCg0KLSBN4buZdCBz4buRIHRodeG6rXQgdG/DoW4gcGjDom4gY+G7pW0ga2jDoWMgY8OzIHRo4buDIHRoYXkgdGjhur8gY2hvIEstbWVhbnMgQ2x1c3RlcmluZywgbmjGsCBbSGllcmFyY2hpY2FsIENsdXN0ZXJpbmddKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0hpZXJhcmNoaWNhbF9jbHVzdGVyaW5nKS4gDQoNCi0gTeG7mXQgc+G7kSB0aHXhuq10IHRvw6FuIHBow6JuIGPhu6VtIGtow6FjIMOhcCBk4bulbmcgY2hvIGPhuqMgY2F0ZWdvcmljYWwgbOG6q24gbnVtZXJpYyBkYXRhLiBI4bqhbiBjaOG6vyBj4bunYSBLLW1lYW5zIENsdXN0ZXJpbmcgbMOgIHRodeG6rXQgdG/DoW4gbsOgeSBjaOG7iSBuaOG6rW4gSW5wdXRzIGzDoCBiaeG6v24gc+G7kSAobnVtZXJpYyBkYXRhKS4gDQoNCg0KIyBSZWZlcmVuY2VzDQoNCjEuIENoYXBtYW4sIEMuLCAmIEZlaXQsIEUuIE0uICgyMDE5KS4gUiBmb3IgbWFya2V0aW5nIHJlc2VhcmNoIGFuZCBhbmFseXRpY3MuIE5ldyBZb3JrLCBOWTogU3ByaW5nZXIuDQoyLiBDaGVuLCBELiwgU2FpbiwgUy4gTC4sICYgR3VvLCBLLiAoMjAxMikuIERhdGEgbWluaW5nIGZvciB0aGUgb25saW5lIHJldGFpbCBpbmR1c3RyeTogQSBjYXNlIHN0dWR5IG9mIFJGTSBtb2RlbC1iYXNlZCBjdXN0b21lciBzZWdtZW50YXRpb24gdXNpbmcgZGF0YSBtaW5pbmcuIEpvdXJuYWwgb2YgRGF0YWJhc2UgTWFya2V0aW5nICYgQ3VzdG9tZXIgU3RyYXRlZ3kgTWFuYWdlbWVudCwgMTkoMyksIDE5Ny0yMDguDQozLiBLaGFqdmFuZCwgTS4sICYgVGFyb2toLCBNLiBKLiAoMjAxMSkuIEVzdGltYXRpbmcgY3VzdG9tZXIgZnV0dXJlIHZhbHVlIG9mIGRpZmZlcmVudCBjdXN0b21lciBzZWdtZW50cyBiYXNlZCBvbiBhZGFwdGVkIFJGTSBtb2RlbCBpbiByZXRhaWwgYmFua2luZyBjb250ZXh0LiBQcm9jZWRpYSBDb21wdXRlciBTY2llbmNlLCAzLCAxMzI3LTEzMzIuDQo0LiBTaG11ZWxpLCBHLiwgQnJ1Y2UsIFAuIEMuLCBZYWhhdiwgSS4sIFBhdGVsLCBOLiBSLiwgJiBMaWNodGVuZGFobCBKciwgSy4gQy4gKDIwMTcpLiBEYXRhIG1pbmluZyBmb3IgYnVzaW5lc3MgYW5hbHl0aWNzOiBjb25jZXB0cywgdGVjaG5pcXVlcywgYW5kIGFwcGxpY2F0aW9ucyBpbiBSLiBKb2huIFdpbGV5ICYgU29ucy4NCjUuIFpha3J6ZXdza2EsIEQuLCAmIE11cmxld3NraSwgSi4gKDIwMDUsIFNlcHRlbWJlcikuIENsdXN0ZXJpbmcgYWxnb3JpdGhtcyBmb3IgYmFuayBjdXN0b21lciBzZWdtZW50YXRpb24uIEluIDV0aCBJbnRlcm5hdGlvbmFsIENvbmZlcmVuY2Ugb24gSW50ZWxsaWdlbnQgU3lzdGVtcyBEZXNpZ24gYW5kIEFwcGxpY2F0aW9ucyAoSVNEQScwNSkgKHBwLiAxOTctMjAyKS4gSUVFRS4NCg0KDQoNCg0KDQo=