use crate::contract::{self, Consumed};
use crate::source::{Fields, Source};
use crate::value::{
Emit, MapLengthPolicy, ProbeRequest, encapsulation_header_in, encapsulation_padding,
wire_wchar_framing_of, wire_wchar_width_of, wire_wstring_unit_order_of,
};
use std::string::String;
use std::vec::Vec;
use zerodds_ast::{Ast, Bound, CharWidth, PrimitiveKind, TypeKind, TypeNode};
use zerodds_ast_core::{Id, Ref, ScopedName};
use zerodds_ast_layout::{
DepCollector, ElementPadding, Endianness, EnumeratorValue, Extent, FixedDigitCount,
KeyHashForm, LayoutKey, LengthCode, MapKeyOrdering, MemberHeader, Purpose, TypeLayout,
WirePosition, WstringCodeUnits, WstringLengthCounts, WstringLengthPolicy, WstringTerminator,
WstringUnitOrder, WstringUnitWidth, array_is_delimited, collection_is_delimited,
enumeration_bit_bound, enumerator_wire_value, key_members_with, map_key_ordering, member_key,
plan, union_negative_matches_nothing,
};
use zerodds_wire::{ParameterRole, WcharPosition, Writer};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ProbeError {
NoSuchType {
name: String,
},
NoLayout {
detail: String,
},
Mismatch {
path: String,
expected: String,
found: String,
},
Missing {
path: String,
},
Discarded {
path: String,
expected: String,
found: String,
},
Unknown {
path: String,
declared: Vec<String>,
},
Unsupported {
path: String,
kind: &'static str,
},
GeneratorDefect {
path: String,
kind: &'static str,
},
MappingDivergence {
path: String,
kind: &'static str,
},
Unfilled {
path: String,
kind: &'static str,
},
ValueTooDeep {
path: String,
limit: usize,
},
NoKey {
type_name: String,
},
}
impl core::fmt::Display for ProbeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::NoSuchType { name } => write!(f, "`{name}` is not in the model"),
Self::NoLayout { detail } => write!(f, "the layout could not be planned: {detail}"),
Self::Mismatch {
path,
expected,
found,
} => write!(f, "{path}: expected {expected}, found {found}"),
Self::Missing { path } => write!(f, "{path}: no value, and the member is not optional"),
Self::Discarded {
path,
expected,
found,
} => write!(
f,
"the sample is discarded: at {path} this type holds {expected} and the \
sample carries {found}, and `@try_construct` there is DISCARD"
),
Self::Unknown { path, declared } => {
write!(f, "{path}: not a member; the type declares {declared:?}")
}
Self::Unsupported { path, kind } => write!(f, "{path}: {kind}"),
Self::GeneratorDefect { path, kind } => write!(f, "{path}: {kind}"),
Self::MappingDivergence { path, kind } => write!(f, "{path}: {kind}"),
Self::Unfilled { path, kind } => {
let article = if kind.starts_with(['a', 'e', 'i', 'o', 'u']) {
"an"
} else {
"a"
};
write!(
f,
"{path}: {article} {kind}, which this probe does not carry yet"
)
}
Self::ValueTooDeep { path, limit } => write!(
f,
"{path}: the value nests more than {limit} aggregates deep; a value whose object \
graph has a cycle is not serializable — XTypes 1.3 §7.3.1.2.1.4"
),
Self::NoKey { type_name } => write!(
f,
"`{type_name}` declares no `@key`, so it has one instance and no KeyHash"
),
}
}
}
impl core::error::Error for ProbeError {}
#[derive(Debug, Clone, Copy)]
struct Frame {
key: LayoutKey,
map_length: MapLengthPolicy,
wchar: WcharPosition,
depth: usize,
}
impl Frame {
fn in_collection(self) -> Self {
Self {
wchar: WcharPosition::Element,
..self
}
}
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(())
}
}
struct Sink {
w: Writer,
seen: Consumed,
}
pub fn encode<V: Source>(
ast: &Ast,
request: &ProbeRequest,
values: &V,
) -> Result<Vec<u8>, ProbeError> {
encode_recording(ast, request, values, &mut Consumed::new())
}
pub fn encode_recording<V: Source>(
ast: &Ast,
request: &ProbeRequest,
values: &V,
seen: &mut Consumed,
) -> Result<Vec<u8>, ProbeError> {
encode_noting_padding(ast, request, values, seen, false, 0).map(|(bytes, _)| bytes)
}
pub fn encode_continuing<V: Source>(
ast: &Ast,
request: &ProbeRequest,
values: &V,
start: usize,
) -> Result<Vec<u8>, ProbeError> {
if request.emit != Emit::Payload {
return Err(ProbeError::Unsupported {
path: request.type_name.clone(),
kind: "a continuation behind an encapsulation header or as a KeyHash",
});
}
encode_noting_padding(ast, request, values, &mut Consumed::new(), false, start)
.map(|(bytes, _)| bytes)
}
pub fn encode_with_padding<V: Source>(
ast: &Ast,
request: &ProbeRequest,
values: &V,
) -> Result<(Vec<u8>, Vec<bool>), ProbeError> {
let (bytes, spans) =
encode_noting_padding(ast, request, values, &mut Consumed::new(), true, 0)?;
let mut mask = vec![false; bytes.len()];
for (at, length) in spans {
for octet in mask.iter_mut().skip(at).take(length) {
*octet = true;
}
}
Ok((bytes, mask))
}
type StreamWithPadding = (Vec<u8>, Vec<(usize, usize)>);
fn encode_noting_padding<V: Source>(
ast: &Ast,
request: &ProbeRequest,
values: &V,
seen: &mut Consumed,
note_padding: bool,
start: usize,
) -> Result<StreamWithPadding, ProbeError> {
let type_ = find_type(ast, &request.type_name)?;
let root = contract::root_path(ast, type_);
if request.emit == Emit::KeyHash {
return key_hash(ast, type_, values, request, seen).map(|bytes| (bytes, Vec::new()));
}
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,
wchar: WcharPosition::Member,
depth: 0,
};
let writer = Writer::continuing(
wire_endianness(request.endianness),
usize::from(request.encoding.max_alignment()),
vec![0; start],
)
.with_pl_cdr1_framing(request.pl_cdr1_framing())
.with_wchar_width(request.wchar_width())
.with_wchar_framing(request.wire_wchar_framing())
.with_wstring_unit_order(request.wire_wstring_unit_order())
.with_short_length_codes(request.length_codes.short_code_where_it_fits());
let mut sink = Sink {
w: if note_padding {
writer.recording_padding()
} else {
writer
},
seen: core::mem::take(seen),
};
let result = write_type(&mut sink, ast, type_, values, frame, &root);
*seen = core::mem::take(&mut sink.seen);
result?;
let mut spans = sink.w.padding_spans().to_vec();
let mut payload = sink.w.finish();
payload.drain(..start);
if request.emit == Emit::PayloadWithEncapsulation {
let padding = encapsulation_padding(payload.len());
let mut out = Vec::with_capacity(payload.len() + 4 + usize::from(padding));
out.extend_from_slice(&encapsulation_header_in(
request.encapsulation_ids,
request.encoding,
request.endianness,
request.endianness == Endianness::Little,
padding,
));
out.extend_from_slice(&payload);
for span in &mut spans {
span.0 += 4;
}
if padding != 0 {
spans.push((out.len(), usize::from(padding)));
}
out.resize(out.len() + usize::from(padding), 0);
return Ok((out, spans));
}
Ok((payload, spans))
}
fn key_hash<V: Source>(
ast: &Ast,
type_: Id<TypeNode>,
values: &V,
request: &ProbeRequest,
seen: &mut Consumed,
) -> Result<Vec<u8>, ProbeError> {
key_material(ast, type_, values, request, seen, true)
}
pub fn serialized_key<V: Source>(
ast: &Ast,
request: &ProbeRequest,
values: &V,
) -> Result<Vec<u8>, ProbeError> {
let type_ = find_type(ast, &request.type_name)?;
let root = contract::root_path(ast, type_);
let frame = Frame {
key: request.layout_key(Purpose::Key),
map_length: request.map_length,
wchar: WcharPosition::Member,
depth: 0,
};
let writer = Writer::with_max_align(
wire_endianness(request.endianness),
usize::from(request.encoding.max_alignment()),
)
.with_pl_cdr1_framing(request.pl_cdr1_framing())
.with_wchar_width(request.wchar_width())
.with_wchar_framing(request.wire_wchar_framing())
.with_wstring_unit_order(request.wire_wstring_unit_order())
.with_short_length_codes(request.length_codes.short_code_where_it_fits());
let mut sink = Sink {
w: writer,
seen: Consumed::new(),
};
write_type(&mut sink, ast, type_, values, frame, &root)?;
let payload = sink.w.finish();
if request.emit != Emit::PayloadWithEncapsulation {
return Ok(payload);
}
let padding = encapsulation_padding(payload.len());
let mut out = Vec::with_capacity(payload.len() + 4 + usize::from(padding));
out.extend_from_slice(&encapsulation_header_in(
request.encapsulation_ids,
request.encoding,
request.endianness,
request.endianness == Endianness::Little,
padding,
));
out.extend_from_slice(&payload);
out.resize(out.len() + usize::from(padding), 0);
Ok(out)
}
pub fn key_holder<V: Source>(
ast: &Ast,
request: &ProbeRequest,
values: &V,
) -> Result<Vec<u8>, ProbeError> {
let type_ = find_type(ast, &request.type_name)?;
key_material(ast, type_, values, request, &mut Consumed::new(), false)
}
fn key_material<V: Source>(
ast: &Ast,
type_: Id<TypeNode>,
values: &V,
request: &ProbeRequest,
seen: &mut Consumed,
hash: bool,
) -> Result<Vec<u8>, ProbeError> {
let holder_key = request.key_holder_key();
let form = zerodds_ast_layout::keyhash::key_hash_form_for(ast, type_, holder_key).map_err(
|error| ProbeError::NoLayout {
detail: format!("{error}"),
},
)?;
let frame = Frame {
key: holder_key,
map_length: request.map_length,
wchar: WcharPosition::Member,
depth: 0,
};
let mut sink = Sink {
w: Writer::with_max_align(
zerodds_wire::Endianness::Big,
usize::from(
holder_key
.key_holder_alignment
.max_alignment(holder_key.encoding),
),
)
.with_wchar_width(wire_wchar_width_of(holder_key.wchar_width))
.with_wchar_framing(wire_wchar_framing_of(holder_key.wchar_framing))
.with_wstring_unit_order(wire_wstring_unit_order_of(holder_key.wstring_unit_order)),
seen: core::mem::take(seen),
};
let root = contract::root_path(ast, type_);
if let Some(discriminator) = zerodds_ast_layout::union_key_discriminator(ast, type_) {
let path = contract::join(&root, "_d");
let value = values
.field("_d")
.ok_or_else(|| ProbeError::Missing { path: path.clone() })?;
write_type(&mut sink, ast, discriminator, value, frame, &path)?;
*seen = core::mem::take(&mut sink.seen);
if !hash {
return Ok(sink.w.finish());
}
return Ok(match form {
KeyHashForm::ZeroPadded => sink.w.zero_padded().to_vec(),
KeyHashForm::Md5 => sink.w.md5().to_vec(),
});
}
let keys = key_members_with(
ast,
type_,
holder_key.key_holder_nesting,
holder_key.key_holder_order,
)
.map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?;
if keys.is_empty() {
return Err(ProbeError::NoKey {
type_name: request.type_name.clone(),
});
}
let mut result = Ok(());
for node in &keys {
let Some((name, member_type, _)) = member_by_node(ast, type_, *node) else {
continue;
};
let path = contract::join(&root, &name);
let Some(value) = values.field(&name) else {
result = Err(ProbeError::Missing { path });
break;
};
result = write_type(&mut sink, ast, member_type, value, frame, &path);
if result.is_err() {
break;
}
}
*seen = core::mem::take(&mut sink.seen);
result?;
if !hash {
return Ok(sink.w.finish());
}
Ok(match form {
KeyHashForm::ZeroPadded => sink.w.zero_padded().to_vec(),
KeyHashForm::Md5 => sink.w.md5().to_vec(),
})
}
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(),
})
}
fn member_by_node(
ast: &Ast,
aggregate: Id<TypeNode>,
node: zerodds_ast_core::NodeId,
) -> Option<(String, Id<TypeNode>, bool)> {
contract::members(ast, aggregate)
.into_iter()
.find(|member| member.node == node)
.map(|member| (member.name, member.type_, member.optional))
}
#[allow(clippy::too_many_lines)]
fn write_type<V: Source>(
s: &mut Sink,
ast: &Ast,
type_: Id<TypeNode>,
value: &V,
frame: Frame,
path: &str,
) -> Result<(), ProbeError> {
let node = ast.type_(type_).map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?;
match &node.kind {
TypeKind::Primitive(kind) => write_primitive(s, *kind, value, frame.wchar, path),
TypeKind::String { width, bound } => {
write_string(s, ast, *width, *bound, value, frame, path)
}
TypeKind::Fixed { digits, scale } => {
write_fixed(s, *digits, *scale, frame.key.fixed_digits, value, path)
}
TypeKind::Alias { aliased } => {
let target = aliased.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the typedef target is unresolved"),
})?;
write_type(s, ast, target, value, frame, path)
}
TypeKind::Enumeration { .. } => write_enumeration(s, ast, type_, value, frame, path),
TypeKind::Bitmask { .. } => write_bitmask(s, ast, type_, value, path),
TypeKind::Bitset { .. } => write_bitset(s, ast, type_, value, frame, path),
TypeKind::Sequence { element, bound } => {
let element = element.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the sequence element is unresolved"),
})?;
let items = contract::bounded_items(ast, value, *bound, "elements", path)?;
let delimited = collection_is_delimited(ast, element, frame.key);
let dheader = delimited.then(|| s.w.write_dheader());
#[allow(clippy::cast_possible_truncation)]
s.w.write_u32(items.len() as u32);
let carries_length = sequence_element_carries_length(ast, element, frame.key)?;
let packed = frame.key.element_padding == ElementPadding::Packed;
for (index, item) in items.iter().enumerate() {
let element_path = format!("{path}[{index}]");
let saved = packed.then(|| s.w.begin_packed_element());
let length = carries_length.then(|| s.w.write_dheader());
write_type(s, ast, element, item, frame.in_collection(), &element_path)?;
if let Some(length) = length {
s.w.try_finish_dheader(length)
.map_err(|error| refused(path, error))?;
}
if let Some(saved) = saved {
s.w.finish_packed_element(saved);
}
}
if let Some(dheader) = dheader {
s.w.try_finish_dheader(dheader)
.map_err(|error| refused(path, error))?;
}
Ok(())
}
TypeKind::Array { element, dims } => {
let element = element.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the array element is unresolved"),
})?;
let counts = contract::array_dimensions(ast, dims, path)?;
let delimited = array_is_delimited(ast, element, frame.key);
let dheader = delimited.then(|| s.w.write_dheader());
write_array_dimension(s, ast, element, value, frame.in_collection(), path, &counts)?;
if let Some(dheader) = dheader {
s.w.try_finish_dheader(dheader)
.map_err(|error| refused(path, error))?;
}
Ok(())
}
TypeKind::Map {
key,
value: v,
bound,
} => write_map(s, ast, type_, key, v, *bound, value, frame, path),
TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
write_aggregate(s, ast, type_, value, frame, path)
}
TypeKind::Union { .. } => write_union(s, ast, type_, value, frame, path),
other => Err(ProbeError::Unfilled {
path: path.to_string(),
kind: other.as_str(),
}),
}
}
fn write_array_dimension<V: Source>(
s: &mut Sink,
ast: &Ast,
element: Id<TypeNode>,
value: &V,
frame: Frame,
path: &str,
counts: &[usize],
) -> Result<(), ProbeError> {
let Some((&count, rest)) = counts.split_first() else {
return write_type(s, ast, element, value, frame, path);
};
let items = contract::array(value, path)?;
if items.len() != count {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: format!("exactly {count} elements"),
found: format!("{}", items.len()),
});
}
for (index, item) in items.iter().enumerate() {
let saved = (frame.key.element_padding == ElementPadding::Packed)
.then(|| s.w.begin_packed_element());
write_array_dimension(
s,
ast,
element,
item,
frame,
&format!("{path}[{index}]"),
rest,
)?;
if let Some(saved) = saved {
s.w.finish_packed_element(saved);
}
}
Ok(())
}
fn write_aggregate<V: Source>(
s: &mut Sink,
ast: &Ast,
type_: Id<TypeNode>,
value: &V,
frame: Frame,
path: &str,
) -> Result<(), ProbeError> {
frame.within_depth(path)?;
let packed = frame.key.element_padding == ElementPadding::Packed;
let saved_origin = packed.then(|| s.w.begin_packed_element());
let object = contract::object(value, path)?;
let mut deps = DepCollector::new();
let layout = plan(ast, type_, frame.key, &mut deps).map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?;
let members = contract::members(ast, type_);
let declared: Vec<String> = members.iter().map(|member| member.name.clone()).collect();
contract::reject_unknown_keys(object, &declared, path)?;
let dheader = layout.needs_dheader.then(|| s.w.write_dheader());
let frame = Frame {
key: member_key(ast, type_, frame.key),
..frame.in_aggregate()
};
for planned in &layout.members {
let Some(member) = members.iter().find(|member| member.node == planned.member) else {
continue;
};
let member_path = contract::join(path, &member.name);
let member_value = contract::member_value(object, member, path)?;
match (planned.header, member_value) {
(MemberHeader::OptionalFlag, present) => {
s.w.write_bool(present.is_some());
if let Some(value) = present {
write_type(s, ast, member.type_, value, frame, &member_path)?;
}
}
(
MemberHeader::OptionalCdr1 {
pid,
extended,
must_understand,
},
present,
) => {
let header = if extended {
s.w.write_pid_extended(
planned.member_id,
ParameterRole::Optional,
must_understand,
)
} else {
s.w.write_pid(pid, ParameterRole::Optional, must_understand)
}
.map_err(|error| refused(&member_path, error))?;
if let Some(value) = present {
write_type(s, ast, member.type_, value, frame, &member_path)?;
}
s.w.finish_pid(header);
}
(
MemberHeader::NoHeader
| MemberHeader::PidCdr1 { .. }
| MemberHeader::EmHeader { .. },
None,
) => {}
(MemberHeader::NoHeader, Some(value)) => {
write_type(s, ast, member.type_, value, frame, &member_path)?;
}
(
MemberHeader::PidCdr1 {
pid,
extended,
must_understand,
},
Some(value),
) => {
let header = if extended {
s.w.write_pid_extended(
planned.member_id,
ParameterRole::Member,
must_understand,
)
} else {
s.w.write_pid(pid, ParameterRole::Member, must_understand)
}
.map_err(|error| refused(&member_path, error))?;
write_type(s, ast, member.type_, value, frame, &member_path)?;
s.w.finish_pid(header);
}
(
MemberHeader::EmHeader {
lc,
must_understand,
},
Some(value),
) => {
let header =
s.w.write_emheader(planned.member_id, length_code(lc), must_understand)
.map_err(|error| refused(&member_path, error))?;
write_type(s, ast, member.type_, value, frame, &member_path)?;
s.w.finish_emheader(header)
.map_err(|error| refused(&member_path, error))?;
}
}
}
if layout.needs_sentinel {
s.w.write_sentinel();
}
if let Some(dheader) = dheader {
s.w.try_finish_dheader(dheader)
.map_err(|error| refused(path, error))?;
}
if let Some(saved) = saved_origin {
s.w.finish_packed_element(saved);
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn write_mmember<V: Source>(
s: &mut Sink,
ast: &Ast,
header: MemberHeader,
member_id: u32,
type_: Id<TypeNode>,
value: &V,
frame: Frame,
path: &str,
) -> Result<(), ProbeError> {
match header {
MemberHeader::PidCdr1 {
pid,
extended,
must_understand,
} => {
let header = if extended {
s.w.write_pid_extended(member_id, ParameterRole::Member, must_understand)
} else {
s.w.write_pid(pid, ParameterRole::Member, must_understand)
}
.map_err(|error| refused(path, error))?;
write_type(s, ast, type_, value, frame, path)?;
s.w.finish_pid(header);
Ok(())
}
MemberHeader::EmHeader {
lc,
must_understand,
} => {
let header =
s.w.write_emheader(member_id, length_code(lc), must_understand)
.map_err(|error| refused(path, error))?;
write_type(s, ast, type_, value, frame, path)?;
s.w.finish_emheader(header)
.map_err(|error| refused(path, error))?;
Ok(())
}
MemberHeader::NoHeader | MemberHeader::OptionalCdr1 { .. } | MemberHeader::OptionalFlag => {
Err(ProbeError::NoLayout {
detail: format!(
"{path}: a `@mutable` member planned with a header that is not one"
),
})
}
}
}
fn write_union<V: Source>(
s: &mut Sink,
ast: &Ast,
type_: Id<TypeNode>,
value: &V,
frame: Frame,
path: &str,
) -> Result<(), ProbeError> {
frame.within_depth(path)?;
let packed = frame.key.element_padding == ElementPadding::Packed;
let saved_origin = packed.then(|| s.w.begin_packed_element());
let object = contract::object(value, path)?;
let discriminator_value = contract::discriminator(object, path)?;
let mut deps = DepCollector::new();
let layout = plan(ast, type_, frame.key, &mut deps).map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?;
let dheader = layout.needs_dheader.then(|| s.w.write_dheader());
let frame = frame.in_aggregate();
let TypeKind::Union { discriminator, .. } = &ast
.type_(type_)
.map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?
.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 discriminator_path = contract::join(path, "_d");
match layout.discriminator {
None => write_type(
s,
ast,
discriminator,
discriminator_value,
frame,
&discriminator_path,
)?,
Some(header) => write_mmember(
s,
ast,
header,
0,
discriminator,
discriminator_value,
frame,
&discriminator_path,
)?,
}
let cases = contract::union_cases(ast, type_);
let suppress_negative = union_negative_matches_nothing(ast, type_, frame.key.union_label);
if suppress_negative {
write_selected_branch(
s,
ast,
&cases,
object,
discriminator_value,
&layout,
frame,
path,
suppress_negative,
)?;
} else {
for (name, member_value) in object {
if name == "_d" {
continue;
}
let case =
cases
.iter()
.find(|case| case.name == name)
.ok_or_else(|| ProbeError::Unknown {
path: contract::join(path, name),
declared: cases.iter().map(|case| case.name.clone()).collect(),
})?;
let member_path = contract::join(path, name);
contract::reject_null(member_value, &member_path)?;
match layout.members.iter().find(|plan| plan.member == case.node) {
None => write_type(s, ast, case.type_, member_value, frame, &member_path)?,
Some(plan) => write_mmember(
s,
ast,
plan.header,
plan.member_id,
case.type_,
member_value,
frame,
&member_path,
)?,
}
}
}
if layout.needs_sentinel {
s.w.write_sentinel();
}
if let Some(dheader) = dheader {
s.w.try_finish_dheader(dheader)
.map_err(|error| refused(path, error))?;
}
if let Some(saved) = saved_origin {
s.w.finish_packed_element(saved);
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn write_selected_branch<V: Source>(
s: &mut Sink,
ast: &Ast,
cases: &[contract::Case],
object: Fields<'_, V>,
discriminator_value: &V,
layout: &TypeLayout,
frame: Frame,
path: &str,
suppress_negative: bool,
) -> Result<(), ProbeError> {
let discriminator = contract::integer(discriminator_value, &contract::join(path, "_d"))?;
let named = object.names().into_iter().find(|name| *name != "_d");
for name in object.names() {
if name != "_d" && !cases.iter().any(|case| case.name == name) {
return Err(ProbeError::Unknown {
path: contract::join(path, name),
declared: cases.iter().map(|case| case.name.clone()).collect(),
});
}
}
let selection = contract::union_selection(ast, cases, discriminator, suppress_negative)
.map_err(|unevaluated| ProbeError::Mismatch {
path: path.to_string(),
expected: String::from("a union whose case labels all evaluate"),
found: format!(
"the label of case `{}`, which the model cannot evaluate",
cases
.get(unevaluated.case)
.map_or("<out of range>", |case| case.name.as_str())
),
})?;
let Some(index) = selection else {
return Ok(());
};
let case = &cases[index];
let member_path = contract::join(path, &case.name);
let planned = layout.members.iter().find(|plan| plan.member == case.node);
if named == Some(case.name.as_str()) {
let member_value = object.get(&case.name).ok_or_else(|| ProbeError::Missing {
path: member_path.clone(),
})?;
contract::reject_null(member_value, &member_path)?;
return match planned {
None => write_type(s, ast, case.type_, member_value, frame, &member_path),
Some(plan) => write_mmember(
s,
ast,
plan.header,
plan.member_id,
case.type_,
member_value,
frame,
&member_path,
),
};
}
let width = storage_width(ast, case.type_, frame.key).ok_or(ProbeError::Unsupported {
path: member_path.clone(),
kind: "a union branch with no fixed-width storage to overlay",
})?;
let octets =
match named {
None => vec![0_u8; width],
Some(name) => {
let source = cases.iter().find(|case| case.name == name).ok_or_else(|| {
ProbeError::Unknown {
path: contract::join(path, name),
declared: cases.iter().map(|case| case.name.clone()).collect(),
}
})?;
let source_width =
storage_width(ast, source.type_, frame.key).ok_or(ProbeError::Unsupported {
path: contract::join(path, name),
kind: "a union branch with no fixed-width storage to overlay",
})?;
let mut host = Sink {
w: Writer::little_endian(),
seen: Consumed::new(),
};
let named_value = object.get(name).ok_or_else(|| ProbeError::Missing {
path: contract::join(path, name),
})?;
write_type(
&mut host,
ast,
source.type_,
named_value,
frame,
&contract::join(path, name),
)?;
s.seen.absorb(&host.seen);
let mut octets = host.w.finish();
if octets.len() != source_width {
return Err(ProbeError::Unsupported {
path: contract::join(path, name),
kind: "a union branch whose octets are not its storage",
});
}
octets.resize(width, 0);
octets
}
};
let mut pattern = [0_u8; 8];
pattern[..width].copy_from_slice(&octets[..width]);
let pattern = u64::from_le_bytes(pattern);
#[allow(clippy::cast_possible_truncation)]
fn write_pattern(s: &mut Sink, width: usize, pattern: u64) {
match width {
1 => s.w.write_u8(pattern as u8),
2 => s.w.write_u16(pattern as u16),
4 => s.w.write_u32(pattern as u32),
_ => s.w.write_u64(pattern),
}
}
if named.is_none() {
s.seen.scalar(&member_path);
}
match planned {
None => write_pattern(s, width, pattern),
Some(plan) => match plan.header {
MemberHeader::PidCdr1 {
pid,
extended,
must_understand,
} => {
let header = if extended {
s.w.write_pid_extended(plan.member_id, ParameterRole::Member, must_understand)
} else {
s.w.write_pid(pid, ParameterRole::Member, must_understand)
}
.map_err(|error| refused(&member_path, error))?;
write_pattern(s, width, pattern);
s.w.finish_pid(header);
}
MemberHeader::EmHeader {
lc,
must_understand,
} => {
let header =
s.w.write_emheader(plan.member_id, length_code(lc), must_understand)
.map_err(|error| refused(&member_path, error))?;
write_pattern(s, width, pattern);
s.w.finish_emheader(header)
.map_err(|error| refused(&member_path, error))?;
}
MemberHeader::NoHeader
| MemberHeader::OptionalCdr1 { .. }
| MemberHeader::OptionalFlag => {
return Err(ProbeError::NoLayout {
detail: format!(
"{member_path}: a `@mutable` member planned with a header that is not one"
),
});
}
},
}
Ok(())
}
fn storage_width(ast: &Ast, type_: Id<TypeNode>, key: LayoutKey) -> Option<usize> {
let mut deps = DepCollector::new();
let layout = plan(ast, type_, key, &mut deps).ok()?;
let width = usize::try_from(layout.extent.sized()?).ok()?;
matches!(width, 1 | 2 | 4 | 8).then_some(width)
}
fn write_enumeration<V: Source>(
s: &mut Sink,
ast: &Ast,
type_: Id<TypeNode>,
value: &V,
frame: Frame,
path: &str,
) -> Result<(), ProbeError> {
let (name, declared) = contract::enumerator(ast, type_, value, path)?;
s.seen.scalar(path);
let bits = enumeration_bit_bound(ast, type_, frame.key.enum_holder);
let value = match wire_value_of(ast, type_, &name, frame.key.enumerator_value) {
Ok(Some(value)) => value,
Ok(None) | Err(StaleEnumerator) => {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: format!(
"an enumerator whose wire value the `{}` setting can compute",
frame.key.enumerator_value.as_str()
),
found: format!("{name}, declared {declared}, whose wire value does not evaluate"),
});
}
};
let pattern = enumerator_pattern(value, bits).ok_or_else(|| ProbeError::Mismatch {
path: path.to_string(),
expected: format!("an enumerator a {bits}-bit holder can hold"),
found: format!("{name} = {value}"),
})?;
#[allow(clippy::cast_possible_truncation)]
match bits {
1..=8 => s.w.write_u8(pattern as u8),
9..=16 => s.w.write_u16(pattern as u16),
_ => s.w.write_u32(pattern),
}
Ok(())
}
fn wire_value_of(
ast: &Ast,
type_: Id<TypeNode>,
name: &str,
policy: EnumeratorValue,
) -> Result<Option<i128>, StaleEnumerator> {
for id in ast.enumerators_of(type_) {
let node = ast.enumerator(*id).map_err(|_| StaleEnumerator)?;
if node.name == name {
return Ok(enumerator_wire_value(ast, *id, policy));
}
}
Ok(None)
}
struct StaleEnumerator;
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;
u32::try_from((value as u128) & mask).ok()
}
fn write_string<V: Source>(
s: &mut Sink,
ast: &Ast,
width: CharWidth,
bound: Bound,
value: &V,
frame: Frame,
path: &str,
) -> Result<(), ProbeError> {
let text = contract::text(value, path)?;
s.seen.scalar(path);
if let Some(limit) = bound.expr().and_then(|expr| ast.eval_integral(expr).ok()) {
let (count, noun) = match width {
CharWidth::Char => (text.len(), "octets"),
CharWidth::WChar => (text.chars().map(utf16_units).sum(), "UTF-16 code units"),
};
if i128::try_from(count).unwrap_or(i128::MAX) > limit {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: format!("at most {limit} {noun}, which is what the bound holds"),
found: format!("{count}"),
});
}
}
match width {
CharWidth::Char => {
s.w.write_string(text);
Ok(())
}
CharWidth::WChar => {
if frame.key.wstring_code_units == WstringCodeUnits::Utf8 {
write_wstring_utf8(
&mut s.w,
text,
frame.key.wstring_length,
layout_position(frame.wchar),
path,
)
} else {
write_wstring(
&mut s.w,
text,
frame.key.wstring_length,
frame.key.wstring_unit_order,
layout_position(frame.wchar),
path,
)
}
}
}
}
const fn utf16_units(character: char) -> usize {
if (character as u32) > 0xFFFF { 2 } else { 1 }
}
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 write_wstring(
w: &mut Writer,
text: &str,
policy: WstringLengthPolicy,
order: WstringUnitOrder,
position: WirePosition,
path: &str,
) -> Result<(), ProbeError> {
let mut units: Vec<u16> =
if matches!(policy.counts_at(position), WstringLengthCounts::Characters)
&& policy.unit_at(position) == WstringUnitWidth::Two
{
if text.chars().any(|c| c as u32 > 0xFFFF) {
return Err(ProbeError::Unsupported {
path: path.to_string(),
kind: concat!(
"a code point outside the BMP under a character count over two-octet ",
"units, which has no way to carry it — Fast CDR truncates it to ",
"sixteen bits and sends a different character"
),
});
}
#[allow(clippy::cast_possible_truncation)]
text.chars().map(|c| c as u32 as u16).collect()
} else {
text.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);
}
let width = policy.character_width(position);
#[allow(clippy::cast_possible_truncation)]
let declared = match policy.counts_at(position) {
WstringLengthCounts::Octets => (units.len() * width) as u32,
WstringLengthCounts::Characters => units.len() as u32,
};
w.write_u32(declared);
for unit in units {
write_wstring_unit(w, unit, width, policy, order);
}
Ok(())
}
fn write_wstring_utf8(
w: &mut Writer,
text: &str,
policy: WstringLengthPolicy,
position: WirePosition,
path: &str,
) -> Result<(), ProbeError> {
let bytes = text.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(|_| ProbeError::Unsupported {
path: path.to_string(),
kind: "a wstring whose UTF-8 octet count fits the UInt32 length field",
})?;
w.write_u32(declared);
for byte in bytes {
w.write_u8(*byte);
}
if terminator {
w.write_u8(0);
}
Ok(())
}
pub(crate) fn sequence_element_carries_length(
ast: &Ast,
element: Id<TypeNode>,
key: LayoutKey,
) -> Result<bool, ProbeError> {
zerodds_ast_layout::xcdr::sequence_element_carries_length(ast, element, key).ok_or_else(|| {
ProbeError::NoLayout {
detail: format!(
"the sequence element {element:?} is not a type the model holds, so whether it \
carries its own length cannot be told"
),
}
})
}
const BYTE_ORDER_MARK: u16 = 0xFEFF;
fn write_wstring_unit(
w: &mut Writer,
unit: u16,
width: usize,
policy: WstringLengthPolicy,
order: WstringUnitOrder,
) {
let _ = policy;
match (width, order) {
(2, WstringUnitOrder::Stream) => w.write_u16(unit),
(2, WstringUnitOrder::BigEndianAlways) => w.write_u16_big(unit),
(2, WstringUnitOrder::ByteOrderMark | WstringUnitOrder::HostUnswapped) => {
w.write_u16_little(unit);
}
(_, WstringUnitOrder::Stream) => w.write_u32(u32::from(unit)),
(_, WstringUnitOrder::BigEndianAlways) => w.write_u32_big(u32::from(unit)),
(_, WstringUnitOrder::ByteOrderMark | WstringUnitOrder::HostUnswapped) => {
w.write_u32_little(u32::from(unit));
}
}
}
fn write_bitmask<V: Source>(
s: &mut Sink,
ast: &Ast,
type_: Id<TypeNode>,
value: &V,
path: &str,
) -> Result<(), ProbeError> {
let set = contract::bit_names(ast, type_, value, path)?;
let bound = ast.effective_bit_bound(type_);
let mut bits: u64 = 0;
for (index, (_, position)) in set.iter().enumerate() {
s.seen.scalar(&format!("{path}[{index}]"));
bits |= 1u64 << position;
}
match bound {
1..=8 => s.w.write_u8(u8::try_from(bits).unwrap_or(0)),
9..=16 => s.w.write_u16(u16::try_from(bits).unwrap_or(0)),
17..=32 => s.w.write_u32(u32::try_from(bits).unwrap_or(0)),
_ => s.w.write_u64(bits),
}
Ok(())
}
fn write_bitset<V: Source>(
s: &mut Sink,
ast: &Ast,
type_: Id<TypeNode>,
value: &V,
frame: Frame,
path: &str,
) -> Result<(), ProbeError> {
let fields = ast.all_bitfields_of(type_);
let object = contract::object(value, path)?;
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 declared = contract::bitfield_names(ast, &fields);
contract::reject_unknown_keys(object, &declared, path)?;
let mut bits: u64 = 0;
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 member_path = contract::join(path, name);
let Some(member_value) = object.get(name.as_str()) else {
return Err(ProbeError::Missing { path: member_path });
};
let raw = contract::integer(member_value, &member_path)?;
s.seen.scalar(&member_path);
let limit = 1i128 << width;
if raw < 0 || raw >= limit {
return Err(ProbeError::Mismatch {
path: member_path,
expected: format!("an unsigned value below {limit}, for bitfield<{width}>"),
found: format!("{raw}"),
});
}
#[allow(clippy::cast_sign_loss)]
let raw = raw as u64;
bits |= raw << position;
}
position += width;
}
match anchor {
1 => s.w.write_u8(u8::try_from(bits).unwrap_or(0)),
2 => s.w.write_u16(u16::try_from(bits).unwrap_or(0)),
4 => s.w.write_u32(u32::try_from(bits).unwrap_or(0)),
8 => s.w.write_u64(bits),
_ => {
return Err(ProbeError::Unsupported {
path: path.to_string(),
kind: "a bitset whose anchor is not one, two, four or eight bytes",
});
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn write_map<V: Source>(
s: &mut Sink,
ast: &Ast,
type_: Id<TypeNode>,
key_type: &Ref<TypeNode>,
value_type: &Ref<TypeNode>,
bound: Bound,
value: &V,
frame: Frame,
path: &str,
) -> Result<(), 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 entries = contract::bounded_items(ast, value, bound, "entries", path)?;
let mut deps = DepCollector::new();
let layout = plan(ast, type_, frame.key, &mut deps).map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?;
let dheader = layout.needs_dheader.then(|| s.w.write_dheader());
if dheader.is_none() || frame.map_length == MapLengthPolicy::SequenceEquivalent {
#[allow(clippy::cast_possible_truncation)]
s.w.write_u32(entries.len() as u32);
}
for index in map_entry_order(ast, type_, key_type, entries, frame, path)? {
let entry = &entries[index];
let entry_path = format!("{path}[{index}]");
let (entry_key, entry_value) = contract::map_entry(entry, &entry_path)?;
write_type(
s,
ast,
key_type,
entry_key,
frame.in_collection(),
&contract::join(&entry_path, "key"),
)?;
write_type(
s,
ast,
value_type,
entry_value,
frame.in_collection(),
&contract::join(&entry_path, "value"),
)?;
}
if let Some(dheader) = dheader {
s.w.try_finish_dheader(dheader)
.map_err(|error| refused(path, error))?;
}
Ok(())
}
fn map_entry_order<V: Source>(
ast: &Ast,
type_: Id<TypeNode>,
key_type: Id<TypeNode>,
entries: &[V],
frame: Frame,
path: &str,
) -> Result<Vec<usize>, ProbeError> {
if frame.key.map_entries.is_default() {
return Ok((0..entries.len()).collect());
}
let Some(ordering) = map_key_ordering(ast, type_, frame.key.map_entries) else {
return Err(ProbeError::Unsupported {
path: path.to_string(),
kind: "a map whose key type has no value order, under --map-entries ascending-key",
});
};
let mut keyed = Vec::with_capacity(entries.len());
for (index, entry) in entries.iter().enumerate() {
let entry_path = format!("{path}[{index}]");
let (entry_key, _) = contract::map_entry(entry, &entry_path)?;
let sort_key = map_sort_key(
ast,
key_type,
ordering,
entry_key,
&contract::join(&entry_path, "key"),
)?;
keyed.push((sort_key, index));
}
keyed.sort();
Ok(keyed.into_iter().map(|(_, index)| index).collect())
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
enum MapSortKey {
Integer(i128),
CodeUnits(Vec<u8>),
}
fn map_sort_key<V: Source>(
ast: &Ast,
key_type: Id<TypeNode>,
ordering: MapKeyOrdering,
value: &V,
path: &str,
) -> Result<MapSortKey, ProbeError> {
let resolved = contract::resolve_alias(ast, key_type);
let kind = ast.type_(resolved).map(|node| node.kind.clone()).ok();
match ordering {
MapKeyOrdering::EnumeratorValue => {
let (_, declared) = contract::enumerator(ast, resolved, value, path)?;
Ok(MapSortKey::Integer(declared))
}
MapKeyOrdering::CodeUnits => Ok(MapSortKey::CodeUnits(
contract::text(value, path)?.as_bytes().to_vec(),
)),
MapKeyOrdering::SignedInteger | MapKeyOrdering::UnsignedInteger => {
let raw = match kind {
Some(TypeKind::Primitive(PrimitiveKind::Boolean)) => {
i128::from(contract::boolean(value, path)?)
}
Some(TypeKind::Primitive(kind @ (PrimitiveKind::Char | PrimitiveKind::WChar))) => {
i128::from(u32::from(contract::character_for(kind, value, path)?))
}
Some(TypeKind::Primitive(kind)) => contract::integer_for(kind, value, path)?,
_ => contract::integer(value, path)?,
};
let signed = ordering == MapKeyOrdering::SignedInteger
&& matches!(kind, Some(TypeKind::Primitive(PrimitiveKind::Char)))
&& raw >= 0x80;
Ok(MapSortKey::Integer(if signed { raw - 0x100 } else { raw }))
}
}
}
fn write_fixed<V: Source>(
s: &mut Sink,
digits: u8,
scale: u8,
policy: FixedDigitCount,
value: &V,
path: &str,
) -> Result<(), ProbeError> {
if digits == 0 {
return Err(ProbeError::Unsupported {
path: path.to_string(),
kind: "a fixed with no digits, which can hold no value",
});
}
if digits > 31 {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: "a fixed of at most 31 digits".to_string(),
found: format!("fixed<{digits}, {scale}>"),
});
}
if scale > digits {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: "a scale no larger than the digit count".to_string(),
found: format!("fixed<{digits}, {scale}>"),
});
}
let text = contract::text(value, path).map_err(|_| ProbeError::Mismatch {
path: path.to_string(),
expected: "a decimal, as a string — `\"123.45\"`".to_string(),
found: contract::kind_of(value).to_string(),
})?;
s.seen.scalar(path);
s.w.write_fixed(text, digits, scale, fixed_digits(policy))
.map_err(|error| refused(path, error))
}
pub(crate) const fn fixed_digits(policy: FixedDigitCount) -> zerodds_wire::FixedDigits {
match policy {
FixedDigitCount::Declared => zerodds_wire::FixedDigits::Declared,
FixedDigitCount::ValuePrecision => zerodds_wire::FixedDigits::ValuePrecision,
FixedDigitCount::ValueStringDigits => zerodds_wire::FixedDigits::ValueStringDigits,
}
}
fn refused(path: &str, error: zerodds_wire::WireError) -> ProbeError {
let expected = match error {
zerodds_wire::WireError::OutOfRange { expected, .. } => expected,
other => format!("{other}"),
};
ProbeError::Mismatch {
path: path.to_string(),
expected,
found: "the value the runtime refused".to_string(),
}
}
#[allow(
clippy::cast_possible_truncation,
clippy::cast_precision_loss,
clippy::cast_sign_loss,
clippy::cast_possible_wrap
)]
fn write_primitive<V: Source>(
s: &mut Sink,
kind: PrimitiveKind,
value: &V,
position: WcharPosition,
path: &str,
) -> Result<(), ProbeError> {
match kind {
PrimitiveKind::Boolean => s.w.write_bool(contract::boolean(value, path)?),
PrimitiveKind::Octet | PrimitiveKind::UInt8 => {
s.w.write_u8(contract::integer_for(kind, value, path)? as u8);
}
PrimitiveKind::Int8 => {
s.w.write_i8(contract::integer_for(kind, value, path)? as i8);
}
PrimitiveKind::Char => {
let character = contract::character_for(kind, value, path)?;
s.w.write_char(character)
.map_err(|error| refused(path, error))?;
}
PrimitiveKind::WChar => {
let character = contract::character_for(kind, value, path)?;
s.w.write_wchar(character, position)
.map_err(|error| refused(path, error))?;
}
PrimitiveKind::Int16 => {
s.w.write_i16(contract::integer_for(kind, value, path)? as i16);
}
PrimitiveKind::UInt16 => {
s.w.write_u16(contract::integer_for(kind, value, path)? as u16);
}
PrimitiveKind::Int32 => {
s.w.write_i32(contract::integer_for(kind, value, path)? as i32);
}
PrimitiveKind::UInt32 => {
s.w.write_u32(contract::integer_for(kind, value, path)? as u32);
}
PrimitiveKind::Int64 => {
s.w.write_i64(contract::integer_for(kind, value, path)? as i64);
}
PrimitiveKind::UInt64 => {
s.w.write_u64(contract::integer_for(kind, value, path)? as u64);
}
PrimitiveKind::Float32 => s.w.write_f32(contract::float(value, path)? as f32),
PrimitiveKind::Float64 => s.w.write_f64(contract::float(value, path)?),
PrimitiveKind::Float128 => s.w.write_f128(contract::float128(value, path)?),
}
s.seen.scalar(path);
Ok(())
}
const fn length_code(lc: LengthCode) -> u8 {
match lc {
LengthCode::Lc0 => 0,
LengthCode::Lc1 => 1,
LengthCode::Lc2 => 2,
LengthCode::Lc3 => 3,
LengthCode::Lc4 => 4,
LengthCode::Lc5 => 5,
LengthCode::Lc6 => 6,
LengthCode::Lc7 => 7,
}
}