
Portfolio Theory Again
Portfolio Theory đã được mô tả và giải thích trực quan trong trong phần 1 tập trung vào hai vấn đề chính:
- Những tính toán ban đầu liên quan đến lí thuyết này bằng ngôn ngữ ma trận (lợi tức và rủi ro).
- Tìm danh mục đầu tư mà rủi ro là bé nhất (Global Minimum Variance - GMV Portfolio) dựa trên phương pháp mô phỏng.
Trước hết chúng ta lặp lại việc tìm GMV Portfolio từ 10000 danh mục như đã biết từ phần 1:
# Clear workspace:
rm(list = ls())
# Some stocks selected (Microsoft, Starbucks and Apple):
my_symbols <- c("MSFT", "SBUX", "GE")
# Number of stocks:
n <- length(my_symbols)
# Collect data:
library(tidyquant)
price_data <- tq_get(my_symbols,
from = "2018-01-01",
to = "2020-12-31",
get = "stock.prices")
# Calculate log return:
library(tidyverse)
price_data %>%
group_by(symbol) %>%
mutate(lag1 = lag(adjusted, n = 1L)) %>%
filter(!is.na(lag1)) %>%
mutate(daily_return = log(adjusted / lag1)) %>%
ungroup() %>%
select(symbol, date, daily_return) -> daily_returns
# Calculate risk and return for assets:
daily_returns %>%
group_by(symbol) %>%
summarise(risk = sd(daily_return), return = mean(daily_return)) -> df_risk_return
# Convert to wide form:
daily_returns %>%
pivot_wider(names_from = symbol, values_from = daily_return) %>%
select(-date) -> returns_wide
# Asset mean return:
mu <- colMeans(returns_wide)
# Covariance matrix:
sigma_matrix <- cov(returns_wide)
# Function calculates risk-return for a random-weight portfolio:
library(rportfolios)
portfolio_risk_return <- function(j) {
# Create random weights:
set.seed(j)
X <- random.bounded(n = n, x.t = 1, x.l = rep(-1, n), x.u = rep(1, n))
# Portfolio return:
mu_port_rand = crossprod(X, mu) %>% as.numeric()
# Portfolio risk:
sig_port_rand <- t(X) %*% sigma_matrix %*% X %>% as.numeric() %>% sqrt()
# Create a data frame of results:
df_poft <- tibble(symbol = my_symbols,
weight = X,
return = mu_port_rand,
risk = sig_port_rand,
port_index = j)
return(df_poft)
}
# Use the function and combine results in form of data frame:
lapply(1:10000, portfolio_risk_return) -> all_return_port
do.call("bind_rows", all_return_port) -> df_return_10000
df_return_10000 %>%
filter(!duplicated(port_index)) -> df_return_mini_10000
# Minimum risk portfolio (GMV Portfolio) from simulation:
min_risk <- df_return_mini_10000 %>% slice(which.min(risk))
GMV Portfolio có rủi ro - lợi tức và trọng số của các tài sản được trình bày ở Table 1:
Table 1: Minimum Risk Portfolio by Simulation
symbol
|
weight
|
return
|
risk
|
MSFT
|
0.4500578
|
0.0009458
|
0.017933
|
SBUX
|
0.4588066
|
0.0009458
|
0.017933
|
GE
|
0.0911357
|
0.0009458
|
0.017933
|
Danh mục có rủi ro thấp nhất này đạt được khi các trọng số của MSFT, SBUX và GE lần lượt là 45%, 46% và 9%. Tuy nhiên đây là kết quả rút ra dựa trên mô phỏng. Nói chính xác hơn, đây là danh mục đầu tư có risk nhỏ nhất trong số 10000 danh mục có trọng số ngẫu nhiên.
GMV Portfolio
Portfolio Theory chỉ ra rằng các trọng số các tài sản của GMV Portfolio được tính toán dựa trên việc giải hệ ma trận dưới đây:
\[\begin{equation}
\tag{1}
z_m = A_mb
\end{equation}\]
Với \(z_m\), \(A_m\) và \(b\) là các ma trận dưới đây:
\[\begin{equation*}
A_m =
\begin{pmatrix}
2\Sigma & 1\\
1' & 0
\end{pmatrix}
\end{equation*}\]
\[\begin{equation*}
z_m =
\begin{pmatrix}
m \\
\lambda
\end{pmatrix}
\end{equation*}\]
\[\begin{equation*}
b =
\begin{pmatrix}
0 \\
1
\end{pmatrix}
\end{equation*}\]
Còn \(m\) là ma trận trọng số các tài sản:
\[\begin{equation*}
m =
\begin{pmatrix}
x_A & x_B & x_C
\end{pmatrix}
\end{equation*}\]
Lưu ý rằng tổng các trọng số luôn thoản mãn \(x_A + x_B + x_C = 1\). Dưới đây là R codes tìm tỉ trọng các tài sản cho GMV Portfolio bằng cách giải ma trận được mô tả trong phương trình 1:
## MSFT SBUX GE
## 0.43822562 0.46718882 0.09458556
Kết quả này chỉ ra rằng các trọng số của các tài sản cho GMV Portfolio có chút khác biệt nhỏ so với cách tìm bằng phương pháp mô phỏng. Điều này không có gì lạ vì Portfolio Theory đưa ra công thức chính xác để tìm trọng số cho GMV Portfolio còn phương pháp mô phỏng chỉ thì lại đi theo một hướng khác: chỉ ra ma trận có risk bé nhất trong số 10000 danh mục đầu tư. Dễ thấy rằng hai kết quả này sẽ tiệm cận nhau nếu chúng ta tìm kiếm danh mục có risk nhỏ nhất trong số 1.000.000 danh mục đầu tư ngẫu nhiên.
Để tiện lợi cho việc so sánh, chúng ta viết hàm tính rủi ro - lợi tức của một danh mục đầu tư bất kì khi biết thêm tỉ trọng của các tài sản:
Rồi sử dụng hàm này:
Table 2: GMV Portfolio by Portfolio Theory
symbol
|
weight
|
return
|
risk
|
MSFT
|
0.4382256
|
0.0009357
|
0.0179318
|
SBUX
|
0.4671888
|
0.0009357
|
0.0179318
|
GE
|
0.0945856
|
0.0009357
|
0.0179318
|
So sánh Table 1 và Table 2 chúng ta có thể thấy rằng sự sai khác về risk chỉ là 0.0067%.
Efficient Portfolio
Một Efficient Portfolio là danh mục đầu tư mà với một lợi tức kì vọng cố định trước nhưng lại có rủi ro thấp nhất. Giả sử đặt lợi tức mục tiêu của danh mục là bằng với lợi tức của mã MSFT. Danh mục có mức lợi tức mục tiêu này nhưng rủi ro thấp hơn rủi ro của MSFT sẽ có các trọng số được tính như sau:
#------------------------------------------------------------------
# Efficient portfolio with the same expected return as Microsoft
#------------------------------------------------------------------
return_target <- mu[my_symbols[1]]
top_mat <- cbind(2*sigma_matrix, mu, rep(1, n))
mid_vec <- c(mu, rep(0, n - 1))
bot_vec <- c(rep(1, n), rep(0, n - 1))
A_mat = rbind(top_mat, mid_vec, bot_vec)
bmsft_vec <- c(rep(0, n), return_target, 1)
z_mat <- solve(A_mat) %*% bmsft_vec
X_efficient_as_msft <- z_mat[1:3, 1]
# Print results:
print(X_efficient_as_msft)
## MSFT SBUX GE
## 0.6385378 0.4650062 -0.1035440
Với trọng số tìm ra ở trên chúng ta tính toán cặp risk-return tương ứng và so sánh với MSFT là điểm màu da cam (kí hiệu EFF1) trong Figure 2 dưới đây:
risk_return_portfolio(weights = X_efficient_as_msft) -> df_as_msft
library(ggrepel)
library(scales)
df_return_mini_10000 %>%
ggplot(aes(risk, return)) +
geom_point(alpha = 0.05, color = "blue") +
geom_point(data = min_var %>% slice(1), color = "red", size = 3, shape = 17) +
geom_text_repel(data = min_var %>% slice(1), aes(label = "GMV")) +
geom_point(data = df_risk_return, color = "green", size = 2) +
geom_text_repel(data = df_risk_return, aes(label = symbol)) +
geom_point(data = df_as_msft, color = "orange", size = 2) +
geom_text_repel(data = df_as_msft %>% slice(1), aes(label = "EFF1")) +
labs(x = expression("Risk" ~ (sigma)),
y = expression("Return" ~ (mu)),
title = "Figure 1: Efficient Portfolio with the same return as Microsoft")

Trong Figure 2 thì điểm màu đỏ chính là GMV Portfolio - danh mục có rủi ro thấp nhất với các trọng số được tính toán theo Portfolio Theory. Để hỗ trợ cho tính toán và so sánh chúng ta nên viết hàm tính toán tỉ trọng cho danh mục đầu tư hiệu quả (Efficient Portfolio) khi định trước lợi tức mục tiêu:
# Function find weights for a efficient portfolio with given return:
find_efficient_port <- function(given_return) {
top_mat <- cbind(2*sigma_matrix, mu, rep(1, n))
mid_vec <- c(mu, rep(0, n - 1))
bot_vec <- c(rep(1, n), rep(0, n - 1))
A_mat = rbind(top_mat, mid_vec, bot_vec)
my_vec <- c(rep(0, n), given_return, 1)
z_mat <- solve(A_mat) %*% my_vec
X_efficient <- z_mat[1:3, 1]
X_efficient %>%
risk_return_portfolio() %>%
return()
}
Với một mục tiêu khác về lợi tức - chẳng hạn r = 0.25% thì tỉ trọng các tài sản mà rủi ro thấp nhất là:
Table 3: Efficient Portfolio which Target Return = 0.25%
symbol
|
weight
|
return
|
risk
|
MSFT
|
1.2691154
|
0.0025
|
0.0305428
|
SBUX
|
0.4581352
|
0.0025
|
0.0305428
|
GE
|
-0.7272506
|
0.0025
|
0.0305428
|
Chúng ta khảo sát cơ cấu của danh mục đầu tư với một loạt ngưỡng lợi tức định trước nằm trong khoảng từ 0 đến 0.3%:
# Generate sequence of returns:
target_returns <- seq(0, 0.0025, length.out = 20) %>% unique()
lapply(target_returns, find_efficient_port) -> list_eff_port
do.call("bind_rows", list_eff_port) -> df_eff_port
paste0(round(target_returns*100, 2), "%") -> x_labels
my_colors <- c('#e41a1c','#377eb8','#4daf4a')
df_eff_port %>%
mutate(return = factor(return)) %>%
ggplot(aes(return, weight, fill = symbol)) +
geom_col() +
scale_x_discrete(labels = x_labels) +
scale_y_continuous(limits = c(-1, 2), breaks = seq(-1, 2, 0.25), labels = percent) +
scale_fill_manual(values = my_colors) +
theme(panel.grid.minor = element_blank()) +
theme(legend.title = element_blank()) +
theme(legend.position = c(0.1, 0.85)) +
theme(axis.text = element_text(size = 8)) +
labs(x = expression("Return" ~ (sigma)),
y = "Weight",
title = "Figure 2: Asset Weight of Efficient Portfolios")

