use crate::canonical;
use crate::encode::{ProbeError, sequence_element_carries_length};
use crate::sink::{Aggregate, Sink};
use crate::source::Source;
use crate::value::{Emit, MapLengthPolicy, ProbeRequest};
use std::string::String;
use std::vec::Vec;
use zerodds_ast::{
Ast, Bound, CharWidth, Extensibility, PrimitiveKind, TryConstruct, TypeKind, TypeNode,
};
use zerodds_ast_core::{Id, Ref, ScopedName};
use zerodds_ast_layout::{
DefaultedDiscriminatorMember, DepCollector, ElementPadding, Endianness, Extent,
FixedDigitCount, LayoutKey, MemberHeader, Purpose, WirePosition, WstringCodeUnits,
WstringLengthCounts, WstringLengthPolicy, WstringTerminator, WstringUnitOrder,
array_is_delimited, collection_is_delimited, enumeration_bit_bound, enumerator_wire_value,
plan, union_negative_matches_nothing,
};
use zerodds_wire::{ParameterRole, Reader, WcharPosition, WireError};
#[derive(Debug, Clone, Copy)]
struct Frame {
key: LayoutKey,
map_length: MapLengthPolicy,
try_construct: TryConstruct,
written_try_construct: Option<TryConstruct>,
default_from: Option<Id<zerodds_ast::MemberNode>>,
wchar: WcharPosition,
depth: usize,
is_discriminator: bool,
}
impl Frame {
fn constructing(
self,
ast: &Ast,
behaviour: Option<TryConstruct>,
used: Option<Id<TypeNode>>,
) -> Self {
Self {
try_construct: ast.effective_try_construct(behaviour, used),
written_try_construct: behaviour,
..self
}
}
fn for_member(
self,
ast: &Ast,
behaviour: Option<TryConstruct>,
used: Option<Id<TypeNode>>,
member: Id<zerodds_ast::MemberNode>,
) -> Self {
Self {
default_from: Some(member),
..self.constructing(ast, behaviour, used)
}
}
const fn resolved(self, behaviour: TryConstruct) -> Self {
Self {
try_construct: behaviour,
written_try_construct: Some(behaviour),
default_from: None,
..self
}
}
const fn in_collection(self) -> Self {
Self {
wchar: WcharPosition::Element,
..self
}
}
const fn in_aggregate(self) -> Self {
Self {
wchar: WcharPosition::Member,
depth: self.depth.saturating_add(1),
..self
}
}
fn within_depth(self, path: &str) -> Result<(), ProbeError> {
if self.depth >= crate::MAX_VALUE_DEPTH {
return Err(ProbeError::ValueTooDeep {
path: String::from(path),
limit: crate::MAX_VALUE_DEPTH,
});
}
Ok(())
}
fn default_value<V: Source + Sink>(
self,
ast: &Ast,
type_: Id<TypeNode>,
path: &str,
) -> Result<V, ProbeError> {
match self.default_from.and_then(|id| ast.member(id).ok()) {
Some(node) => crate::default::member(ast, node, type_, path),
None => crate::default::of_type(ast, type_, path),
}
}
}
fn narrowed<V, D>(
behaviour: TryConstruct,
trimmed: Option<V>,
default: D,
path: &str,
expected: &str,
found: &str,
) -> Result<V, ProbeError>
where
D: FnOnce() -> Result<V, ProbeError>,
{
match behaviour {
TryConstruct::Discard => Err(ProbeError::Discarded {
path: path.to_string(),
expected: expected.to_string(),
found: found.to_string(),
}),
TryConstruct::UseDefault => default(),
TryConstruct::Trim => trimmed.ok_or(ProbeError::Unsupported {
path: path.to_string(),
kind: "`@try_construct(TRIM)` on something that cannot be trimmed",
}),
}
}
#[must_use]
pub fn padding_present(payload_len: usize, options_low: u8) -> usize {
let declared = usize::from(options_low & 0x3);
let body = payload_len.saturating_sub(4);
if declared == 0 {
0
} else if declared > body {
declared
} else if payload_len % 4 != 0 {
0
} else {
declared
}
}
pub fn decode<V: Source + Sink>(
ast: &Ast,
request: &ProbeRequest,
bytes: &[u8],
) -> Result<V, ProbeError> {
decode_continuing(ast, request, bytes, 0)
}
pub fn decode_continuing<V: Source + Sink>(
ast: &Ast,
request: &ProbeRequest,
bytes: &[u8],
start: usize,
) -> Result<V, ProbeError> {
if start != 0 && request.emit != Emit::Payload {
return Err(ProbeError::Unsupported {
path: request.type_name.clone(),
kind: "a continuation behind an encapsulation header or as a KeyHash",
});
}
if request.emit == Emit::KeyHash {
return Err(ProbeError::Unsupported {
path: request.type_name.clone(),
kind: "a KeyHash, which is a digest and holds no value to read back",
});
}
let type_ = find_type(ast, &request.type_name)?;
let payload = if request.emit == Emit::PayloadWithEncapsulation {
let (header, body) = bytes
.split_at_checked(4)
.ok_or_else(|| ProbeError::Mismatch {
path: request.type_name.clone(),
expected: "at least the four-byte encapsulation header".to_string(),
found: format!("{} bytes", bytes.len()),
})?;
let declared = padding_present(bytes.len(), header[3]);
let end = body
.len()
.checked_sub(declared)
.ok_or_else(|| ProbeError::Mismatch {
path: request.type_name.clone(),
expected: format!(
"a payload long enough to hold the {declared} padding byte(s) the \
encapsulation options declared"
),
found: format!("{} byte(s) behind the header", body.len()),
})?;
if body[end..].iter().any(|byte| *byte != 0) {
return Err(ProbeError::Mismatch {
path: request.type_name.clone(),
expected: format!("{declared} zero byte(s) of trailing padding, per §7.4.1.1"),
found: format!("{:02x?} at offset {}", &body[end..], end + 4),
});
}
&body[..end]
} else {
bytes
};
let frame = Frame {
key: request
.layout_key(Purpose::Data)
.for_sample(ast, type_)
.map_err(|e| ProbeError::NoLayout {
detail: e.to_string(),
})?,
map_length: request.map_length,
try_construct: TryConstruct::ABSENT,
written_try_construct: None,
default_from: None,
wchar: WcharPosition::Member,
depth: 0,
is_discriminator: false,
};
let mut r = Reader::with_max_align(
payload,
wire_endianness(request.endianness),
usize::from(request.encoding.max_alignment()),
)
.with_pl_cdr1_framing(request.pl_cdr1_framing())
.with_pl_cdr1_reading(request.wire_pl_cdr1_reading())
.with_wchar_width(request.wchar_width())
.with_wchar_framing(request.wire_wchar_framing())
.with_wstring_unit_order(request.wire_wstring_unit_order());
if start != 0 {
r.skip_to(start).map_err(|e| ProbeError::Mismatch {
path: request.type_name.clone(),
expected: format!("a value at offset {start}"),
found: e.to_string(),
})?;
}
let key = frame.key;
let value = read_type(&mut r, ast, type_, frame, &request.type_name)?;
if r.remaining() != 0 && appended_members_end_the_sample(ast, type_, key) {
return Ok(value);
}
trailing(&r, &request.type_name)?;
Ok(value)
}
pub fn decode_prefix<V: Source + Sink>(
ast: &Ast,
request: &ProbeRequest,
bytes: &[u8],
) -> Result<(V, usize), ProbeError> {
if request.emit != Emit::Payload {
return Err(ProbeError::Unsupported {
path: request.type_name.clone(),
kind: "a prefix read of anything but the bare payload",
});
}
let type_ = find_type(ast, &request.type_name)?;
let frame = Frame {
key: request
.layout_key(Purpose::Data)
.for_sample(ast, type_)
.map_err(|e| ProbeError::NoLayout {
detail: e.to_string(),
})?,
map_length: request.map_length,
try_construct: TryConstruct::ABSENT,
written_try_construct: None,
default_from: None,
wchar: WcharPosition::Member,
depth: 0,
is_discriminator: false,
};
let mut r = Reader::with_max_align(
bytes,
wire_endianness(request.endianness),
usize::from(request.encoding.max_alignment()),
)
.with_pl_cdr1_framing(request.pl_cdr1_framing())
.with_pl_cdr1_reading(request.wire_pl_cdr1_reading())
.with_wchar_width(request.wchar_width())
.with_wchar_framing(request.wire_wchar_framing())
.with_wstring_unit_order(request.wire_wstring_unit_order());
let value = read_type(&mut r, ast, type_, frame, &request.type_name)?;
Ok((value, bytes.len() - r.remaining()))
}
fn appended_members_end_the_sample(ast: &Ast, type_: Id<TypeNode>, key: LayoutKey) -> bool {
if !key.encoding.is_v1() {
return false;
}
let Ok(node) = ast.type_(type_) else {
return false;
};
matches!(
node.kind,
TypeKind::Structure { .. } | TypeKind::Union { .. }
) && zerodds_ast_layout::xcdr::extensibility_of(ast, node, key) == Extensibility::Appendable
}
pub fn validate_key_holder(ast: &Ast, request: &ProbeRequest) -> Result<(), ProbeError> {
holder_members(ast, request).map(|_| ())
}
fn holder_members(
ast: &Ast,
request: &ProbeRequest,
) -> Result<(Id<TypeNode>, Vec<MemberSlot>), ProbeError> {
let type_ = find_type(ast, &request.type_name)?;
let bad = || ProbeError::Unsupported {
path: request.type_name.clone(),
kind: "inverse holder requires structure keys with supported scalar/alias/nested contexts",
};
let node = ast.type_(type_).map_err(|_| bad())?;
if !matches!(node.kind, TypeKind::Structure { .. }) {
return Err(bad());
}
let key = request.key_holder_key();
let specification = LayoutKey::key(zerodds_ast_layout::Encoding::PlainCdr2);
let holder_form = LayoutKey {
pl_cdr1_headers: specification.pl_cdr1_headers,
pl_cdr1_reading: specification.pl_cdr1_reading,
..key
};
if holder_form != specification {
return Err(bad());
}
let layout = plan(
ast,
type_,
request.layout_key(Purpose::Data),
&mut DepCollector::default(),
)
.map_err(|e| ProbeError::NoLayout {
detail: e.to_string(),
})?;
let members = member_table(ast, type_, &layout);
let keys = zerodds_ast_layout::keyhash::key_members_with(
ast,
type_,
key.key_holder_nesting,
key.key_holder_order,
)
.map_err(|e| ProbeError::NoLayout {
detail: e.to_string(),
})?;
if keys.is_empty() {
return Err(ProbeError::NoKey {
type_name: request.type_name.clone(),
});
}
for id in keys {
let Some(member) = members
.iter()
.find(|m| ast.member(m.member).is_ok_and(|n| n.node_id == id))
else {
continue;
};
let node = ast.member(member.member).map_err(|_| bad())?;
if member.optional || node.facets.xtypes.as_ref().is_some_and(|x| x.is_external) {
return Err(bad());
}
validate_holder_context(ast, member.type_, key, 0, &request.type_name)?;
}
Ok((type_, members))
}
fn validate_holder_context(
ast: &Ast,
type_: Id<TypeNode>,
key: LayoutKey,
depth: usize,
path: &str,
) -> Result<(), ProbeError> {
let bad = || ProbeError::Unsupported {
path: path.into(),
kind: "unsupported or recursive inverse key context",
};
if depth >= crate::MAX_VALUE_DEPTH {
return Err(bad());
}
let node = ast.type_(type_).map_err(|_| bad())?;
match &node.kind {
TypeKind::Primitive(_) | TypeKind::String { .. } | TypeKind::Enumeration { .. } => Ok(()),
TypeKind::Alias { aliased } => validate_holder_context(
ast,
aliased.resolved().ok_or_else(bad)?,
key,
depth + 1,
path,
),
TypeKind::Structure { .. } => {
let layout = plan(ast, type_, key, &mut DepCollector::default()).map_err(|e| {
ProbeError::NoLayout {
detail: e.to_string(),
}
})?;
let inside = zerodds_ast_layout::member_key(ast, type_, key);
for slot in member_table(ast, type_, &layout) {
let member = ast.member(slot.member).map_err(|_| bad())?;
if slot.optional || member.facets.xtypes.as_ref().is_some_and(|x| x.is_external) {
return Err(bad());
}
validate_holder_context(ast, slot.type_, inside, depth + 1, path)?;
}
Ok(())
}
_ => Err(bad()),
}
}
pub fn serialized_key<V: Source + Sink>(
ast: &Ast,
request: &ProbeRequest,
bytes: &[u8],
) -> Result<V, ProbeError> {
let type_ = find_type(ast, &request.type_name)?;
let payload = if request.emit == Emit::PayloadWithEncapsulation {
let (header, body) = bytes
.split_at_checked(4)
.ok_or_else(|| ProbeError::Mismatch {
path: request.type_name.clone(),
expected: "at least the four-byte encapsulation header".to_string(),
found: format!("{} bytes", bytes.len()),
})?;
let declared = padding_present(bytes.len(), header[3]);
let end = body
.len()
.checked_sub(declared)
.ok_or_else(|| ProbeError::Mismatch {
path: request.type_name.clone(),
expected: format!(
"a payload long enough to hold the {declared} padding byte(s) the \
encapsulation options declared"
),
found: format!("{} byte(s) behind the header", body.len()),
})?;
if body[end..].iter().any(|byte| *byte != 0) {
return Err(ProbeError::Mismatch {
path: request.type_name.clone(),
expected: format!("{declared} zero byte(s) of trailing padding, per §7.4.1.1"),
found: format!("{:02x?} at offset {}", &body[end..], end + 4),
});
}
&body[..end]
} else {
bytes
};
let frame = Frame {
key: request.layout_key(Purpose::Key),
map_length: request.map_length,
try_construct: TryConstruct::ABSENT,
written_try_construct: None,
default_from: None,
wchar: WcharPosition::Member,
depth: 0,
is_discriminator: false,
};
let mut r = Reader::with_max_align(
payload,
wire_endianness(request.endianness),
usize::from(request.encoding.max_alignment()),
)
.with_pl_cdr1_framing(request.pl_cdr1_framing())
.with_pl_cdr1_reading(request.wire_pl_cdr1_reading())
.with_wchar_width(request.wchar_width())
.with_wchar_framing(request.wire_wchar_framing())
.with_wstring_unit_order(request.wire_wstring_unit_order());
let value = read_type(&mut r, ast, type_, frame, &request.type_name)?;
trailing(&r, &request.type_name)?;
Ok(value)
}
pub fn key_holder<V: Source + Sink>(
ast: &Ast,
request: &ProbeRequest,
bytes: &[u8],
) -> Result<V, ProbeError> {
let (value, consumed) = key_holder_prefix(ast, request, bytes)?;
if consumed != bytes.len() {
return Err(ProbeError::Mismatch {
path: request.type_name.clone(),
expected: "fully consumed key holder".into(),
found: format!("{} trailing bytes", bytes.len() - consumed),
});
}
Ok(value)
}
pub fn key_holder_prefix<V: Source + Sink>(
ast: &Ast,
request: &ProbeRequest,
bytes: &[u8],
) -> Result<(V, usize), ProbeError> {
let (type_, members) = holder_members(ast, request)?;
let key = request.key_holder_key();
let keys = zerodds_ast_layout::keyhash::key_members_with(
ast,
type_,
key.key_holder_nesting,
key.key_holder_order,
)
.map_err(|e| ProbeError::NoLayout {
detail: e.to_string(),
})?;
let frame = Frame {
key,
map_length: request.map_length,
try_construct: TryConstruct::ABSENT,
written_try_construct: None,
default_from: None,
wchar: WcharPosition::Member,
depth: 0,
is_discriminator: false,
};
let mut reader = Reader::with_max_align(
bytes,
zerodds_wire::Endianness::Big,
usize::from(key.key_holder_alignment.max_alignment(key.encoding)),
)
.with_wchar_width(crate::value::wire_wchar_width_of(key.wchar_width))
.with_wchar_framing(crate::value::wire_wchar_framing_of(key.wchar_framing))
.with_wstring_unit_order(crate::value::wire_wstring_unit_order_of(
key.wstring_unit_order,
));
let mut out = Aggregate::new();
for id in keys {
let Some(slot) = members
.iter()
.find(|m| ast.member(m.member).is_ok_and(|n| n.node_id == id))
else {
continue;
};
out.insert(
slot.name.clone(),
read_type(
&mut reader,
ast,
slot.type_,
frame,
&format!("{}.{}", request.type_name, slot.name),
)?,
);
}
let consumed = bytes.len() - reader.remaining();
defaults_from(ast, &members, &mut out, &request.type_name)?;
Ok((V::aggregate(out), consumed))
}
fn trailing(r: &Reader<'_>, path: &str) -> Result<(), ProbeError> {
let left = r.remaining();
if left == 0 {
return Ok(());
}
let cap = r.max_align().min(4);
let padding = (cap - (r.position() % cap)) % cap;
if left <= padding {
return Ok(());
}
Err(ProbeError::Mismatch {
path: path.to_string(),
expected: format!("the whole payload consumed, bar at most {padding} byte(s) of padding"),
found: format!("{left} byte(s) left at offset {}", r.position()),
})
}
const fn wire_endianness(endianness: Endianness) -> zerodds_wire::Endianness {
match endianness {
Endianness::Little => zerodds_wire::Endianness::Little,
Endianness::Big => zerodds_wire::Endianness::Big,
}
}
fn find_type(ast: &Ast, name: &str) -> Result<Id<TypeNode>, ProbeError> {
let segments: Vec<String> = name
.split("::")
.filter(|segment| !segment.is_empty())
.map(std::string::ToString::to_string)
.collect();
ast.find_by_scoped_name(&ScopedName::absolute(segments))
.ok()
.and_then(zerodds_ast::AnyId::as_type)
.ok_or_else(|| ProbeError::NoSuchType {
name: name.to_string(),
})
}
#[allow(clippy::too_many_lines)]
fn read_type<V: Source + Sink>(
r: &mut Reader<'_>,
ast: &Ast,
type_: Id<TypeNode>,
frame: Frame,
path: &str,
) -> Result<V, ProbeError> {
let node = ast.type_(type_).map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?;
match &node.kind {
TypeKind::Primitive(kind) => read_primitive(r, *kind, frame.wchar, path),
TypeKind::String {
width: CharWidth::Char,
bound,
} => {
let text = wire(r.read_string(), path)?;
fit_string(ast, type_, text, *bound, CharWidth::Char, frame, path)
}
TypeKind::String {
width: CharWidth::WChar,
bound,
} => {
let text = if frame.key.wstring_code_units == WstringCodeUnits::Utf8 {
read_wstring_utf8(
r,
frame.key.wstring_length,
layout_position(frame.wchar),
path,
)?
} else {
read_wstring(
r,
frame.key.wstring_length,
frame.key.wstring_unit_order,
layout_position(frame.wchar),
path,
)?
};
fit_string(ast, type_, text, *bound, CharWidth::WChar, frame, path)
}
TypeKind::Fixed { digits, scale } => {
read_fixed(r, *digits, *scale, frame.key.fixed_digits, path)
}
TypeKind::Alias { aliased } => {
let target = aliased.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the typedef target is unresolved"),
})?;
read_type(r, ast, target, frame, path)
}
TypeKind::Enumeration { .. } => read_enumeration(r, ast, type_, frame, path),
TypeKind::Bitmask { .. } => read_bitmask(r, ast, type_, frame.is_discriminator, path),
TypeKind::Bitset { .. } => read_bitset(r, ast, type_, frame, path),
TypeKind::Sequence { element, bound } => {
let element = element.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the sequence element is unresolved"),
})?;
if collection_is_delimited(ast, element, frame.key) {
let _ = wire(r.read_dheader(), path)?;
}
let count = wire(r.read_u32(), path)? as usize;
if count > r.remaining() {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: "an element count the stream can hold".to_string(),
found: format!("{count} with {} byte(s) left", r.remaining()),
});
}
let inside = frame
.resolved(ast.element_try_construct(type_, None))
.in_collection();
let mut items = Vec::with_capacity(count);
let carries_length = sequence_element_carries_length(ast, element, frame.key)?;
let packed = frame.key.element_padding == ElementPadding::Packed;
for index in 0..count {
let element_path = format!("{path}[{index}]");
let saved = packed.then(|| r.begin_packed_element());
if carries_length {
let declared = wire(r.read_u32(), &element_path)? as usize;
let before = r.remaining();
items.push(read_type(r, ast, element, inside, &element_path)?);
let consumed = before.saturating_sub(r.remaining());
if consumed != declared {
return Err(ProbeError::Mismatch {
path: element_path,
expected: format!("an element of exactly {declared} octet(s)"),
found: format!("{consumed}"),
});
}
} else {
items.push(read_type(r, ast, element, inside, &element_path)?);
}
if let Some(saved) = saved {
r.finish_packed_element(saved);
}
}
let limit = bound_value(ast, bound);
fit_collection(ast, items, limit, type_, frame, path, "elements")
}
TypeKind::Array { element, dims } => {
let element = element.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the array element is unresolved"),
})?;
let counts = dimensions(ast, dims);
if array_is_delimited(ast, element, frame.key) {
let _ = wire(r.read_dheader(), path)?;
}
let inside = frame
.resolved(ast.element_try_construct(type_, frame.written_try_construct))
.in_collection();
read_array_dimension(r, ast, element, inside, path, &counts)
}
TypeKind::Map {
key,
value: entry,
bound,
} => read_map(r, ast, type_, key, entry, bound, frame, path),
TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
read_aggregate(r, ast, type_, frame, path)
}
TypeKind::Union { .. } => read_union(r, ast, type_, frame, path),
other => Err(ProbeError::Unfilled {
path: path.to_string(),
kind: other.as_str(),
}),
}
}
fn fit_string<V: Source + Sink>(
ast: &Ast,
type_: Id<TypeNode>,
text: String,
bound: Bound,
width: CharWidth,
frame: Frame,
path: &str,
) -> Result<V, ProbeError> {
let Some(limit) = bound_value(ast, &bound).and_then(|limit| usize::try_from(limit).ok()) else {
return Ok(V::text(text));
};
let length = match width {
CharWidth::Char => text.len(),
CharWidth::WChar => text.chars().map(char::len_utf16).sum(),
};
if length <= limit {
return Ok(V::text(text));
}
let trimmed = V::text(trim_text(&text, limit, width));
let found = format!("{length} of them");
let unit = match width {
CharWidth::Char => "octets",
CharWidth::WChar => "code units",
};
narrowed(
frame.try_construct,
Some(trimmed),
|| frame.default_value(ast, type_, path),
path,
&format!("at most {limit} {unit}"),
&found,
)
}
fn trim_text(text: &str, limit: usize, width: CharWidth) -> String {
let mut used = 0usize;
let mut out = String::new();
for character in text.chars() {
let size = match width {
CharWidth::Char => character.len_utf8(),
CharWidth::WChar => character.len_utf16(),
};
if used + size > limit {
break;
}
used += size;
out.push(character);
}
out
}
fn fit_collection<V: Source + Sink>(
ast: &Ast,
items: Vec<V>,
limit: Option<i128>,
type_: Id<TypeNode>,
frame: Frame,
path: &str,
what: &str,
) -> Result<V, ProbeError> {
let Some(limit) = limit.and_then(|limit| usize::try_from(limit).ok()) else {
return Ok(V::collection(items));
};
if items.len() <= limit {
return Ok(V::collection(items));
}
let found = format!("{}", items.len());
let mut trimmed = items;
trimmed.truncate(limit);
narrowed(
frame.try_construct,
Some(V::collection(trimmed)),
|| frame.default_value(ast, type_, path),
path,
&format!("at most {limit} {what}"),
&found,
)
}
fn dimensions(ast: &Ast, dims: &[Id<zerodds_ast::ConstExprNode>]) -> Vec<usize> {
let mut counts = Vec::with_capacity(dims.len());
for dim in dims {
let count = match ast.eval_integral(*dim) {
Ok(value) => usize::try_from(value).unwrap_or(0),
Err(_) => 0,
};
counts.push(count);
}
counts
}
fn read_array_dimension<V: Source + Sink>(
r: &mut Reader<'_>,
ast: &Ast,
element: Id<TypeNode>,
frame: Frame,
path: &str,
counts: &[usize],
) -> Result<V, ProbeError> {
let Some((&count, rest)) = counts.split_first() else {
return read_type(r, ast, element, frame, path);
};
let mut items = Vec::with_capacity(count);
for index in 0..count {
let saved =
(frame.key.element_padding == ElementPadding::Packed).then(|| r.begin_packed_element());
items.push(read_array_dimension(
r,
ast,
element,
frame,
&format!("{path}[{index}]"),
rest,
)?);
if let Some(saved) = saved {
r.finish_packed_element(saved);
}
}
Ok(V::collection(items))
}
fn read_aggregate<V: Source + Sink>(
r: &mut Reader<'_>,
ast: &Ast,
type_: Id<TypeNode>,
frame: Frame,
path: &str,
) -> Result<V, ProbeError> {
frame.within_depth(path)?;
let packed = frame.key.element_padding == ElementPadding::Packed;
let saved_origin = packed.then(|| r.begin_packed_element());
let mut deps = DepCollector::new();
let layout = plan(ast, type_, frame.key, &mut deps).map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?;
let end = if layout.needs_dheader {
let length = wire(r.read_dheader(), path)?;
Some(r.position().saturating_add(length))
} else {
None
};
let members = member_table(ast, type_, &layout);
let node = ast.type_(type_).map_err(|e| ProbeError::NoLayout {
detail: e.to_string(),
})?;
let extensibility = zerodds_ast_layout::xcdr::extensibility_of(ast, node, frame.key);
let holder = frame.key.purpose == Purpose::Key;
let original_key = frame.key;
let frame = Frame {
key: zerodds_ast_layout::member_key(ast, type_, frame.key),
..frame
};
let mutable = extensibility == Extensibility::Mutable
&& frame.key.aggregate_delimiter.frames_mutable_members();
let mut out = if mutable && frame.key.encoding.is_v1() {
read_parameter_list(r, ast, &members, frame, path)?
} else if mutable {
read_member_headers(r, ast, &members, frame, path, end)?
} else {
let tail = Tail {
end,
appendable: extensibility == Extensibility::Appendable,
};
read_in_plan_order(r, ast, &members, frame, path, tail)?
};
if let Some(end) = end {
if r.position() > end {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: format!("the members to end at offset {end}, as the DHEADER said"),
found: format!("offset {}", r.position()),
});
}
skip_to(r, end);
}
if holder {
let data_key = LayoutKey {
purpose: Purpose::Data,
..original_key
};
let full = plan(ast, type_, data_key, &mut DepCollector::default()).map_err(|e| {
ProbeError::NoLayout {
detail: e.to_string(),
}
})?;
defaults_from(ast, &member_table(ast, type_, &full), &mut out, path)?;
}
if let Some(saved) = saved_origin {
r.finish_packed_element(saved);
}
Ok(V::aggregate(out))
}
struct MemberSlot {
name: String,
type_: Id<TypeNode>,
optional: bool,
member_id: u32,
header: MemberHeader,
member: Id<zerodds_ast::MemberNode>,
try_construct: Option<TryConstruct>,
}
impl MemberSlot {
fn frame(&self, ast: &Ast, frame: Frame) -> Frame {
frame
.for_member(ast, self.try_construct, Some(self.type_), self.member)
.in_aggregate()
}
}
fn member_table(
ast: &Ast,
type_: Id<TypeNode>,
layout: &zerodds_ast_layout::TypeLayout,
) -> Vec<MemberSlot> {
let all = ast.all_members_of(type_);
let mut out = Vec::with_capacity(layout.members.len());
for member in &layout.members {
let mut found = None;
for id in &all {
let Ok(node) = ast.member(*id) else { continue };
if node.node_id == member.member {
found = Some((*id, node));
break;
}
}
let Some((id, node)) = found else { continue };
let Some(member_type) = node.type_.resolved() else {
continue;
};
out.push(MemberSlot {
name: node.name.clone(),
type_: member_type,
optional: node.facets.is_optional(),
member_id: member.member_id,
header: member.header,
member: id,
try_construct: node.facets.xtypes.as_ref().and_then(|x| x.try_construct),
});
}
out
}
#[derive(Debug, Clone, Copy)]
struct Tail {
end: Option<usize>,
appendable: bool,
}
impl Tail {
fn reached(self, r: &Reader<'_>) -> bool {
match self.end {
Some(end) => r.position() >= end,
None => self.appendable && r.remaining() == 0,
}
}
}
fn read_in_plan_order<V: Source + Sink>(
r: &mut Reader<'_>,
ast: &Ast,
members: &[MemberSlot],
frame: Frame,
path: &str,
tail: Tail,
) -> Result<Aggregate<V>, ProbeError> {
let mut out = Aggregate::new();
for (index, slot) in members.iter().enumerate() {
let member_path = format!("{path}.{}", slot.name);
if tail.reached(r) {
defaults_from(ast, &members[index..], &mut out, path)?;
break;
}
match slot.header {
MemberHeader::NoHeader => {
out.insert(
slot.name.clone(),
read_type(r, ast, slot.type_, slot.frame(ast, frame), &member_path)?,
);
}
MemberHeader::OptionalFlag => {
if wire(r.read_bool(), &member_path)? {
out.insert(
slot.name.clone(),
read_type(r, ast, slot.type_, slot.frame(ast, frame), &member_path)?,
);
}
}
MemberHeader::OptionalCdr1 { .. } => {
let header = wire(r.read_pid_as(ParameterRole::Optional), &member_path)?;
if header.end || header.pid != slot.member_id {
return Err(ProbeError::Mismatch {
path: member_path,
expected: format!(
"a parameter header for member id {}, which §7.4.1.2 writes \
whether or not the member has a value",
slot.member_id
),
found: if header.end {
format!("PID_LIST_END at offset {}", r.position())
} else {
format!("member id {} at offset {}", header.pid, r.position())
},
});
}
if header.length > 0 {
let at = r.position();
out.insert(
slot.name.clone(),
read_type(r, ast, slot.type_, slot.frame(ast, frame), &member_path)?,
);
overran(r, at, header.length, &member_path)?;
}
wire(r.finish_pid(header), &member_path)?;
}
MemberHeader::EmHeader { .. } | MemberHeader::PidCdr1 { .. } => {
return Err(ProbeError::NoLayout {
detail: format!(
"{member_path}: an MMEMBER header in a type that is not `@mutable`"
),
});
}
}
}
Ok(out)
}
fn read_member_headers<V: Source + Sink>(
r: &mut Reader<'_>,
ast: &Ast,
members: &[MemberSlot],
frame: Frame,
path: &str,
end: Option<usize>,
) -> Result<Aggregate<V>, ProbeError> {
let Some(end) = end else {
return Err(ProbeError::NoLayout {
detail: format!("{path}: a mutable type under XCDR2 with no DHEADER planned"),
});
};
let mut out = Aggregate::new();
while r.position() < end {
let at = r.position();
let header = wire(r.read_emheader(), path)?;
let mut slot = None;
for candidate in members {
if candidate.member_id == header.member_id {
slot = Some(candidate);
break;
}
}
let ends_at = r.position().saturating_add(header.extent);
let Some(slot) = slot else {
if header.must_understand {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: "a member the type declares".to_string(),
found: format!(
"member id {} with must_understand set, at offset {at}",
header.member_id
),
});
}
skip_to(r, ends_at);
continue;
};
let member_path = format!("{path}.{}", slot.name);
let value = read_type(r, ast, slot.type_, slot.frame(ast, frame), &member_path)?;
if r.position() != ends_at {
return Err(ProbeError::Mismatch {
path: member_path,
expected: format!(
"the member to end at offset {ends_at}, as length code {} said",
header.length_code
),
found: format!("offset {}", r.position()),
});
}
if out.insert(slot.name.clone(), value).is_some() {
return Err(ProbeError::Mismatch {
path: member_path,
expected: "each member once".to_string(),
found: "a second EMHEADER for the same member id".to_string(),
});
}
}
require_present(ast, members, &mut out, path)?;
Ok(out)
}
fn read_parameter_list<V: Source + Sink>(
r: &mut Reader<'_>,
ast: &Ast,
members: &[MemberSlot],
frame: Frame,
path: &str,
) -> Result<Aggregate<V>, ProbeError> {
let mut out = Aggregate::new();
loop {
if r.remaining() < 4 {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: "a PID_LIST_END sentinel".to_string(),
found: format!("the stream ended at offset {}", r.position()),
});
}
let header = wire(r.read_pid(), path)?;
if header.end {
break;
}
let at = r.position();
if header.ignore {
skip_to(r, at.saturating_add(header.length));
wire(r.finish_pid(header), path)?;
continue;
}
let mut slot = None;
if header.is_user_member() {
for candidate in members {
if candidate.member_id == header.pid {
slot = Some(candidate);
break;
}
}
}
let Some(slot) = slot else {
if header.must_understand {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: "a member the type declares".to_string(),
found: format!(
"member id {} with must_understand set, at offset {at}",
header.pid
),
});
}
skip_to(r, at.saturating_add(header.length));
wire(r.finish_pid(header), path)?;
continue;
};
let member_path = format!("{path}.{}", slot.name);
let value = read_type(r, ast, slot.type_, slot.frame(ast, frame), &member_path)?;
overran(r, at, header.length, &member_path)?;
if out.insert(slot.name.clone(), value).is_some() {
return Err(ProbeError::Mismatch {
path: member_path,
expected: "each member once".to_string(),
found: "a second parameter for the same id".to_string(),
});
}
wire(r.finish_pid(header), path)?;
}
require_present(ast, members, &mut out, path)?;
Ok(out)
}
fn overran(r: &Reader<'_>, at: usize, length: usize, path: &str) -> Result<(), ProbeError> {
if r.position() > at.saturating_add(length) {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: format!("a value within the {length} byte(s) the header declared"),
found: format!("{} byte(s)", r.position().saturating_sub(at)),
});
}
Ok(())
}
fn require_present<V: Source + Sink>(
ast: &Ast,
members: &[MemberSlot],
out: &mut Aggregate<V>,
path: &str,
) -> Result<(), ProbeError> {
defaults_from(ast, members, out, path)
}
fn defaults_from<V: Source + Sink>(
ast: &Ast,
members: &[MemberSlot],
out: &mut Aggregate<V>,
path: &str,
) -> Result<(), ProbeError> {
for slot in members {
if out.contains(&slot.name) {
continue;
}
if slot.optional {
continue;
}
let member_path = format!("{path}.{}", slot.name);
let node = ast
.member(slot.member)
.map_err(|error| ProbeError::NoLayout {
detail: format!("{member_path}: {error}"),
})?;
out.insert(
slot.name.clone(),
crate::default::member(ast, node, slot.type_, &member_path)?,
);
}
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MemberIdCheck {
Exact,
Streamed,
}
impl MemberIdCheck {
const fn is_exact(self) -> bool {
matches!(self, Self::Exact)
}
}
#[allow(clippy::too_many_arguments)]
fn read_mmember<V: Source + Sink>(
r: &mut Reader<'_>,
ast: &Ast,
header: MemberHeader,
member_id: u32,
id_check: MemberIdCheck,
type_: Id<TypeNode>,
frame: Frame,
path: &str,
) -> Result<V, ProbeError> {
match header {
MemberHeader::PidCdr1 { .. } => {
let parameter = wire(r.read_pid(), path)?;
if parameter.end || (id_check.is_exact() && parameter.pid != member_id) {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: format!("a parameter header for member id {member_id}"),
found: if parameter.end {
format!("PID_LIST_END at offset {}", r.position())
} else {
format!("member id {} at offset {}", parameter.pid, r.position())
},
});
}
let at = r.position();
let value = match read_type(r, ast, type_, frame, path) {
Ok(value) => value,
Err(discarded @ ProbeError::Discarded { .. }) => {
skip_to(r, at.saturating_add(parameter.length));
wire(r.finish_pid(parameter), path)?;
return Err(discarded);
}
Err(other) => return Err(other),
};
overran(r, at, parameter.length, path)?;
wire(r.finish_pid(parameter), path)?;
Ok(value)
}
MemberHeader::EmHeader { .. } => {
let emheader = wire(r.read_emheader(), path)?;
if id_check.is_exact() && emheader.member_id != member_id {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: format!("an EMHEADER for member id {member_id}"),
found: format!(
"member id {} at offset {}",
emheader.member_id,
r.position()
),
});
}
let ends_at = r.position().saturating_add(emheader.extent);
let value = match read_type(r, ast, type_, frame, path) {
Ok(value) => value,
Err(discarded @ ProbeError::Discarded { .. }) => {
skip_to(r, ends_at);
return Err(discarded);
}
Err(other) => return Err(other),
};
if r.position() != ends_at {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: format!("the member to end at offset {ends_at}"),
found: format!("offset {}", r.position()),
});
}
Ok(value)
}
MemberHeader::NoHeader | MemberHeader::OptionalCdr1 { .. } | MemberHeader::OptionalFlag => {
Err(ProbeError::NoLayout {
detail: format!(
"{path}: a `@mutable` member planned with a header that is not one"
),
})
}
}
}
fn case_extent_is_fixed(ast: &Ast, type_: Id<TypeNode>, frame: Frame) -> bool {
let mut deps = DepCollector::new();
plan(ast, type_, frame.key, &mut deps).is_ok_and(|layout| layout.extent.is_sized())
}
fn read_union<V: Source + Sink>(
r: &mut Reader<'_>,
ast: &Ast,
type_: Id<TypeNode>,
frame: Frame,
path: &str,
) -> Result<V, ProbeError> {
frame.within_depth(path)?;
let packed = frame.key.element_padding == ElementPadding::Packed;
let saved_origin = packed.then(|| r.begin_packed_element());
let mut deps = DepCollector::new();
let layout = plan(ast, type_, frame.key, &mut deps).map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?;
let end = if layout.needs_dheader {
let length = wire(r.read_dheader(), path)?;
Some(r.position().saturating_add(length))
} else {
None
};
let node = ast.type_(type_).map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?;
let TypeKind::Union { discriminator, .. } = &node.kind else {
return Err(ProbeError::Unsupported {
path: path.to_string(),
kind: "a union whose kind changed under the probe",
});
};
let discriminator = discriminator
.resolved()
.ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the discriminator is unresolved"),
})?;
let discriminant_path = format!("{path}._d");
let discarding = Frame {
is_discriminator: true,
..frame.resolved(TryConstruct::Discard).in_aggregate()
};
let raw = match layout.discriminator {
None => read_type(r, ast, discriminator, discarding, &discriminant_path),
Some(header) => read_mmember(
r,
ast,
header,
0,
MemberIdCheck::Exact,
discriminator,
discarding,
&discriminant_path,
),
};
let mut discriminator_defaulted = false;
let discriminant = match raw {
Ok(value) => value,
Err(discarded @ ProbeError::Discarded { .. }) => {
if ast.discriminator_try_construct(type_) != TryConstruct::UseDefault {
return Err(discarded);
}
discriminator_defaulted = true;
crate::default::of_type(ast, discriminator, &discriminant_path)?
}
Err(other) => return Err(other),
};
let selector = selector_of(ast, discriminator, &discriminant, &discriminant_path)?;
let mut out = Aggregate::new();
out.insert("_d".to_string(), discriminant);
let mut sentinel_seen = false;
let suppress_negative = union_negative_matches_nothing(ast, type_, frame.key.union_label);
let selected =
select_case(ast, type_, selector, suppress_negative).map_err(|_| ProbeError::Mismatch {
path: path.to_string(),
expected: String::from("a union whose case labels all evaluate"),
found: String::from("a case label the model cannot evaluate"),
})?;
let keep_streamed_member = discriminator_defaulted
&& frame.key.defaulted_discriminator_member == DefaultedDiscriminatorMember::StreamValue
&& selected.as_ref().is_some_and(|case| {
end.is_some() || layout.needs_sentinel || case_extent_is_fixed(ast, case.type_, frame)
});
if discriminator_defaulted && !keep_streamed_member {
match (end, layout.needs_sentinel) {
(Some(end), _) => skip_to(r, end),
(None, true) => skip_parameter_list(r, path)?,
(None, false) => {
return Err(ProbeError::Mismatch {
path: discriminant_path,
expected: "a delimiter or a sentinel, so that a defaulted \
discriminator can step over the member the writer \
selected — or read it, under \
`DefaultedDiscriminatorMember::StreamValue`"
.to_string(),
found: "an undelimited union, whose member extent nothing states".to_string(),
});
}
}
}
if let Some(case) = selected {
let member_path = format!("{path}.{}", case.name);
if discriminator_defaulted && !keep_streamed_member {
let value = crate::default::of_type(ast, case.type_, &member_path)?;
out.insert(case.name, value);
return Ok(V::aggregate(out));
}
let id_check = if keep_streamed_member {
MemberIdCheck::Streamed
} else {
MemberIdCheck::Exact
};
let case_frame = frame
.constructing(ast, case.try_construct, Some(case.type_))
.in_aggregate();
let value = match layout.members.iter().find(|plan| plan.member == case.node) {
None => read_type(r, ast, case.type_, case_frame, &member_path)?,
Some(plan) => {
match union_member_frame(
r,
plan.header,
plan.member_id,
id_check,
end,
&member_path,
)? {
UnionMember::AbsentAtSentinel => {
sentinel_seen = true;
crate::default::of_type(ast, case.type_, &member_path)?
}
UnionMember::AbsentAtDelimiter => {
crate::default::of_type(ast, case.type_, &member_path)?
}
UnionMember::Parameter(parameter) => {
let at = r.position();
let value = read_type(r, ast, case.type_, case_frame, &member_path)?;
overran(r, at, parameter.length, &member_path)?;
wire(r.finish_pid(parameter), &member_path)?;
value
}
UnionMember::Framed => read_mmember(
r,
ast,
plan.header,
plan.member_id,
id_check,
case.type_,
case_frame,
&member_path,
)?,
}
}
};
out.insert(case.name, value);
}
if discriminator_defaulted && !keep_streamed_member {
if let Some(saved) = saved_origin {
r.finish_packed_element(saved);
}
return Ok(V::aggregate(out));
}
if layout.needs_sentinel && !sentinel_seen {
let sentinel = wire(r.read_pid(), path)?;
if !sentinel.end {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: "PID_LIST_END closing the union's parameter list".to_string(),
found: format!("member id {} at offset {}", sentinel.pid, r.position()),
});
}
}
if let Some(end) = end {
if r.position() > end {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: format!("the union to end at offset {end}, as the DHEADER said"),
found: format!("offset {}", r.position()),
});
}
skip_to(r, end);
}
if let Some(saved) = saved_origin {
r.finish_packed_element(saved);
}
Ok(V::aggregate(out))
}
fn skip_parameter_list(r: &mut Reader<'_>, path: &str) -> Result<(), ProbeError> {
while r.remaining() >= 4 {
let header = wire(r.read_pid(), path)?;
if header.end {
return Ok(());
}
let at = r.position();
skip_to(r, at.saturating_add(header.length));
wire(r.finish_pid(header), path)?;
}
Err(ProbeError::Mismatch {
path: path.to_string(),
expected: "PID_LIST_END closing the union's parameter list".to_string(),
found: format!("the stream ended at offset {}", r.position()),
})
}
#[derive(Debug)]
enum UnionMember {
AbsentAtSentinel,
AbsentAtDelimiter,
Parameter(zerodds_wire::reader::PidRead),
Framed,
}
fn union_member_frame(
r: &mut Reader<'_>,
header: MemberHeader,
member_id: u32,
id_check: MemberIdCheck,
end: Option<usize>,
path: &str,
) -> Result<UnionMember, ProbeError> {
match header {
MemberHeader::PidCdr1 { .. } => {
let parameter = wire(r.read_pid(), path)?;
if parameter.end {
return Ok(UnionMember::AbsentAtSentinel);
}
if id_check.is_exact() && parameter.pid != member_id {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: format!("a parameter header for member id {member_id}"),
found: format!("member id {} at offset {}", parameter.pid, r.position()),
});
}
Ok(UnionMember::Parameter(parameter))
}
MemberHeader::EmHeader { .. } if end.is_some_and(|end| r.position() >= end) => {
Ok(UnionMember::AbsentAtDelimiter)
}
_ => Ok(UnionMember::Framed),
}
}
pub(crate) fn selector_of<V: Source>(
ast: &Ast,
discriminator: Id<TypeNode>,
value: &V,
path: &str,
) -> Result<i128, ProbeError> {
let node = ast
.type_(discriminator)
.map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?;
match &node.kind {
TypeKind::Alias { aliased } => {
let target = aliased.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the typedef target is unresolved"),
})?;
selector_of(ast, target, value, path)
}
TypeKind::Enumeration { .. } => {
let name = value.as_text().ok_or_else(|| ProbeError::Mismatch {
path: path.to_string(),
expected: "an enumerator name".to_string(),
found: "something else".to_string(),
})?;
for id in ast.enumerators_of(discriminator) {
let Ok(enumerator) = ast.enumerator(*id) else {
continue;
};
if enumerator.name == name {
if let Some(number) = ast.effective_enumerator_value(*id) {
return Ok(number);
}
}
}
Err(ProbeError::Unknown {
path: path.to_string(),
declared: Vec::new(),
})
}
TypeKind::Primitive(PrimitiveKind::Boolean) => {
let flag = value.as_bool().ok_or_else(|| ProbeError::Mismatch {
path: path.to_string(),
expected: "true or false".to_string(),
found: "something else".to_string(),
})?;
Ok(i128::from(flag))
}
TypeKind::Bitmask { .. } => {
let mut bits: u64 = 0;
for (_, position) in crate::contract::bit_names(ast, discriminator, value, path)? {
bits |= 1u64 << position;
}
Ok(i128::from(bits))
}
TypeKind::Primitive(PrimitiveKind::Char | PrimitiveKind::WChar) => {
let text = value.as_text().ok_or_else(|| ProbeError::Mismatch {
path: path.to_string(),
expected: "a one-character string".to_string(),
found: "something else".to_string(),
})?;
match text.chars().next() {
Some(ch) => Ok(i128::from(u32::from(ch))),
None => Err(ProbeError::Mismatch {
path: path.to_string(),
expected: "exactly one character".to_string(),
found: "an empty string".to_string(),
}),
}
}
_ => integer_of(value, path),
}
}
pub(crate) fn select_case(
ast: &Ast,
type_: Id<TypeNode>,
selector: i128,
suppress_negative: bool,
) -> Result<Option<crate::contract::Case>, crate::contract::UnevaluatedLabel> {
let mut fallback = None;
for id in ast.cases_of(type_) {
let Ok(node) = ast.case(*id) else { continue };
let Ok(member) = ast.member(node.member) else {
continue;
};
let Some(member_type) = member.type_.resolved() else {
continue;
};
let case = crate::contract::Case {
node: member.node_id,
name: member.name.clone(),
type_: member_type,
try_construct: member.facets.xtypes.as_ref().and_then(|x| x.try_construct),
labels: node.labels.clone(),
};
for (index, label) in node.labels.iter().enumerate() {
match label.expr() {
Some(expr) => {
let value = ast
.eval_integral(expr)
.map_err(|_| crate::contract::UnevaluatedLabel { case: index })?;
if suppress_negative && value < 0 {
continue;
}
if value == selector {
return Ok(Some(case));
}
}
None => fallback = Some(case.clone()),
}
}
}
Ok(fallback)
}
fn read_enumeration<V: Source + Sink>(
r: &mut Reader<'_>,
ast: &Ast,
type_: Id<TypeNode>,
frame: Frame,
path: &str,
) -> Result<V, ProbeError> {
let bits = enumeration_bit_bound(ast, type_, frame.key.enum_holder);
let pattern = match bits {
0 => {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: "an @bit_bound between 1 and 32".to_string(),
found: "0".to_string(),
});
}
1..=8 => u32::from(wire(r.read_u8(), path)?),
9..=16 => u32::from(wire(r.read_u16(), path)?),
17..=32 => wire(r.read_u32(), path)?,
other => {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: "an @bit_bound between 1 and 32, per §7.3.1.2.1.5".to_string(),
found: format!("{other}"),
});
}
};
let mut declared = Vec::new();
for id in ast.enumerators_of(type_) {
let Ok(node) = ast.enumerator(*id) else {
continue;
};
declared.push(node.name.clone());
let Some(value) = enumerator_wire_value(ast, *id, frame.key.enumerator_value) else {
continue;
};
if enumerator_pattern(value, bits) == Some(pattern) {
return Ok(V::enumerator(node.name.clone()));
}
}
let found = format!("{pattern:#x}, which names no enumerator; the type declares {declared:?}");
narrowed(
frame.try_construct,
None,
|| frame.default_value(ast, type_, path),
path,
"an enumerator this type declares",
&found,
)
}
fn enumerator_pattern(value: i128, bits: u16) -> Option<u32> {
if bits == 0 || bits > 32 {
return None;
}
let width = i128::from(bits);
if value < -(1i128 << (width - 1)) || value > (1i128 << width) - 1 {
return None;
}
let mask = (1u128 << width) - 1;
#[allow(clippy::cast_sign_loss)]
u32::try_from((value as u128) & mask).ok()
}
fn read_bitmask<V: Sink>(
r: &mut Reader<'_>,
ast: &Ast,
type_: Id<TypeNode>,
refuse_undeclared: bool,
path: &str,
) -> Result<V, ProbeError> {
let bound = ast.effective_bit_bound(type_);
let bits = match bound {
1..=8 => u64::from(wire(r.read_u8(), path)?),
9..=16 => u64::from(wire(r.read_u16(), path)?),
17..=32 => u64::from(wire(r.read_u32(), path)?),
33..=64 => wire(r.read_u64(), path)?,
other => {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: "an @bit_bound between 1 and 64, as Table 33 tabulates".to_string(),
found: format!("{other}"),
});
}
};
let mut names = Vec::new();
let mut declared: u64 = 0;
for id in ast.bits_of(type_) {
let Ok(node) = ast.bit_value(*id) else {
continue;
};
let Some(position) = ast.effective_bit_position(*id) else {
continue;
};
if position >= bound {
continue;
}
let mask = 1u64 << position;
declared |= mask;
if bits & mask != 0 {
names.push(node.name.clone());
}
}
if refuse_undeclared {
let width_mask = if bound >= 64 {
u64::MAX
} else {
(1u64 << bound) - 1
};
let undeclared = bits & width_mask & !declared;
if undeclared != 0 {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: format!(
"only the bits `{}` declares as a union discriminator",
ast.type_(type_)
.map_or("<bitmask>", |node| node.name.as_str())
),
found: format!(
"bit {} set, which no flag of the bitmask names",
undeclared.trailing_zeros()
),
});
}
}
Ok(V::bit_names(names))
}
fn read_bitset<V: Sink>(
r: &mut Reader<'_>,
ast: &Ast,
type_: Id<TypeNode>,
frame: Frame,
path: &str,
) -> Result<V, ProbeError> {
let fields = ast.all_bitfields_of(type_);
let mut deps = DepCollector::new();
let layout = plan(ast, type_, frame.key, &mut deps).map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?;
let Extent::Sized(anchor) = layout.extent else {
return Err(ProbeError::Unsupported {
path: path.to_string(),
kind: "a bitset whose anchor the planner does not size",
});
};
let bits = match anchor {
1 => u64::from(wire(r.read_u8(), path)?),
2 => u64::from(wire(r.read_u16(), path)?),
4 => u64::from(wire(r.read_u32(), path)?),
8 => wire(r.read_u64(), path)?,
other => {
return Err(ProbeError::Unsupported {
path: path.to_string(),
kind: match other {
0 => "a bitset with no anchor",
_ => "a bitset whose anchor is not one, two, four or eight bytes",
},
});
}
};
let mut out = Vec::new();
let mut position: u32 = 0;
for id in &fields {
let Ok(field) = ast.bitfield(*id) else {
continue;
};
let width = u32::from(field.bits);
if position.saturating_add(width) > 64 {
return Err(ProbeError::Unsupported {
path: path.to_string(),
kind: "a bitset wider than the sixty-four bits an anchor holds",
});
}
if let Some(name) = field.name.as_ref() {
let mask = if width >= 64 {
u64::MAX
} else {
(1u64 << width) - 1
};
let raw = (bits >> position) & mask;
out.push((name.clone(), i128::from(raw)));
}
position += width;
}
V::bitfields(out, path)
}
#[allow(clippy::too_many_arguments)]
fn read_map<V: Source + Sink>(
r: &mut Reader<'_>,
ast: &Ast,
type_: Id<TypeNode>,
key_type: &Ref<TypeNode>,
value_type: &Ref<TypeNode>,
bound: &Bound,
frame: Frame,
path: &str,
) -> Result<V, ProbeError> {
let key_type = key_type.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the map key type is unresolved"),
})?;
let value_type = value_type.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the map value type is unresolved"),
})?;
let mut deps = DepCollector::new();
let layout = plan(ast, type_, frame.key, &mut deps).map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?;
let end = if layout.needs_dheader {
let length = wire(r.read_dheader(), path)?;
Some(r.position().saturating_add(length))
} else {
None
};
let counted = end.is_none() || frame.map_length == MapLengthPolicy::SequenceEquivalent;
let declared = if counted {
Some(wire(r.read_u32(), path)? as usize)
} else {
None
};
let mut entries = Vec::new();
let mut index = 0usize;
loop {
match (declared, end) {
(Some(count), _) if index >= count => break,
(None, Some(end)) if r.position() >= end => break,
(None, None) => break,
_ => {}
}
let entry_path = format!("{path}[{index}]");
let key = read_type(
r,
ast,
key_type,
frame.resolved(ast.key_try_construct(type_)).in_collection(),
&format!("{entry_path}.key"),
)?;
let value = read_type(
r,
ast,
value_type,
frame
.resolved(ast.element_try_construct(type_, None))
.in_collection(),
&format!("{entry_path}.value"),
)?;
entries.push(V::map_entry(key, value));
index += 1;
}
let limit = bound_value(ast, bound);
let over_bound = limit.is_some_and(|limit| entries.len() as i128 > limit);
if let Some(end) = end {
skip_to(r, end);
}
if over_bound {
entries.sort_by_key(canonical::text);
return fit_collection(ast, entries, limit, type_, frame, path, "entries");
}
entries.sort_by_key(canonical::text);
Ok(V::collection(entries))
}
const fn layout_position(position: WcharPosition) -> WirePosition {
match position {
WcharPosition::Member => WirePosition::Member,
WcharPosition::Element => WirePosition::Element,
WcharPosition::Sentinel => WirePosition::Sentinel,
WcharPosition::ExtendedMarker => WirePosition::ExtendedMarker,
}
}
fn read_wstring(
r: &mut Reader<'_>,
policy: WstringLengthPolicy,
order: WstringUnitOrder,
position: WirePosition,
path: &str,
) -> Result<String, ProbeError> {
let declared = wire(r.read_u32(), path)? as usize;
let width = policy.character_width(position);
let mut units = if matches!(policy.counts_at(position), WstringLengthCounts::Characters) {
read_wstring_units(r, declared, width, order, path)?
} else {
if declared % width != 0 {
return Err(wstring_form_mismatch(policy, declared, path));
}
read_wstring_units(r, declared / width, width, order, path)?
};
if matches!(policy.terminator_at(position), WstringTerminator::Present) {
if declared < 1 {
return Err(wstring_form_mismatch(policy, declared, path));
}
let terminator = units.pop();
if terminator != Some(0) {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: "a NUL character ending the wstring, as its count includes one"
.to_string(),
found: format!("{:04x}", terminator.unwrap_or_default()),
});
}
}
if matches!(policy.terminator_at(position), WstringTerminator::Present) {
if let Some(offset) = units.iter().position(|unit| *unit == 0) {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: "no NUL inside a wstring a C peer would stop at".to_string(),
found: format!("a NUL at character {offset} of {}", units.len()),
});
}
}
if order.has_mark() {
strip_byte_order_mark(&mut units);
}
String::from_utf16(&units).map_err(|_| ProbeError::Mismatch {
path: path.to_string(),
expected: "well-formed UTF-16".to_string(),
found: "an unpaired surrogate".to_string(),
})
}
fn read_wstring_utf8(
r: &mut Reader<'_>,
policy: WstringLengthPolicy,
position: WirePosition,
path: &str,
) -> Result<String, ProbeError> {
let declared = wire(r.read_u32(), path)? as usize;
if declared > r.remaining() {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: "a wstring length the stream can hold".to_string(),
found: format!("{declared} byte(s) with {} left", r.remaining()),
});
}
let mut bytes = Vec::with_capacity(declared);
for _ in 0..declared {
bytes.push(wire(r.read_u8(), path)?);
}
if matches!(policy.terminator_at(position), WstringTerminator::Present) {
if bytes.pop() != Some(0) {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: "a NUL octet ending the wstring, as its count includes one".to_string(),
found: "a byte that is not zero".to_string(),
});
}
if bytes.contains(&0) {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: "no NUL inside a wstring a C peer would stop at".to_string(),
found: "a NUL before the terminator".to_string(),
});
}
}
String::from_utf8(bytes).map_err(|_| ProbeError::Mismatch {
path: path.to_string(),
expected: "well-formed UTF-8".to_string(),
found: "an invalid sequence".to_string(),
})
}
fn read_wstring_units(
r: &mut Reader<'_>,
count: usize,
width: usize,
order: WstringUnitOrder,
path: &str,
) -> Result<Vec<u16>, ProbeError> {
let bytes = count.checked_mul(width).unwrap_or(usize::MAX);
if bytes > r.remaining() {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: "a length the stream can hold".to_string(),
found: format!("{bytes} byte(s) with {} left", r.remaining()),
});
}
let mut units = Vec::with_capacity(count);
for _ in 0..count {
if width == 4 {
let wide = match order {
WstringUnitOrder::Stream => wire(r.read_u32(), path)?,
WstringUnitOrder::BigEndianAlways => wire(r.read_u32_big(), path)?,
WstringUnitOrder::ByteOrderMark | WstringUnitOrder::HostUnswapped => {
wire(r.read_u32_little(), path)?
}
};
let unit = u16::try_from(wide).map_err(|_| ProbeError::Mismatch {
path: path.to_string(),
expected: "a four-byte wstring character holding one UTF-16 code unit".to_string(),
found: format!("{wide:08x}"),
})?;
units.push(unit);
} else {
units.push(match order {
WstringUnitOrder::Stream => wire(r.read_u16(), path)?,
WstringUnitOrder::BigEndianAlways => wire(r.read_u16_big(), path)?,
WstringUnitOrder::ByteOrderMark | WstringUnitOrder::HostUnswapped => {
wire(r.read_u16_little(), path)?
}
});
}
}
Ok(units)
}
const BYTE_ORDER_MARK: u16 = 0xFEFF;
const SWAPPED_BYTE_ORDER_MARK: u16 = 0xFFFE;
fn strip_byte_order_mark(units: &mut Vec<u16>) {
match units.first().copied() {
Some(BYTE_ORDER_MARK) => {
units.remove(0);
}
Some(SWAPPED_BYTE_ORDER_MARK) => {
units.remove(0);
for unit in units.iter_mut() {
*unit = unit.swap_bytes();
}
}
_ => {
for unit in units.iter_mut() {
*unit = unit.swap_bytes();
}
}
}
}
fn wstring_form_mismatch(policy: WstringLengthPolicy, declared: usize, path: &str) -> ProbeError {
let candidates: Vec<&str> = WstringLengthPolicy::ALL_CELLS
.into_iter()
.filter(|other| *other != policy && wstring_length_is_possible(*other, declared))
.map(WstringLengthPolicy::as_str)
.collect();
let fits = if candidates.is_empty() {
String::from("no other wstring length policy either")
} else {
format!("--wstring-length {}", candidates.join(" or "))
};
ProbeError::Mismatch {
path: path.to_string(),
expected: format!(
"a wstring length the {} policy can produce",
policy.as_str()
),
found: format!("{declared}, which fits {fits}"),
}
}
const fn wstring_length_is_possible(policy: WstringLengthPolicy, declared: usize) -> bool {
if matches!(policy.counts, WstringLengthCounts::Octets)
&& declared % policy.unit.octets() as usize != 0
{
return false;
}
if matches!(policy.terminator, WstringTerminator::Present) && declared < 1 {
return false;
}
true
}
fn read_fixed<V: Sink>(
r: &mut Reader<'_>,
digits: u8,
scale: u8,
policy: FixedDigitCount,
path: &str,
) -> Result<V, ProbeError> {
let text = r
.read_fixed(digits, scale, crate::encode::fixed_digits(policy))
.map_err(|error| ProbeError::Mismatch {
path: path.to_string(),
expected: format!("a fixed<{digits}, {scale}> the stream can carry"),
found: format!("{error}"),
})?;
Ok(V::fixed(text))
}
fn read_primitive<V: Sink>(
r: &mut Reader<'_>,
kind: PrimitiveKind,
position: WcharPosition,
path: &str,
) -> Result<V, ProbeError> {
match kind {
PrimitiveKind::Boolean => Ok(V::boolean(wire(r.read_bool(), path)?)),
PrimitiveKind::Octet | PrimitiveKind::UInt8 => {
V::integer(kind, i128::from(wire(r.read_u8(), path)?), path)
}
PrimitiveKind::Int8 => V::integer(kind, i128::from(wire(r.read_i8(), path)?), path),
PrimitiveKind::Char => Ok(V::character(kind, wire(r.read_char(), path)?)),
PrimitiveKind::WChar => Ok(V::character(kind, wire(r.read_wchar(position), path)?)),
PrimitiveKind::Int16 => V::integer(kind, i128::from(wire(r.read_i16(), path)?), path),
PrimitiveKind::UInt16 => V::integer(kind, i128::from(wire(r.read_u16(), path)?), path),
PrimitiveKind::Int32 => V::integer(kind, i128::from(wire(r.read_i32(), path)?), path),
PrimitiveKind::UInt32 => V::integer(kind, i128::from(wire(r.read_u32(), path)?), path),
PrimitiveKind::Int64 => V::integer(kind, i128::from(wire(r.read_i64(), path)?), path),
PrimitiveKind::UInt64 => V::integer(kind, i128::from(wire(r.read_u64(), path)?), path),
PrimitiveKind::Float32 => V::float(kind, f64::from(wire(r.read_f32(), path)?), path),
PrimitiveKind::Float64 => V::float(kind, wire(r.read_f64(), path)?, path),
PrimitiveKind::Float128 => Ok(V::float128(wire(r.read_f128(), path)?)),
}
}
fn skip_to(r: &mut Reader<'_>, target: usize) {
while r.position() < target && r.remaining() > 0 {
if r.read_u8().is_err() {
return;
}
}
}
fn bound_value(ast: &Ast, bound: &Bound) -> Option<i128> {
match bound.expr() {
Some(expr) => ast.eval_integral(expr).ok(),
None => None,
}
}
fn integer_of<V: Source>(value: &V, path: &str) -> Result<i128, ProbeError> {
value.as_integer(path)
}
fn wire<T>(result: Result<T, WireError>, path: &str) -> Result<T, ProbeError> {
result.map_err(|error| ProbeError::Mismatch {
path: path.to_string(),
expected: "a readable value".to_string(),
found: format!("{error}"),
})
}
#[cfg(test)]
mod padding_tests {
use super::padding_present;
#[test]
fn padding_is_counted_only_where_it_can_be_present() {
assert_eq!(padding_present(8, 0x02), 2);
assert_eq!(padding_present(68, 0x02), 2);
assert_eq!(padding_present(70, 0x02), 0);
assert_eq!(padding_present(9, 0x03), 0);
assert_eq!(padding_present(8, 0x00), 0);
assert_eq!(padding_present(4, 0x01), 1);
assert_eq!(padding_present(5, 0x03), 3);
}
}