Skip to content

Commit 565923e

Browse files
committed
fig: clean up wire_format header labels + hallucination bottom margin + legend
1 parent c8c9200 commit 565923e

3 files changed

Lines changed: 67 additions & 49 deletions

File tree

6.79 KB
Loading

docs/paper/figures/make_figures.py

Lines changed: 67 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -148,43 +148,57 @@ def arrow(x1, y1, x2, y2, label, dy_label=2, color="black"):
148148
# Figure 2 — Wire format and comparison to MCP JSON-RPC.
149149

150150
def fig_wire_format():
151-
fig = plt.figure(figsize=(6.5, 3.2))
152-
gs = fig.add_gridspec(2, 1, height_ratios=[1.2, 1], hspace=0.55)
151+
fig = plt.figure(figsize=(7.5, 3.6))
152+
gs = fig.add_gridspec(2, 1, height_ratios=[1.3, 1], hspace=0.65)
153153

154154
# Top: byte-level layout of a DCP frame.
155+
# Three big sections (header / payload / HMAC). The header breakdown
156+
# (ver/kind/seq/iid) is too narrow to label per-field at honest scale,
157+
# so we only call out the three macro regions and put the per-field
158+
# detail in a small inset box on the right.
155159
ax1 = fig.add_subplot(gs[0])
156-
ax1.set_xlim(0, 35); ax1.set_ylim(-0.4, 1.6); ax1.axis("off")
157-
158-
segments = [
159-
(0, 1, "ver", C["header"]),
160-
(1, 1, "kind", C["header"]),
161-
(2, 2, "seq", C["header"]),
162-
(4, 2, "iid", C["header"]),
163-
(6, 13, "CBOR payload (variable)", C["cbor"]),
164-
(19, 16, "optional HMAC-SHA256[:16]", C["hmac"]),
165-
]
166-
for x, w, label, color in segments:
167-
ax1.add_patch(mpatches.Rectangle((x, 0), w, 1,
168-
facecolor=color,
169-
edgecolor="white",
170-
linewidth=1.0,
171-
alpha=0.9))
172-
ax1.text(x + w / 2, 0.5, label,
173-
ha="center", va="center",
174-
fontsize=8, color="white", fontweight="bold")
175-
if w == 1 or w == 2:
176-
ax1.text(x + w / 2, -0.15, f"{w} B",
177-
ha="center", va="top", fontsize=7, color="#444")
178-
179-
ax1.annotate("6-byte fixed header", xy=(3, 1.0), xytext=(3, 1.45),
180-
fontsize=8, color="#333", ha="center",
181-
arrowprops=dict(arrowstyle="-", linewidth=0.5, color="#777"))
182-
ax1.annotate("CBOR map (RFC 8949)", xy=(12.5, 1.0), xytext=(12.5, 1.45),
183-
fontsize=8, color="#333", ha="center",
184-
arrowprops=dict(arrowstyle="-", linewidth=0.5, color="#777"))
185-
ax1.annotate("opt. integrity", xy=(27, 1.0), xytext=(27, 1.45),
186-
fontsize=8, color="#333", ha="center",
187-
arrowprops=dict(arrowstyle="-", linewidth=0.5, color="#777"))
160+
ax1.set_xlim(-0.5, 35.5); ax1.set_ylim(-1.4, 1.9); ax1.axis("off")
161+
162+
# Per-field sub-shading inside the header block, but no per-field labels.
163+
sub_colors = [C["header"], "#2d6aa3", C["header"], "#2d6aa3"]
164+
sub_widths = [1, 1, 2, 2]
165+
sx = 0
166+
for w, c in zip(sub_widths, sub_colors):
167+
ax1.add_patch(mpatches.Rectangle((sx, 0), w, 1,
168+
facecolor=c, edgecolor="white",
169+
linewidth=1.0, alpha=0.95))
170+
sx += w
171+
172+
# The two big regions.
173+
ax1.add_patch(mpatches.Rectangle((6, 0), 13, 1,
174+
facecolor=C["cbor"], edgecolor="white",
175+
linewidth=1.0, alpha=0.92))
176+
ax1.text(12.5, 0.5, "CBOR payload (variable)",
177+
ha="center", va="center", fontsize=9, color="white",
178+
fontweight="bold")
179+
ax1.add_patch(mpatches.Rectangle((19, 0), 16, 1,
180+
facecolor=C["hmac"], edgecolor="white",
181+
linewidth=1.0, alpha=0.92))
182+
ax1.text(27, 0.5, "optional HMAC-SHA256[:16]",
183+
ha="center", va="center", fontsize=9, color="white",
184+
fontweight="bold")
185+
186+
# Three top bracket labels with leader lines, comfortably spaced.
187+
ax1.annotate("6-byte fixed header", xy=(3, 1.0), xytext=(3, 1.65),
188+
fontsize=8.5, color="#222", ha="center",
189+
arrowprops=dict(arrowstyle="-", linewidth=0.5, color="#888"))
190+
ax1.annotate("CBOR map (RFC 8949)", xy=(12.5, 1.0), xytext=(12.5, 1.65),
191+
fontsize=8.5, color="#222", ha="center",
192+
arrowprops=dict(arrowstyle="-", linewidth=0.5, color="#888"))
193+
ax1.annotate("optional 16-byte tag", xy=(27, 1.0), xytext=(27, 1.65),
194+
fontsize=8.5, color="#222", ha="center",
195+
arrowprops=dict(arrowstyle="-", linewidth=0.5, color="#888"))
196+
197+
# Below the header: spell out the field breakdown horizontally as text.
198+
ax1.text(3, -0.55,
199+
"ver(1) + kind(1) + seq(2) + intent_id(2) = 6 bytes",
200+
ha="center", va="top", fontsize=7.5, color="#444", style="italic")
201+
188202
ax1.set_title("DCP frame layout", loc="left", pad=4, fontsize=10)
189203

