Hasil
Eksplorasi Data
Awal
statdesk <- data.frame(
Mean = sapply(kopi_num, mean),
SD = sapply(kopi_num, sd),
Min = sapply(kopi_num, min),
Median = sapply(kopi_num, median),
Max = sapply(kopi_num, max)
)
kable(round(statdesk, 3), caption = "Statistik deskriptif skor sensori kopi arabika")
Statistik deskriptif skor sensori kopi arabika
| Aroma |
7.570 |
0.316 |
5.08 |
7.58 |
8.75 |
| Flavor |
7.524 |
0.342 |
6.08 |
7.58 |
8.83 |
| Aftertaste |
7.403 |
0.350 |
6.17 |
7.42 |
8.67 |
| Acidity |
7.539 |
0.320 |
5.25 |
7.50 |
8.75 |
| Body |
7.523 |
0.293 |
5.25 |
7.50 |
8.58 |
| Balance |
7.523 |
0.349 |
6.08 |
7.50 |
8.75 |
| Uniformity |
9.841 |
0.489 |
6.00 |
10.00 |
10.00 |
| Clean.Cup |
9.841 |
0.722 |
0.00 |
10.00 |
10.00 |
| Sweetness |
9.911 |
0.455 |
1.33 |
10.00 |
10.00 |
| Cupper.Points |
7.504 |
0.427 |
5.17 |
7.50 |
10.00 |
boxplot(kopi_num, las = 2, col = "lightblue",
main = "Boxplot Skor Sensori Kopi Arabika (Data Asli)")

kopi_num %>%
pivot_longer(everything(), names_to = "Variabel", values_to = "Skor") %>%
ggplot(aes(x = Skor)) +
geom_histogram(bins = 30, fill = "steelblue", color = "white") +
facet_wrap(~Variabel, scales = "free") +
labs(title = "Distribusi Skor Sensori Kopi Arabika",
x = "Skor", y = "Frekuensi") +
theme_minimal()

kor <- cor(kopi_num)
corrplot(kor, method = "color", type = "upper",
addCoef.col = "black", number.cex = 0.6,
tl.col = "black", tl.srt = 45)

Interpretasi eksplorasi. (Isi setelah melihat
hasil: bentuk sebaran tiap variabel, variabel yang hampir selalu
bernilai 10, keberadaan pencilan, dan kelompok variabel yang berkorelasi
tinggi sebagai dasar dilakukannya PCA.)
Preprocessing:
Standarisasi
Skor memiliki ragam yang berbeda antar variabel, sehingga dilakukan
standarisasi agar setiap variabel berkontribusi setara dan PCA berbasis
matriks korelasi.
kopi_scale <- scale(kopi_num)
boxplot(kopi_scale, las = 2, col = "lightgreen",
main = "Boxplot Skor Sensori Setelah Standarisasi")

Principal Component
Analysis
hasil_pca <- prcomp(kopi_num, scale. = TRUE)
eig <- hasil_pca$sdev^2
var_exp <- round(eig / sum(eig) * 100, 1)
tabel_eig <- data.frame(
PC = paste0("PC", seq_along(eig)),
Eigenvalue = round(eig, 4),
Varians_Persen = round(eig / sum(eig) * 100, 2),
Kumulatif_Persen = round(cumsum(eig / sum(eig) * 100), 2)
)
kable(tabel_eig, caption = "Eigenvalue dan proporsi varians tiap komponen utama")
Eigenvalue dan proporsi varians tiap komponen utama
| PC1 |
5.2303 |
52.30 |
52.30 |
| PC2 |
1.7027 |
17.03 |
69.33 |
| PC3 |
0.6153 |
6.15 |
75.48 |
| PC4 |
0.5638 |
5.64 |
81.12 |
| PC5 |
0.4698 |
4.70 |
85.82 |
| PC6 |
0.3922 |
3.92 |
89.74 |
| PC7 |
0.3707 |
3.71 |
93.45 |
| PC8 |
0.2916 |
2.92 |
96.36 |
| PC9 |
0.2313 |
2.31 |
98.68 |
| PC10 |
0.1324 |
1.32 |
100.00 |
df_scree <- data.frame(PC = seq_along(eig), Eigenvalue = eig,
Persen = eig / sum(eig) * 100)
ggplot(df_scree, aes(x = PC, y = Persen)) +
geom_col(fill = "steelblue", alpha = 0.8) +
geom_line() + geom_point() +
geom_text(aes(label = paste0(round(Persen, 1), "%")), vjust = -0.6, size = 3) +
scale_x_continuous(breaks = df_scree$PC) +
labs(title = "Scree Plot (Persen Varians)",
x = "Komponen Utama", y = "Persen Varians") +
theme_minimal()

ggplot(df_scree, aes(x = PC, y = Eigenvalue)) +
geom_line() + geom_point(size = 2) +
geom_hline(yintercept = 1, linetype = "dashed", color = "red") +
scale_x_continuous(breaks = df_scree$PC) +
labs(title = "Scree Plot (Eigenvalue) dengan Garis Kaiser",
x = "Komponen Utama", y = "Eigenvalue") +
theme_minimal()

# Kaiser (eigenvalue > 1) -> 2 PC; varians kumulatif 70-80% -> 3 PC.
# Dipakai 3 PC (ubah ke 2 bila ingin lebih ringkas; plot 3D butuh n_pc = 3).
n_pc <- 3
skor_pca <- as.data.frame(hasil_pca$x[, 1:n_pc])
Berdasarkan kriteria Kaiser, komponen dengan eigenvalue lebih dari 1
adalah 2 komponen, sedangkan 3 komponen utama pertama menjelaskan 75.48%
keragaman data. Karena itu dipertahankan 3 komponen
utama untuk analisis selanjutnya. PC1 menjelaskan 52.3% dan PC2
menjelaskan 17.03% keragaman.
loading <- round(hasil_pca$rotation[, 1:n_pc], 3)
kable(loading, caption = "Loading (koefisien eigenvector) komponen utama")
Loading (koefisien eigenvector) komponen utama
| Aroma |
-0.348 |
-0.098 |
-0.001 |
| Flavor |
-0.404 |
-0.045 |
-0.008 |
| Aftertaste |
-0.399 |
-0.052 |
0.009 |
| Acidity |
-0.357 |
-0.116 |
-0.020 |
| Body |
-0.338 |
-0.159 |
-0.143 |
| Balance |
-0.374 |
-0.042 |
0.019 |
| Uniformity |
-0.126 |
0.547 |
0.692 |
| Clean.Cup |
-0.148 |
0.555 |
0.015 |
| Sweetness |
-0.081 |
0.580 |
-0.705 |
| Cupper.Points |
-0.362 |
-0.039 |
0.045 |
rot <- hasil_pca$rotation
kontribusi <- data.frame(Variabel = rownames(rot),
PC1 = rot[, 1]^2 * 100,
PC2 = rot[, 2]^2 * 100)
buat_kontribusi <- function(kolom, judul) {
ggplot(kontribusi, aes(x = reorder(Variabel, .data[[kolom]]),
y = .data[[kolom]])) +
geom_col(fill = "steelblue") +
coord_flip() +
geom_hline(yintercept = 100 / nrow(kontribusi),
linetype = "dashed", color = "red") +
labs(title = judul, x = NULL, y = "Kontribusi (%)") +
theme_minimal()
}
buat_kontribusi("PC1", "Kontribusi Variabel terhadap PC1")

buat_kontribusi("PC2", "Kontribusi Variabel terhadap PC2")

koord <- data.frame(Variabel = rownames(rot),
PC1 = rot[, 1] * hasil_pca$sdev[1],
PC2 = rot[, 2] * hasil_pca$sdev[2])
lingkaran <- data.frame(x = cos(seq(0, 2 * pi, length.out = 200)),
y = sin(seq(0, 2 * pi, length.out = 200)))
ggplot() +
geom_path(data = lingkaran, aes(x = x, y = y), color = "grey60") +
geom_hline(yintercept = 0, linetype = "dashed", color = "grey70") +
geom_vline(xintercept = 0, linetype = "dashed", color = "grey70") +
geom_segment(data = koord, aes(x = 0, y = 0, xend = PC1, yend = PC2),
arrow = arrow(length = unit(0.2, "cm")), color = "red") +
geom_text(data = koord, aes(x = PC1 * 1.1, y = PC2 * 1.1, label = Variabel),
size = 3) +
coord_fixed(xlim = c(-1.2, 1.2), ylim = c(-1.2, 1.2)) +
labs(title = "Variabel pada Ruang PC1-PC2",
x = paste0("PC1 (", var_exp[1], "%)"),
y = paste0("PC2 (", var_exp[2], "%)")) +
theme_minimal()

