1 Pendahuluan dan Deskripsi Dataset

Laporan ini menerapkan Principal Component Analysis (PCA) dan clustering K-Means pada data penilaian sensori kopi arabika.

Sumber data. Coffee Quality Database dari Coffee Quality Institute (CQI), dikumpulkan oleh James LeDoux dan tersedia di GitHub (https://github.com/jldbc/coffee-quality-database) serta Kaggle (https://www.kaggle.com/datasets/volpatto/coffee-quality-database-from-cqi). Berkas yang dipakai: arabica_data_cleaned.csv.

Ukuran data. Berkas asli berisi 1311 observasi dan 44 kolom. Setelah membuang 1 baris yang seluruh skornya bernilai 0 (data cacat), tersisa 1310 observasi dengan 10 variabel numerik.

Variabel yang dianalisis adalah 10 skor sensori hasil cupping (skala 0-10):

Variabel Keterangan
Aroma, Flavor, Aftertaste Penilaian aroma, rasa, dan kesan rasa setelah ditelan
Acidity, Body, Balance Keasaman, kekentalan, dan keseimbangan
Uniformity, Clean.Cup, Sweetness Konsistensi antar cangkir, kebersihan cangkir, dan kemanisan
Cupper.Points Penilaian keseluruhan oleh penguji

Variabel Total.Cup.Points tidak dipakai karena hampir sama dengan jumlah kesepuluh skor di atas sehingga akan menduplikasi informasi.

2 Tahapan Analisis

  1. Eksplorasi data (statistik deskriptif, boxplot, histogram, korelasi).
  2. Preprocessing: standarisasi (mean 0, simpangan baku 1).
  3. PCA dan penentuan jumlah komponen utama.
  4. Penentuan jumlah cluster optimal (Elbow Method dan Silhouette Score) pada skor PCA.
  5. K-Means sebagai metode utama dan Hierarchical Clustering (Ward.D2) sebagai pembanding.
  6. Visualisasi cluster pada ruang PCA dan interpretasi profil cluster.

3 Hasil

3.1 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
Mean SD Min Median Max
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.)

3.2 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")

3.3 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
PC Eigenvalue Varians_Persen Kumulatif_Persen
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
PC1 PC2 PC3
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.)

3.4 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)
k WSS Silhouette
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.)

3.5 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)
Cluster Jumlah Observasi
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]")
}

3.6 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
Cluster n Aroma Flavor Aftertaste Acidity Body Balance Uniformity Clean.Cup Sweetness Cupper.Points
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")

3.7 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)
1 2 3
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
Metode Silhouette
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()

4 Diskusi

Interpretasi PCA. (Jelaskan makna tiap komponen utama berdasarkan loading, besar varians yang dijelaskan, dan alasan memilih jumlah komponen.)

Interpretasi clustering. (Jelaskan karakteristik tiap cluster berdasarkan tabel profil dan boxplot, kualitas pemisahan cluster berdasarkan Silhouette, dan perbandingan dengan Hierarchical Clustering.)

Keterbatasan. (Contoh: banyak skor mendekati 10 pada Uniformity, Clean.Cup, dan Sweetness sehingga sebarannya miring; pengaruh pencilan terhadap K-Means; hasil K-Means bergantung pada inisialisasi sehingga dipakai set.seed dan nstart.)

5 Kesimpulan

(Tuliskan 3-4 kalimat: jumlah komponen dan varians yang dijelaskan, makna komponen utama, jumlah cluster optimal beserta ciri tiap cluster, dan kesesuaian K-Means dengan Hierarchical.)

6 Lampiran: Kode Analisis

Kode analisis lengkap (R) dapat diakses melalui tautan Google Drive berikut: [tempel tautan Google Drive di sini]

