1. load libraries
2. Read obj
# Load your object
All_sample_Merged <- readRDS("../All_samples_Merged_with_STCAT_and_renamed_FINAL.rds")
# Check unique cell_line values
unique(All_sample_Merged$cell_line)
[1] L1 L2 L3 L4 L5 L6 L7 CD4Tcells_lab
[9] CD4Tcells_10x
Levels: L1 L2 L3 L4 L5 L6 L7 CD4Tcells_lab CD4Tcells_10x
# [Expect something like: "L1","L2","L3","L4","L5","L6","L7","CD4Tcells_lab","CD4Tcells_10x"]
# Subset for malignant cell lines only
malignant_lines <- c("L1","L2","L3","L4","L5","L6","L7")
Malignant_cells <- subset(All_sample_Merged, subset = cell_line %in% malignant_lines)
# Confirm
table(Malignant_cells$cell_line)
L1 L2 L3 L4 L5 L6 L7
5825 5935 6428 6006 6022 5148 5331
3. Check all coulumns
# Adjust these if your object or column names differ
seurat_obj <- Malignant_cells
cluster_col <- "seurat_clusters"
azimuth_col <- "predicted.celltype.l2" # as you specified
stcat_col <- "Prediction" # STCAT automatic annotation column
# ADT assay common names we'll try to detect
adt_assay_candidates <- c("ADT", "AntibodyCapture", "Antibody", "CITE", "CITEseq")
cat("Seurat object present in workspace? ", exists("All_sample_Merged"), "\n")
Seurat object present in workspace? TRUE
4. Detect ADT assay & list ADT features
# detect ADT assay
assays_present <- Assays(seurat_obj)
adt_assay <- intersect(adt_assay_candidates, assays_present)
if (length(adt_assay) == 0) {
adt_assay <- NA
warning("No common ADT assay name found. Available assays: ", paste(assays_present, collapse = ", "))
} else {
adt_assay <- adt_assay[1]
}
cat("ADT assay detected: ", adt_assay, "\n")
ADT assay detected: ADT
if (!is.na(adt_assay)) {
# list features
adt_features <- rownames(GetAssayData(seurat_obj, assay = adt_assay, slot = "counts"))
cat("Number of ADT features: ", length(adt_features), "\n")
print(adt_features)
} else {
adt_features <- character(0)
}
Number of ADT features: 28
[1] "CD274" "CD30" "CD40" "CD3" "CD45RA" "CD7" "CCR4" "CD4" "CD25" "CD45RO" "PD1" "CD44"
[13] "CD5" "CXCR3" "CCR6" "CD62L" "CCR7" "CD95" "TCRab" "CXCR4" "CD2" "CD28" "CD127" "CD45"
[25] "CD26" "CCR10" "CCR8" "CD19"
5. Compare your ADT features against a canonical T-cell ADT
list
canonical_adt <- c("CD3", "CD4", "CD8", "CD45RA", "CD45RO", "CD62L", "CCR7",
"CD27", "CD28", "CD25", "CD127", "PD1", "PDCD1", "CTLA4", "CD69",
"HLA-DR", "CD38", "ICOS", "CXCR5", "CCR4", "CCR6", "CD45",
"CD7", "CD2", "CD26", "CD5", "TIM3", "LAG3", "CD16", "CD19")
present_in_panel <- intersect(canonical_adt, adt_features)
missing_from_panel <- setdiff(canonical_adt, adt_features)
cat("Canonical ADT markers present in your panel (intersection):\n")
Canonical ADT markers present in your panel (intersection):
print(present_in_panel)
[1] "CD3" "CD4" "CD45RA" "CD45RO" "CD62L" "CCR7" "CD28" "CD25" "CD127" "PD1" "CCR4" "CCR6"
[13] "CD45" "CD7" "CD2" "CD26" "CD5" "CD19"
cat("\nCanonical ADT markers NOT present / differently named: (inspect adt features manually)\n")
Canonical ADT markers NOT present / differently named: (inspect adt features manually)
print(missing_from_panel)
[1] "CD8" "CD27" "PDCD1" "CTLA4" "CD69" "HLA-DR" "CD38" "ICOS" "CXCR5" "TIM3" "LAG3" "CD16"
Interpretation notes
- If
CD45RA
/ CD45RO
are present you can
separate naïve vs memory by ADT. CD25
and
CD127
help to define Tregs.
- Panel naming can vary (e.g.
PDCD1
vs PD-1
,
TIM3
vs Tim-3
) — if names differ, map them
manually.
6. Validate automatic labels (Azimuth & STCAT) using RNA marker
genes (cluster-level)
# Canonical RNA signatures for CD4+ T subsets (expand as needed)
signatures <- list(
Tn = c("CCR7","SELL","LEF1","TCF7"),
Tcm = c("CCR7","SELL","IL7R","CD27"),
Tem = c("GZMK","PRF1","IFNG","KLRB1"),
Treg = c("FOXP3","IL2RA","CTLA4","IKZF2"),
Tex = c("PDCD1","CTLA4","LAG3","TOX","HAVCR2"),
Temra = c("GZMB","PRF1","KLRG1","CX3CR1","IFNG"),
Th1 = c("TBX21","IFNG","CXCR3"),
Th2 = c("GATA3","IL4","IL5","IL13"),
Th17 = c("RORC","IL17A","IL17F","CCR6"),
Tfh = c("CXCR5","BCL6","PDCD1")
)
# flatten and check presence
all_sig_genes <- unique(unlist(signatures))
genes_present <- intersect(all_sig_genes, rownames(seurat_obj))
cat("Signature genes present in your RNA data:", length(genes_present), "of", length(all_sig_genes), "\n")
Signature genes present in your RNA data: 33 of 33
# Detect Azimuth/STCAT columns presence
cat('Azimuth column present?:', azimuth_col %in% colnames(seurat_obj@meta.data), '\n')
Azimuth column present?: TRUE
cat('STCAT Prediction column present?:', stcat_col %in% colnames(seurat_obj@meta.data), '\n')
STCAT Prediction column present?: TRUE
# compute average expression per cluster for signature genes
features_to_use <- intersect(all_sig_genes, rownames(seurat_obj))
if (length(features_to_use) == 0) stop("No canonical signature genes found in the RNA assay. Check gene names / expression matrix.")
avg_rna_by_cluster <- AverageExpression(seurat_obj, assays = "RNA", features = features_to_use,
slot = "data", group.by = cluster_col)$RNA
# z-score rows for heatmap visualization
zmat <- t(scale(t(as.matrix(avg_rna_by_cluster) + 1e-6)))
pheatmap(zmat, cluster_rows = TRUE, cluster_cols = TRUE, main = "Cluster x RNA marker (z-scaled average)")

# DotPlot of canonical RNA markers by cluster
DotPlot(seurat_obj, features = features_to_use, group.by = cluster_col) + RotatedAxis()+ scale_color_gradient2(low = "lightyellow", mid = "red", high = "firebrick", midpoint = 1) + ggtitle("RNA canonical markers by cluster")

Interpretation notes
- Clusters high for
FOXP3
+ IL2RA
likely
represent Tregs.
CCR7
+ SELL
mark Naïve/Tcm; verify with
other genes (TCF7/LEF1).
GZMK
/PRF1
/IFNG
suggest
effector/Tem-like cells.
- Use DotPlots and the heatmap to map clusters to candidate
labels.
7. ADT validation: ensure normalization and compute cluster-level
ADT averages
# Check if ADT assay exists
adt_assay <- intersect(adt_assay_candidates, Assays(seurat_obj))
if (length(adt_assay) == 0) {
message("No ADT assay detected; skipping ADT validation.")
} else {
adt_assay <- adt_assay[1] # use the first match
DefaultAssay(seurat_obj) <- adt_assay
# Check slots
adt_slots <- slotNames(seurat_obj[[adt_assay]])
cat("ADT assay slots:", paste(adt_slots, collapse = ","), "\n")
# Extract ADT data
adt_data <- GetAssayData(seurat_obj, assay = adt_assay, slot = "data")
# Replace NA/Inf with 0
adt_data[is.na(adt_data)] <- 0
adt_data[is.infinite(adt_data)] <- 0
seurat_obj[[adt_assay]]@data <- adt_data
# CLR normalization if data looks unnormalized (optional check)
if (max(adt_data, na.rm = TRUE) > 50 || min(adt_data, na.rm = TRUE) < -10) {
cat("Running CLR normalization and scaling ADT.\n")
seurat_obj <- NormalizeData(seurat_obj, assay = adt_assay, normalization.method = "CLR")
seurat_obj <- ScaleData(seurat_obj, assay = adt_assay)
} else {
cat("ADT assay assumed normalized (CLR/modal).\n")
}
# Compute cluster-level average ADT values
avg_adt_by_cluster <- AverageExpression(
seurat_obj,
assays = adt_assay,
features = intersect(canonical_adt, adt_features),
slot = "data",
group.by = cluster_col
)[[adt_assay]]
# z-score across clusters and plot heatmap
z_adt <- t(scale(t(as.matrix(avg_adt_by_cluster) + 1e-6)))
pheatmap(z_adt, main = "ADT markers (z-scored avg per cluster)")
# Violin plots for key ADTs (subset to avoid overcrowding)
adt_to_plot <- intersect(c("CD4","CD8","CD45RA","CD45RO","CD25","CD127","PD1","CXCR5"), adt_features)
if (length(adt_to_plot) > 0) {
print(
VlnPlot(
seurat_obj,
features = adt_to_plot,
assay = adt_assay,
slot = "data",
group.by = cluster_col,
pt.size = 0.05,
ncol = 3
)
)
}
# Reset default assay
DefaultAssay(seurat_obj) <- "RNA"
}
ADT assay slots: counts,data,scale.data,assay.orig,var.features,meta.features,misc,key
ADT assay assumed normalized (CLR/modal).