Khi lợi tức yêu cầu của danh mục đầu tư tăng, thì từ Figure 3 có thể thấy:
- Tỉ trọng tài sản phân bổ cho MSFT tăng.
- Tỉ trọng tài sản phân bổ cho GE tăng nhưng đây là tài sản “vay mượn”.
- Tại bất kì ngưỡng nào của lực tức thì tỉ trọng của SBUX có vẻ như không thay đổi nhiều. Điều này ngụ ý rằng: lợi tức của danh mục đầu tư là “trơ”, gần như không nhạy trước sự thay đổi về tỉ trọng của mã cổ phiếu này.
Efficient Frontier
Giả sử có hai danh mục đầu tư hiệu quả dưới đây:
- Danh mục đầu tư có rủi ro là thấp nhất nhưng lợi tức là cao nhất có thể được. Đây chính là GMV Portfolio.
- Danh mục đầu tư có lợi tức định trước là 0.25% nhưng rủi ro là thấp nhất (kí hiệu là MaxReturn Portfolio).
Xét danh mục đầu tư được tạo thành từ sự kết hợp của hai danh mục đầu tư trên trong đó tỉ trọng đầu tư vào hai danh mục này là \(\alpha\) và \(\beta\), kí hiệu là Combined Portfolio - CP. Lúc này có hai khả năng xẩy ra:
- Nếu danh mục này có lợi tức cao hơn lợi tức của GMV Portfolio thì danh mục đầu tư này sẽ thuộc đường danh mục hiệu quả (Efficient Frontier).
- Ngược lại, nếu danh mục đầu tư này có lợi tức thấp hơn lợi tức của GMV Portfolio thì danh mục đầu tư này thuộc đường danh mục đầu tư phi hiệu quả (Inefficient Frontier).
Chúng ta xét danh mục đầu tư được hình thành từ hai danh mục đầu tư trên với các trọng số lần lượt là \(\alpha = 0.4\) và \(\beta = 1 - \alpha\) rồi tính toán cặp risk-return cho danh mục này:
Table 4: Risk and Return for Combined Portfolio
symbol
|
weight
|
return
|
risk
|
MSFT
|
0.9367595
|
0.0018743
|
0.0232728
|
SBUX
|
0.4617566
|
0.0018743
|
0.0232728
|
GE
|
-0.3985162
|
0.0018743
|
0.0232728
|
Table 4 cho thấy đây là danh mục thuộc Efficient Frontier vì lợi tức của danh mục này là 0.19% cao hơn lợi tức của GMV Portfolio chỉ có lợi tức là 0.09%. Dễ kiểm tra thấy rằng rủi ro của danh mục đầu tư này 0.0233 - một con số thấp hơn 0.0305 (là rủi ro của danh mục đầu tư hiệu quả có lợi tức mục tiêu 0.25$) nhưng thấp hơn 0.0179 (là rủi ro của GMV Portfolio).
Chúng ta có thể khảo sát và vẽ Efficient Frontier bằng cách tính toán cặp risk-return cho 100 ngưỡng khác nhau của lợi tức từ 0% đến 0.25% như dưới đây:
low_return <- 0
up_return <- df_efficient_025$return[1]
target_returns_100 <- seq(low_return, up_return, length.out = 100) %>% unique()
lapply(target_returns_100, find_efficient_port) -> list_eff_port_100
do.call("bind_rows", list_eff_port_100) -> df_eff_port_100
df_eff_port_100 %>%
filter(!duplicated(return)) -> df_eff_frontier
# GMV portfolio:
df_eff_frontier %>% slice(which.min(risk)) -> gmv_port
# Maximum return portfolio:
df_eff_frontier %>% slice(which.max(risk)) -> max_return_port
# Portfolios belong to Efficient Frontier:
df_eff_frontier %>% filter(return > gmv_port$return) -> df_eff_frontier_line
# Portfolio belong to Inefficient Frontier:
df_eff_frontier %>% filter(return < gmv_port$return) -> df_ineff_frontier
# Graph of Efficient Frontier:
df_return_mini_10000 %>%
ggplot(aes(risk, return)) +
geom_point(alpha = 0.05, color = "blue") +
geom_point(data = df_eff_frontier_line, color = "blue") +
geom_point(data = max_return_port, color = "purple", shape = 18, size = 3) +
geom_text_repel(data = max_return_port, aes(label = "Min25"), direction = "y") +
geom_point(data = df_ineff_frontier, color = "red") +
geom_point(data = gmv_port, color = "green", shape = 18, size = 3) +
geom_text_repel(data = gmv_port, aes(label = "GMV"), direction = "x") +
geom_point(data = df_risk_return, color = "orange", size = 3) +
geom_text_repel(data = df_risk_return, aes(label = symbol)) +
annotate("text", label = "Efficient Frontier", x = 0.022, y = 0.002) +
annotate("text", label = "Inefficient Frontier", x = 0.021, y = 0) +
scale_y_continuous(labels = percent) +
scale_x_continuous(labels = percent) +
labs(x = expression("Risk" ~ (sigma)),
y = expression("Return" ~ (mu)),
title = "Figure 3: Efficient Frontier")

Ở Figure 3 thì Efficient Frontier là đường cong tạo ra bằng cách nối các điểm màu xanh. Inefficient Frontier là đường cong tạo ra bằng cách nối các điểm màu đỏ.
Tangency Portfolio
Sharpe Ratio là một thước đo đánh giá hiệu quả của danh mục đầu tư và được tính theo công thức:
\[S=\frac{\mu_p - r_{f}}{\sigma_p}\] Trong đó \(\sigma_p\) và \(\mu_p\) lần lượt là rủi ro và lợi tức kì vọng của danh mục còn \(r_f\) là lợi tức của tài sản phi rủi ro. Ở đây lợi tức của tài sản phi rủi ro được chọn làm tham chiếu và thường là trái phiếu chính phủ (hoặc T-bills).
Tangency Portfolio là danh mục đầu tư mà có Sharpe Ratio là cao nhất. Dưới đây là R codes tìm tỉ trọng cho các tài sản cho Tangency Portfolio (kí hiệu là TAN) nếu biết trước lãi suất (daily return) của tài sản phi rủi ro (như trái phiếu chính phủ) là 0.01%:
## MSFT SBUX GE
## 1.2563678 0.4582741 -0.7146419
Với tỉ trọng các tài sản tìm được thì cặp risk-return của Tangency Portfolio được tính ở Table 5:
Table 4: Risk and Return for Tangency Portfolio
symbol
|
weight
|
return
|
risk
|
MSFT
|
1.2563678
|
0.002476
|
0.0302365
|
SBUX
|
0.4582741
|
0.002476
|
0.0302365
|
GE
|
-0.7146419
|
0.002476
|
0.0302365
|
Và Sharpe Ratio của danh mục này sẽ là:
## [1] 0.0785804
Mutual Fund Separation Theorem
Xét một danh mục đầu tư được hình thành từ việc phân bổ tỉ trọng là \(x_t\) cho Tangency Portfolio ở trên và phần còn lại là \(1 - x_t\) cho tài sản phi rủi ro T-bills. Rủi ro và lợi tức của danh mục đầu tư này, lần lượt kí hiệu là \(\sigma^{e}_p\) và \(\mu^{e}_p\) sẽ là:
\[\begin{equation} \tag{2} \sigma^{e}_p = x_t\sigma_p \end{equation}\]
\[\begin{equation} \tag{3} \mu^{e}_p = r_f + x_t(\mu_p - r_f) \end{equation}\]
Giả sử một nhà đầu tư thuộc nhóm ngại rủi ro (a highly risk averse investor) đầu tư vào Tangency Portfolio và T-bills với một mục tiêu khiêm tốn về lợi tức và \(\sigma^{e}_p = 0.02\). Danh mục đầu thư thỏa mãn yêu cầu này của nhà đầu tư sẽ có tỉ trọng cho T-bills theo công thức 2 là:
## [1] 0.6614513
Và tỉ trọng phân bổ cho T-bills là:
## [1] 0.3385487
Và lợi tức của danh mục đầu tư theo công thức 3 sẽ là:
## [1] 0.001671608
Cụ thể hơn, tỉ trọng đầu tư vào ba tài sản rủi ro sẽ là:
## MSFT SBUX GE
## 0.8310261 0.3031260 -0.4727008
Dễ suy ra rằng tổng tỉ trọng của các tài sản rủi ro sẽ bằng 0.6614513:
## [1] 0.6614513
Đặt tên cho danh mục đầu tư này là TAN1. Các thông tin về TAN1:
Table 5: Risk and Return for TAN1 Portfolio
symbol
|
weight
|
return
|
risk
|
MSFT
|
0.8310261
|
0.0016716
|
0.02
|
SBUX
|
0.3031260
|
0.0016716
|
0.02
|
GE
|
-0.4727008
|
0.0016716
|
0.02
|
T-bills
|
0.3385487
|
0.0016716
|
0.02
|
Trên hệ trục tọa độ risk-return TAN1 sẽ là điểm nằm trên đường thẳng nối hai điểm TAN và T-bills và đường thẳng này được gọi là Tangency Line. Dưới đây là R codes cho Tangency Line:
# Prepare data:
df_free_risk <- tibble(symbol = "T-bills", risk = 0, return = rf)
df_tangency %>%
slice(1) %>%
mutate(symbol = "TAN") %>%
bind_rows(df_tangency1 %>% slice(1) %>% mutate(symbol = "TAN1")) %>%
bind_rows(df_free_risk %>% mutate(symbol = "T-bills")) -> df_tangency_line
# Plot Tangency Line:
df_eff_frontier_line %>%
ggplot(aes(risk, return)) +
geom_point(color = "blue") +
geom_point(data = df_ineff_frontier, color = "red") +
geom_point(data = gmv_port, color = "green", shape = 18, size = 3) +
geom_text_repel(data = gmv_port, aes(label = "GMV"), direction = "x") +
geom_point(data = df_risk_return, color = "orange", size = 3) +
geom_text_repel(data = df_risk_return, aes(label = symbol)) +
geom_point(data = df_tangency_line, color = "purple", size = 3, shape = 18) +
geom_line(data = df_tangency_line, color = "purple", size = 1) +
geom_text_repel(data = df_tangency_line, aes(label = symbol), direction = "y") +
annotate("text", label = "Tangency Line", x = 0.01, y = 0.0011) +
scale_y_continuous(labels = percent) +
scale_x_continuous(labels = percent) +
labs(x = expression("Risk" ~ (sigma)),
y = expression("Return" ~ (mu)),
title = "Figure 4: Tangency Line")

