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}