190204
# Bottom: size comparison bars.
@@ -249,23 +263,24 @@ def fig_footprint():
249263
# Figure 4 — Hallucination rejection rate (the killer experiment).
250264

251265
def fig_hallucination():
252-
fig, ax = plt.subplots(figsize=(6.8, 3.4))
266+
fig, ax = plt.subplots(figsize=(9.0, 4.0))
253267

254268
attacks = [
255269
"Out-of-range\nvalue",
256270
"Unit\nconfusion",
257271
"Wrong\ntype",
258272
"Unknown\nintent",
259-
"Capability\nover-reach",
260-
"Indirect\nprompt-injection",
273+
"Capability\nescalation",
274+
"Prompt\ninjection",
261275
]
262276
series = {
263277
"DCP": [100, 100, 100, 100, 100, 60],
264278
"IoT-MCP": [60, 10, 95, 100, 0, 0],
265279
"Raw MCP": [30, 5, 95, 100, 0, 0],
266280
"OpenAPI": [70, 5, 100, 100, 50, 5],
267281
}
268-
colors = {"DCP": C["dcp"], "IoT-MCP": C["iotmcp"], "Raw MCP": C["rawmcp"], "OpenAPI": C["openapi"]}
282+
colors = {"DCP": C["dcp"], "IoT-MCP": C["iotmcp"],
283+
"Raw MCP": C["rawmcp"], "OpenAPI": C["openapi"]}
269284

270285
x = np.arange(len(attacks))
271286
width = 0.2
@@ -275,25 +290,28 @@ def fig_hallucination():
275290
label=name, edgecolor="white", linewidth=0.4)
276291
for bar, v in zip(bars, vals):
277292
if v > 0:
278-
ax.text(bar.get_x() + bar.get_width() / 2, v + 1.5,
293+
ax.text(bar.get_x() + bar.get_width() / 2, v + 2,
279294
f"{v}",
280295
ha="center", va="bottom", fontsize=6.5, color="#333")
281296

282297
ax.set_xticks(x)
283-
ax.set_xticklabels(attacks, fontsize=8)
298+
ax.set_xticklabels(attacks, fontsize=8.5)
284299
ax.set_ylabel("% of malformed/adversarial calls rejected\nbefore reaching device")
285-
ax.set_ylim(0, 115)
300+
ax.set_ylim(0, 118)
286301
ax.set_yticks([0, 25, 50, 75, 100])
287-
ax.legend(loc="upper right", frameon=False, ncol=4, bbox_to_anchor=(1.0, 1.13))
302+
ax.legend(loc="upper center", frameon=False, ncol=4, bbox_to_anchor=(0.5, 1.10))
288303
ax.grid(axis="y", linewidth=0.4, alpha=0.5)
289304
ax.set_axisbelow(True)
290-
ax.text(-0.7, -25,
291-
"Synthetic data for illustration. Values reflect what each protocol's "
292-
"schema is expressive enough to reject at the host before any byte\n"
293-
"reaches the device; they do not account for hand-written application code "
294-
"that any of these protocols may layer on top.",
295-
fontsize=7, color="#666", style="italic")
296-
fig.tight_layout()
305+
306+
# Give the figure enough bottom margin for the footnote sitting below the
307+
# x-axis category labels.
308+
fig.subplots_adjust(bottom=0.27, top=0.88, left=0.08, right=0.98)
309+
fig.text(0.5, 0.02,
310+
"Synthetic data for illustration. Values reflect what each protocol's "
311+
"schema is expressive enough to reject at the host\n"
312+
"before any byte reaches the device — they do NOT account for "
313+
"hand-written application code layered on top.",
314+
ha="center", va="bottom", fontsize=7.5, color="#666", style="italic")
297315
save(fig, "hallucination")
298316

299317

docs/paper/figures/wire_format.png

8.74 KB
Loading

0 commit comments

Comments
 (0)