1use anyhow::{bail, ensure, Context, Result};
9use serde_json::{json, Value};
10use tokio::io::{AsyncRead, AsyncReadExt};
11
12pub const REVISION: u64 = 54429;
14pub const VERSION_MAJOR: u64 = 24;
15pub const VERSION_MINOR: u64 = 8;
16pub const VERSION_PATCH: u64 = 0;
17pub const MAX_STRING: usize = 1024 * 1024;
19pub const MAX_BLOCK_BYTES: usize = 16 * 1024 * 1024;
21pub const MAX_COLUMNS: usize = 1000;
22pub const MAX_ROWS: usize = 100_000;
23pub const MAX_SETTINGS: usize = 1000;
25pub const IO_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
27
28pub mod client_packet {
29 pub const HELLO: u64 = 0;
30 pub const QUERY: u64 = 1;
31 pub const DATA: u64 = 2;
32 pub const CANCEL: u64 = 3;
33 pub const PING: u64 = 4;
34}
35
36pub mod server_packet {
37 pub const HELLO: u64 = 0;
38 pub const DATA: u64 = 1;
39 pub const EXCEPTION: u64 = 2;
40 pub const PROGRESS: u64 = 3;
41 pub const PONG: u64 = 4;
42 pub const END_OF_STREAM: u64 = 5;
43 pub const PROFILE_INFO: u64 = 6;
44 pub const TOTALS: u64 = 7;
45 pub const EXTREMES: u64 = 8;
46 pub const LOG: u64 = 10;
47 pub const TABLE_COLUMNS: u64 = 11;
48}
49
50pub fn put_varuint(out: &mut Vec<u8>, mut v: u64) {
51 loop {
52 let b = (v & 0x7f) as u8;
53 v >>= 7;
54 if v == 0 {
55 out.push(b);
56 return;
57 }
58 out.push(b | 0x80);
59 }
60}
61
62pub fn put_string(out: &mut Vec<u8>, s: &[u8]) {
63 put_varuint(out, s.len() as u64);
64 out.extend_from_slice(s);
65}
66
67pub struct Source<'a, R> {
70 inner: &'a mut R,
71 compressed: bool,
72 buf: Vec<u8>,
73 pos: usize,
74 budget: usize,
75}
76
77impl<'a, R: AsyncRead + Unpin> Source<'a, R> {
78 pub fn plain(inner: &'a mut R) -> Self {
79 Self::new(inner, false)
80 }
81 pub fn new(inner: &'a mut R, compressed: bool) -> Self {
82 Self {
83 inner,
84 compressed,
85 buf: Vec::new(),
86 pos: 0,
87 budget: MAX_BLOCK_BYTES,
88 }
89 }
90
91 fn spend(&mut self, n: usize) -> Result<()> {
92 ensure!(
93 n <= self.budget,
94 "ClickHouse block exceeds {MAX_BLOCK_BYTES} bytes"
95 );
96 self.budget -= n;
97 Ok(())
98 }
99
100 async fn refill(&mut self) -> Result<()> {
101 let mut header = [0u8; 25];
102 self.inner.read_exact(&mut header).await?;
103 let method = header[16];
104 let compressed_size = u32::from_le_bytes(header[17..21].try_into()?) as usize;
105 let size = u32::from_le_bytes(header[21..25].try_into()?) as usize;
106 ensure!(
107 compressed_size >= 9,
108 "compressed frame smaller than its header"
109 );
110 self.spend(compressed_size + 16)?;
111 ensure!(
112 size <= self.budget,
113 "compressed frame decompresses past the block bound"
114 );
115 let mut frame = Vec::with_capacity(compressed_size);
116 frame.extend_from_slice(&header[16..25]);
117 frame.resize(compressed_size, 0);
118 self.inner.read_exact(&mut frame[9..]).await?;
119 let sum = naive_cityhash::cityhash128(&frame);
120 let mut expected = [0u8; 16];
121 expected[..8].copy_from_slice(&sum.lo.to_le_bytes());
122 expected[8..].copy_from_slice(&sum.hi.to_le_bytes());
123 ensure!(
124 expected == header[..16],
125 "compressed frame checksum mismatch"
126 );
127 let data = match method {
128 0x02 => frame[9..].to_vec(),
129 0x82 => lz4_flex::block::decompress(&frame[9..], size)
130 .map_err(|e| anyhow::anyhow!("LZ4 frame: {e}"))?,
131 other => bail!("compression method 0x{other:02x} is not supported (LZ4 or none)"),
132 };
133 ensure!(
134 data.len() == size,
135 "compressed frame decompressed to the wrong size"
136 );
137 self.buf.drain(..self.pos);
138 self.pos = 0;
139 self.buf.extend_from_slice(&data);
140 Ok(())
141 }
142
143 pub async fn bytes(&mut self, n: usize) -> Result<Vec<u8>> {
144 if self.compressed {
145 while self.buf.len() - self.pos < n {
146 self.refill().await?;
147 }
148 let out = self.buf[self.pos..self.pos + n].to_vec();
149 self.pos += n;
150 Ok(out)
151 } else {
152 self.spend(n)?;
153 let mut out = vec![0u8; n];
154 self.inner.read_exact(&mut out).await?;
155 Ok(out)
156 }
157 }
158 pub async fn u8(&mut self) -> Result<u8> {
159 Ok(self.bytes(1).await?[0])
160 }
161 pub async fn varuint(&mut self) -> Result<u64> {
162 let mut v = 0u64;
163 for i in 0..10 {
164 let b = self.u8().await?;
165 v |= u64::from(b & 0x7f) << (7 * i);
166 if b & 0x80 == 0 {
167 return Ok(v);
168 }
169 }
170 bail!("VarUInt longer than 10 bytes")
171 }
172 pub async fn string(&mut self) -> Result<String> {
173 let b = self.raw_string().await?;
174 Ok(String::from_utf8_lossy(&b).to_string())
175 }
176 pub async fn raw_string(&mut self) -> Result<Vec<u8>> {
177 let n = self.varuint().await? as usize;
178 ensure!(n <= MAX_STRING, "string of {n} bytes exceeds {MAX_STRING}");
179 self.bytes(n).await
180 }
181 pub fn drained(&self) -> bool {
183 self.pos == self.buf.len()
184 }
185}
186
187pub async fn read_packet_type<R: AsyncRead + Unpin>(
189 r: &mut R,
190 idle: std::time::Duration,
191) -> Result<Option<u64>> {
192 let mut first = [0u8; 1];
193 match tokio::time::timeout(idle, r.read(&mut first)).await {
194 Ok(Ok(0)) => return Ok(None),
195 Ok(Ok(_)) => {}
196 Ok(Err(e)) => return Err(e.into()),
197 Err(_) => bail!("ClickHouse peer idle for {}s", idle.as_secs()),
198 }
199 let mut v = u64::from(first[0] & 0x7f);
200 let mut b = first[0];
201 let mut shift = 7;
202 while b & 0x80 != 0 {
203 ensure!(shift < 64, "VarUInt longer than 10 bytes");
204 b = r.read_u8().await?;
205 v |= u64::from(b & 0x7f) << shift;
206 shift += 7;
207 }
208 Ok(Some(v))
209}
210
211#[derive(Debug, Clone, PartialEq, Eq)]
213pub enum ColType {
214 UInt8,
215 UInt16,
216 UInt32,
217 UInt64,
218 Int8,
219 Int16,
220 Int32,
221 Int64,
222 Float32,
223 Float64,
224 Bool,
225 String,
226 Date,
227 DateTime,
228 Nullable(Box<ColType>),
229}
230
231impl ColType {
232 pub fn parse(t: &str) -> Result<Self> {
233 let t = t.trim();
234 if let Some(inner) = t
235 .strip_prefix("Nullable(")
236 .and_then(|r| r.strip_suffix(')'))
237 {
238 let inner = Self::parse(inner)?;
239 ensure!(
240 !matches!(inner, ColType::Nullable(_)),
241 "Nullable cannot nest"
242 );
243 return Ok(ColType::Nullable(Box::new(inner)));
244 }
245 if t.starts_with("DateTime(") && !t.starts_with("DateTime64") {
246 return Ok(ColType::DateTime);
247 }
248 Ok(match t {
249 "UInt8" => ColType::UInt8,
250 "UInt16" => ColType::UInt16,
251 "UInt32" => ColType::UInt32,
252 "UInt64" => ColType::UInt64,
253 "Int8" => ColType::Int8,
254 "Int16" => ColType::Int16,
255 "Int32" => ColType::Int32,
256 "Int64" => ColType::Int64,
257 "Float32" => ColType::Float32,
258 "Float64" => ColType::Float64,
259 "Bool" => ColType::Bool,
260 "String" => ColType::String,
261 "Date" => ColType::Date,
262 "DateTime" => ColType::DateTime,
263 other => bail!("column type {other} is not supported (integers, floats, Bool, String, Date, DateTime, Nullable of those)"),
264 })
265 }
266
267 fn width(&self) -> usize {
268 match self {
269 ColType::UInt8 | ColType::Int8 | ColType::Bool => 1,
270 ColType::UInt16 | ColType::Int16 | ColType::Date => 2,
271 ColType::UInt32 | ColType::Int32 | ColType::Float32 | ColType::DateTime => 4,
272 ColType::UInt64 | ColType::Int64 | ColType::Float64 => 8,
273 ColType::String | ColType::Nullable(_) => 0,
274 }
275 }
276
277 fn encode(&self, v: &Value, out: &mut Vec<u8>) -> Result<()> {
279 let int = |v: &Value| -> Result<i128> {
280 v.as_i64()
281 .map(i128::from)
282 .or_else(|| v.as_u64().map(i128::from))
283 .or_else(|| v.as_str().and_then(|s| s.parse().ok()))
284 .context("expected an integer")
285 };
286 let ranged = |lo: i128, hi: i128| -> Result<i128> {
287 let n = int(v)?;
288 ensure!((lo..=hi).contains(&n), "{n} out of range for this column");
289 Ok(n)
290 };
291 match self {
292 ColType::UInt8 => out.push(ranged(0, u8::MAX.into())? as u8),
293 ColType::UInt16 => out.extend((ranged(0, u16::MAX.into())? as u16).to_le_bytes()),
294 ColType::UInt32 => out.extend((ranged(0, u32::MAX.into())? as u32).to_le_bytes()),
295 ColType::UInt64 => out.extend((ranged(0, u64::MAX.into())? as u64).to_le_bytes()),
296 ColType::Int8 => {
297 out.extend((ranged(i8::MIN.into(), i8::MAX.into())? as i8).to_le_bytes())
298 }
299 ColType::Int16 => {
300 out.extend((ranged(i16::MIN.into(), i16::MAX.into())? as i16).to_le_bytes())
301 }
302 ColType::Int32 => {
303 out.extend((ranged(i32::MIN.into(), i32::MAX.into())? as i32).to_le_bytes())
304 }
305 ColType::Int64 => {
306 out.extend((ranged(i64::MIN.into(), i64::MAX.into())? as i64).to_le_bytes())
307 }
308 ColType::Float32 => {
309 out.extend((v.as_f64().context("expected a number")? as f32).to_le_bytes())
310 }
311 ColType::Float64 => out.extend(v.as_f64().context("expected a number")?.to_le_bytes()),
312 ColType::Bool => out.push(u8::from(v.as_bool().context("expected a boolean")?)),
313 ColType::String => {
314 let s = match v {
315 Value::String(s) => s.clone(),
316 other => other.to_string(),
317 };
318 ensure!(s.len() <= MAX_STRING, "string value too long");
319 put_string(out, s.as_bytes());
320 }
321 ColType::Date => {
322 let d = chrono::NaiveDate::parse_from_str(
323 v.as_str().context("expected YYYY-MM-DD")?,
324 "%Y-%m-%d",
325 )?;
326 let days =
327 (d - chrono::NaiveDate::from_ymd_opt(1970, 1, 1).context("epoch")?).num_days();
328 ensure!(
329 (0..=i64::from(u16::MAX)).contains(&days),
330 "date out of range"
331 );
332 out.extend((days as u16).to_le_bytes());
333 }
334 ColType::DateTime => {
335 let t = chrono::NaiveDateTime::parse_from_str(
336 v.as_str().context("expected YYYY-MM-DD hh:mm:ss")?,
337 "%Y-%m-%d %H:%M:%S",
338 )?;
339 let secs = t.and_utc().timestamp();
340 ensure!(
341 (0..=i64::from(u32::MAX)).contains(&secs),
342 "datetime out of range"
343 );
344 out.extend((secs as u32).to_le_bytes());
345 }
346 ColType::Nullable(_) => bail!("nested Nullable"),
347 }
348 Ok(())
349 }
350
351 fn default_bytes(&self, out: &mut Vec<u8>) {
352 match self {
353 ColType::String => out.push(0),
354 other => out.extend(std::iter::repeat_n(0, other.width())),
355 }
356 }
357
358 pub fn encode_column(&self, values: &[&Value]) -> Result<Vec<u8>> {
360 let mut out = Vec::new();
361 match self {
362 ColType::Nullable(inner) => {
363 for v in values {
364 out.push(u8::from(v.is_null()));
365 }
366 for v in values {
367 if v.is_null() {
368 inner.default_bytes(&mut out);
369 } else {
370 inner.encode(v, &mut out)?;
371 }
372 }
373 }
374 other => {
375 for v in values {
376 ensure!(!v.is_null(), "null in a non-Nullable column");
377 other.encode(v, &mut out)?;
378 }
379 }
380 }
381 Ok(out)
382 }
383
384 async fn decode_value<R: AsyncRead + Unpin>(&self, s: &mut Source<'_, R>) -> Result<Value> {
385 let w = self.width();
386 Ok(match self {
387 ColType::String => {
388 let b = s.raw_string().await?;
389 match String::from_utf8(b) {
390 Ok(t) => json!(t),
391 Err(e) => json!(e
392 .into_bytes()
393 .iter()
394 .map(|x| format!("{x:02x}"))
395 .collect::<String>()),
396 }
397 }
398 _ => {
399 let b = s.bytes(w).await?;
400 match self {
401 ColType::UInt8 => json!(b[0]),
402 ColType::UInt16 => json!(u16::from_le_bytes(b[..2].try_into()?)),
403 ColType::UInt32 => json!(u32::from_le_bytes(b[..4].try_into()?)),
404 ColType::UInt64 => json!(u64::from_le_bytes(b[..8].try_into()?)),
405 ColType::Int8 => json!(b[0] as i8),
406 ColType::Int16 => json!(i16::from_le_bytes(b[..2].try_into()?)),
407 ColType::Int32 => json!(i32::from_le_bytes(b[..4].try_into()?)),
408 ColType::Int64 => json!(i64::from_le_bytes(b[..8].try_into()?)),
409 ColType::Float32 => json!(f32::from_le_bytes(b[..4].try_into()?)),
410 ColType::Float64 => json!(f64::from_le_bytes(b[..8].try_into()?)),
411 ColType::Bool => json!(b[0] != 0),
412 ColType::Date => {
413 let days = u16::from_le_bytes(b[..2].try_into()?);
414 let d = chrono::NaiveDate::from_ymd_opt(1970, 1, 1).context("epoch")?
415 + chrono::Duration::days(i64::from(days));
416 json!(d.format("%Y-%m-%d").to_string())
417 }
418 ColType::DateTime => {
419 let secs = u32::from_le_bytes(b[..4].try_into()?);
420 let t = chrono::DateTime::from_timestamp(i64::from(secs), 0)
421 .context("timestamp")?;
422 json!(t.format("%Y-%m-%d %H:%M:%S").to_string())
423 }
424 _ => unreachable!("handled above"),
425 }
426 }
427 })
428 }
429
430 async fn decode_column<R: AsyncRead + Unpin>(
431 &self,
432 s: &mut Source<'_, R>,
433 rows: usize,
434 ) -> Result<Vec<Value>> {
435 let mut out = Vec::with_capacity(rows.min(1024));
436 match self {
437 ColType::Nullable(inner) => {
438 let nulls = s.bytes(rows).await?;
439 for null in nulls {
440 let v = Box::pin(inner.decode_value(s)).await?;
441 out.push(if null != 0 { Value::Null } else { v });
442 }
443 }
444 other => {
445 for _ in 0..rows {
446 out.push(Box::pin(other.decode_value(s)).await?);
447 }
448 }
449 }
450 Ok(out)
451 }
452}
453
454#[derive(Debug, Clone, PartialEq, Default)]
456pub struct Block {
457 pub columns: Vec<(String, String)>,
458 pub rows: Vec<Vec<Value>>,
459}
460
461impl Block {
462 pub fn is_empty(&self) -> bool {
463 self.columns.is_empty() && self.rows.is_empty()
464 }
465
466 pub fn from_json(columns: &Value, rows: &Value) -> Result<Self> {
468 let cols = columns.as_array().context("columns must be an array")?;
469 ensure!(cols.len() <= MAX_COLUMNS, "at most {MAX_COLUMNS} columns");
470 let mut columns = Vec::with_capacity(cols.len());
471 for c in cols {
472 let name = c["name"].as_str().context("each column needs a name")?;
473 let t = c["type"].as_str().context("each column needs a type")?;
474 ColType::parse(t)?;
475 columns.push((name.to_string(), t.to_string()));
476 }
477 let rows: Vec<Vec<Value>> = match rows {
478 Value::Null => Vec::new(),
479 Value::Array(rs) => {
480 ensure!(rs.len() <= MAX_ROWS, "at most {MAX_ROWS} rows");
481 rs.iter()
482 .map(|r| {
483 let r = r.as_array().context("each row must be an array")?;
484 ensure!(
485 r.len() == columns.len(),
486 "a row has {} values for {} columns",
487 r.len(),
488 columns.len()
489 );
490 Ok(r.clone())
491 })
492 .collect::<Result<_>>()?
493 }
494 _ => bail!("rows must be an array of arrays"),
495 };
496 let block = Block { columns, rows };
497 block.encode_body()?;
498 Ok(block)
499 }
500
501 pub fn encode_body(&self) -> Result<Vec<u8>> {
503 let mut out = Vec::new();
504 put_varuint(&mut out, 1);
505 out.push(0); put_varuint(&mut out, 2);
507 out.extend((-1i32).to_le_bytes()); put_varuint(&mut out, 0);
509 put_varuint(&mut out, self.columns.len() as u64);
510 put_varuint(&mut out, self.rows.len() as u64);
511 for (i, (name, t)) in self.columns.iter().enumerate() {
512 put_string(&mut out, name.as_bytes());
513 put_string(&mut out, t.as_bytes());
514 let values: Vec<&Value> = self.rows.iter().map(|r| &r[i]).collect();
515 out.extend(
516 ColType::parse(t)?
517 .encode_column(&values)
518 .with_context(|| format!("column {name}"))?,
519 );
520 }
521 Ok(out)
522 }
523
524 pub async fn decode<R: AsyncRead + Unpin>(s: &mut Source<'_, R>) -> Result<Self> {
525 loop {
526 match s.varuint().await? {
527 0 => break,
528 1 => {
529 s.u8().await?;
530 }
531 2 => {
532 s.bytes(4).await?;
533 }
534 other => bail!("unknown BlockInfo field {other}"),
535 }
536 }
537 let ncols = s.varuint().await? as usize;
538 let nrows = s.varuint().await? as usize;
539 ensure!(ncols <= MAX_COLUMNS, "block of {ncols} columns");
540 ensure!(nrows <= MAX_ROWS, "block of {nrows} rows");
541 let mut columns = Vec::with_capacity(ncols);
542 let mut data = Vec::with_capacity(ncols);
543 for _ in 0..ncols {
544 let name = s.string().await?;
545 let t = s.string().await?;
546 let col = ColType::parse(&t)?;
547 data.push(col.decode_column(s, nrows).await?);
548 columns.push((name, t));
549 }
550 let rows = (0..nrows)
551 .map(|r| data.iter().map(|c| c[r].clone()).collect())
552 .collect();
553 Ok(Block { columns, rows })
554 }
555
556 pub fn columns_json(&self) -> Value {
557 Value::Array(
558 self.columns
559 .iter()
560 .map(|(n, t)| json!({"name": n, "type": t}))
561 .collect(),
562 )
563 }
564}
565
566pub fn compress(data: &[u8]) -> Vec<u8> {
568 let body = lz4_flex::block::compress(data);
569 let mut frame = Vec::with_capacity(body.len() + 25);
570 frame.push(0x82);
571 frame.extend(((body.len() + 9) as u32).to_le_bytes());
572 frame.extend((data.len() as u32).to_le_bytes());
573 frame.extend_from_slice(&body);
574 let sum = naive_cityhash::cityhash128(&frame);
575 let mut out = Vec::with_capacity(frame.len() + 16);
576 out.extend(sum.lo.to_le_bytes());
577 out.extend(sum.hi.to_le_bytes());
578 out.extend(frame);
579 out
580}
581
582pub fn data_packet(kind: u64, block: &Block, compressed: bool) -> Result<Vec<u8>> {
585 let mut out = Vec::new();
586 put_varuint(&mut out, kind);
587 put_string(&mut out, b"");
588 let body = block.encode_body()?;
589 if compressed {
590 out.extend(compress(&body));
591 } else {
592 out.extend(body);
593 }
594 Ok(out)
595}
596
597pub fn exception(code: i32, message: &str) -> Vec<u8> {
599 let mut out = Vec::new();
600 put_varuint(&mut out, server_packet::EXCEPTION);
601 out.extend(code.to_le_bytes());
602 put_string(&mut out, b"DB::Exception");
603 put_string(&mut out, format!("DB::Exception: {message}").as_bytes());
604 put_string(&mut out, b"");
605 out.push(0);
606 out
607}
608
609pub fn progress(rows: u64, bytes: u64, written_rows: u64) -> Vec<u8> {
610 let mut out = Vec::new();
611 put_varuint(&mut out, server_packet::PROGRESS);
612 put_varuint(&mut out, rows);
613 put_varuint(&mut out, bytes);
614 put_varuint(&mut out, rows);
615 put_varuint(&mut out, written_rows);
616 put_varuint(&mut out, 0);
617 out
618}
619
620#[derive(Debug, Clone, Default)]
622pub struct Query {
623 pub query_id: String,
624 pub client_name: String,
625 pub settings: Vec<(String, String)>,
626 pub stage: u64,
627 pub compression: bool,
628 pub query: String,
629}
630
631pub async fn read_query<R: AsyncRead + Unpin>(
633 s: &mut Source<'_, R>,
634 revision: u64,
635) -> Result<Query> {
636 let mut q = Query {
637 query_id: s.string().await?,
638 ..Default::default()
639 };
640 if revision >= 54032 {
641 let kind = s.u8().await?;
642 if kind != 0 {
643 for _ in 0..3 {
644 s.string().await?; }
646 let interface = s.u8().await?;
647 ensure!(interface == 1, "client interface {interface} is not TCP");
648 s.string().await?; s.string().await?; q.client_name = s.string().await?;
651 s.varuint().await?;
652 s.varuint().await?;
653 s.varuint().await?;
654 if revision >= 54060 {
655 s.string().await?; }
657 if revision >= 54401 {
658 s.varuint().await?; }
660 }
661 }
662 for _ in 0..=MAX_SETTINGS {
663 let name = s.string().await?;
664 if name.is_empty() {
665 q.stage = s.varuint().await?;
666 q.compression = s.varuint().await? != 0;
667 q.query = s.string().await?;
668 return Ok(q);
669 }
670 s.varuint().await?; let value = s.string().await?;
672 q.settings.push((name, value));
673 }
674 bail!("more than {MAX_SETTINGS} settings")
675}
676
677pub fn query_packet(query: &str, compression: bool) -> Vec<u8> {
679 let mut out = Vec::new();
680 put_varuint(&mut out, client_packet::QUERY);
681 put_string(&mut out, b"");
682 out.push(1); put_string(&mut out, b"");
684 put_string(&mut out, b"");
685 put_string(&mut out, b"0.0.0.0:0");
686 out.push(1); put_string(&mut out, b"netget");
688 put_string(&mut out, b"netget");
689 put_string(&mut out, b"NetGet");
690 put_varuint(&mut out, VERSION_MAJOR);
691 put_varuint(&mut out, VERSION_MINOR);
692 put_varuint(&mut out, REVISION);
693 put_string(&mut out, b"");
694 put_varuint(&mut out, VERSION_PATCH);
695 put_string(&mut out, b""); put_varuint(&mut out, 2); put_varuint(&mut out, u64::from(compression));
698 put_string(&mut out, query.as_bytes());
699 out
700}
701
702pub async fn read_exception<R: AsyncRead + Unpin>(s: &mut Source<'_, R>) -> Result<(i32, String)> {
704 let mut first: Option<(i32, String)> = None;
705 for _ in 0..16 {
706 let code = i32::from_le_bytes(
707 s.bytes(4)
708 .await?
709 .try_into()
710 .map_err(|_| anyhow::anyhow!("code"))?,
711 );
712 s.string().await?; let message = s.string().await?;
714 s.string().await?; let nested = s.u8().await? != 0;
716 if first.is_none() {
717 first = Some((code, message));
718 }
719 if !nested {
720 return first.context("exception");
721 }
722 }
723 bail!("exception nested too deeply")
724}