Một nhà đầu tư có ngưỡng chịu rủi ro cao (a risk tolerant investor) rất có thể ưa thích một danh mục đầu tư có lợi tức cao hơn với cái giá phải trả là rủi ro cao hơn. Những danh mục đầu tư kiểu này sẽ: (1) vẫn nằm trên Tangency Line, và (2) nằm về phía trái của TAN1.
LS0tDQp0aXRsZTogJ1F1YW50aXRhdGl2ZSBGaW5hbmNlOiBQb3J0Zm9saW8gVGhlb3J5IChQYXJ0IDIpJw0KYXV0aG9yOiAnQXV0aG9yOiBOZ3V5ZW4gQ2hpIER1bmcnDQpzdWJ0aXRsZTogIlIgRmluYW5jZSBTZXJpZXMiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6IA0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICAjIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGhpZ2hsaWdodDogemVuYnVybg0KICAgICMgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0aGVtZTogImZsYXRseSINCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfZmxvYXQ6IFRSVUUNCi0tLQ0KDQpgYGB7ciBzZXR1cCxpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgY2FjaGUgPSBUUlVFLCBmaWcuaGVpZ2h0ID0gNywgZmlnLndpZHRoID0gMTApDQoNCmBgYA0KDQohW10oQzovVXNlcnMvQURNSU4vRG9jdW1lbnRzL2ludjIuanBnKQ0KDQojIFBvcnRmb2xpbyBUaGVvcnkgQWdhaW4NCg0KW1BvcnRmb2xpbyBUaGVvcnldKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL01vZGVybl9wb3J0Zm9saW9fdGhlb3J5KSDEkcOjIMSRxrDhu6NjIG3DtCB04bqjIHbDoCBnaeG6o2kgdGjDrWNoIHRy4buxYyBxdWFuIHRyb25nIHRyb25nIFtwaOG6p24gMV0oaHR0cHM6Ly9ycHVicy5jb20vY2hpZHVuZ2t0LzcxNjU0NSkgdOG6rXAgdHJ1bmcgdsOgbyBoYWkgduG6pW4gxJHhu4EgY2jDrW5oOiANCg0KMS4gTmjhu69uZyB0w61uaCB0b8OhbiBiYW4gxJHhuqd1IGxpw6puIHF1YW4gxJHhur9uIGzDrSB0aHV54bq/dCBuw6B5IGLhurFuZyBuZ8O0biBuZ+G7ryBtYSB0cuG6rW4gKGzhu6NpIHThu6ljIHbDoCBy4bunaSBybykuDQoyLiBUw6xtIGRhbmggbeG7pWMgxJHhuqd1IHTGsCBtw6AgcuG7p2kgcm8gbMOgIGLDqSBuaOG6pXQgKEdsb2JhbCBNaW5pbXVtIFZhcmlhbmNlIC0gR01WIFBvcnRmb2xpbykgZOG7sWEgdHLDqm4gcGjGsMahbmcgcGjDoXAgbcO0IHBo4buPbmcuIA0KDQpUcsaw4bubYyBo4bq/dCBjaMO6bmcgdGEgbOG6t3AgbOG6oWkgdmnhu4djIHTDrG0gR01WIFBvcnRmb2xpbyB04burIDEwMDAwIGRhbmggbeG7pWMgbmjGsCDEkcOjIGJp4bq/dCB04burIHBo4bqnbiAxOiANCg0KYGBge3J9DQojIENsZWFyIHdvcmtzcGFjZTogDQpybShsaXN0ID0gbHMoKSkNCg0KIyBTb21lIHN0b2NrcyBzZWxlY3RlZCAoTWljcm9zb2Z0LCBTdGFyYnVja3MgYW5kIEFwcGxlKTogDQoNCm15X3N5bWJvbHMgPC0gYygiTVNGVCIsICJTQlVYIiwgIkdFIikNCg0KIyBOdW1iZXIgb2Ygc3RvY2tzOiANCg0KbiA8LSBsZW5ndGgobXlfc3ltYm9scykNCg0KIyBDb2xsZWN0IGRhdGE6IA0KDQpsaWJyYXJ5KHRpZHlxdWFudCkNCnByaWNlX2RhdGEgPC0gdHFfZ2V0KG15X3N5bWJvbHMsDQogICAgICAgICAgICAgICAgICAgICBmcm9tID0gIjIwMTgtMDEtMDEiLA0KICAgICAgICAgICAgICAgICAgICAgdG8gPSAiMjAyMC0xMi0zMSIsDQogICAgICAgICAgICAgICAgICAgICBnZXQgPSAic3RvY2sucHJpY2VzIikNCg0KIyBDYWxjdWxhdGUgbG9nIHJldHVybjogDQoNCmxpYnJhcnkodGlkeXZlcnNlKQ0KDQpwcmljZV9kYXRhICU+JQ0KICBncm91cF9ieShzeW1ib2wpICU+JSANCiAgbXV0YXRlKGxhZzEgPSBsYWcoYWRqdXN0ZWQsIG4gPSAxTCkpICU+JSANCiAgZmlsdGVyKCFpcy5uYShsYWcxKSkgJT4lIA0KICBtdXRhdGUoZGFpbHlfcmV0dXJuID0gbG9nKGFkanVzdGVkIC8gbGFnMSkpICU+JSANCiAgdW5ncm91cCgpICU+JSANCiAgc2VsZWN0KHN5bWJvbCwgZGF0ZSwgZGFpbHlfcmV0dXJuKSAtPiBkYWlseV9yZXR1cm5zDQoNCiMgQ2FsY3VsYXRlIHJpc2sgYW5kIHJldHVybiBmb3IgYXNzZXRzOg0KICANCmRhaWx5X3JldHVybnMgJT4lIA0KICBncm91cF9ieShzeW1ib2wpICU+JSANCiAgc3VtbWFyaXNlKHJpc2sgPSBzZChkYWlseV9yZXR1cm4pLCByZXR1cm4gPSBtZWFuKGRhaWx5X3JldHVybikpIC0+IGRmX3Jpc2tfcmV0dXJuDQoNCiMgQ29udmVydCB0byB3aWRlIGZvcm06IA0KDQpkYWlseV9yZXR1cm5zICU+JSANCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IHN5bWJvbCwgdmFsdWVzX2Zyb20gPSBkYWlseV9yZXR1cm4pICU+JSANCiAgc2VsZWN0KC1kYXRlKSAtPiByZXR1cm5zX3dpZGUNCg0KIyBBc3NldCBtZWFuIHJldHVybjogDQoNCm11IDwtIGNvbE1lYW5zKHJldHVybnNfd2lkZSkgDQoNCiMgQ292YXJpYW5jZSBtYXRyaXg6IA0KDQpzaWdtYV9tYXRyaXggPC0gY292KHJldHVybnNfd2lkZSkNCg0KIyBGdW5jdGlvbiBjYWxjdWxhdGVzIHJpc2stcmV0dXJuIGZvciBhIHJhbmRvbS13ZWlnaHQgcG9ydGZvbGlvOiANCmxpYnJhcnkocnBvcnRmb2xpb3MpDQoNCnBvcnRmb2xpb19yaXNrX3JldHVybiA8LSBmdW5jdGlvbihqKSB7DQogIA0KICAjIENyZWF0ZSByYW5kb20gd2VpZ2h0czogDQogIHNldC5zZWVkKGopDQogIA0KICBYIDwtIHJhbmRvbS5ib3VuZGVkKG4gPSBuLCB4LnQgPSAxLCB4LmwgPSByZXAoLTEsIG4pLCB4LnUgPSByZXAoMSwgbikpDQoNCiAgIyBQb3J0Zm9saW8gcmV0dXJuOiANCiAgbXVfcG9ydF9yYW5kID0gY3Jvc3Nwcm9kKFgsIG11KSAlPiUgYXMubnVtZXJpYygpDQoNCiAgIyBQb3J0Zm9saW8gcmlzazogDQogIHNpZ19wb3J0X3JhbmQgPC0gdChYKSAlKiUgc2lnbWFfbWF0cml4ICUqJSBYICU+JSBhcy5udW1lcmljKCkgJT4lIHNxcnQoKQ0KICANCiAgIyBDcmVhdGUgYSBkYXRhIGZyYW1lIG9mIHJlc3VsdHM6IA0KICBkZl9wb2Z0IDwtIHRpYmJsZShzeW1ib2wgPSBteV9zeW1ib2xzLCANCiAgICAgICAgICAgICAgICAgICAgd2VpZ2h0ID0gWCwgDQogICAgICAgICAgICAgICAgICAgIHJldHVybiA9IG11X3BvcnRfcmFuZCwgDQogICAgICAgICAgICAgICAgICAgIHJpc2sgPSBzaWdfcG9ydF9yYW5kLA0KICAgICAgICAgICAgICAgICAgICBwb3J0X2luZGV4ID0gaikNCiAgDQogIHJldHVybihkZl9wb2Z0KQ0KDQp9DQoNCiMgVXNlIHRoZSBmdW5jdGlvbiBhbmQgY29tYmluZSByZXN1bHRzIGluIGZvcm0gb2YgZGF0YSBmcmFtZTogDQoNCmxhcHBseSgxOjEwMDAwLCBwb3J0Zm9saW9fcmlza19yZXR1cm4pIC0+IGFsbF9yZXR1cm5fcG9ydA0KDQpkby5jYWxsKCJiaW5kX3Jvd3MiLCBhbGxfcmV0dXJuX3BvcnQpIC0+IGRmX3JldHVybl8xMDAwMA0KDQpkZl9yZXR1cm5fMTAwMDAgJT4lIA0KICBmaWx0ZXIoIWR1cGxpY2F0ZWQocG9ydF9pbmRleCkpIC0+IGRmX3JldHVybl9taW5pXzEwMDAwDQoNCiMgTWluaW11bSByaXNrIHBvcnRmb2xpbyAoR01WIFBvcnRmb2xpbykgZnJvbSBzaW11bGF0aW9uOiANCm1pbl9yaXNrIDwtIGRmX3JldHVybl9taW5pXzEwMDAwICU+JSBzbGljZSh3aGljaC5taW4ocmlzaykpIA0KDQpgYGANCg0KR01WIFBvcnRmb2xpbyBjw7MgcuG7p2kgcm8gLSBs4bujaSB04bupYyB2w6AgdHLhu41uZyBz4buRIGPhu6dhIGPDoWMgdMOgaSBz4bqjbiDEkcaw4bujYyB0csOsbmggYsOgeSDhu58gVGFibGUgMTogDQoNCmBgYHtyfQ0KbGlicmFyeShrYWJsZUV4dHJhKSAjIEZvciBwcmVzZW50aW5nIHRhYmxlLiANCg0KZGZfcmV0dXJuXzEwMDAwICU+JSANCiAgZmlsdGVyKHBvcnRfaW5kZXggPT0gbWluX3Jpc2skcG9ydF9pbmRleCkgJT4lIA0KICBzZWxlY3QoLXBvcnRfaW5kZXgpICU+JSANCiAga2JsKGNhcHRpb24gPSAiVGFibGUgMTogTWluaW11bSBSaXNrIFBvcnRmb2xpbyBieSBTaW11bGF0aW9uIiwgZXNjYXBlID0gVFJVRSkgJT4lDQogIGthYmxlX2NsYXNzaWMoZnVsbF93aWR0aCA9IEZBTFNFLCBodG1sX2ZvbnQgPSAiQ2FtYnJpYSIpDQpgYGANCg0KRGFuaCBt4bulYyBjw7MgcuG7p2kgcm8gdGjhuqVwIG5o4bqldCBuw6B5IMSR4bqhdCDEkcaw4bujYyBraGkgY8OhYyB0cuG7jW5nIHPhu5EgY+G7p2EgTVNGVCwgU0JVWCB2w6AgR0UgbOG6p24gbMaw4bujdCBsw6AgNDUlLCA0NiUgdsOgIDklLiBUdXkgbmhpw6puIMSRw6J5IGzDoCBr4bq/dCBxdeG6oyByw7p0IHJhIGThu7FhIHRyw6puIG3DtCBwaOG7j25nLiBOw7NpIGNow61uaCB4w6FjIGjGoW4sIMSRw6J5IGzDoCBkYW5oIG3hu6VjIMSR4bqndSB0xrAgY8OzIHJpc2sgbmjhu48gbmjhuqV0IHRyb25nIHPhu5EgMTAwMDAgZGFuaCBt4bulYyBjw7MgdHLhu41uZyBz4buRIG5n4bqrdSBuaGnDqm4uIA0KDQoNCiMgR01WIFBvcnRmb2xpbw0KDQpQb3J0Zm9saW8gVGhlb3J5IGNo4buJIHJhIHLhurFuZyBjw6FjIHRy4buNbmcgc+G7kSBjw6FjIHTDoGkgc+G6o24gY+G7p2EgR01WIFBvcnRmb2xpbyDEkcaw4bujYyB0w61uaCB0b8OhbiBk4buxYSB0csOqbiB2aeG7h2MgZ2nhuqNpIGjhu4cgbWEgdHLhuq1uIGTGsOG7m2kgxJHDonk6IA0KDQokJFxiZWdpbntlcXVhdGlvbn0NClx0YWd7MX0NCnpfbSA9IEFfbWINClxlbmR7ZXF1YXRpb259JCQgIA0KDQpW4bubaSAkel9tJCwgJEFfbSQgdsOgICRiJCBsw6AgY8OhYyBtYSB0cuG6rW4gZMaw4bubaSDEkcOieTogDQoNCg0KJCRcYmVnaW57ZXF1YXRpb24qfQ0KQV9tID0gDQpcYmVnaW57cG1hdHJpeH0NCjJcU2lnbWEgJiAxXFwNCjEnICYgMCAgDQpcZW5ke3BtYXRyaXh9DQpcZW5ke2VxdWF0aW9uKn0kJCANCg0KJCRcYmVnaW57ZXF1YXRpb24qfQ0Kel9tID0gDQpcYmVnaW57cG1hdHJpeH0NCm0gXFwNClxsYW1iZGEgIA0KXGVuZHtwbWF0cml4fQ0KXGVuZHtlcXVhdGlvbip9JCQNCg0KJCRcYmVnaW57ZXF1YXRpb24qfQ0KYiA9IA0KXGJlZ2lue3BtYXRyaXh9DQowIFxcDQoxICANClxlbmR7cG1hdHJpeH0NClxlbmR7ZXF1YXRpb24qfSQkDQoNCkPDsm4gJG0kIGzDoCBtYSB0cuG6rW4gdHLhu41uZyBz4buRIGPDoWMgdMOgaSBz4bqjbjogDQoNCiQkXGJlZ2lue2VxdWF0aW9uKn0NCm0gPSANClxiZWdpbntwbWF0cml4fQ0KeF9BICYgeF9CICYgeF9DIA0KXGVuZHtwbWF0cml4fQ0KXGVuZHtlcXVhdGlvbip9JCQNCg0KTMawdSDDvSBy4bqxbmcgdOG7lW5nIGPDoWMgdHLhu41uZyBz4buRIGx1w7RuIHRob+G6o24gbcOjbiAkeF9BICsgeF9CICsgeF9DID0gMSQuIETGsOG7m2kgxJHDonkgbMOgIFIgY29kZXMgdMOsbSB04buJIHRy4buNbmcgY8OhYyB0w6BpIHPhuqNuIGNobyBHTVYgUG9ydGZvbGlvIGLhurFuZyBjw6FjaCBnaeG6o2kgbWEgdHLhuq1uIMSRxrDhu6NjIG3DtCB04bqjIHRyb25nIHBoxrDGoW5nIHRyw6xuaCAxOiANCg0KDQpgYGB7cn0NCg0KIyBDYWxjdWxhdGUgd2VpZ2h0cyBmb3IgR01WIFBvcnRmb2xpbzogDQoNCnRvcF9tYXQgPC0gY2JpbmQoMipzaWdtYV9tYXRyaXgsIHJlcCgxLCBuKSkNCg0KYm90X3ZlYyA8LSBjKHJlcCgxLCBuKSwgMCkNCg0KQW1fbWF0IDwtIHJiaW5kKHRvcF9tYXQsIGJvdF92ZWMpDQoNCmJfdmVjIDwtIGMocmVwKDAsIG4pLCAxKQ0KDQp6bV9tYXQgPC0gc29sdmUoQW1fbWF0KSAlKiUgYl92ZWMNCg0KWF9taW5pbXVtX3ZhciA8LSB6bV9tYXRbMTozLCAxXQ0KDQojIFByaW50IHJlc3VsdHM6IA0KDQpwcmludChYX21pbmltdW1fdmFyKQ0KDQpgYGANCg0KS+G6v3QgcXXhuqMgbsOgeSBjaOG7iSByYSBy4bqxbmcgY8OhYyB0cuG7jW5nIHPhu5EgY+G7p2EgY8OhYyB0w6BpIHPhuqNuIGNobyBHTVYgUG9ydGZvbGlvIGPDsyBjaMO6dCBraMOhYyBiaeG7h3Qgbmjhu48gc28gduG7m2kgY8OhY2ggdMOsbSBi4bqxbmcgcGjGsMahbmcgcGjDoXAgbcO0IHBo4buPbmcuIMSQaeG7gXUgbsOgeSBraMO0bmcgY8OzIGfDrCBs4bqhIHbDrCBQb3J0Zm9saW8gVGhlb3J5IMSRxrBhIHJhIGPDtG5nIHRo4bupYyBjaMOtbmggeMOhYyDEkeG7gyB0w6xtIHRy4buNbmcgc+G7kSBjaG8gR01WIFBvcnRmb2xpbyBjw7JuIHBoxrDGoW5nIHBow6FwIG3DtCBwaOG7j25nIGNo4buJIHRow6wgbOG6oWkgxJFpIHRoZW8gbeG7mXQgaMaw4bubbmcga2jDoWM6IGNo4buJIHJhIG1hIHRy4bqtbiBjw7MgcmlzayBiw6kgbmjhuqV0IHRyb25nIHPhu5EgMTAwMDAgZGFuaCBt4bulYyDEkeG6p3UgdMawLiBE4buFIHRo4bqleSBy4bqxbmcgaGFpIGvhur90IHF14bqjIG7DoHkgc+G6vSB0aeG7h20gY+G6rW4gbmhhdSBu4bq/dSBjaMO6bmcgdGEgdMOsbSBraeG6v20gZGFuaCBt4bulYyBjw7MgcmlzayBuaOG7jyBuaOG6pXQgdHJvbmcgc+G7kSAxLjAwMC4wMDAgZGFuaCBt4bulYyDEkeG6p3UgdMawIG5n4bqrdSBuaGnDqm4uIA0KDQrEkOG7gyB0aeG7h24gbOG7o2kgY2hvIHZp4buHYyBzbyBzw6FuaCwgY2jDum5nIHRhIHZp4bq/dCBow6BtIHTDrW5oIHLhu6dpIHJvIC0gbOG7o2kgdOG7qWMgY+G7p2EgbeG7mXQgZGFuaCBt4bulYyDEkeG6p3UgdMawIGLhuqV0IGvDrCBraGkgYmnhur90IHRow6ptIHThu4kgdHLhu41uZyBj4bunYSBjw6FjIHTDoGkgc+G6o246IA0KDQpgYGB7cn0NCiMgRnVuY3Rpb24gY2FsY3VsYXRlIHJpc2stcmV0dXJuIHdpdGggZ2l2ZSB3ZWlnaHRzOg0KDQpyaXNrX3JldHVybl9wb3J0Zm9saW8gPC0gZnVuY3Rpb24od2VpZ2h0cykgew0KICANCiAgIyBQb3J0Zm9saW8gcmV0dXJuOiANCiAgbXVfcG9ydCA8LSBjcm9zc3Byb2Qod2VpZ2h0cywgbXUpICU+JSBhcy5udW1lcmljKCkNCg0KICAjIFBvcnRmb2xpbyByaXNrOiANCg0KICBzaWcyX3BvcnQgPC0gdCh3ZWlnaHRzKSAlKiUgc2lnbWFfbWF0cml4ICUqJSB3ZWlnaHRzDQoNCiAgc2lnX3BvcnQgPC0gc2lnMl9wb3J0ICU+JSBhcy5udW1lcmljKCkgJT4lIHNxcnQoKQ0KDQogICMgUHJpbnQgcmVzdWx0czogDQogIA0KICB0aWJibGUoc3ltYm9sID0gbXlfc3ltYm9scywgd2VpZ2h0ID0gd2VpZ2h0cywgcmV0dXJuID0gbXVfcG9ydCwgcmlzayA9IHNpZ19wb3J0KSAtPiBkZl9yZXN1bHRzDQogIA0KICByZXR1cm4oZGZfcmVzdWx0cykNCg0KfQ0KDQpgYGANCg0KUuG7k2kgc+G7rSBk4bulbmcgaMOgbSBuw6B5OiANCg0KYGBge3J9DQpyaXNrX3JldHVybl9wb3J0Zm9saW8od2VpZ2h0cyA9IFhfbWluaW11bV92YXIpIC0+IG1pbl92YXINCg0KbWluX3ZhciAlPiUgDQogIGtibChjYXB0aW9uID0gIlRhYmxlIDI6IEdNViBQb3J0Zm9saW8gYnkgUG9ydGZvbGlvIFRoZW9yeSIsIGVzY2FwZSA9IFRSVUUpICU+JQ0KICBrYWJsZV9jbGFzc2ljKGZ1bGxfd2lkdGggPSBGQUxTRSwgaHRtbF9mb250ID0gIkNhbWJyaWEiKQ0KICANCmBgYA0KDQpTbyBzw6FuaCBUYWJsZSAxIHbDoCBUYWJsZSAyIGNow7puZyB0YSBjw7MgdGjhu4MgdGjhuqV5IHLhurFuZyBz4buxIHNhaSBraMOhYyB24buBIHJpc2sgY2jhu4kgbMOgIDAuMDA2NyUuIA0KDQojIEVmZmljaWVudCBQb3J0Zm9saW8NCg0KTeG7mXQgRWZmaWNpZW50IFBvcnRmb2xpbyBsw6AgZGFuaCBt4bulYyDEkeG6p3UgdMawIG3DoCB24bubaSBt4buZdCBs4bujaSB04bupYyBrw6wgduG7jW5nIGPhu5EgxJHhu4tuaCB0csaw4bubYyBuaMawbmcgbOG6oWkgY8OzIHLhu6dpIHJvIHRo4bqlcCBuaOG6pXQuIEdp4bqjIHPhu60gxJHhurd0IGzhu6NpIHThu6ljIG3hu6VjIHRpw6p1IGPhu6dhIGRhbmggbeG7pWMgbMOgIGLhurFuZyB24bubaSBs4bujaSB04bupYyBj4bunYSBtw6MgTVNGVC4gRGFuaCBt4bulYyBjw7MgbeG7qWMgbOG7o2kgdOG7qWMgbeG7pWMgdGnDqnUgbsOgeSBuaMawbmcgcuG7p2kgcm8gdGjhuqVwIGjGoW4gcuG7p2kgcm8gY+G7p2EgTVNGVCBz4bq9IGPDsyBjw6FjIHRy4buNbmcgc+G7kSDEkcaw4bujYyB0w61uaCBuaMawIHNhdTogDQoNCg0KYGBge3J9DQojLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQojICBFZmZpY2llbnQgcG9ydGZvbGlvIHdpdGggdGhlIHNhbWUgZXhwZWN0ZWQgcmV0dXJuIGFzIE1pY3Jvc29mdA0KIy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQpyZXR1cm5fdGFyZ2V0IDwtIG11W215X3N5bWJvbHNbMV1dDQoNCnRvcF9tYXQgPC0gY2JpbmQoMipzaWdtYV9tYXRyaXgsIG11LCByZXAoMSwgbikpDQoNCm1pZF92ZWMgPC0gYyhtdSwgcmVwKDAsIG4gLSAxKSkNCg0KYm90X3ZlYyA8LSBjKHJlcCgxLCBuKSwgcmVwKDAsIG4gLSAxKSkNCg0KQV9tYXQgPSByYmluZCh0b3BfbWF0LCBtaWRfdmVjLCBib3RfdmVjKQ0KDQpibXNmdF92ZWMgPC0gYyhyZXAoMCwgbiksIHJldHVybl90YXJnZXQsIDEpDQoNCnpfbWF0IDwtIHNvbHZlKEFfbWF0KSAlKiUgYm1zZnRfdmVjDQoNClhfZWZmaWNpZW50X2FzX21zZnQgPC0gel9tYXRbMTozLCAxXQ0KDQojIFByaW50IHJlc3VsdHM6IA0KcHJpbnQoWF9lZmZpY2llbnRfYXNfbXNmdCkNCg0KYGBgDQoNClbhu5tpIHRy4buNbmcgc+G7kSB0w6xtIHJhIOG7nyB0csOqbiBjaMO6bmcgdGEgdMOtbmggdG/DoW4gY+G6t3Agcmlzay1yZXR1cm4gdMawxqFuZyDhu6luZyB2w6Agc28gc8OhbmggduG7m2kgTVNGVCBsw6AgxJFp4buDbSBtw6B1IGRhIGNhbSAoa8OtIGhp4buHdSBFRkYxKSB0cm9uZyBGaWd1cmUgMiBkxrDhu5tpIMSRw6J5OiANCg0KYGBge3J9DQoNCnJpc2tfcmV0dXJuX3BvcnRmb2xpbyh3ZWlnaHRzID0gWF9lZmZpY2llbnRfYXNfbXNmdCkgLT4gZGZfYXNfbXNmdA0KDQpsaWJyYXJ5KGdncmVwZWwpDQpsaWJyYXJ5KHNjYWxlcykNCg0KZGZfcmV0dXJuX21pbmlfMTAwMDAgJT4lIA0KICBnZ3Bsb3QoYWVzKHJpc2ssIHJldHVybikpICsgDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjA1LCBjb2xvciA9ICJibHVlIikgKyANCiAgZ2VvbV9wb2ludChkYXRhID0gbWluX3ZhciAlPiUgc2xpY2UoMSksIGNvbG9yID0gInJlZCIsIHNpemUgPSAzLCBzaGFwZSA9IDE3KSArIA0KICBnZW9tX3RleHRfcmVwZWwoZGF0YSA9IG1pbl92YXIgJT4lIHNsaWNlKDEpLCBhZXMobGFiZWwgPSAiR01WIikpICsgDQogIGdlb21fcG9pbnQoZGF0YSA9IGRmX3Jpc2tfcmV0dXJuLCBjb2xvciA9ICJncmVlbiIsIHNpemUgPSAyKSArIA0KICBnZW9tX3RleHRfcmVwZWwoZGF0YSA9IGRmX3Jpc2tfcmV0dXJuLCBhZXMobGFiZWwgPSBzeW1ib2wpKSArICANCiAgZ2VvbV9wb2ludChkYXRhID0gZGZfYXNfbXNmdCwgY29sb3IgPSAib3JhbmdlIiwgc2l6ZSA9IDIpICsgDQogIGdlb21fdGV4dF9yZXBlbChkYXRhID0gZGZfYXNfbXNmdCAlPiUgc2xpY2UoMSksIGFlcyhsYWJlbCA9ICJFRkYxIikpICsgDQogIGxhYnMoeCA9IGV4cHJlc3Npb24oIlJpc2siIH4gKHNpZ21hKSksIA0KICAgICAgIHkgPSBleHByZXNzaW9uKCJSZXR1cm4iIH4gKG11KSksIA0KICAgICAgIHRpdGxlID0gIkZpZ3VyZSAxOiBFZmZpY2llbnQgUG9ydGZvbGlvIHdpdGggdGhlIHNhbWUgcmV0dXJuIGFzIE1pY3Jvc29mdCIpIA0KICANCmBgYA0KDQpUcm9uZyBGaWd1cmUgMiB0aMOsIMSRaeG7g20gbcOgdSDEkeG7jyBjaMOtbmggbMOgIEdNViBQb3J0Zm9saW8gLSBkYW5oIG3hu6VjIGPDsyBy4bunaSBybyB0aOG6pXAgbmjhuqV0IHbhu5tpIGPDoWMgdHLhu41uZyBz4buRIMSRxrDhu6NjIHTDrW5oIHRvw6FuIHRoZW8gUG9ydGZvbGlvIFRoZW9yeS4gxJDhu4MgaOG7lyB0cuG7oyBjaG8gdMOtbmggdG/DoW4gdsOgIHNvIHPDoW5oIGNow7puZyB0YSBuw6puIHZp4bq/dCBow6BtIHTDrW5oIHRvw6FuIHThu4kgdHLhu41uZyBjaG8gZGFuaCBt4bulYyDEkeG6p3UgdMawIGhp4buHdSBxdeG6oyAoRWZmaWNpZW50IFBvcnRmb2xpbykga2hpIMSR4buLbmggdHLGsOG7m2MgbOG7o2kgdOG7qWMgbeG7pWMgdGnDqnU6IA0KDQpgYGB7cn0NCiMgRnVuY3Rpb24gZmluZCB3ZWlnaHRzIGZvciBhIGVmZmljaWVudCBwb3J0Zm9saW8gd2l0aCBnaXZlbiByZXR1cm46IA0KDQpmaW5kX2VmZmljaWVudF9wb3J0IDwtIGZ1bmN0aW9uKGdpdmVuX3JldHVybikgew0KICANCiAgdG9wX21hdCA8LSBjYmluZCgyKnNpZ21hX21hdHJpeCwgbXUsIHJlcCgxLCBuKSkNCiAgDQogIG1pZF92ZWMgPC0gYyhtdSwgcmVwKDAsIG4gLSAxKSkNCg0KICBib3RfdmVjIDwtIGMocmVwKDEsIG4pLCByZXAoMCwgbiAtIDEpKQ0KDQogIEFfbWF0ID0gcmJpbmQodG9wX21hdCwgbWlkX3ZlYywgYm90X3ZlYykNCg0KICBteV92ZWMgPC0gYyhyZXAoMCwgbiksIGdpdmVuX3JldHVybiwgMSkNCg0KICB6X21hdCA8LSBzb2x2ZShBX21hdCkgJSolIG15X3ZlYw0KDQogIFhfZWZmaWNpZW50IDwtIHpfbWF0WzE6MywgMV0NCiAgDQogIFhfZWZmaWNpZW50ICU+JSANCiAgICByaXNrX3JldHVybl9wb3J0Zm9saW8oKSAlPiUgDQogICAgcmV0dXJuKCkNCn0NCmBgYA0KDQoNClbhu5tpIG3hu5l0IG3hu6VjIHRpw6p1IGtow6FjIHbhu4EgbOG7o2kgdOG7qWMgLSBjaOG6s25nIGjhuqFuICpyID0gMC4yNSUqIHRow6wgdOG7iSB0cuG7jW5nIGPDoWMgdMOgaSBz4bqjbiBtw6AgcuG7p2kgcm8gdGjhuqVwIG5o4bqldCBsw6A6IA0KDQpgYGB7cn0NCmZpbmRfZWZmaWNpZW50X3BvcnQoZ2l2ZW5fcmV0dXJuID0gMC4wMDI1KSAtPiBkZl9lZmZpY2llbnRfMDI1DQoNCg0KZGZfZWZmaWNpZW50XzAyNSAlPiUgDQogIGtibChjYXB0aW9uID0gIlRhYmxlIDM6IEVmZmljaWVudCBQb3J0Zm9saW8gd2hpY2ggVGFyZ2V0IFJldHVybiA9IDAuMjUlIiwgZXNjYXBlID0gVFJVRSkgJT4lDQogIGthYmxlX2NsYXNzaWMoZnVsbF93aWR0aCA9IEZBTFNFLCBodG1sX2ZvbnQgPSAiQ2FtYnJpYSIpDQpgYGANCg0KQ2jDum5nIHRhIGto4bqjbyBzw6F0IGPGoSBj4bqldSBj4bunYSBkYW5oIG3hu6VjIMSR4bqndSB0xrAgduG7m2kgbeG7mXQgbG/huqF0IG5nxrDhu6FuZyBs4bujaSB04bupYyDEkeG7i25oIHRyxrDhu5tjIG7hurFtIHRyb25nIGtob+G6o25nIHThu6sgMCDEkeG6v24gMC4zJTogDQoNCmBgYHtyfQ0KDQojIEdlbmVyYXRlIHNlcXVlbmNlIG9mIHJldHVybnM6IA0KdGFyZ2V0X3JldHVybnMgPC0gc2VxKDAsIDAuMDAyNSwgbGVuZ3RoLm91dCA9IDIwKSAlPiUgdW5pcXVlKCkNCmxhcHBseSh0YXJnZXRfcmV0dXJucywgZmluZF9lZmZpY2llbnRfcG9ydCkgLT4gbGlzdF9lZmZfcG9ydA0KZG8uY2FsbCgiYmluZF9yb3dzIiwgbGlzdF9lZmZfcG9ydCkgLT4gZGZfZWZmX3BvcnQNCg0KcGFzdGUwKHJvdW5kKHRhcmdldF9yZXR1cm5zKjEwMCwgMiksICIlIikgLT4geF9sYWJlbHMNCm15X2NvbG9ycyA8LSBjKCcjZTQxYTFjJywnIzM3N2ViOCcsJyM0ZGFmNGEnKQ0KDQpkZl9lZmZfcG9ydCAlPiUgDQogIG11dGF0ZShyZXR1cm4gPSBmYWN0b3IocmV0dXJuKSkgJT4lIA0KICBnZ3Bsb3QoYWVzKHJldHVybiwgd2VpZ2h0LCBmaWxsID0gc3ltYm9sKSkgKyANCiAgZ2VvbV9jb2woKSArIA0KICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscyA9IHhfbGFiZWxzKSArIA0KICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygtMSwgMiksIGJyZWFrcyA9IHNlcSgtMSwgMiwgMC4yNSksIGxhYmVscyA9IHBlcmNlbnQpICsgDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IG15X2NvbG9ycykgKyANCiAgdGhlbWUocGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSkgKyANCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArIA0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuMSwgMC44NSkpICsgDQogIHRoZW1lKGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gOCkpICsgDQogIGxhYnMoeCA9IGV4cHJlc3Npb24oIlJldHVybiIgfiAoc2lnbWEpKSwgDQogICAgICAgeSA9ICJXZWlnaHQiLCANCiAgICAgICB0aXRsZSA9ICJGaWd1cmUgMjogQXNzZXQgV2VpZ2h0IG9mIEVmZmljaWVudCBQb3J0Zm9saW9zIikgDQogIA0KYGBgDQoNCktoaSBs4bujaSB04bupYyB5w6p1IGPhuqd1IGPhu6dhIGRhbmggbeG7pWMgxJHhuqd1IHTGsCB0xINuZywgdGjDrCB04burIEZpZ3VyZSAzIGPDsyB0aOG7gyB0aOG6pXk6IA0KDQoxLiBU4buJIHRy4buNbmcgdMOgaSBz4bqjbiBwaMOibiBi4buVIGNobyBNU0ZUIHTEg25nLiANCjIuIFThu4kgdHLhu41uZyB0w6BpIHPhuqNuIHBow6JuIGLhu5UgY2hvIEdFIHTEg25nIG5oxrBuZyDEkcOieSBsw6AgdMOgaSBz4bqjbiAidmF5IG3GsOG7o24iLiANCjMuIFThuqFpIGLhuqV0IGvDrCBuZ8aw4buhbmcgbsOgbyBj4bunYSBs4buxYyB04bupYyB0aMOsIHThu4kgdHLhu41uZyBj4bunYSBTQlVYIGPDsyB24bq7IG5oxrAga2jDtG5nIHRoYXkgxJHhu5VpIG5oaeG7gXUuIMSQaeG7gXUgbsOgeSBuZ+G7pSDDvSBy4bqxbmc6IGzhu6NpIHThu6ljIGPhu6dhIGRhbmggbeG7pWMgxJHhuqd1IHTGsCBsw6AgInRyxqEiLCBn4bqnbiBuaMawIGtow7RuZyBuaOG6oXkgdHLGsOG7m2Mgc+G7sSB0aGF5IMSR4buVaSB24buBIHThu4kgdHLhu41uZyBj4bunYSBtw6MgY+G7lSBwaGnhur91IG7DoHkuIA0KDQoNCiMgRWZmaWNpZW50IEZyb250aWVyDQoNCkdp4bqjIHPhu60gY8OzIGhhaSBkYW5oIG3hu6VjIMSR4bqndSB0xrAgaGnhu4d1IHF14bqjIGTGsOG7m2kgxJHDonk6IA0KDQoxLiBEYW5oIG3hu6VjIMSR4bqndSB0xrAgY8OzIHLhu6dpIHJvIGzDoCB0aOG6pXAgbmjhuqV0IG5oxrBuZyBs4bujaSB04bupYyBsw6AgY2FvIG5o4bqldCBjw7MgdGjhu4MgxJHGsOG7o2MuIMSQw6J5IGNow61uaCBsw6AgR01WIFBvcnRmb2xpby4gDQoyLiBEYW5oIG3hu6VjIMSR4bqndSB0xrAgY8OzIGzhu6NpIHThu6ljIMSR4buLbmggdHLGsOG7m2MgbMOgIDAuMjUlIG5oxrBuZyBy4bunaSBybyBsw6AgdGjhuqVwIG5o4bqldCAoa8OtIGhp4buHdSBsw6AgTWF4UmV0dXJuIFBvcnRmb2xpbykuIA0KDQpYw6l0IGRhbmggbeG7pWMgxJHhuqd1IHTGsCDEkcaw4bujYyB04bqhbyB0aMOgbmggdOG7qyBz4buxIGvhur90IGjhu6NwIGPhu6dhIGhhaSBkYW5oIG3hu6VjIMSR4bqndSB0xrAgdHLDqm4gdHJvbmcgxJHDsyB04buJIHRy4buNbmcgxJHhuqd1IHTGsCB2w6BvIGhhaSBkYW5oIG3hu6VjIG7DoHkgbMOgICRcYWxwaGEkIHbDoCAkXGJldGEkLCBrw60gaGnhu4d1IGzDoCBDb21iaW5lZCBQb3J0Zm9saW8gLSBDUC4gTMO6YyBuw6B5IGPDsyBoYWkga2jhuqMgbsSDbmcgeOG6qXkgcmE6IA0KDQoxLiBO4bq/dSBkYW5oIG3hu6VjIG7DoHkgY8OzIGzhu6NpIHThu6ljIGNhbyBoxqFuIGzhu6NpIHThu6ljIGPhu6dhIEdNViBQb3J0Zm9saW8gdGjDrCBkYW5oIG3hu6VjIMSR4bqndSB0xrAgbsOgeSBz4bq9IHRodeG7mWMgxJHGsOG7nW5nIGRhbmggbeG7pWMgaGnhu4d1IHF14bqjIChFZmZpY2llbnQgRnJvbnRpZXIpLiAgDQoyLiBOZ8aw4bujYyBs4bqhaSwgbuG6v3UgZGFuaCBt4bulYyDEkeG6p3UgdMawIG7DoHkgY8OzIGzhu6NpIHThu6ljIHRo4bqlcCBoxqFuIGzhu6NpIHThu6ljIGPhu6dhIEdNViBQb3J0Zm9saW8gdGjDrCBkYW5oIG3hu6VjIMSR4bqndSB0xrAgbsOgeSB0aHXhu5ljIMSRxrDhu51uZyBkYW5oIG3hu6VjIMSR4bqndSB0xrAgcGhpIGhp4buHdSBxdeG6oyAoSW5lZmZpY2llbnQgRnJvbnRpZXIpLiANCg0KQ2jDum5nIHRhIHjDqXQgZGFuaCBt4bulYyDEkeG6p3UgdMawIMSRxrDhu6NjIGjDrG5oIHRow6BuaCB04burIGhhaSBkYW5oIG3hu6VjIMSR4bqndSB0xrAgdHLDqm4gduG7m2kgY8OhYyB0cuG7jW5nIHPhu5EgbOG6p24gbMaw4bujdCBsw6AgJFxhbHBoYSA9IDAuNCQgdsOgICRcYmV0YSA9IDEgLSBcYWxwaGEkIHLhu5NpIHTDrW5oIHRvw6FuIGPhurdwIHJpc2stcmV0dXJuIGNobyBkYW5oIG3hu6VjIG7DoHk6DQoNCg0KYGBge3J9DQojIEFzc2V0IHdlaWdodHMgZm9yIGNvbWJpbmVkIHBvcnRmb2xpbzogDQoNCmFscGhhIDwtIDAuNA0KYmV0YSA8LSAxIC0gYWxwaGENClhfY29tYmluZWQgPC0gYWxwaGEqbWluX3ZhciR3ZWlnaHQgKyBiZXRhKmRmX2VmZmljaWVudF8wMjUkd2VpZ2h0DQoNCiMgUmlzayBhbmQgUmV0dXJuOiANCg0Kcmlza19yZXR1cm5fcG9ydGZvbGlvKHdlaWdodHMgPSBYX2NvbWJpbmVkKSAtPiBkZl9jb21iaW5lZF9wb3J0DQoNCmRmX2NvbWJpbmVkX3BvcnQgJT4lIA0KICBrYmwoY2FwdGlvbiA9ICJUYWJsZSA0OiBSaXNrIGFuZCBSZXR1cm4gZm9yIENvbWJpbmVkIFBvcnRmb2xpbyIsIGVzY2FwZSA9IFRSVUUpICU+JQ0KICBrYWJsZV9jbGFzc2ljKGZ1bGxfd2lkdGggPSBGQUxTRSwgaHRtbF9mb250ID0gIkNhbWJyaWEiKQ0KYGBgDQoNClRhYmxlIDQgY2hvIHRo4bqleSDEkcOieSBsw6AgZGFuaCBt4bulYyB0aHXhu5ljIEVmZmljaWVudCBGcm9udGllciB2w6wgbOG7o2kgdOG7qWMgY+G7p2EgZGFuaCBt4bulYyBuw6B5IGzDoCAwLjE5JSBjYW8gaMahbiBs4bujaSB04bupYyBj4bunYSBHTVYgUG9ydGZvbGlvIGNo4buJIGPDsyBs4bujaSB04bupYyBsw6AgMC4wOSUuIEThu4Uga2nhu4NtIHRyYSB0aOG6pXkgcuG6sW5nIHLhu6dpIHJvIGPhu6dhIGRhbmggbeG7pWMgxJHhuqd1IHTGsCBuw6B5IDAuMDIzMyAtIG3hu5l0IGNvbiBz4buRIHRo4bqlcCBoxqFuIDAuMDMwNSAobMOgIHLhu6dpIHJvIGPhu6dhIGRhbmggbeG7pWMgxJHhuqd1IHTGsCBoaeG7h3UgcXXhuqMgY8OzIGzhu6NpIHThu6ljIG3hu6VjIHRpw6p1IDAuMjUkKSBuaMawbmcgdGjhuqVwIGjGoW4gMC4wMTc5IChsw6AgcuG7p2kgcm8gY+G7p2EgR01WIFBvcnRmb2xpbykuIA0KDQpDaMO6bmcgdGEgY8OzIHRo4buDIGto4bqjbyBzw6F0IHbDoCB24bq9IEVmZmljaWVudCBGcm9udGllciBi4bqxbmcgY8OhY2ggdMOtbmggdG/DoW4gY+G6t3Agcmlzay1yZXR1cm4gY2hvIDEwMCBuZ8aw4buhbmcga2jDoWMgbmhhdSBj4bunYSBs4bujaSB04bupYyB04burIDAlIMSR4bq/biAwLjI1JSBuaMawIGTGsOG7m2kgxJHDonk6ICANCg0KYGBge3J9DQpsb3dfcmV0dXJuIDwtIDANCg0KdXBfcmV0dXJuIDwtIGRmX2VmZmljaWVudF8wMjUkcmV0dXJuWzFdDQoNCnRhcmdldF9yZXR1cm5zXzEwMCA8LSBzZXEobG93X3JldHVybiwgdXBfcmV0dXJuLCBsZW5ndGgub3V0ID0gMTAwKSAlPiUgdW5pcXVlKCkNCg0KbGFwcGx5KHRhcmdldF9yZXR1cm5zXzEwMCwgZmluZF9lZmZpY2llbnRfcG9ydCkgLT4gbGlzdF9lZmZfcG9ydF8xMDANCg0KZG8uY2FsbCgiYmluZF9yb3dzIiwgbGlzdF9lZmZfcG9ydF8xMDApIC0+IGRmX2VmZl9wb3J0XzEwMA0KDQpkZl9lZmZfcG9ydF8xMDAgJT4lIA0KICBmaWx0ZXIoIWR1cGxpY2F0ZWQocmV0dXJuKSkgLT4gZGZfZWZmX2Zyb250aWVyDQoNCiMgR01WIHBvcnRmb2xpbzogDQpkZl9lZmZfZnJvbnRpZXIgJT4lIHNsaWNlKHdoaWNoLm1pbihyaXNrKSkgLT4gZ212X3BvcnQgDQoNCiMgTWF4aW11bSByZXR1cm4gcG9ydGZvbGlvOiANCmRmX2VmZl9mcm9udGllciAlPiUgc2xpY2Uod2hpY2gubWF4KHJpc2spKSAtPiBtYXhfcmV0dXJuX3BvcnQgDQoNCiMgUG9ydGZvbGlvcyBiZWxvbmcgdG8gRWZmaWNpZW50IEZyb250aWVyOiANCmRmX2VmZl9mcm9udGllciAlPiUgZmlsdGVyKHJldHVybiA+IGdtdl9wb3J0JHJldHVybikgLT4gZGZfZWZmX2Zyb250aWVyX2xpbmUNCg0KIyBQb3J0Zm9saW8gYmVsb25nIHRvIEluZWZmaWNpZW50IEZyb250aWVyOiANCmRmX2VmZl9mcm9udGllciAlPiUgZmlsdGVyKHJldHVybiA8IGdtdl9wb3J0JHJldHVybikgLT4gZGZfaW5lZmZfZnJvbnRpZXINCg0KIyBHcmFwaCBvZiBFZmZpY2llbnQgRnJvbnRpZXI6IA0KDQpkZl9yZXR1cm5fbWluaV8xMDAwMCAlPiUgDQogIGdncGxvdChhZXMocmlzaywgcmV0dXJuKSkgKyANCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMDUsIGNvbG9yID0gImJsdWUiKSArIA0KICBnZW9tX3BvaW50KGRhdGEgPSBkZl9lZmZfZnJvbnRpZXJfbGluZSwgY29sb3IgPSAiYmx1ZSIpICsgDQogIGdlb21fcG9pbnQoZGF0YSA9IG1heF9yZXR1cm5fcG9ydCwgY29sb3IgPSAicHVycGxlIiwgc2hhcGUgPSAxOCwgc2l6ZSA9IDMpICsgDQogIGdlb21fdGV4dF9yZXBlbChkYXRhID0gbWF4X3JldHVybl9wb3J0LCBhZXMobGFiZWwgPSAiTWluMjUiKSwgZGlyZWN0aW9uID0gInkiKSArIA0KICBnZW9tX3BvaW50KGRhdGEgPSBkZl9pbmVmZl9mcm9udGllciwgY29sb3IgPSAicmVkIikgKyANCiAgZ2VvbV9wb2ludChkYXRhID0gZ212X3BvcnQsIGNvbG9yID0gImdyZWVuIiwgc2hhcGUgPSAxOCwgc2l6ZSA9IDMpICsgDQogIGdlb21fdGV4dF9yZXBlbChkYXRhID0gZ212X3BvcnQsIGFlcyhsYWJlbCA9ICJHTVYiKSwgZGlyZWN0aW9uID0gIngiKSArIA0KICBnZW9tX3BvaW50KGRhdGEgPSBkZl9yaXNrX3JldHVybiwgY29sb3IgPSAib3JhbmdlIiwgc2l6ZSA9IDMpICsgDQogIGdlb21fdGV4dF9yZXBlbChkYXRhID0gZGZfcmlza19yZXR1cm4sIGFlcyhsYWJlbCA9IHN5bWJvbCkpICsgDQogIGFubm90YXRlKCJ0ZXh0IiwgbGFiZWwgPSAiRWZmaWNpZW50IEZyb250aWVyIiwgeCA9IDAuMDIyLCB5ID0gMC4wMDIpICsgDQogIGFubm90YXRlKCJ0ZXh0IiwgbGFiZWwgPSAiSW5lZmZpY2llbnQgRnJvbnRpZXIiLCB4ID0gMC4wMjEsIHkgPSAwKSArIA0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gcGVyY2VudCkgKyANCiAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IHBlcmNlbnQpICsgDQogIGxhYnMoeCA9IGV4cHJlc3Npb24oIlJpc2siIH4gKHNpZ21hKSksIA0KICAgICAgIHkgPSBleHByZXNzaW9uKCJSZXR1cm4iIH4gKG11KSksIA0KICAgICAgIHRpdGxlID0gIkZpZ3VyZSAzOiBFZmZpY2llbnQgRnJvbnRpZXIiKSANCg0KDQpgYGANCg0K4bueIEZpZ3VyZSAzIHRow6wgRWZmaWNpZW50IEZyb250aWVyIGzDoCDEkcaw4budbmcgY29uZyB04bqhbyByYSBi4bqxbmcgY8OhY2ggbuG7kWkgY8OhYyDEkWnhu4NtIG3DoHUgeGFuaC4gSW5lZmZpY2llbnQgRnJvbnRpZXIgbMOgIMSRxrDhu51uZyBjb25nIHThuqFvIHJhIGLhurFuZyBjw6FjaCBu4buRaSBjw6FjIMSRaeG7g20gbcOgdSDEkeG7jy4gDQoNCiMgVGFuZ2VuY3kgUG9ydGZvbGlvDQoNCltTaGFycGUgUmF0aW9dKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1NoYXJwZV9yYXRpbykgbMOgIG3hu5l0IHRoxrDhu5tjIMSRbyDEkcOhbmggZ2nDoSBoaeG7h3UgcXXhuqMgY+G7p2EgZGFuaCBt4bulYyDEkeG6p3UgdMawIHbDoCDEkcaw4bujYyB0w61uaCB0aGVvIGPDtG5nIHRo4bupYzogDQoNCiQkUz1cZnJhY3tcbXVfcCAtIHJfe2Z9fXtcc2lnbWFfcH0kJA0KVHJvbmcgxJHDsyAkXHNpZ21hX3AkIHbDoCAkXG11X3AkIGzhuqduIGzGsOG7o3QgbMOgIHLhu6dpIHJvIHbDoCBs4bujaSB04bupYyBrw6wgduG7jW5nIGPhu6dhIGRhbmggbeG7pWMgY8OybiAkcl9mJCBsw6AgbOG7o2kgdOG7qWMgY+G7p2EgdMOgaSBz4bqjbiBwaGkgcuG7p2kgcm8uIOG7niDEkcOieSBs4bujaSB04bupYyBj4bunYSB0w6BpIHPhuqNuIHBoaSBy4bunaSBybyDEkcaw4bujYyBjaOG7jW4gbMOgbSB0aGFtIGNoaeG6v3UgdsOgIHRoxrDhu51uZyBsw6AgdHLDoWkgcGhp4bq/dSBjaMOtbmggcGjhu6cgKGhv4bq3YyBULWJpbGxzKS4gDQoNClRhbmdlbmN5IFBvcnRmb2xpbyBsw6AgZGFuaCBt4bulYyDEkeG6p3UgdMawIG3DoCBjw7MgU2hhcnBlIFJhdGlvIGzDoCBjYW8gbmjhuqV0LiBExrDhu5tpIMSRw6J5IGzDoCBSIGNvZGVzIHTDrG0gdOG7iSB0cuG7jW5nIGNobyBjw6FjIHTDoGkgc+G6o24gY2hvIFRhbmdlbmN5IFBvcnRmb2xpbyAoa8OtIGhp4buHdSBsw6AgVEFOKSBu4bq/dSBiaeG6v3QgdHLGsOG7m2MgbMOjaSBzdeG6pXQgKGRhaWx5IHJldHVybikgY+G7p2EgdMOgaSBz4bqjbiBwaGkgcuG7p2kgcm8gKG5oxrAgdHLDoWkgcGhp4bq/dSBjaMOtbmggcGjhu6cpIGzDoCAwLjAxJTogDQoNCmBgYHtyfQ0KIyBDYWxjdWxhdGUgd2VpZ2h0cyBmb3IgVGFuZ2VuY3kgUG9ydGZvbGlvOiANCg0KcmYgPC0gMC4wMDAxDQoNCnNpZ21hX2ludl9tYXQgPC0gc29sdmUoc2lnbWFfbWF0cml4KQ0KDQpvbmVfdmVjIDwtIHJlcCgxLCBuKQ0KDQptdV9taW51c19yZiA8LSBtdSAtIHJmKm9uZV92ZWMNCg0KdG9wX21hdCA8LSBzaWdtYV9pbnZfbWF0ICUqJSBtdV9taW51c19yZg0KDQpib3RfdmFsIDwtIGFzLm51bWVyaWModChvbmVfdmVjKSAlKiUgdG9wX21hdCkNCg0KWF90YW5nZW5jeSA8LSB0b3BfbWF0WywgMV0gLyBib3RfdmFsDQoNCiMgUHJpbnQgcmVzdWx0czogDQoNCnByaW50KFhfdGFuZ2VuY3kpDQoNCmBgYA0KDQpW4bubaSB04buJIHRy4buNbmcgY8OhYyB0w6BpIHPhuqNuIHTDrG0gxJHGsOG7o2MgdGjDrCBj4bq3cCByaXNrLXJldHVybiBj4bunYSBUYW5nZW5jeSBQb3J0Zm9saW8gxJHGsOG7o2MgdMOtbmgg4bufIFRhYmxlIDU6IA0KDQpgYGB7cn0NCnJpc2tfcmV0dXJuX3BvcnRmb2xpbyh3ZWlnaHRzID0gWF90YW5nZW5jeSkgLT4gZGZfdGFuZ2VuY3kNCg0KZGZfdGFuZ2VuY3kgJT4lIA0KICBrYmwoY2FwdGlvbiA9ICJUYWJsZSA0OiBSaXNrIGFuZCBSZXR1cm4gZm9yIFRhbmdlbmN5IFBvcnRmb2xpbyIsIGVzY2FwZSA9IFRSVUUpICU+JQ0KICBrYWJsZV9jbGFzc2ljKGZ1bGxfd2lkdGggPSBGQUxTRSwgaHRtbF9mb250ID0gIkNhbWJyaWEiKQ0KDQpgYGANCg0KVsOgIFNoYXJwZSBSYXRpbyBj4bunYSBkYW5oIG3hu6VjIG7DoHkgc+G6vSBsw6A6IA0KDQpgYGB7cn0NCmRmX3RhbmdlbmN5ICU+JSANCiAgc2xpY2UoMSkgJT4lIA0KICBtdXRhdGUocHJlbWl1bSA9IHJldHVybiAtIHJmKSAlPiUgDQogIG11dGF0ZShzaGFycGVSYXRpbyA9IHByZW1pdW0gLyByaXNrKSAlPiUgDQogIHB1bGwoc2hhcnBlUmF0aW8pDQpgYGANCg0KDQojIE11dHVhbCBGdW5kIFNlcGFyYXRpb24gVGhlb3JlbQ0KDQpYw6l0IG3hu5l0IGRhbmggbeG7pWMgxJHhuqd1IHTGsCDEkcaw4bujYyBow6xuaCB0aMOgbmggdOG7qyB2aeG7h2MgcGjDom4gYuG7lSB04buJIHRy4buNbmcgbMOgICR4X3QkIGNobyBUYW5nZW5jeSBQb3J0Zm9saW8g4bufIHRyw6puIHbDoCBwaOG6p24gY8OybiBs4bqhaSBsw6AgJDEgLSB4X3QkIGNobyB0w6BpIHPhuqNuIHBoaSBy4bunaSBybyBULWJpbGxzLiBS4bunaSBybyB2w6AgbOG7o2kgdOG7qWMgY+G7p2EgZGFuaCBt4bulYyDEkeG6p3UgdMawIG7DoHksIGzhuqduIGzGsOG7o3Qga8OtIGhp4buHdSBsw6AgJFxzaWdtYV57ZX1fcCQgdsOgICRcbXVee2V9X3AkIHPhur0gbMOgOiANCg0KJCRcYmVnaW57ZXF1YXRpb259IFx0YWd7Mn0gXHNpZ21hXntlfV9wID0geF90XHNpZ21hX3AgXGVuZHtlcXVhdGlvbn0kJA0KDQokJFxiZWdpbntlcXVhdGlvbn0gXHRhZ3szfSBcbXVee2V9X3AgPSByX2YgKyB4X3QoXG11X3AgLSByX2YpIFxlbmR7ZXF1YXRpb259JCQNCg0KR2nhuqMgc+G7rSBt4buZdCBuaMOgIMSR4bqndSB0xrAgdGh14buZYyBuaMOzbSBuZ+G6oWkgcuG7p2kgcm8gKGEgaGlnaGx5IHJpc2sgYXZlcnNlIGludmVzdG9yKSDEkeG6p3UgdMawIHbDoG8gVGFuZ2VuY3kgUG9ydGZvbGlvIHbDoCBULWJpbGxzIHbhu5tpIG3hu5l0IG3hu6VjIHRpw6p1IGtoacOqbSB04buRbiB24buBIGzhu6NpIHThu6ljIHbDoCAkXHNpZ21hXntlfV9wID0gMC4wMiQuIERhbmggbeG7pWMgxJHhuqd1IHRoxrAgdGjhu49hIG3Do24gecOqdSBj4bqndSBuw6B5IGPhu6dhIG5ow6AgxJHhuqd1IHTGsCBz4bq9IGPDsyB04buJIHRy4buNbmcgY2hvIFQtYmlsbHMgdGhlbyBjw7RuZyB0aOG7qWMgMiBsw6A6IA0KDQpgYGB7cn0NCnNpZ21hX3RhcmdldCA8LSAwLjAyDQoNClhfZm9yX1RhbmdlbmN5IDwtIHNpZ21hX3RhcmdldCAvICBkZl90YW5nZW5jeSRyaXNrWzFdDQoNCnByaW50KFhfZm9yX1RhbmdlbmN5KQ0KYGBgDQoNClbDoCB04buJIHRy4buNbmcgcGjDom4gYuG7lSBjaG8gVC1iaWxscyBsw6A6IA0KDQpgYGB7cn0NClhfZm9yX1RiaWxscyA8LSAxIC0gWF9mb3JfVGFuZ2VuY3kNCnByaW50KFhfZm9yX1RiaWxscykNCmBgYA0KDQpWw6AgbOG7o2kgdOG7qWMgY+G7p2EgZGFuaCBt4bulYyDEkeG6p3UgdMawIHRoZW8gY8O0bmcgdGjhu6ljIDMgc+G6vSBsw6A6IA0KDQpgYGB7cn0NCm11X1RhbmdlbmN5X1RiaWxsIDwtIHJmICsgWF9mb3JfVGFuZ2VuY3kqKGRmX3RhbmdlbmN5JHJldHVyblsxXSAtIHJmKQ0KDQpwcmludChtdV9UYW5nZW5jeV9UYmlsbCkNCmBgYA0KDQpD4bulIHRo4buDIGjGoW4sIHThu4kgdHLhu41uZyDEkeG6p3UgdMawIHbDoG8gYmEgdMOgaSBz4bqjbiBy4bunaSBybyBz4bq9IGzDoDogDQoNCmBgYHtyfQ0KWF90YW5nZW5jeSpYX2Zvcl9UYW5nZW5jeQ0KYGBgDQoNCkThu4Ugc3V5IHJhIHLhurFuZyB04buVbmcgdOG7iSB0cuG7jW5nIGPhu6dhIGPDoWMgdMOgaSBz4bqjbiBy4bunaSBybyBz4bq9IGLhurFuZyAwLjY2MTQ1MTM6IA0KDQpgYGB7cn0NCnN1bShYX3RhbmdlbmN5KlhfZm9yX1RhbmdlbmN5KQ0KYGBgDQoNCsSQ4bq3dCB0w6puIGNobyBkYW5oIG3hu6VjIMSR4bqndSB0xrAgbsOgeSBsw6AgVEFOMS4gQ8OhYyB0aMO0bmcgdGluIHbhu4EgVEFOMTogDQoNCmBgYHtyfQ0KZGZfdGFuZ2VuY3kxIDwtIHRpYmJsZShzeW1ib2wgPSBjKG15X3N5bWJvbHMsICJULWJpbGxzIiksDQogICAgICAgICAgICAgICAgICAgICAgIHdlaWdodCA9IGMoWF90YW5nZW5jeSpYX2Zvcl9UYW5nZW5jeSwgWF9mb3JfVGJpbGxzKSwgDQogICAgICAgICAgICAgICAgICAgICAgIHJldHVybiA9IG11X1RhbmdlbmN5X1RiaWxsLCANCiAgICAgICAgICAgICAgICAgICAgICAgcmlzayA9IHNpZ21hX3RhcmdldCkNCg0KZGZfdGFuZ2VuY3kxICU+JSANCiAga2JsKGNhcHRpb24gPSAiVGFibGUgNTogUmlzayBhbmQgUmV0dXJuIGZvciBUQU4xIFBvcnRmb2xpbyIsIGVzY2FwZSA9IFRSVUUpICU+JQ0KICBrYWJsZV9jbGFzc2ljKGZ1bGxfd2lkdGggPSBGQUxTRSwgaHRtbF9mb250ID0gIkNhbWJyaWEiKQ0KDQpgYGANCg0KVHLDqm4gaOG7hyB0cuG7pWMgdOG7jWEgxJHhu5kgcmlzay1yZXR1cm4gVEFOMSBz4bq9IGzDoCDEkWnhu4NtIG7hurFtIHRyw6puIMSRxrDhu51uZyB0aOG6s25nIG7hu5FpIGhhaSDEkWnhu4NtIFRBTiB2w6AgVC1iaWxscyB2w6AgxJHGsOG7nW5nIHRo4bqzbmcgbsOgeSDEkcaw4bujYyBn4buNaSBsw6AgVGFuZ2VuY3kgTGluZS4gRMaw4bubaSDEkcOieSBsw6AgUiBjb2RlcyBjaG8gVGFuZ2VuY3kgTGluZTogDQoNCmBgYHtyfQ0KDQojIFByZXBhcmUgZGF0YTogDQoNCmRmX2ZyZWVfcmlzayA8LSB0aWJibGUoc3ltYm9sID0gIlQtYmlsbHMiLCByaXNrID0gMCwgcmV0dXJuID0gcmYpDQoNCmRmX3RhbmdlbmN5ICU+JSANCiAgc2xpY2UoMSkgJT4lIA0KICBtdXRhdGUoc3ltYm9sID0gIlRBTiIpICU+JSANCiAgYmluZF9yb3dzKGRmX3RhbmdlbmN5MSAlPiUgc2xpY2UoMSkgJT4lIG11dGF0ZShzeW1ib2wgPSAiVEFOMSIpKSAlPiUgDQogIGJpbmRfcm93cyhkZl9mcmVlX3Jpc2sgJT4lIG11dGF0ZShzeW1ib2wgPSAiVC1iaWxscyIpKSAtPiBkZl90YW5nZW5jeV9saW5lDQoNCiMgUGxvdCBUYW5nZW5jeSBMaW5lOiANCg0KZGZfZWZmX2Zyb250aWVyX2xpbmUgJT4lIA0KICBnZ3Bsb3QoYWVzKHJpc2ssIHJldHVybikpICsgDQogIGdlb21fcG9pbnQoY29sb3IgPSAiYmx1ZSIpICsgDQogIGdlb21fcG9pbnQoZGF0YSA9IGRmX2luZWZmX2Zyb250aWVyLCBjb2xvciA9ICJyZWQiKSArICANCiAgZ2VvbV9wb2ludChkYXRhID0gZ212X3BvcnQsIGNvbG9yID0gImdyZWVuIiwgc2hhcGUgPSAxOCwgc2l6ZSA9IDMpICsgDQogIGdlb21fdGV4dF9yZXBlbChkYXRhID0gZ212X3BvcnQsIGFlcyhsYWJlbCA9ICJHTVYiKSwgZGlyZWN0aW9uID0gIngiKSArIA0KICBnZW9tX3BvaW50KGRhdGEgPSBkZl9yaXNrX3JldHVybiwgY29sb3IgPSAib3JhbmdlIiwgc2l6ZSA9IDMpICsgDQogIGdlb21fdGV4dF9yZXBlbChkYXRhID0gZGZfcmlza19yZXR1cm4sIGFlcyhsYWJlbCA9IHN5bWJvbCkpICsgDQogIGdlb21fcG9pbnQoZGF0YSA9IGRmX3RhbmdlbmN5X2xpbmUsIGNvbG9yID0gInB1cnBsZSIsIHNpemUgPSAzLCBzaGFwZSA9IDE4KSArIA0KICBnZW9tX2xpbmUoZGF0YSA9IGRmX3RhbmdlbmN5X2xpbmUsIGNvbG9yID0gInB1cnBsZSIsIHNpemUgPSAxKSArIA0KICBnZW9tX3RleHRfcmVwZWwoZGF0YSA9IGRmX3RhbmdlbmN5X2xpbmUsIGFlcyhsYWJlbCA9IHN5bWJvbCksIGRpcmVjdGlvbiA9ICJ5IikgKw0KICBhbm5vdGF0ZSgidGV4dCIsIGxhYmVsID0gIlRhbmdlbmN5IExpbmUiLCB4ID0gMC4wMSwgeSA9IDAuMDAxMSkgKyANCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHBlcmNlbnQpICsgDQogIHNjYWxlX3hfY29udGludW91cyhsYWJlbHMgPSBwZXJjZW50KSArIA0KICBsYWJzKHggPSBleHByZXNzaW9uKCJSaXNrIiB+IChzaWdtYSkpLCANCiAgICAgICB5ID0gZXhwcmVzc2lvbigiUmV0dXJuIiB+IChtdSkpLCANCiAgICAgICB0aXRsZSA9ICJGaWd1cmUgNDogVGFuZ2VuY3kgTGluZSIpICANCg0KYGBgDQoNCk3hu5l0IG5ow6AgxJHhuqd1IHTGsCBjw7MgbmfGsOG7oW5nIGNo4buLdSBy4bunaSBybyBjYW8gKGEgcmlzayB0b2xlcmFudCBpbnZlc3RvcikgcuG6pXQgY8OzIHRo4buDIMawYSB0aMOtY2ggbeG7mXQgZGFuaCBt4bulYyDEkeG6p3UgdMawIGPDsyBs4bujaSB04bupYyBjYW8gaMahbiB24bubaSBjw6FpIGdpw6EgcGjhuqNpIHRy4bqjIGzDoCBy4bunaSBybyBjYW8gaMahbi4gTmjhu69uZyBkYW5oIG3hu6VjIMSR4bqndSB0xrAga2nhu4N1IG7DoHkgc+G6vTogKDEpIHbhuqtuIG7hurFtIHRyw6puIFRhbmdlbmN5IExpbmUsIHbDoCAoMikgbuG6sW0gduG7gSBwaMOtYSB0csOhaSBj4bunYSBUQU4xLiANCg0KIyBSZWZlcmVuY2VzDQoNCjEuIFtJbnZlc3RtZW50cyAxMHRoIEVkaXRpb24gYnkgWnZpIEJvZGllLCBBbGV4IEthbmUgKEF1dGhvciksIEFsYW4gSi4gTWFyY3VzXShodHRwczovL3d3dy5hbWF6b24uY29tL0ludmVzdG1lbnRzLTEwdGgtWnZpLUJvZGllL2RwLzAwNzc4NjE2NzEpLiANCjIuIFtNb2Rlcm4gUG9ydGZvbGlvIFRoZW9yeSBhbmQgSW52ZXN0bWVudCBBbmFseXNpcywgOXRoIEVkaXRpb24gYnkgRWx0b24gZXQgYWwuXShodHRwczovL3d3dy53aWxleS5jb20vZW4tdXMvTW9kZXJuK1BvcnRmb2xpbytUaGVvcnkrYW5kK0ludmVzdG1lbnQrQW5hbHlzaXMlMkMrOXRoK0VkaXRpb24tcC05NzgxMTE4NDY5OTQxKS4gDQozLiBbQmFzaWNzIG9mIE1hdHJpeCBBbGdlYnJhIGZvciBTdGF0aXN0aWNzIHdpdGggUiBieSBOaWNrIEZpZWxsZXJdKGh0dHBzOi8vd3d3LmFtYXpvbi5jb20vQmFzaWNzLU1hdHJpeC1BbGdlYnJhLVN0YXRpc3RpY3MtQ2hhcG1hbi1lYm9vay9kcC9CMDExNlI0NlRRL3JlZj1zcl8xXzE/ZGNoaWxkPTEma2V5d29yZHM9bWF0cml4K2NhbGN1bGF0aW9uK2luK3ImcWlkPTE2MTE0OTE5Njkmcz1ib29rcyZzcj0xLTEpLiANCjQuIFtQb3J0Zm9saW8gVGhlb3J5IHdpdGggTWF0cml4IEFsZ2VicmEgYnkgRXJpYyBaaXZvdF0oaHR0cHM6Ly9mYWN1bHR5Lndhc2hpbmd0b24uZWR1L2V6aXZvdC9lY29uNDI0L3BvcnRmb2xpb1RoZW9yeU1hdHJpeC5wZGYpLiANCg0KDQoNCg0K