1use serde_json::Value;
13
14use crate::wire::{Event, Reply};
15
16pub const MAX_MESSAGE: usize = 32 << 20;
18
19const PASTE_START: &[u8] = b"\x1b[200~";
21const PASTE_END: &[u8] = b"\x1b[201~";
23const APC_PREFIX: &[u8] = b"tsp;";
25const OSC_PREFIX: &[u8] = b"877;tsp;";
27const CLIPBOARD_PREFIX: &[u8] = b"5522;";
29
30#[derive(Clone, Debug, PartialEq)]
32pub enum Item {
33 Keys(Vec<u8>),
35 Reply(Reply),
37 Event(Event),
39 Da1,
41 Osc5522(Vec<u8>),
44}
45
46#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
48enum Kind {
49 #[default]
51 Tsp,
52 Clipboard,
54}
55
56enum Scan {
58 Undecided,
60 Keys(usize),
62 Da1(usize),
64 Paste(usize),
66 Message(usize, Kind),
68}
69
70#[derive(Clone, Debug, Default)]
82pub struct Parser {
83 buf: Vec<u8>,
85 paste: bool,
87 tsp: Option<usize>,
89 kind: Kind,
91 scanned: usize,
93 skipping: bool,
95}
96
97impl Parser {
98 pub fn new() -> Self {
100 Self::default()
101 }
102
103 pub const fn pending(&self) -> bool {
106 self.tsp.is_none() && !self.skipping && !self.paste && !self.buf.is_empty()
107 }
108
109 pub fn feed(&mut self, bytes: &[u8]) -> Vec<Item> {
111 let mut out = Vec::new();
112 self.buf.extend_from_slice(bytes);
113 self.run(&mut out);
114 out
115 }
116
117 pub fn flush(&mut self) -> Vec<Item> {
120 if self.pending() {
121 vec![Item::Keys(std::mem::take(&mut self.buf))]
122 } else {
123 Vec::new()
124 }
125 }
126
127 fn run(&mut self, out: &mut Vec<Item>) {
129 let mut keys: Vec<u8> = Vec::new();
130 let mut at = 0;
131 loop {
132 if self.skipping || self.tsp.is_some() {
133 match self.message(at, out, &mut keys) {
134 Some(next) => at = next,
135 None => break,
136 }
137 continue;
138 }
139 if self.paste {
140 let rest = &self.buf[at..];
141 if let Some(end) = find(rest, PASTE_END) {
142 keys.extend_from_slice(&rest[..end + PASTE_END.len()]);
143 at += end + PASTE_END.len();
144 self.paste = false;
145 continue;
146 }
147 let hold = partial_suffix(rest, PASTE_END);
148 keys.extend_from_slice(&rest[..rest.len() - hold]);
149 at = self.buf.len() - hold;
150 break;
151 }
152 let rest = &self.buf[at..];
153 if rest.is_empty() {
154 break;
155 }
156 let plain = rest.iter().position(|&b| b == 0x1b).unwrap_or(rest.len());
157 if plain > 0 {
158 keys.extend_from_slice(&rest[..plain]);
159 at += plain;
160 continue;
161 }
162 match scan(rest) {
163 Scan::Undecided => break,
164 Scan::Keys(n) => {
165 keys.extend_from_slice(&rest[..n]);
166 at += n;
167 },
168 Scan::Paste(n) => {
169 keys.extend_from_slice(&rest[..n]);
170 at += n;
171 self.paste = true;
172 },
173 Scan::Da1(n) => {
174 push_keys(out, &mut keys);
175 out.push(Item::Da1);
176 at += n;
177 },
178 Scan::Message(content, kind) => {
179 self.buf.drain(..at);
181 self.tsp = Some(content);
182 self.kind = kind;
183 self.scanned = content;
184 at = 0;
185 },
186 }
187 }
188 push_keys(out, &mut keys);
189 self.buf.drain(..at);
190 }
191
192 fn message(&mut self, at: usize, out: &mut Vec<Item>, keys: &mut Vec<u8>) -> Option<usize> {
196 debug_assert_eq!(at, 0, "a TSP message is rebased to the buffer start");
197 let from = self.scanned;
198 let Some((end, term_len)) = terminator(&self.buf[from..]).map(|(i, n)| (from + i, n)) else {
199 self.scanned = self.buf.len().saturating_sub(1).max(from);
201 if !self.skipping && self.buf.len() > MAX_MESSAGE {
202 tracing::warn!(bytes = self.buf.len(), "dropping an oversized TSP message");
203 self.skipping = true;
204 self.tsp = None;
205 }
206 if self.skipping {
207 let keep = usize::from(self.buf.last() == Some(&0x1b));
208 let len = self.buf.len();
209 self.buf.drain(..len - keep);
210 self.scanned = 0;
211 }
212 return None;
213 };
214 if !self.skipping
215 && let Some(content) = self.tsp
216 {
217 push_keys(out, keys);
218 let body = &self.buf[content..end];
219 let item = match self.kind {
220 Kind::Tsp => decode(body),
221 Kind::Clipboard => Some(Item::Osc5522(body.to_vec())),
222 };
223 out.extend(item);
224 }
225 self.skipping = false;
226 self.tsp = None;
227 self.scanned = 0;
228 self.buf.drain(..end + term_len);
229 Some(0)
230 }
231}
232
233fn push_keys(out: &mut Vec<Item>, keys: &mut Vec<u8>) {
235 if !keys.is_empty() {
236 out.push(Item::Keys(std::mem::take(keys)));
237 }
238}
239
240fn find(hay: &[u8], needle: &[u8]) -> Option<usize> {
242 hay.windows(needle.len()).position(|w| w == needle)
243}
244
245fn partial_suffix(hay: &[u8], needle: &[u8]) -> usize {
247 (1..needle.len())
248 .rev()
249 .find(|&n| hay.ends_with(&needle[..n]))
250 .unwrap_or(0)
251}
252
253fn terminator(bytes: &[u8]) -> Option<(usize, usize)> {
255 let mut i = 0;
256 while i < bytes.len() {
257 match bytes[i] {
258 0x07 => return Some((i, 1)),
259 0x1b if bytes.get(i + 1) == Some(&b'\\') => return Some((i, 2)),
260 _ => {},
261 }
262 i += 1;
263 }
264 None
265}
266
267fn scan(rest: &[u8]) -> Scan {
270 let Some(&second) = rest.get(1) else {
271 return Scan::Undecided;
272 };
273 match second {
274 b'_' => prefixed(rest, APC_PREFIX, Kind::Tsp),
275 b']' => match (
276 prefixed(rest, OSC_PREFIX, Kind::Tsp),
277 prefixed(rest, CLIPBOARD_PREFIX, Kind::Clipboard),
278 ) {
279 (scan @ Scan::Message(..), _) | (_, scan @ Scan::Message(..)) => scan,
280 (Scan::Undecided, _) | (_, Scan::Undecided) => Scan::Undecided,
281 _ => Scan::Keys(2),
282 },
283 b'[' => csi(rest),
284 _ => Scan::Keys(1),
286 }
287}
288
289fn prefixed(rest: &[u8], prefix: &[u8], kind: Kind) -> Scan {
291 let after = &rest[2..];
292 let n = after.len().min(prefix.len());
293 if after[..n] != prefix[..n] {
294 return Scan::Keys(2);
295 }
296 if n < prefix.len() {
297 return Scan::Undecided;
298 }
299 Scan::Message(2 + prefix.len(), kind)
300}
301
302fn csi(rest: &[u8]) -> Scan {
304 let mut i = 2;
305 while let Some(&b) = rest.get(i) {
306 match b {
307 0x20..=0x3f => i += 1,
308 0x40..=0x7e => {
309 let seq = &rest[..=i];
310 let params = &seq[2..i];
311 if b == b'c'
312 && params.first() == Some(&b'?')
313 && params[1..].iter().all(|&p| p.is_ascii_digit() || p == b';')
314 {
315 return Scan::Da1(i + 1);
316 }
317 if seq == PASTE_START {
318 return Scan::Paste(i + 1);
319 }
320 return Scan::Keys(i + 1);
321 },
322 _ => return Scan::Keys(i),
324 }
325 }
326 Scan::Undecided
327}
328
329fn decode(content: &[u8]) -> Option<Item> {
332 let semi = content.iter().position(|&b| b == b';')?;
333 let verb = &content[..semi];
334 let mut pos = semi + 1;
335 while let Some(len) = content[pos..].iter().position(|&b| b == b';') {
336 if !parameter(&content[pos..pos + len]) {
337 break;
338 }
339 pos += len + 1;
340 }
341 let body = &content[pos..];
342 let Ok(Value::Object(map)) = serde_json::from_slice::<Value>(body) else {
343 tracing::debug!("dropping a TSP message whose body is not a JSON object");
344 return None;
345 };
346 match verb {
347 b"r" => Some(Item::Reply(Reply::decode(map))),
348 b"e" => Some(Item::Event(Event::decode(map))),
349 _ => None,
350 }
351}
352
353fn parameter(segment: &[u8]) -> bool {
355 let Some(eq) = segment.iter().position(|&b| b == b'=') else {
356 return false;
357 };
358 let (key, value) = (&segment[..eq], &segment[eq + 1..]);
359 !key.is_empty()
360 && key
361 .iter()
362 .all(|&b| b.is_ascii_alphanumeric() || b == b'_' || b == b'-')
363 && value
364 .iter()
365 .all(|&b| (0x21..=0x7e).contains(&b) && b != b';')
366}
367
368#[cfg(test)]
369mod tests {
370 use super::{Item, Parser};
371
372 #[test]
373 fn clipboard_packet_split_across_feeds_is_one_item() {
374 let mut parser = Parser::new();
375 let mut items = parser.feed(b"a\x1b]55");
376 items.extend(parser.feed(b"22;type=read;Zm9v\x1b\\b"));
377 assert_eq!(items, [
378 Item::Keys(b"a".to_vec()),
379 Item::Osc5522(b"type=read;Zm9v".to_vec()),
380 Item::Keys(b"b".to_vec()),
381 ]);
382 }
383
384 #[test]
385 fn clipboard_packet_inside_paste_stays_keys() {
386 let mut parser = Parser::new();
387 let bytes = b"\x1b[200~\x1b]5522;x\x07\x1b[201~";
388 let items = parser.feed(bytes);
389 assert_eq!(items, [Item::Keys(bytes.to_vec())]);
390 }
391}