use crate::field_map::{FieldMap, Member};
use crate::message::Message;
use crate::tags::{BEGIN_STRING, BODY_LENGTH, CHECK_SUM, MSG_TYPE, SOH};
pub fn encode(msg: &Message) -> Vec<u8> {
encode_with_order(msg, None)
}
pub fn encode_with_order(msg: &Message, field_order: Option<&[u32]>) -> Vec<u8> {
let begin = msg
.header
.get(BEGIN_STRING)
.map(|f| f.value_bytes().to_vec())
.unwrap_or_default();
let msg_type = msg
.header
.get(MSG_TYPE)
.map(|f| f.value_bytes().to_vec())
.unwrap_or_default();
let mut middle = Vec::new();
render_raw(MSG_TYPE, &msg_type, &mut middle);
render_members(
&msg.header,
&[BEGIN_STRING, BODY_LENGTH, MSG_TYPE],
&mut middle,
);
match field_order {
Some(order) => render_members_ordered(&msg.body, order, &mut middle),
None => render_members(&msg.body, &[], &mut middle),
}
render_members(&msg.trailer, &[CHECK_SUM], &mut middle);
let mut out = Vec::new();
render_raw(BEGIN_STRING, &begin, &mut out);
render_raw(BODY_LENGTH, middle.len().to_string().as_bytes(), &mut out);
out.extend_from_slice(&middle);
let checksum: u32 = (out.iter().map(|&b| u64::from(b)).sum::<u64>() & 0xFF) as u32;
render_raw(CHECK_SUM, format!("{checksum:03}").as_bytes(), &mut out);
out
}
fn render_members_ordered(map: &FieldMap, order: &[u32], out: &mut Vec<u8>) {
let members: &[Member] = map.raw_members();
let tag_of = |m: &Member| match m {
Member::Field(f) => Some(f.tag()),
Member::Group { count_tag, .. } => Some(*count_tag),
};
let mut emitted: std::collections::HashSet<u32> = std::collections::HashSet::new();
for &wanted in order {
if !emitted.insert(wanted) {
continue;
}
for member in members {
if tag_of(member) == Some(wanted) {
render_one_member(member, out);
}
}
}
for member in members {
let tag = tag_of(member).unwrap_or(0);
if !emitted.contains(&tag) {
render_one_member(member, out);
}
}
}
fn group_count_to_emit(entries: &[FieldMap], declared_count: Option<i64>) -> String {
declared_count.map_or_else(|| entries.len().to_string(), |n| n.to_string())
}
fn render_one_member(member: &Member, out: &mut Vec<u8>) {
match member {
Member::Field(f) => render_raw(f.tag(), f.value_bytes(), out),
Member::Group {
count_tag,
entries,
declared_count,
} => {
render_raw(
*count_tag,
group_count_to_emit(entries, *declared_count).as_bytes(),
out,
);
for entry in entries {
render_members(entry, &[], out);
}
}
}
}
fn render_members(map: &FieldMap, skip: &[u32], out: &mut Vec<u8>) {
for member in map.raw_members() {
match member {
Member::Field(f) => {
if !skip.contains(&f.tag()) {
render_raw(f.tag(), f.value_bytes(), out);
}
}
Member::Group {
count_tag,
entries,
declared_count,
} => {
render_raw(
*count_tag,
group_count_to_emit(entries, *declared_count).as_bytes(),
out,
);
for entry in entries {
render_members(entry, &[], out);
}
}
}
}
}
fn render_raw(tag: u32, value: &[u8], out: &mut Vec<u8>) {
write_tag(tag, out);
out.push(b'=');
out.extend_from_slice(value);
out.push(SOH);
}
fn write_tag(tag: u32, out: &mut Vec<u8>) {
let mut buf = [0u8; 10]; let mut i = buf.len();
let mut n = tag;
loop {
i -= 1;
if let Some(slot) = buf.get_mut(i) {
*slot = b'0' + (n % 10) as u8;
}
n /= 10;
if n == 0 || i == 0 {
break;
}
}
if let Some(digits) = buf.get(i..) {
out.extend_from_slice(digits);
}
}