1use std::mem::size_of;
9
10use crate::{client, meta, write_concern, MAX_BATCH_ITEMS, MAX_FRAME_BYTES};
11
12pub const DEFAULT_MAX_DECODED_RESPONSE_BYTES: usize = 64 * 1024 * 1024;
15pub const PORTABLE_CELL_HEADER_CHARGE: usize = 64;
18const MAP_SLOT_CHARGE: usize = 128;
19const VALIDATION_SLOT_CHARGE: usize = 64;
20const MAX_WRITER_BYTES: usize = 255;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28#[non_exhaustive]
29pub struct ClientDecodeReport {
30 pub accounted_bytes: usize,
31 pub raw_bytes: usize,
32 pub header_bytes: usize,
33 pub payload_bytes: usize,
34 pub query_cells: usize,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38enum Operation {
39 Read(usize),
40 Mutation(usize),
41 Query,
42 Inspect,
43 Scan,
44 Unsupported,
45}
46
47#[derive(Debug, Clone, Copy)]
51pub struct ClientResponseShape {
52 id: u64,
53 operation: Operation,
54 columns: Option<usize>,
55 rows: usize,
56 aggregate: bool,
57}
58impl ClientResponseShape {
59 pub fn from_request(request: &client::Request) -> Self {
60 use client::request::Operation as Request;
61 let operation = match &request.operation {
62 Some(Request::Get(_)) => Operation::Read(1),
63 Some(Request::BatchGet(batch)) => Operation::Read(batch.keys.len()),
64 Some(Request::Put(_) | Request::Delete(_)) => Operation::Mutation(1),
65 Some(Request::BatchPut(batch)) => Operation::Mutation(batch.puts.len()),
66 Some(Request::Query(_)) => Operation::Query,
67 Some(Request::Inspect(_)) => Operation::Inspect,
68 Some(Request::Scan(_)) => Operation::Scan,
69 None => Operation::Unsupported,
70 };
71 Self {
72 id: request.id,
73 operation,
74 columns: None,
75 rows: 100_000,
76 aggregate: false,
77 }
78 }
79
80 pub fn with_query_shape(
83 mut self,
84 columns: usize,
85 limit: Option<u64>,
86 aggregate: bool,
87 ) -> Result<Self, &'static str> {
88 if self.operation != Operation::Query
89 || !(1..=32).contains(&columns)
90 || limit.is_some_and(|limit| limit > 100_000)
91 {
92 return Err("invalid expected query shape");
93 }
94 self.columns = Some(columns);
95 self.rows = if aggregate {
96 1
97 } else {
98 limit.unwrap_or(100_000) as usize
99 };
100 self.aggregate = aggregate;
101 Ok(self)
102 }
103}
104
105pub fn preflight_client_frame(bytes: &[u8]) -> Result<(), &'static str> {
109 preflight_client_frame_with_budget(bytes, DEFAULT_MAX_DECODED_RESPONSE_BYTES).map(|_| ())
110}
111
112pub fn preflight_client_frame_with_budget(
115 bytes: &[u8],
116 max_accounted_bytes: usize,
117) -> Result<ClientDecodeReport, &'static str> {
118 preflight(bytes, max_accounted_bytes, None, None)
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123#[non_exhaustive]
124pub enum ClientFrameKind {
125 Hello,
126 Auth,
127 Request,
128 Response,
129}
130
131pub fn preflight_client_frame_kind(
132 bytes: &[u8],
133 max_accounted_bytes: usize,
134 kind: ClientFrameKind,
135) -> Result<ClientDecodeReport, &'static str> {
136 preflight(bytes, max_accounted_bytes, None, Some(kind))
137}
138
139pub fn preflight_client_response(
142 bytes: &[u8],
143 max_accounted_bytes: usize,
144 shape: &ClientResponseShape,
145) -> Result<ClientDecodeReport, &'static str> {
146 preflight(
147 bytes,
148 max_accounted_bytes,
149 Some(shape),
150 Some(ClientFrameKind::Response),
151 )
152}
153
154fn preflight(
155 bytes: &[u8],
156 maximum: usize,
157 shape: Option<&ClientResponseShape>,
158 kind: Option<ClientFrameKind>,
159) -> Result<ClientDecodeReport, &'static str> {
160 if bytes.is_empty() || bytes.len() > MAX_FRAME_BYTES {
161 return Err("invalid application frame size");
162 }
163 if bytes.len() > maximum {
164 return Err("decoded application response exceeds admission allowance");
165 }
166 if let Some(expected) = kind {
167 if envelope_kind(bytes)? != expected {
168 return Err("unexpected application message kind");
169 }
170 }
171 let mut budget = Budget {
172 maximum,
173 report: ClientDecodeReport {
174 accounted_bytes: bytes.len(),
175 raw_bytes: bytes.len(),
176 header_bytes: 0,
177 payload_bytes: 0,
178 query_cells: 0,
179 },
180 columns: 0,
181 rows: 0,
182 wrong_row_width: false,
183 };
184 budget.charge(0, false)?;
185 walk(bytes, Context::Frame, &mut budget, shape)?;
186 crate::validate_client_frame_structure(bytes)?;
190 Ok(budget.report)
191}
192
193struct Budget {
194 maximum: usize,
195 report: ClientDecodeReport,
196 columns: usize,
197 rows: usize,
198 wrong_row_width: bool,
199}
200impl Budget {
201 fn charge(&mut self, bytes: usize, payload: bool) -> Result<(), &'static str> {
202 self.report.accounted_bytes = self
203 .report
204 .accounted_bytes
205 .checked_add(bytes)
206 .ok_or("decoded application accounting overflow")?;
207 if self.report.accounted_bytes > self.maximum {
208 return Err("decoded application response exceeds admission allowance");
209 }
210 let counter = if payload {
211 &mut self.report.payload_bytes
212 } else {
213 &mut self.report.header_bytes
214 };
215 *counter = counter
216 .checked_add(bytes)
217 .ok_or("decoded application accounting overflow")?;
218 Ok(())
219 }
220 fn payload(&mut self, bytes: usize) -> Result<(), &'static str> {
221 self.charge(
222 bytes
223 .max(if bytes == 0 { 0 } else { 8 })
224 .checked_mul(2)
225 .ok_or("decoded payload accounting overflow")?,
226 true,
227 )
228 }
229 fn repeated(&mut self, count: usize, element: usize) -> Result<(), &'static str> {
230 let capacity = count
233 .checked_next_power_of_two()
234 .ok_or("decoded collection overflow")?
235 .max(4);
236 let previous = if count == 1 {
237 0
238 } else {
239 (count - 1)
240 .checked_next_power_of_two()
241 .ok_or("decoded collection overflow")?
242 .max(4)
243 };
244 self.charge(
245 (capacity - previous)
246 .checked_mul(element)
247 .ok_or("decoded collection overflow")?,
248 false,
249 )
250 }
251}
252
253#[derive(Clone, Copy, PartialEq, Eq)]
254enum Context {
255 Frame,
256 Hello,
257 Auth,
258 Request,
259 Get,
260 Put,
261 Delete,
262 BatchGet,
263 BatchPut,
264 Query,
265 Inspect,
266 Scan,
267 Response,
268 Error,
269 Value,
270 Read,
271 Mutation,
272 QueryResult,
273 Row,
274 Cell,
275 Inspection,
276 Health,
277 Placement,
278 MapEntry,
279 ScanMatch,
280 Receipt,
281 Evidence,
282 OperationEvidence,
283 Acknowledgement,
284}
285impl Context {
286 fn size(self) -> usize {
287 use Context::*;
288 match self {
289 Frame => size_of::<client::Frame>(),
290 Hello => size_of::<client::Hello>(),
291 Auth => size_of::<client::Auth>(),
292 Request => size_of::<client::Request>(),
293 Get => size_of::<client::Get>(),
294 Put => size_of::<client::Put>(),
295 Delete => size_of::<client::Delete>(),
296 BatchGet => size_of::<client::BatchGet>(),
297 BatchPut => size_of::<client::BatchPut>(),
298 Query => size_of::<client::Query>(),
299 Inspect => size_of::<client::Inspect>(),
300 Scan => size_of::<client::Scan>(),
301 Response => size_of::<client::Response>(),
302 Error => size_of::<client::Error>(),
303 Value => size_of::<client::Value>(),
304 Read => size_of::<client::ReadResult>(),
305 Mutation => size_of::<client::MutationResult>(),
306 QueryResult => size_of::<client::QueryResult>(),
307 Row => size_of::<meta::QueryPageRow>(),
308 Cell => size_of::<meta::QueryCell>(),
309 Inspection => size_of::<client::Inspection>(),
310 Health => size_of::<client::FragmentHealth>(),
311 Placement => size_of::<client::Placement>(),
312 MapEntry => size_of::<(String, u64)>(),
313 ScanMatch => size_of::<meta::ScanMatch>(),
314 Receipt => size_of::<meta::WriteConcernResult>(),
315 Evidence => size_of::<meta::WriteConcernEvidence>(),
316 OperationEvidence => size_of::<meta::WriteOperationEvidence>(),
317 Acknowledgement => size_of::<meta::WriteAcknowledgement>(),
318 }
319 }
320}
321
322#[derive(Clone, Copy)]
323enum Field {
324 Scalar,
325 Text,
326 Bytes,
327 Message(Context),
328 Texts {
329 limit: usize,
330 extra: usize,
331 },
332 Messages {
333 context: Context,
334 limit: usize,
335 extra: usize,
336 },
337 Map {
338 limit: usize,
339 },
340 ReceiptBytes,
341}
342fn schema(context: Context, field: u64) -> Option<Field> {
343 use Context::*;
344 use Field::{Bytes, Map, Message, Messages, ReceiptBytes, Scalar, Text, Texts};
345 let strings = |limit| Texts { limit, extra: 0 };
346 let messages = |context, limit| Messages {
347 context,
348 limit,
349 extra: 0,
350 };
351 Some(match (context, field) {
352 (Frame, 1) => Message(Hello),
353 (Frame, 2) => Message(Auth),
354 (Frame, 3) => Message(Request),
355 (Frame, 4) => Message(Response),
356 (Hello, 1) => Text,
357 (Hello, 2) | (Auth, 1) => Bytes,
358 (Hello, 3) => Scalar,
359 (Request, 1) => Scalar,
360 (Request, 10) => Text,
361 (Request, 2) => Message(Get),
362 (Request, 3) => Message(Put),
363 (Request, 4) => Message(Delete),
364 (Request, 5) => Message(BatchGet),
365 (Request, 6) => Message(BatchPut),
366 (Request, 7) => Message(Query),
367 (Request, 8) => Message(Inspect),
368 (Request, 9) => Message(Scan),
369 (Get, 1) | (Put, 1) | (Delete, 1) => Text,
370 (Get, 2) | (Put, 3 | 4) | (Delete, 2) => Scalar,
371 (Put, 2 | 5) | (Delete, 3) => Bytes,
372 (BatchGet, 1) => strings(MAX_BATCH_ITEMS),
373 (BatchGet | BatchPut, 2) => Scalar,
374 (BatchPut, 1) => messages(Put, MAX_BATCH_ITEMS),
375 (Query, 1) => Text,
376 (Query, 2) => Messages {
377 context: Cell,
378 limit: MAX_BATCH_ITEMS,
379 extra: PORTABLE_CELL_HEADER_CHARGE,
380 },
381 (Scan, 1 | 3 | 4 | 5 | 6) => Scalar,
382 (Scan, 2) => Bytes,
383 (Response, 1) => Scalar,
384 (Response, 2) => Message(Error),
385 (Response, 3) => messages(Read, MAX_BATCH_ITEMS),
386 (Response, 4) => messages(Mutation, MAX_BATCH_ITEMS),
387 (Response, 5) => Message(QueryResult),
388 (Response, 6) => Message(Inspection),
389 (Response, 7) => messages(ScanMatch, 100_000),
390 (Error, 1 | 3 | 5 | 6) => Scalar,
391 (Error, 2 | 4) => Text,
392 (Value, 1) => Bytes,
393 (Value, 2 | 4 | 5) => Scalar,
394 (Value, 3) => Text,
395 (Read, 1) | (Mutation, 1) => Text,
396 (Read, 2) => Message(Value),
397 (Read, 3) | (Mutation, 2) => Message(Error),
398 (Mutation, 3) => ReceiptBytes,
399 (QueryResult, 1) => strings(32),
400 (QueryResult, 2) => Messages {
401 context: Row,
402 limit: 100_000,
403 extra: size_of::<Vec<()>>(),
404 },
405 (Row, 1) => Text,
406 (Row, 2) => Messages {
407 context: Cell,
408 limit: 32,
409 extra: PORTABLE_CELL_HEADER_CHARGE,
410 },
411 (Cell, 1) => Scalar,
412 (Cell, 2 | 4) => Text,
413 (Cell, 3) => Bytes,
414 (Inspection, 1 | 2) => Text,
415 (Inspection, 3 | 6) => Scalar,
416 (Inspection, 4) => messages(Health, 100_000),
417 (Inspection, 5) => messages(Placement, 100_000),
418 (Health, 1 | 2 | 6 | 7 | 8) => Scalar,
419 (Health, 3) => strings(MAX_BATCH_ITEMS),
420 (Health, 4) => Map {
421 limit: MAX_BATCH_ITEMS,
422 },
423 (Placement, 1) => Scalar,
424 (Placement, 2) => strings(MAX_BATCH_ITEMS),
425 (Placement, 3) => Text,
426 (MapEntry, 1) => Text,
427 (MapEntry, 2) => Scalar,
428 (ScanMatch, 1) => Text,
429 (ScanMatch, 2) => Bytes,
430 (Receipt, 1 | 3 | 4 | 5 | 11) => Scalar,
431 (Receipt, 2 | 7 | 8) => Text,
432 (Receipt, 6 | 9) => Texts {
433 limit: write_concern::MAX_WRITE_CONCERN_REPLICAS,
434 extra: VALIDATION_SLOT_CHARGE,
435 },
436 (Receipt, 10) => Message(Evidence),
437 (Evidence, 1) => Message(OperationEvidence),
438 (Evidence, 3) => Bytes,
439 (Evidence, 2) => Texts {
440 limit: write_concern::MAX_WRITE_CONCERN_REPLICAS,
441 extra: VALIDATION_SLOT_CHARGE,
442 },
443 (Evidence, 4) => Messages {
444 context: Acknowledgement,
445 limit: write_concern::MAX_WRITE_CONCERN_REPLICAS,
446 extra: size_of::<write_concern::WriteAcknowledgement>() + MAX_WRITER_BYTES,
447 },
448 (OperationEvidence, 1 | 4 | 5 | 7 | 8) | (Acknowledgement, 1 | 2) => Scalar,
449 (OperationEvidence, 2 | 3 | 9) => Bytes,
450 (OperationEvidence, 6) => Text,
451 _ => return None,
452 })
453}
454
455fn varint(bytes: &[u8], position: &mut usize) -> Result<u64, &'static str> {
456 let mut result = 0;
457 for shift in (0..70).step_by(7) {
458 let byte = *bytes.get(*position).ok_or("truncated varint")?;
459 *position += 1;
460 if shift == 63 && byte > 1 {
461 return Err("overflowing varint");
462 }
463 result |= u64::from(byte & 127) << shift;
464 if byte & 128 == 0 {
465 return Ok(result);
466 }
467 }
468 Err("overflowing varint")
469}
470
471fn skip(bytes: &[u8], position: &mut usize, wire: u64) -> Result<(), &'static str> {
472 let length = match wire {
473 0 => {
474 varint(bytes, position)?;
475 return Ok(());
476 }
477 1 => 8,
478 5 => 4,
479 2 => usize::try_from(varint(bytes, position)?).map_err(|_| "overflow")?,
480 _ => return Err("invalid wire type"),
481 };
482 *position = position.checked_add(length).ok_or("overflow")?;
483 if *position > bytes.len() {
484 return Err("truncated field");
485 }
486 Ok(())
487}
488fn envelope_kind(bytes: &[u8]) -> Result<ClientFrameKind, &'static str> {
489 let mut position = 0;
490 let mut kind = None;
491 while position < bytes.len() {
492 let tag = varint(bytes, &mut position)?;
493 let next = match tag >> 3 {
494 1 => Some(ClientFrameKind::Hello),
495 2 => Some(ClientFrameKind::Auth),
496 3 => Some(ClientFrameKind::Request),
497 4 => Some(ClientFrameKind::Response),
498 0 => return Err("invalid field"),
499 _ => None,
500 };
501 if let Some(next) = next {
502 if kind.replace(next).is_some() || tag & 7 != 2 {
503 return Err("duplicate or invalid message");
504 }
505 }
506 skip(bytes, &mut position, tag & 7)?;
507 }
508 kind.ok_or("missing message")
509}
510fn response_id(bytes: &[u8]) -> Result<u64, &'static str> {
511 let mut position = 0;
512 let mut id = 0;
513 while position < bytes.len() {
514 let tag = varint(bytes, &mut position)?;
515 if tag >> 3 == 1 {
516 if tag & 7 != 0 {
517 return Err("invalid response id wire type");
518 }
519 id = varint(bytes, &mut position)?;
520 } else {
521 skip(bytes, &mut position, tag & 7)?;
522 }
523 }
524 Ok(id)
525}
526
527fn walk(
528 bytes: &[u8],
529 context: Context,
530 budget: &mut Budget,
531 shape: Option<&ClientResponseShape>,
532) -> Result<(), &'static str> {
533 if context == Context::Response {
534 if let Some(shape) = shape {
535 if response_id(bytes)? != shape.id {
536 return Err("response id does not match pending request");
537 }
538 }
539 }
540 budget.charge(context.size(), false)?;
541 let mut position = 0;
542 let mut counts = [0usize; 12];
543 while position < bytes.len() {
544 let tag = varint(bytes, &mut position)?;
545 let field = tag >> 3;
546 if field == 0 {
547 return Err("invalid field");
548 }
549 let kind = schema(context, field);
550 if let Some(kind) = kind {
551 let expected_wire = if matches!(kind, Field::Scalar) { 0 } else { 2 };
552 if tag & 7 != expected_wire {
553 return Err("invalid known application field wire type");
554 }
555 let count = counts
556 .get_mut(field as usize)
557 .ok_or("invalid known field")?;
558 *count = count.checked_add(1).ok_or("decoded collection overflow")?;
559 let repeated = match kind {
560 Field::Texts { limit, extra } => Some((limit, size_of::<String>() + extra)),
561 Field::Messages {
562 context,
563 limit,
564 extra,
565 } => Some((limit, context.size() + extra)),
566 Field::Map { limit } => Some((limit, MAP_SLOT_CHARGE)),
567 _ => None,
568 };
569 if let Some((limit, element)) = repeated {
570 if *count > limit {
571 return Err("invalid or excessive repeated field");
572 }
573 budget.repeated(*count, element)?;
574 }
575 if context == Context::Row && field == 2 {
576 budget.report.query_cells = budget
577 .report
578 .query_cells
579 .checked_add(1)
580 .ok_or("decoded cell overflow")?;
581 }
582 }
583 match tag & 7 {
584 0 => {
585 varint(bytes, &mut position)?;
586 }
587 1 => {
588 position = position.checked_add(8).ok_or("overflow")?;
589 }
590 5 => {
591 position = position.checked_add(4).ok_or("overflow")?;
592 }
593 2 => {
594 let length =
595 usize::try_from(varint(bytes, &mut position)?).map_err(|_| "overflow")?;
596 let end = position.checked_add(length).ok_or("overflow")?;
597 let inner = bytes.get(position..end).ok_or("truncated field")?;
598 match kind {
599 Some(Field::Text | Field::Texts { .. }) => {
600 std::str::from_utf8(inner).map_err(|_| "invalid application UTF-8")?;
601 budget.payload(inner.len())?;
602 }
603 Some(Field::Bytes) => budget.payload(inner.len())?,
604 Some(
605 Field::Message(nested)
606 | Field::Messages {
607 context: nested, ..
608 },
609 ) => {
610 walk(inner, nested, budget, shape)?;
611 }
612 Some(Field::Map { .. }) => walk(inner, Context::MapEntry, budget, shape)?,
613 Some(Field::ReceiptBytes) => {
614 budget.payload(inner.len())?;
615 walk(inner, Context::Receipt, budget, shape)?;
618 }
619 _ => {}
620 }
621 position = end;
622 }
623 _ => return Err("invalid wire type"),
624 }
625 if position > bytes.len() {
626 return Err("truncated field");
627 }
628 }
629 if context == Context::Row {
630 budget.wrong_row_width |= shape
631 .and_then(|shape| shape.columns)
632 .is_some_and(|columns| counts[2] != columns);
633 }
634 if context == Context::QueryResult {
635 budget.columns = counts[1];
636 budget.rows = counts[2];
637 }
638 if context == Context::Frame && shape.is_some() && counts[4] != 1 {
639 return Err("expected pending application response");
640 }
641 if context == Context::Response && counts[2] == 0 {
642 if let Some(shape) = shape {
643 let (reads, mutations, query, inspection, scans) = match shape.operation {
644 Operation::Read(reads) => (reads, 0, 0, 0, false),
645 Operation::Mutation(mutations) => (0, mutations, 0, 0, false),
646 Operation::Query => (0, 0, 1, 0, false),
647 Operation::Inspect => (0, 0, 0, 1, false),
648 Operation::Scan => (0, 0, 0, 0, true),
649 Operation::Unsupported => (0, 0, 0, 0, false),
650 };
651 if counts[3] != reads
652 || counts[4] != mutations
653 || counts[5] != query
654 || counts[6] != inspection
655 || (!scans && counts[7] != 0)
656 || (query == 1
657 && (shape
658 .columns
659 .is_some_and(|columns| columns != budget.columns)
660 || budget.rows > shape.rows
661 || (shape.aggregate && budget.rows != 1)
662 || budget.wrong_row_width))
663 {
664 return Err("decoded response does not match pending request shape");
665 }
666 }
667 }
668 Ok(())
669}