fix44_forge_helpers/
buffer.rs

1//! Buffer management for FIX message creation and manipulation.
2//!
3//! This module provides high-performance buffer initialization and management
4//! specifically designed for FIX protocol message generation. The core concept
5//! is pre-initializing buffers with the fixed header structure that appears
6//! in every FIX message.
7
8use core::ptr;
9
10/// Default buffer size for FIX message writing operations.
11/// 1024 bytes should accommodate most FIX messages in practice.
12pub const FORGE_BUFFER_SIZE: usize = 1024;
13
14/// The complete pre-initialized header that is baked into every forge buffer.
15/// Layout: "8=FIX.4.4\x019=0000\x0135="
16/// - BeginString: "8=FIX.4.4\x01" (positions 0-9)
17/// - BodyLength: "9=0000\x01" (positions 10-16, will be updated later)
18/// - MsgType tag: "35=" (positions 17-19)
19pub const FORGE_HEADER: &[u8] = b"8=FIX.4.4\x019=0000\x0135=";
20
21/// Length of the complete pre-initialized header.
22pub const FORGE_HEADER_LEN: usize = 20;
23
24/// Position where BeginString starts (always 0).
25pub const BEGIN_STRING_POS: usize = 0;
26
27/// Position where BodyLength value starts (the "0000" part).
28pub const BODY_LENGTH_VALUE_POS: usize = 12;
29
30/// Position where MsgType tag starts ("35=").
31pub const MSG_TYPE_TAG_POS: usize = 17;
32
33/// Starting position for writing MsgType value after the pre-initialized header.
34/// This is where MsgType value writing should begin.
35pub const FORGE_WRITE_START: usize = 20;
36
37/// Create a pre-initialized buffer for FIX message writing.
38///
39/// This function returns a buffer that is already initialized with the
40/// fixed FIX header structure: "8=FIX.4.4\x019=0000\x0135="
41///
42/// # Buffer Layout
43/// - Bytes 0-9: BeginString "8=FIX.4.4\x01"
44/// - Bytes 10-16: BodyLength placeholder "9=0000\x01"
45/// - Bytes 17-19: MsgType tag "35="
46/// - Bytes 20+: Available for MsgType value and remaining message content
47///
48/// # Example
49/// ```
50/// # use fix44_forge_helpers::*;
51/// let mut buffer = forge_out_buffer();
52///
53/// // Fixed header is already there, start writing MsgType value
54/// let mut pos = FORGE_WRITE_START;
55/// buffer[pos] = b'D'; pos += 1; // Just the MsgType value
56/// buffer[pos] = 0x01; pos += 1; // SOH after MsgType
57/// // ... continue writing other fields
58/// // Finally, update BodyLength at BODY_LENGTH_VALUE_POS
59/// ```
60///
61/// # Performance
62/// This function performs a single 20-byte copy to initialize the entire
63/// fixed header structure. BeginString, BodyLength structure, and MsgType tag
64/// are never written again during message serialization.
65#[inline]
66pub fn forge_out_buffer() -> [u8; FORGE_BUFFER_SIZE] {
67    let mut buffer = [0u8; FORGE_BUFFER_SIZE];
68
69    // Pre-initialize with complete fixed header: "8=FIX.4.4\x019=0000\x0135="
70    // This is the only time these bytes are ever written
71    unsafe {
72        ptr::copy_nonoverlapping(
73            FORGE_HEADER.as_ptr(),
74            buffer.as_mut_ptr(),
75            FORGE_HEADER_LEN,
76        );
77    }
78
79    buffer
80}
81
82/// Update the BodyLength field in a forge buffer with the actual message length.
83///
84/// This function updates the "0000" placeholder in the BodyLength field (9=0000)
85/// with the actual body length value. The body length is everything after
86/// BeginString and BodyLength field itself, so it's `message_length - 17`.
87///
88/// # Safety
89/// Caller must ensure the buffer was created with `forge_out_buffer()` and
90/// the body_length fits in 4 digits (0-9999).
91///
92/// # Example
93/// ```
94/// # use fix44_forge_helpers::*;
95/// let mut buffer = forge_out_buffer();
96/// let mut pos = FORGE_WRITE_START;
97/// // ... write message content and any trailer fields except CheckSum
98/// // pos is now at the position where CheckSum (10=XXX) will be written
99/// update_body_length(&mut buffer, pos);
100/// ```
101#[inline(always)]
102pub fn update_body_length(
103    buffer: &mut [u8],
104    message_length: usize,
105) {
106    let body_length: u16 = (message_length - 17) as u16;
107    // Write 4-digit zero-padded body length at position 12-15
108    let thousands = (body_length / 1000) % 10;
109    let hundreds = (body_length / 100) % 10;
110    let tens = (body_length / 10) % 10;
111    let units = body_length % 10;
112
113    unsafe {
114        let ptr = buffer
115            .as_mut_ptr()
116            .add(BODY_LENGTH_VALUE_POS);
117        *ptr = b'0' + thousands as u8;
118        *ptr.add(1) = b'0' + hundreds as u8;
119        *ptr.add(2) = b'0' + tens as u8;
120        *ptr.add(3) = b'0' + units as u8;
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127    use crate::writing::write_tag_and_u32;
128
129    #[test]
130    fn test_forge_out_buffer() {
131        let buffer = forge_out_buffer();
132
133        // Verify buffer size
134        assert_eq!(
135            buffer.len(),
136            FORGE_BUFFER_SIZE
137        );
138        assert_eq!(buffer.len(), 1024);
139
140        // Verify complete header is pre-initialized
141        assert_eq!(
142            &buffer[..FORGE_HEADER_LEN],
143            FORGE_HEADER
144        );
145
146        // Verify rest is zero-initialized
147        assert!(
148            buffer[FORGE_WRITE_START..]
149                .iter()
150                .all(|&b| b == 0)
151        );
152    }
153
154    #[test]
155    fn test_forge_out_buffer_ready_for_writing() {
156        let mut buffer = forge_out_buffer();
157
158        // Start writing MsgType value after pre-initialized header
159        let mut pos = FORGE_WRITE_START;
160
161        // Write MsgType value (tag "35=" is already there)
162        buffer[pos] = b'D';
163        pos += 1;
164        buffer[pos] = 0x01;
165        pos += 1; // SOH
166
167        // Write other fields normally
168        pos += write_tag_and_u32(&mut buffer, pos, b"34=", 123);
169
170        // Update body length (everything after BeginString and BodyLength field)
171        update_body_length(&mut buffer, pos);
172
173        // Verify the complete message
174        let expected = b"8=FIX.4.4\x019=0012\x0135=D\x0134=123\x01";
175        assert_eq!(&buffer[..pos], expected);
176
177        // Verify header was never overwritten
178        assert_eq!(&buffer[..3], b"8=F"); // BeginString start
179        assert_eq!(&buffer[10..12], b"9="); // BodyLength tag
180        assert_eq!(&buffer[17..20], b"35="); // MsgType tag
181    }
182
183    #[test]
184    fn test_forge_out_buffer_independence() {
185        let buffer1 = forge_out_buffer();
186        let mut buffer2 = forge_out_buffer();
187
188        // Modify buffer2
189        buffer2[FORGE_WRITE_START] = b'X';
190
191        // buffer1 should be unchanged
192        assert_eq!(
193            &buffer1[..FORGE_HEADER_LEN],
194            FORGE_HEADER
195        );
196        assert_eq!(buffer1[FORGE_WRITE_START], 0);
197
198        // buffer2 should have the modification
199        assert_eq!(
200            &buffer2[..FORGE_HEADER_LEN],
201            FORGE_HEADER
202        );
203        assert_eq!(
204            buffer2[FORGE_WRITE_START],
205            b'X'
206        );
207    }
208
209    #[test]
210    fn test_update_body_length() {
211        let mut buffer = forge_out_buffer();
212
213        // Test various body lengths
214        update_body_length(&mut buffer, 17); // body_length = 17 - 17 = 0
215        assert_eq!(
216            &buffer[BODY_LENGTH_VALUE_POS..BODY_LENGTH_VALUE_POS + 4],
217            b"0000"
218        );
219
220        update_body_length(&mut buffer, 59); // body_length = 59 - 17 = 42
221        assert_eq!(
222            &buffer[BODY_LENGTH_VALUE_POS..BODY_LENGTH_VALUE_POS + 4],
223            b"0042"
224        );
225
226        update_body_length(&mut buffer, 1251); // body_length = 1251 - 17 = 1234
227        assert_eq!(
228            &buffer[BODY_LENGTH_VALUE_POS..BODY_LENGTH_VALUE_POS + 4],
229            b"1234"
230        );
231
232        update_body_length(&mut buffer, 10016); // body_length = 10016 - 17 = 9999
233        assert_eq!(
234            &buffer[BODY_LENGTH_VALUE_POS..BODY_LENGTH_VALUE_POS + 4],
235            b"9999"
236        );
237
238        // Verify the rest of the header is unchanged
239        assert_eq!(
240            &buffer[..10],
241            b"8=FIX.4.4\x01"
242        );
243        assert_eq!(&buffer[10..12], b"9=");
244        assert_eq!(&buffer[16..20], b"\x0135=");
245    }
246
247    #[test]
248    fn test_forge_header_constants() {
249        // Verify our constants are correct
250        assert_eq!(
251            FORGE_HEADER,
252            b"8=FIX.4.4\x019=0000\x0135="
253        );
254        assert_eq!(FORGE_HEADER_LEN, 20);
255        assert_eq!(BODY_LENGTH_VALUE_POS, 12);
256        assert_eq!(MSG_TYPE_TAG_POS, 17);
257        assert_eq!(FORGE_WRITE_START, 20);
258
259        // Verify positions align correctly
260        assert_eq!(
261            &FORGE_HEADER[0..10],
262            b"8=FIX.4.4\x01"
263        );
264        assert_eq!(
265            &FORGE_HEADER[10..17],
266            b"9=0000\x01"
267        );
268        assert_eq!(&FORGE_HEADER[17..20], b"35=");
269    }
270}