use crate::error::DecodeError;
use crate::field::Field;
use crate::field_map::FieldMap;
use crate::framing::MAX_BODY_LEN;
use crate::group::{Group, GroupSpec};
use crate::message::Message;
use crate::tags::{
BEGIN_STRING, BODY_LENGTH, CHECK_SUM, MSG_TYPE, SOH, data_field_for_length, is_header,
is_trailer,
};
type Token = (u32, Vec<u8>, usize);
fn section_of(tag: u32) -> u8 {
if is_trailer(tag) {
2
} else if is_header(tag) {
0
} else {
1
}
}
pub fn decode(input: &[u8]) -> Result<Message, DecodeError> {
let fields = tokenize_validated(input)?;
let mut msg = Message::new();
let mut max_section_seen = 0u8;
for (i, (tag, value, _)) in fields.into_iter().enumerate() {
let section = section_of(tag);
if section < max_section_seen {
msg.fields_out_of_order = true;
} else {
max_section_seen = section;
}
if i == 2 && tag != MSG_TYPE {
msg.fields_out_of_order = true;
}
let field = Field::new(tag, value);
if is_trailer(tag) {
msg.trailer.add_field(field);
} else if is_header(tag) {
msg.header.add_field(field);
} else {
msg.body.add_field(field);
}
}
Ok(msg)
}
pub fn decode_with_groups(input: &[u8], spec: &dyn GroupSpec) -> Result<Message, DecodeError> {
let fields = tokenize_validated(input)?;
let mut msg = Message::new();
let mut header: Vec<Token> = Vec::new();
let mut body: Vec<Token> = Vec::new();
let mut trailer: Vec<Token> = Vec::new();
let mut max_section_seen = 0u8;
for (i, tok) in fields.iter().enumerate() {
let tag = tok.0;
let section = section_of(tag);
if section < max_section_seen {
msg.fields_out_of_order = true;
} else {
max_section_seen = section;
}
if i == 2 && tag != MSG_TYPE {
msg.fields_out_of_order = true;
}
}
for tok in fields {
let tag = tok.0;
if is_trailer(tag) {
trailer.push(tok);
} else if is_header(tag) {
header.push(tok);
} else {
body.push(tok);
}
}
decode_section_with_groups(&header, spec, &mut msg.header)?;
decode_section_with_groups(&body, spec, &mut msg.body)?;
decode_section_with_groups(&trailer, spec, &mut msg.trailer)?;
Ok(msg)
}
pub fn restructure_groups(map: &mut FieldMap, spec: &dyn GroupSpec) -> Result<(), DecodeError> {
let mut tokens: Vec<Token> = Vec::new();
flatten_to_tokens(map, &mut tokens);
let mut rebuilt = FieldMap::new();
decode_section_with_groups(&tokens, spec, &mut rebuilt)?;
*map = rebuilt;
Ok(())
}
fn flatten_to_tokens(map: &FieldMap, out: &mut Vec<Token>) {
for member in map.members() {
match member {
crate::field_map::MemberRef::Field(f) => {
out.push((f.tag(), f.value_bytes().to_vec(), 0));
}
crate::field_map::MemberRef::Group {
count_tag,
entries,
declared_count,
} => {
let count =
declared_count.map_or_else(|| entries.len().to_string(), |n| n.to_string());
out.push((count_tag, count.into_bytes(), 0));
for entry in entries {
flatten_to_tokens(entry, out);
}
}
}
}
}
fn decode_section_with_groups(
tokens: &[Token],
spec: &dyn GroupSpec,
out: &mut FieldMap,
) -> Result<(), DecodeError> {
let mut pos = 0usize;
while let Some(tok) = tokens.get(pos) {
let tag = tok.0;
if let Some((delimiter, members)) = spec.group_of(tag) {
let group = build_group(tokens, &mut pos, spec, tag, delimiter, members, 0)?;
out.add_group(group);
} else {
out.add_field(Field::new(tag, tok.1.clone()));
pos += 1;
}
}
Ok(())
}
const MAX_GROUP_NESTING_DEPTH: usize = 32;
fn build_group(
tokens: &[Token],
pos: &mut usize,
spec: &dyn GroupSpec,
count_tag: u32,
delimiter: u32,
members: &[u32],
depth: usize,
) -> Result<Group, DecodeError> {
if depth >= MAX_GROUP_NESTING_DEPTH {
return Err(DecodeError::GroupNestingTooDeep {
max: MAX_GROUP_NESTING_DEPTH,
});
}
let declared: Option<i64> = tokens
.get(*pos)
.and_then(|tok| core::str::from_utf8(&tok.1).ok())
.and_then(|s| s.parse().ok());
*pos += 1; let mut group = Group::new(count_tag);
while let Some(tok) = tokens.get(*pos) {
if tok.0 != delimiter {
break; }
let mut entry = FieldMap::new();
entry.add_field(Field::new(delimiter, tok.1.clone()));
*pos += 1;
while let Some(t) = tokens.get(*pos) {
let tag = t.0;
if tag == delimiter || !members.contains(&tag) {
break;
}
if let Some((d2, m2)) = spec.group_of(tag) {
let sub = build_group(tokens, pos, spec, tag, d2, m2, depth + 1)?;
entry.add_group(sub);
} else {
entry.add_field(Field::new(tag, t.1.clone()));
*pos += 1;
}
}
group.add_entry(entry);
}
if let Some(n) = declared {
group.set_declared_count(n);
}
Ok(group)
}
fn tokenize_validated(input: &[u8]) -> Result<Vec<Token>, DecodeError> {
if input.is_empty() {
return Err(DecodeError::Empty);
}
let fields = tokenize(input)?;
let first = fields.first().ok_or(DecodeError::Empty)?;
if first.0 != BEGIN_STRING {
return Err(DecodeError::MissingBeginString);
}
let second = fields.get(1).ok_or(DecodeError::InvalidBodyLength)?;
if second.0 != BODY_LENGTH {
return Err(DecodeError::InvalidBodyLength);
}
let declared_bl = parse_usize(&second.1).ok_or(DecodeError::InvalidBodyLength)?;
if declared_bl > MAX_BODY_LEN {
return Err(DecodeError::BodyLengthTooLarge {
declared: declared_bl,
max: MAX_BODY_LEN,
});
}
if !fields.iter().any(|f| f.0 == MSG_TYPE) {
return Err(DecodeError::MissingMsgType);
}
let last = fields.last().ok_or(DecodeError::MissingChecksum)?;
if last.0 != CHECK_SUM {
return Err(DecodeError::MissingChecksum);
}
if last.1.len() != 3 || !last.1.iter().all(u8::is_ascii_digit) {
return Err(DecodeError::MissingChecksum);
}
let declared_cs = parse_u32(&last.1).ok_or(DecodeError::MissingChecksum)?;
let cs_offset = last.2;
let body_start = fields.get(2).map_or(cs_offset, |f| f.2);
let actual_bl = cs_offset
.checked_sub(body_start)
.ok_or(DecodeError::InvalidBodyLength)?;
if actual_bl != declared_bl {
return Err(DecodeError::BodyLengthMismatch {
declared: declared_bl,
actual: actual_bl,
});
}
let pre = input.get(..cs_offset).ok_or(DecodeError::MissingChecksum)?;
let computed: u32 = (pre.iter().map(|&b| u64::from(b)).sum::<u64>() & 0xFF) as u32;
if computed != declared_cs {
return Err(DecodeError::ChecksumMismatch {
declared: declared_cs,
computed,
});
}
Ok(fields)
}
fn tokenize(input: &[u8]) -> Result<Vec<Token>, DecodeError> {
let mut tokens = Vec::new();
let mut pos = 0usize;
let mut pending_data: Option<(u32, usize)> = None;
while pos < input.len() {
let start = pos;
let rest = input
.get(pos..)
.ok_or(DecodeError::Truncated { offset: start })?;
let eq_rel = memchr(rest, b'=').ok_or(DecodeError::GarbledField {
offset: start,
reason: "missing '=' in field",
})?;
let tag_bytes = rest.get(..eq_rel).unwrap_or(&[]);
let tag = parse_u32(tag_bytes).ok_or(DecodeError::InvalidTag { offset: start })?;
if tag == 0 {
return Err(DecodeError::InvalidTag { offset: start });
}
let val_start = pos + eq_rel + 1;
let data_len = match pending_data.take() {
Some((expected_tag, len)) if expected_tag == tag => Some(len),
Some(_) => {
return Err(DecodeError::GarbledField {
offset: start,
reason: "data field does not match its declared length field's partner tag",
});
}
None => None,
};
let (value, next) = if let Some(len) = data_len {
let val_end = val_start
.checked_add(len)
.ok_or(DecodeError::GarbledField {
offset: start,
reason: "data length overflow",
})?;
let v = input
.get(val_start..val_end)
.ok_or(DecodeError::Truncated { offset: val_start })?;
match input.get(val_end) {
Some(&b) if b == SOH => (v.to_vec(), val_end + 1),
_ => {
return Err(DecodeError::GarbledField {
offset: val_end,
reason: "data field not terminated by SOH",
});
}
}
} else {
let after = input
.get(val_start..)
.ok_or(DecodeError::Truncated { offset: val_start })?;
let soh_rel = memchr(after, SOH).ok_or(DecodeError::GarbledField {
offset: val_start,
reason: "field not terminated by SOH",
})?;
let v = after.get(..soh_rel).unwrap_or(&[]);
(v.to_vec(), val_start + soh_rel + 1)
};
if let Some(expected_tag) = data_field_for_length(tag) {
let len = parse_usize(&value).ok_or(DecodeError::GarbledField {
offset: start,
reason: "data-length field value is not a valid non-negative integer",
})?;
pending_data = Some((expected_tag, len));
}
tokens.push((tag, value, start));
pos = next;
}
Ok(tokens)
}
fn memchr(haystack: &[u8], needle: u8) -> Option<usize> {
memchr::memchr(needle, haystack)
}
fn parse_u32(bytes: &[u8]) -> Option<u32> {
core::str::from_utf8(bytes).ok()?.parse().ok()
}
fn parse_usize(bytes: &[u8]) -> Option<usize> {
core::str::from_utf8(bytes).ok()?.parse().ok()
}