1use std::mem::size_of;
9
10use crate::{client, meta, write_concern, MAX_BATCH_ITEMS, MAX_RPC_MESSAGE_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 RpcDecodeReport {
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 RpcResponseShape {
52 operation: Operation,
53 columns: Option<usize>,
54 rows: usize,
55 aggregate: bool,
56}
57impl RpcResponseShape {
58 pub fn from_request(request: &client::Request) -> Self {
59 use client::request::Operation as Request;
60 let operation = match &request.operation {
61 Some(Request::Get(_)) => Operation::Read(1),
62 Some(Request::BatchGet(batch)) => Operation::Read(batch.keys.len()),
63 Some(Request::Put(_) | Request::Delete(_)) => Operation::Mutation(1),
64 Some(Request::BatchPut(batch)) => Operation::Mutation(batch.puts.len()),
65 Some(Request::Query(_)) => Operation::Query,
66 Some(Request::Inspect(_)) => Operation::Inspect,
67 Some(Request::Scan(_)) => Operation::Scan,
68 None => Operation::Unsupported,
69 };
70 Self {
71 operation,
72 columns: None,
73 rows: 100_000,
74 aggregate: false,
75 }
76 }
77
78 pub fn with_query_shape(
81 mut self,
82 columns: usize,
83 limit: Option<u64>,
84 aggregate: bool,
85 ) -> Result<Self, &'static str> {
86 if self.operation != Operation::Query
87 || !(1..=32).contains(&columns)
88 || limit.is_some_and(|limit| limit > 100_000)
89 {
90 return Err("invalid expected query shape");
91 }
92 self.columns = Some(columns);
93 self.rows = if aggregate {
94 1
95 } else {
96 limit.unwrap_or(100_000) as usize
97 };
98 self.aggregate = aggregate;
99 Ok(self)
100 }
101}
102
103pub fn preflight_rpc_request(
105 bytes: &[u8],
106 maximum: usize,
107) -> Result<RpcDecodeReport, &'static str> {
108 preflight(bytes, maximum, Context::Request, None)
109}
110
111pub fn preflight_rpc_response(
113 bytes: &[u8],
114 maximum: usize,
115 shape: &RpcResponseShape,
116) -> Result<RpcDecodeReport, &'static str> {
117 preflight(bytes, maximum, Context::Response, Some(shape))
118}
119
120pub fn preflight_rpc_response_unchecked_shape(
122 bytes: &[u8],
123 maximum: usize,
124) -> Result<RpcDecodeReport, &'static str> {
125 preflight(bytes, maximum, Context::Response, None)
126}
127
128fn preflight(
129 bytes: &[u8],
130 maximum: usize,
131 context: Context,
132 shape: Option<&RpcResponseShape>,
133) -> Result<RpcDecodeReport, &'static str> {
134 if bytes.len() > MAX_RPC_MESSAGE_BYTES || bytes.len() > maximum {
135 return Err("RPC message exceeds admission allowance");
136 }
137 let mut budget = Budget {
138 maximum,
139 report: RpcDecodeReport {
140 accounted_bytes: bytes.len(),
141 raw_bytes: bytes.len(),
142 header_bytes: 0,
143 payload_bytes: 0,
144 query_cells: 0,
145 },
146 columns: 0,
147 rows: 0,
148 wrong_row_width: false,
149 };
150 walk(bytes, context, &mut budget, shape)?;
151 Ok(budget.report)
152}
153
154struct Budget {
155 maximum: usize,
156 report: RpcDecodeReport,
157 columns: usize,
158 rows: usize,
159 wrong_row_width: bool,
160}
161impl Budget {
162 fn charge(&mut self, bytes: usize, payload: bool) -> Result<(), &'static str> {
163 self.report.accounted_bytes = self
164 .report
165 .accounted_bytes
166 .checked_add(bytes)
167 .ok_or("decoded application accounting overflow")?;
168 if self.report.accounted_bytes > self.maximum {
169 return Err("decoded application response exceeds admission allowance");
170 }
171 let counter = if payload {
172 &mut self.report.payload_bytes
173 } else {
174 &mut self.report.header_bytes
175 };
176 *counter = counter
177 .checked_add(bytes)
178 .ok_or("decoded application accounting overflow")?;
179 Ok(())
180 }
181 fn payload(&mut self, bytes: usize) -> Result<(), &'static str> {
182 self.charge(
183 bytes
184 .max(if bytes == 0 { 0 } else { 8 })
185 .checked_mul(2)
186 .ok_or("decoded payload accounting overflow")?,
187 true,
188 )
189 }
190 fn repeated(&mut self, count: usize, element: usize) -> Result<(), &'static str> {
191 let capacity = count
194 .checked_next_power_of_two()
195 .ok_or("decoded collection overflow")?
196 .max(4);
197 let previous = if count == 1 {
198 0
199 } else {
200 (count - 1)
201 .checked_next_power_of_two()
202 .ok_or("decoded collection overflow")?
203 .max(4)
204 };
205 self.charge(
206 (capacity - previous)
207 .checked_mul(element)
208 .ok_or("decoded collection overflow")?,
209 false,
210 )
211 }
212}
213
214#[derive(Clone, Copy, PartialEq, Eq)]
215enum Context {
216 Request,
217 Get,
218 Put,
219 Delete,
220 BatchGet,
221 BatchPut,
222 Query,
223 Inspect,
224 Scan,
225 Response,
226 Error,
227 Value,
228 Read,
229 Mutation,
230 QueryResult,
231 Row,
232 Cell,
233 Inspection,
234 Health,
235 Placement,
236 MapEntry,
237 ScanMatch,
238 Receipt,
239 Evidence,
240 OperationEvidence,
241 Acknowledgement,
242}
243impl Context {
244 fn size(self) -> usize {
245 use Context::*;
246 match self {
247 Request => size_of::<client::Request>(),
248 Get => size_of::<client::Get>(),
249 Put => size_of::<client::Put>(),
250 Delete => size_of::<client::Delete>(),
251 BatchGet => size_of::<client::BatchGet>(),
252 BatchPut => size_of::<client::BatchPut>(),
253 Query => size_of::<client::Query>(),
254 Inspect => size_of::<client::Inspect>(),
255 Scan => size_of::<client::Scan>(),
256 Response => size_of::<client::Response>(),
257 Error => size_of::<client::Error>(),
258 Value => size_of::<client::Value>(),
259 Read => size_of::<client::ReadResult>(),
260 Mutation => size_of::<client::MutationResult>(),
261 QueryResult => size_of::<client::QueryResult>(),
262 Row => size_of::<client::QueryRow>(),
263 Cell => size_of::<meta::QueryCell>(),
264 Inspection => size_of::<client::Inspection>(),
265 Health => size_of::<client::FragmentHealth>(),
266 Placement => size_of::<client::Placement>(),
267 MapEntry => size_of::<(String, u64)>(),
268 ScanMatch => size_of::<meta::ScanMatch>(),
269 Receipt => size_of::<meta::WriteConcernResult>(),
270 Evidence => size_of::<meta::WriteConcernEvidence>(),
271 OperationEvidence => size_of::<meta::WriteOperationEvidence>(),
272 Acknowledgement => size_of::<meta::WriteAcknowledgement>(),
273 }
274 }
275}
276
277#[derive(Clone, Copy)]
278enum Field {
279 Scalar,
280 Text,
281 Bytes,
282 Message(Context),
283 Texts {
284 limit: usize,
285 extra: usize,
286 },
287 Messages {
288 context: Context,
289 limit: usize,
290 extra: usize,
291 },
292 Map {
293 limit: usize,
294 },
295 ReceiptBytes,
296}
297fn schema(context: Context, field: u64) -> Option<Field> {
298 use Context::*;
299 use Field::{Bytes, Map, Message, Messages, ReceiptBytes, Scalar, Text, Texts};
300 let strings = |limit| Texts { limit, extra: 0 };
301 let messages = |context, limit| Messages {
302 context,
303 limit,
304 extra: 0,
305 };
306 Some(match (context, field) {
307 (Request, 1) => Text,
308 (Request, 2) => Message(Get),
309 (Request, 3) => Message(Put),
310 (Request, 4) => Message(Delete),
311 (Request, 5) => Message(BatchGet),
312 (Request, 6) => Message(BatchPut),
313 (Request, 7) => Message(Query),
314 (Request, 8) => Message(Inspect),
315 (Request, 9) => Message(Scan),
316 (Get, 1) | (Put, 1) | (Delete, 1) => Text,
317 (Get, 2) | (Put, 3 | 4) | (Delete, 2) => Scalar,
318 (Put, 2 | 5) | (Delete, 3) => Bytes,
319 (BatchGet, 1) => strings(MAX_BATCH_ITEMS),
320 (BatchGet | BatchPut, 2) => Scalar,
321 (BatchPut, 1) => messages(Put, MAX_BATCH_ITEMS),
322 (Query, 1) => Text,
323 (Query, 2) => Messages {
324 context: Cell,
325 limit: MAX_BATCH_ITEMS,
326 extra: PORTABLE_CELL_HEADER_CHARGE,
327 },
328 (Scan, 1 | 3 | 4 | 5 | 6) => Scalar,
329 (Scan, 2) => Bytes,
330 (Response, 1) => Message(Error),
331 (Response, 2) => messages(Read, MAX_BATCH_ITEMS),
332 (Response, 3) => messages(Mutation, MAX_BATCH_ITEMS),
333 (Response, 4) => Message(QueryResult),
334 (Response, 5) => Message(Inspection),
335 (Response, 6) => messages(ScanMatch, 100_000),
336 (Error, 1 | 3 | 5 | 6) => Scalar,
337 (Error, 2 | 4) => Text,
338 (Value, 1) => Bytes,
339 (Value, 2 | 4 | 5) => Scalar,
340 (Value, 3) => Text,
341 (Read, 1) | (Mutation, 1) => Text,
342 (Read, 2) => Message(Value),
343 (Read, 3) | (Mutation, 2) => Message(Error),
344 (Mutation, 3) => ReceiptBytes,
345 (QueryResult, 1) => strings(32),
346 (QueryResult, 2) => Messages {
347 context: Row,
348 limit: 100_000,
349 extra: size_of::<Vec<()>>(),
350 },
351 (Row, 1) => Messages {
352 context: Cell,
353 limit: 32,
354 extra: PORTABLE_CELL_HEADER_CHARGE,
355 },
356 (Cell, 1) => Scalar,
357 (Cell, 2 | 4) => Text,
358 (Cell, 3) => Bytes,
359 (Inspection, 1 | 2) => Text,
360 (Inspection, 3 | 6) => Scalar,
361 (Inspection, 4) => messages(Health, 100_000),
362 (Inspection, 5) => messages(Placement, 100_000),
363 (Health, 1 | 2 | 6 | 7 | 8) => Scalar,
364 (Health, 3) => strings(MAX_BATCH_ITEMS),
365 (Health, 4) => Map {
366 limit: MAX_BATCH_ITEMS,
367 },
368 (Placement, 1) => Scalar,
369 (Placement, 2) => strings(MAX_BATCH_ITEMS),
370 (Placement, 3) => Text,
371 (MapEntry, 1) => Text,
372 (MapEntry, 2) => Scalar,
373 (ScanMatch, 1) => Text,
374 (ScanMatch, 2) => Bytes,
375 (Receipt, 1 | 3 | 4 | 5 | 11) => Scalar,
376 (Receipt, 2 | 7 | 8) => Text,
377 (Receipt, 6 | 9) => Texts {
378 limit: write_concern::MAX_WRITE_CONCERN_REPLICAS,
379 extra: VALIDATION_SLOT_CHARGE,
380 },
381 (Receipt, 10) => Message(Evidence),
382 (Evidence, 1) => Message(OperationEvidence),
383 (Evidence, 3) => Bytes,
384 (Evidence, 2) => Texts {
385 limit: write_concern::MAX_WRITE_CONCERN_REPLICAS,
386 extra: VALIDATION_SLOT_CHARGE,
387 },
388 (Evidence, 4) => Messages {
389 context: Acknowledgement,
390 limit: write_concern::MAX_WRITE_CONCERN_REPLICAS,
391 extra: size_of::<write_concern::WriteAcknowledgement>() + MAX_WRITER_BYTES,
392 },
393 (OperationEvidence, 1 | 4 | 5 | 7 | 8) | (Acknowledgement, 1 | 2) => Scalar,
394 (OperationEvidence, 2 | 3 | 9) => Bytes,
395 (OperationEvidence, 6) => Text,
396 _ => return None,
397 })
398}
399
400fn varint(bytes: &[u8], position: &mut usize) -> Result<u64, &'static str> {
401 let mut result = 0;
402 for shift in (0..70).step_by(7) {
403 let byte = *bytes.get(*position).ok_or("truncated varint")?;
404 *position += 1;
405 if shift == 63 && byte > 1 {
406 return Err("overflowing varint");
407 }
408 result |= u64::from(byte & 127) << shift;
409 if byte & 128 == 0 {
410 return Ok(result);
411 }
412 }
413 Err("overflowing varint")
414}
415
416fn walk(
417 bytes: &[u8],
418 context: Context,
419 budget: &mut Budget,
420 shape: Option<&RpcResponseShape>,
421) -> Result<(), &'static str> {
422 budget.charge(context.size(), false)?;
423 let mut position = 0;
424 let mut counts = [0usize; 12];
425 let mut variants = 0usize;
426 while position < bytes.len() {
427 let tag = varint(bytes, &mut position)?;
428 let field = tag >> 3;
429 if field == 0 {
430 return Err("invalid field");
431 }
432 if (context == Context::Request && (2..=9).contains(&field))
433 || (context == Context::Cell && (1..=4).contains(&field))
434 {
435 variants += 1;
436 if variants > 1 {
437 return Err("duplicate RPC oneof");
438 }
439 }
440 let kind = schema(context, field);
441 if let Some(kind) = kind {
442 let expected_wire = if matches!(kind, Field::Scalar) { 0 } else { 2 };
443 if tag & 7 != expected_wire {
444 return Err("invalid known application field wire type");
445 }
446 let count = counts
447 .get_mut(field as usize)
448 .ok_or("invalid known field")?;
449 *count = count.checked_add(1).ok_or("decoded collection overflow")?;
450 let repeated = match kind {
451 Field::Texts { limit, extra } => Some((limit, size_of::<String>() + extra)),
452 Field::Messages {
453 context,
454 limit,
455 extra,
456 } => Some((limit, context.size() + extra)),
457 Field::Map { limit } => Some((limit, MAP_SLOT_CHARGE)),
458 _ => None,
459 };
460 if repeated.is_none() && *count > 1 {
461 return Err("duplicate RPC field");
462 }
463 if let Some((limit, element)) = repeated {
464 if *count > limit {
465 return Err("invalid or excessive repeated field");
466 }
467 budget.repeated(*count, element)?;
468 }
469 if context == Context::Row && field == 1 {
470 budget.report.query_cells = budget
471 .report
472 .query_cells
473 .checked_add(1)
474 .ok_or("decoded cell overflow")?;
475 }
476 }
477 match tag & 7 {
478 0 => {
479 varint(bytes, &mut position)?;
480 }
481 1 => {
482 position = position.checked_add(8).ok_or("overflow")?;
483 }
484 5 => {
485 position = position.checked_add(4).ok_or("overflow")?;
486 }
487 2 => {
488 let length =
489 usize::try_from(varint(bytes, &mut position)?).map_err(|_| "overflow")?;
490 let end = position.checked_add(length).ok_or("overflow")?;
491 let inner = bytes.get(position..end).ok_or("truncated field")?;
492 match kind {
493 Some(Field::Text | Field::Texts { .. }) => {
494 std::str::from_utf8(inner).map_err(|_| "invalid application UTF-8")?;
495 budget.payload(inner.len())?;
496 }
497 Some(Field::Bytes) => {
498 if matches!((context, field), (Context::Put, 5) | (Context::Delete, 3))
499 && inner.len() > write_concern::MAX_WRITE_CONCERN_POLICY_BYTES
500 {
501 return Err("write concern policy exceeds byte limit");
502 }
503 budget.payload(inner.len())?;
504 }
505 Some(
506 Field::Message(nested)
507 | Field::Messages {
508 context: nested, ..
509 },
510 ) => {
511 walk(inner, nested, budget, shape)?;
512 }
513 Some(Field::Map { .. }) => walk(inner, Context::MapEntry, budget, shape)?,
514 Some(Field::ReceiptBytes) => {
515 budget.payload(inner.len())?;
516 walk(inner, Context::Receipt, budget, shape)?;
519 write_concern::preflight_result(inner)
520 .map_err(|_| "invalid write concern receipt")?;
521 }
522 _ => {}
523 }
524 position = end;
525 }
526 _ => return Err("invalid wire type"),
527 }
528 if position > bytes.len() {
529 return Err("truncated field");
530 }
531 }
532 if context == Context::Row {
533 budget.wrong_row_width |= shape
534 .and_then(|shape| shape.columns)
535 .is_some_and(|columns| counts[1] != columns);
536 }
537 if context == Context::QueryResult {
538 budget.columns = counts[1];
539 budget.rows = counts[2];
540 }
541 if context == Context::Request && (variants != 1 || counts[1] != 1) {
542 return Err("RPC request requires a table and exactly one operation");
543 }
544 if context == Context::Response && counts[1] == 0 {
545 if let Some(shape) = shape {
546 let (reads, mutations, query, inspection, scans) = match shape.operation {
547 Operation::Read(reads) => (reads, 0, 0, 0, false),
548 Operation::Mutation(mutations) => (0, mutations, 0, 0, false),
549 Operation::Query => (0, 0, 1, 0, false),
550 Operation::Inspect => (0, 0, 0, 1, false),
551 Operation::Scan => (0, 0, 0, 0, true),
552 Operation::Unsupported => (0, 0, 0, 0, false),
553 };
554 if counts[2] != reads
555 || counts[3] != mutations
556 || counts[4] != query
557 || counts[5] != inspection
558 || (!scans && counts[6] != 0)
559 || (query == 1
560 && (shape
561 .columns
562 .is_some_and(|columns| columns != budget.columns)
563 || budget.rows > shape.rows
564 || (shape.aggregate && budget.rows != 1)
565 || budget.wrong_row_width))
566 {
567 return Err("decoded response does not match pending request shape");
568 }
569 }
570 }
571 Ok(())
572}