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/// Length of the complete pre-initialized header for FIX 4.x versions.
15/// Layout: "8=FIX.4.4\x019=0000\x0135=" (20 bytes)
16/// Layout: "8=FIX.4.2\x019=0000\x0135=" (20 bytes)
17/// All FIX 4.x versions have the same header length.
18pub const FORGE_HEADER_LEN: usize = 20;
19
20/// Position where BodyLength value starts (the "0000" part).
21/// For all FIX 4.x versions: "8=FIX.4.x\x01" (10 bytes) + "9=" (2 bytes) = position 12
22pub const BODY_LENGTH_VALUE_POS: usize = 12;
23
24/// Starting position for writing MsgType value after the pre-initialized header.
25/// This is where MsgType value writing should begin for all FIX 4.x versions.
26pub const FORGE_WRITE_START: usize = 20;
27
28/// Create a pre-initialized buffer for FIX message writing with specified version.
29///
30/// This function returns a buffer that is already initialized with the
31/// version-specific FIX header structure.
32///
33/// # Arguments
34/// * `fix_version` - The FIX version string (e.g., "FIX.4.4", "FIX.4.2", "FIXT.1.1")
35///
36/// # Buffer Layout Examples
37/// For all FIX 4.x versions:
38/// - Bytes 0-9: BeginString "8=FIX.4.x\x01"
39/// - Bytes 10-16: BodyLength placeholder "9=0000\x01"
40/// - Bytes 17-19: MsgType tag "35="
41/// - Bytes 20+: Available for MsgType value and remaining message content
42///
43/// # Example
44/// ```
45/// # use fix44_forge_helpers::*;
46/// let mut buffer = forge_out_buffer("FIX.4.4");
47///
48/// // Fixed header is already there, start writing MsgType value
49/// let mut pos = FORGE_WRITE_START;
50/// buffer[pos] = b'D'; pos += 1; // Just the MsgType value
51/// buffer[pos] = 0x01; pos += 1; // SOH after MsgType
52/// // ... continue writing other fields
53/// // Finally, update BodyLength
54/// ```
55///
56/// # Performance
57/// This function performs a single copy to initialize the entire
58/// fixed header structure. BeginString, BodyLength structure, and MsgType tag
59/// are never written again during message serialization.
60#[inline]
61pub fn forge_out_buffer(fix_version: &str) -> [u8; FORGE_BUFFER_SIZE] {
62    let mut buffer = [0u8; FORGE_BUFFER_SIZE];
63
64    // Build the header dynamically: "8={version}\x019=0000\x0135="
65    let mut pos = 0;
66
67    // Write "8="
68    buffer[pos] = b'8';
69    buffer[pos + 1] = b'=';
70    pos += 2;
71
72    // Write version string
73    let version_bytes = fix_version.as_bytes();
74    unsafe {
75        ptr::copy_nonoverlapping(
76            version_bytes.as_ptr(),
77            buffer.as_mut_ptr().add(pos),
78            version_bytes.len(),
79        );
80    }
81    pos += version_bytes.len();
82
83    // Write SOH + "9=0000" + SOH + "35="
84    let suffix = b"\x019=0000\x0135=";
85    unsafe {
86        ptr::copy_nonoverlapping(
87            suffix.as_ptr(),
88            buffer.as_mut_ptr().add(pos),
89            suffix.len(),
90        );
91    }
92
93    buffer
94}
95
96/// Update the BodyLength field in a forge buffer with the actual message length.
97///
98/// This function updates the "0000" placeholder in the BodyLength field (9=0000)
99/// with the actual body length value. The body length is everything after
100/// BeginString and BodyLength field itself.
101///
102/// # Arguments
103/// * `buffer` - The forge buffer created with `forge_out_buffer()`
104/// * `fix_version` - The FIX version used to create the buffer
105/// * `message_length` - Position where CheckSum will be written
106///
107/// # Safety
108/// Caller must ensure the buffer was created with `forge_out_buffer()` and
109/// the body_length fits in 4 digits (0-9999).
110///
111/// # Example
112/// ```
113/// # use fix44_forge_helpers::*;
114/// let mut buffer = forge_out_buffer("FIX.4.4");
115/// let mut pos = forge_write_start("FIX.4.4");
116/// // ... write message content and any trailer fields except CheckSum
117/// // pos is now at the position where CheckSum (10=XXX) will be written
118/// update_body_length(&mut buffer, pos);
119/// ```
120#[inline(always)]
121pub fn update_body_length(
122    buffer: &mut [u8],
123    message_length: usize,
124) {
125    // All FIX 4.x versions have same structure: "8=FIX.4.x\x01" (10 bytes) + "9=0000\x01" (7 bytes) = 17 bytes total
126    let body_length: u16 = (message_length - 17) as u16;
127
128    // Write 4-digit zero-padded body length
129    let thousands = (body_length / 1000) % 10;
130    let hundreds = (body_length / 100) % 10;
131    let tens = (body_length / 10) % 10;
132    let units = body_length % 10;
133
134    unsafe {
135        let ptr = buffer
136            .as_mut_ptr()
137            .add(BODY_LENGTH_VALUE_POS);
138        *ptr = b'0' + thousands as u8;
139        *ptr.add(1) = b'0' + hundreds as u8;
140        *ptr.add(2) = b'0' + tens as u8;
141        *ptr.add(3) = b'0' + units as u8;
142    }
143}
144
145/// Get the starting position for writing MsgType value for FIX 4.x versions.
146/// All FIX 4.x versions have the same header length, so this returns the constant.
147///
148/// # Example
149/// ```
150/// # use fix44_forge_helpers::*;
151/// let start_pos = forge_write_start("FIX.4.4");
152/// assert_eq!(start_pos, 20);
153///
154/// let start_pos = forge_write_start("FIX.4.2");
155/// assert_eq!(start_pos, 20);
156/// ```
157#[inline]
158pub fn forge_write_start(_fix_version: &str) -> usize {
159    FORGE_WRITE_START
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165    use crate::writing::write_tag_and_u32;
166
167    #[test]
168    fn test_forge_out_buffer_fix44() {
169        let buffer = forge_out_buffer("FIX.4.4");
170
171        // Verify buffer size
172        assert_eq!(
173            buffer.len(),
174            FORGE_BUFFER_SIZE
175        );
176        assert_eq!(buffer.len(), 1024);
177
178        // Verify complete header is pre-initialized for FIX.4.4
179        let expected_header = b"8=FIX.4.4\x019=0000\x0135=";
180        assert_eq!(
181            &buffer[..expected_header.len()],
182            expected_header
183        );
184
185        // Verify rest is zero-initialized
186        assert!(
187            buffer[FORGE_WRITE_START..]
188                .iter()
189                .all(|&b| b == 0)
190        );
191    }
192
193    #[test]
194    fn test_forge_out_buffer_fix42() {
195        let buffer = forge_out_buffer("FIX.4.2");
196
197        // Verify complete header is pre-initialized for FIX.4.2
198        let expected_header = b"8=FIX.4.2\x019=0000\x0135=";
199        assert_eq!(
200            &buffer[..expected_header.len()],
201            expected_header
202        );
203
204        // Verify rest is zero-initialized
205        assert!(
206            buffer[FORGE_WRITE_START..]
207                .iter()
208                .all(|&b| b == 0)
209        );
210    }
211
212    #[test]
213    fn test_forge_out_buffer_ready_for_writing() {
214        let mut buffer = forge_out_buffer("FIX.4.4");
215
216        // Start writing MsgType value after pre-initialized header
217        let mut pos = FORGE_WRITE_START;
218
219        // Write MsgType value (tag "35=" is already there)
220        buffer[pos] = b'D';
221        pos += 1;
222        buffer[pos] = 0x01;
223        pos += 1; // SOH
224
225        // Write other fields normally
226        pos += write_tag_and_u32(&mut buffer, pos, b"34=", 123);
227
228        // Update body length (everything after BeginString and BodyLength field)
229        update_body_length(&mut buffer, pos);
230
231        // Verify the complete message
232        let expected = b"8=FIX.4.4\x019=0012\x0135=D\x0134=123\x01";
233        assert_eq!(&buffer[..pos], expected);
234
235        // Verify header was never overwritten
236        assert_eq!(&buffer[..3], b"8=F"); // BeginString start
237        assert_eq!(&buffer[10..12], b"9="); // BodyLength tag
238        assert_eq!(&buffer[17..20], b"35="); // MsgType tag
239    }
240
241    #[test]
242    fn test_forge_out_buffer_independence() {
243        let buffer1 = forge_out_buffer("FIX.4.4");
244        let mut buffer2 = forge_out_buffer("FIX.4.4");
245
246        // Modify buffer2
247        buffer2[FORGE_WRITE_START] = b'X';
248
249        // buffer1 should be unchanged
250        let expected_header = b"8=FIX.4.4\x019=0000\x0135=";
251        assert_eq!(
252            &buffer1[..expected_header.len()],
253            expected_header
254        );
255        assert_eq!(buffer1[FORGE_WRITE_START], 0);
256
257        // buffer2 should have the modification
258        assert_eq!(
259            &buffer2[..expected_header.len()],
260            expected_header
261        );
262        assert_eq!(
263            buffer2[FORGE_WRITE_START],
264            b'X'
265        );
266    }
267
268    #[test]
269    fn test_update_body_length() {
270        let mut buffer = forge_out_buffer("FIX.4.4");
271
272        // Test various body lengths for FIX.4.4
273        update_body_length(&mut buffer, 17); // body_length = 17 - 17 = 0
274        assert_eq!(
275            &buffer[BODY_LENGTH_VALUE_POS..BODY_LENGTH_VALUE_POS + 4],
276            b"0000"
277        );
278
279        update_body_length(&mut buffer, 59); // body_length = 59 - 17 = 42
280        assert_eq!(
281            &buffer[BODY_LENGTH_VALUE_POS..BODY_LENGTH_VALUE_POS + 4],
282            b"0042"
283        );
284
285        update_body_length(&mut buffer, 1251); // body_length = 1251 - 17 = 1234
286        assert_eq!(
287            &buffer[BODY_LENGTH_VALUE_POS..BODY_LENGTH_VALUE_POS + 4],
288            b"1234"
289        );
290
291        update_body_length(&mut buffer, 10016); // body_length = 10016 - 17 = 9999
292        assert_eq!(
293            &buffer[BODY_LENGTH_VALUE_POS..BODY_LENGTH_VALUE_POS + 4],
294            b"9999"
295        );
296
297        // Verify the rest of the header is unchanged
298        assert_eq!(
299            &buffer[..10],
300            b"8=FIX.4.4\x01"
301        );
302        assert_eq!(&buffer[10..12], b"9=");
303        assert_eq!(&buffer[16..20], b"\x0135=");
304    }
305
306    #[test]
307    fn test_forge_helper_functions() {
308        // Test FIX.4.4
309        assert_eq!(
310            forge_write_start("FIX.4.4"),
311            20
312        );
313
314        // Test FIX.4.2
315        assert_eq!(
316            forge_write_start("FIX.4.2"),
317            20
318        );
319
320        // Verify constants
321        assert_eq!(FORGE_WRITE_START, 20);
322        assert_eq!(BODY_LENGTH_VALUE_POS, 12);
323        assert_eq!(FORGE_HEADER_LEN, 20);
324    }
325}