Skip to main content

truefix_core/codec/
encode.rs

1//! Encode a [`Message`] to wire bytes, computing BodyLength (tag 9) and CheckSum (tag 10).
2
3use crate::field_map::{FieldMap, Member};
4use crate::message::Message;
5use crate::tags::{BEGIN_STRING, BODY_LENGTH, CHECK_SUM, MSG_TYPE, SOH};
6
7/// Encode `msg` to SOH-delimited wire bytes.
8///
9/// Emits the canonical order `8, 9, 35, <rest of header>, <body>, <trailer except 10>, 10`.
10/// BodyLength is the byte count of everything after the `9=..<SOH>` up to and including the
11/// `<SOH>` before `10=`; CheckSum is the sum of all preceding bytes modulo 256.
12pub fn encode(msg: &Message) -> Vec<u8> {
13    encode_with_order(msg, None)
14}
15
16/// As [`encode`], but when `field_order` is `Some`, the message body's top-level fields are
17/// emitted in that tag order instead of insertion order (US9, feature 005, FR-027) — fields
18/// present in `field_order` come first (in that order), then any body field not listed in it
19/// (e.g. a UDF), in its original insertion-relative order, matching QFJ's own
20/// `FieldOrderComparator` "unspecified fields last" semantics. Repeating-group entries are
21/// unaffected (each entry's own field order is unconditionally preserved) — `field_order` only
22/// reorders the message body's direct top-level members.
23pub fn encode_with_order(msg: &Message, field_order: Option<&[u32]>) -> Vec<u8> {
24    let begin = msg
25        .header
26        .get(BEGIN_STRING)
27        .map(|f| f.value_bytes().to_vec())
28        .unwrap_or_default();
29    let msg_type = msg
30        .header
31        .get(MSG_TYPE)
32        .map(|f| f.value_bytes().to_vec())
33        .unwrap_or_default();
34
35    // Everything counted by BodyLength: MsgType, the rest of the header, the body, and the
36    // trailer (excluding CheckSum).
37    let mut middle = Vec::new();
38    render_raw(MSG_TYPE, &msg_type, &mut middle);
39    render_members(
40        &msg.header,
41        &[BEGIN_STRING, BODY_LENGTH, MSG_TYPE],
42        &mut middle,
43    );
44    match field_order {
45        Some(order) => render_members_ordered(&msg.body, order, &mut middle),
46        None => render_members(&msg.body, &[], &mut middle),
47    }
48    render_members(&msg.trailer, &[CHECK_SUM], &mut middle);
49
50    let mut out = Vec::new();
51    render_raw(BEGIN_STRING, &begin, &mut out);
52    render_raw(BODY_LENGTH, middle.len().to_string().as_bytes(), &mut out);
53    out.extend_from_slice(&middle);
54
55    // BUG-24/FR-032 (feature 007): a `u64` accumulator, not `u32` — `Iterator::sum::<u32>()` panics
56    // on overflow in debug builds (violating the crate's "no path panics" invariant) once the
57    // summed bytes exceed ~16.8M in a way whose sum surpasses `u32::MAX` (reachable: `encode()` has
58    // no `MAX_BODY_LEN`-style cap of its own, since that limit is enforced only on the *decode*
59    // path in `frame_length`). `u64` can't realistically overflow this sum (would need billions of
60    // bytes), and `& 0xFF` gives the identical mod-256 result either width, since 2^32 and 2^64 are
61    // both multiples of 256.
62    let checksum: u32 = (out.iter().map(|&b| u64::from(b)).sum::<u64>() & 0xFF) as u32;
63    render_raw(CHECK_SUM, format!("{checksum:03}").as_bytes(), &mut out);
64    out
65}
66
67/// Render `map`'s top-level members ordered by `order` (fields listed in `order` first, in that
68/// order; then any remaining top-level member — a field not in `order`, or a group — in its
69/// original relative position among the remaining members). Each group entry's own internal
70/// field order is untouched.
71fn render_members_ordered(map: &FieldMap, order: &[u32], out: &mut Vec<u8>) {
72    let members: &[Member] = map.raw_members();
73    let tag_of = |m: &Member| match m {
74        Member::Field(f) => Some(f.tag()),
75        Member::Group { count_tag, .. } => Some(*count_tag),
76    };
77    // NEW-80 (feature 009): `order` (a dictionary's `field_order`) may itself list the same tag
78    // more than once -- track already-emitted tags so a duplicate entry in `order` doesn't render
79    // its member twice.
80    let mut emitted: std::collections::HashSet<u32> = std::collections::HashSet::new();
81    for &wanted in order {
82        if !emitted.insert(wanted) {
83            continue;
84        }
85        for member in members {
86            if tag_of(member) == Some(wanted) {
87                render_one_member(member, out);
88            }
89        }
90    }
91    for member in members {
92        let tag = tag_of(member).unwrap_or(0);
93        if !emitted.contains(&tag) {
94            render_one_member(member, out);
95        }
96    }
97}
98
99/// NEW-22 (feature 009): the count to emit for a group's `NoXxx` field — its wire-declared count
100/// if one was recorded (preserving fidelity to a decoded message even when that declaration
101/// didn't match the actual entry count), otherwise `entries.len()` (today's behavior, unchanged
102/// for any group not decoded from the wire).
103fn group_count_to_emit(entries: &[FieldMap], declared_count: Option<i64>) -> String {
104    declared_count.map_or_else(|| entries.len().to_string(), |n| n.to_string())
105}
106
107fn render_one_member(member: &Member, out: &mut Vec<u8>) {
108    match member {
109        Member::Field(f) => render_raw(f.tag(), f.value_bytes(), out),
110        Member::Group {
111            count_tag,
112            entries,
113            declared_count,
114        } => {
115            render_raw(
116                *count_tag,
117                group_count_to_emit(entries, *declared_count).as_bytes(),
118                out,
119            );
120            for entry in entries {
121                render_members(entry, &[], out);
122            }
123        }
124    }
125}
126
127fn render_members(map: &FieldMap, skip: &[u32], out: &mut Vec<u8>) {
128    for member in map.raw_members() {
129        match member {
130            Member::Field(f) => {
131                if !skip.contains(&f.tag()) {
132                    render_raw(f.tag(), f.value_bytes(), out);
133                }
134            }
135            Member::Group {
136                count_tag,
137                entries,
138                declared_count,
139            } => {
140                render_raw(
141                    *count_tag,
142                    group_count_to_emit(entries, *declared_count).as_bytes(),
143                    out,
144                );
145                for entry in entries {
146                    render_members(entry, &[], out);
147                }
148            }
149        }
150    }
151}
152
153fn render_raw(tag: u32, value: &[u8], out: &mut Vec<u8>) {
154    write_tag(tag, out);
155    out.push(b'=');
156    out.extend_from_slice(value);
157    out.push(SOH);
158}
159
160/// T177/T178 (feature 009, NEW-35): write `tag`'s decimal digits directly into `out`, avoiding
161/// the per-field heap allocation `tag.to_string()` made on every rendered field.
162fn write_tag(tag: u32, out: &mut Vec<u8>) {
163    let mut buf = [0u8; 10]; // u32::MAX ("4294967295") is 10 digits
164    let mut i = buf.len();
165    let mut n = tag;
166    loop {
167        i -= 1;
168        if let Some(slot) = buf.get_mut(i) {
169            *slot = b'0' + (n % 10) as u8;
170        }
171        n /= 10;
172        if n == 0 || i == 0 {
173            break;
174        }
175    }
176    if let Some(digits) = buf.get(i..) {
177        out.extend_from_slice(digits);
178    }
179}