Skip to main content

iso8583_core/
message.rs

1//! ISO 8583 Message structure and operations
2//!
3//! This module provides the main message type and operations for
4//! parsing and generating ISO 8583 messages.
5
6use crate::bitmap::Bitmap;
7use crate::error::{ISO8583Error, Result};
8use crate::field::{Field, FieldDefinition, FieldLength, FieldType, FieldValue};
9use crate::mti::MessageType;
10use std::collections::HashMap;
11
12/// ISO 8583 Message
13#[derive(Debug, Clone, PartialEq)]
14pub struct ISO8583Message {
15    /// Message Type Indicator
16    pub mti: MessageType,
17    /// Field values (keyed by field number)
18    fields: HashMap<u8, FieldValue>,
19    /// Bitmap indicating present fields
20    bitmap: Bitmap,
21}
22
23impl ISO8583Message {
24    /// Create a new message with given MTI
25    pub fn new(mti: MessageType) -> Self {
26        Self {
27            mti,
28            fields: HashMap::new(),
29            bitmap: Bitmap::new(),
30        }
31    }
32
33    /// Parse message from bytes (ASCII encoding)
34    ///
35    /// # Format
36    /// ```text
37    /// [MTI (4 bytes)][Bitmap (8/16/24 bytes)][Fields...]
38    /// ```
39    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
40        if bytes.len() < 12 {
41            // Minimum: 4 (MTI) + 8 (bitmap)
42            return Err(ISO8583Error::message_too_short(12, bytes.len()));
43        }
44
45        let mut offset = 0;
46
47        // 1. Parse MTI (first 4 bytes)
48        let mti = MessageType::from_bytes(&bytes[offset..offset + 4])?;
49        offset += 4;
50
51        // 2. Parse primary bitmap (8 bytes = 16 hex chars)
52        let bitmap_hex = hex::encode(&bytes[offset..offset + 8]);
53        let mut bitmap = Bitmap::from_hex(&bitmap_hex)?;
54        offset += 8;
55
56        // 3. Check for secondary bitmap (if field 1 is set)
57        if bitmap.is_set(1) {
58            if bytes.len() < offset + 8 {
59                return Err(ISO8583Error::message_too_short(offset + 8, bytes.len()));
60            }
61            let secondary_hex = hex::encode(&bytes[offset..offset + 8]);
62            let secondary_bitmap = Bitmap::from_hex(&secondary_hex)?;
63
64            // Merge secondary bitmap into main bitmap
65            for field_num in 65..=128 {
66                if secondary_bitmap.is_set(field_num) {
67                    bitmap.set(field_num)?;
68                }
69            }
70            offset += 8;
71        }
72
73        // 4. Parse fields based on bitmap
74        let mut fields = HashMap::new();
75        let (field_array, field_count) = bitmap.get_set_fields();
76
77        for item in field_array.iter().take(field_count) {
78            let field_num = *item;
79            if field_num == 1 || field_num == 65 {
80                continue; // Skip bitmap indicators
81            }
82
83            let field = Field::from_number(field_num)?;
84            let def = field.definition();
85
86            // Parse field based on its length specification
87            let (value, bytes_consumed) = Self::parse_field(&bytes[offset..], &def)?;
88            fields.insert(field_num, value);
89            offset += bytes_consumed;
90        }
91
92        Ok(Self {
93            mti,
94            fields,
95            bitmap,
96        })
97    }
98
99    /// Parse a single field from bytes
100    fn parse_field(bytes: &[u8], def: &FieldDefinition) -> Result<(FieldValue, usize)> {
101        // Ensure we have at least some bytes to parse
102        if bytes.is_empty() {
103            return Err(ISO8583Error::message_too_short(1, 0));
104        }
105
106        match def.length {
107            FieldLength::Fixed(len) => {
108                // Bounds check for fixed length
109                if bytes.len() < len {
110                    return Err(ISO8583Error::field_length_mismatch(
111                        def.number,
112                        len,
113                        bytes.len(),
114                    ));
115                }
116
117                let value = match def.field_type {
118                    FieldType::Binary => FieldValue::from_binary(bytes[..len].to_vec()),
119                    _ => {
120                        let s = std::str::from_utf8(&bytes[..len]).map_err(|e| {
121                            ISO8583Error::EncodingError(format!(
122                                "Invalid UTF-8 in field {}: {}",
123                                def.number, e
124                            ))
125                        })?;
126                        FieldValue::from_string(s.to_string())
127                    }
128                };
129
130                Ok((value, len))
131            }
132            FieldLength::LLVar(max_len) => {
133                // 2-digit length indicator - bounds check
134                if bytes.len() < 2 {
135                    return Err(ISO8583Error::message_too_short(2, bytes.len()));
136                }
137
138                let length_str = std::str::from_utf8(&bytes[..2]).map_err(|e| {
139                    ISO8583Error::EncodingError(format!(
140                        "Invalid length indicator for field {}: {}",
141                        def.number, e
142                    ))
143                })?;
144                let length: usize = length_str.parse().map_err(|e| {
145                    ISO8583Error::EncodingError(format!(
146                        "Invalid length value for field {}: {}",
147                        def.number, e
148                    ))
149                })?;
150
151                if length > max_len {
152                    return Err(ISO8583Error::invalid_field_value(
153                        def.number,
154                        format!(
155                            "Length {} exceeds maximum {} for field {}",
156                            length, max_len, def.number
157                        ),
158                    ));
159                }
160
161                // Bounds check for field data
162                if bytes.len() < 2 + length {
163                    return Err(ISO8583Error::message_too_short(2 + length, bytes.len()));
164                }
165
166                let value = match def.field_type {
167                    FieldType::Binary => FieldValue::from_binary(bytes[2..2 + length].to_vec()),
168                    _ => {
169                        let s = std::str::from_utf8(&bytes[2..2 + length]).map_err(|e| {
170                            ISO8583Error::EncodingError(format!(
171                                "Invalid UTF-8 in field {}: {}",
172                                def.number, e
173                            ))
174                        })?;
175                        FieldValue::from_string(s.to_string())
176                    }
177                };
178
179                Ok((value, 2 + length))
180            }
181            FieldLength::LLLVar(max_len) => {
182                // 3-digit length indicator - bounds check
183                if bytes.len() < 3 {
184                    return Err(ISO8583Error::message_too_short(3, bytes.len()));
185                }
186
187                let length_str = std::str::from_utf8(&bytes[..3]).map_err(|e| {
188                    ISO8583Error::EncodingError(format!(
189                        "Invalid length indicator for field {}: {}",
190                        def.number, e
191                    ))
192                })?;
193                let length: usize = length_str.parse().map_err(|e| {
194                    ISO8583Error::EncodingError(format!(
195                        "Invalid length value for field {}: {}",
196                        def.number, e
197                    ))
198                })?;
199
200                if length > max_len {
201                    return Err(ISO8583Error::invalid_field_value(
202                        def.number,
203                        format!(
204                            "Length {} exceeds maximum {} for field {}",
205                            length, max_len, def.number
206                        ),
207                    ));
208                }
209
210                // Bounds check for field data
211                if bytes.len() < 3 + length {
212                    return Err(ISO8583Error::message_too_short(3 + length, bytes.len()));
213                }
214
215                let value = match def.field_type {
216                    FieldType::Binary => FieldValue::from_binary(bytes[3..3 + length].to_vec()),
217                    _ => {
218                        let s = std::str::from_utf8(&bytes[3..3 + length]).map_err(|e| {
219                            ISO8583Error::EncodingError(format!(
220                                "Invalid UTF-8 in field {}: {}",
221                                def.number, e
222                            ))
223                        })?;
224                        FieldValue::from_string(s.to_string())
225                    }
226                };
227
228                Ok((value, 3 + length))
229            }
230        }
231    }
232
233    /// Generate message bytes (ASCII encoding)
234    pub fn to_bytes(&self) -> Vec<u8> {
235        let mut bytes = Vec::new();
236
237        // 1. Add MTI
238        bytes.extend_from_slice(&self.mti.to_bytes());
239
240        // 2. Add bitmap(s)
241        let (bitmap_bytes, bitmap_len) = self.bitmap.to_bytes();
242        bytes.extend_from_slice(&bitmap_bytes[..bitmap_len]);
243
244        // 3. Add fields in numerical order
245        let mut field_numbers: Vec<u8> = self.fields.keys().copied().collect();
246        field_numbers.sort();
247
248        for field_num in field_numbers {
249            if field_num == 1 || field_num == 65 {
250                continue; // Skip bitmap indicators
251            }
252
253            if let Some(value) = self.fields.get(&field_num) {
254                let field = Field::from_number(field_num).unwrap();
255                let field_bytes = Self::generate_field(&field, value);
256                bytes.extend_from_slice(&field_bytes);
257            }
258        }
259
260        bytes
261    }
262
263    /// Generate bytes for a single field
264    fn generate_field(field: &Field, value: &FieldValue) -> Vec<u8> {
265        let def = field.definition();
266        let mut bytes = Vec::new();
267
268        match def.length {
269            FieldLength::Fixed(len) => {
270                // Fixed length field
271                match value {
272                    FieldValue::String(s) => {
273                        let mut field_str = s.clone();
274                        // Pad or truncate to exact length
275                        if field_str.len() < len {
276                            // Pad with spaces or zeros depending on field type
277                            match def.field_type {
278                                FieldType::Numeric => {
279                                    field_str = format!("{:0>width$}", field_str, width = len);
280                                }
281                                _ => {
282                                    field_str = format!("{:<width$}", field_str, width = len);
283                                }
284                            }
285                        } else if field_str.len() > len {
286                            field_str.truncate(len);
287                        }
288                        bytes.extend_from_slice(field_str.as_bytes());
289                    }
290                    FieldValue::Binary(b) => {
291                        let mut bin = b.clone();
292                        bin.resize(len, 0); // Pad with zeros if needed
293                        bytes.extend_from_slice(&bin);
294                    }
295                }
296            }
297            FieldLength::LLVar(_max_len) => {
298                // Variable length with 2-digit length indicator
299                match value {
300                    FieldValue::String(s) => {
301                        let length = format!("{:02}", s.len());
302                        bytes.extend_from_slice(length.as_bytes());
303                        bytes.extend_from_slice(s.as_bytes());
304                    }
305                    FieldValue::Binary(b) => {
306                        let length = format!("{:02}", b.len());
307                        bytes.extend_from_slice(length.as_bytes());
308                        bytes.extend_from_slice(b);
309                    }
310                }
311            }
312            FieldLength::LLLVar(_max_len) => {
313                // Variable length with 3-digit length indicator
314                match value {
315                    FieldValue::String(s) => {
316                        let length = format!("{:03}", s.len());
317                        bytes.extend_from_slice(length.as_bytes());
318                        bytes.extend_from_slice(s.as_bytes());
319                    }
320                    FieldValue::Binary(b) => {
321                        let length = format!("{:03}", b.len());
322                        bytes.extend_from_slice(length.as_bytes());
323                        bytes.extend_from_slice(b);
324                    }
325                }
326            }
327        }
328
329        bytes
330    }
331
332    /// Get field value
333    pub fn get_field(&self, field: Field) -> Option<&FieldValue> {
334        self.fields.get(&field.number())
335    }
336
337    /// Set field value
338    pub fn set_field(&mut self, field: Field, value: FieldValue) -> Result<()> {
339        let field_num = field.number();
340
341        // Update bitmap
342        self.bitmap.set(field_num)?;
343
344        // Store value
345        self.fields.insert(field_num, value);
346
347        Ok(())
348    }
349
350    /// Remove field
351    pub fn remove_field(&mut self, field: Field) -> Result<()> {
352        let field_num = field.number();
353
354        // Update bitmap
355        self.bitmap.clear(field_num)?;
356
357        // Remove value
358        self.fields.remove(&field_num);
359
360        Ok(())
361    }
362
363    /// Check if field is present
364    pub fn has_field(&self, field: Field) -> bool {
365        self.fields.contains_key(&field.number())
366    }
367
368    /// Get all present field numbers
369    pub fn get_field_numbers(&self) -> Vec<u8> {
370        let mut numbers: Vec<u8> = self.fields.keys().copied().collect();
371        numbers.sort();
372        numbers
373    }
374
375    /// Get bitmap reference
376    pub fn bitmap(&self) -> &Bitmap {
377        &self.bitmap
378    }
379
380    /// Create a builder for constructing messages
381    pub fn builder() -> MessageBuilder {
382        MessageBuilder::new()
383    }
384}
385
386/// Builder for ISO 8583 messages
387#[derive(Debug)]
388pub struct MessageBuilder {
389    message: ISO8583Message,
390}
391
392impl MessageBuilder {
393    /// Create a new builder
394    pub fn new() -> Self {
395        Self {
396            message: ISO8583Message::new(MessageType::AUTHORIZATION_REQUEST),
397        }
398    }
399
400    /// Set the MTI
401    pub fn mti(mut self, mti: MessageType) -> Self {
402        self.message.mti = mti;
403        self
404    }
405
406    /// Add a field
407    pub fn field<S: Into<String>>(mut self, field: Field, value: S) -> Self {
408        let _ = self
409            .message
410            .set_field(field, FieldValue::from_string(value.into()));
411        self
412    }
413
414    /// Add a binary field
415    pub fn binary_field(mut self, field: Field, value: Vec<u8>) -> Self {
416        let _ = self
417            .message
418            .set_field(field, FieldValue::from_binary(value));
419        self
420    }
421
422    /// Build the message
423    pub fn build(self) -> Result<ISO8583Message> {
424        // Validate the message
425        crate::validation::Validator::validate_required_fields(&self.message)?;
426
427        Ok(self.message)
428    }
429}
430
431impl Default for MessageBuilder {
432    fn default() -> Self {
433        Self::new()
434    }
435}
436
437#[cfg(test)]
438mod tests {
439    use super::*;
440
441    #[test]
442    fn test_message_creation() {
443        let msg = ISO8583Message::new(MessageType::AUTHORIZATION_REQUEST);
444        assert_eq!(msg.mti, MessageType::AUTHORIZATION_REQUEST);
445        assert_eq!(msg.get_field_numbers().len(), 0);
446    }
447
448    #[test]
449    fn test_set_and_get_field() {
450        let mut msg = ISO8583Message::new(MessageType::AUTHORIZATION_REQUEST);
451
452        msg.set_field(
453            Field::PrimaryAccountNumber,
454            FieldValue::from_string("4111111111111111"),
455        )
456        .unwrap();
457
458        assert!(msg.has_field(Field::PrimaryAccountNumber));
459        let value = msg.get_field(Field::PrimaryAccountNumber).unwrap();
460        assert_eq!(value.as_string(), Some("4111111111111111"));
461    }
462
463    #[test]
464    fn test_remove_field() {
465        let mut msg = ISO8583Message::new(MessageType::AUTHORIZATION_REQUEST);
466
467        msg.set_field(
468            Field::PrimaryAccountNumber,
469            FieldValue::from_string("4111111111111111"),
470        )
471        .unwrap();
472
473        assert!(msg.has_field(Field::PrimaryAccountNumber));
474
475        msg.remove_field(Field::PrimaryAccountNumber).unwrap();
476
477        assert!(!msg.has_field(Field::PrimaryAccountNumber));
478    }
479
480    #[test]
481    fn test_builder() {
482        let msg = ISO8583Message::builder()
483            .mti(MessageType::FINANCIAL_REQUEST)
484            .field(Field::ProcessingCode, "000000")
485            .field(Field::SystemTraceAuditNumber, "123456")
486            .field(Field::LocalTransactionTime, "120000")
487            .field(Field::LocalTransactionDate, "0101");
488
489        // Note: build() will fail because required fields are missing
490        // This is expected behavior
491        assert!(msg.build().is_err());
492    }
493}