faktor <- 0.8 * max(abs(skor_pca[, 1:2])) / max(abs(koord[, c("PC1", "PC2")]))
ggplot() +
geom_point(data = skor_pca, aes(x = PC1, y = PC2),
color = "grey50", alpha = 0.3, size = 1.2) +
geom_segment(data = koord,
aes(x = 0, y = 0, xend = PC1 * faktor, yend = PC2 * faktor),
arrow = arrow(length = unit(0.2, "cm")), color = "red") +
geom_text(data = koord,
aes(x = PC1 * faktor * 1.1, y = PC2 * faktor * 1.1, label = Variabel),
color = "red", size = 3) +
labs(title = "Biplot PCA",
x = paste0("PC1 (", var_exp[1], "%)"),
y = paste0("PC2 (", var_exp[2], "%)")) +
theme_minimal()

Interpretasi PCA. (Isi setelah melihat tabel
loading dan grafik: variabel apa yang memiliki loading terbesar pada
PC1, PC2, dan PC3, lalu beri nama tiap komponen sesuai maknanya.
Catatan: tanda positif atau negatif pada loading bersifat bebas dan
tidak mengubah interpretasi.)
Penentuan Jumlah
Cluster Optimal
Clustering dilakukan pada skor 3 komponen utama pertama.
jarak <- dist(skor_pca)
tabel_k <- data.frame(k = 1:10, WSS = NA_real_, Silhouette = NA_real_)
for (i in 1:10) {
set.seed(123)
km <- kmeans(skor_pca, centers = i, nstart = 25)
tabel_k$WSS[i] <- round(km$tot.withinss, 2)
if (i > 1) {
tabel_k$Silhouette[i] <- round(
mean(silhouette(km$cluster, jarak)[, "sil_width"]), 4)
}
}
kable(tabel_k, caption = "Nilai WSS dan rata-rata Silhouette untuk tiap jumlah cluster (k)")
Nilai WSS dan rata-rata Silhouette untuk tiap jumlah cluster
(k)
| 1 |
9880.79 |
NA |
| 2 |
6014.42 |
0.4422 |
| 3 |
4523.83 |
0.4415 |
| 4 |
3453.38 |
0.4317 |
| 5 |
2923.99 |
0.4106 |
| 6 |
2472.61 |
0.4247 |
| 7 |
2174.52 |
0.4270 |
| 8 |
1927.22 |
0.4081 |
| 9 |
1765.77 |
0.4048 |
| 10 |
1563.29 |
0.4058 |
ggplot(tabel_k, aes(x = k, y = WSS)) +
geom_line() + geom_point(size = 2) +
scale_x_continuous(breaks = 1:10) +
labs(title = "Elbow Method", x = "Jumlah Cluster (k)",
y = "Total Within Sum of Squares") +
theme_minimal()

sil_data <- tabel_k[tabel_k$k >= 2, ]
ggplot(sil_data, aes(x = k, y = Silhouette)) +
geom_line() + geom_point(size = 2) +
geom_vline(xintercept = sil_data$k[which.max(sil_data$Silhouette)],
linetype = "dashed", color = "blue") +
scale_x_continuous(breaks = 2:10) +
labs(title = "Silhouette Score", x = "Jumlah Cluster (k)",
y = "Rata-rata Silhouette") +
theme_minimal()

# Tetapkan k berdasarkan Elbow dan Silhouette (lihat tabel di atas).
k_opt <- 3
Penentuan k. (Isi setelah melihat hasil: k
dengan Silhouette tertinggi, ada atau tidaknya siku pada Elbow, dan
alasan memilih k = 3.)
Hasil K-Means
set.seed(123)
km_final <- kmeans(skor_pca, centers = k_opt, nstart = 50)
kopi_ara$Cluster <- factor(km_final$cluster)
kable(as.data.frame(table(Cluster = kopi_ara$Cluster)),
col.names = c("Cluster", "Jumlah Observasi"),
caption = "Ukuran tiap cluster (K-Means)")
Ukuran tiap cluster (K-Means)
| 1 |
651 |
| 2 |
600 |
| 3 |
59 |
Proporsi keragaman antar-cluster (between SS / total SS) sebesar
54.22%.
sil_km <- silhouette(km_final$cluster, jarak)
plot(sil_km, col = 2:(k_opt + 1), border = NA,
main = "Silhouette Plot K-Means")

skor_plot <- cbind(skor_pca, Cluster = kopi_ara$Cluster)
ggplot(skor_plot, aes(x = PC1, y = PC2, color = Cluster)) +
geom_point(alpha = 0.6, size = 1.8) +
stat_ellipse(level = 0.90) +
labs(title = "Hasil K-Means pada Ruang PCA (2D)",
x = paste0("PC1 (", var_exp[1], "%)"),
y = paste0("PC2 (", var_exp[2], "%)")) +
theme_minimal()

# Tampilan statis PC1 vs PC3 (selalu bisa tampil di HTML maupun PDF)
ggplot(skor_plot, aes(x = PC1, y = PC3, color = Cluster)) +
geom_point(alpha = 0.6, size = 1.8) +
labs(title = "Hasil K-Means pada Ruang PCA (PC1 vs PC3)",
x = paste0("PC1 (", var_exp[1], "%)"),
y = paste0("PC3 (", var_exp[3], "%)")) +
theme_minimal()

# Plot 3D interaktif hanya bisa tampil pada keluaran HTML.
# Untuk PDF: jalankan chunk ini di RStudio, ambil screenshot jendela Viewer,
# simpan sebagai output/cluster_3d.png, lalu knit ke PDF.
p_3d <- plot_ly(skor_plot, x = ~PC1, y = ~PC2, z = ~PC3,
color = ~Cluster, type = "scatter3d", mode = "markers",
marker = list(size = 3, opacity = 0.7)) %>%
layout(title = "Hasil K-Means pada Ruang PCA (3D)",
scene = list(
xaxis = list(title = paste0("PC1 (", var_exp[1], "%)")),
yaxis = list(title = paste0("PC2 (", var_exp[2], "%)")),
zaxis = list(title = paste0("PC3 (", var_exp[3], "%)"))))
if (knitr::is_html_output()) {
print(p_3d)
} else if (file.exists("output/cluster_3d.png")) {
knitr::include_graphics("output/cluster_3d.png")
} else {
cat("[Tambahkan screenshot plot 3D sebagai output/cluster_3d.png]")
}
Profil Cluster
profil <- kopi_ara %>%
group_by(Cluster) %>%
summarise(n = n(), across(all_of(vars), ~ round(mean(.x), 2)))
kable(profil, caption = "Rata-rata skor sensori (data asli) tiap cluster")
Rata-rata skor sensori (data asli) tiap cluster
| 1 |
651 |
7.76 |
7.76 |
7.64 |
7.74 |
7.71 |
7.75 |
9.90 |
9.94 |
9.94 |
7.77 |
| 2 |
600 |
7.40 |
7.33 |
7.21 |
7.36 |
7.35 |
7.34 |
9.91 |
9.96 |
9.97 |
7.28 |
| 3 |
59 |
7.17 |
6.94 |
6.82 |
7.14 |
7.25 |
6.96 |
8.52 |
7.55 |
8.98 |
6.83 |
kopi_ara %>%
select(Cluster, all_of(vars)) %>%
pivot_longer(-Cluster, names_to = "Variabel", values_to = "Skor") %>%
ggplot(aes(x = Cluster, y = Skor, fill = Cluster)) +
geom_boxplot(outlier.size = 0.8) +
facet_wrap(~Variabel, scales = "free_y") +
labs(title = "Profil Skor Sensori per Cluster") +
theme_minimal() +
theme(legend.position = "none")

Pembanding:
Hierarchical Clustering (Ward.D2)
hc <- hclust(jarak, method = "ward.D2")
plot(hc, labels = FALSE, hang = -1,
main = "Dendrogram Hierarchical (Ward.D2)", xlab = "", sub = "")
rect.hclust(hc, k = k_opt, border = 2:(k_opt + 1))

