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}