Interpretation notes
- ADT
CD4
high + CD8
low confirms CD4 T
identity per cluster (useful QC).
CD45RA
vs CD45RO
separates Naïve vs
Memory.
CD25
high and CD127
low (by ADT) supports
Treg calls.
- PD-1/ICOS/LAG3 ADT elevation suggests activation/exhaustion
phenotypes.
8. Module scoring (per-cell) for signatures and cluster-level
summarization
# Set SCT assay
DefaultAssay(seurat_obj) <- "SCT"
# Ensure signature genes exist in the dataset
sigs_present <- lapply(signatures, function(g) intersect(g, rownames(seurat_obj)))
sigs_present <- sigs_present[sapply(sigs_present, length) > 0]
if (length(sigs_present) == 0) {
stop("No signature genes present in SCT assay for module scoring. Adjust gene names.")
}
# Keep signatures with >=3 genes and non-zero variance
sigs_present <- lapply(sigs_present, function(g) {
if(length(g) < 3) return(NULL)
expr_mat <- GetAssayData(seurat_obj, assay = "SCT", slot = "data")[g, , drop = FALSE]
if(all(rowSums(expr_mat) == 0)) return(NULL)
return(g)
})
sigs_present <- sigs_present[sapply(sigs_present, length) > 0]
if(length(sigs_present) == 0) stop("No valid signatures with enough variance for module scoring.")
# Add module scores safely
seurat_obj <- AddModuleScore(seurat_obj, features = sigs_present, name = "mod", ctrl = 5)
# Rename module columns to readable names
added_cols <- paste0("mod", seq_along(sigs_present))
new_colnames <- paste0("score_", names(sigs_present))
for (i in seq_along(added_cols)) {
old <- added_cols[i]
new <- new_colnames[i]
if(old %in% colnames(seurat_obj@meta.data)) {
colnames(seurat_obj@meta.data)[which(colnames(seurat_obj@meta.data) == old)] <- new
}
}
# Prepare module scores for UMAP plotting (subset first 6)
plot_scores <- new_colnames[1:min(6, length(new_colnames))]
# Filter out constant module scores to avoid FeaturePlot errors
plot_scores <- plot_scores[sapply(plot_scores, function(f) length(unique(seurat_obj[[f]])) > 1)]
if(length(plot_scores) > 0) {
print(
FeaturePlot(
seurat_obj,
features = plot_scores,
reduction = "umap",
ncol = 3,
pt.size = 0.05,
cols = c("lightgrey", "blue")
)
)
} else {
message("No variable module scores to plot with FeaturePlot().")
}
# Violin plots per cluster for all module scores
vln_scores <- new_colnames[sapply(new_colnames, function(f) length(unique(seurat_obj[[f]])) > 1)]
if(length(vln_scores) > 0) {
print(
VlnPlot(
seurat_obj,
features = vln_scores,
group.by = cluster_col,
pt.size = 0.05,
ncol = 3
)
)
} else {
message("No variable module scores to plot with VlnPlot().")
}
# Cluster-level mean module scores
cluster_scores_df <- seurat_obj@meta.data %>%
as.data.frame() %>%
group_by(!!sym(cluster_col)) %>%
summarise(across(all_of(new_colnames), mean, .names = "{.col}"))
print(cluster_scores_df)
# Convert to matrix for heatmap
mat <- as.data.frame(cluster_scores_df)
rownames(mat) <- mat[[cluster_col]]
mat[[cluster_col]] <- NULL
# Optional: z-score across clusters for readability
mat_z <- t(scale(t(as.matrix(mat) + 1e-6)))
pheatmap(mat_z, main = "Mean module scores per cluster", cluster_rows = TRUE, cluster_cols = TRUE)

Interpretation notes
- The highest module score per cluster provides a
gene-expression-driven candidate label (e.g., cluster with highest
score_Treg
suggests a regulatory phenotype).
- Module scores are robust when signatures contain several genes;
expand gene lists if a signature is weak.
9. Consensus labelling (weighted voting across Azimuth, STCAT
Prediction, module scores, and ADT rules)
library(tibble)
# Helper: modal label in a cluster
get_modal <- function(vec) {
if (all(is.na(vec))) return(NA)
ux <- sort(table(vec), decreasing = TRUE)
names(ux)[1]
}
meta <- seurat_obj@meta.data
clusters <- sort(unique(meta[[cluster_col]]))
# modal Azimuth and STCAT per cluster
modal_azimuth <- if (azimuth_col %in% colnames(meta)) {
meta %>% group_by(!!sym(cluster_col)) %>% summarise(mod = get_modal(!!sym(azimuth_col))) %>% deframe()
} else {
NULL
}
modal_stcat <- if (stcat_col %in% colnames(meta)) {
meta %>% group_by(!!sym(cluster_col)) %>% summarise(mod = get_modal(!!sym(stcat_col))) %>% deframe()
} else {
NULL
}
# top module signature per cluster
mean_sig_by_cluster <- seurat_obj@meta.data %>% as.data.frame() %>%
group_by(!!sym(cluster_col)) %>%
summarise(across(starts_with('score_'), mean))
# tidy and get top signature
mean_long <- mean_sig_by_cluster %>% pivot_longer(-all_of(cluster_col), names_to = 'sig', values_to = 'val')
mean_long$sig <- sub('^score_', '', mean_long$sig)
top_sig <- mean_long %>% group_by(!!sym(cluster_col)) %>% slice_max(order_by = val, n = 1) %>%
select(all_of(cluster_col), sig) %>% deframe()
# ADT votes per cluster (simple heuristic on z-scored cluster means)
adt_votes <- NULL
if (exists('avg_adt_by_cluster')) {
adt_z <- t(scale(t(as.matrix(avg_adt_by_cluster) + 1e-6)))
adt_votes <- apply(adt_z, 1, function(r) {
# Treg: CD25 high & CD127 low
if (!is.na(r['CD25']) && !is.na(r['CD127']) && (r['CD25'] > 0.5) && (r['CD127'] < 0)) return('Treg')
# Naive: CD45RA high
if (!is.na(r['CD45RA']) && (r['CD45RA'] > 0.5)) return('Naive')
# Memory: CD45RO high
if (!is.na(r['CD45RO']) && (r['CD45RO'] > 0.5)) return('Memory')
# CD4 vs CD8 confirmation
if (!is.na(r['CD4']) && !is.na(r['CD8']) && (r['CD4'] - r['CD8'] > 0.3)) return('CD4')
return(NA)
})
}
# weights (tunable) --- you can adjust these; here we give equal weight
weights <- list(azimuth = 0.23, stcat = 0.15, module = 0.25, adt = 0.25)
# Voting per cluster
consensus <- setNames(rep(NA, length(clusters)), clusters)
for (cl in clusters) {
votes <- list()
if (!is.null(modal_azimuth)) {
lbl <- modal_azimuth[as.character(cl)]
if (!is.na(lbl)) votes[[lbl]] <- (votes[[lbl]] %||% 0) + weights$azimuth
}
if (!is.null(modal_stcat)) {
lbl <- modal_stcat[as.character(cl)]
if (!is.na(lbl)) votes[[lbl]] <- (votes[[lbl]] %||% 0) + weights$stcat
}
# module
msig <- top_sig[as.character(cl)]
if (!is.na(msig)) votes[[msig]] <- (votes[[msig]] %||% 0) + weights$module
# adt
if (!is.null(adt_votes)) {
lbl <- adt_votes[as.character(cl)]
if (!is.na(lbl)) votes[[lbl]] <- (votes[[lbl]] %||% 0) + weights$adt
}
if (length(votes) == 0) {
consensus[as.character(cl)] <- NA
} else {
top_score <- max(unlist(votes))
top_labels <- names(votes)[unlist(votes) == top_score]
if (length(top_labels) == 1) {
consensus[as.character(cl)] <- top_labels
} else {
# tie-break: prefer Azimuth -> STCAT -> module -> ADT
chosen <- NA
if (!is.null(modal_azimuth) && modal_azimuth[as.character(cl)] %in% top_labels) chosen <- modal_azimuth[as.character(cl)]
else if (!is.null(modal_stcat) && modal_stcat[as.character(cl)] %in% top_labels) chosen <- modal_stcat[as.character(cl)]
else if (!is.na(msig) && msig %in% top_labels) chosen <- msig
else chosen <- top_labels[1]
consensus[as.character(cl)] <- chosen
}
}
}
# compile summary table
consensus_df <- data.frame(
cluster = names(consensus),
consensus_label = unname(consensus),
azimuth = if (!is.null(modal_azimuth)) modal_azimuth[names(consensus)] else NA,
stcat = if (!is.null(modal_stcat)) modal_stcat[names(consensus)] else NA,
top_module = top_sig[names(consensus)],
adt_vote = if (!is.null(adt_votes)) adt_votes[names(consensus)] else NA,
stringsAsFactors = FALSE
)
print(consensus_df)
# Extract per-cell cluster IDs
clusters <- as.character(seurat_obj[[cluster_col]][,1])
# Map cluster IDs to consensus labels
consensus_per_cell <- consensus[match(clusters, names(consensus))]
# Make sure the vector has the same names as the cells in the Seurat object
names(consensus_per_cell) <- colnames(seurat_obj)
# Add to metadata
seurat_obj$consensus_label <- consensus_per_cell
# Check
table(seurat_obj$consensus_label, clusters, useNA = "ifany")
clusters
0 1 10 11 12 13 2 3 4 5 6 7 8 9
Tcm 0 0 0 0 0 686 0 0 0 0 0 3408 0 0
Th1 0 5256 18 0 0 0 0 0 0 0 0 0 0 0
Th2 0 0 0 1658 0 0 0 0 0 0 3536 0 0 0
Tn 0 0 0 0 0 0 0 0 0 2986 0 0 0 3238
Treg 6788 0 0 0 1035 0 4663 1 4086 0 0 0 3336 0
# Plot on UMAP
DimPlot(seurat_obj, group.by = "consensus_label", label = TRUE, repel = TRUE) + NoLegend()

DimPlot(seurat_obj, group.by = "seurat_clusters", label = TRUE, repel = TRUE) + NoLegend()

# UMAP colored by consensus
if ("umap" %in% names(seurat_obj@reductions)) {
print(DimPlot(seurat_obj, group.by = 'consensus_label', label = TRUE, repel = TRUE) + ggtitle('Consensus labels (cluster-level)'))
} else {
message('No UMAP reduction found — skip UMAP plotting')
}

