@@ -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
150150def 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
251265def 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\n value" ,
256270 "Unit\n confusion" ,
257271 "Wrong\n type" ,
258272 "Unknown\n intent" ,
259- "Capability\n over-reach " ,
260- "Indirect \n prompt-injection " ,
273+ "Capability\n escalation " ,
274+ "Prompt \n injection " ,
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\n before 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
0 commit comments