kopi_ara$Cluster_HC <- factor(cutree(hc, k = k_opt))
kable(as.data.frame.matrix(table(KMeans = kopi_ara$Cluster,
Hierarki = kopi_ara$Cluster_HC)),
caption = "Kesesuaian keanggotaan cluster: K-Means (baris) vs Hierarchical (kolom)")
Kesesuaian keanggotaan cluster: K-Means (baris) vs Hierarchical
(kolom)
| 249 |
389 |
13 |
| 0 |
428 |
172 |
| 0 |
0 |
59 |
sil_hc <- silhouette(cutree(hc, k = k_opt), jarak)
perbandingan <- data.frame(
Metode = c("K-Means", "Hierarchical (Ward.D2)"),
Silhouette = round(c(mean(sil_km[, "sil_width"]),
mean(sil_hc[, "sil_width"])), 4)
)
kable(perbandingan, caption = "Perbandingan rata-rata Silhouette kedua metode")
Perbandingan rata-rata Silhouette kedua metode
| K-Means |
0.4411 |
| Hierarchical (Ward.D2) |
0.4165 |
ggplot(cbind(skor_pca, Cluster = kopi_ara$Cluster_HC),
aes(x = PC1, y = PC2, color = Cluster)) +
geom_point(alpha = 0.6, size = 1.8) +
labs(title = "Hierarchical (Ward.D2) pada Ruang PCA",
x = paste0("PC1 (", var_exp[1], "%)"),
y = paste0("PC2 (", var_exp[2], "%)")) +
theme_minimal()