Interpretation notes
consensus_df
summarizes the weighted votes for each
cluster. Clusters with NA
consensus are ambiguous — inspect
them manually.
- You may tune
weights
depending on which annotation you
trust more. For example, if Azimuth mapping used your exact reference
and looked high-confidence, increase weights$azimuth
to
0.35 and proportionally reduce others.
10. Inspect ambiguous clusters (manual rescue)
ambiguous <- consensus_df %>% filter(is.na(consensus_label) | (azimuth != consensus_label & stcat != consensus_label))
print(ambiguous)
if (nrow(ambiguous) > 0) {
for (cl in ambiguous$cluster) {
cat("\n---\nInspecting cluster", cl, "\n")
# Extract cells by seurat_clusters
cells <- WhichCells(seurat_obj, expression = seurat_obj[[cluster_col]] == cl)
cat("Number of cells in cluster:", length(cells), "\n")
if (length(cells) == 0) {
cat("⚠️ No cells found for cluster", cl, "\n")
next
}
# Temporarily switch Idents to seurat_clusters
Idents(seurat_obj) <- seurat_obj[[cluster_col]][,1]
# Top RNA markers
markers <- FindMarkers(
seurat_obj,
ident.1 = cl,
logfc.threshold = 0.25
)
cat("Top RNA markers (head):\n")
print(head(markers, 20))
# ADT summary if available
if (exists("avg_adt_by_cluster")) {
if (cl %in% rownames(avg_adt_by_cluster)) {
cat("ADT averages for this cluster:\n")
print(avg_adt_by_cluster[as.character(cl), , drop = FALSE])
} else {
cat("No ADT summary available for cluster", cl, "\n")
}
}
}
} else {
cat("No ambiguous clusters found by the chosen rules.\n")
}
---
Inspecting cluster 0
Error in `FetchData()`:
! None of the requested variables were found:
Run `]8;;x-r-run:rlang::last_trace()rlang::last_trace()]8;;` to see where the error occurred.
Interpretation & manual curation tips
- For ambiguous clusters, inspect the top RNA markers (from
FindMarkers
) and ADT averages side-by-side.
- Consider re-clustering the ambiguous cluster cells at higher
resolution or performing sub-clustering, because clusters sometimes mix
states (e.g. activated Tcm + early effector).
- You can also assign per-cell consensus (using the same voting rules)
to capture mixed clusters — this workflow uses cluster-level consensus
to be conservative.
11. Reporting recommendations (Methods text snippet)
LS0tCnRpdGxlOiAiTWFudWFsIENsdXN0ZXIgQW5ub3RhdGlvbiBvZiBNYWxpZ25hbnQgQ2VsbCBMaW5lcyIKYXV0aG9yOiBOYXNpciBNYWhtb29kIEFiYmFzaQpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiCm91dHB1dDoKICAjcm1kZm9ybWF0czo6cmVhZHRoZWRvd24KICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIHRvY19jb2xsYXBzZWQ6IHRydWUKLS0tCgojIDEuIGxvYWQgbGlicmFyaWVzCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQpsaWJyYXJ5KFNldXJhdCkKbGlicmFyeShkcGx5cikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHBoZWF0bWFwKQpsaWJyYXJ5KHRpZHlyKQoKYGBgCgoKIyAyLiBSZWFkIG9iagpgYGB7ciBvYmp9CgojIExvYWQgeW91ciBvYmplY3QKQWxsX3NhbXBsZV9NZXJnZWQgPC0gcmVhZFJEUygiLi4vQWxsX3NhbXBsZXNfTWVyZ2VkX3dpdGhfU1RDQVRfYW5kX3JlbmFtZWRfRklOQUwucmRzIikKCgojIENoZWNrIHVuaXF1ZSBjZWxsX2xpbmUgdmFsdWVzCnVuaXF1ZShBbGxfc2FtcGxlX01lcmdlZCRjZWxsX2xpbmUpCiMgW0V4cGVjdCBzb21ldGhpbmcgbGlrZTogIkwxIiwiTDIiLCJMMyIsIkw0IiwiTDUiLCJMNiIsIkw3IiwiQ0Q0VGNlbGxzX2xhYiIsIkNENFRjZWxsc18xMHgiXQoKIyBTdWJzZXQgZm9yIG1hbGlnbmFudCBjZWxsIGxpbmVzIG9ubHkKbWFsaWduYW50X2xpbmVzIDwtIGMoIkwxIiwiTDIiLCJMMyIsIkw0IiwiTDUiLCJMNiIsIkw3IikKTWFsaWduYW50X2NlbGxzIDwtIHN1YnNldChBbGxfc2FtcGxlX01lcmdlZCwgc3Vic2V0ID0gY2VsbF9saW5lICVpbiUgbWFsaWduYW50X2xpbmVzKQoKIyBDb25maXJtCnRhYmxlKE1hbGlnbmFudF9jZWxscyRjZWxsX2xpbmUpCmBgYAoKIyAzLiBDaGVjayBhbGwgY291bHVtbnMKYGBge3IgY2hlY2ssIGVycm9yPVRSVUV9CgojIEFkanVzdCB0aGVzZSBpZiB5b3VyIG9iamVjdCBvciBjb2x1bW4gbmFtZXMgZGlmZmVyCnNldXJhdF9vYmogPC0gTWFsaWduYW50X2NlbGxzCmNsdXN0ZXJfY29sIDwtICJzZXVyYXRfY2x1c3RlcnMiCmF6aW11dGhfY29sIDwtICJwcmVkaWN0ZWQuY2VsbHR5cGUubDIiICAjIGFzIHlvdSBzcGVjaWZpZWQKc3RjYXRfY29sIDwtICJQcmVkaWN0aW9uIiAgICAgICAgICAgICAgICAgIyBTVENBVCBhdXRvbWF0aWMgYW5ub3RhdGlvbiBjb2x1bW4KCiMgQURUIGFzc2F5IGNvbW1vbiBuYW1lcyB3ZSdsbCB0cnkgdG8gZGV0ZWN0CmFkdF9hc3NheV9jYW5kaWRhdGVzIDwtIGMoIkFEVCIsICJBbnRpYm9keUNhcHR1cmUiLCAiQW50aWJvZHkiLCAiQ0lURSIsICJDSVRFc2VxIikKCmNhdCgiU2V1cmF0IG9iamVjdCBwcmVzZW50IGluIHdvcmtzcGFjZT8gIiwgZXhpc3RzKCJBbGxfc2FtcGxlX01lcmdlZCIpLCAiXG4iKQoKYGBgCgojIDQuIERldGVjdCBBRFQgYXNzYXkgJiBsaXN0IEFEVCBmZWF0dXJlcwpgYGB7ciBkZXRlY3QtYWR0fQojIGRldGVjdCBBRFQgYXNzYXkKYXNzYXlzX3ByZXNlbnQgPC0gQXNzYXlzKHNldXJhdF9vYmopCmFkdF9hc3NheSA8LSBpbnRlcnNlY3QoYWR0X2Fzc2F5X2NhbmRpZGF0ZXMsIGFzc2F5c19wcmVzZW50KQppZiAobGVuZ3RoKGFkdF9hc3NheSkgPT0gMCkgewogIGFkdF9hc3NheSA8LSBOQQogIHdhcm5pbmcoIk5vIGNvbW1vbiBBRFQgYXNzYXkgbmFtZSBmb3VuZC4gQXZhaWxhYmxlIGFzc2F5czogIiwgcGFzdGUoYXNzYXlzX3ByZXNlbnQsIGNvbGxhcHNlID0gIiwgIikpCn0gZWxzZSB7CiAgYWR0X2Fzc2F5IDwtIGFkdF9hc3NheVsxXQp9CmNhdCgiQURUIGFzc2F5IGRldGVjdGVkOiAiLCBhZHRfYXNzYXksICJcbiIpCgppZiAoIWlzLm5hKGFkdF9hc3NheSkpIHsKICAjIGxpc3QgZmVhdHVyZXMKICBhZHRfZmVhdHVyZXMgPC0gcm93bmFtZXMoR2V0QXNzYXlEYXRhKHNldXJhdF9vYmosIGFzc2F5ID0gYWR0X2Fzc2F5LCBzbG90ID0gImNvdW50cyIpKQogIGNhdCgiTnVtYmVyIG9mIEFEVCBmZWF0dXJlczogIiwgbGVuZ3RoKGFkdF9mZWF0dXJlcyksICJcbiIpCiAgcHJpbnQoYWR0X2ZlYXR1cmVzKQp9IGVsc2UgewogIGFkdF9mZWF0dXJlcyA8LSBjaGFyYWN0ZXIoMCkKfQpgYGAKCgoKIyA1LiBDb21wYXJlIHlvdXIgQURUIGZlYXR1cmVzIGFnYWluc3QgYSBjYW5vbmljYWwgVC1jZWxsIEFEVCBsaXN0CmBgYHtyIGNhbm9uaWNhbC1hZHR9CmNhbm9uaWNhbF9hZHQgPC0gYygiQ0QzIiwgIkNENCIsICJDRDgiLCAiQ0Q0NVJBIiwgIkNENDVSTyIsICJDRDYyTCIsICJDQ1I3IiwKICAgICAgICAgICAgICAgICAgICJDRDI3IiwgIkNEMjgiLCAiQ0QyNSIsICJDRDEyNyIsICJQRDEiLCAiUERDRDEiLCAiQ1RMQTQiLCAiQ0Q2OSIsCiAgICAgICAgICAgICAgICAgICAiSExBLURSIiwgIkNEMzgiLCAiSUNPUyIsICJDWENSNSIsICJDQ1I0IiwgIkNDUjYiLCAiQ0Q0NSIsCiAgICAgICAgICAgICAgICAgICAiQ0Q3IiwgIkNEMiIsICJDRDI2IiwgIkNENSIsICJUSU0zIiwgIkxBRzMiLCAiQ0QxNiIsICJDRDE5IikKCnByZXNlbnRfaW5fcGFuZWwgPC0gaW50ZXJzZWN0KGNhbm9uaWNhbF9hZHQsIGFkdF9mZWF0dXJlcykKbWlzc2luZ19mcm9tX3BhbmVsIDwtIHNldGRpZmYoY2Fub25pY2FsX2FkdCwgYWR0X2ZlYXR1cmVzKQoKY2F0KCJDYW5vbmljYWwgQURUIG1hcmtlcnMgcHJlc2VudCBpbiB5b3VyIHBhbmVsIChpbnRlcnNlY3Rpb24pOlxuIikKcHJpbnQocHJlc2VudF9pbl9wYW5lbCkKCmNhdCgiXG5DYW5vbmljYWwgQURUIG1hcmtlcnMgTk9UIHByZXNlbnQgLyBkaWZmZXJlbnRseSBuYW1lZDogKGluc3BlY3QgYWR0IGZlYXR1cmVzIG1hbnVhbGx5KVxuIikKcHJpbnQobWlzc2luZ19mcm9tX3BhbmVsKQpgYGAKCiMjIyBJbnRlcnByZXRhdGlvbiBub3RlcwoKLSBJZiBgQ0Q0NVJBYCAvIGBDRDQ1Uk9gIGFyZSBwcmVzZW50IHlvdSBjYW4gc2VwYXJhdGUgbmHDr3ZlIHZzIG1lbW9yeSBieSBBRFQuIGBDRDI1YCBhbmQgYENEMTI3YCBoZWxwIHRvIGRlZmluZSBUcmVncy4KLSBQYW5lbCBuYW1pbmcgY2FuIHZhcnkgKGUuZy4gYFBEQ0QxYCB2cyBgUEQtMWAsIGBUSU0zYCB2cyBgVGltLTNgKSDigJQgaWYgbmFtZXMgZGlmZmVyLCBtYXAgdGhlbSBtYW51YWxseS4KCgojIDYuIFZhbGlkYXRlIGF1dG9tYXRpYyBsYWJlbHMgKEF6aW11dGggJiBTVENBVCkgdXNpbmcgUk5BIG1hcmtlciBnZW5lcyAoY2x1c3Rlci1sZXZlbCkKYGBge3IgZGVmaW5lLXNpZ25hdHVyZXMsIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTEyfQojIENhbm9uaWNhbCBSTkEgc2lnbmF0dXJlcyBmb3IgQ0Q0KyBUIHN1YnNldHMgKGV4cGFuZCBhcyBuZWVkZWQpCnNpZ25hdHVyZXMgPC0gbGlzdCgKICBUbiA9IGMoIkNDUjciLCJTRUxMIiwiTEVGMSIsIlRDRjciKSwKICBUY20gPSBjKCJDQ1I3IiwiU0VMTCIsIklMN1IiLCJDRDI3IiksCiAgVGVtID0gYygiR1pNSyIsIlBSRjEiLCJJRk5HIiwiS0xSQjEiKSwKICBUcmVnID0gYygiRk9YUDMiLCJJTDJSQSIsIkNUTEE0IiwiSUtaRjIiKSwKICBUZXggPSBjKCJQRENEMSIsIkNUTEE0IiwiTEFHMyIsIlRPWCIsIkhBVkNSMiIpLCAgICAgCiAgVGVtcmEgPSBjKCJHWk1CIiwiUFJGMSIsIktMUkcxIiwiQ1gzQ1IxIiwiSUZORyIpLCAgICAKICBUaDEgPSBjKCJUQlgyMSIsIklGTkciLCJDWENSMyIpLAogIFRoMiA9IGMoIkdBVEEzIiwiSUw0IiwiSUw1IiwiSUwxMyIpLAogIFRoMTcgPSBjKCJST1JDIiwiSUwxN0EiLCJJTDE3RiIsIkNDUjYiKSwKICBUZmggPSBjKCJDWENSNSIsIkJDTDYiLCJQRENEMSIpCiAgCikKCiMgZmxhdHRlbiBhbmQgY2hlY2sgcHJlc2VuY2UKYWxsX3NpZ19nZW5lcyA8LSB1bmlxdWUodW5saXN0KHNpZ25hdHVyZXMpKQpnZW5lc19wcmVzZW50IDwtIGludGVyc2VjdChhbGxfc2lnX2dlbmVzLCByb3duYW1lcyhzZXVyYXRfb2JqKSkKY2F0KCJTaWduYXR1cmUgZ2VuZXMgcHJlc2VudCBpbiB5b3VyIFJOQSBkYXRhOiIsIGxlbmd0aChnZW5lc19wcmVzZW50KSwgIm9mIiwgbGVuZ3RoKGFsbF9zaWdfZ2VuZXMpLCAiXG4iKQoKIyBEZXRlY3QgQXppbXV0aC9TVENBVCBjb2x1bW5zIHByZXNlbmNlCmNhdCgnQXppbXV0aCBjb2x1bW4gcHJlc2VudD86JywgYXppbXV0aF9jb2wgJWluJSBjb2xuYW1lcyhzZXVyYXRfb2JqQG1ldGEuZGF0YSksICdcbicpCmNhdCgnU1RDQVQgUHJlZGljdGlvbiBjb2x1bW4gcHJlc2VudD86Jywgc3RjYXRfY29sICVpbiUgY29sbmFtZXMoc2V1cmF0X29iakBtZXRhLmRhdGEpLCAnXG4nKQpgYGAKCmBgYHtyIGF2Zy1ybmEtaGVhdG1hcCwgZmlnLmhlaWdodD03LCBmaWcud2lkdGg9OH0KIyBjb21wdXRlIGF2ZXJhZ2UgZXhwcmVzc2lvbiBwZXIgY2x1c3RlciBmb3Igc2lnbmF0dXJlIGdlbmVzCmZlYXR1cmVzX3RvX3VzZSA8LSBpbnRlcnNlY3QoYWxsX3NpZ19nZW5lcywgcm93bmFtZXMoc2V1cmF0X29iaikpCmlmIChsZW5ndGgoZmVhdHVyZXNfdG9fdXNlKSA9PSAwKSBzdG9wKCJObyBjYW5vbmljYWwgc2lnbmF0dXJlIGdlbmVzIGZvdW5kIGluIHRoZSBSTkEgYXNzYXkuIENoZWNrIGdlbmUgbmFtZXMgLyBleHByZXNzaW9uIG1hdHJpeC4iKQoKYXZnX3JuYV9ieV9jbHVzdGVyIDwtIEF2ZXJhZ2VFeHByZXNzaW9uKHNldXJhdF9vYmosIGFzc2F5cyA9ICJSTkEiLCBmZWF0dXJlcyA9IGZlYXR1cmVzX3RvX3VzZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNsb3QgPSAiZGF0YSIsIGdyb3VwLmJ5ID0gY2x1c3Rlcl9jb2wpJFJOQQoKIyB6LXNjb3JlIHJvd3MgZm9yIGhlYXRtYXAgdmlzdWFsaXphdGlvbgp6bWF0IDwtIHQoc2NhbGUodChhcy5tYXRyaXgoYXZnX3JuYV9ieV9jbHVzdGVyKSArIDFlLTYpKSkKCnBoZWF0bWFwKHptYXQsIGNsdXN0ZXJfcm93cyA9IFRSVUUsIGNsdXN0ZXJfY29scyA9IFRSVUUsIG1haW4gPSAiQ2x1c3RlciB4IFJOQSBtYXJrZXIgKHotc2NhbGVkIGF2ZXJhZ2UpIikKYGBgCgpgYGB7ciBkb3RwbG90LXJuYSwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9MTJ9CiMgRG90UGxvdCBvZiBjYW5vbmljYWwgUk5BIG1hcmtlcnMgYnkgY2x1c3RlcgpEb3RQbG90KHNldXJhdF9vYmosIGZlYXR1cmVzID0gZmVhdHVyZXNfdG9fdXNlLCBncm91cC5ieSA9IGNsdXN0ZXJfY29sKSArIFJvdGF0ZWRBeGlzKCkrIHNjYWxlX2NvbG9yX2dyYWRpZW50Mihsb3cgPSAibGlnaHR5ZWxsb3ciLCBtaWQgPSAicmVkIiwgaGlnaCA9ICJmaXJlYnJpY2siLCBtaWRwb2ludCA9IDEpICsgZ2d0aXRsZSgiUk5BIGNhbm9uaWNhbCBtYXJrZXJzIGJ5IGNsdXN0ZXIiKQpgYGAKCiMjIyBJbnRlcnByZXRhdGlvbiBub3RlcwoKLSBDbHVzdGVycyBoaWdoIGZvciBgRk9YUDNgICsgYElMMlJBYCBsaWtlbHkgcmVwcmVzZW50IFRyZWdzLgotIGBDQ1I3YCArIGBTRUxMYCBtYXJrIE5hw692ZS9UY207IHZlcmlmeSB3aXRoIG90aGVyIGdlbmVzIChUQ0Y3L0xFRjEpLgotIGBHWk1LYC9gUFJGMWAvYElGTkdgIHN1Z2dlc3QgZWZmZWN0b3IvVGVtLWxpa2UgY2VsbHMuCi0gVXNlIERvdFBsb3RzIGFuZCB0aGUgaGVhdG1hcCB0byBtYXAgY2x1c3RlcnMgdG8gY2FuZGlkYXRlIGxhYmVscy4KCgojIDcuIEFEVCB2YWxpZGF0aW9uOiBlbnN1cmUgbm9ybWFsaXphdGlvbiBhbmQgY29tcHV0ZSBjbHVzdGVyLWxldmVsIEFEVCBhdmVyYWdlcwpgYGB7ciBhZHQtbm9ybWFsaXplLWFuZC1hdmcsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTEwfQoKIyBDaGVjayBpZiBBRFQgYXNzYXkgZXhpc3RzCmFkdF9hc3NheSA8LSBpbnRlcnNlY3QoYWR0X2Fzc2F5X2NhbmRpZGF0ZXMsIEFzc2F5cyhzZXVyYXRfb2JqKSkKaWYgKGxlbmd0aChhZHRfYXNzYXkpID09IDApIHsKICBtZXNzYWdlKCJObyBBRFQgYXNzYXkgZGV0ZWN0ZWQ7IHNraXBwaW5nIEFEVCB2YWxpZGF0aW9uLiIpCn0gZWxzZSB7CiAgYWR0X2Fzc2F5IDwtIGFkdF9hc3NheVsxXSAgIyB1c2UgdGhlIGZpcnN0IG1hdGNoCiAgRGVmYXVsdEFzc2F5KHNldXJhdF9vYmopIDwtIGFkdF9hc3NheQogIAogICMgQ2hlY2sgc2xvdHMKICBhZHRfc2xvdHMgPC0gc2xvdE5hbWVzKHNldXJhdF9vYmpbW2FkdF9hc3NheV1dKQogIGNhdCgiQURUIGFzc2F5IHNsb3RzOiIsIHBhc3RlKGFkdF9zbG90cywgY29sbGFwc2UgPSAiLCIpLCAiXG4iKQogIAogICMgRXh0cmFjdCBBRFQgZGF0YQogIGFkdF9kYXRhIDwtIEdldEFzc2F5RGF0YShzZXVyYXRfb2JqLCBhc3NheSA9IGFkdF9hc3NheSwgc2xvdCA9ICJkYXRhIikKICAKICAjIFJlcGxhY2UgTkEvSW5mIHdpdGggMAogIGFkdF9kYXRhW2lzLm5hKGFkdF9kYXRhKV0gPC0gMAogIGFkdF9kYXRhW2lzLmluZmluaXRlKGFkdF9kYXRhKV0gPC0gMAogIHNldXJhdF9vYmpbW2FkdF9hc3NheV1dQGRhdGEgPC0gYWR0X2RhdGEKICAKICAjIENMUiBub3JtYWxpemF0aW9uIGlmIGRhdGEgbG9va3MgdW5ub3JtYWxpemVkIChvcHRpb25hbCBjaGVjaykKICBpZiAobWF4KGFkdF9kYXRhLCBuYS5ybSA9IFRSVUUpID4gNTAgfHwgbWluKGFkdF9kYXRhLCBuYS5ybSA9IFRSVUUpIDwgLTEwKSB7CiAgICBjYXQoIlJ1bm5pbmcgQ0xSIG5vcm1hbGl6YXRpb24gYW5kIHNjYWxpbmcgQURULlxuIikKICAgIHNldXJhdF9vYmogPC0gTm9ybWFsaXplRGF0YShzZXVyYXRfb2JqLCBhc3NheSA9IGFkdF9hc3NheSwgbm9ybWFsaXphdGlvbi5tZXRob2QgPSAiQ0xSIikKICAgIHNldXJhdF9vYmogPC0gU2NhbGVEYXRhKHNldXJhdF9vYmosIGFzc2F5ID0gYWR0X2Fzc2F5KQogIH0gZWxzZSB7CiAgICBjYXQoIkFEVCBhc3NheSBhc3N1bWVkIG5vcm1hbGl6ZWQgKENMUi9tb2RhbCkuXG4iKQogIH0KICAKICAjIENvbXB1dGUgY2x1c3Rlci1sZXZlbCBhdmVyYWdlIEFEVCB2YWx1ZXMKICBhdmdfYWR0X2J5X2NsdXN0ZXIgPC0gQXZlcmFnZUV4cHJlc3Npb24oCiAgICBzZXVyYXRfb2JqLAogICAgYXNzYXlzID0gYWR0X2Fzc2F5LAogICAgZmVhdHVyZXMgPSBpbnRlcnNlY3QoY2Fub25pY2FsX2FkdCwgYWR0X2ZlYXR1cmVzKSwKICAgIHNsb3QgPSAiZGF0YSIsCiAgICBncm91cC5ieSA9IGNsdXN0ZXJfY29sCiAgKVtbYWR0X2Fzc2F5XV0KICAKICAjIHotc2NvcmUgYWNyb3NzIGNsdXN0ZXJzIGFuZCBwbG90IGhlYXRtYXAKICB6X2FkdCA8LSB0KHNjYWxlKHQoYXMubWF0cml4KGF2Z19hZHRfYnlfY2x1c3RlcikgKyAxZS02KSkpCiAgcGhlYXRtYXAoel9hZHQsIG1haW4gPSAiQURUIG1hcmtlcnMgKHotc2NvcmVkIGF2ZyBwZXIgY2x1c3RlcikiKQogIAogICMgVmlvbGluIHBsb3RzIGZvciBrZXkgQURUcyAoc3Vic2V0IHRvIGF2b2lkIG92ZXJjcm93ZGluZykKICBhZHRfdG9fcGxvdCA8LSBpbnRlcnNlY3QoYygiQ0Q0IiwiQ0Q4IiwiQ0Q0NVJBIiwiQ0Q0NVJPIiwiQ0QyNSIsIkNEMTI3IiwiUEQxIiwiQ1hDUjUiKSwgYWR0X2ZlYXR1cmVzKQogIGlmIChsZW5ndGgoYWR0X3RvX3Bsb3QpID4gMCkgewogICAgcHJpbnQoCiAgICAgIFZsblBsb3QoCiAgICAgICAgICAgICAgc2V1cmF0X29iaiwKICAgICAgICAgICAgICBmZWF0dXJlcyA9IGFkdF90b19wbG90LAogICAgICAgICAgICAgIGFzc2F5ID0gYWR0X2Fzc2F5LAogICAgICAgICAgICAgIHNsb3QgPSAiZGF0YSIsCiAgICAgICAgICAgICAgZ3JvdXAuYnkgPSBjbHVzdGVyX2NvbCwKICAgICAgICAgICAgICBwdC5zaXplID0gMC4wNSwKICAgICAgICAgICAgICBuY29sID0gMwopCgogICAgICApCiAgICAKICB9CiAgCiAgIyBSZXNldCBkZWZhdWx0IGFzc2F5CiAgRGVmYXVsdEFzc2F5KHNldXJhdF9vYmopIDwtICJSTkEiCn0KYGBgCgoKIyMjIEludGVycHJldGF0aW9uIG5vdGVzCgotIEFEVCBgQ0Q0YCBoaWdoICsgYENEOGAgbG93IGNvbmZpcm1zIENENCBUIGlkZW50aXR5IHBlciBjbHVzdGVyICh1c2VmdWwgUUMpLgotIGBDRDQ1UkFgIHZzIGBDRDQ1Uk9gIHNlcGFyYXRlcyBOYcOvdmUgdnMgTWVtb3J5LgotIGBDRDI1YCBoaWdoIGFuZCBgQ0QxMjdgIGxvdyAoYnkgQURUKSBzdXBwb3J0cyBUcmVnIGNhbGxzLgotIFBELTEvSUNPUy9MQUczIEFEVCBlbGV2YXRpb24gc3VnZ2VzdHMgYWN0aXZhdGlvbi9leGhhdXN0aW9uIHBoZW5vdHlwZXMuCgoKIyA4LiBNb2R1bGUgc2NvcmluZyAocGVyLWNlbGwpIGZvciBzaWduYXR1cmVzIGFuZCBjbHVzdGVyLWxldmVsIHN1bW1hcml6YXRpb24KYGBge3IgbW9kdWxlLXNjb3Jlc30KCiMgU2V0IFNDVCBhc3NheQpEZWZhdWx0QXNzYXkoc2V1cmF0X29iaikgPC0gIlNDVCIKCiMgRW5zdXJlIHNpZ25hdHVyZSBnZW5lcyBleGlzdCBpbiB0aGUgZGF0YXNldApzaWdzX3ByZXNlbnQgPC0gbGFwcGx5KHNpZ25hdHVyZXMsIGZ1bmN0aW9uKGcpIGludGVyc2VjdChnLCByb3duYW1lcyhzZXVyYXRfb2JqKSkpCnNpZ3NfcHJlc2VudCA8LSBzaWdzX3ByZXNlbnRbc2FwcGx5KHNpZ3NfcHJlc2VudCwgbGVuZ3RoKSA+IDBdCgppZiAobGVuZ3RoKHNpZ3NfcHJlc2VudCkgPT0gMCkgewogIHN0b3AoIk5vIHNpZ25hdHVyZSBnZW5lcyBwcmVzZW50IGluIFNDVCBhc3NheSBmb3IgbW9kdWxlIHNjb3JpbmcuIEFkanVzdCBnZW5lIG5hbWVzLiIpCn0KCiMgS2VlcCBzaWduYXR1cmVzIHdpdGggPj0zIGdlbmVzIGFuZCBub24temVybyB2YXJpYW5jZQpzaWdzX3ByZXNlbnQgPC0gbGFwcGx5KHNpZ3NfcHJlc2VudCwgZnVuY3Rpb24oZykgewogIGlmKGxlbmd0aChnKSA8IDMpIHJldHVybihOVUxMKQogIGV4cHJfbWF0IDwtIEdldEFzc2F5RGF0YShzZXVyYXRfb2JqLCBhc3NheSA9ICJTQ1QiLCBzbG90ID0gImRhdGEiKVtnLCAsIGRyb3AgPSBGQUxTRV0KICBpZihhbGwocm93U3VtcyhleHByX21hdCkgPT0gMCkpIHJldHVybihOVUxMKQogIHJldHVybihnKQp9KQpzaWdzX3ByZXNlbnQgPC0gc2lnc19wcmVzZW50W3NhcHBseShzaWdzX3ByZXNlbnQsIGxlbmd0aCkgPiAwXQoKaWYobGVuZ3RoKHNpZ3NfcHJlc2VudCkgPT0gMCkgc3RvcCgiTm8gdmFsaWQgc2lnbmF0dXJlcyB3aXRoIGVub3VnaCB2YXJpYW5jZSBmb3IgbW9kdWxlIHNjb3JpbmcuIikKCiMgQWRkIG1vZHVsZSBzY29yZXMgc2FmZWx5CnNldXJhdF9vYmogPC0gQWRkTW9kdWxlU2NvcmUoc2V1cmF0X29iaiwgZmVhdHVyZXMgPSBzaWdzX3ByZXNlbnQsIG5hbWUgPSAibW9kIiwgY3RybCA9IDUpCgojIFJlbmFtZSBtb2R1bGUgY29sdW1ucyB0byByZWFkYWJsZSBuYW1lcwphZGRlZF9jb2xzIDwtIHBhc3RlMCgibW9kIiwgc2VxX2Fsb25nKHNpZ3NfcHJlc2VudCkpCm5ld19jb2xuYW1lcyA8LSBwYXN0ZTAoInNjb3JlXyIsIG5hbWVzKHNpZ3NfcHJlc2VudCkpCmZvciAoaSBpbiBzZXFfYWxvbmcoYWRkZWRfY29scykpIHsKICBvbGQgPC0gYWRkZWRfY29sc1tpXQogIG5ldyA8LSBuZXdfY29sbmFtZXNbaV0KICBpZihvbGQgJWluJSBjb2xuYW1lcyhzZXVyYXRfb2JqQG1ldGEuZGF0YSkpIHsKICAgIGNvbG5hbWVzKHNldXJhdF9vYmpAbWV0YS5kYXRhKVt3aGljaChjb2xuYW1lcyhzZXVyYXRfb2JqQG1ldGEuZGF0YSkgPT0gb2xkKV0gPC0gbmV3CiAgfQp9CgojIFByZXBhcmUgbW9kdWxlIHNjb3JlcyBmb3IgVU1BUCBwbG90dGluZyAoc3Vic2V0IGZpcnN0IDYpCnBsb3Rfc2NvcmVzIDwtIG5ld19jb2xuYW1lc1sxOm1pbig2LCBsZW5ndGgobmV3X2NvbG5hbWVzKSldCgojIEZpbHRlciBvdXQgY29uc3RhbnQgbW9kdWxlIHNjb3JlcyB0byBhdm9pZCBGZWF0dXJlUGxvdCBlcnJvcnMKcGxvdF9zY29yZXMgPC0gcGxvdF9zY29yZXNbc2FwcGx5KHBsb3Rfc2NvcmVzLCBmdW5jdGlvbihmKSBsZW5ndGgodW5pcXVlKHNldXJhdF9vYmpbW2ZdXSkpID4gMSldCgppZihsZW5ndGgocGxvdF9zY29yZXMpID4gMCkgewogIHByaW50KAogICAgRmVhdHVyZVBsb3QoCiAgICAgIHNldXJhdF9vYmosCiAgICAgIGZlYXR1cmVzID0gcGxvdF9zY29yZXMsCiAgICAgIHJlZHVjdGlvbiA9ICJ1bWFwIiwKICAgICAgbmNvbCA9IDMsCiAgICAgIHB0LnNpemUgPSAwLjA1LAogICAgICBjb2xzID0gYygibGlnaHRncmV5IiwgImJsdWUiKQogICAgKQogICkKfSBlbHNlIHsKICBtZXNzYWdlKCJObyB2YXJpYWJsZSBtb2R1bGUgc2NvcmVzIHRvIHBsb3Qgd2l0aCBGZWF0dXJlUGxvdCgpLiIpCn0KCiMgVmlvbGluIHBsb3RzIHBlciBjbHVzdGVyIGZvciBhbGwgbW9kdWxlIHNjb3Jlcwp2bG5fc2NvcmVzIDwtIG5ld19jb2xuYW1lc1tzYXBwbHkobmV3X2NvbG5hbWVzLCBmdW5jdGlvbihmKSBsZW5ndGgodW5pcXVlKHNldXJhdF9vYmpbW2ZdXSkpID4gMSldCmlmKGxlbmd0aCh2bG5fc2NvcmVzKSA+IDApIHsKICBwcmludCgKICAgIFZsblBsb3QoCiAgICAgIHNldXJhdF9vYmosCiAgICAgIGZlYXR1cmVzID0gdmxuX3Njb3JlcywKICAgICAgZ3JvdXAuYnkgPSBjbHVzdGVyX2NvbCwKICAgICAgcHQuc2l6ZSA9IDAuMDUsCiAgICAgIG5jb2wgPSAzCiAgICApCiAgKQp9IGVsc2UgewogIG1lc3NhZ2UoIk5vIHZhcmlhYmxlIG1vZHVsZSBzY29yZXMgdG8gcGxvdCB3aXRoIFZsblBsb3QoKS4iKQp9CgojIENsdXN0ZXItbGV2ZWwgbWVhbiBtb2R1bGUgc2NvcmVzCmNsdXN0ZXJfc2NvcmVzX2RmIDwtIHNldXJhdF9vYmpAbWV0YS5kYXRhICU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUKICBncm91cF9ieSghIXN5bShjbHVzdGVyX2NvbCkpICU+JQogIHN1bW1hcmlzZShhY3Jvc3MoYWxsX29mKG5ld19jb2xuYW1lcyksIG1lYW4sIC5uYW1lcyA9ICJ7LmNvbH0iKSkKCnByaW50KGNsdXN0ZXJfc2NvcmVzX2RmKQoKIyBDb252ZXJ0IHRvIG1hdHJpeCBmb3IgaGVhdG1hcAptYXQgPC0gYXMuZGF0YS5mcmFtZShjbHVzdGVyX3Njb3Jlc19kZikKcm93bmFtZXMobWF0KSA8LSBtYXRbW2NsdXN0ZXJfY29sXV0KbWF0W1tjbHVzdGVyX2NvbF1dIDwtIE5VTEwKCiMgT3B0aW9uYWw6IHotc2NvcmUgYWNyb3NzIGNsdXN0ZXJzIGZvciByZWFkYWJpbGl0eQptYXRfeiA8LSB0KHNjYWxlKHQoYXMubWF0cml4KG1hdCkgKyAxZS02KSkpCgpwaGVhdG1hcChtYXRfeiwgbWFpbiA9ICJNZWFuIG1vZHVsZSBzY29yZXMgcGVyIGNsdXN0ZXIiLCBjbHVzdGVyX3Jvd3MgPSBUUlVFLCBjbHVzdGVyX2NvbHMgPSBUUlVFKQoKYGBgCgoKIyMjIEludGVycHJldGF0aW9uIG5vdGVzCgotIFRoZSBoaWdoZXN0IG1vZHVsZSBzY29yZSBwZXIgY2x1c3RlciBwcm92aWRlcyBhIGdlbmUtZXhwcmVzc2lvbi1kcml2ZW4gY2FuZGlkYXRlIGxhYmVsIChlLmcuLCBjbHVzdGVyIHdpdGggaGlnaGVzdCBgc2NvcmVfVHJlZ2Agc3VnZ2VzdHMgYSByZWd1bGF0b3J5IHBoZW5vdHlwZSkuCi0gTW9kdWxlIHNjb3JlcyBhcmUgcm9idXN0IHdoZW4gc2lnbmF0dXJlcyBjb250YWluIHNldmVyYWwgZ2VuZXM7IGV4cGFuZCBnZW5lIGxpc3RzIGlmIGEgc2lnbmF0dXJlIGlzIHdlYWsuCgoKIyA5LiBDb25zZW5zdXMgbGFiZWxsaW5nICh3ZWlnaHRlZCB2b3RpbmcgYWNyb3NzIEF6aW11dGgsIFNUQ0FUIFByZWRpY3Rpb24sIG1vZHVsZSBzY29yZXMsIGFuZCBBRFQgcnVsZXMpCmBgYHtyIGNvbnNlbnN1cy12b3Rpbmd9CgpsaWJyYXJ5KHRpYmJsZSkKCiMgSGVscGVyOiBtb2RhbCBsYWJlbCBpbiBhIGNsdXN0ZXIKZ2V0X21vZGFsIDwtIGZ1bmN0aW9uKHZlYykgewogIGlmIChhbGwoaXMubmEodmVjKSkpIHJldHVybihOQSkKICB1eCA8LSBzb3J0KHRhYmxlKHZlYyksIGRlY3JlYXNpbmcgPSBUUlVFKQogIG5hbWVzKHV4KVsxXQp9CgptZXRhIDwtIHNldXJhdF9vYmpAbWV0YS5kYXRhCmNsdXN0ZXJzIDwtIHNvcnQodW5pcXVlKG1ldGFbW2NsdXN0ZXJfY29sXV0pKQoKIyBtb2RhbCBBemltdXRoIGFuZCBTVENBVCBwZXIgY2x1c3Rlcgptb2RhbF9hemltdXRoIDwtIGlmIChhemltdXRoX2NvbCAlaW4lIGNvbG5hbWVzKG1ldGEpKSB7CiAgbWV0YSAlPiUgZ3JvdXBfYnkoISFzeW0oY2x1c3Rlcl9jb2wpKSAlPiUgc3VtbWFyaXNlKG1vZCA9IGdldF9tb2RhbCghIXN5bShhemltdXRoX2NvbCkpKSAlPiUgZGVmcmFtZSgpCn0gZWxzZSB7CiAgTlVMTAp9Cm1vZGFsX3N0Y2F0IDwtIGlmIChzdGNhdF9jb2wgJWluJSBjb2xuYW1lcyhtZXRhKSkgewogIG1ldGEgJT4lIGdyb3VwX2J5KCEhc3ltKGNsdXN0ZXJfY29sKSkgJT4lIHN1bW1hcmlzZShtb2QgPSBnZXRfbW9kYWwoISFzeW0oc3RjYXRfY29sKSkpICU+JSBkZWZyYW1lKCkKfSBlbHNlIHsKICBOVUxMCn0KCiMgdG9wIG1vZHVsZSBzaWduYXR1cmUgcGVyIGNsdXN0ZXIKbWVhbl9zaWdfYnlfY2x1c3RlciA8LSBzZXVyYXRfb2JqQG1ldGEuZGF0YSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JQogIGdyb3VwX2J5KCEhc3ltKGNsdXN0ZXJfY29sKSkgJT4lCiAgc3VtbWFyaXNlKGFjcm9zcyhzdGFydHNfd2l0aCgnc2NvcmVfJyksIG1lYW4pKQoKIyB0aWR5IGFuZCBnZXQgdG9wIHNpZ25hdHVyZQptZWFuX2xvbmcgPC0gbWVhbl9zaWdfYnlfY2x1c3RlciAlPiUgcGl2b3RfbG9uZ2VyKC1hbGxfb2YoY2x1c3Rlcl9jb2wpLCBuYW1lc190byA9ICdzaWcnLCB2YWx1ZXNfdG8gPSAndmFsJykKbWVhbl9sb25nJHNpZyA8LSBzdWIoJ15zY29yZV8nLCAnJywgbWVhbl9sb25nJHNpZykKdG9wX3NpZyA8LSBtZWFuX2xvbmcgJT4lIGdyb3VwX2J5KCEhc3ltKGNsdXN0ZXJfY29sKSkgJT4lIHNsaWNlX21heChvcmRlcl9ieSA9IHZhbCwgbiA9IDEpICU+JQogIHNlbGVjdChhbGxfb2YoY2x1c3Rlcl9jb2wpLCBzaWcpICU+JSBkZWZyYW1lKCkKCiMgQURUIHZvdGVzIHBlciBjbHVzdGVyIChzaW1wbGUgaGV1cmlzdGljIG9uIHotc2NvcmVkIGNsdXN0ZXIgbWVhbnMpCmFkdF92b3RlcyA8LSBOVUxMCmlmIChleGlzdHMoJ2F2Z19hZHRfYnlfY2x1c3RlcicpKSB7CiAgYWR0X3ogPC0gdChzY2FsZSh0KGFzLm1hdHJpeChhdmdfYWR0X2J5X2NsdXN0ZXIpICsgMWUtNikpKQogIGFkdF92b3RlcyA8LSBhcHBseShhZHRfeiwgMSwgZnVuY3Rpb24ocikgewogICAgIyBUcmVnOiBDRDI1IGhpZ2ggJiBDRDEyNyBsb3cKICAgIGlmICghaXMubmEoclsnQ0QyNSddKSAmJiAhaXMubmEoclsnQ0QxMjcnXSkgJiYgKHJbJ0NEMjUnXSA+IDAuNSkgJiYgKHJbJ0NEMTI3J10gPCAwKSkgcmV0dXJuKCdUcmVnJykKICAgICMgTmFpdmU6IENENDVSQSBoaWdoCiAgICBpZiAoIWlzLm5hKHJbJ0NENDVSQSddKSAmJiAoclsnQ0Q0NVJBJ10gPiAwLjUpKSByZXR1cm4oJ05haXZlJykKICAgICMgTWVtb3J5OiBDRDQ1Uk8gaGlnaAogICAgaWYgKCFpcy5uYShyWydDRDQ1Uk8nXSkgJiYgKHJbJ0NENDVSTyddID4gMC41KSkgcmV0dXJuKCdNZW1vcnknKQogICAgIyBDRDQgdnMgQ0Q4IGNvbmZpcm1hdGlvbgogICAgaWYgKCFpcy5uYShyWydDRDQnXSkgJiYgIWlzLm5hKHJbJ0NEOCddKSAmJiAoclsnQ0Q0J10gLSByWydDRDgnXSA+IDAuMykpIHJldHVybignQ0Q0JykKICAgIHJldHVybihOQSkKICB9KQp9CgojIHdlaWdodHMgKHR1bmFibGUpIC0tLSB5b3UgY2FuIGFkanVzdCB0aGVzZTsgaGVyZSB3ZSBnaXZlIGVxdWFsIHdlaWdodAp3ZWlnaHRzIDwtIGxpc3QoYXppbXV0aCA9IDAuMjMsIHN0Y2F0ID0gMC4xNSwgbW9kdWxlID0gMC4yNSwgYWR0ID0gMC4yNSkKCiMgVm90aW5nIHBlciBjbHVzdGVyCmNvbnNlbnN1cyA8LSBzZXROYW1lcyhyZXAoTkEsIGxlbmd0aChjbHVzdGVycykpLCBjbHVzdGVycykKZm9yIChjbCBpbiBjbHVzdGVycykgewogIHZvdGVzIDwtIGxpc3QoKQogIGlmICghaXMubnVsbChtb2RhbF9hemltdXRoKSkgewogICAgbGJsIDwtIG1vZGFsX2F6aW11dGhbYXMuY2hhcmFjdGVyKGNsKV0KICAgIGlmICghaXMubmEobGJsKSkgdm90ZXNbW2xibF1dIDwtICh2b3Rlc1tbbGJsXV0gJXx8JSAwKSArIHdlaWdodHMkYXppbXV0aAogIH0KICBpZiAoIWlzLm51bGwobW9kYWxfc3RjYXQpKSB7CiAgICBsYmwgPC0gbW9kYWxfc3RjYXRbYXMuY2hhcmFjdGVyKGNsKV0KICAgIGlmICghaXMubmEobGJsKSkgdm90ZXNbW2xibF1dIDwtICh2b3Rlc1tbbGJsXV0gJXx8JSAwKSArIHdlaWdodHMkc3RjYXQKICB9CiAgIyBtb2R1bGUKICBtc2lnIDwtIHRvcF9zaWdbYXMuY2hhcmFjdGVyKGNsKV0KICBpZiAoIWlzLm5hKG1zaWcpKSB2b3Rlc1tbbXNpZ11dIDwtICh2b3Rlc1tbbXNpZ11dICV8fCUgMCkgKyB3ZWlnaHRzJG1vZHVsZQogICMgYWR0CiAgaWYgKCFpcy5udWxsKGFkdF92b3RlcykpIHsKICAgIGxibCA8LSBhZHRfdm90ZXNbYXMuY2hhcmFjdGVyKGNsKV0KICAgIGlmICghaXMubmEobGJsKSkgdm90ZXNbW2xibF1dIDwtICh2b3Rlc1tbbGJsXV0gJXx8JSAwKSArIHdlaWdodHMkYWR0CiAgfQoKICBpZiAobGVuZ3RoKHZvdGVzKSA9PSAwKSB7CiAgICBjb25zZW5zdXNbYXMuY2hhcmFjdGVyKGNsKV0gPC0gTkEKICB9IGVsc2UgewogICAgdG9wX3Njb3JlIDwtIG1heCh1bmxpc3Qodm90ZXMpKQogICAgdG9wX2xhYmVscyA8LSBuYW1lcyh2b3RlcylbdW5saXN0KHZvdGVzKSA9PSB0b3Bfc2NvcmVdCiAgICBpZiAobGVuZ3RoKHRvcF9sYWJlbHMpID09IDEpIHsKICAgICAgY29uc2Vuc3VzW2FzLmNoYXJhY3RlcihjbCldIDwtIHRvcF9sYWJlbHMKICAgIH0gZWxzZSB7CiAgICAgICMgdGllLWJyZWFrOiBwcmVmZXIgQXppbXV0aCAtPiBTVENBVCAtPiBtb2R1bGUgLT4gQURUCiAgICAgIGNob3NlbiA8LSBOQQogICAgICBpZiAoIWlzLm51bGwobW9kYWxfYXppbXV0aCkgJiYgbW9kYWxfYXppbXV0aFthcy5jaGFyYWN0ZXIoY2wpXSAlaW4lIHRvcF9sYWJlbHMpIGNob3NlbiA8LSBtb2RhbF9hemltdXRoW2FzLmNoYXJhY3RlcihjbCldCiAgICAgIGVsc2UgaWYgKCFpcy5udWxsKG1vZGFsX3N0Y2F0KSAmJiBtb2RhbF9zdGNhdFthcy5jaGFyYWN0ZXIoY2wpXSAlaW4lIHRvcF9sYWJlbHMpIGNob3NlbiA8LSBtb2RhbF9zdGNhdFthcy5jaGFyYWN0ZXIoY2wpXQogICAgICBlbHNlIGlmICghaXMubmEobXNpZykgJiYgbXNpZyAlaW4lIHRvcF9sYWJlbHMpIGNob3NlbiA8LSBtc2lnCiAgICAgIGVsc2UgY2hvc2VuIDwtIHRvcF9sYWJlbHNbMV0KICAgICAgY29uc2Vuc3VzW2FzLmNoYXJhY3RlcihjbCldIDwtIGNob3NlbgogICAgfQogIH0KfQoKIyBjb21waWxlIHN1bW1hcnkgdGFibGUKY29uc2Vuc3VzX2RmIDwtIGRhdGEuZnJhbWUoCiAgY2x1c3RlciA9IG5hbWVzKGNvbnNlbnN1cyksCiAgY29uc2Vuc3VzX2xhYmVsID0gdW5uYW1lKGNvbnNlbnN1cyksCiAgYXppbXV0aCA9IGlmICghaXMubnVsbChtb2RhbF9hemltdXRoKSkgbW9kYWxfYXppbXV0aFtuYW1lcyhjb25zZW5zdXMpXSBlbHNlIE5BLAogIHN0Y2F0ID0gaWYgKCFpcy5udWxsKG1vZGFsX3N0Y2F0KSkgbW9kYWxfc3RjYXRbbmFtZXMoY29uc2Vuc3VzKV0gZWxzZSBOQSwKICB0b3BfbW9kdWxlID0gdG9wX3NpZ1tuYW1lcyhjb25zZW5zdXMpXSwKICBhZHRfdm90ZSA9IGlmICghaXMubnVsbChhZHRfdm90ZXMpKSBhZHRfdm90ZXNbbmFtZXMoY29uc2Vuc3VzKV0gZWxzZSBOQSwKICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKKQoKcHJpbnQoY29uc2Vuc3VzX2RmKQoKIyBFeHRyYWN0IHBlci1jZWxsIGNsdXN0ZXIgSURzCmNsdXN0ZXJzIDwtIGFzLmNoYXJhY3RlcihzZXVyYXRfb2JqW1tjbHVzdGVyX2NvbF1dWywxXSkKCiMgTWFwIGNsdXN0ZXIgSURzIHRvIGNvbnNlbnN1cyBsYWJlbHMKY29uc2Vuc3VzX3Blcl9jZWxsIDwtIGNvbnNlbnN1c1ttYXRjaChjbHVzdGVycywgbmFtZXMoY29uc2Vuc3VzKSldCgojIE1ha2Ugc3VyZSB0aGUgdmVjdG9yIGhhcyB0aGUgc2FtZSBuYW1lcyBhcyB0aGUgY2VsbHMgaW4gdGhlIFNldXJhdCBvYmplY3QKbmFtZXMoY29uc2Vuc3VzX3Blcl9jZWxsKSA8LSBjb2xuYW1lcyhzZXVyYXRfb2JqKQoKIyBBZGQgdG8gbWV0YWRhdGEKc2V1cmF0X29iaiRjb25zZW5zdXNfbGFiZWwgPC0gY29uc2Vuc3VzX3Blcl9jZWxsCgojIENoZWNrCnRhYmxlKHNldXJhdF9vYmokY29uc2Vuc3VzX2xhYmVsLCBjbHVzdGVycywgdXNlTkEgPSAiaWZhbnkiKQoKCiMgUGxvdCBvbiBVTUFQCkRpbVBsb3Qoc2V1cmF0X29iaiwgZ3JvdXAuYnkgPSAiY29uc2Vuc3VzX2xhYmVsIiwgbGFiZWwgPSBUUlVFLCByZXBlbCA9IFRSVUUpICsgTm9MZWdlbmQoKQoKRGltUGxvdChzZXVyYXRfb2JqLCBncm91cC5ieSA9ICJzZXVyYXRfY2x1c3RlcnMiLCBsYWJlbCA9IFRSVUUsIHJlcGVsID0gVFJVRSkgKyBOb0xlZ2VuZCgpCgojIFVNQVAgY29sb3JlZCBieSBjb25zZW5zdXMKaWYgKCJ1bWFwIiAlaW4lIG5hbWVzKHNldXJhdF9vYmpAcmVkdWN0aW9ucykpIHsKICBwcmludChEaW1QbG90KHNldXJhdF9vYmosIGdyb3VwLmJ5ID0gJ2NvbnNlbnN1c19sYWJlbCcsIGxhYmVsID0gVFJVRSwgcmVwZWwgPSBUUlVFKSArIGdndGl0bGUoJ0NvbnNlbnN1cyBsYWJlbHMgKGNsdXN0ZXItbGV2ZWwpJykpCn0gZWxzZSB7CiAgbWVzc2FnZSgnTm8gVU1BUCByZWR1Y3Rpb24gZm91bmQg4oCUIHNraXAgVU1BUCBwbG90dGluZycpCn0KYGBgCgojIyMgSW50ZXJwcmV0YXRpb24gbm90ZXMKCi0gYGNvbnNlbnN1c19kZmAgc3VtbWFyaXplcyB0aGUgd2VpZ2h0ZWQgdm90ZXMgZm9yIGVhY2ggY2x1c3Rlci4gQ2x1c3RlcnMgd2l0aCBgTkFgIGNvbnNlbnN1cyBhcmUgYW1iaWd1b3VzIOKAlCBpbnNwZWN0IHRoZW0gbWFudWFsbHkuCi0gWW91IG1heSB0dW5lIGB3ZWlnaHRzYCBkZXBlbmRpbmcgb24gd2hpY2ggYW5ub3RhdGlvbiB5b3UgdHJ1c3QgbW9yZS4gRm9yIGV4YW1wbGUsIGlmIEF6aW11dGggbWFwcGluZyB1c2VkIHlvdXIgZXhhY3QgcmVmZXJlbmNlIGFuZCBsb29rZWQgaGlnaC1jb25maWRlbmNlLCBpbmNyZWFzZSBgd2VpZ2h0cyRhemltdXRoYCB0byAwLjM1IGFuZCBwcm9wb3J0aW9uYWxseSByZWR1Y2Ugb3RoZXJzLgoKIyAxMC4gSW5zcGVjdCBhbWJpZ3VvdXMgY2x1c3RlcnMgKG1hbnVhbCByZXNjdWUpCmBgYHtyIGluc3BlY3QtYW1iaWd1b3VzfQoKYW1iaWd1b3VzIDwtIGNvbnNlbnN1c19kZiAlPiUgZmlsdGVyKGlzLm5hKGNvbnNlbnN1c19sYWJlbCkgfCAoYXppbXV0aCAhPSBjb25zZW5zdXNfbGFiZWwgJiBzdGNhdCAhPSBjb25zZW5zdXNfbGFiZWwpKQpwcmludChhbWJpZ3VvdXMpCgppZiAobnJvdyhhbWJpZ3VvdXMpID4gMCkgewogIGZvciAoY2wgaW4gYW1iaWd1b3VzJGNsdXN0ZXIpIHsKICAgIGNhdCgiXG4tLS1cbkluc3BlY3RpbmcgY2x1c3RlciIsIGNsLCAiXG4iKQoKICAgICMgRXh0cmFjdCBjZWxscyBieSBzZXVyYXRfY2x1c3RlcnMKICAgIGNlbGxzIDwtIFdoaWNoQ2VsbHMoc2V1cmF0X29iaiwgZXhwcmVzc2lvbiA9IHNldXJhdF9vYmpbW2NsdXN0ZXJfY29sXV0gPT0gY2wpCiAgICBjYXQoIk51bWJlciBvZiBjZWxscyBpbiBjbHVzdGVyOiIsIGxlbmd0aChjZWxscyksICJcbiIpCgogICAgaWYgKGxlbmd0aChjZWxscykgPT0gMCkgewogICAgICBjYXQoIuKaoO+4jyBObyBjZWxscyBmb3VuZCBmb3IgY2x1c3RlciIsIGNsLCAiXG4iKQogICAgICBuZXh0CiAgICB9CgogICAgIyBUZW1wb3JhcmlseSBzd2l0Y2ggSWRlbnRzIHRvIHNldXJhdF9jbHVzdGVycwogICAgSWRlbnRzKHNldXJhdF9vYmopIDwtIHNldXJhdF9vYmpbW2NsdXN0ZXJfY29sXV1bLDFdCgogICAgIyBUb3AgUk5BIG1hcmtlcnMKICAgIG1hcmtlcnMgPC0gRmluZE1hcmtlcnMoCiAgICAgIHNldXJhdF9vYmosCiAgICAgIGlkZW50LjEgPSBjbCwKICAgICAgbG9nZmMudGhyZXNob2xkID0gMC4yNQogICAgKQogICAgY2F0KCJUb3AgUk5BIG1hcmtlcnMgKGhlYWQpOlxuIikKICAgIHByaW50KGhlYWQobWFya2VycywgMjApKQoKICAgICMgQURUIHN1bW1hcnkgaWYgYXZhaWxhYmxlCiAgICBpZiAoZXhpc3RzKCJhdmdfYWR0X2J5X2NsdXN0ZXIiKSkgewogICAgICBpZiAoY2wgJWluJSByb3duYW1lcyhhdmdfYWR0X2J5X2NsdXN0ZXIpKSB7CiAgICAgICAgY2F0KCJBRFQgYXZlcmFnZXMgZm9yIHRoaXMgY2x1c3RlcjpcbiIpCiAgICAgICAgcHJpbnQoYXZnX2FkdF9ieV9jbHVzdGVyW2FzLmNoYXJhY3RlcihjbCksICwgZHJvcCA9IEZBTFNFXSkKICAgICAgfSBlbHNlIHsKICAgICAgICBjYXQoIk5vIEFEVCBzdW1tYXJ5IGF2YWlsYWJsZSBmb3IgY2x1c3RlciIsIGNsLCAiXG4iKQogICAgICB9CiAgICB9CiAgfQp9IGVsc2UgewogIGNhdCgiTm8gYW1iaWd1b3VzIGNsdXN0ZXJzIGZvdW5kIGJ5IHRoZSBjaG9zZW4gcnVsZXMuXG4iKQp9CgoKYGBgCgojIyMgSW50ZXJwcmV0YXRpb24gJiBtYW51YWwgY3VyYXRpb24gdGlwcwoKLSBGb3IgYW1iaWd1b3VzIGNsdXN0ZXJzLCBpbnNwZWN0IHRoZSB0b3AgUk5BIG1hcmtlcnMgKGZyb20gYEZpbmRNYXJrZXJzYCkgYW5kIEFEVCBhdmVyYWdlcyBzaWRlLWJ5LXNpZGUuCi0gQ29uc2lkZXIgcmUtY2x1c3RlcmluZyB0aGUgYW1iaWd1b3VzIGNsdXN0ZXIgY2VsbHMgYXQgaGlnaGVyIHJlc29sdXRpb24gb3IgcGVyZm9ybWluZyBzdWItY2x1c3RlcmluZywgYmVjYXVzZSBjbHVzdGVycyBzb21ldGltZXMgbWl4IHN0YXRlcyAoZS5nLiBhY3RpdmF0ZWQgVGNtICsgZWFybHkgZWZmZWN0b3IpLgotIFlvdSBjYW4gYWxzbyBhc3NpZ24gcGVyLWNlbGwgY29uc2Vuc3VzICh1c2luZyB0aGUgc2FtZSB2b3RpbmcgcnVsZXMpIHRvIGNhcHR1cmUgbWl4ZWQgY2x1c3RlcnMg4oCUIHRoaXMgd29ya2Zsb3cgdXNlcyBjbHVzdGVyLWxldmVsIGNvbnNlbnN1cyB0byBiZSBjb25zZXJ2YXRpdmUuCgojIDExLiBSZXBvcnRpbmcgcmVjb21tZW5kYXRpb25zIChNZXRob2RzIHRleHQgc25pcHBldCkKYGBge3IgbWV0aG9kcy1zbmlwcGV0LCBlY2hvPUZBTFNFfQpjYXQoIlN1Z2dlc3RlZCBNZXRob2RzIHRleHQgKGVkaXQgYmVmb3JlIHVzaW5nKTpcblxuIikKY2F0KCJDZWxsIHR5cGUgYW5ub3RhdGlvbiB3YXMgcGVyZm9ybWVkIGJ5IGNvbWJpbmluZyBhdXRvbWF0ZWQgcmVmZXJlbmNlIG1hcHBpbmcgYW5kIG1hcmtlci1iYXNlZCB2YWxpZGF0aW9uLiBXZSBtYXBwZWQgY2VsbHMgdXNpbmcgQXppbXV0aCAocHJlZGljdGVkLmNlbGx0eXBlLmwyKSBhbmQgYW4gaW5kZXBlbmRlbnQgY2xhc3NpZmllciAoU1RDQVQsIGNvbHVtbiAnUHJlZGljdGlvbicpLiBXZSB2YWxpZGF0ZWQgYXV0b21hdGVkIGxhYmVscyB1c2luZyAoMSkgY2Fub25pY2FsIFJOQSBtYXJrZXIgZXhwcmVzc2lvbiAoQ0NSNywgU0VMTCwgRk9YUDMsIFRCWDIxLCBldGMuKSwgKDIpIEFEVCAoQ0lURS1zZXEpIHN1cmZhY2UgcHJvdGVpbiBleHByZXNzaW9uIChDTFItbm9ybWFsaXplZCksIGFuZCAoMykgZ2VuZS1zaWduYXR1cmUgbW9kdWxlIHNjb3Jlcy4gRm9yIGVhY2ggY2x1c3RlciB3ZSBjb21wdXRlZCB0aGUgbW9kYWwgQXppbXV0aCBhbmQgU1RDQVQgbGFiZWxzLCB0aGUgdG9wLXNjb3JpbmcgZ2VuZSBzaWduYXR1cmUgYW5kIEFEVC1iYXNlZCBoZXVyaXN0aWNzIChDRDQ1UkEvQ0Q0NVJPIGZvciBuYcOvdmUgdnMgbWVtb3J5OyBDRDI1IGhpZ2ggLyBDRDEyNyBsb3cgZm9yIFRyZWcpLiBXZSBjb21iaW5lZCB0aGVzZSBmb3VyIGV2aWRlbmNlIHNvdXJjZXMgaW4gYSB3ZWlnaHRlZCB2b3Rpbmcgc2NoZW1lIChlcXVhbCB3ZWlnaHRzIGJ5IGRlZmF1bHQpIHRvIHByb2R1Y2UgYSBjb25zZW5zdXMgY2x1c3RlciBsYWJlbC4gQW1iaWd1b3VzIGNsdXN0ZXJzIHdlcmUgaW5zcGVjdGVkIG1hbnVhbGx5IHVzaW5nIGNsdXN0ZXItbGV2ZWwgbWFya2VycyBhbmQgQURUIHBhdHRlcm5zLlxuIikKYGBgCgo=