Skip to main content

ironfix_fast/
decoder.rs

1/******************************************************************************
2   Author: Joaquín Béjar García
3   Email: jb@taunais.com
4   Date: 27/1/26
5******************************************************************************/
6
7//! FAST protocol decoder.
8//!
9//! This module provides decoding of FAST-encoded messages using stop-bit
10//! encoding and presence maps.
11
12use crate::error::FastError;
13use crate::operators::DictionaryValue;
14use crate::pmap::PresenceMap;
15use std::collections::HashMap;
16
17/// FAST protocol decoder.
18#[derive(Debug)]
19pub struct FastDecoder {
20    /// Global dictionary for operator state.
21    global_dict: HashMap<String, DictionaryValue>,
22    /// Template-specific dictionaries.
23    template_dicts: HashMap<u32, HashMap<String, DictionaryValue>>,
24    /// Last used template ID.
25    last_template_id: Option<u32>,
26}
27
28impl FastDecoder {
29    /// Creates a new FAST decoder.
30    #[must_use]
31    pub fn new() -> Self {
32        Self {
33            global_dict: HashMap::new(),
34            template_dicts: HashMap::new(),
35            last_template_id: None,
36        }
37    }
38
39    /// Resets the decoder state.
40    pub fn reset(&mut self) {
41        self.global_dict.clear();
42        self.template_dicts.clear();
43        self.last_template_id = None;
44    }
45
46    /// Decodes an unsigned integer using stop-bit encoding.
47    ///
48    /// # Arguments
49    /// * `data` - The input bytes
50    /// * `offset` - Current position (will be updated)
51    ///
52    /// # Returns
53    /// The decoded unsigned integer.
54    ///
55    /// # Errors
56    /// Returns `FastError::UnexpectedEof` if data is incomplete.
57    pub fn decode_uint(data: &[u8], offset: &mut usize) -> Result<u64, FastError> {
58        let mut result: u64 = 0;
59
60        loop {
61            if *offset >= data.len() {
62                return Err(FastError::UnexpectedEof);
63            }
64
65            let byte = data[*offset];
66            *offset += 1;
67
68            // Check for overflow
69            if result > (u64::MAX >> 7) {
70                return Err(FastError::IntegerOverflow);
71            }
72
73            result = (result << 7) | (byte & 0x7F) as u64;
74
75            // Check stop bit
76            if byte & 0x80 != 0 {
77                break;
78            }
79        }
80
81        Ok(result)
82    }
83
84    /// Decodes a signed integer using stop-bit encoding.
85    ///
86    /// # Arguments
87    /// * `data` - The input bytes
88    /// * `offset` - Current position (will be updated)
89    ///
90    /// # Returns
91    /// The decoded signed integer.
92    ///
93    /// # Errors
94    /// Returns `FastError::UnexpectedEof` if data is incomplete.
95    pub fn decode_int(data: &[u8], offset: &mut usize) -> Result<i64, FastError> {
96        if *offset >= data.len() {
97            return Err(FastError::UnexpectedEof);
98        }
99
100        let first_byte = data[*offset];
101        let negative = (first_byte & 0x40) != 0;
102
103        let mut result: i64 = if negative { -1 } else { 0 };
104
105        loop {
106            if *offset >= data.len() {
107                return Err(FastError::UnexpectedEof);
108            }
109
110            let byte = data[*offset];
111            *offset += 1;
112
113            result = (result << 7) | (byte & 0x7F) as i64;
114
115            if byte & 0x80 != 0 {
116                break;
117            }
118        }
119
120        Ok(result)
121    }
122
123    /// Decodes an ASCII string using stop-bit encoding.
124    ///
125    /// # Arguments
126    /// * `data` - The input bytes
127    /// * `offset` - Current position (will be updated)
128    ///
129    /// # Returns
130    /// The decoded string.
131    ///
132    /// # Errors
133    /// Returns `FastError::UnexpectedEof` if data is incomplete.
134    pub fn decode_ascii(data: &[u8], offset: &mut usize) -> Result<String, FastError> {
135        let mut result = Vec::new();
136
137        loop {
138            if *offset >= data.len() {
139                return Err(FastError::UnexpectedEof);
140            }
141
142            let byte = data[*offset];
143            *offset += 1;
144
145            // Add character (without stop bit)
146            result.push(byte & 0x7F);
147
148            // Check stop bit
149            if byte & 0x80 != 0 {
150                break;
151            }
152        }
153
154        String::from_utf8(result).map_err(|_| FastError::InvalidString)
155    }
156
157    /// Decodes a byte vector.
158    ///
159    /// # Arguments
160    /// * `data` - The input bytes
161    /// * `offset` - Current position (will be updated)
162    ///
163    /// # Returns
164    /// The decoded bytes.
165    ///
166    /// # Errors
167    /// Returns `FastError::UnexpectedEof` if data is incomplete.
168    pub fn decode_bytes(data: &[u8], offset: &mut usize) -> Result<Vec<u8>, FastError> {
169        let length = Self::decode_uint(data, offset)? as usize;
170
171        if *offset + length > data.len() {
172            return Err(FastError::UnexpectedEof);
173        }
174
175        let bytes = data[*offset..*offset + length].to_vec();
176        *offset += length;
177
178        Ok(bytes)
179    }
180
181    /// Decodes a presence map.
182    ///
183    /// # Arguments
184    /// * `data` - The input bytes
185    /// * `offset` - Current position (will be updated)
186    ///
187    /// # Returns
188    /// The decoded presence map.
189    ///
190    /// # Errors
191    /// Returns `FastError::UnexpectedEof` if data is incomplete.
192    pub fn decode_pmap(data: &[u8], offset: &mut usize) -> Result<PresenceMap, FastError> {
193        PresenceMap::decode(data, offset)
194    }
195
196    /// Gets a value from the global dictionary.
197    #[must_use]
198    pub fn get_global(&self, key: &str) -> Option<&DictionaryValue> {
199        self.global_dict.get(key)
200    }
201
202    /// Sets a value in the global dictionary.
203    pub fn set_global(&mut self, key: impl Into<String>, value: DictionaryValue) {
204        self.global_dict.insert(key.into(), value);
205    }
206
207    /// Gets a value from a template dictionary.
208    #[must_use]
209    pub fn get_template(&self, template_id: u32, key: &str) -> Option<&DictionaryValue> {
210        self.template_dicts
211            .get(&template_id)
212            .and_then(|dict| dict.get(key))
213    }
214
215    /// Sets a value in a template dictionary.
216    pub fn set_template(
217        &mut self,
218        template_id: u32,
219        key: impl Into<String>,
220        value: DictionaryValue,
221    ) {
222        self.template_dicts
223            .entry(template_id)
224            .or_default()
225            .insert(key.into(), value);
226    }
227
228    /// Returns the last used template ID.
229    #[must_use]
230    pub const fn last_template_id(&self) -> Option<u32> {
231        self.last_template_id
232    }
233
234    /// Sets the last used template ID.
235    pub fn set_last_template_id(&mut self, id: u32) {
236        self.last_template_id = Some(id);
237    }
238}
239
240impl Default for FastDecoder {
241    fn default() -> Self {
242        Self::new()
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249
250    #[test]
251    fn test_decode_uint_single_byte() {
252        let data = [0x81]; // 1 with stop bit
253        let mut offset = 0;
254        let result = FastDecoder::decode_uint(&data, &mut offset).unwrap();
255        assert_eq!(result, 1);
256        assert_eq!(offset, 1);
257    }
258
259    #[test]
260    fn test_decode_uint_multi_byte() {
261        let data = [0x00, 0x81]; // 1 in two bytes
262        let mut offset = 0;
263        let result = FastDecoder::decode_uint(&data, &mut offset).unwrap();
264        assert_eq!(result, 1);
265        assert_eq!(offset, 2);
266    }
267
268    #[test]
269    fn test_decode_uint_larger() {
270        // 942 = 0x3AE = 0b11_1010_1110
271        // In stop-bit encoding: 0x07 (0b0000111), 0xAE | 0x80 = 0x2E | 0x80 = 0xAE
272        // Actually: 942 = 7 * 128 + 46 = 896 + 46
273        // First byte: 7 (0x07), second byte: 46 | 0x80 = 0xAE
274        let data = [0x07, 0xAE]; // 942 in stop-bit encoding
275        let mut offset = 0;
276        let result = FastDecoder::decode_uint(&data, &mut offset).unwrap();
277        assert_eq!(result, 942);
278    }
279
280    #[test]
281    fn test_decode_int_positive() {
282        let data = [0x81]; // 1
283        let mut offset = 0;
284        let result = FastDecoder::decode_int(&data, &mut offset).unwrap();
285        assert_eq!(result, 1);
286    }
287
288    #[test]
289    fn test_decode_int_negative() {
290        let data = [0xFF]; // -1
291        let mut offset = 0;
292        let result = FastDecoder::decode_int(&data, &mut offset).unwrap();
293        assert_eq!(result, -1);
294    }
295
296    #[test]
297    fn test_decode_ascii() {
298        let data = [b'H', b'i', b'!' | 0x80]; // "Hi!"
299        let mut offset = 0;
300        let result = FastDecoder::decode_ascii(&data, &mut offset).unwrap();
301        assert_eq!(result, "Hi!");
302    }
303
304    #[test]
305    fn test_decoder_dictionary() {
306        let mut decoder = FastDecoder::new();
307
308        decoder.set_global("test", DictionaryValue::Int(42));
309        assert_eq!(decoder.get_global("test").unwrap().as_i64(), Some(42));
310
311        decoder.set_template(1, "field", DictionaryValue::UInt(100));
312        assert_eq!(
313            decoder.get_template(1, "field").unwrap().as_u64(),
314            Some(100)
315        );
316    }
317}