LS0tDQp0aXRsZTogIkFuYWxpc2lzIFByaW5jaXBhbCBDb21wb25lbnQgQW5hbHlzaXMgZGFuIENsdXN0ZXJpbmcgcGFkYSBEYXRhIEt1YWxpdGFzIEtvcGkgQXJhYmlrYSINCmF1dGhvcjogIlJpc2thIFNhcHV0cmkgLSBIMDUxMjQxMDgwIg0KZGF0ZTogImByIGZvcm1hdChTeXMuRGF0ZSgpLCAnJWQgJUIgJVknKWAiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICBwZGZfZG9jdW1lbnQ6DQogICAgdG9jOiB0cnVlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQojIGVjaG8gPSBUUlVFICAgOiBrb2RlIGlrdXQgdGFtcGlsIGRpIGxhcG9yYW4uIFBhZGEgSFRNTCwga29kZSBkaWxpcGF0DQojICAgICAgICAgICAgICAgICAoY29kZV9mb2xkaW5nOiBoaWRlKSBkYW4gcGVtYmFjYSBiaXNhIG1lbWJ1a2FueWEgbGV3YXQNCiMgICAgICAgICAgICAgICAgIHRvbWJvbCAiQ29kZSIuIFViYWgga2UgRkFMU0UgdW50dWsgbWVueWVtYnVueWlrYW4ga29kZS4NCiMgZmlnLnBhdGggICAgICA6IHNlbXVhIGdhbWJhciBvdG9tYXRpcyBqdWdhIHRlcnNpbXBhbiBzZWJhZ2FpIGZpbGUgUE5HDQprbml0cjo6b3B0c19jaHVuayRzZXQoDQogIGVjaG8gPSBUUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwNCiAgZmlnLmFsaWduID0gImNlbnRlciIsIGZpZy53aWR0aCA9IDcsIGZpZy5oZWlnaHQgPSA1LCBkcGkgPSAxNTAsDQogIGZpZy5wYXRoID0gIm91dHB1dC9nYW1iYXIvIg0KKQ0KDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoY29ycnBsb3QpDQpsaWJyYXJ5KGNsdXN0ZXIpDQpsaWJyYXJ5KHBsb3RseSkNCmxpYnJhcnkoa25pdHIpDQoNCmRpci5jcmVhdGUoIm91dHB1dCIsIHNob3dXYXJuaW5ncyA9IEZBTFNFKQ0KYGBgDQoNCmBgYHtyIGltcG9yLCBpbmNsdWRlPUZBTFNFfQ0KIyBGaWxlIENTViBoYXJ1cyBiZXJhZGEgZGkgZm9sZGVyIHlhbmcgU0FNQSBkZW5nYW4gZmlsZSAuUm1kIGluaS4NCmtvcGkgPC0gcmVhZC5jc3YoImFyYWJpY2FfZGF0YV9jbGVhbmVkLmNzdiIpDQoNCnZhcnMgPC0gYygiQXJvbWEiLCAiRmxhdm9yIiwgIkFmdGVydGFzdGUiLCAiQWNpZGl0eSIsICJCb2R5IiwNCiAgICAgICAgICAiQmFsYW5jZSIsICJVbmlmb3JtaXR5IiwgIkNsZWFuLkN1cCIsICJTd2VldG5lc3MiLA0KICAgICAgICAgICJDdXBwZXIuUG9pbnRzIikNCg0Ka29waV9hcmEgPC0ga29waSAlPiUgZmlsdGVyKEFyb21hID4gMCkgICAjIGJ1YW5nIDEgYmFyaXMgZGF0YSBjYWNhdCAoc2VtdWEgc2tvciAwKQ0Ka29waV9udW0gPC0ga29waV9hcmFbLCB2YXJzXQ0KYGBgDQoNCiMgUGVuZGFodWx1YW4gZGFuIERlc2tyaXBzaSBEYXRhc2V0DQoNCkxhcG9yYW4gaW5pIG1lbmVyYXBrYW4gUHJpbmNpcGFsIENvbXBvbmVudCBBbmFseXNpcyAoUENBKSBkYW4gY2x1c3RlcmluZyBLLU1lYW5zIHBhZGEgZGF0YSBwZW5pbGFpYW4gc2Vuc29yaSBrb3BpIGFyYWJpa2EuDQoNCioqU3VtYmVyIGRhdGEuKiogQ29mZmVlIFF1YWxpdHkgRGF0YWJhc2UgZGFyaSBDb2ZmZWUgUXVhbGl0eSBJbnN0aXR1dGUgKENRSSksIGRpa3VtcHVsa2FuIG9sZWggSmFtZXMgTGVEb3V4IGRhbiB0ZXJzZWRpYSBkaSBHaXRIdWIgKGh0dHBzOi8vZ2l0aHViLmNvbS9qbGRiYy9jb2ZmZWUtcXVhbGl0eS1kYXRhYmFzZSkgc2VydGEgS2FnZ2xlIChodHRwczovL3d3dy5rYWdnbGUuY29tL2RhdGFzZXRzL3ZvbHBhdHRvL2NvZmZlZS1xdWFsaXR5LWRhdGFiYXNlLWZyb20tY3FpKS4gQmVya2FzIHlhbmcgZGlwYWthaTogYGFyYWJpY2FfZGF0YV9jbGVhbmVkLmNzdmAuDQoNCioqVWt1cmFuIGRhdGEuKiogQmVya2FzIGFzbGkgYmVyaXNpIGByIG5yb3coa29waSlgIG9ic2VydmFzaSBkYW4gYHIgbmNvbChrb3BpKWAga29sb20uIFNldGVsYWggbWVtYnVhbmcgYHIgbnJvdyhrb3BpKSAtIG5yb3coa29waV9hcmEpYCBiYXJpcyB5YW5nIHNlbHVydWggc2tvcm55YSBiZXJuaWxhaSAwIChkYXRhIGNhY2F0KSwgdGVyc2lzYSBgciBucm93KGtvcGlfYXJhKWAgb2JzZXJ2YXNpIGRlbmdhbiBgciBsZW5ndGgodmFycylgIHZhcmlhYmVsIG51bWVyaWsuDQoNCioqVmFyaWFiZWwgeWFuZyBkaWFuYWxpc2lzKiogYWRhbGFoIDEwIHNrb3Igc2Vuc29yaSBoYXNpbCBjdXBwaW5nIChza2FsYSAwLTEwKToNCg0KfCBWYXJpYWJlbCB8IEtldGVyYW5nYW4gfA0KfC0tLXwtLS18DQp8IEFyb21hLCBGbGF2b3IsIEFmdGVydGFzdGUgfCBQZW5pbGFpYW4gYXJvbWEsIHJhc2EsIGRhbiBrZXNhbiByYXNhIHNldGVsYWggZGl0ZWxhbiB8DQp8IEFjaWRpdHksIEJvZHksIEJhbGFuY2UgfCBLZWFzYW1hbiwga2VrZW50YWxhbiwgZGFuIGtlc2VpbWJhbmdhbiB8DQp8IFVuaWZvcm1pdHksIENsZWFuLkN1cCwgU3dlZXRuZXNzIHwgS29uc2lzdGVuc2kgYW50YXIgY2FuZ2tpciwga2ViZXJzaWhhbiBjYW5na2lyLCBkYW4ga2VtYW5pc2FuIHwNCnwgQ3VwcGVyLlBvaW50cyB8IFBlbmlsYWlhbiBrZXNlbHVydWhhbiBvbGVoIHBlbmd1amkgfA0KDQpWYXJpYWJlbCBgVG90YWwuQ3VwLlBvaW50c2AgdGlkYWsgZGlwYWthaSBrYXJlbmEgaGFtcGlyIHNhbWEgZGVuZ2FuIGp1bWxhaCBrZXNlcHVsdWggc2tvciBkaSBhdGFzIHNlaGluZ2dhIGFrYW4gbWVuZHVwbGlrYXNpIGluZm9ybWFzaS4NCg0KIyBUYWhhcGFuIEFuYWxpc2lzDQoNCjEuIEVrc3Bsb3Jhc2kgZGF0YSAoc3RhdGlzdGlrIGRlc2tyaXB0aWYsIGJveHBsb3QsIGhpc3RvZ3JhbSwga29yZWxhc2kpLg0KMi4gUHJlcHJvY2Vzc2luZzogc3RhbmRhcmlzYXNpIChtZWFuIDAsIHNpbXBhbmdhbiBiYWt1IDEpLg0KMy4gUENBIGRhbiBwZW5lbnR1YW4ganVtbGFoIGtvbXBvbmVuIHV0YW1hLg0KNC4gUGVuZW50dWFuIGp1bWxhaCBjbHVzdGVyIG9wdGltYWwgKEVsYm93IE1ldGhvZCBkYW4gU2lsaG91ZXR0ZSBTY29yZSkgcGFkYSBza29yIFBDQS4NCjUuIEstTWVhbnMgc2ViYWdhaSBtZXRvZGUgdXRhbWEgZGFuIEhpZXJhcmNoaWNhbCBDbHVzdGVyaW5nIChXYXJkLkQyKSBzZWJhZ2FpIHBlbWJhbmRpbmcuDQo2LiBWaXN1YWxpc2FzaSBjbHVzdGVyIHBhZGEgcnVhbmcgUENBIGRhbiBpbnRlcnByZXRhc2kgcHJvZmlsIGNsdXN0ZXIuDQoNCiMgSGFzaWwNCg0KIyMgRWtzcGxvcmFzaSBEYXRhIEF3YWwNCg0KYGBge3Igc3RhdGRlc2t9DQpzdGF0ZGVzayA8LSBkYXRhLmZyYW1lKA0KICBNZWFuICAgPSBzYXBwbHkoa29waV9udW0sIG1lYW4pLA0KICBTRCAgICAgPSBzYXBwbHkoa29waV9udW0sIHNkKSwNCiAgTWluICAgID0gc2FwcGx5KGtvcGlfbnVtLCBtaW4pLA0KICBNZWRpYW4gPSBzYXBwbHkoa29waV9udW0sIG1lZGlhbiksDQogIE1heCAgICA9IHNhcHBseShrb3BpX251bSwgbWF4KQ0KKQ0Ka2FibGUocm91bmQoc3RhdGRlc2ssIDMpLCBjYXB0aW9uID0gIlN0YXRpc3RpayBkZXNrcmlwdGlmIHNrb3Igc2Vuc29yaSBrb3BpIGFyYWJpa2EiKQ0KYGBgDQoNCmBgYHtyIGJveHBsb3QtYXNsaX0NCmJveHBsb3Qoa29waV9udW0sIGxhcyA9IDIsIGNvbCA9ICJsaWdodGJsdWUiLA0KICAgICAgICBtYWluID0gIkJveHBsb3QgU2tvciBTZW5zb3JpIEtvcGkgQXJhYmlrYSAoRGF0YSBBc2xpKSIpDQpgYGANCg0KYGBge3IgaGlzdG9ncmFtLCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD02fQ0Ka29waV9udW0gJT4lDQogIHBpdm90X2xvbmdlcihldmVyeXRoaW5nKCksIG5hbWVzX3RvID0gIlZhcmlhYmVsIiwgdmFsdWVzX3RvID0gIlNrb3IiKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gU2tvcikpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDMwLCBmaWxsID0gInN0ZWVsYmx1ZSIsIGNvbG9yID0gIndoaXRlIikgKw0KICBmYWNldF93cmFwKH5WYXJpYWJlbCwgc2NhbGVzID0gImZyZWUiKSArDQogIGxhYnModGl0bGUgPSAiRGlzdHJpYnVzaSBTa29yIFNlbnNvcmkgS29waSBBcmFiaWthIiwNCiAgICAgICB4ID0gIlNrb3IiLCB5ID0gIkZyZWt1ZW5zaSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KYGBge3Iga29yZWxhc2ksIGZpZy53aWR0aD03LCBmaWcuaGVpZ2h0PTd9DQprb3IgPC0gY29yKGtvcGlfbnVtKQ0KY29ycnBsb3Qoa29yLCBtZXRob2QgPSAiY29sb3IiLCB0eXBlID0gInVwcGVyIiwNCiAgICAgICAgIGFkZENvZWYuY29sID0gImJsYWNrIiwgbnVtYmVyLmNleCA9IDAuNiwNCiAgICAgICAgIHRsLmNvbCA9ICJibGFjayIsIHRsLnNydCA9IDQ1KQ0KYGBgDQoNCioqSW50ZXJwcmV0YXNpIGVrc3Bsb3Jhc2kuKiogKihJc2kgc2V0ZWxhaCBtZWxpaGF0IGhhc2lsOiBiZW50dWsgc2ViYXJhbiB0aWFwIHZhcmlhYmVsLCB2YXJpYWJlbCB5YW5nIGhhbXBpciBzZWxhbHUgYmVybmlsYWkgMTAsIGtlYmVyYWRhYW4gcGVuY2lsYW4sIGRhbiBrZWxvbXBvayB2YXJpYWJlbCB5YW5nIGJlcmtvcmVsYXNpIHRpbmdnaSBzZWJhZ2FpIGRhc2FyIGRpbGFrdWthbm55YSBQQ0EuKSoNCg0KIyMgUHJlcHJvY2Vzc2luZzogU3RhbmRhcmlzYXNpDQoNClNrb3IgbWVtaWxpa2kgcmFnYW0geWFuZyBiZXJiZWRhIGFudGFyIHZhcmlhYmVsLCBzZWhpbmdnYSBkaWxha3VrYW4gc3RhbmRhcmlzYXNpIGFnYXIgc2V0aWFwIHZhcmlhYmVsIGJlcmtvbnRyaWJ1c2kgc2V0YXJhIGRhbiBQQ0EgYmVyYmFzaXMgbWF0cmlrcyBrb3JlbGFzaS4NCg0KYGBge3Igc3RhbmRhcmlzYXNpfQ0Ka29waV9zY2FsZSA8LSBzY2FsZShrb3BpX251bSkNCmJveHBsb3Qoa29waV9zY2FsZSwgbGFzID0gMiwgY29sID0gImxpZ2h0Z3JlZW4iLA0KICAgICAgICBtYWluID0gIkJveHBsb3QgU2tvciBTZW5zb3JpIFNldGVsYWggU3RhbmRhcmlzYXNpIikNCmBgYA0KDQojIyBQcmluY2lwYWwgQ29tcG9uZW50IEFuYWx5c2lzDQoNCmBgYHtyIHBjYX0NCmhhc2lsX3BjYSA8LSBwcmNvbXAoa29waV9udW0sIHNjYWxlLiA9IFRSVUUpDQplaWcgPC0gaGFzaWxfcGNhJHNkZXZeMg0KdmFyX2V4cCA8LSByb3VuZChlaWcgLyBzdW0oZWlnKSAqIDEwMCwgMSkNCg0KdGFiZWxfZWlnIDwtIGRhdGEuZnJhbWUoDQogIFBDICAgICAgICAgICAgICAgPSBwYXN0ZTAoIlBDIiwgc2VxX2Fsb25nKGVpZykpLA0KICBFaWdlbnZhbHVlICAgICAgID0gcm91bmQoZWlnLCA0KSwNCiAgVmFyaWFuc19QZXJzZW4gICA9IHJvdW5kKGVpZyAvIHN1bShlaWcpICogMTAwLCAyKSwNCiAgS3VtdWxhdGlmX1BlcnNlbiA9IHJvdW5kKGN1bXN1bShlaWcgLyBzdW0oZWlnKSAqIDEwMCksIDIpDQopDQprYWJsZSh0YWJlbF9laWcsIGNhcHRpb24gPSAiRWlnZW52YWx1ZSBkYW4gcHJvcG9yc2kgdmFyaWFucyB0aWFwIGtvbXBvbmVuIHV0YW1hIikNCmBgYA0KDQpgYGB7ciBzY3JlZX0NCmRmX3NjcmVlIDwtIGRhdGEuZnJhbWUoUEMgPSBzZXFfYWxvbmcoZWlnKSwgRWlnZW52YWx1ZSA9IGVpZywNCiAgICAgICAgICAgICAgICAgICAgICAgUGVyc2VuID0gZWlnIC8gc3VtKGVpZykgKiAxMDApDQoNCmdncGxvdChkZl9zY3JlZSwgYWVzKHggPSBQQywgeSA9IFBlcnNlbikpICsNCiAgZ2VvbV9jb2woZmlsbCA9ICJzdGVlbGJsdWUiLCBhbHBoYSA9IDAuOCkgKw0KICBnZW9tX2xpbmUoKSArIGdlb21fcG9pbnQoKSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSBwYXN0ZTAocm91bmQoUGVyc2VuLCAxKSwgIiUiKSksIHZqdXN0ID0gLTAuNiwgc2l6ZSA9IDMpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IGRmX3NjcmVlJFBDKSArDQogIGxhYnModGl0bGUgPSAiU2NyZWUgUGxvdCAoUGVyc2VuIFZhcmlhbnMpIiwNCiAgICAgICB4ID0gIktvbXBvbmVuIFV0YW1hIiwgeSA9ICJQZXJzZW4gVmFyaWFucyIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KYGBge3Iga2Fpc2VyfQ0KZ2dwbG90KGRmX3NjcmVlLCBhZXMoeCA9IFBDLCB5ID0gRWlnZW52YWx1ZSkpICsNCiAgZ2VvbV9saW5lKCkgKyBnZW9tX3BvaW50KHNpemUgPSAyKSArDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDEsIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gInJlZCIpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IGRmX3NjcmVlJFBDKSArDQogIGxhYnModGl0bGUgPSAiU2NyZWUgUGxvdCAoRWlnZW52YWx1ZSkgZGVuZ2FuIEdhcmlzIEthaXNlciIsDQogICAgICAgeCA9ICJLb21wb25lbiBVdGFtYSIsIHkgPSAiRWlnZW52YWx1ZSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KYGBge3IgcGlsaWgtcGN9DQojIEthaXNlciAoZWlnZW52YWx1ZSA+IDEpIC0+IDIgUEM7IHZhcmlhbnMga3VtdWxhdGlmIDcwLTgwJSAtPiAzIFBDLg0KIyBEaXBha2FpIDMgUEMgKHViYWgga2UgMiBiaWxhIGluZ2luIGxlYmloIHJpbmdrYXM7IHBsb3QgM0QgYnV0dWggbl9wYyA9IDMpLg0Kbl9wYyA8LSAzDQpza29yX3BjYSA8LSBhcy5kYXRhLmZyYW1lKGhhc2lsX3BjYSR4WywgMTpuX3BjXSkNCmBgYA0KDQpCZXJkYXNhcmthbiBrcml0ZXJpYSBLYWlzZXIsIGtvbXBvbmVuIGRlbmdhbiBlaWdlbnZhbHVlIGxlYmloIGRhcmkgMSBhZGFsYWggYHIgc3VtKGVpZyA+IDEpYCBrb21wb25lbiwgc2VkYW5na2FuIGByIG5fcGNgIGtvbXBvbmVuIHV0YW1hIHBlcnRhbWEgbWVuamVsYXNrYW4gYHIgdGFiZWxfZWlnJEt1bXVsYXRpZl9QZXJzZW5bbl9wY11gJSBrZXJhZ2FtYW4gZGF0YS4gS2FyZW5hIGl0dSBkaXBlcnRhaGFua2FuICoqYHIgbl9wY2Aga29tcG9uZW4gdXRhbWEqKiB1bnR1ayBhbmFsaXNpcyBzZWxhbmp1dG55YS4gUEMxIG1lbmplbGFza2FuIGByIHRhYmVsX2VpZyRWYXJpYW5zX1BlcnNlblsxXWAlIGRhbiBQQzIgbWVuamVsYXNrYW4gYHIgdGFiZWxfZWlnJFZhcmlhbnNfUGVyc2VuWzJdYCUga2VyYWdhbWFuLg0KDQpgYGB7ciBsb2FkaW5nfQ0KbG9hZGluZyA8LSByb3VuZChoYXNpbF9wY2Ekcm90YXRpb25bLCAxOm5fcGNdLCAzKQ0Ka2FibGUobG9hZGluZywgY2FwdGlvbiA9ICJMb2FkaW5nIChrb2VmaXNpZW4gZWlnZW52ZWN0b3IpIGtvbXBvbmVuIHV0YW1hIikNCmBgYA0KDQpgYGB7ciBrb250cmlidXNpLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD00LjV9DQpyb3QgPC0gaGFzaWxfcGNhJHJvdGF0aW9uDQprb250cmlidXNpIDwtIGRhdGEuZnJhbWUoVmFyaWFiZWwgPSByb3duYW1lcyhyb3QpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIFBDMSA9IHJvdFssIDFdXjIgKiAxMDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgUEMyID0gcm90WywgMl1eMiAqIDEwMCkNCg0KYnVhdF9rb250cmlidXNpIDwtIGZ1bmN0aW9uKGtvbG9tLCBqdWR1bCkgew0KICBnZ3Bsb3Qoa29udHJpYnVzaSwgYWVzKHggPSByZW9yZGVyKFZhcmlhYmVsLCAuZGF0YVtba29sb21dXSksDQogICAgICAgICAgICAgICAgICAgICAgICAgeSA9IC5kYXRhW1trb2xvbV1dKSkgKw0KICAgIGdlb21fY29sKGZpbGwgPSAic3RlZWxibHVlIikgKw0KICAgIGNvb3JkX2ZsaXAoKSArDQogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMTAwIC8gbnJvdyhrb250cmlidXNpKSwNCiAgICAgICAgICAgICAgIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gInJlZCIpICsNCiAgICBsYWJzKHRpdGxlID0ganVkdWwsIHggPSBOVUxMLCB5ID0gIktvbnRyaWJ1c2kgKCUpIikgKw0KICAgIHRoZW1lX21pbmltYWwoKQ0KfQ0KYnVhdF9rb250cmlidXNpKCJQQzEiLCAiS29udHJpYnVzaSBWYXJpYWJlbCB0ZXJoYWRhcCBQQzEiKQ0KYnVhdF9rb250cmlidXNpKCJQQzIiLCAiS29udHJpYnVzaSBWYXJpYWJlbCB0ZXJoYWRhcCBQQzIiKQ0KYGBgDQoNCmBgYHtyIGxpbmdrYXJhbi12YXJpYWJlbCwgZmlnLndpZHRoPTYuNSwgZmlnLmhlaWdodD02fQ0Ka29vcmQgPC0gZGF0YS5mcmFtZShWYXJpYWJlbCA9IHJvd25hbWVzKHJvdCksDQogICAgICAgICAgICAgICAgICAgIFBDMSA9IHJvdFssIDFdICogaGFzaWxfcGNhJHNkZXZbMV0sDQogICAgICAgICAgICAgICAgICAgIFBDMiA9IHJvdFssIDJdICogaGFzaWxfcGNhJHNkZXZbMl0pDQpsaW5na2FyYW4gPC0gZGF0YS5mcmFtZSh4ID0gY29zKHNlcSgwLCAyICogcGksIGxlbmd0aC5vdXQgPSAyMDApKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBzaW4oc2VxKDAsIDIgKiBwaSwgbGVuZ3RoLm91dCA9IDIwMCkpKQ0KDQpnZ3Bsb3QoKSArDQogIGdlb21fcGF0aChkYXRhID0gbGluZ2thcmFuLCBhZXMoeCA9IHgsIHkgPSB5KSwgY29sb3IgPSAiZ3JleTYwIikgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJncmV5NzAiKSArDQogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gImdyZXk3MCIpICsNCiAgZ2VvbV9zZWdtZW50KGRhdGEgPSBrb29yZCwgYWVzKHggPSAwLCB5ID0gMCwgeGVuZCA9IFBDMSwgeWVuZCA9IFBDMiksDQogICAgICAgICAgICAgICBhcnJvdyA9IGFycm93KGxlbmd0aCA9IHVuaXQoMC4yLCAiY20iKSksIGNvbG9yID0gInJlZCIpICsNCiAgZ2VvbV90ZXh0KGRhdGEgPSBrb29yZCwgYWVzKHggPSBQQzEgKiAxLjEsIHkgPSBQQzIgKiAxLjEsIGxhYmVsID0gVmFyaWFiZWwpLA0KICAgICAgICAgICAgc2l6ZSA9IDMpICsNCiAgY29vcmRfZml4ZWQoeGxpbSA9IGMoLTEuMiwgMS4yKSwgeWxpbSA9IGMoLTEuMiwgMS4yKSkgKw0KICBsYWJzKHRpdGxlID0gIlZhcmlhYmVsIHBhZGEgUnVhbmcgUEMxLVBDMiIsDQogICAgICAgeCA9IHBhc3RlMCgiUEMxICgiLCB2YXJfZXhwWzFdLCAiJSkiKSwNCiAgICAgICB5ID0gcGFzdGUwKCJQQzIgKCIsIHZhcl9leHBbMl0sICIlKSIpKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCmBgYHtyIGJpcGxvdCwgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9Nn0NCmZha3RvciA8LSAwLjggKiBtYXgoYWJzKHNrb3JfcGNhWywgMToyXSkpIC8gbWF4KGFicyhrb29yZFssIGMoIlBDMSIsICJQQzIiKV0pKQ0KDQpnZ3Bsb3QoKSArDQogIGdlb21fcG9pbnQoZGF0YSA9IHNrb3JfcGNhLCBhZXMoeCA9IFBDMSwgeSA9IFBDMiksDQogICAgICAgICAgICAgY29sb3IgPSAiZ3JleTUwIiwgYWxwaGEgPSAwLjMsIHNpemUgPSAxLjIpICsNCiAgZ2VvbV9zZWdtZW50KGRhdGEgPSBrb29yZCwNCiAgICAgICAgICAgICAgIGFlcyh4ID0gMCwgeSA9IDAsIHhlbmQgPSBQQzEgKiBmYWt0b3IsIHllbmQgPSBQQzIgKiBmYWt0b3IpLA0KICAgICAgICAgICAgICAgYXJyb3cgPSBhcnJvdyhsZW5ndGggPSB1bml0KDAuMiwgImNtIikpLCBjb2xvciA9ICJyZWQiKSArDQogIGdlb21fdGV4dChkYXRhID0ga29vcmQsDQogICAgICAgICAgICBhZXMoeCA9IFBDMSAqIGZha3RvciAqIDEuMSwgeSA9IFBDMiAqIGZha3RvciAqIDEuMSwgbGFiZWwgPSBWYXJpYWJlbCksDQogICAgICAgICAgICBjb2xvciA9ICJyZWQiLCBzaXplID0gMykgKw0KICBsYWJzKHRpdGxlID0gIkJpcGxvdCBQQ0EiLA0KICAgICAgIHggPSBwYXN0ZTAoIlBDMSAoIiwgdmFyX2V4cFsxXSwgIiUpIiksDQogICAgICAgeSA9IHBhc3RlMCgiUEMyICgiLCB2YXJfZXhwWzJdLCAiJSkiKSkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQoqKkludGVycHJldGFzaSBQQ0EuKiogKihJc2kgc2V0ZWxhaCBtZWxpaGF0IHRhYmVsIGxvYWRpbmcgZGFuIGdyYWZpazogdmFyaWFiZWwgYXBhIHlhbmcgbWVtaWxpa2kgbG9hZGluZyB0ZXJiZXNhciBwYWRhIFBDMSwgUEMyLCBkYW4gUEMzLCBsYWx1IGJlcmkgbmFtYSB0aWFwIGtvbXBvbmVuIHNlc3VhaSBtYWtuYW55YS4gQ2F0YXRhbjogdGFuZGEgcG9zaXRpZiBhdGF1IG5lZ2F0aWYgcGFkYSBsb2FkaW5nIGJlcnNpZmF0IGJlYmFzIGRhbiB0aWRhayBtZW5ndWJhaCBpbnRlcnByZXRhc2kuKSoNCg0KIyMgUGVuZW50dWFuIEp1bWxhaCBDbHVzdGVyIE9wdGltYWwNCg0KQ2x1c3RlcmluZyBkaWxha3VrYW4gcGFkYSBza29yIGByIG5fcGNgIGtvbXBvbmVuIHV0YW1hIHBlcnRhbWEuDQoNCmBgYHtyIHBpbGloLWt9DQpqYXJhayA8LSBkaXN0KHNrb3JfcGNhKQ0KDQp0YWJlbF9rIDwtIGRhdGEuZnJhbWUoayA9IDE6MTAsIFdTUyA9IE5BX3JlYWxfLCBTaWxob3VldHRlID0gTkFfcmVhbF8pDQpmb3IgKGkgaW4gMToxMCkgew0KICBzZXQuc2VlZCgxMjMpDQogIGttIDwtIGttZWFucyhza29yX3BjYSwgY2VudGVycyA9IGksIG5zdGFydCA9IDI1KQ0KICB0YWJlbF9rJFdTU1tpXSA8LSByb3VuZChrbSR0b3Qud2l0aGluc3MsIDIpDQogIGlmIChpID4gMSkgew0KICAgIHRhYmVsX2skU2lsaG91ZXR0ZVtpXSA8LSByb3VuZCgNCiAgICAgIG1lYW4oc2lsaG91ZXR0ZShrbSRjbHVzdGVyLCBqYXJhaylbLCAic2lsX3dpZHRoIl0pLCA0KQ0KICB9DQp9DQprYWJsZSh0YWJlbF9rLCBjYXB0aW9uID0gIk5pbGFpIFdTUyBkYW4gcmF0YS1yYXRhIFNpbGhvdWV0dGUgdW50dWsgdGlhcCBqdW1sYWggY2x1c3RlciAoaykiKQ0KYGBgDQoNCmBgYHtyIGVsYm93fQ0KZ2dwbG90KHRhYmVsX2ssIGFlcyh4ID0gaywgeSA9IFdTUykpICsNCiAgZ2VvbV9saW5lKCkgKyBnZW9tX3BvaW50KHNpemUgPSAyKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSAxOjEwKSArDQogIGxhYnModGl0bGUgPSAiRWxib3cgTWV0aG9kIiwgeCA9ICJKdW1sYWggQ2x1c3RlciAoaykiLA0KICAgICAgIHkgPSAiVG90YWwgV2l0aGluIFN1bSBvZiBTcXVhcmVzIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQpgYGB7ciBzaWxob3VldHRlLWt9DQpzaWxfZGF0YSA8LSB0YWJlbF9rW3RhYmVsX2skayA+PSAyLCBdDQpnZ3Bsb3Qoc2lsX2RhdGEsIGFlcyh4ID0gaywgeSA9IFNpbGhvdWV0dGUpKSArDQogIGdlb21fbGluZSgpICsgZ2VvbV9wb2ludChzaXplID0gMikgKw0KICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBzaWxfZGF0YSRrW3doaWNoLm1heChzaWxfZGF0YSRTaWxob3VldHRlKV0sDQogICAgICAgICAgICAgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAiYmx1ZSIpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IDI6MTApICsNCiAgbGFicyh0aXRsZSA9ICJTaWxob3VldHRlIFNjb3JlIiwgeCA9ICJKdW1sYWggQ2x1c3RlciAoaykiLA0KICAgICAgIHkgPSAiUmF0YS1yYXRhIFNpbGhvdWV0dGUiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCmBgYHtyIHRldGFwa2FuLWt9DQojIFRldGFwa2FuIGsgYmVyZGFzYXJrYW4gRWxib3cgZGFuIFNpbGhvdWV0dGUgKGxpaGF0IHRhYmVsIGRpIGF0YXMpLg0Ka19vcHQgPC0gMw0KYGBgDQoNCioqUGVuZW50dWFuIGsuKiogKihJc2kgc2V0ZWxhaCBtZWxpaGF0IGhhc2lsOiBrIGRlbmdhbiBTaWxob3VldHRlIHRlcnRpbmdnaSwgYWRhIGF0YXUgdGlkYWtueWEgc2lrdSBwYWRhIEVsYm93LCBkYW4gYWxhc2FuIG1lbWlsaWggayA9IGByIGtfb3B0YC4pKg0KDQojIyBIYXNpbCBLLU1lYW5zDQoNCmBgYHtyIGttZWFuc30NCnNldC5zZWVkKDEyMykNCmttX2ZpbmFsIDwtIGttZWFucyhza29yX3BjYSwgY2VudGVycyA9IGtfb3B0LCBuc3RhcnQgPSA1MCkNCmtvcGlfYXJhJENsdXN0ZXIgPC0gZmFjdG9yKGttX2ZpbmFsJGNsdXN0ZXIpDQoNCmthYmxlKGFzLmRhdGEuZnJhbWUodGFibGUoQ2x1c3RlciA9IGtvcGlfYXJhJENsdXN0ZXIpKSwNCiAgICAgIGNvbC5uYW1lcyA9IGMoIkNsdXN0ZXIiLCAiSnVtbGFoIE9ic2VydmFzaSIpLA0KICAgICAgY2FwdGlvbiA9ICJVa3VyYW4gdGlhcCBjbHVzdGVyIChLLU1lYW5zKSIpDQpgYGANCg0KUHJvcG9yc2kga2VyYWdhbWFuIGFudGFyLWNsdXN0ZXIgKGJldHdlZW4gU1MgLyB0b3RhbCBTUykgc2ViZXNhciBgciByb3VuZChrbV9maW5hbCRiZXR3ZWVuc3MgLyBrbV9maW5hbCR0b3RzcyAqIDEwMCwgMilgJS4NCg0KYGBge3Igc2lsaG91ZXR0ZS1wbG90LCBmaWcuaGVpZ2h0PTZ9DQpzaWxfa20gPC0gc2lsaG91ZXR0ZShrbV9maW5hbCRjbHVzdGVyLCBqYXJhaykNCnBsb3Qoc2lsX2ttLCBjb2wgPSAyOihrX29wdCArIDEpLCBib3JkZXIgPSBOQSwNCiAgICAgbWFpbiA9ICJTaWxob3VldHRlIFBsb3QgSy1NZWFucyIpDQpgYGANCg0KYGBge3IgY2x1c3Rlci0yZCwgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9NS41fQ0Kc2tvcl9wbG90IDwtIGNiaW5kKHNrb3JfcGNhLCBDbHVzdGVyID0ga29waV9hcmEkQ2x1c3RlcikNCg0KZ2dwbG90KHNrb3JfcGxvdCwgYWVzKHggPSBQQzEsIHkgPSBQQzIsIGNvbG9yID0gQ2x1c3RlcikpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNiwgc2l6ZSA9IDEuOCkgKw0KICBzdGF0X2VsbGlwc2UobGV2ZWwgPSAwLjkwKSArDQogIGxhYnModGl0bGUgPSAiSGFzaWwgSy1NZWFucyBwYWRhIFJ1YW5nIFBDQSAoMkQpIiwNCiAgICAgICB4ID0gcGFzdGUwKCJQQzEgKCIsIHZhcl9leHBbMV0sICIlKSIpLA0KICAgICAgIHkgPSBwYXN0ZTAoIlBDMiAoIiwgdmFyX2V4cFsyXSwgIiUpIikpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KYGBge3IgY2x1c3Rlci1wYzEzLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD01LjV9DQojIFRhbXBpbGFuIHN0YXRpcyBQQzEgdnMgUEMzIChzZWxhbHUgYmlzYSB0YW1waWwgZGkgSFRNTCBtYXVwdW4gUERGKQ0KZ2dwbG90KHNrb3JfcGxvdCwgYWVzKHggPSBQQzEsIHkgPSBQQzMsIGNvbG9yID0gQ2x1c3RlcikpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNiwgc2l6ZSA9IDEuOCkgKw0KICBsYWJzKHRpdGxlID0gIkhhc2lsIEstTWVhbnMgcGFkYSBSdWFuZyBQQ0EgKFBDMSB2cyBQQzMpIiwNCiAgICAgICB4ID0gcGFzdGUwKCJQQzEgKCIsIHZhcl9leHBbMV0sICIlKSIpLA0KICAgICAgIHkgPSBwYXN0ZTAoIlBDMyAoIiwgdmFyX2V4cFszXSwgIiUpIikpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KYGBge3IgY2x1c3Rlci0zZH0NCiMgUGxvdCAzRCBpbnRlcmFrdGlmIGhhbnlhIGJpc2EgdGFtcGlsIHBhZGEga2VsdWFyYW4gSFRNTC4NCiMgVW50dWsgUERGOiBqYWxhbmthbiBjaHVuayBpbmkgZGkgUlN0dWRpbywgYW1iaWwgc2NyZWVuc2hvdCBqZW5kZWxhIFZpZXdlciwNCiMgc2ltcGFuIHNlYmFnYWkgb3V0cHV0L2NsdXN0ZXJfM2QucG5nLCBsYWx1IGtuaXQga2UgUERGLg0KcF8zZCA8LSBwbG90X2x5KHNrb3JfcGxvdCwgeCA9IH5QQzEsIHkgPSB+UEMyLCB6ID0gflBDMywNCiAgICAgICAgICAgICAgICBjb2xvciA9IH5DbHVzdGVyLCB0eXBlID0gInNjYXR0ZXIzZCIsIG1vZGUgPSAibWFya2VycyIsDQogICAgICAgICAgICAgICAgbWFya2VyID0gbGlzdChzaXplID0gMywgb3BhY2l0eSA9IDAuNykpICU+JQ0KICBsYXlvdXQodGl0bGUgPSAiSGFzaWwgSy1NZWFucyBwYWRhIFJ1YW5nIFBDQSAoM0QpIiwNCiAgICAgICAgIHNjZW5lID0gbGlzdCgNCiAgICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gcGFzdGUwKCJQQzEgKCIsIHZhcl9leHBbMV0sICIlKSIpKSwNCiAgICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gcGFzdGUwKCJQQzIgKCIsIHZhcl9leHBbMl0sICIlKSIpKSwNCiAgICAgICAgICAgemF4aXMgPSBsaXN0KHRpdGxlID0gcGFzdGUwKCJQQzMgKCIsIHZhcl9leHBbM10sICIlKSIpKSkpDQoNCmlmIChrbml0cjo6aXNfaHRtbF9vdXRwdXQoKSkgew0KICBwcmludChwXzNkKQ0KfSBlbHNlIGlmIChmaWxlLmV4aXN0cygib3V0cHV0L2NsdXN0ZXJfM2QucG5nIikpIHsNCiAga25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIm91dHB1dC9jbHVzdGVyXzNkLnBuZyIpDQp9IGVsc2Ugew0KICBjYXQoIltUYW1iYWhrYW4gc2NyZWVuc2hvdCBwbG90IDNEIHNlYmFnYWkgb3V0cHV0L2NsdXN0ZXJfM2QucG5nXSIpDQp9DQpgYGANCg0KIyMgUHJvZmlsIENsdXN0ZXINCg0KYGBge3IgcHJvZmlsfQ0KcHJvZmlsIDwtIGtvcGlfYXJhICU+JQ0KICBncm91cF9ieShDbHVzdGVyKSAlPiUNCiAgc3VtbWFyaXNlKG4gPSBuKCksIGFjcm9zcyhhbGxfb2YodmFycyksIH4gcm91bmQobWVhbigueCksIDIpKSkNCmthYmxlKHByb2ZpbCwgY2FwdGlvbiA9ICJSYXRhLXJhdGEgc2tvciBzZW5zb3JpIChkYXRhIGFzbGkpIHRpYXAgY2x1c3RlciIpDQpgYGANCg0KYGBge3IgcHJvZmlsLWJveHBsb3QsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD03fQ0Ka29waV9hcmEgJT4lDQogIHNlbGVjdChDbHVzdGVyLCBhbGxfb2YodmFycykpICU+JQ0KICBwaXZvdF9sb25nZXIoLUNsdXN0ZXIsIG5hbWVzX3RvID0gIlZhcmlhYmVsIiwgdmFsdWVzX3RvID0gIlNrb3IiKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gQ2x1c3RlciwgeSA9IFNrb3IsIGZpbGwgPSBDbHVzdGVyKSkgKw0KICBnZW9tX2JveHBsb3Qob3V0bGllci5zaXplID0gMC44KSArDQogIGZhY2V0X3dyYXAoflZhcmlhYmVsLCBzY2FsZXMgPSAiZnJlZV95IikgKw0KICBsYWJzKHRpdGxlID0gIlByb2ZpbCBTa29yIFNlbnNvcmkgcGVyIENsdXN0ZXIiKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCmBgYA0KDQojIyBQZW1iYW5kaW5nOiBIaWVyYXJjaGljYWwgQ2x1c3RlcmluZyAoV2FyZC5EMikNCg0KYGBge3IgaGllcmFya2ksIGZpZy5oZWlnaHQ9NX0NCmhjIDwtIGhjbHVzdChqYXJhaywgbWV0aG9kID0gIndhcmQuRDIiKQ0KcGxvdChoYywgbGFiZWxzID0gRkFMU0UsIGhhbmcgPSAtMSwNCiAgICAgbWFpbiA9ICJEZW5kcm9ncmFtIEhpZXJhcmNoaWNhbCAoV2FyZC5EMikiLCB4bGFiID0gIiIsIHN1YiA9ICIiKQ0KcmVjdC5oY2x1c3QoaGMsIGsgPSBrX29wdCwgYm9yZGVyID0gMjooa19vcHQgKyAxKSkNCmtvcGlfYXJhJENsdXN0ZXJfSEMgPC0gZmFjdG9yKGN1dHJlZShoYywgayA9IGtfb3B0KSkNCmBgYA0KDQpgYGB7ciBiYW5kaW5na2FufQ0Ka2FibGUoYXMuZGF0YS5mcmFtZS5tYXRyaXgodGFibGUoS01lYW5zID0ga29waV9hcmEkQ2x1c3RlciwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEhpZXJhcmtpID0ga29waV9hcmEkQ2x1c3Rlcl9IQykpLA0KICAgICAgY2FwdGlvbiA9ICJLZXNlc3VhaWFuIGtlYW5nZ290YWFuIGNsdXN0ZXI6IEstTWVhbnMgKGJhcmlzKSB2cyBIaWVyYXJjaGljYWwgKGtvbG9tKSIpDQoNCnNpbF9oYyA8LSBzaWxob3VldHRlKGN1dHJlZShoYywgayA9IGtfb3B0KSwgamFyYWspDQpwZXJiYW5kaW5nYW4gPC0gZGF0YS5mcmFtZSgNCiAgTWV0b2RlICAgICA9IGMoIkstTWVhbnMiLCAiSGllcmFyY2hpY2FsIChXYXJkLkQyKSIpLA0KICBTaWxob3VldHRlID0gcm91bmQoYyhtZWFuKHNpbF9rbVssICJzaWxfd2lkdGgiXSksDQogICAgICAgICAgICAgICAgICAgICAgIG1lYW4oc2lsX2hjWywgInNpbF93aWR0aCJdKSksIDQpDQopDQprYWJsZShwZXJiYW5kaW5nYW4sIGNhcHRpb24gPSAiUGVyYmFuZGluZ2FuIHJhdGEtcmF0YSBTaWxob3VldHRlIGtlZHVhIG1ldG9kZSIpDQpgYGANCg0KYGBge3IgaGllcmFya2ktcGNhLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD01LjV9DQpnZ3Bsb3QoY2JpbmQoc2tvcl9wY2EsIENsdXN0ZXIgPSBrb3BpX2FyYSRDbHVzdGVyX0hDKSwNCiAgICAgICBhZXMoeCA9IFBDMSwgeSA9IFBDMiwgY29sb3IgPSBDbHVzdGVyKSkgKw0KICBnZW9tX3BvaW50KGFscGhhID0gMC42LCBzaXplID0gMS44KSArDQogIGxhYnModGl0bGUgPSAiSGllcmFyY2hpY2FsIChXYXJkLkQyKSBwYWRhIFJ1YW5nIFBDQSIsDQogICAgICAgeCA9IHBhc3RlMCgiUEMxICgiLCB2YXJfZXhwWzFdLCAiJSkiKSwNCiAgICAgICB5ID0gcGFzdGUwKCJQQzIgKCIsIHZhcl9leHBbMl0sICIlKSIpKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCiMgRGlza3VzaQ0KDQoqKkludGVycHJldGFzaSBQQ0EuKiogKihKZWxhc2thbiBtYWtuYSB0aWFwIGtvbXBvbmVuIHV0YW1hIGJlcmRhc2Fya2FuIGxvYWRpbmcsIGJlc2FyIHZhcmlhbnMgeWFuZyBkaWplbGFza2FuLCBkYW4gYWxhc2FuIG1lbWlsaWgganVtbGFoIGtvbXBvbmVuLikqDQoNCioqSW50ZXJwcmV0YXNpIGNsdXN0ZXJpbmcuKiogKihKZWxhc2thbiBrYXJha3RlcmlzdGlrIHRpYXAgY2x1c3RlciBiZXJkYXNhcmthbiB0YWJlbCBwcm9maWwgZGFuIGJveHBsb3QsIGt1YWxpdGFzIHBlbWlzYWhhbiBjbHVzdGVyIGJlcmRhc2Fya2FuIFNpbGhvdWV0dGUsIGRhbiBwZXJiYW5kaW5nYW4gZGVuZ2FuIEhpZXJhcmNoaWNhbCBDbHVzdGVyaW5nLikqDQoNCioqS2V0ZXJiYXRhc2FuLioqICooQ29udG9oOiBiYW55YWsgc2tvciBtZW5kZWthdGkgMTAgcGFkYSBVbmlmb3JtaXR5LCBDbGVhbi5DdXAsIGRhbiBTd2VldG5lc3Mgc2VoaW5nZ2Egc2ViYXJhbm55YSBtaXJpbmc7IHBlbmdhcnVoIHBlbmNpbGFuIHRlcmhhZGFwIEstTWVhbnM7IGhhc2lsIEstTWVhbnMgYmVyZ2FudHVuZyBwYWRhIGluaXNpYWxpc2FzaSBzZWhpbmdnYSBkaXBha2FpIHNldC5zZWVkIGRhbiBuc3RhcnQuKSoNCg0KIyBLZXNpbXB1bGFuDQoNCiooVHVsaXNrYW4gMy00IGthbGltYXQ6IGp1bWxhaCBrb21wb25lbiBkYW4gdmFyaWFucyB5YW5nIGRpamVsYXNrYW4sIG1ha25hIGtvbXBvbmVuIHV0YW1hLCBqdW1sYWggY2x1c3RlciBvcHRpbWFsIGJlc2VydGEgY2lyaSB0aWFwIGNsdXN0ZXIsIGRhbiBrZXNlc3VhaWFuIEstTWVhbnMgZGVuZ2FuIEhpZXJhcmNoaWNhbC4pKg0KDQojIExhbXBpcmFuOiBLb2RlIEFuYWxpc2lzDQoNCktvZGUgYW5hbGlzaXMgbGVuZ2thcCAoUikgZGFwYXQgZGlha3NlcyBtZWxhbHVpIHRhdXRhbiBHb29nbGUgRHJpdmUgYmVyaWt1dDogKipbdGVtcGVsIHRhdXRhbiBHb29nbGUgRHJpdmUgZGkgc2luaV0qKg0KDQpgYGB7ciBzZXNpLCBlY2hvPVRSVUV9DQpzZXNzaW9uSW5mbygpDQpgYGANCg==