1extern crate alloc;
59
60use alloc::string::{String, ToString};
61use alloc::vec::Vec;
62
63use crate::error::{RpcError, RpcResult};
64
65pub const MAX_HEADER_BYTES: usize = 64 * 1024;
67
68pub const MAX_STRING_LEN: u32 = 8 * 1024;
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
77pub struct SampleIdentity {
78 pub writer_guid: [u8; 16],
80 pub sequence_number: u64,
82}
83
84impl SampleIdentity {
85 #[must_use]
87 pub const fn new(writer_guid: [u8; 16], sequence_number: u64) -> Self {
88 Self {
89 writer_guid,
90 sequence_number,
91 }
92 }
93
94 pub const UNKNOWN: Self = Self {
96 writer_guid: [0u8; 16],
97 sequence_number: 0,
98 };
99
100 #[must_use]
102 pub fn to_cdr_le(&self) -> Vec<u8> {
103 let mut out = Vec::with_capacity(24);
104 encode_sample_identity(&mut out, self, true);
105 out
106 }
107
108 #[must_use]
110 pub fn to_cdr_be(&self) -> Vec<u8> {
111 let mut out = Vec::with_capacity(24);
112 encode_sample_identity(&mut out, self, false);
113 out
114 }
115
116 pub fn from_cdr_le(bytes: &[u8]) -> RpcResult<Self> {
121 check_cap(bytes)?;
122 let mut cur = Cursor::new(bytes);
123 cur.read_sample_identity(true)
124 }
125
126 pub fn from_cdr_be(bytes: &[u8]) -> RpcResult<Self> {
131 check_cap(bytes)?;
132 let mut cur = Cursor::new(bytes);
133 cur.read_sample_identity(false)
134 }
135}
136
137#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
143#[repr(u32)]
144pub enum RemoteExceptionCode {
145 #[default]
147 Ok = 0,
148 Unsupported = 1,
150 InvalidArgument = 2,
152 OutOfResources = 3,
154 UnknownOperation = 4,
156 UnknownException = 5,
159 UnknownInterface = 6,
161}
162
163impl RemoteExceptionCode {
164 pub fn from_u32(v: u32) -> RpcResult<Self> {
169 match v {
170 0 => Ok(Self::Ok),
171 1 => Ok(Self::Unsupported),
172 2 => Ok(Self::InvalidArgument),
173 3 => Ok(Self::OutOfResources),
174 4 => Ok(Self::UnknownOperation),
175 5 => Ok(Self::UnknownException),
176 6 => Ok(Self::UnknownInterface),
177 other => Err(RpcError::UnknownExceptionCode(other)),
178 }
179 }
180
181 #[must_use]
183 pub const fn as_u32(self) -> u32 {
184 self as u32
185 }
186}
187
188#[derive(Debug, Clone, PartialEq, Eq, Default)]
194pub struct RequestHeader {
195 pub request_id: SampleIdentity,
197 pub instance_name: String,
199}
200
201impl RequestHeader {
202 #[must_use]
204 pub fn new(request_id: SampleIdentity, instance_name: impl Into<String>) -> Self {
205 Self {
206 request_id,
207 instance_name: instance_name.into(),
208 }
209 }
210
211 #[must_use]
213 pub fn to_cdr_le(&self) -> Vec<u8> {
214 encode_request_header(self, true)
215 }
216
217 #[must_use]
219 pub fn to_cdr_be(&self) -> Vec<u8> {
220 encode_request_header(self, false)
221 }
222
223 pub fn from_cdr_le(bytes: &[u8]) -> RpcResult<Self> {
228 check_cap(bytes)?;
229 let mut cur = Cursor::new(bytes);
230 let request_id = cur.read_sample_identity(true)?;
231 let instance_name = cur.read_string(true)?;
232 Ok(Self {
233 request_id,
234 instance_name,
235 })
236 }
237
238 pub fn from_cdr_be(bytes: &[u8]) -> RpcResult<Self> {
243 check_cap(bytes)?;
244 let mut cur = Cursor::new(bytes);
245 let request_id = cur.read_sample_identity(false)?;
246 let instance_name = cur.read_string(false)?;
247 Ok(Self {
248 request_id,
249 instance_name,
250 })
251 }
252}
253
254#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
260pub struct ReplyHeader {
261 pub related_request_id: SampleIdentity,
263 pub remote_ex: RemoteExceptionCode,
265}
266
267impl ReplyHeader {
268 #[must_use]
270 pub const fn new(related_request_id: SampleIdentity, remote_ex: RemoteExceptionCode) -> Self {
271 Self {
272 related_request_id,
273 remote_ex,
274 }
275 }
276
277 #[must_use]
279 pub fn to_cdr_le(&self) -> Vec<u8> {
280 encode_reply_header(self, true)
281 }
282
283 #[must_use]
285 pub fn to_cdr_be(&self) -> Vec<u8> {
286 encode_reply_header(self, false)
287 }
288
289 pub fn from_cdr_le(bytes: &[u8]) -> RpcResult<Self> {
295 check_cap(bytes)?;
296 let mut cur = Cursor::new(bytes);
297 let related_request_id = cur.read_sample_identity(true)?;
298 let raw = cur.read_u32(true)?;
299 let remote_ex = RemoteExceptionCode::from_u32(raw)?;
300 Ok(Self {
301 related_request_id,
302 remote_ex,
303 })
304 }
305
306 pub fn from_cdr_be(bytes: &[u8]) -> RpcResult<Self> {
312 check_cap(bytes)?;
313 let mut cur = Cursor::new(bytes);
314 let related_request_id = cur.read_sample_identity(false)?;
315 let raw = cur.read_u32(false)?;
316 let remote_ex = RemoteExceptionCode::from_u32(raw)?;
317 Ok(Self {
318 related_request_id,
319 remote_ex,
320 })
321 }
322}
323
324fn check_cap(bytes: &[u8]) -> RpcResult<()> {
339 if bytes.len() > MAX_HEADER_BYTES {
340 return Err(RpcError::PayloadTooLarge {
341 got: bytes.len(),
342 max: MAX_HEADER_BYTES,
343 });
344 }
345 Ok(())
346}
347
348fn align_to(out: &mut Vec<u8>, n: usize) {
349 let pad = (n - out.len() % n) % n;
350 for _ in 0..pad {
351 out.push(0);
352 }
353}
354
355fn encode_u32(out: &mut Vec<u8>, v: u32, le: bool) {
356 align_to(out, 4);
357 if le {
358 out.extend_from_slice(&v.to_le_bytes());
359 } else {
360 out.extend_from_slice(&v.to_be_bytes());
361 }
362}
363
364fn encode_u64_xcdr2(out: &mut Vec<u8>, v: u64, le: bool) {
365 align_to(out, 4);
367 if le {
368 out.extend_from_slice(&v.to_le_bytes());
369 } else {
370 out.extend_from_slice(&v.to_be_bytes());
371 }
372}
373
374fn encode_string(out: &mut Vec<u8>, s: &str, le: bool) {
375 let bytes = s.as_bytes();
376 let len = (bytes.len() + 1) as u32;
377 encode_u32(out, len, le);
378 out.extend_from_slice(bytes);
379 out.push(0);
380}
381
382fn encode_sample_identity(out: &mut Vec<u8>, id: &SampleIdentity, le: bool) {
383 out.extend_from_slice(&id.writer_guid);
385 encode_u64_xcdr2(out, id.sequence_number, le);
386}
387
388fn encode_request_header(h: &RequestHeader, le: bool) -> Vec<u8> {
389 let mut out = Vec::with_capacity(64);
390 encode_sample_identity(&mut out, &h.request_id, le);
391 encode_string(&mut out, &h.instance_name, le);
392 out
393}
394
395fn encode_reply_header(h: &ReplyHeader, le: bool) -> Vec<u8> {
396 let mut out = Vec::with_capacity(32);
397 encode_sample_identity(&mut out, &h.related_request_id, le);
398 encode_u32(&mut out, h.remote_ex.as_u32(), le);
399 out
400}
401
402struct Cursor<'a> {
403 buf: &'a [u8],
404 pos: usize,
405}
406
407impl<'a> Cursor<'a> {
408 fn new(buf: &'a [u8]) -> Self {
409 Self { buf, pos: 0 }
410 }
411
412 fn align_to(&mut self, n: usize) {
413 let pad = (n - self.pos % n) % n;
414 self.pos = self.pos.saturating_add(pad);
415 }
416
417 fn ensure(&self, need: usize) -> RpcResult<()> {
418 if self.pos.saturating_add(need) > self.buf.len() {
419 return Err(RpcError::codec("truncated buffer"));
420 }
421 Ok(())
422 }
423
424 fn read_u32(&mut self, le: bool) -> RpcResult<u32> {
425 self.align_to(4);
426 self.ensure(4)?;
427 let raw = [
428 self.buf[self.pos],
429 self.buf[self.pos + 1],
430 self.buf[self.pos + 2],
431 self.buf[self.pos + 3],
432 ];
433 self.pos += 4;
434 Ok(if le {
435 u32::from_le_bytes(raw)
436 } else {
437 u32::from_be_bytes(raw)
438 })
439 }
440
441 fn read_u64_xcdr2(&mut self, le: bool) -> RpcResult<u64> {
442 self.align_to(4);
444 self.ensure(8)?;
445 let mut raw = [0u8; 8];
446 raw.copy_from_slice(&self.buf[self.pos..self.pos + 8]);
447 self.pos += 8;
448 Ok(if le {
449 u64::from_le_bytes(raw)
450 } else {
451 u64::from_be_bytes(raw)
452 })
453 }
454
455 fn read_string(&mut self, le: bool) -> RpcResult<String> {
456 let len = self.read_u32(le)?;
457 if len > MAX_STRING_LEN {
458 return Err(RpcError::codec("string exceeds cap"));
459 }
460 if len == 0 {
461 return Err(RpcError::codec("zero-length string body"));
462 }
463 self.ensure(len as usize)?;
464 let body = &self.buf[self.pos..self.pos + len as usize];
465 self.pos += len as usize;
466 if body.last() != Some(&0) {
467 return Err(RpcError::codec("string missing trailing NUL"));
468 }
469 let rest = &body[..body.len() - 1];
470 let s = core::str::from_utf8(rest)
471 .map_err(|_| RpcError::codec("string not UTF-8"))?
472 .to_string();
473 Ok(s)
474 }
475
476 fn read_sample_identity(&mut self, le: bool) -> RpcResult<SampleIdentity> {
477 self.ensure(16)?;
478 let mut writer_guid = [0u8; 16];
479 writer_guid.copy_from_slice(&self.buf[self.pos..self.pos + 16]);
480 self.pos += 16;
481 let sequence_number = self.read_u64_xcdr2(le)?;
482 Ok(SampleIdentity {
483 writer_guid,
484 sequence_number,
485 })
486 }
487}
488
489#[cfg(test)]
490#[allow(clippy::unwrap_used, clippy::expect_used)]
491mod tests {
492 use super::*;
493
494 fn sample_id() -> SampleIdentity {
495 SampleIdentity::new(
496 [
497 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
498 0x0F, 0x10,
499 ],
500 0xDEAD_BEEF_CAFE_BABE,
501 )
502 }
503
504 #[test]
505 fn sample_identity_roundtrip_le() {
506 let id = sample_id();
507 let bytes = id.to_cdr_le();
508 assert_eq!(bytes.len(), 24);
509 let back = SampleIdentity::from_cdr_le(&bytes).unwrap();
510 assert_eq!(id, back);
511 }
512
513 #[test]
514 fn sample_identity_roundtrip_be() {
515 let id = sample_id();
516 let bytes = id.to_cdr_be();
517 let back = SampleIdentity::from_cdr_be(&bytes).unwrap();
518 assert_eq!(id, back);
519 }
520
521 #[test]
522 fn sample_identity_le_be_streams_differ() {
523 let id = sample_id();
524 let le = id.to_cdr_le();
525 let be = id.to_cdr_be();
526 assert_ne!(le, be);
527 }
528
529 #[test]
530 fn sample_identity_unknown_constant_is_zero() {
531 let id = SampleIdentity::UNKNOWN;
532 assert_eq!(id.writer_guid, [0u8; 16]);
533 assert_eq!(id.sequence_number, 0);
534 }
535
536 #[test]
537 fn sample_identity_truncated_buffer_is_error() {
538 let bytes = vec![0u8; 23];
539 let err = SampleIdentity::from_cdr_le(&bytes).unwrap_err();
540 assert!(matches!(err, RpcError::Codec(_)));
541 }
542
543 #[test]
544 fn request_header_roundtrip_le() {
545 let h = RequestHeader::new(sample_id(), "calc-instance-1");
546 let bytes = h.to_cdr_le();
547 let back = RequestHeader::from_cdr_le(&bytes).unwrap();
548 assert_eq!(h, back);
549 }
550
551 #[test]
552 fn request_header_roundtrip_be() {
553 let h = RequestHeader::new(sample_id(), "calc-instance-1");
554 let bytes = h.to_cdr_be();
555 let back = RequestHeader::from_cdr_be(&bytes).unwrap();
556 assert_eq!(h, back);
557 }
558
559 #[test]
560 fn request_header_empty_instance_name_roundtrip() {
561 let h = RequestHeader::new(sample_id(), "");
562 let bytes = h.to_cdr_le();
563 let back = RequestHeader::from_cdr_le(&bytes).unwrap();
564 assert_eq!(h, back);
565 assert!(back.instance_name.is_empty());
566 }
567
568 #[test]
569 fn request_header_string_missing_nul_rejected() {
570 let mut bytes = sample_id().to_cdr_le();
572 bytes.extend_from_slice(&1u32.to_le_bytes());
573 bytes.push(b'A');
574 let err = RequestHeader::from_cdr_le(&bytes).unwrap_err();
575 assert!(matches!(err, RpcError::Codec(_)));
576 }
577
578 #[test]
579 fn request_header_zero_length_string_rejected() {
580 let mut bytes = sample_id().to_cdr_le();
581 bytes.extend_from_slice(&0u32.to_le_bytes());
582 let err = RequestHeader::from_cdr_le(&bytes).unwrap_err();
583 assert!(matches!(err, RpcError::Codec(_)));
584 }
585
586 #[test]
587 fn request_header_invalid_utf8_rejected() {
588 let mut bytes = sample_id().to_cdr_le();
589 bytes.extend_from_slice(&3u32.to_le_bytes());
591 bytes.extend_from_slice(&[0xFF, 0xFE, 0x00]);
592 let err = RequestHeader::from_cdr_le(&bytes).unwrap_err();
593 assert!(matches!(err, RpcError::Codec(_)));
594 }
595
596 #[test]
597 fn reply_header_roundtrip_all_codes() {
598 for code in [
599 RemoteExceptionCode::Ok,
600 RemoteExceptionCode::Unsupported,
601 RemoteExceptionCode::InvalidArgument,
602 RemoteExceptionCode::OutOfResources,
603 RemoteExceptionCode::UnknownOperation,
604 RemoteExceptionCode::UnknownException,
605 RemoteExceptionCode::UnknownInterface,
606 ] {
607 let h = ReplyHeader::new(sample_id(), code);
608 let le = h.to_cdr_le();
609 let be = h.to_cdr_be();
610 assert_eq!(h, ReplyHeader::from_cdr_le(&le).unwrap());
611 assert_eq!(h, ReplyHeader::from_cdr_be(&be).unwrap());
612 }
613 }
614
615 #[test]
616 fn reply_header_unknown_discriminator_is_error() {
617 let mut bytes = sample_id().to_cdr_le();
618 bytes.extend_from_slice(&999u32.to_le_bytes());
619 let err = ReplyHeader::from_cdr_le(&bytes).unwrap_err();
620 assert_eq!(err, RpcError::UnknownExceptionCode(999));
621 }
622
623 #[test]
624 fn remote_exception_code_as_u32_round_trips() {
625 for v in 0u32..=6 {
626 let code = RemoteExceptionCode::from_u32(v).unwrap();
627 assert_eq!(code.as_u32(), v);
628 }
629 }
630
631 #[test]
632 fn remote_exception_code_default_is_ok() {
633 assert_eq!(RemoteExceptionCode::default(), RemoteExceptionCode::Ok);
634 }
635
636 #[test]
637 fn dos_cap_rejects_oversized_buffer() {
638 let big = vec![0u8; MAX_HEADER_BYTES + 1];
639 let err = RequestHeader::from_cdr_le(&big).unwrap_err();
640 assert!(matches!(
641 err,
642 RpcError::PayloadTooLarge {
643 got: _,
644 max: MAX_HEADER_BYTES
645 }
646 ));
647 }
648
649 #[test]
650 fn xcdr2_layout_sample_identity_le_is_24_bytes_no_padding() {
651 let id = SampleIdentity::new([0xAB; 16], 0x0102_0304_0506_0708);
654 let bytes = id.to_cdr_le();
655 assert_eq!(bytes.len(), 24);
656 assert_eq!(&bytes[16..24], &0x0102_0304_0506_0708u64.to_le_bytes());
658 }
659
660 #[test]
661 fn xcdr2_layout_string_includes_nul() {
662 let h = RequestHeader::new(SampleIdentity::UNKNOWN, "A");
664 let bytes = h.to_cdr_le();
665 assert_eq!(bytes.len(), 30);
668 assert_eq!(&bytes[24..28], &2u32.to_le_bytes());
669 assert_eq!(bytes[28], b'A');
670 assert_eq!(bytes[29], 0);
671 }
672}