use crate::{Endianness, WireError};
use alloc::format;
use alloc::vec::Vec;
use zerodds_float128::Float128;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PlCdr1Framing {
pub pid_flags: PidFlagPolicy,
pub pad_declared_length: bool,
pub pad_declared_optional_length: bool,
pub member_must_understand: bool,
pub highest_short_id: u16,
pub require_extended: bool,
pub pad_after_value: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PlCdr1Reading {
#[default]
Strict,
Tolerant,
}
impl PlCdr1Reading {
#[must_use]
pub const fn is_tolerant(self) -> bool {
matches!(self, Self::Tolerant)
}
}
impl PlCdr1Framing {
pub const DEFAULT: Self = Self {
pid_flags: PidFlagPolicy::SentinelMustUnderstand,
highest_short_id: PID_SHORT_MAX_ID,
pad_declared_length: false,
pad_declared_optional_length: false,
member_must_understand: true,
require_extended: false,
pad_after_value: true,
};
#[must_use]
pub const fn pads_declared_length(self, role: ParameterRole) -> bool {
match role {
ParameterRole::Member => self.pad_declared_length,
ParameterRole::Optional => self.pad_declared_optional_length,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum WcharWidth {
#[default]
Two,
Four,
}
impl WcharWidth {
#[must_use]
pub const fn bytes(self) -> usize {
match self {
Self::Two => 2,
Self::Four => 4,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FixedDigits {
#[default]
Declared,
ValuePrecision,
ValueStringDigits,
}
impl FixedDigits {
#[must_use]
pub const fn wire_digits(self, declared: u8, significant: u8, string_digits: u8) -> u8 {
match self {
Self::Declared => declared,
Self::ValuePrecision => {
if significant == 0 {
1
} else {
significant
}
}
Self::ValueStringDigits => {
if string_digits == 0 {
1
} else if string_digits % 2 == 0 {
string_digits + 1
} else {
string_digits
}
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum WcharFraming {
#[default]
Fixed,
GiopLengthPrefixed,
GiopLengthPrefixedMemberOnly,
}
impl WcharFraming {
#[must_use]
pub const fn has_length_octet(self, position: WcharPosition) -> bool {
match self {
Self::Fixed => false,
Self::GiopLengthPrefixed => true,
Self::GiopLengthPrefixedMemberOnly => matches!(position, WcharPosition::Member),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum WirePosition {
#[default]
Member,
Element,
Sentinel,
ExtendedMarker,
}
pub type WcharPosition = WirePosition;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PidFlagPolicy {
#[default]
SentinelMustUnderstand,
Bare,
ImplExtensionOnMembers,
}
impl PidFlagPolicy {
#[must_use]
pub const fn flags_at(self, position: WirePosition) -> u16 {
match (self, position) {
(
Self::SentinelMustUnderstand,
WirePosition::Sentinel | WirePosition::ExtendedMarker,
) => PID_MUST_UNDERSTAND,
(Self::ImplExtensionOnMembers, WirePosition::Member | WirePosition::ExtendedMarker) => {
PID_IMPL_EXTENSION | PID_MUST_UNDERSTAND
}
_ => 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum WstringUnitOrder {
#[default]
Stream,
BigEndianAlways,
ByteOrderMark,
HostUnswapped,
}
impl WstringUnitOrder {
#[must_use]
pub const fn has_mark(self) -> bool {
matches!(self, Self::ByteOrderMark)
}
#[must_use]
pub const fn writes_little_endian_units(self, _position: WirePosition) -> bool {
matches!(self, Self::ByteOrderMark | Self::HostUnswapped)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum WstringLengthCounts {
#[default]
Octets,
Characters,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum WstringTerminator {
#[default]
Absent,
Present,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum WstringUnitWidth {
#[default]
Two,
Four,
}
impl WstringUnitWidth {
#[must_use]
pub const fn octets(self) -> usize {
match self {
Self::Two => 2,
Self::Four => 4,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct WstringLengthPolicy {
pub counts: WstringLengthCounts,
pub terminator: WstringTerminator,
pub unit: WstringUnitWidth,
}
impl WstringLengthPolicy {
#[must_use]
pub const fn new(
counts: WstringLengthCounts,
terminator: WstringTerminator,
unit: WstringUnitWidth,
) -> Self {
Self {
counts,
terminator,
unit,
}
}
pub const SPECIFICATION: Self = Self::new(
WstringLengthCounts::Octets,
WstringTerminator::Absent,
WstringUnitWidth::Two,
);
pub const CHARACTER_COUNT: Self = Self::new(
WstringLengthCounts::Characters,
WstringTerminator::Absent,
WstringUnitWidth::Two,
);
pub const CHARACTER_COUNT_TERMINATED: Self = Self::new(
WstringLengthCounts::Characters,
WstringTerminator::Present,
WstringUnitWidth::Four,
);
pub const SPECIFICATION_WIDE_UNITS: Self = Self::new(
WstringLengthCounts::Octets,
WstringTerminator::Absent,
WstringUnitWidth::Four,
);
pub const ROS_TYPE_SUPPORT: Self = Self::new(
WstringLengthCounts::Characters,
WstringTerminator::Absent,
WstringUnitWidth::Four,
);
pub const ALL_CELLS: [Self; 8] = [
Self::new(
WstringLengthCounts::Octets,
WstringTerminator::Absent,
WstringUnitWidth::Two,
),
Self::new(
WstringLengthCounts::Octets,
WstringTerminator::Absent,
WstringUnitWidth::Four,
),
Self::new(
WstringLengthCounts::Octets,
WstringTerminator::Present,
WstringUnitWidth::Two,
),
Self::new(
WstringLengthCounts::Octets,
WstringTerminator::Present,
WstringUnitWidth::Four,
),
Self::new(
WstringLengthCounts::Characters,
WstringTerminator::Absent,
WstringUnitWidth::Two,
),
Self::new(
WstringLengthCounts::Characters,
WstringTerminator::Absent,
WstringUnitWidth::Four,
),
Self::new(
WstringLengthCounts::Characters,
WstringTerminator::Present,
WstringUnitWidth::Two,
),
Self::new(
WstringLengthCounts::Characters,
WstringTerminator::Present,
WstringUnitWidth::Four,
),
];
#[must_use]
pub const fn is_default(self) -> bool {
matches!(
(self.counts, self.terminator, self.unit),
(
WstringLengthCounts::Octets,
WstringTerminator::Absent,
WstringUnitWidth::Two
)
)
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match (self.counts, self.terminator, self.unit) {
(WstringLengthCounts::Octets, WstringTerminator::Absent, WstringUnitWidth::Two) => {
"specification"
}
(WstringLengthCounts::Octets, WstringTerminator::Absent, WstringUnitWidth::Four) => {
"specification-wide-units"
}
(WstringLengthCounts::Characters, WstringTerminator::Absent, WstringUnitWidth::Two) => {
"character-count"
}
(
WstringLengthCounts::Characters,
WstringTerminator::Absent,
WstringUnitWidth::Four,
) => "ros-type-support",
(
WstringLengthCounts::Characters,
WstringTerminator::Present,
WstringUnitWidth::Four,
) => "character-count-terminated",
(WstringLengthCounts::Octets, WstringTerminator::Present, WstringUnitWidth::Two) => {
"octets/terminated/2"
}
(WstringLengthCounts::Octets, WstringTerminator::Present, WstringUnitWidth::Four) => {
"octets/terminated/4"
}
(
WstringLengthCounts::Characters,
WstringTerminator::Present,
WstringUnitWidth::Two,
) => "characters/terminated/2",
}
}
#[must_use]
pub const fn flag(self) -> &'static str {
match (self.counts, self.terminator, self.unit) {
(WstringLengthCounts::Octets, WstringTerminator::Absent, WstringUnitWidth::Two) => {
"--wstring-length specification"
}
(WstringLengthCounts::Octets, WstringTerminator::Absent, WstringUnitWidth::Four) => {
"--wstring-length specification-wide-units"
}
(WstringLengthCounts::Characters, WstringTerminator::Absent, WstringUnitWidth::Two) => {
"--wstring-length character-count"
}
(
WstringLengthCounts::Characters,
WstringTerminator::Absent,
WstringUnitWidth::Four,
) => "--wstring-length ros-type-support",
(
WstringLengthCounts::Characters,
WstringTerminator::Present,
WstringUnitWidth::Four,
) => "--wstring-length character-count-terminated",
(WstringLengthCounts::Octets, WstringTerminator::Present, WstringUnitWidth::Two) => {
"--wstring-length octets/terminated/2"
}
(WstringLengthCounts::Octets, WstringTerminator::Present, WstringUnitWidth::Four) => {
"--wstring-length octets/terminated/4"
}
(
WstringLengthCounts::Characters,
WstringTerminator::Present,
WstringUnitWidth::Two,
) => "--wstring-length characters/terminated/2",
}
}
#[must_use]
pub const fn counts_at(self, _position: WirePosition) -> WstringLengthCounts {
self.counts
}
#[must_use]
pub const fn terminator_at(self, _position: WirePosition) -> WstringTerminator {
self.terminator
}
#[must_use]
pub const fn unit_at(self, _position: WirePosition) -> WstringUnitWidth {
self.unit
}
#[must_use]
pub const fn character_width(self, position: WirePosition) -> usize {
self.unit_at(position).octets()
}
#[must_use]
pub const fn declared_length(self, units: usize, position: WirePosition) -> Option<u32> {
let counted = match self.counts_at(position) {
WstringLengthCounts::Octets => {
match units.checked_mul(self.character_width(position)) {
Some(counted) => counted,
None => return None,
}
}
WstringLengthCounts::Characters => units,
};
if counted > u32::MAX as usize {
return None;
}
#[allow(clippy::cast_possible_truncation)]
Some(counted as u32)
}
#[must_use]
pub const fn admits_length(self, declared: usize) -> bool {
if matches!(self.counts, WstringLengthCounts::Octets) && declared % self.unit.octets() != 0
{
return false;
}
if matches!(self.terminator, WstringTerminator::Present) && declared < 1 {
return false;
}
true
}
}
pub(crate) const BYTE_ORDER_MARK: u16 = 0xFEFF;
pub(crate) const SWAPPED_BYTE_ORDER_MARK: u16 = 0xFFFE;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ParameterRole {
#[default]
Member,
Optional,
}
impl Default for PlCdr1Framing {
fn default() -> Self {
Self::DEFAULT
}
}
const PID_MUST_UNDERSTAND: u16 = 0x4000;
pub const E_FLAG_MUST_UNDERSTAND: u32 = 0x4000_0000;
pub const E_FLAG_IMPL_EXTENSION: u32 = 0x8000_0000;
pub const PID_IMPL_EXTENSION: u16 = 0x8000;
pub const PID_IGNORE: u16 = 0x3F03;
pub const E_MEMBER_ID_MASK: u32 = 0x0FFF_FFFF;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FixedParts<'a> {
pub negative: bool,
pub whole: &'a str,
pub fraction: &'a str,
}
pub fn parse_fixed(
text: &str,
digits: u8,
scale: u8,
at: usize,
) -> Result<FixedParts<'_>, WireError> {
if digits == 0 {
return Err(WireError::refused(at, "a fixed with at least one digit"));
}
if digits > 31 {
return Err(WireError::refused(at, "a fixed of at most 31 digits"));
}
if scale > digits {
return Err(WireError::refused(at, "a scale no larger than the digits"));
}
let (negative, magnitude) = match text.strip_prefix('-') {
Some(rest) => (true, rest),
None => (false, text.strip_prefix('+').unwrap_or(text)),
};
let (whole, fraction) = match magnitude.split_once('.') {
Some((w, f)) => (w, f),
None => (magnitude, ""),
};
if whole.is_empty() && fraction.is_empty() {
return Err(WireError::refused(at, "a decimal with at least one digit"));
}
if !whole
.bytes()
.chain(fraction.bytes())
.all(|b| b.is_ascii_digit())
{
return Err(WireError::refused(at, "a decimal of digits and one point"));
}
let whole = whole.trim_start_matches('0');
if whole.len() > usize::from(digits - scale) {
return Err(WireError::refused(
at,
"a decimal whose integer part fits the declared digits",
));
}
if fraction.len() > usize::from(scale) {
return Err(WireError::refused(
at,
"a decimal whose fraction fits the declared scale",
));
}
Ok(FixedParts {
negative,
whole,
fraction,
})
}
fn fixed_nibbles(
text: &str,
digits: u8,
scale: u8,
policy: FixedDigits,
at: usize,
) -> Result<Vec<u8>, WireError> {
let parts = parse_fixed(text, digits, scale, at)?;
let integral_width = usize::from(digits - scale);
let mut all = Vec::with_capacity(usize::from(digits));
all.extend(core::iter::repeat_n(
0u8,
integral_width - parts.whole.len(),
));
all.extend(parts.whole.bytes().map(|byte| byte - b'0'));
all.extend(parts.fraction.bytes().map(|byte| byte - b'0'));
all.extend(core::iter::repeat_n(
0u8,
usize::from(scale) - parts.fraction.len(),
));
let significant =
u8::try_from(all.len() - all.iter().take_while(|n| **n == 0).count()).unwrap_or(u8::MAX);
let magnitude = text.trim_start_matches(['+', '-']);
let whole_as_written = magnitude.split('.').next().map_or(0, str::len);
let written = whole_as_written + usize::from(scale);
if policy == FixedDigits::ValueStringDigits && written + (1 - written % 2) > 31 {
return Err(WireError::refused(
at,
"a fixed of at most 31 digits on the wire, as written",
));
}
let string_digits = if policy == FixedDigits::ValueStringDigits {
u8::try_from(written).map_err(|_| {
WireError::refused(at, "a fixed of at most 31 digits on the wire, as written")
})?
} else {
0
};
let wire = usize::from(policy.wire_digits(digits, significant, string_digits));
let kept = &all[all.len().saturating_sub(wire)..];
let mut nibbles = Vec::with_capacity(wire + 2);
if (wire + 1) % 2 == 1 {
nibbles.push(0);
}
nibbles.extend(core::iter::repeat_n(0u8, wire.saturating_sub(kept.len())));
nibbles.extend_from_slice(kept);
nibbles.push(if parts.negative && nibbles.iter().any(|n| *n != 0) {
0x0d
} else {
0x0c
});
Ok(nibbles)
}
#[derive(Debug, Clone)]
pub struct Writer {
out: Vec<u8>,
endianness: Endianness,
pl_cdr1: PlCdr1Framing,
origin: usize,
max_align: usize,
wchar_width: WcharWidth,
wchar_framing: WcharFraming,
wstring_unit_order: WstringUnitOrder,
short_length_codes: bool,
minimal_member_trailing_octet: bool,
padding: Option<Vec<(usize, usize)>>,
deferred: Option<WireError>,
}
impl Writer {
#[must_use]
pub fn new(endianness: Endianness) -> Self {
Self {
out: Vec::new(),
endianness,
pl_cdr1: PlCdr1Framing::default(),
origin: 0,
max_align: 8,
wchar_width: WcharWidth::Two,
wchar_framing: WcharFraming::Fixed,
wstring_unit_order: WstringUnitOrder::Stream,
short_length_codes: false,
minimal_member_trailing_octet: false,
padding: None,
deferred: None,
}
}
#[must_use]
pub fn recording_padding(mut self) -> Self {
self.padding = Some(Vec::new());
self
}
#[must_use]
pub fn padding_spans(&self) -> &[(usize, usize)] {
self.padding.as_deref().unwrap_or(&[])
}
#[must_use]
pub const fn is_recording_padding(&self) -> bool {
self.padding.is_some()
}
fn shift_padding(&mut self, from: usize, delta: isize) {
let Some(spans) = self.padding.as_mut() else {
return;
};
for span in spans.iter_mut() {
if span.0 >= from {
span.0 = span.0.saturating_add_signed(delta);
}
}
}
#[must_use]
pub const fn with_wchar_framing(mut self, framing: WcharFraming) -> Self {
self.wchar_framing = framing;
self
}
#[must_use]
pub const fn wchar_framing(&self) -> WcharFraming {
self.wchar_framing
}
#[must_use]
pub const fn with_wstring_unit_order(mut self, order: WstringUnitOrder) -> Self {
self.wstring_unit_order = order;
self
}
#[must_use]
pub const fn wstring_unit_order(&self) -> WstringUnitOrder {
self.wstring_unit_order
}
#[must_use]
pub const fn with_wchar_width(mut self, width: WcharWidth) -> Self {
self.wchar_width = width;
self
}
#[must_use]
pub const fn wchar_width(&self) -> WcharWidth {
self.wchar_width
}
#[must_use]
pub const fn with_short_length_codes(mut self, shorten: bool) -> Self {
self.short_length_codes = shorten;
self
}
#[must_use]
pub const fn short_length_codes(&self) -> bool {
self.short_length_codes
}
#[must_use]
pub const fn with_pl_cdr1_framing(mut self, framing: PlCdr1Framing) -> Self {
self.pl_cdr1 = framing;
self
}
#[must_use]
pub const fn pl_cdr1_framing(&self) -> PlCdr1Framing {
self.pl_cdr1
}
#[must_use]
pub const fn with_minimal_member_trailing_octet(mut self, on: bool) -> Self {
self.minimal_member_trailing_octet = on;
self
}
#[must_use]
pub const fn minimal_member_trailing_octet(&self) -> bool {
self.minimal_member_trailing_octet
}
#[must_use]
pub fn with_max_align(endianness: Endianness, max_align: usize) -> Self {
Self {
max_align: max_align.max(1),
..Self::new(endianness)
}
}
#[must_use]
pub fn appending(endianness: Endianness, max_align: usize, out: Vec<u8>) -> Self {
Self {
origin: out.len(),
out,
..Self::with_max_align(endianness, max_align)
}
}
#[must_use]
pub fn continuing(endianness: Endianness, max_align: usize, prefix: Vec<u8>) -> Self {
Self {
out: prefix,
..Self::with_max_align(endianness, max_align)
}
}
#[must_use]
pub const fn max_align(&self) -> usize {
self.max_align
}
#[must_use]
pub const fn endianness(&self) -> Endianness {
self.endianness
}
#[must_use]
pub fn big_endian() -> Self {
Self::new(Endianness::Big)
}
#[must_use]
pub fn little_endian() -> Self {
Self::new(Endianness::Little)
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.out
}
#[must_use]
pub fn finish(self) -> Vec<u8> {
self.out
}
#[must_use]
pub fn position(&self) -> usize {
self.out.len()
}
pub fn align_to(&mut self, align: usize) {
let align = align.min(self.max_align);
if align <= 1 {
return;
}
let offset = self.out.len().saturating_sub(self.origin);
let padding = (align - (offset % align)) % align;
if padding == 0 {
return;
}
if let Some(spans) = self.padding.as_mut() {
spans.push((self.out.len(), padding));
}
for _ in 0..padding {
self.out.push(0);
}
}
pub fn begin_packed_element(&mut self) -> usize {
let saved = self.origin;
self.origin = self.out.len();
saved
}
pub fn finish_packed_element(&mut self, saved: usize) {
self.origin = saved;
}
pub fn write_bool(&mut self, value: bool) {
self.out.push(u8::from(value));
}
pub fn write_u8(&mut self, value: u8) {
self.out.push(value);
}
pub fn write_fixed(
&mut self,
text: &str,
digits: u8,
scale: u8,
policy: FixedDigits,
) -> Result<(), WireError> {
let nibbles = fixed_nibbles(text, digits, scale, policy, self.position())?;
for pair in nibbles.chunks_exact(2) {
self.write_u8((pair[0] << 4) | pair[1]);
}
Ok(())
}
pub fn write_i8(&mut self, value: i8) {
#[allow(clippy::cast_sign_loss)]
self.out.push(value as u8);
}
pub fn write_char(&mut self, value: char) -> Result<(), WireError> {
let point = u32::from(value);
let byte = u8::try_from(point).map_err(|_| WireError::OutOfRange {
at: self.out.len(),
expected: format!("a character below U+0100, not U+{point:04X}"),
})?;
self.out.push(byte);
Ok(())
}
pub fn write_wchar(&mut self, value: char, position: WcharPosition) -> Result<(), WireError> {
let point = u32::from(value);
let unit = u16::try_from(point).map_err(|_| WireError::OutOfRange {
at: self.out.len(),
expected: format!("a BMP character, not U+{point:04X}"),
})?;
if self.wchar_framing.has_length_octet(position) {
let width = self.wchar_width.bytes();
#[allow(clippy::cast_possible_truncation)]
self.out.push(width as u8);
let big = !matches!(self.wstring_unit_order, WstringUnitOrder::Stream);
match (self.wchar_width, big) {
(WcharWidth::Two, false) => self.extend(&self.order2(unit)),
(WcharWidth::Two, true) => self.extend(&unit.to_be_bytes()),
(WcharWidth::Four, false) => self.extend(&self.order4(u32::from(unit))),
(WcharWidth::Four, true) => self.extend(&u32::from(unit).to_be_bytes()),
}
} else {
match self.wchar_width {
WcharWidth::Two => self.write_u16(unit),
WcharWidth::Four => self.write_u32(u32::from(unit)),
}
}
Ok(())
}
pub fn write_wchar_member(&mut self, value: char) -> Result<(), WireError> {
self.write_wchar(value, WcharPosition::Member)
}
pub fn write_wchar_element(&mut self, value: &char) -> Result<(), WireError> {
self.write_wchar(*value, WcharPosition::Element)
}
pub fn write_i16(&mut self, value: i16) {
#[allow(clippy::cast_sign_loss)]
self.write_u16(value as u16);
}
pub fn write_bitmask(&mut self, bit_bound: u16, bits: u64) -> Result<(), WireError> {
if bit_bound == 0 || bit_bound > 64 {
return Err(WireError::OutOfRange {
at: self.out.len(),
expected: alloc::format!(
"a bitmask bound between 1 and 64, and this one is {bit_bound}"
),
});
}
if bit_bound < 64 && (bits >> bit_bound) != 0 {
return Err(WireError::OutOfRange {
at: self.out.len(),
expected: alloc::format!(
"a bitmask value inside its bound of {bit_bound} bits, and this one is {bits}"
),
});
}
#[allow(clippy::cast_possible_truncation)]
match bit_bound {
0..=8 => self.write_u8(bits as u8),
9..=16 => self.write_u16(bits as u16),
17..=32 => self.write_u32(bits as u32),
_ => self.write_u64(bits),
}
Ok(())
}
pub fn write_bitset(&mut self, anchor_bytes: u32, bits: u64) -> Result<(), WireError> {
#[allow(clippy::cast_possible_truncation)]
match anchor_bytes {
1 => self.write_u8(bits as u8),
2 => self.write_u16(bits as u16),
4 => self.write_u32(bits as u32),
8 => self.write_u64(bits),
other => {
return Err(WireError::OutOfRange {
at: self.out.len(),
expected: alloc::format!(
"a bitset anchor of 1, 2, 4 or 8 octets, and this one is {other}"
),
});
}
}
Ok(())
}
pub fn bitfield(&mut self, value: u64, width: u32, position: u32) -> Result<u64, WireError> {
if width == 0 || width > 64 || position.saturating_add(width) > 64 {
return Err(WireError::OutOfRange {
at: self.out.len(),
expected: alloc::format!(
"a bitfield inside the anchor's 64 bits, and this one is {width} bits at {position}"
),
});
}
let limit = if width >= 64 {
u64::MAX
} else {
(1u64 << width) - 1
};
if value > limit {
return Err(WireError::OutOfRange {
at: self.out.len(),
expected: alloc::format!(
"a bitfield value inside its {width} bits, and this one is {value}"
),
});
}
Ok(value << position)
}
pub fn write_u16(&mut self, value: u16) {
self.align_to(2);
self.extend(&self.order2(value));
}
pub fn write_u16_little(&mut self, value: u16) {
self.align_to(2);
self.extend(&value.to_le_bytes());
}
pub fn write_u16_big(&mut self, value: u16) {
self.align_to(2);
self.extend(&value.to_be_bytes());
}
pub fn write_u32_little(&mut self, value: u32) {
self.align_to(4);
self.extend(&value.to_le_bytes());
}
pub fn write_u32_big(&mut self, value: u32) {
self.align_to(4);
self.extend(&value.to_be_bytes());
}
pub fn write_i32(&mut self, value: i32) {
#[allow(clippy::cast_sign_loss)]
self.write_u32(value as u32);
}
pub fn write_u32(&mut self, value: u32) {
self.align_to(4);
self.extend(&self.order4(value));
}
pub fn write_i64(&mut self, value: i64) {
#[allow(clippy::cast_sign_loss)]
self.write_u64(value as u64);
}
pub fn write_u64(&mut self, value: u64) {
self.align_to(8);
self.extend(&self.order8(value));
}
pub fn write_f32(&mut self, value: f32) {
self.write_u32(value.to_bits());
}
pub fn write_f64(&mut self, value: f64) {
self.write_u64(value.to_bits());
}
pub fn write_f128(&mut self, value: Float128) {
self.align_to(8);
let bytes = if self.endianness.is_little() {
value.to_le_bytes()
} else {
value.to_be_bytes()
};
self.extend(&bytes);
}
pub fn write_string(&mut self, value: &str) {
#[allow(clippy::cast_possible_truncation)]
let length = (value.len() + 1) as u32;
self.write_u32(length);
self.extend(value.as_bytes());
self.out.push(0);
}
pub fn write_string_bounded(&mut self, value: &str, bound: usize) -> Result<(), WireError> {
if value.len() > bound {
return Err(WireError::OutOfRange {
at: self.out.len(),
expected: alloc::format!(
"at most {bound} octets in a bounded string, and this one has {}",
value.len()
),
});
}
self.write_string(value);
Ok(())
}
pub fn write_wstring(
&mut self,
value: &str,
policy: WstringLengthPolicy,
order: WstringUnitOrder,
position: WirePosition,
) -> Result<(), WireError> {
let units = self.wstring_run(value, policy, order, position)?;
let width = policy.character_width(position);
let counts = match policy.counts_at(position) {
WstringLengthCounts::Octets => "octet count",
WstringLengthCounts::Characters => "code-unit count",
};
let run = units.len();
let declared =
policy
.declared_length(run, position)
.ok_or_else(|| WireError::OutOfRange {
at: self.out.len(),
expected: format!(
"a wstring whose {counts} fits the UInt32 length field, and this one \
has {run} code units at {width} octets each"
),
})?;
self.write_u32(declared);
for unit in units {
self.write_wstring_unit(unit, width, order);
}
Ok(())
}
pub fn write_wstring_bounded(
&mut self,
value: &str,
bound: usize,
policy: WstringLengthPolicy,
order: WstringUnitOrder,
position: WirePosition,
) -> Result<(), WireError> {
let units = value.encode_utf16().count();
if units > bound {
return Err(WireError::OutOfRange {
at: self.out.len(),
expected: format!(
"at most {bound} UTF-16 code units in a bounded wstring, \
and this one has {units}"
),
});
}
self.write_wstring(value, policy, order, position)
}
pub fn write_wstring_utf8(
&mut self,
value: &str,
policy: WstringLengthPolicy,
position: WirePosition,
) -> Result<(), WireError> {
let bytes = value.as_bytes();
let terminator = matches!(policy.terminator_at(position), WstringTerminator::Present);
let content = bytes.len() + usize::from(terminator);
let declared = u32::try_from(content).map_err(|_| WireError::OutOfRange {
at: self.out.len(),
expected: format!(
"a wstring whose UTF-8 octet count fits the UInt32 length field, and this one \
has {content} octets"
),
})?;
self.write_u32(declared);
for byte in bytes {
self.write_u8(*byte);
}
if terminator {
self.write_u8(0);
}
Ok(())
}
pub fn write_wstring_utf8_bounded(
&mut self,
value: &str,
bound: usize,
policy: WstringLengthPolicy,
position: WirePosition,
) -> Result<(), WireError> {
let units = value.encode_utf16().count();
if units > bound {
return Err(WireError::OutOfRange {
at: self.out.len(),
expected: format!(
"at most {bound} UTF-16 code units in a bounded wstring, \
and this one has {units}"
),
});
}
self.write_wstring_utf8(value, policy, position)
}
fn wstring_run(
&self,
value: &str,
policy: WstringLengthPolicy,
order: WstringUnitOrder,
position: WirePosition,
) -> Result<Vec<u16>, WireError> {
let narrow_count = matches!(policy.counts_at(position), WstringLengthCounts::Characters)
&& policy.unit_at(position) == WstringUnitWidth::Two;
let mut units: Vec<u16> = if narrow_count {
if let Some(point) = value.chars().find(|c| *c as u32 > 0xFFFF) {
let point = u32::from(point);
return Err(WireError::OutOfRange {
at: self.out.len(),
expected: format!(
"a code point inside the BMP, not U+{point:04X}: a character count \
over two-octet units has no way to carry one outside it, and Fast \
CDR truncates it to sixteen bits and sends a different character"
),
});
}
#[allow(clippy::cast_possible_truncation)]
value.chars().map(|c| c as u32 as u16).collect()
} else {
value.encode_utf16().collect()
};
if matches!(policy.terminator_at(position), WstringTerminator::Present) {
units.push(0);
}
if order.has_mark() && !units.is_empty() {
units.insert(0, BYTE_ORDER_MARK);
}
Ok(units)
}
fn write_wstring_unit(&mut self, unit: u16, width: usize, order: WstringUnitOrder) {
match (width, order) {
(2, WstringUnitOrder::Stream) => self.write_u16(unit),
(2, WstringUnitOrder::BigEndianAlways) => self.write_u16_big(unit),
(2, WstringUnitOrder::ByteOrderMark | WstringUnitOrder::HostUnswapped) => {
self.write_u16_little(unit);
}
(_, WstringUnitOrder::Stream) => self.write_u32(u32::from(unit)),
(_, WstringUnitOrder::BigEndianAlways) => self.write_u32_big(u32::from(unit)),
(_, WstringUnitOrder::ByteOrderMark | WstringUnitOrder::HostUnswapped) => {
self.write_u32_little(u32::from(unit));
}
}
}
#[must_use]
pub fn write_dheader(&mut self) -> DHeader {
self.align_to(4);
let at = self.out.len();
self.extend(&[0u8; 4]);
DHeader { at }
}
pub fn finish_dheader(&mut self, header: DHeader) {
let span = self.out.len() - header.at - 4;
self.finish_dheader_spanning(header, span);
}
fn finish_dheader_spanning(&mut self, header: DHeader, span: usize) {
if let Err(error) = self.patch_dheader(header, span) {
self.defer(error);
}
}
fn patch_dheader(&mut self, header: DHeader, span: usize) -> Result<(), WireError> {
let length = checked_dheader_length(span, header.at)?;
let bytes = self.order4(length);
self.out[header.at..header.at + 4].copy_from_slice(&bytes);
Ok(())
}
fn defer(&mut self, error: WireError) {
if self.deferred.is_none() {
self.deferred = Some(error);
}
}
#[must_use]
pub fn deferred_error(&self) -> Option<&WireError> {
self.deferred.as_ref()
}
pub fn check_deferred(&self) -> Result<(), WireError> {
match &self.deferred {
Some(error) => Err(error.clone()),
None => Ok(()),
}
}
pub fn finish_checked(self) -> Result<Vec<u8>, WireError> {
match self.deferred {
Some(error) => Err(error),
None => Ok(self.out),
}
}
pub fn try_finish_dheader(&mut self, header: DHeader) -> Result<(), WireError> {
let span = self.out.len() - header.at - 4;
self.patch_dheader(header, span)
}
pub fn write_emheader(
&mut self,
member_id: u32,
length_code: u8,
must_understand: bool,
) -> Result<EmHeader, WireError> {
if member_id > E_MEMBER_ID_MASK {
return Err(WireError::OutOfRange {
at: self.out.len(),
expected: format!("a member id inside the 28 bits of an EMHEADER, not {member_id}"),
});
}
self.align_to(4);
let word = (u32::from(must_understand) << 31)
| (u32::from(length_code & 0x7) << 28)
| (member_id & 0x0FFF_FFFF);
self.write_u32(word);
let word_at = self.out.len() - 4;
if length_code == 4 {
let at = self.out.len();
self.extend(&[0u8; 4]);
return Ok(EmHeader {
word: word_at,
next_int: Some(at),
length_code,
});
}
Ok(EmHeader {
word: word_at,
next_int: None,
length_code,
})
}
pub fn finish_emheader(&mut self, header: EmHeader) -> Result<(), WireError> {
let Some(at) = header.next_int else {
if header.length_code <= 3 && header.word + 4 <= self.out.len() {
let declared = 1usize << header.length_code;
let body = self.out.len() - header.word - 4;
if body != declared {
let code = header.length_code;
return Err(WireError::OutOfRange {
at: self.out.len(),
expected: format!(
"a member of {declared} octets under length code {code}, not {body}"
),
});
}
}
return Ok(());
};
#[allow(clippy::cast_possible_truncation)]
let length = (self.out.len() - at - 4) as u32;
if self.short_length_codes && matches!(length, 1 | 2 | 4 | 8) {
self.shorten_emheader(header.word, at, length);
return Ok(());
}
let bytes = self.order4(length);
self.out[at..at + 4].copy_from_slice(&bytes);
Ok(())
}
fn shorten_emheader(&mut self, word_at: usize, next_int_at: usize, length: u32) {
let code: u32 = match length {
1 => 0,
2 => 1,
4 => 2,
_ => 3,
};
self.out.drain(next_int_at..next_int_at + 4);
self.shift_padding(next_int_at, -4_isize);
let previous = if self.endianness.is_little() {
u32::from_le_bytes([
self.out[word_at],
self.out[word_at + 1],
self.out[word_at + 2],
self.out[word_at + 3],
])
} else {
u32::from_be_bytes([
self.out[word_at],
self.out[word_at + 1],
self.out[word_at + 2],
self.out[word_at + 3],
])
};
let patched = (previous & !(0x7 << 28)) | (code << 28);
let out = self.order4(patched);
self.out[word_at..word_at + 4].copy_from_slice(&out);
}
pub fn write_pid(
&mut self,
pid: u16,
role: ParameterRole,
must_understand: bool,
) -> Result<Pid, WireError> {
if pid > self.pl_cdr1.highest_short_id {
return Err(WireError::OutOfRange {
at: self.out.len(),
expected: format!("a parameter id the short header carries, not {pid}")
+ " — the extended header takes it",
});
}
self.align_to(4);
let flags = self.pl_cdr1.pid_flags.flags_at(WirePosition::Member);
let pid = if must_understand && self.pl_cdr1.member_must_understand {
pid | PID_MUST_UNDERSTAND | flags
} else {
pid | flags
};
self.write_u16(pid);
let at = self.out.len();
self.extend(&[0u8; 2]);
Ok(Pid {
at,
extended: false,
pid: u32::from(pid & !(PID_MUST_UNDERSTAND | PID_IMPL_EXTENSION)),
must_understand,
role,
saved_origin: self.begin_parameter_value(),
})
}
pub fn write_pid_extended(
&mut self,
pid: u32,
role: ParameterRole,
must_understand: bool,
) -> Result<Pid, WireError> {
if pid > E_MEMBER_ID_MASK {
return Err(WireError::OutOfRange {
at: self.out.len(),
expected: format!("a member id inside the 28 bits of an eMemberHeader, not {pid}"),
});
}
self.align_to(4);
let marker = PID_EXTENDED
| self
.pl_cdr1
.pid_flags
.flags_at(WirePosition::ExtendedMarker);
self.write_u16(marker);
self.write_u16(8);
let member_header = if must_understand && self.pl_cdr1.member_must_understand {
(pid & E_MEMBER_ID_MASK) | E_FLAG_MUST_UNDERSTAND
} else {
pid & E_MEMBER_ID_MASK
};
self.write_u32(member_header);
let at = self.out.len();
self.extend(&[0u8; 4]);
Ok(Pid {
at,
extended: true,
pid,
must_understand,
role,
saved_origin: self.begin_parameter_value(),
})
}
fn begin_parameter_value(&mut self) -> usize {
let saved = self.origin;
self.origin = self.out.len();
saved
}
pub fn finish_pid(&mut self, header: Pid) {
let width = if header.extended { 4 } else { 2 };
let mut length = self.out.len() - header.at - width;
if self.pl_cdr1.pads_declared_length(header.role) {
let past = (self.out.len() - header.saved_origin) % 4;
if past != 0 {
length += 4 - past;
}
}
if header.extended {
#[allow(clippy::cast_possible_truncation)]
let bytes = self.order4(length as u32);
self.out[header.at..header.at + 4].copy_from_slice(&bytes);
} else if length > PID_SHORT_MAX_LEN {
self.promote_pid(&header, length);
} else {
#[allow(clippy::cast_possible_truncation)]
let bytes = self.order2(length as u16);
self.out[header.at..header.at + 2].copy_from_slice(&bytes);
}
self.origin = header.saved_origin;
if self.pl_cdr1.pad_after_value {
self.align_to(4);
}
}
fn promote_pid(&mut self, header: &Pid, length: usize) {
let start = header.at - 2;
self.out.splice(header.at + 2..header.at + 2, [0u8; 8]);
self.shift_padding(header.at + 2, 8_isize);
let marker = PID_EXTENDED
| self
.pl_cdr1
.pid_flags
.flags_at(WirePosition::ExtendedMarker);
let marker = self.order2(marker);
self.out[start..start + 2].copy_from_slice(&marker);
let slength = self.order2(8);
self.out[start + 2..start + 4].copy_from_slice(&slength);
let member_header = if header.must_understand && self.pl_cdr1.member_must_understand {
(header.pid & E_MEMBER_ID_MASK) | E_FLAG_MUST_UNDERSTAND
} else {
header.pid & E_MEMBER_ID_MASK
};
let member_header = self.order4(member_header);
self.out[start + 4..start + 8].copy_from_slice(&member_header);
#[allow(clippy::cast_possible_truncation)]
let llength = self.order4(length as u32);
self.out[start + 8..start + 12].copy_from_slice(&llength);
}
pub fn write_sentinel(&mut self) {
self.align_to(4);
let sentinel = PID_LIST_END | self.pl_cdr1.pid_flags.flags_at(WirePosition::Sentinel);
self.write_u16(sentinel);
self.write_u16(0);
}
pub fn write_sequence<T, F: Fn(&mut Self, &T) -> Result<(), WireError>>(
&mut self,
values: &[T],
element: F,
) -> Result<(), WireError> {
#[allow(clippy::cast_possible_truncation)]
self.write_u32(values.len() as u32);
for value in values {
element(self, value)?;
}
Ok(())
}
pub fn write_array<T, F: Fn(&mut Self, &T) -> Result<(), WireError>>(
&mut self,
values: &[T],
element: F,
) -> Result<(), WireError> {
for value in values {
element(self, value)?;
}
Ok(())
}
pub fn write_delimited_sequence<T, F: Fn(&mut Self, &T) -> Result<(), WireError>>(
&mut self,
values: &[T],
element: F,
) -> Result<(), WireError> {
let dheader = self.write_dheader();
self.write_sequence(values, element)?;
self.try_finish_dheader(dheader)
}
pub fn write_delimited_array<T, F: Fn(&mut Self, &T) -> Result<(), WireError>>(
&mut self,
values: &[T],
element: F,
) -> Result<(), WireError> {
let dheader = self.write_dheader();
self.write_array(values, element)?;
self.try_finish_dheader(dheader)
}
pub fn write_sequence_bounded<T, F: Fn(&mut Self, &T) -> Result<(), WireError>>(
&mut self,
values: &[T],
bound: usize,
element: F,
) -> Result<(), WireError> {
self.check_sequence_bound(values.len(), bound)?;
self.write_sequence(values, element)
}
pub fn write_delimited_sequence_bounded<T, F: Fn(&mut Self, &T) -> Result<(), WireError>>(
&mut self,
values: &[T],
bound: usize,
element: F,
) -> Result<(), WireError> {
self.check_sequence_bound(values.len(), bound)?;
self.write_delimited_sequence(values, element)
}
fn check_sequence_bound(&self, length: usize, bound: usize) -> Result<(), WireError> {
if length > bound {
return Err(WireError::OutOfRange {
at: self.out.len(),
expected: alloc::format!(
"at most {bound} elements in a bounded sequence, and this one has {length}"
),
});
}
Ok(())
}
pub fn write_map<M, K, V, FK, FV>(
&mut self,
entries: &M,
key: FK,
value: FV,
) -> Result<(), WireError>
where
M: crate::WireMap<K, V> + ?Sized,
FK: Fn(&mut Self, &K) -> Result<(), WireError>,
FV: Fn(&mut Self, &V) -> Result<(), WireError>,
{
#[allow(clippy::cast_possible_truncation)]
self.write_u32(entries.entry_count() as u32);
entries.try_visit(|k, v| {
key(self, k)?;
value(self, v)
})
}
pub fn write_delimited_map<M, K, V, FK, FV>(
&mut self,
entries: &M,
key: FK,
value: FV,
) -> Result<(), WireError>
where
M: crate::WireMap<K, V> + ?Sized,
FK: Fn(&mut Self, &K) -> Result<(), WireError>,
FV: Fn(&mut Self, &V) -> Result<(), WireError>,
{
let dheader = self.write_dheader();
self.write_map(entries, key, value)?;
self.try_finish_dheader(dheader)
}
pub fn write_map_bounded<M, K, V, FK, FV>(
&mut self,
entries: &M,
bound: usize,
key: FK,
value: FV,
) -> Result<(), WireError>
where
M: crate::WireMap<K, V> + ?Sized,
FK: Fn(&mut Self, &K) -> Result<(), WireError>,
FV: Fn(&mut Self, &V) -> Result<(), WireError>,
{
self.check_map_bound(entries.entry_count(), bound)?;
self.write_map(entries, key, value)
}
pub fn write_delimited_map_bounded<M, K, V, FK, FV>(
&mut self,
entries: &M,
bound: usize,
key: FK,
value: FV,
) -> Result<(), WireError>
where
M: crate::WireMap<K, V> + ?Sized,
FK: Fn(&mut Self, &K) -> Result<(), WireError>,
FV: Fn(&mut Self, &V) -> Result<(), WireError>,
{
self.check_map_bound(entries.entry_count(), bound)?;
self.write_delimited_map(entries, key, value)
}
fn check_map_bound(&self, entries: usize, bound: usize) -> Result<(), WireError> {
if entries > bound {
return Err(WireError::OutOfRange {
at: self.out.len(),
expected: alloc::format!(
"at most {bound} entries in a bounded map, and this one has {entries}"
),
});
}
Ok(())
}
#[must_use]
pub fn zero_padded(&self) -> [u8; 16] {
let mut out = [0u8; 16];
let take = self.out.len().min(16);
out[..take].copy_from_slice(&self.out[..take]);
out
}
#[must_use]
pub fn md5(&self) -> [u8; 16] {
crate::md5(&self.out)
}
pub fn write_bytes(&mut self, bytes: &[u8]) {
self.out.extend_from_slice(bytes);
}
fn extend(&mut self, bytes: &[u8]) {
self.out.extend_from_slice(bytes);
}
fn order2(&self, value: u16) -> [u8; 2] {
if self.endianness.is_little() {
value.to_le_bytes()
} else {
value.to_be_bytes()
}
}
fn order4(&self, value: u32) -> [u8; 4] {
if self.endianness.is_little() {
value.to_le_bytes()
} else {
value.to_be_bytes()
}
}
fn order8(&self, value: u64) -> [u8; 8] {
if self.endianness.is_little() {
value.to_le_bytes()
} else {
value.to_be_bytes()
}
}
}
pub fn checked_dheader_length(span: usize, at: usize) -> Result<u32, WireError> {
u32::try_from(span).map_err(|_| WireError::OutOfRange {
at,
expected: alloc::string::String::from(
"an object smaller than 4 GiB, which a DHEADER can hold (XTypes 1.3 7.4.3.4.1)",
),
})
}
#[derive(Debug, Clone, Copy)]
pub struct DHeader {
at: usize,
}
#[derive(Debug, Clone, Copy)]
pub struct EmHeader {
word: usize,
next_int: Option<usize>,
length_code: u8,
}
#[derive(Debug, Clone, Copy)]
pub struct Pid {
at: usize,
extended: bool,
pid: u32,
must_understand: bool,
role: ParameterRole,
saved_origin: usize,
}
pub const PID_EXTENDED: u16 = 0x3F01;
pub const PID_LIST_END: u16 = 0x3F02;
pub const PID_SHORT_MAX_ID: u16 = 0x3F00;
pub const PID_SHORT_MAX_LEN: usize = 0xFFFF;
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use super::*;
#[test]
#[cfg(target_pointer_width = "64")]
fn an_infallible_finish_dheader_over_4_gib_is_deferred_and_not_truncated() {
let mut w = Writer::little_endian();
let header = w.write_dheader();
w.write_u32(7);
w.finish_dheader_spanning(header, u32::MAX as usize + 1);
assert!(
matches!(
w.deferred_error(),
Some(WireError::OutOfRange { at: 0, .. })
),
"{:?}",
w.deferred_error()
);
assert!(w.check_deferred().is_err());
assert_eq!(
&w.as_bytes()[..4],
&[0, 0, 0, 0],
"a length modulo 2^32 was written"
);
assert!(matches!(
w.finish_checked(),
Err(WireError::OutOfRange { .. })
));
}
#[test]
fn a_dheader_below_4_gib_is_patched_and_nothing_is_deferred() {
let mut w = Writer::little_endian();
let header = w.write_dheader();
w.write_u32(7);
w.finish_dheader(header);
assert!(w.deferred_error().is_none());
assert_eq!(
w.finish_checked().expect("no error"),
[4, 0, 0, 0, 7, 0, 0, 0]
);
}
}