sessionInfo()
## R version 4.6.1 (2026-06-24 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 26200)
## 
## Matrix products: default
##   LAPACK version 3.12.1
## 
## locale:
## [1] LC_COLLATE=English_United States.utf8 
## [2] LC_CTYPE=English_United States.utf8   
## [3] LC_MONETARY=English_United States.utf8
## [4] LC_NUMERIC=C                          
## [5] LC_TIME=English_United States.utf8    
## 
## time zone: Asia/Shanghai
## tzcode source: internal
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] knitr_1.52      plotly_4.12.1   cluster_2.1.8.2 corrplot_0.95  
## [5] ggplot2_4.0.3   tidyr_1.3.2     dplyr_1.2.1    
## 
## loaded via a namespace (and not attached):
##  [1] gtable_0.3.6        jsonlite_2.0.0      compiler_4.6.1     
##  [4] tidyselect_1.2.1    jquerylib_0.1.4     scales_1.4.0       
##  [7] yaml_2.3.12         fastmap_1.2.0       R6_2.6.1           
## [10] labeling_0.4.3      generics_0.1.4      MASS_7.3-65        
## [13] htmlwidgets_1.6.4   tibble_3.3.1        bslib_0.12.0       
## [16] pillar_1.11.1       RColorBrewer_1.1-3  rlang_1.3.0        
## [19] cachem_1.1.0        xfun_0.61           sass_0.4.10        
## [22] S7_0.2.2            otel_0.2.0          viridisLite_0.4.3  
## [25] cli_3.6.6           withr_3.0.3         magrittr_2.0.5     
## [28] crosstalk_1.2.2     digest_0.6.39       grid_4.6.1         
## [31] lifecycle_1.0.5     vctrs_0.7.3         data.table_1.18.6.1
## [34] evaluate_1.0.5      glue_1.8.1          farver_2.1.2       
## [37] httr_1.4.9          rmarkdown_2.32      purrr_1.2.2        
## [40] tools_4.6.1         pkgconfig_2.0.3     htmltools_0.5.9
LS0tDQp0aXRsZTogIkFuYWxpc2lzIFByaW5jaXBhbCBDb21wb25lbnQgQW5hbHlzaXMgZGFuIENsdXN0ZXJpbmcgcGFkYSBEYXRhIEt1YWxpdGFzIEtvcGkgQXJhYmlrYSINCmF1dGhvcjogIlJpc2thIFNhcHV0cmkgLSBIMDUxMjQxMDgwIg0KZGF0ZTogImByIGZvcm1hdChTeXMuRGF0ZSgpLCAnJWQgJUIgJVknKWAiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICBwZGZfZG9jdW1lbnQ6DQogICAgdG9jOiB0cnVlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQojIGVjaG8gPSBUUlVFICAgOiBrb2RlIGlrdXQgdGFtcGlsIGRpIGxhcG9yYW4uIFBhZGEgSFRNTCwga29kZSBkaWxpcGF0DQojICAgICAgICAgICAgICAgICAoY29kZV9mb2xkaW5nOiBoaWRlKSBkYW4gcGVtYmFjYSBiaXNhIG1lbWJ1a2FueWEgbGV3YXQNCiMgICAgICAgICAgICAgICAgIHRvbWJvbCAiQ29kZSIuIFViYWgga2UgRkFMU0UgdW50dWsgbWVueWVtYnVueWlrYW4ga29kZS4NCiMgZmlnLnBhdGggICAgICA6IHNlbXVhIGdhbWJhciBvdG9tYXRpcyBqdWdhIHRlcnNpbXBhbiBzZWJhZ2FpIGZpbGUgUE5HDQprbml0cjo6b3B0c19jaHVuayRzZXQoDQogIGVjaG8gPSBUUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwNCiAgZmlnLmFsaWduID0gImNlbnRlciIsIGZpZy53aWR0aCA9IDcsIGZpZy5oZWlnaHQgPSA1LCBkcGkgPSAxNTAsDQogIGZpZy5wYXRoID0gIm91dHB1dC9nYW1iYXIvIg0KKQ0KDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoY29ycnBsb3QpDQpsaWJyYXJ5KGNsdXN0ZXIpDQpsaWJyYXJ5KHBsb3RseSkNCmxpYnJhcnkoa25pdHIpDQoNCmRpci5jcmVhdGUoIm91dHB1dCIsIHNob3dXYXJuaW5ncyA9IEZBTFNFKQ0KYGBgDQoNCmBgYHtyIGltcG9yLCBpbmNsdWRlPUZBTFNFfQ0KIyBGaWxlIENTViBoYXJ1cyBiZXJhZGEgZGkgZm9sZGVyIHlhbmcgU0FNQSBkZW5nYW4gZmlsZSAuUm1kIGluaS4NCmtvcGkgPC0gcmVhZC5jc3YoImFyYWJpY2FfZGF0YV9jbGVhbmVkLmNzdiIpDQoNCnZhcnMgPC0gYygiQXJvbWEiLCAiRmxhdm9yIiwgIkFmdGVydGFzdGUiLCAiQWNpZGl0eSIsICJCb2R5IiwNCiAgICAgICAgICAiQmFsYW5jZSIsICJVbmlmb3JtaXR5IiwgIkNsZWFuLkN1cCIsICJTd2VldG5lc3MiLA0KICAgICAgICAgICJDdXBwZXIuUG9pbnRzIikNCg0Ka29waV9hcmEgPC0ga29waSAlPiUgZmlsdGVyKEFyb21hID4gMCkgICAjIGJ1YW5nIDEgYmFyaXMgZGF0YSBjYWNhdCAoc2VtdWEgc2tvciAwKQ0Ka29waV9udW0gPC0ga29waV9hcmFbLCB2YXJzXQ0KYGBgDQoNCiMgUGVuZGFodWx1YW4gZGFuIERlc2tyaXBzaSBEYXRhc2V0DQoNCkxhcG9yYW4gaW5pIG1lbmVyYXBrYW4gUHJpbmNpcGFsIENvbXBvbmVudCBBbmFseXNpcyAoUENBKSBkYW4gY2x1c3RlcmluZyBLLU1lYW5zIHBhZGEgZGF0YSBwZW5pbGFpYW4gc2Vuc29yaSBrb3BpIGFyYWJpa2EuDQoNCioqU3VtYmVyIGRhdGEuKiogQ29mZmVlIFF1YWxpdHkgRGF0YWJhc2UgZGFyaSBDb2ZmZWUgUXVhbGl0eSBJbnN0aXR1dGUgKENRSSksIGRpa3VtcHVsa2FuIG9sZWggSmFtZXMgTGVEb3V4IGRhbiB0ZXJzZWRpYSBkaSBHaXRIdWIgKGh0dHBzOi8vZ2l0aHViLmNvbS9qbGRiYy9jb2ZmZWUtcXVhbGl0eS1kYXRhYmFzZSkgc2VydGEgS2FnZ2xlIChodHRwczovL3d3dy5rYWdnbGUuY29tL2RhdGFzZXRzL3ZvbHBhdHRvL2NvZmZlZS1xdWFsaXR5LWRhdGFiYXNlLWZyb20tY3FpKS4gQmVya2FzIHlhbmcgZGlwYWthaTogYGFyYWJpY2FfZGF0YV9jbGVhbmVkLmNzdmAuDQoNCioqVWt1cmFuIGRhdGEuKiogQmVya2FzIGFzbGkgYmVyaXNpIGByIG5yb3coa29waSlgIG9ic2VydmFzaSBkYW4gYHIgbmNvbChrb3BpKWAga29sb20uIFNldGVsYWggbWVtYnVhbmcgYHIgbnJvdyhrb3BpKSAtIG5yb3coa29waV9hcmEpYCBiYXJpcyB5YW5nIHNlbHVydWggc2tvcm55YSBiZXJuaWxhaSAwIChkYXRhIGNhY2F0KSwgdGVyc2lzYSBgciBucm93KGtvcGlfYXJhKWAgb2JzZXJ2YXNpIGRlbmdhbiBgciBsZW5ndGgodmFycylgIHZhcmlhYmVsIG51bWVyaWsuDQoNCioqVmFyaWFiZWwgeWFuZyBkaWFuYWxpc2lzKiogYWRhbGFoIDEwIHNrb3Igc2Vuc29yaSBoYXNpbCBjdXBwaW5nIChza2FsYSAwLTEwKToNCg0KfCBWYXJpYWJlbCB8IEtldGVyYW5nYW4gfA0KfC0tLXwtLS18DQp8IEFyb21hLCBGbGF2b3IsIEFmdGVydGFzdGUgfCBQZW5pbGFpYW4gYXJvbWEsIHJhc2EsIGRhbiBrZXNhbiByYXNhIHNldGVsYWggZGl0ZWxhbiB8DQp8IEFjaWRpdHksIEJvZHksIEJhbGFuY2UgfCBLZWFzYW1hbiwga2VrZW50YWxhbiwgZGFuIGtlc2VpbWJhbmdhbiB8DQp8IFVuaWZvcm1pdHksIENsZWFuLkN1cCwgU3dlZXRuZXNzIHwgS29uc2lzdGVuc2kgYW50YXIgY2FuZ2tpciwga2ViZXJzaWhhbiBjYW5na2lyLCBkYW4ga2VtYW5pc2FuIHwNCnwgQ3VwcGVyLlBvaW50cyB8IFBlbmlsYWlhbiBrZXNlbHVydWhhbiBvbGVoIHBlbmd1amkgfA0KDQpWYXJpYWJlbCBgVG90YWwuQ3VwLlBvaW50c2AgdGlkYWsgZGlwYWthaSBrYXJlbmEgaGFtcGlyIHNhbWEgZGVuZ2FuIGp1bWxhaCBrZXNlcHVsdWggc2tvciBkaSBhdGFzIHNlaGluZ2dhIGFrYW4gbWVuZHVwbGlrYXNpIGluZm9ybWFzaS4NCg0KIyBUYWhhcGFuIEFuYWxpc2lzDQoNCjEuIEVrc3Bsb3Jhc2kgZGF0YSAoc3RhdGlzdGlrIGRlc2tyaXB0aWYsIGJveHBsb3QsIGhpc3RvZ3JhbSwga29yZWxhc2kpLg0KMi4gUHJlcHJvY2Vzc2luZzogc3RhbmRhcmlzYXNpIChtZWFuIDAsIHNpbXBhbmdhbiBiYWt1IDEpLg0KMy4gUENBIGRhbiBwZW5lbnR1YW4ganVtbGFoIGtvbXBvbmVuIHV0YW1hLg0KNC4gUGVuZW50dWFuIGp1bWxhaCBjbHVzdGVyIG9wdGltYWwgKEVsYm93IE1ldGhvZCBkYW4gU2lsaG91ZXR0ZSBTY29yZSkgcGFkYSBza29yIFBDQS4NCjUuIEstTWVhbnMgc2ViYWdhaSBtZXRvZGUgdXRhbWEgZGFuIEhpZXJhcmNoaWNhbCBDbHVzdGVyaW5nIChXYXJkLkQyKSBzZWJhZ2FpIHBlbWJhbmRpbmcuDQo2LiBWaXN1YWxpc2FzaSBjbHVzdGVyIHBhZGEgcnVhbmcgUENBIGRhbiBpbnRlcnByZXRhc2kgcHJvZmlsIGNsdXN0ZXIuDQoNCiMgSGFzaWwNCg0KIyMgRWtzcGxvcmFzaSBEYXRhIEF3YWwNCg0KYGBge3Igc3RhdGRlc2t9DQpzdGF0ZGVzayA8LSBkYXRhLmZyYW1lKA0KICBNZWFuICAgPSBzYXBwbHkoa29waV9udW0sIG1lYW4pLA0KICBTRCAgICAgPSBzYXBwbHkoa29waV9udW0sIHNkKSwNCiAgTWluICAgID0gc2FwcGx5KGtvcGlfbnVtLCBtaW4pLA0KICBNZWRpYW4gPSBzYXBwbHkoa29waV9udW0sIG1lZGlhbiksDQogIE1heCAgICA9IHNhcHBseShrb3BpX251bSwgbWF4KQ0KKQ0Ka2FibGUocm91bmQoc3RhdGRlc2ssIDMpLCBjYXB0aW9uID0gIlN0YXRpc3RpayBkZXNrcmlwdGlmIHNrb3Igc2Vuc29yaSBrb3BpIGFyYWJpa2EiKQ0KYGBgDQoNCmBgYHtyIGJveHBsb3QtYXNsaX0NCmJveHBsb3Qoa29waV9udW0sIGxhcyA9IDIsIGNvbCA9ICJsaWdodGJsdWUiLA0KICAgICAgICBtYWluID0gIkJveHBsb3QgU2tvciBTZW5zb3JpIEtvcGkgQXJhYmlrYSAoRGF0YSBBc2xpKSIpDQpgYGANCg0KYGBge3IgaGlzdG9ncmFtLCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD02fQ0Ka29waV9udW0gJT4lDQogIHBpdm90X2xvbmdlcihldmVyeXRoaW5nKCksIG5hbWVzX3RvID0gIlZhcmlhYmVsIiwgdmFsdWVzX3RvID0gIlNrb3IiKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gU2tvcikpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDMwLCBmaWxsID0gInN0ZWVsYmx1ZSIsIGNvbG9yID0gIndoaXRlIikgKw0KICBmYWNldF93cmFwKH5WYXJpYWJlbCwgc2NhbGVzID0gImZyZWUiKSArDQogIGxhYnModGl0bGUgPSAiRGlzdHJpYnVzaSBTa29yIFNlbnNvcmkgS29waSBBcmFiaWthIiwNCiAgICAgICB4ID0gIlNrb3IiLCB5ID0gIkZyZWt1ZW5zaSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KYGBge3Iga29yZWxhc2ksIGZpZy53aWR0aD03LCBmaWcuaGVpZ2h0PTd9DQprb3IgPC0gY29yKGtvcGlfbnVtKQ0KY29ycnBsb3Qoa29yLCBtZXRob2QgPSAiY29sb3IiLCB0eXBlID0gInVwcGVyIiwNCiAgICAgICAgIGFkZENvZWYuY29sID0gImJsYWNrIiwgbnVtYmVyLmNleCA9IDAuNiwNCiAgICAgICAgIHRsLmNvbCA9ICJibGFjayIsIHRsLnNydCA9IDQ1KQ0KYGBgDQoNCioqSW50ZXJwcmV0YXNpIGVrc3Bsb3Jhc2kuKiogKihJc2kgc2V0ZWxhaCBtZWxpaGF0IGhhc2lsOiBiZW50dWsgc2ViYXJhbiB0aWFwIHZhcmlhYmVsLCB2YXJpYWJlbCB5YW5nIGhhbXBpciBzZWxhbHUgYmVybmlsYWkgMTAsIGtlYmVyYWRhYW4gcGVuY2lsYW4sIGRhbiBrZWxvbXBvayB2YXJpYWJlbCB5YW5nIGJlcmtvcmVsYXNpIHRpbmdnaSBzZWJhZ2FpIGRhc2FyIGRpbGFrdWthbm55YSBQQ0EuKSoNCg0KIyMgUHJlcHJvY2Vzc2luZzogU3RhbmRhcmlzYXNpDQoNClNrb3IgbWVtaWxpa2kgcmFnYW0geWFuZyBiZXJiZWRhIGFudGFyIHZhcmlhYmVsLCBzZWhpbmdnYSBkaWxha3VrYW4gc3RhbmRhcmlzYXNpIGFnYXIgc2V0aWFwIHZhcmlhYmVsIGJlcmtvbnRyaWJ1c2kgc2V0YXJhIGRhbiBQQ0EgYmVyYmFzaXMgbWF0cmlrcyBrb3JlbGFzaS4NCg0KYGBge3Igc3RhbmRhcmlzYXNpfQ0Ka29waV9zY2FsZSA8LSBzY2FsZShrb3BpX251bSkNCmJveHBsb3Qoa29waV9zY2FsZSwgbGFzID0gMiwgY29sID0gImxpZ2h0Z3JlZW4iLA0KICAgICAgICBtYWluID0gIkJveHBsb3QgU2tvciBTZW5zb3JpIFNldGVsYWggU3RhbmRhcmlzYXNpIikNCmBgYA0KDQojIyBQcmluY2lwYWwgQ29tcG9uZW50IEFuYWx5c2lzDQoNCmBgYHtyIHBjYX0NCmhhc2lsX3BjYSA8LSBwcmNvbXAoa29waV9udW0sIHNjYWxlLiA9IFRSVUUpDQplaWcgPC0gaGFzaWxfcGNhJHNkZXZeMg0KdmFyX2V4cCA8LSByb3VuZChlaWcgLyBzdW0oZWlnKSAqIDEwMCwgMSkNCg0KdGFiZWxfZWlnIDwtIGRhdGEuZnJhbWUoDQogIFBDICAgICAgICAgICAgICAgPSBwYXN0ZTAoIlBDIiwgc2VxX2Fsb25nKGVpZykpLA0KICBFaWdlbnZhbHVlICAgICAgID0gcm91bmQoZWlnLCA0KSwNCiAgVmFyaWFuc19QZXJzZW4gICA9IHJvdW5kKGVpZyAvIHN1bShlaWcpICogMTAwLCAyKSwNCiAgS3VtdWxhdGlmX1BlcnNlbiA9IHJvdW5kKGN1bXN1bShlaWcgLyBzdW0oZWlnKSAqIDEwMCksIDIpDQopDQprYWJsZSh0YWJlbF9laWcsIGNhcHRpb24gPSAiRWlnZW52YWx1ZSBkYW4gcHJvcG9yc2kgdmFyaWFucyB0aWFwIGtvbXBvbmVuIHV0YW1hIikNCmBgYA0KDQpgYGB7ciBzY3JlZX0NCmRmX3NjcmVlIDwtIGRhdGEuZnJhbWUoUEMgPSBzZXFfYWxvbmcoZWlnKSwgRWlnZW52YWx1ZSA9IGVpZywNCiAgICAgICAgICAgICAgICAgICAgICAgUGVyc2VuID0gZWlnIC8gc3VtKGVpZykgKiAxMDApDQoNCmdncGxvdChkZl9zY3JlZSwgYWVzKHggPSBQQywgeSA9IFBlcnNlbikpICsNCiAgZ2VvbV9jb2woZmlsbCA9ICJzdGVlbGJsdWUiLCBhbHBoYSA9IDAuOCkgKw0KICBnZW9tX2xpbmUoKSArIGdlb21fcG9pbnQoKSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSBwYXN0ZTAocm91bmQoUGVyc2VuLCAxKSwgIiUiKSksIHZqdXN0ID0gLTAuNiwgc2l6ZSA9IDMpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IGRmX3NjcmVlJFBDKSArDQogIGxhYnModGl0bGUgPSAiU2NyZWUgUGxvdCAoUGVyc2VuIFZhcmlhbnMpIiwNCiAgICAgICB4ID0gIktvbXBvbmVuIFV0YW1hIiwgeSA9ICJQZXJzZW4gVmFyaWFucyIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KYGBge3Iga2Fpc2VyfQ0KZ2dwbG90KGRmX3NjcmVlLCBhZXMoeCA9IFBDLCB5ID0gRWlnZW52YWx1ZSkpICsNCiAgZ2VvbV9saW5lKCkgKyBnZW9tX3BvaW50KHNpemUgPSAyKSArDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDEsIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gInJlZCIpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IGRmX3NjcmVlJFBDKSArDQogIGxhYnModGl0bGUgPSAiU2NyZWUgUGxvdCAoRWlnZW52YWx1ZSkgZGVuZ2FuIEdhcmlzIEthaXNlciIsDQogICAgICAgeCA9ICJLb21wb25lbiBVdGFtYSIsIHkgPSAiRWlnZW52YWx1ZSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KYGBge3IgcGlsaWgtcGN9DQojIEthaXNlciAoZWlnZW52YWx1ZSA+IDEpIC0+IDIgUEM7IHZhcmlhbnMga3VtdWxhdGlmIDcwLTgwJSAtPiAzIFBDLg0KIyBEaXBha2FpIDMgUEMgKHViYWgga2UgMiBiaWxhIGluZ2luIGxlYmloIHJpbmdrYXM7IHBsb3QgM0QgYnV0dWggbl9wYyA9IDMpLg0Kbl9wYyA8LSAzDQpza29yX3BjYSA8LSBhcy5kYXRhLmZyYW1lKGhhc2lsX3BjYSR4WywgMTpuX3BjXSkNCmBgYA0KDQpCZXJkYXNhcmthbiBrcml0ZXJpYSBLYWlzZXIsIGtvbXBvbmVuIGRlbmdhbiBlaWdlbnZhbHVlIGxlYmloIGRhcmkgMSBhZGFsYWggYHIgc3VtKGVpZyA+IDEpYCBrb21wb25lbiwgc2VkYW5na2FuIGByIG5fcGNgIGtvbXBvbmVuIHV0YW1hIHBlcnRhbWEgbWVuamVsYXNrYW4gYHIgdGFiZWxfZWlnJEt1bXVsYXRpZl9QZXJzZW5bbl9wY11gJSBrZXJhZ2FtYW4gZGF0YS4gS2FyZW5hIGl0dSBkaXBlcnRhaGFua2FuICoqYHIgbl9wY2Aga29tcG9uZW4gdXRhbWEqKiB1bnR1ayBhbmFsaXNpcyBzZWxhbmp1dG55YS4gUEMxIG1lbmplbGFza2FuIGByIHRhYmVsX2VpZyRWYXJpYW5zX1BlcnNlblsxXWAlIGRhbiBQQzIgbWVuamVsYXNrYW4gYHIgdGFiZWxfZWlnJFZhcmlhbnNfUGVyc2VuWzJdYCUga2VyYWdhbWFuLg0KDQpgYGB7ciBsb2FkaW5nfQ0KbG9hZGluZyA8LSByb3VuZChoYXNpbF9wY2Ekcm90YXRpb25bLCAxOm5fcGNdLCAzKQ0Ka2FibGUobG9hZGluZywgY2FwdGlvbiA9ICJMb2FkaW5nIChrb2VmaXNpZW4gZWlnZW52ZWN0b3IpIGtvbXBvbmVuIHV0YW1hIikNCmBgYA0KDQpgYGB7ciBrb250cmlidXNpLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD00LjV9DQpyb3QgPC0gaGFzaWxfcGNhJHJvdGF0aW9uDQprb250cmlidXNpIDwtIGRhdGEuZnJhbWUoVmFyaWFiZWwgPSByb3duYW1lcyhyb3QpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIFBDMSA9IHJvdFssIDFdXjIgKiAxMDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgUEMyID0gcm90WywgMl1eMiAqIDEwMCkNCg0KYnVhdF9rb250cmlidXNpIDwtIGZ1bmN0aW9uKGtvbG9tLCBqdWR1bCkgew0KICBnZ3Bsb3Qoa29udHJpYnVzaSwgYWVzKHggPSByZW9yZGVyKFZhcmlhYmVsLCAuZGF0YVtba29sb21dXSksDQogICAgICAgICAgICAgICAgICAgICAgICAgeSA9IC5kYXRhW1trb2xvbV1dKSkgKw0KICAgIGdlb21fY29sKGZpbGwgPSAic3RlZWxibHVlIikgKw0KICAgIGNvb3JkX2ZsaXAoKSArDQogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMTAwIC8gbnJvdyhrb250cmlidXNpKSwNCiAgICAgICAgICAgICAgIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gInJlZCIpICsNCiAgICBsYWJzKHRpdGxlID0ganVkdWwsIHggPSBOVUxMLCB5ID0gIktvbnRyaWJ1c2kgKCUpIikgKw0KICAgIHRoZW1lX21pbmltYWwoKQ0KfQ0KYnVhdF9rb250cmlidXNpKCJQQzEiLCAiS29udHJpYnVzaSBWYXJpYWJlbCB0ZXJoYWRhcCBQQzEiKQ0KYnVhdF9rb250cmlidXNpKCJQQzIiLCAiS29udHJpYnVzaSBWYXJpYWJlbCB0ZXJoYWRhcCBQQzIiKQ0KYGBgDQoNCmBgYHtyIGxpbmdrYXJhbi12YXJpYWJlbCwgZmlnLndpZHRoPTYuNSwgZmlnLmhlaWdodD02fQ0Ka29vcmQgPC0gZGF0YS5mcmFtZShWYXJpYWJlbCA9IHJvd25hbWVzKHJvdCksDQogICAgICAgICAgICAgICAgICAgIFBDMSA9IHJvdFssIDFdICogaGFzaWxfcGNhJHNkZXZbMV0sDQogICAgICAgICAgICAgICAgICAgIFBDMiA9IHJvdFssIDJdICogaGFzaWxfcGNhJHNkZXZbMl0pDQpsaW5na2FyYW4gPC0gZGF0YS5mcmFtZSh4ID0gY29zKHNlcSgwLCAyICogcGksIGxlbmd0aC5vdXQgPSAyMDApKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBzaW4oc2VxKDAsIDIgKiBwaSwgbGVuZ3RoLm91dCA9IDIwMCkpKQ0KDQpnZ3Bsb3QoKSArDQogIGdlb21fcGF0aChkYXRhID0gbGluZ2thcmFuLCBhZXMoeCA9IHgsIHkgPSB5KSwgY29sb3IgPSAiZ3JleTYwIikgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJncmV5NzAiKSArDQogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gImdyZXk3MCIpICsNCiAgZ2VvbV9zZWdtZW50KGRhdGEgPSBrb29yZCwgYWVzKHggPSAwLCB5ID0gMCwgeGVuZCA9IFBDMSwgeWVuZCA9IFBDMiksDQogICAgICAgICAgICAgICBhcnJvdyA9IGFycm93KGxlbmd0aCA9IHVuaXQoMC4yLCAiY20iKSksIGNvbG9yID0gInJlZCIpICsNCiAgZ2VvbV90ZXh0KGRhdGEgPSBrb29yZCwgYWVzKHggPSBQQzEgKiAxLjEsIHkgPSBQQzIgKiAxLjEsIGxhYmVsID0gVmFyaWFiZWwpLA0KICAgICAgICAgICAgc2l6ZSA9IDMpICsNCiAgY29vcmRfZml4ZWQoeGxpbSA9IGMoLTEuMiwgMS4yKSwgeWxpbSA9IGMoLTEuMiwgMS4yKSkgKw0KICBsYWJzKHRpdGxlID0gIlZhcmlhYmVsIHBhZGEgUnVhbmcgUEMxLVBDMiIsDQogICAgICAgeCA9IHBhc3RlMCgiUEMxICgiLCB2YXJfZXhwWzFdLCAiJSkiKSwNCiAgICAgICB5ID0gcGFzdGUwKCJQQzIgKCIsIHZhcl9leHBbMl0sICIlKSIpKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCmBgYHtyIGJpcGxvdCwgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9Nn0NCmZha3RvciA8LSAwLjggKiBtYXgoYWJzKHNrb3JfcGNhWywgMToyXSkpIC8gbWF4KGFicyhrb29yZFssIGMoIlBDMSIsICJQQzIiKV0pKQ0KDQpnZ3Bsb3QoKSArDQogIGdlb21fcG9pbnQoZGF0YSA9IHNrb3JfcGNhLCBhZXMoeCA9IFBDMSwgeSA9IFBDMiksDQogICAgICAgICAgICAgY29sb3IgPSAiZ3JleTUwIiwgYWxwaGEgPSAwLjMsIHNpemUgPSAxLjIpICsNCiAgZ2VvbV9zZWdtZW50KGRhdGEgPSBrb29yZCwNCiAgICAgICAgICAgICAgIGFlcyh4ID0gMCwgeSA9IDAsIHhlbmQgPSBQQzEgKiBmYWt0b3IsIHllbmQgPSBQQzIgKiBmYWt0b3IpLA0KICAgICAgICAgICAgICAgYXJyb3cgPSBhcnJvdyhsZW5ndGggPSB1bml0KDAuMiwgImNtIikpLCBjb2xvciA9ICJyZWQiKSArDQogIGdlb21fdGV4dChkYXRhID0ga29vcmQsDQogICAgICAgICAgICBhZXMoeCA9IFBDMSAqIGZha3RvciAqIDEuMSwgeSA9IFBDMiAqIGZha3RvciAqIDEuMSwgbGFiZWwgPSBWYXJpYWJlbCksDQogICAgICAgICAgICBjb2xvciA9ICJyZWQiLCBzaXplID0gMykgKw0KICBsYWJzKHRpdGxlID0gIkJpcGxvdCBQQ0EiLA0KICAgICAgIHggPSBwYXN0ZTAoIlBDMSAoIiwgdmFyX2V4cFsxXSwgIiUpIiksDQogICAgICAgeSA9IHBhc3RlMCgiUEMyICgiLCB2YXJfZXhwWzJdLCAiJSkiKSkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQoqKkludGVycHJldGFzaSBQQ0EuKiogKihJc2kgc2V0ZWxhaCBtZWxpaGF0IHRhYmVsIGxvYWRpbmcgZGFuIGdyYWZpazogdmFyaWFiZWwgYXBhIHlhbmcgbWVtaWxpa2kgbG9hZGluZyB0ZXJiZXNhciBwYWRhIFBDMSwgUEMyLCBkYW4gUEMzLCBsYWx1IGJlcmkgbmFtYSB0aWFwIGtvbXBvbmVuIHNlc3VhaSBtYWtuYW55YS4gQ2F0YXRhbjogdGFuZGEgcG9zaXRpZiBhdGF1IG5lZ2F0aWYgcGFkYSBsb2FkaW5nIGJlcnNpZmF0IGJlYmFzIGRhbiB0aWRhayBtZW5ndWJhaCBpbnRlcnByZXRhc2kuKSoNCg0KIyMgUGVuZW50dWFuIEp1bWxhaCBDbHVzdGVyIE9wdGltYWwNCg0KQ2x1c3RlcmluZyBkaWxha3VrYW4gcGFkYSBza29yIGByIG5fcGNgIGtvbXBvbmVuIHV0YW1hIHBlcnRhbWEuDQoNCmBgYHtyIHBpbGloLWt9DQpqYXJhayA8LSBkaXN0KHNrb3JfcGNhKQ0KDQp0YWJlbF9rIDwtIGRhdGEuZnJhbWUoayA9IDE6MTAsIFdTUyA9IE5BX3JlYWxfLCBTaWxob3VldHRlID0gTkFfcmVhbF8pDQpmb3IgKGkgaW4gMToxMCkgew0KICBzZXQuc2VlZCgxMjMpDQogIGttIDwtIGttZWFucyhza29yX3BjYSwgY2VudGVycyA9IGksIG5zdGFydCA9IDI1KQ0KICB0YWJlbF9rJFdTU1tpXSA8LSByb3VuZChrbSR0b3Qud2l0aGluc3MsIDIpDQogIGlmIChpID4gMSkgew0KICAgIHRhYmVsX2skU2lsaG91ZXR0ZVtpXSA8LSByb3VuZCgNCiAgICAgIG1lYW4oc2lsaG91ZXR0ZShrbSRjbHVzdGVyLCBqYXJhaylbLCAic2lsX3dpZHRoIl0pLCA0KQ0KICB9DQp9DQprYWJsZSh0YWJlbF9rLCBjYXB0aW9uID0gIk5pbGFpIFdTUyBkYW4gcmF0YS1yYXRhIFNpbGhvdWV0dGUgdW50dWsgdGlhcCBqdW1sYWggY2x1c3RlciAoaykiKQ0KYGBgDQoNCmBgYHtyIGVsYm93fQ0KZ2dwbG90KHRhYmVsX2ssIGFlcyh4ID0gaywgeSA9IFdTUykpICsNCiAgZ2VvbV9saW5lKCkgKyBnZW9tX3BvaW50KHNpemUgPSAyKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSAxOjEwKSArDQogIGxhYnModGl0bGUgPSAiRWxib3cgTWV0aG9kIiwgeCA9ICJKdW1sYWggQ2x1c3RlciAoaykiLA0KICAgICAgIHkgPSAiVG90YWwgV2l0aGluIFN1bSBvZiBTcXVhcmVzIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQpgYGB7ciBzaWxob3VldHRlLWt9DQpzaWxfZGF0YSA8LSB0YWJlbF9rW3RhYmVsX2skayA+PSAyLCBdDQpnZ3Bsb3Qoc2lsX2RhdGEsIGFlcyh4ID0gaywgeSA9IFNpbGhvdWV0dGUpKSArDQogIGdlb21fbGluZSgpICsgZ2VvbV9wb2ludChzaXplID0gMikgKw0KICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBzaWxfZGF0YSRrW3doaWNoLm1heChzaWxfZGF0YSRTaWxob3VldHRlKV0sDQogICAgICAgICAgICAgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAiYmx1ZSIpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IDI6MTApICsNCiAgbGFicyh0aXRsZSA9ICJTaWxob3VldHRlIFNjb3JlIiwgeCA9ICJKdW1sYWggQ2x1c3RlciAoaykiLA0KICAgICAgIHkgPSAiUmF0YS1yYXRhIFNpbGhvdWV0dGUiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCmBgYHtyIHRldGFwa2FuLWt9DQojIFRldGFwa2FuIGsgYmVyZGFzYXJrYW4gRWxib3cgZGFuIFNpbGhvdWV0dGUgKGxpaGF0IHRhYmVsIGRpIGF0YXMpLg0Ka19vcHQgPC0gMw0KYGBgDQoNCioqUGVuZW50dWFuIGsuKiogKihJc2kgc2V0ZWxhaCBtZWxpaGF0IGhhc2lsOiBrIGRlbmdhbiBTaWxob3VldHRlIHRlcnRpbmdnaSwgYWRhIGF0YXUgdGlkYWtueWEgc2lrdSBwYWRhIEVsYm93LCBkYW4gYWxhc2FuIG1lbWlsaWggayA9IGByIGtfb3B0YC4pKg0KDQojIyBIYXNpbCBLLU1lYW5zDQoNCmBgYHtyIGttZWFuc30NCnNldC5zZWVkKDEyMykNCmttX2ZpbmFsIDwtIGttZWFucyhza29yX3BjYSwgY2VudGVycyA9IGtfb3B0LCBuc3RhcnQgPSA1MCkNCmtvcGlfYXJhJENsdXN0ZXIgPC0gZmFjdG9yKGttX2ZpbmFsJGNsdXN0ZXIpDQoNCmthYmxlKGFzLmRhdGEuZnJhbWUodGFibGUoQ2x1c3RlciA9IGtvcGlfYXJhJENsdXN0ZXIpKSwNCiAgICAgIGNvbC5uYW1lcyA9IGMoIkNsdXN0ZXIiLCAiSnVtbGFoIE9ic2VydmFzaSIpLA0KICAgICAgY2FwdGlvbiA9ICJVa3VyYW4gdGlhcCBjbHVzdGVyIChLLU1lYW5zKSIpDQpgYGANCg0KUHJvcG9yc2kga2VyYWdhbWFuIGFudGFyLWNsdXN0ZXIgKGJldHdlZW4gU1MgLyB0b3RhbCBTUykgc2ViZXNhciBgciByb3VuZChrbV9maW5hbCRiZXR3ZWVuc3MgLyBrbV9maW5hbCR0b3RzcyAqIDEwMCwgMilgJS4NCg0KYGBge3Igc2lsaG91ZXR0ZS1wbG90LCBmaWcuaGVpZ2h0PTZ9DQpzaWxfa20gPC0gc2lsaG91ZXR0ZShrbV9maW5hbCRjbHVzdGVyLCBqYXJhaykNCnBsb3Qoc2lsX2ttLCBjb2wgPSAyOihrX29wdCArIDEpLCBib3JkZXIgPSBOQSwNCiAgICAgbWFpbiA9ICJTaWxob3VldHRlIFBsb3QgSy1NZWFucyIpDQpgYGANCg0KYGBge3IgY2x1c3Rlci0yZCwgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9NS41fQ0Kc2tvcl9wbG90IDwtIGNiaW5kKHNrb3JfcGNhLCBDbHVzdGVyID0ga29waV9hcmEkQ2x1c3RlcikNCg0KZ2dwbG90KHNrb3JfcGxvdCwgYWVzKHggPSBQQzEsIHkgPSBQQzIsIGNvbG9yID0gQ2x1c3RlcikpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNiwgc2l6ZSA9IDEuOCkgKw0KICBzdGF0X2VsbGlwc2UobGV2ZWwgPSAwLjkwKSArDQogIGxhYnModGl0bGUgPSAiSGFzaWwgSy1NZWFucyBwYWRhIFJ1YW5nIFBDQSAoMkQpIiwNCiAgICAgICB4ID0gcGFzdGUwKCJQQzEgKCIsIHZhcl9leHBbMV0sICIlKSIpLA0KICAgICAgIHkgPSBwYXN0ZTAoIlBDMiAoIiwgdmFyX2V4cFsyXSwgIiUpIikpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KYGBge3IgY2x1c3Rlci1wYzEzLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD01LjV9DQojIFRhbXBpbGFuIHN0YXRpcyBQQzEgdnMgUEMzIChzZWxhbHUgYmlzYSB0YW1waWwgZGkgSFRNTCBtYXVwdW4gUERGKQ0KZ2dwbG90KHNrb3JfcGxvdCwgYWVzKHggPSBQQzEsIHkgPSBQQzMsIGNvbG9yID0gQ2x1c3RlcikpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNiwgc2l6ZSA9IDEuOCkgKw0KICBsYWJzKHRpdGxlID0gIkhhc2lsIEstTWVhbnMgcGFkYSBSdWFuZyBQQ0EgKFBDMSB2cyBQQzMpIiwNCiAgICAgICB4ID0gcGFzdGUwKCJQQzEgKCIsIHZhcl9leHBbMV0sICIlKSIpLA0KICAgICAgIHkgPSBwYXN0ZTAoIlBDMyAoIiwgdmFyX2V4cFszXSwgIiUpIikpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KYGBge3IgY2x1c3Rlci0zZH0NCiMgUGxvdCAzRCBpbnRlcmFrdGlmIGhhbnlhIGJpc2EgdGFtcGlsIHBhZGEga2VsdWFyYW4gSFRNTC4NCiMgVW50dWsgUERGOiBqYWxhbmthbiBjaHVuayBpbmkgZGkgUlN0dWRpbywgYW1iaWwgc2NyZWVuc2hvdCBqZW5kZWxhIFZpZXdlciwNCiMgc2ltcGFuIHNlYmFnYWkgb3V0cHV0L2NsdXN0ZXJfM2QucG5nLCBsYWx1IGtuaXQga2UgUERGLg0KcF8zZCA8LSBwbG90X2x5KHNrb3JfcGxvdCwgeCA9IH5QQzEsIHkgPSB+UEMyLCB6ID0gflBDMywNCiAgICAgICAgICAgICAgICBjb2xvciA9IH5DbHVzdGVyLCB0eXBlID0gInNjYXR0ZXIzZCIsIG1vZGUgPSAibWFya2VycyIsDQogICAgICAgICAgICAgICAgbWFya2VyID0gbGlzdChzaXplID0gMywgb3BhY2l0eSA9IDAuNykpICU+JQ0KICBsYXlvdXQodGl0bGUgPSAiSGFzaWwgSy1NZWFucyBwYWRhIFJ1YW5nIFBDQSAoM0QpIiwNCiAgICAgICAgIHNjZW5lID0gbGlzdCgNCiAgICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gcGFzdGUwKCJQQzEgKCIsIHZhcl9leHBbMV0sICIlKSIpKSwNCiAgICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gcGFzdGUwKCJQQzIgKCIsIHZhcl9leHBbMl0sICIlKSIpKSwNCiAgICAgICAgICAgemF4aXMgPSBsaXN0KHRpdGxlID0gcGFzdGUwKCJQQzMgKCIsIHZhcl9leHBbM10sICIlKSIpKSkpDQoNCmlmIChrbml0cjo6aXNfaHRtbF9vdXRwdXQoKSkgew0KICBwcmludChwXzNkKQ0KfSBlbHNlIGlmIChmaWxlLmV4aXN0cygib3V0cHV0L2NsdXN0ZXJfM2QucG5nIikpIHsNCiAga25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIm91dHB1dC9jbHVzdGVyXzNkLnBuZyIpDQp9IGVsc2Ugew0KICBjYXQoIltUYW1iYWhrYW4gc2NyZWVuc2hvdCBwbG90IDNEIHNlYmFnYWkgb3V0cHV0L2NsdXN0ZXJfM2QucG5nXSIpDQp9DQpgYGANCg0KIyMgUHJvZmlsIENsdXN0ZXINCg0KYGBge3IgcHJvZmlsfQ0KcHJvZmlsIDwtIGtvcGlfYXJhICU+JQ0KICBncm91cF9ieShDbHVzdGVyKSAlPiUNCiAgc3VtbWFyaXNlKG4gPSBuKCksIGFjcm9zcyhhbGxfb2YodmFycyksIH4gcm91bmQobWVhbigueCksIDIpKSkNCmthYmxlKHByb2ZpbCwgY2FwdGlvbiA9ICJSYXRhLXJhdGEgc2tvciBzZW5zb3JpIChkYXRhIGFzbGkpIHRpYXAgY2x1c3RlciIpDQpgYGANCg0KYGBge3IgcHJvZmlsLWJveHBsb3QsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD03fQ0Ka29waV9hcmEgJT4lDQogIHNlbGVjdChDbHVzdGVyLCBhbGxfb2YodmFycykpICU+JQ0KICBwaXZvdF9sb25nZXIoLUNsdXN0ZXIsIG5hbWVzX3RvID0gIlZhcmlhYmVsIiwgdmFsdWVzX3RvID0gIlNrb3IiKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gQ2x1c3RlciwgeSA9IFNrb3IsIGZpbGwgPSBDbHVzdGVyKSkgKw0KICBnZW9tX2JveHBsb3Qob3V0bGllci5zaXplID0gMC44KSArDQogIGZhY2V0X3dyYXAoflZhcmlhYmVsLCBzY2FsZXMgPSAiZnJlZV95IikgKw0KICBsYWJzKHRpdGxlID0gIlByb2ZpbCBTa29yIFNlbnNvcmkgcGVyIENsdXN0ZXIiKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCmBgYA0KDQojIyBQZW1iYW5kaW5nOiBIaWVyYXJjaGljYWwgQ2x1c3RlcmluZyAoV2FyZC5EMikNCg0KYGBge3IgaGllcmFya2ksIGZpZy5oZWlnaHQ9NX0NCmhjIDwtIGhjbHVzdChqYXJhaywgbWV0aG9kID0gIndhcmQuRDIiKQ0KcGxvdChoYywgbGFiZWxzID0gRkFMU0UsIGhhbmcgPSAtMSwNCiAgICAgbWFpbiA9ICJEZW5kcm9ncmFtIEhpZXJhcmNoaWNhbCAoV2FyZC5EMikiLCB4bGFiID0gIiIsIHN1YiA9ICIiKQ0KcmVjdC5oY2x1c3QoaGMsIGsgPSBrX29wdCwgYm9yZGVyID0gMjooa19vcHQgKyAxKSkNCmtvcGlfYXJhJENsdXN0ZXJfSEMgPC0gZmFjdG9yKGN1dHJlZShoYywgayA9IGtfb3B0KSkNCmBgYA0KDQpgYGB7ciBiYW5kaW5na2FufQ0Ka2FibGUoYXMuZGF0YS5mcmFtZS5tYXRyaXgodGFibGUoS01lYW5zID0ga29waV9hcmEkQ2x1c3RlciwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEhpZXJhcmtpID0ga29waV9hcmEkQ2x1c3Rlcl9IQykpLA0KICAgICAgY2FwdGlvbiA9ICJLZXNlc3VhaWFuIGtlYW5nZ290YWFuIGNsdXN0ZXI6IEstTWVhbnMgKGJhcmlzKSB2cyBIaWVyYXJjaGljYWwgKGtvbG9tKSIpDQoNCnNpbF9oYyA8LSBzaWxob3VldHRlKGN1dHJlZShoYywgayA9IGtfb3B0KSwgamFyYWspDQpwZXJiYW5kaW5nYW4gPC0gZGF0YS5mcmFtZSgNCiAgTWV0b2RlICAgICA9IGMoIkstTWVhbnMiLCAiSGllcmFyY2hpY2FsIChXYXJkLkQyKSIpLA0KICBTaWxob3VldHRlID0gcm91bmQoYyhtZWFuKHNpbF9rbVssICJzaWxfd2lkdGgiXSksDQogICAgICAgICAgICAgICAgICAgICAgIG1lYW4oc2lsX2hjWywgInNpbF93aWR0aCJdKSksIDQpDQopDQprYWJsZShwZXJiYW5kaW5nYW4sIGNhcHRpb24gPSAiUGVyYmFuZGluZ2FuIHJhdGEtcmF0YSBTaWxob3VldHRlIGtlZHVhIG1ldG9kZSIpDQpgYGANCg0KYGBge3IgaGllcmFya2ktcGNhLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD01LjV9DQpnZ3Bsb3QoY2JpbmQoc2tvcl9wY2EsIENsdXN0ZXIgPSBrb3BpX2FyYSRDbHVzdGVyX0hDKSwNCiAgICAgICBhZXMoeCA9IFBDMSwgeSA9IFBDMiwgY29sb3IgPSBDbHVzdGVyKSkgKw0KICBnZW9tX3BvaW50KGFscGhhID0gMC42LCBzaXplID0gMS44KSArDQogIGxhYnModGl0bGUgPSAiSGllcmFyY2hpY2FsIChXYXJkLkQyKSBwYWRhIFJ1YW5nIFBDQSIsDQogICAgICAgeCA9IHBhc3RlMCgiUEMxICgiLCB2YXJfZXhwWzFdLCAiJSkiKSwNCiAgICAgICB5ID0gcGFzdGUwKCJQQzIgKCIsIHZhcl9leHBbMl0sICIlKSIpKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCiMgRGlza3VzaQ0KDQoqKkludGVycHJldGFzaSBQQ0EuKiogKihKZWxhc2thbiBtYWtuYSB0aWFwIGtvbXBvbmVuIHV0YW1hIGJlcmRhc2Fya2FuIGxvYWRpbmcsIGJlc2FyIHZhcmlhbnMgeWFuZyBkaWplbGFza2FuLCBkYW4gYWxhc2FuIG1lbWlsaWgganVtbGFoIGtvbXBvbmVuLikqDQoNCioqSW50ZXJwcmV0YXNpIGNsdXN0ZXJpbmcuKiogKihKZWxhc2thbiBrYXJha3RlcmlzdGlrIHRpYXAgY2x1c3RlciBiZXJkYXNhcmthbiB0YWJlbCBwcm9maWwgZGFuIGJveHBsb3QsIGt1YWxpdGFzIHBlbWlzYWhhbiBjbHVzdGVyIGJlcmRhc2Fya2FuIFNpbGhvdWV0dGUsIGRhbiBwZXJiYW5kaW5nYW4gZGVuZ2FuIEhpZXJhcmNoaWNhbCBDbHVzdGVyaW5nLikqDQoNCioqS2V0ZXJiYXRhc2FuLioqICooQ29udG9oOiBiYW55YWsgc2tvciBtZW5kZWthdGkgMTAgcGFkYSBVbmlmb3JtaXR5LCBDbGVhbi5DdXAsIGRhbiBTd2VldG5lc3Mgc2VoaW5nZ2Egc2ViYXJhbm55YSBtaXJpbmc7IHBlbmdhcnVoIHBlbmNpbGFuIHRlcmhhZGFwIEstTWVhbnM7IGhhc2lsIEstTWVhbnMgYmVyZ2FudHVuZyBwYWRhIGluaXNpYWxpc2FzaSBzZWhpbmdnYSBkaXBha2FpIHNldC5zZWVkIGRhbiBuc3RhcnQuKSoNCg0KIyBLZXNpbXB1bGFuDQoNCiooVHVsaXNrYW4gMy00IGthbGltYXQ6IGp1bWxhaCBrb21wb25lbiBkYW4gdmFyaWFucyB5YW5nIGRpamVsYXNrYW4sIG1ha25hIGtvbXBvbmVuIHV0YW1hLCBqdW1sYWggY2x1c3RlciBvcHRpbWFsIGJlc2VydGEgY2lyaSB0aWFwIGNsdXN0ZXIsIGRhbiBrZXNlc3VhaWFuIEstTWVhbnMgZGVuZ2FuIEhpZXJhcmNoaWNhbC4pKg0KDQojIExhbXBpcmFuOiBLb2RlIEFuYWxpc2lzDQoNCktvZGUgYW5hbGlzaXMgbGVuZ2thcCAoUikgZGFwYXQgZGlha3NlcyBtZWxhbHVpIHRhdXRhbiBHb29nbGUgRHJpdmUgYmVyaWt1dDogKipbdGVtcGVsIHRhdXRhbiBHb29nbGUgRHJpdmUgZGkgc2luaV0qKg0KDQpgYGB7ciBzZXNpLCBlY2hvPVRSVUV9DQpzZXNzaW9uSW5mbygpDQpgYGANCg==