use zerodds_ast::Extensibility;
use zerodds_ast_layout::{
AggregateDelimiter, CollectionDelimiterPolicy, DefaultedDiscriminatorMember, Dialect,
ElementPadding, Encoding, Endianness, EnumHolderWidth, EnumeratorValue, FixedDigitCount,
KeyHolderAlignment, KeyHolderExtensibility, KeyHolderNesting, KeyHolderOrder, LayoutKey,
LengthCodePolicy, MapDelimiter, MapEntryOrder, MustUnderstandPolicy, MutableUnionFraming,
PidFlagPolicy, PlCdr1HeaderPolicy, PlCdr1Reading, Purpose, SequenceElementDelimiter,
ShortPidRange, UnionLabelMatch, WcharFraming, WcharWidth, WstringCodeUnits,
WstringLengthPolicy, WstringUnitOrder,
};
use zerodds_wire::{
PidFlagPolicy as WirePidFlagPolicy, PlCdr1Framing, PlCdr1Reading as WirePlCdr1Reading,
WcharFraming as WireWcharFraming, WcharWidth as WireWcharWidth,
WstringUnitOrder as WireWstringUnitOrder,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub enum MapLengthPolicy {
#[default]
SequenceEquivalent,
Rule15Omits,
}
impl MapLengthPolicy {
pub const ALL: [Self; 2] = [Self::SequenceEquivalent, Self::Rule15Omits];
#[must_use]
pub const fn is_default(self) -> bool {
matches!(self, Self::SequenceEquivalent)
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::SequenceEquivalent => "sequence-equivalent",
Self::Rule15Omits => "rule-15-omits",
}
}
#[must_use]
pub fn parse(value: &str) -> Option<Self> {
Self::ALL
.into_iter()
.find(|policy| policy.as_str() == value)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Emit {
#[default]
Payload,
PayloadWithEncapsulation,
KeyHash,
}
impl Emit {
pub const ALL: [Self; 3] = [Self::Payload, Self::PayloadWithEncapsulation, Self::KeyHash];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Payload => "payload",
Self::PayloadWithEncapsulation => "payload+encapsulation",
Self::KeyHash => "keyhash",
}
}
#[must_use]
pub fn parse(value: &str) -> Option<Self> {
Self::ALL.into_iter().find(|form| form.as_str() == value)
}
}
#[derive(Debug, Clone)]
pub struct ProbeRequest {
pub type_name: String,
pub encoding: Encoding,
pub endianness: Endianness,
pub emit: Emit,
pub length_codes: LengthCodePolicy,
pub collection_delimiters: CollectionDelimiterPolicy,
pub map_delimiter: MapDelimiter,
pub wstring_length: WstringLengthPolicy,
pub wchar_framing: WcharFraming,
pub wchar_width: WcharWidth,
pub wstring_unit_order: WstringUnitOrder,
pub pl_cdr1_headers: PlCdr1HeaderPolicy,
pub pid_flags: PidFlagPolicy,
pub pl_cdr1_reading: PlCdr1Reading,
pub encapsulation_ids: EncapsulationIds,
pub must_understand: MustUnderstandPolicy,
pub enum_holder: EnumHolderWidth,
pub union_label: UnionLabelMatch,
pub map_entries: MapEntryOrder,
pub array_element_flattening: zerodds_ast_layout::key::ArrayElementFlattening,
pub enumerator_value: EnumeratorValue,
pub union_framing: MutableUnionFraming,
pub short_pid_range: ShortPidRange,
pub fixed_digits: FixedDigitCount,
pub default_extensibility: Extensibility,
pub key_holder: KeyHolderExtensibility,
pub key_holder_nesting: KeyHolderNesting,
pub key_holder_order: KeyHolderOrder,
pub key_holder_alignment: KeyHolderAlignment,
pub defaulted_discriminator_member: DefaultedDiscriminatorMember,
pub map_length: MapLengthPolicy,
pub element_padding: ElementPadding,
pub aggregate_delimiter: AggregateDelimiter,
pub sequence_element_delimiter: SequenceElementDelimiter,
pub wstring_code_units: WstringCodeUnits,
}
impl ProbeRequest {
#[must_use]
pub fn new(type_name: &str, encoding: Encoding, endianness: Endianness) -> Self {
Self::in_dialect(type_name, encoding, endianness, Dialect::default())
}
#[must_use]
pub fn in_dialect(
type_name: &str,
encoding: Encoding,
endianness: Endianness,
dialect: Dialect,
) -> Self {
let key = LayoutKey::in_dialect(dialect, encoding, endianness, Purpose::Data);
let map_length = if dialect.map_count_omitted_when_delimited() {
MapLengthPolicy::Rule15Omits
} else {
MapLengthPolicy::SequenceEquivalent
};
Self::from_key(type_name, key, map_length)
}
#[must_use]
pub fn from_key(type_name: &str, key: LayoutKey, map_length: MapLengthPolicy) -> Self {
Self {
type_name: type_name.to_string(),
encoding: key.encoding,
endianness: key.endianness,
emit: Emit::Payload,
length_codes: key.length_codes,
collection_delimiters: key.collection_delimiters,
map_delimiter: key.map_delimiter,
wstring_length: key.wstring_length,
wchar_framing: key.wchar_framing,
wchar_width: key.wchar_width,
wstring_unit_order: key.wstring_unit_order,
pl_cdr1_headers: key.pl_cdr1_headers,
pid_flags: key.pid_flags,
pl_cdr1_reading: key.pl_cdr1_reading,
encapsulation_ids: EncapsulationIds::default(),
must_understand: key.must_understand,
enum_holder: key.enum_holder,
union_label: key.union_label,
map_entries: key.map_entries,
array_element_flattening: key.array_element_flattening,
enumerator_value: key.enumerator_value,
union_framing: key.union_framing,
short_pid_range: key.short_pid_range,
fixed_digits: key.fixed_digits,
default_extensibility: key.default_extensibility,
key_holder: key.key_holder,
key_holder_nesting: key.key_holder_nesting,
key_holder_order: key.key_holder_order,
key_holder_alignment: key.key_holder_alignment,
defaulted_discriminator_member: key.defaulted_discriminator_member,
map_length,
element_padding: key.element_padding,
aggregate_delimiter: key.aggregate_delimiter,
sequence_element_delimiter: key.sequence_element_delimiter,
wstring_code_units: key.wstring_code_units,
}
}
#[must_use]
pub fn emitting(mut self, emit: Emit) -> Self {
self.emit = emit;
self
}
#[must_use]
pub fn with_pl_cdr1_reading(mut self, reading: PlCdr1Reading) -> Self {
self.pl_cdr1_reading = reading;
self
}
#[must_use]
pub fn with_encapsulation_ids(mut self, ids: EncapsulationIds) -> Self {
self.encapsulation_ids = ids;
self
}
#[must_use]
pub const fn wire_pl_cdr1_reading(&self) -> WirePlCdr1Reading {
match self.pl_cdr1_reading {
PlCdr1Reading::Strict => WirePlCdr1Reading::Strict,
PlCdr1Reading::Tolerant => WirePlCdr1Reading::Tolerant,
}
}
#[must_use]
pub fn layout_key(&self, purpose: Purpose) -> LayoutKey {
LayoutKey {
encoding: self.encoding,
endianness: self.endianness,
purpose,
length_codes: self.length_codes,
collection_delimiters: self.collection_delimiters,
map_delimiter: self.map_delimiter,
wstring_length: self.wstring_length,
wchar_framing: self.wchar_framing,
wchar_width: self.wchar_width,
wstring_unit_order: self.wstring_unit_order,
pl_cdr1_headers: self.pl_cdr1_headers,
pid_flags: self.pid_flags,
pl_cdr1_reading: self.pl_cdr1_reading,
must_understand: self.must_understand,
enum_holder: self.enum_holder,
union_label: self.union_label,
map_entries: self.map_entries,
array_element_flattening: self.array_element_flattening,
enumerator_value: self.enumerator_value,
union_framing: self.union_framing,
short_pid_range: self.short_pid_range,
fixed_digits: self.fixed_digits,
default_extensibility: self.default_extensibility,
key_holder: self.key_holder,
key_holder_nesting: self.key_holder_nesting,
key_holder_order: self.key_holder_order,
key_holder_alignment: self.key_holder_alignment,
defaulted_discriminator_member: self.defaulted_discriminator_member,
element_padding: self.element_padding,
aggregate_delimiter: self.aggregate_delimiter,
sequence_element_delimiter: self.sequence_element_delimiter,
wstring_code_units: self.wstring_code_units,
}
}
#[must_use]
pub fn key_holder_key(&self) -> LayoutKey {
LayoutKey {
endianness: Endianness::Big,
..self.layout_key(Purpose::Key)
}
}
#[must_use]
pub const fn wchar_width(&self) -> WireWcharWidth {
wire_wchar_width_of(self.wchar_width)
}
#[must_use]
pub const fn wire_wchar_framing(&self) -> WireWcharFraming {
wire_wchar_framing_of(self.wchar_framing)
}
#[must_use]
pub const fn wire_wstring_unit_order(&self) -> WireWstringUnitOrder {
match self.wstring_unit_order {
WstringUnitOrder::Stream => WireWstringUnitOrder::Stream,
WstringUnitOrder::BigEndianAlways => WireWstringUnitOrder::BigEndianAlways,
WstringUnitOrder::ByteOrderMark => WireWstringUnitOrder::ByteOrderMark,
WstringUnitOrder::HostUnswapped => WireWstringUnitOrder::HostUnswapped,
}
}
#[must_use]
pub fn pl_cdr1_framing(&self) -> PlCdr1Framing {
let values = self.layout_key(Purpose::Data).pl_cdr1_framing();
PlCdr1Framing {
pid_flags: wire_pid_flags_of(values.pid_flags),
highest_short_id: values.highest_short_id,
pad_declared_length: values.pad_declared_length,
pad_declared_optional_length: values.pad_declared_optional_length,
member_must_understand: values.member_must_understand,
require_extended: values.require_extended,
pad_after_value: values.pad_after_value,
}
}
}
#[must_use]
pub const fn wire_wchar_framing_of(framing: WcharFraming) -> WireWcharFraming {
match framing {
WcharFraming::Fixed => WireWcharFraming::Fixed,
WcharFraming::GiopLengthPrefixed => WireWcharFraming::GiopLengthPrefixed,
WcharFraming::GiopLengthPrefixedMemberOnly => {
WireWcharFraming::GiopLengthPrefixedMemberOnly
}
}
}
#[must_use]
pub const fn wire_wstring_unit_order_of(order: WstringUnitOrder) -> WireWstringUnitOrder {
match order {
WstringUnitOrder::Stream => WireWstringUnitOrder::Stream,
WstringUnitOrder::BigEndianAlways => WireWstringUnitOrder::BigEndianAlways,
WstringUnitOrder::ByteOrderMark => WireWstringUnitOrder::ByteOrderMark,
WstringUnitOrder::HostUnswapped => WireWstringUnitOrder::HostUnswapped,
}
}
#[must_use]
pub const fn wire_pid_flags_of(policy: PidFlagPolicy) -> WirePidFlagPolicy {
match policy {
PidFlagPolicy::SentinelMustUnderstand => WirePidFlagPolicy::SentinelMustUnderstand,
PidFlagPolicy::Bare => WirePidFlagPolicy::Bare,
PidFlagPolicy::ImplExtensionOnMembers => WirePidFlagPolicy::ImplExtensionOnMembers,
}
}
#[must_use]
pub const fn wire_wchar_width_of(width: WcharWidth) -> WireWcharWidth {
match width {
WcharWidth::Two => WireWcharWidth::Two,
WcharWidth::Four => WireWcharWidth::Four,
}
}
#[must_use]
pub const fn encapsulation_padding(length: usize) -> u8 {
((4 - (length & 3)) & 3) as u8
}
#[must_use]
pub fn encapsulation_header(
encoding: Encoding,
endianness: Endianness,
little: bool,
padding: u8,
) -> [u8; 4] {
encapsulation_header_in(
EncapsulationIds::Table60,
encoding,
endianness,
little,
padding,
)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub enum EncapsulationIds {
#[default]
Table60,
Table39,
}
impl EncapsulationIds {
pub const ALL: [Self; 2] = [Self::Table60, Self::Table39];
#[must_use]
pub const fn is_default(self) -> bool {
matches!(self, Self::Table60)
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Table60 => "table-60",
Self::Table39 => "table-39",
}
}
#[must_use]
pub fn parse(value: &str) -> Option<Self> {
Self::ALL.into_iter().find(|ids| ids.as_str() == value)
}
#[must_use]
pub const fn identifier(self, encoding: Encoding, endianness: Endianness) -> [u8; 2] {
let id: u8 = match (self, encoding, endianness) {
(_, Encoding::PlainCdr1, Endianness::Big) => 0x00,
(_, Encoding::PlainCdr1, Endianness::Little) => 0x01,
(_, Encoding::PlCdr1, Endianness::Big) => 0x02,
(_, Encoding::PlCdr1, Endianness::Little) => 0x03,
(Self::Table60, Encoding::PlainCdr2, Endianness::Big) => 0x06,
(Self::Table60, Encoding::PlainCdr2, Endianness::Little) => 0x07,
(Self::Table60, Encoding::DelimitedCdr, Endianness::Big) => 0x08,
(Self::Table60, Encoding::DelimitedCdr, Endianness::Little) => 0x09,
(Self::Table60, Encoding::PlCdr2, Endianness::Big) => 0x0a,
(Self::Table60, Encoding::PlCdr2, Endianness::Little) => 0x0b,
(Self::Table39, Encoding::PlainCdr2, Endianness::Big) => 0x10,
(Self::Table39, Encoding::PlainCdr2, Endianness::Little) => 0x11,
(Self::Table39, Encoding::PlCdr2, Endianness::Big) => 0x12,
(Self::Table39, Encoding::PlCdr2, Endianness::Little) => 0x13,
(Self::Table39, Encoding::DelimitedCdr, Endianness::Big) => 0x14,
(Self::Table39, Encoding::DelimitedCdr, Endianness::Little) => 0x15,
};
[0x00, id]
}
#[must_use]
pub const fn xml_identifier(self) -> [u8; 2] {
match self {
Self::Table60 => [0x00, 0x04],
Self::Table39 => [0x01, 0x00],
}
}
}
#[must_use]
pub const fn encapsulation_form(identifier: [u8; 2]) -> Option<(Encoding, Endianness)> {
if identifier[0] != 0x00 {
return None;
}
Some(match identifier[1] {
0x00 => (Encoding::PlainCdr1, Endianness::Big),
0x01 => (Encoding::PlainCdr1, Endianness::Little),
0x02 => (Encoding::PlCdr1, Endianness::Big),
0x03 => (Encoding::PlCdr1, Endianness::Little),
0x06 | 0x10 => (Encoding::PlainCdr2, Endianness::Big),
0x07 | 0x11 => (Encoding::PlainCdr2, Endianness::Little),
0x08 | 0x14 => (Encoding::DelimitedCdr, Endianness::Big),
0x09 | 0x15 => (Encoding::DelimitedCdr, Endianness::Little),
0x0a | 0x12 => (Encoding::PlCdr2, Endianness::Big),
0x0b | 0x13 => (Encoding::PlCdr2, Endianness::Little),
_ => return None,
})
}
#[must_use]
pub fn encapsulation_header_in(
ids: EncapsulationIds,
encoding: Encoding,
endianness: Endianness,
little: bool,
padding: u8,
) -> [u8; 4] {
let _ = little;
let [high, low] = ids.identifier(encoding, endianness);
[high, low, 0, padding & 0x3]
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use super::*;
#[test]
fn the_emit_names_round_trip_and_a_typo_is_rejected() {
for emit in Emit::ALL {
assert_eq!(Emit::parse(emit.as_str()), Some(emit));
}
assert_eq!(Emit::parse("Payload"), None);
assert_eq!(Emit::parse(""), None);
}
#[test]
fn the_map_length_names_round_trip_and_a_typo_is_rejected() {
for policy in MapLengthPolicy::ALL {
assert_eq!(MapLengthPolicy::parse(policy.as_str()), Some(policy));
}
assert_eq!(MapLengthPolicy::parse("rule15-omits"), None);
assert!(MapLengthPolicy::default().is_default());
}
#[test]
fn map_length_spellings_match_the_axis_table() {
assert_eq!(MapLengthPolicy::ALL.len(), 2);
assert_eq!(
MapLengthPolicy::SequenceEquivalent.as_str(),
"sequence-equivalent"
);
assert_eq!(MapLengthPolicy::Rule15Omits.as_str(), "rule-15-omits");
}
#[test]
fn the_default_framing_is_the_default_dialects() {
for endianness in [Endianness::Little, Endianness::Big] {
let request = ProbeRequest::new("Sample", Encoding::PlCdr1, endianness);
assert_eq!(
request.pl_cdr1_framing(),
PlCdr1Framing::DEFAULT,
"`PlCdr1Framing::DEFAULT` is not `Dialect::default`'s framing"
);
}
let default = Dialect::default().pl_cdr1_headers();
assert_eq!(
default.always_extended(),
PlCdr1Framing::DEFAULT.require_extended,
"the clause takes the short parameter header wherever §7.4.1.2.2 allows it"
);
assert_eq!(
default.pads_declared_optional_length(),
PlCdr1Framing::DEFAULT.pad_declared_optional_length,
"the clause declares an `@optional` member's exact length"
);
assert_eq!(
default,
PlCdr1HeaderPolicy::Specification,
"the default dialect frames a PL_CDR1 parameter list some other way now"
);
let cyclone = ProbeRequest::in_dialect(
"Sample",
Encoding::PlCdr1,
Endianness::Little,
Dialect::Cyclone,
)
.pl_cdr1_framing();
assert_ne!(cyclone, PlCdr1Framing::DEFAULT);
assert!(cyclone.require_extended);
assert!(!cyclone.pad_declared_optional_length);
assert_eq!(cyclone.pid_flags, PlCdr1Framing::DEFAULT.pid_flags);
assert_eq!(
cyclone.highest_short_id,
PlCdr1Framing::DEFAULT.highest_short_id
);
assert_eq!(
default.pads_declared_length(),
PlCdr1Framing::DEFAULT.pad_declared_length,
"XTypes 1.3 §7.4.1.2 declares a member's exact length"
);
assert!(
cyclone.pad_declared_length,
"Cyclone declares the length rounded up to four"
);
assert_eq!(
cyclone.member_must_understand,
PlCdr1Framing::DEFAULT.member_must_understand
);
}
#[test]
fn the_encapsulation_identifiers_are_the_ones_the_vendors_wrote() {
assert_eq!(
encapsulation_header(Encoding::PlainCdr1, Endianness::Little, true, 0)[..2],
[0x00, 0x01]
);
assert_eq!(
encapsulation_header(Encoding::PlainCdr2, Endianness::Little, true, 0)[..2],
[0x00, 0x07]
);
assert_eq!(
encapsulation_header(Encoding::DelimitedCdr, Endianness::Little, true, 0)[..2],
[0x00, 0x09]
);
assert_eq!(
encapsulation_header(Encoding::PlCdr2, Endianness::Little, true, 0)[..2],
[0x00, 0x0b]
);
assert_eq!(
encapsulation_header(Encoding::PlCdr1, Endianness::Little, true, 0)[..2],
[0x00, 0x03]
);
}
}