Skip to main content

max / shop

26.8 KB · 807 lines History Blame Raw
1 //! VT500-series escape-sequence parser.
2 //!
3 //! Implements the [Paul Williams state machine](https://vt100.net/emu/dec_ansi_parser)
4 //! and adds an `apc_dispatch` callback that the `vte` crate on crates.io
5 //! declines to expose. That callback is what makes the kitty graphics
6 //! protocol land — its APC bodies (`\e_G…\e\`) are unreachable through
7 //! stock vte.
8 //!
9 //! Deliberately narrow scope:
10 //! - Written from the spec (not vendored from vte).
11 //! - Params live in `Vec<Vec<u16>>` for clarity; not the tightest packing but
12 //! trivial to iterate, and bounded by [`MAX_PARAMS`] because the writer at
13 //! the far end of the PTY is not assumed to be well behaved.
14 //! - State the parser holds on behalf of an incomplete sequence is bounded:
15 //! [`MAX_PARAMS`], [`MAX_STRING_BYTES`] and [`MAX_OSC_PARAMS`]. Nothing here
16 //! grows with what a writer sends, because the writer is whatever program
17 //! holds the far end of the PTY.
18 //! - No SIMD UTF-8 fast path yet — that's a `simdutf8` swap when we care.
19 //! - No sync-update (BSU/ESU) hooks yet — added when someone starts using
20 //! them and paint tearing shows up.
21 //!
22 //! State transitions are those of the reference DEC parser plus 8-bit C1
23 //! shortcuts (0x9B for CSI etc.) accepted only in Ground; 0x80-0xFF in
24 //! Ground is otherwise treated as UTF-8 continuation. That matches every
25 //! modern UTF-8 terminal.
26
27 #![deny(unsafe_op_in_unsafe_fn)]
28
29 /// Handler for the parser's dispatch events.
30 pub trait Perform {
31 /// A printable UTF-8 character was received.
32 fn print(&mut self, c: char) {
33 let _ = c;
34 }
35
36 /// A C0 (0x00-0x1F) or C1 (0x80-0x9F) control code was received.
37 fn execute(&mut self, byte: u8) {
38 let _ = byte;
39 }
40
41 /// A CSI sequence completed (`\e[…<final>`).
42 fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], ignore: bool, action: char) {
43 let _ = (params, intermediates, ignore, action);
44 }
45
46 /// An ESC sequence completed (`\e<intermediates><final>`).
47 fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) {
48 let _ = (intermediates, ignore, byte);
49 }
50
51 /// An OSC (`\e]…\e\`) or (`\e]…\a`) completed. `params` are the
52 /// semicolon-separated fields of the body.
53 fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) {
54 let _ = (params, bell_terminated);
55 }
56
57 /// A DCS sequence started; call [`Perform::put`] for each subsequent byte
58 /// and [`Perform::unhook`] on termination.
59 fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, action: char) {
60 let _ = (params, intermediates, ignore, action);
61 }
62
63 fn put(&mut self, byte: u8) {
64 let _ = byte;
65 }
66
67 fn unhook(&mut self) {}
68
69 /// An APC / SOS / PM string completed. `data` is the string body without
70 /// the leading `\e_` (etc.) or the trailing terminator. This is the hook
71 /// the kitty graphics protocol needs.
72 fn apc_dispatch(&mut self, data: &[u8]) {
73 let _ = data;
74 }
75 }
76
77 /// Parameter slots a single CSI or DCS sequence may hold, counting
78 /// subparameters.
79 ///
80 /// Past this the sequence is marked ignored and the extra slots are dropped,
81 /// which is what the reference DEC parser does with its own fixed parameter
82 /// store and what this parser already does with a third intermediate. Without
83 /// it every `;` pushes a fresh `Vec<u16>` for as long as a writer keeps
84 /// sending them: `ESC [` followed by four million separators retains 27.2 bytes
85 /// of buffer per input byte, so about 19 MB of hostile stdout retains a
86 /// gigabyte.
87 ///
88 /// 32 is `vte`'s number, and this crate exists to be a drop-in for `vte` with
89 /// an APC callback, so matching it means an application that renders under one
90 /// renders under the other. xterm's NPARAM is 30, and the widest real sequence
91 /// anyone sends is a 6-slot `38:2::R:G:B`.
92 pub const MAX_PARAMS: usize = 32;
93
94 /// Bytes an OSC or APC body may accumulate before the parser stops buffering
95 /// and drops the sequence.
96 ///
97 /// Unterminated bodies accumulated 1:1 with no ceiling at all, so a writer that
98 /// opens `ESC ]` and never terminates it grew the terminal by whatever it felt
99 /// like sending.
100 ///
101 /// 8 MiB is far above anything either protocol asks for. The kitty graphics
102 /// protocol requires payloads over 4096 bytes to be chunked, so an APC body is
103 /// a few kilobytes; OSC 52 carries a base64 clipboard selection, which is the
104 /// one field with any real size to it. A body over the limit is dropped rather
105 /// than truncated: half a base64 clipboard write or half an image chunk is not
106 /// a smaller version of the request, it is a different one.
107 pub const MAX_STRING_BYTES: usize = 8 * 1024 * 1024;
108
109 /// Fields (`;`-separated) an OSC body may hold before the sequence is dropped.
110 ///
111 /// Separated from [`MAX_STRING_BYTES`] because a separator costs 16 bytes of
112 /// index pair and contributes no body byte, so a stream of nothing but `;`
113 /// amplifies about 32x against the byte budget. 1024 is past any real OSC:
114 /// the widest is a multi-colour `OSC 4`, and shop's own handler reads two
115 /// fields.
116 pub const MAX_OSC_PARAMS: usize = 1024;
117
118 /// What the two body buffers are allocated with, and shrunk back to once a
119 /// sequence ends.
120 ///
121 /// Shrinking is the half that makes the cap hold across sequences: capacity is
122 /// a high-water mark, so without it one large body leaves the parser holding
123 /// that much for the life of the terminal.
124 const INITIAL_BODY_CAPACITY: usize = 2048;
125
126 /// Iterable parameter list for CSI / DCS. Each iteration yields one
127 /// parameter's subparameters (colon-separated, e.g. `38:2::R:G:B` gives one
128 /// entry `[38, 2, 0, R, G, B]`; `38;2;R;G;B` gives five entries `[38] [2]
129 /// [R] [G] [B]`).
130 #[derive(Debug, Default, Clone)]
131 pub struct Params {
132 inner: Vec<Vec<u16>>,
133 /// Slots used across every group, which is what [`MAX_PARAMS`] bounds.
134 /// Held rather than summed because it is consulted per byte.
135 slots: usize,
136 }
137
138 impl Params {
139 pub fn iter(&self) -> impl Iterator<Item = &[u16]> {
140 self.inner.iter().map(Vec::as_slice)
141 }
142
143 pub fn is_empty(&self) -> bool {
144 self.inner.is_empty()
145 }
146
147 pub fn len(&self) -> usize {
148 self.inner.len()
149 }
150
151 fn clear(&mut self) {
152 self.inner.clear();
153 self.slots = 0;
154 }
155
156 fn is_full(&self) -> bool {
157 self.slots >= MAX_PARAMS
158 }
159
160 /// Opens a group, or reports that the sequence has run out of slots.
161 fn push_new(&mut self) -> bool {
162 if self.is_full() {
163 return false;
164 }
165 self.inner.push(Vec::with_capacity(1));
166 self.slots += 1;
167 true
168 }
169
170 fn ensure_open(&mut self) -> bool {
171 if self.inner.is_empty() {
172 return self.push_new();
173 }
174 true
175 }
176
177 /// Digits only ever rewrite the slot already open, so this cannot grow the
178 /// list and does not report an overflow of its own.
179 fn append_digit(&mut self, digit: u16) {
180 if !self.ensure_open() {
181 return;
182 }
183 let group = self.inner.last_mut().unwrap();
184 if group.is_empty() {
185 group.push(digit);
186 } else {
187 let last = group.last_mut().unwrap();
188 *last = last.saturating_mul(10).saturating_add(digit);
189 }
190 }
191
192 fn new_subparam(&mut self) -> bool {
193 if !self.ensure_open() {
194 return false;
195 }
196 if self.is_full() {
197 return false;
198 }
199 self.inner.last_mut().unwrap().push(0);
200 self.slots += 1;
201 true
202 }
203
204 fn new_param(&mut self) -> bool {
205 self.push_new()
206 }
207
208 /// Heap bytes this parameter list is holding.
209 ///
210 /// One `Vec` per parameter is the storage shape the module header admits
211 /// to, and this is what makes the cost of that choice observable to
212 /// [`Parser::buffered_bytes`] instead of only to the machine.
213 fn footprint(&self) -> usize {
214 self.inner.capacity() * std::mem::size_of::<Vec<u16>>()
215 + self
216 .inner
217 .iter()
218 .map(|g| g.capacity() * std::mem::size_of::<u16>())
219 .sum::<usize>()
220 }
221 }
222
223 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
224 enum State {
225 Ground,
226 Escape,
227 EscapeIntermediate,
228 CsiEntry,
229 CsiParam,
230 CsiIntermediate,
231 CsiIgnore,
232 OscString,
233 DcsEntry,
234 DcsParam,
235 DcsIntermediate,
236 DcsIgnore,
237 DcsPassthrough,
238 ApcString,
239 Utf8,
240 }
241
242 /// Byte-stream parser. State persists across `advance` calls so partial
243 /// sequences and UTF-8 continuations survive chunked reads.
244 #[derive(Debug)]
245 pub struct Parser {
246 state: State,
247 prev_string_state: State,
248 intermediates: [u8; 2],
249 intermediates_idx: usize,
250 ignoring: bool,
251 params: Params,
252 osc_buf: Vec<u8>,
253 osc_params: Vec<(usize, usize)>,
254 apc_buf: Vec<u8>,
255 /// Set when the open OSC or APC body has passed [`MAX_STRING_BYTES`] or
256 /// [`MAX_OSC_PARAMS`]. The parser keeps scanning for the terminator so the
257 /// stream stays in sync, buffers nothing more, and dispatches nothing.
258 string_overflow: bool,
259 utf8_buf: [u8; 4],
260 utf8_idx: usize,
261 utf8_expected: usize,
262 }
263
264 impl Default for Parser {
265 fn default() -> Self {
266 Self::new()
267 }
268 }
269
270 impl Parser {
271 pub fn new() -> Self {
272 Self {
273 state: State::Ground,
274 prev_string_state: State::Ground,
275 intermediates: [0; 2],
276 intermediates_idx: 0,
277 ignoring: false,
278 params: Params::default(),
279 osc_buf: Vec::with_capacity(INITIAL_BODY_CAPACITY),
280 osc_params: Vec::with_capacity(8),
281 apc_buf: Vec::with_capacity(INITIAL_BODY_CAPACITY),
282 string_overflow: false,
283 utf8_buf: [0; 4],
284 utf8_idx: 0,
285 utf8_expected: 0,
286 }
287 }
288
289 pub fn advance<P: Perform>(&mut self, perform: &mut P, bytes: &[u8]) {
290 for &b in bytes {
291 self.step(perform, b);
292 }
293 }
294
295 fn step<P: Perform>(&mut self, perform: &mut P, byte: u8) {
296 match self.state {
297 State::Ground => self.ground(perform, byte),
298 State::Utf8 => self.utf8(perform, byte),
299 State::Escape => self.escape(perform, byte),
300 State::EscapeIntermediate => self.escape_intermediate(perform, byte),
301 State::CsiEntry => self.csi_entry(perform, byte),
302 State::CsiParam => self.csi_param(perform, byte),
303 State::CsiIntermediate => self.csi_intermediate(perform, byte),
304 State::CsiIgnore => self.csi_ignore(perform, byte),
305 State::OscString => self.osc_string(perform, byte),
306 State::DcsEntry => self.dcs_entry(perform, byte),
307 State::DcsParam => self.dcs_param(perform, byte),
308 State::DcsIntermediate => self.dcs_intermediate(perform, byte),
309 State::DcsIgnore => self.dcs_ignore(perform, byte),
310 State::DcsPassthrough => self.dcs_passthrough(perform, byte),
311 State::ApcString => self.apc_string(perform, byte),
312 }
313 }
314
315 // ---- state handlers ------------------------------------------------
316
317 fn ground<P: Perform>(&mut self, perform: &mut P, b: u8) {
318 match b {
319 0x00..=0x17 | 0x19 | 0x1C..=0x1F => perform.execute(b),
320 0x18 | 0x1A => perform.execute(b),
321 0x1B => {
322 self.clear();
323 self.state = State::Escape;
324 }
325 0x20..=0x7F => perform.print(b as char),
326 0x80..=0xBF => {} // Stray continuation byte — ignore.
327 0xC2..=0xDF => self.begin_utf8(b, 2),
328 0xE0..=0xEF => self.begin_utf8(b, 3),
329 0xF0..=0xF4 => self.begin_utf8(b, 4),
330 _ => {} // 0xC0/0xC1/0xF5-0xFF invalid.
331 }
332 }
333
334 fn begin_utf8(&mut self, first: u8, expected: usize) {
335 self.utf8_buf[0] = first;
336 self.utf8_idx = 1;
337 self.utf8_expected = expected;
338 self.state = State::Utf8;
339 }
340
341 fn utf8<P: Perform>(&mut self, perform: &mut P, b: u8) {
342 // ESC in the middle of a UTF-8 sequence resets.
343 if b == 0x1B {
344 self.utf8_idx = 0;
345 self.clear();
346 self.state = State::Escape;
347 return;
348 }
349 // Non-continuation byte breaks the sequence; return to Ground and
350 // re-process the byte.
351 if !(0x80..=0xBF).contains(&b) {
352 self.utf8_idx = 0;
353 self.state = State::Ground;
354 self.ground(perform, b);
355 return;
356 }
357 self.utf8_buf[self.utf8_idx] = b;
358 self.utf8_idx += 1;
359 if self.utf8_idx == self.utf8_expected {
360 if let Ok(s) = std::str::from_utf8(&self.utf8_buf[..self.utf8_idx])
361 && let Some(c) = s.chars().next()
362 {
363 perform.print(c);
364 }
365 self.utf8_idx = 0;
366 self.state = State::Ground;
367 }
368 }
369
370 fn escape<P: Perform>(&mut self, perform: &mut P, b: u8) {
371 match b {
372 0x00..=0x17 | 0x19 | 0x1C..=0x1F => perform.execute(b),
373 0x20..=0x2F => {
374 self.collect(b);
375 self.state = State::EscapeIntermediate;
376 }
377 0x30..=0x4F | 0x51..=0x57 | 0x59 | 0x5A | 0x5C | 0x60..=0x7E => {
378 perform.esc_dispatch(
379 &self.intermediates[..self.intermediates_idx],
380 self.ignoring,
381 b,
382 );
383 self.state = State::Ground;
384 }
385 0x50 => {
386 self.clear();
387 self.state = State::DcsEntry;
388 }
389 0x58 | 0x5E => {
390 self.apc_start();
391 self.state = State::ApcString;
392 }
393 0x5B => {
394 self.clear();
395 self.state = State::CsiEntry;
396 }
397 0x5D => {
398 self.osc_start();
399 self.state = State::OscString;
400 }
401 0x5F => {
402 self.apc_start();
403 self.state = State::ApcString;
404 }
405 0x7F => {} // Ignore.
406 0x18 | 0x1A => {
407 perform.execute(b);
408 self.state = State::Ground;
409 }
410 0x1B => self.clear(),
411 _ => {}
412 }
413 }
414
415 fn escape_intermediate<P: Perform>(&mut self, perform: &mut P, b: u8) {
416 match b {
417 0x00..=0x17 | 0x19 | 0x1C..=0x1F => perform.execute(b),
418 0x20..=0x2F => self.collect(b),
419 0x30..=0x7E => {
420 perform.esc_dispatch(
421 &self.intermediates[..self.intermediates_idx],
422 self.ignoring,
423 b,
424 );
425 self.state = State::Ground;
426 }
427 0x7F => {}
428 _ => {}
429 }
430 }
431
432 fn csi_entry<P: Perform>(&mut self, perform: &mut P, b: u8) {
433 match b {
434 0x00..=0x17 | 0x19 | 0x1C..=0x1F => perform.execute(b),
435 0x20..=0x2F => {
436 self.collect(b);
437 self.state = State::CsiIntermediate;
438 }
439 0x30..=0x39 => {
440 self.params.append_digit((b - b'0') as u16);
441 self.state = State::CsiParam;
442 }
443 0x3A => {
444 if !self.params.new_subparam() {
445 self.ignoring = true;
446 }
447 self.state = State::CsiParam;
448 }
449 0x3B => {
450 if !self.params.new_param() {
451 self.ignoring = true;
452 }
453 self.state = State::CsiParam;
454 }
455 0x3C..=0x3F => {
456 self.collect(b);
457 self.state = State::CsiParam;
458 }
459 0x40..=0x7E => {
460 perform.csi_dispatch(
461 &self.params,
462 &self.intermediates[..self.intermediates_idx],
463 self.ignoring,
464 b as char,
465 );
466 self.state = State::Ground;
467 }
468 0x7F => {}
469 _ => {}
470 }
471 }
472
473 fn csi_param<P: Perform>(&mut self, perform: &mut P, b: u8) {
474 match b {
475 0x00..=0x17 | 0x19 | 0x1C..=0x1F => perform.execute(b),
476 0x20..=0x2F => {
477 self.collect(b);
478 self.state = State::CsiIntermediate;
479 }
480 0x30..=0x39 => self.params.append_digit((b - b'0') as u16),
481 0x3A => {
482 if !self.params.new_subparam() {
483 self.ignoring = true;
484 }
485 }
486 0x3B => {
487 if !self.params.new_param() {
488 self.ignoring = true;
489 }
490 }
491 0x3C..=0x3F => self.state = State::CsiIgnore,
492 0x40..=0x7E => {
493 perform.csi_dispatch(
494 &self.params,
495 &self.intermediates[..self.intermediates_idx],
496 self.ignoring,
497 b as char,
498 );
499 self.state = State::Ground;
500 }
501 0x7F => {}
502 _ => {}
503 }
504 }
505
506 fn csi_intermediate<P: Perform>(&mut self, perform: &mut P, b: u8) {
507 match b {
508 0x00..=0x17 | 0x19 | 0x1C..=0x1F => perform.execute(b),
509 0x20..=0x2F => self.collect(b),
510 0x30..=0x3F => self.state = State::CsiIgnore,
511 0x40..=0x7E => {
512 perform.csi_dispatch(
513 &self.params,
514 &self.intermediates[..self.intermediates_idx],
515 self.ignoring,
516 b as char,
517 );
518 self.state = State::Ground;
519 }
520 0x7F => {}
521 _ => {}
522 }
523 }
524
525 fn csi_ignore<P: Perform>(&mut self, perform: &mut P, b: u8) {
526 match b {
527 0x00..=0x17 | 0x19 | 0x1C..=0x1F => perform.execute(b),
528 0x40..=0x7E => self.state = State::Ground,
529 _ => {}
530 }
531 }
532
533 fn osc_string<P: Perform>(&mut self, perform: &mut P, b: u8) {
534 match b {
535 0x07 => {
536 self.osc_close_current();
537 self.dispatch_osc(perform, true);
538 self.osc_end();
539 self.state = State::Ground;
540 }
541 0x1B => {
542 // Possible ST — end the OSC now, transition to Escape so a
543 // following `\` gets consumed as a no-op esc_dispatch.
544 self.osc_close_current();
545 self.dispatch_osc(perform, false);
546 self.osc_end();
547 self.prev_string_state = State::OscString;
548 self.state = State::Escape;
549 self.clear();
550 }
551 0x3B => {
552 // Parameter separator: close current field, open next.
553 if self.osc_params.len() >= MAX_OSC_PARAMS {
554 self.string_overflow = true;
555 return;
556 }
557 self.osc_close_current();
558 let end = self.osc_buf.len();
559 self.osc_params.push((end, end));
560 }
561 _ => {
562 if self.osc_buf.len() >= MAX_STRING_BYTES {
563 self.string_overflow = true;
564 return;
565 }
566 self.osc_buf.push(b);
567 }
568 }
569 }
570
571 fn osc_start(&mut self) {
572 self.osc_buf.clear();
573 self.osc_params.clear();
574 self.string_overflow = false;
575 // Open the first parameter with a placeholder end index that
576 // `osc_close_current` finalizes on ; / BEL / ST.
577 self.osc_params.push((0, 0));
578 }
579
580 /// Returns the OSC buffers to their resting size once a body has ended.
581 ///
582 /// `clear` leaves capacity behind, so a single oversized body would keep
583 /// the cap's worth of memory reserved for the life of the parser and the
584 /// limit above would bound one sequence rather than the process.
585 fn osc_end(&mut self) {
586 self.osc_buf.clear();
587 self.osc_buf.shrink_to(INITIAL_BODY_CAPACITY);
588 self.osc_params.clear();
589 self.osc_params.shrink_to(8);
590 self.string_overflow = false;
591 }
592
593 fn osc_close_current(&mut self) {
594 if let Some(last) = self.osc_params.last_mut() {
595 last.1 = self.osc_buf.len();
596 }
597 }
598
599 fn dispatch_osc<P: Perform>(&self, perform: &mut P, bell_terminated: bool) {
600 // An over-long body is dropped, not truncated: half a base64 clipboard
601 // write is a different request, not a smaller one.
602 if self.string_overflow {
603 return;
604 }
605 let slices: Vec<&[u8]> = self
606 .osc_params
607 .iter()
608 .map(|&(s, e)| &self.osc_buf[s..e])
609 .collect();
610 perform.osc_dispatch(&slices, bell_terminated);
611 }
612
613 fn dcs_entry<P: Perform>(&mut self, perform: &mut P, b: u8) {
614 match b {
615 0x00..=0x17 | 0x19 | 0x1C..=0x1F => {}
616 0x20..=0x2F => {
617 self.collect(b);
618 self.state = State::DcsIntermediate;
619 }
620 0x30..=0x39 => {
621 self.params.append_digit((b - b'0') as u16);
622 self.state = State::DcsParam;
623 }
624 0x3A => self.state = State::DcsIgnore,
625 0x3B => {
626 if !self.params.new_param() {
627 self.ignoring = true;
628 }
629 self.state = State::DcsParam;
630 }
631 0x3C..=0x3F => {
632 self.collect(b);
633 self.state = State::DcsParam;
634 }
635 0x40..=0x7E => {
636 perform.hook(
637 &self.params,
638 &self.intermediates[..self.intermediates_idx],
639 self.ignoring,
640 b as char,
641 );
642 self.state = State::DcsPassthrough;
643 }
644 0x7F => {}
645 _ => {}
646 }
647 }
648
649 fn dcs_param<P: Perform>(&mut self, perform: &mut P, b: u8) {
650 match b {
651 0x00..=0x17 | 0x19 | 0x1C..=0x1F => {}
652 0x20..=0x2F => {
653 self.collect(b);
654 self.state = State::DcsIntermediate;
655 }
656 0x30..=0x39 => self.params.append_digit((b - b'0') as u16),
657 0x3A => self.state = State::DcsIgnore,
658 0x3B => {
659 if !self.params.new_param() {
660 self.ignoring = true;
661 }
662 }
663 0x3C..=0x3F => self.state = State::DcsIgnore,
664 0x40..=0x7E => {
665 perform.hook(
666 &self.params,
667 &self.intermediates[..self.intermediates_idx],
668 self.ignoring,
669 b as char,
670 );
671 self.state = State::DcsPassthrough;
672 }
673 0x7F => {}
674 _ => {}
675 }
676 }
677
678 fn dcs_intermediate<P: Perform>(&mut self, perform: &mut P, b: u8) {
679 match b {
680 0x00..=0x17 | 0x19 | 0x1C..=0x1F => {}
681 0x20..=0x2F => self.collect(b),
682 0x30..=0x3F => self.state = State::DcsIgnore,
683 0x40..=0x7E => {
684 perform.hook(
685 &self.params,
686 &self.intermediates[..self.intermediates_idx],
687 self.ignoring,
688 b as char,
689 );
690 self.state = State::DcsPassthrough;
691 }
692 0x7F => {}
693 _ => {}
694 }
695 }
696
697 fn dcs_ignore<P: Perform>(&mut self, perform: &mut P, b: u8) {
698 if b == 0x1B {
699 self.prev_string_state = State::DcsIgnore;
700 self.state = State::Escape;
701 self.clear();
702 let _ = perform;
703 }
704 }
705
706 fn dcs_passthrough<P: Perform>(&mut self, perform: &mut P, b: u8) {
707 match b {
708 0x1B => {
709 perform.unhook();
710 self.prev_string_state = State::DcsPassthrough;
711 self.state = State::Escape;
712 self.clear();
713 }
714 0x07 => {
715 perform.unhook();
716 self.state = State::Ground;
717 }
718 0x00..=0x17 | 0x19 | 0x1C..=0x1F | 0x20..=0x7E => perform.put(b),
719 _ => {}
720 }
721 }
722
723 fn apc_string<P: Perform>(&mut self, perform: &mut P, b: u8) {
724 match b {
725 0x07 => {
726 if !self.string_overflow {
727 perform.apc_dispatch(&self.apc_buf);
728 }
729 self.apc_end();
730 self.state = State::Ground;
731 }
732 0x1B => {
733 if !self.string_overflow {
734 perform.apc_dispatch(&self.apc_buf);
735 }
736 self.apc_end();
737 self.prev_string_state = State::ApcString;
738 self.state = State::Escape;
739 self.clear();
740 }
741 _ => {
742 if self.apc_buf.len() >= MAX_STRING_BYTES {
743 self.string_overflow = true;
744 return;
745 }
746 self.apc_buf.push(b);
747 }
748 }
749 }
750
751 /// Opens an APC body. Paired with [`Parser::apc_end`] for the same reason
752 /// [`Parser::osc_start`] is paired with [`Parser::osc_end`].
753 fn apc_start(&mut self) {
754 self.apc_buf.clear();
755 self.string_overflow = false;
756 }
757
758 fn apc_end(&mut self) {
759 self.apc_buf.clear();
760 self.apc_buf.shrink_to(INITIAL_BODY_CAPACITY);
761 self.string_overflow = false;
762 }
763
764 /// Is the parser between sequences, holding no partial state?
765 ///
766 /// Ground is the only state in which a stream can be cut without losing
767 /// something, so this is what a caller checks to know a chunk boundary is
768 /// safe and what the fuzz oracle checks after a terminated sequence.
769 #[must_use]
770 pub fn in_ground(&self) -> bool {
771 matches!(self.state, State::Ground)
772 }
773
774 /// Heap bytes held on behalf of sequences that have not completed.
775 ///
776 /// The parser accumulates three unbounded things — OSC body, APC body and
777 /// the CSI parameter list — and none of them is capped by the state
778 /// machine. Exposing the total is what lets the soak oracle assert an
779 /// amplification ceiling instead of waiting for libFuzzer's RSS limit to
780 /// notice a machine-sized allocation.
781 #[must_use]
782 pub fn buffered_bytes(&self) -> usize {
783 self.osc_buf.capacity()
784 + self.apc_buf.capacity()
785 + self.osc_params.capacity() * std::mem::size_of::<(usize, usize)>()
786 + self.params.footprint()
787 }
788
789 fn collect(&mut self, b: u8) {
790 if self.intermediates_idx < self.intermediates.len() {
791 self.intermediates[self.intermediates_idx] = b;
792 self.intermediates_idx += 1;
793 } else {
794 self.ignoring = true;
795 }
796 }
797
798 fn clear(&mut self) {
799 self.intermediates_idx = 0;
800 self.ignoring = false;
801 self.params.clear();
802 }
803 }
804
805 #[cfg(test)]
806 mod tests;
807