Skip to main content

iso8583_core/
spec.rs

1//! ISO 8583 Field Specifications - Zero-Allocation Static Tables
2//!
3//! This module provides compile-time field definitions with zero runtime overhead.
4//! All field metadata is stored in static const tables.
5
6#![cfg_attr(not(feature = "std"), no_std)]
7
8/// Data type for field values
9#[repr(u8)]
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum DataType {
12    /// Numeric digits only (0-9)
13    Numeric = 0,
14    /// Alphabetic characters only (A-Z, a-z)
15    Alpha = 1,
16    /// Alphanumeric (0-9, A-Z, a-z)
17    Alphanumeric = 2,
18    /// Alphanumeric with special characters
19    AlphanumericSpecial = 3,
20    /// Binary data
21    Binary = 4,
22    /// Track 2 magnetic stripe format
23    Track2 = 5,
24    /// Track 3 magnetic stripe format
25    Track3 = 6,
26}
27
28/// Length encoding type for field
29#[repr(u8)]
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum LengthType {
32    /// Fixed length (no length indicator)
33    Fixed = 0,
34    /// Variable length with 2-digit length indicator (LLVAR)
35    Llvar = 1,
36    /// Variable length with 3-digit length indicator (LLLVAR)
37    Lllvar = 2,
38}
39
40/// Field definition - small, copyable, stored in static memory
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub struct FieldDefinition {
43    /// Data type of the field
44    pub data_type: DataType,
45    /// Length encoding type
46    pub length_type: LengthType,
47    /// Maximum length in bytes
48    pub max_len: u16,
49}
50
51impl FieldDefinition {
52    /// Create a new field definition
53    #[inline]
54    pub const fn new(data_type: DataType, length_type: LengthType, max_len: u16) -> Self {
55        Self {
56            data_type,
57            length_type,
58            max_len,
59        }
60    }
61
62    /// Create a fixed-length field
63    #[inline]
64    pub const fn fixed(data_type: DataType, len: u16) -> Self {
65        Self::new(data_type, LengthType::Fixed, len)
66    }
67
68    /// Create an LLVAR field
69    #[inline]
70    pub const fn llvar(data_type: DataType, max_len: u16) -> Self {
71        Self::new(data_type, LengthType::Llvar, max_len)
72    }
73
74    /// Create an LLLVAR field
75    #[inline]
76    pub const fn lllvar(data_type: DataType, max_len: u16) -> Self {
77        Self::new(data_type, LengthType::Lllvar, max_len)
78    }
79}
80
81/// Macro to generate ISO 8583 field specification table
82macro_rules! iso_table {
83    ($($field:expr => $def:expr),* $(,)?) => {{
84        let mut table: [Option<FieldDefinition>; 129] = [None; 129];
85        $(
86            table[$field] = Some($def);
87        )*
88        table
89    }};
90}
91
92/// ISO 8583:1987 Specification Table
93///
94/// This is a compile-time const array with zero runtime overhead.
95/// Field lookup is O(1) with no heap allocation.
96pub const ISO8583_1987_TABLE: [Option<FieldDefinition>; 129] = iso_table! {
97    // Field 1: Secondary Bitmap (binary, fixed 8 bytes)
98    1 => FieldDefinition::fixed(DataType::Binary, 8),
99
100    // Field 2: Primary Account Number (numeric, LLVAR, max 19)
101    2 => FieldDefinition::llvar(DataType::Numeric, 19),
102
103    // Field 3: Processing Code (numeric, fixed 6)
104    3 => FieldDefinition::fixed(DataType::Numeric, 6),
105
106    // Field 4: Transaction Amount (numeric, fixed 12)
107    4 => FieldDefinition::fixed(DataType::Numeric, 12),
108
109    // Field 5: Settlement Amount (numeric, fixed 12)
110    5 => FieldDefinition::fixed(DataType::Numeric, 12),
111
112    // Field 6: Cardholder Billing Amount (numeric, fixed 12)
113    6 => FieldDefinition::fixed(DataType::Numeric, 12),
114
115    // Field 7: Transmission Date & Time (numeric, fixed 10 - MMDDhhmmss)
116    7 => FieldDefinition::fixed(DataType::Numeric, 10),
117
118    // Field 8: Cardholder Billing Fee Amount (numeric, fixed 8)
119    8 => FieldDefinition::fixed(DataType::Numeric, 8),
120
121    // Field 9: Settlement Conversion Rate (numeric, fixed 8)
122    9 => FieldDefinition::fixed(DataType::Numeric, 8),
123
124    // Field 10: Cardholder Billing Conversion Rate (numeric, fixed 8)
125    10 => FieldDefinition::fixed(DataType::Numeric, 8),
126
127    // Field 11: System Trace Audit Number (numeric, fixed 6)
128    11 => FieldDefinition::fixed(DataType::Numeric, 6),
129
130    // Field 12: Local Transaction Time (numeric, fixed 6 - hhmmss)
131    12 => FieldDefinition::fixed(DataType::Numeric, 6),
132
133    // Field 13: Local Transaction Date (numeric, fixed 4 - MMDD)
134    13 => FieldDefinition::fixed(DataType::Numeric, 4),
135
136    // Field 14: Expiration Date (numeric, fixed 4 - YYMM)
137    14 => FieldDefinition::fixed(DataType::Numeric, 4),
138
139    // Field 15: Settlement Date (numeric, fixed 4 - MMDD)
140    15 => FieldDefinition::fixed(DataType::Numeric, 4),
141
142    // Field 16: Currency Conversion Date (numeric, fixed 4)
143    16 => FieldDefinition::fixed(DataType::Numeric, 4),
144
145    // Field 17: Capture Date (numeric, fixed 4)
146    17 => FieldDefinition::fixed(DataType::Numeric, 4),
147
148    // Field 18: Merchant Type (numeric, fixed 4)
149    18 => FieldDefinition::fixed(DataType::Numeric, 4),
150
151    // Field 19: Acquiring Institution Country Code (numeric, fixed 3)
152    19 => FieldDefinition::fixed(DataType::Numeric, 3),
153
154    // Field 20: PAN Extended Country Code (numeric, fixed 3)
155    20 => FieldDefinition::fixed(DataType::Numeric, 3),
156
157    // Field 21: Forwarding Institution Country Code (numeric, fixed 3)
158    21 => FieldDefinition::fixed(DataType::Numeric, 3),
159
160    // Field 22: Point of Service Entry Mode (numeric, fixed 3)
161    22 => FieldDefinition::fixed(DataType::Numeric, 3),
162
163    // Field 23: Card Sequence Number (numeric, fixed 3)
164    23 => FieldDefinition::fixed(DataType::Numeric, 3),
165
166    // Field 24: Function Code (numeric, fixed 3)
167    24 => FieldDefinition::fixed(DataType::Numeric, 3),
168
169    // Field 25: Point of Service Condition Code (numeric, fixed 2)
170    25 => FieldDefinition::fixed(DataType::Numeric, 2),
171
172    // Field 26: Point of Service Capture Code (numeric, fixed 2)
173    26 => FieldDefinition::fixed(DataType::Numeric, 2),
174
175    // Field 27: Authorization Identification Response Length (numeric, fixed 1)
176    27 => FieldDefinition::fixed(DataType::Numeric, 1),
177
178    // Field 28: Transaction Fee Amount (numeric, fixed 9)
179    28 => FieldDefinition::fixed(DataType::Numeric, 9),
180
181    // Field 29: Settlement Fee Amount (numeric, fixed 9)
182    29 => FieldDefinition::fixed(DataType::Numeric, 9),
183
184    // Field 30: Transaction Processing Fee Amount (numeric, fixed 9)
185    30 => FieldDefinition::fixed(DataType::Numeric, 9),
186
187    // Field 31: Settlement Processing Fee Amount (numeric, fixed 9)
188    31 => FieldDefinition::fixed(DataType::Numeric, 9),
189
190    // Field 32: Acquiring Institution ID Code (LLVAR, max 11)
191    32 => FieldDefinition::llvar(DataType::Numeric, 11),
192
193    // Field 33: Forwarding Institution ID Code (LLVAR, max 11)
194    33 => FieldDefinition::llvar(DataType::Numeric, 11),
195
196    // Field 34: Extended PAN (LLVAR, max 28)
197    34 => FieldDefinition::llvar(DataType::Alphanumeric, 28),
198
199    // Field 35: Track 2 Data (LLVAR, max 37)
200    35 => FieldDefinition::llvar(DataType::Track2, 37),
201
202    // Field 36: Track 3 Data (LLLVAR, max 104)
203    36 => FieldDefinition::lllvar(DataType::Track3, 104),
204
205    // Field 37: Retrieval Reference Number (alphanumeric, fixed 12)
206    37 => FieldDefinition::fixed(DataType::Alphanumeric, 12),
207
208    // Field 38: Authorization ID Response (alphanumeric, fixed 6)
209    38 => FieldDefinition::fixed(DataType::Alphanumeric, 6),
210
211    // Field 39: Response Code (alphanumeric, fixed 2)
212    39 => FieldDefinition::fixed(DataType::Alphanumeric, 2),
213
214    // Field 40: Service Restriction Code (alphanumeric, fixed 3)
215    40 => FieldDefinition::fixed(DataType::Alphanumeric, 3),
216
217    // Field 41: Card Acceptor Terminal ID (alphanumeric special, fixed 8)
218    41 => FieldDefinition::fixed(DataType::AlphanumericSpecial, 8),
219
220    // Field 42: Card Acceptor ID Code (alphanumeric special, fixed 15)
221    42 => FieldDefinition::fixed(DataType::AlphanumericSpecial, 15),
222
223    // Field 43: Card Acceptor Name/Location (alphanumeric special, fixed 40)
224    43 => FieldDefinition::fixed(DataType::AlphanumericSpecial, 40),
225
226    // Field 44: Additional Response Data (LLVAR, max 25)
227    44 => FieldDefinition::llvar(DataType::AlphanumericSpecial, 25),
228
229    // Field 45: Track 1 Data (LLVAR, max 76)
230    45 => FieldDefinition::llvar(DataType::AlphanumericSpecial, 76),
231
232    // Field 46: Additional Data - ISO (LLLVAR, max 999)
233    46 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
234
235    // Field 47: Additional Data - National (LLLVAR, max 999)
236    47 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
237
238    // Field 48: Additional Data - Private (LLLVAR, max 999)
239    48 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
240
241    // Field 49: Currency Code, Transaction (alphanumeric, fixed 3)
242    49 => FieldDefinition::fixed(DataType::Alphanumeric, 3),
243
244    // Field 50: Currency Code, Settlement (alphanumeric, fixed 3)
245    50 => FieldDefinition::fixed(DataType::Alphanumeric, 3),
246
247    // Field 51: Currency Code, Cardholder Billing (alphanumeric, fixed 3)
248    51 => FieldDefinition::fixed(DataType::Alphanumeric, 3),
249
250    // Field 52: PIN Data (binary, fixed 8)
251    52 => FieldDefinition::fixed(DataType::Binary, 8),
252
253    // Field 53: Security Related Control Information (numeric, fixed 16)
254    53 => FieldDefinition::fixed(DataType::Numeric, 16),
255
256    // Field 54: Additional Amounts (LLLVAR, max 120)
257    54 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 120),
258
259    // Field 55: ICC Data - EMV (LLLVAR, max 999)
260    55 => FieldDefinition::lllvar(DataType::Binary, 999),
261
262    // Fields 56-63: Reserved for ISO use
263    56 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
264    57 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
265    58 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
266    59 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
267    60 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
268    61 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
269    62 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
270    63 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
271
272    // Field 64: Message Authentication Code (binary, fixed 8)
273    64 => FieldDefinition::fixed(DataType::Binary, 8),
274
275    // Field 65: Tertiary Bitmap (binary, fixed 8)
276    65 => FieldDefinition::fixed(DataType::Binary, 8),
277
278    // Fields 66-128: Additional fields
279    66 => FieldDefinition::fixed(DataType::Numeric, 1),
280    67 => FieldDefinition::fixed(DataType::Numeric, 2),
281    68 => FieldDefinition::fixed(DataType::Numeric, 3),
282    69 => FieldDefinition::fixed(DataType::Numeric, 3),
283    70 => FieldDefinition::fixed(DataType::Numeric, 3),
284    71 => FieldDefinition::fixed(DataType::Numeric, 4),
285    72 => FieldDefinition::fixed(DataType::Numeric, 4),
286    73 => FieldDefinition::fixed(DataType::Numeric, 6),
287    74 => FieldDefinition::fixed(DataType::Numeric, 10),
288    75 => FieldDefinition::fixed(DataType::Numeric, 10),
289    76 => FieldDefinition::fixed(DataType::Numeric, 10),
290    77 => FieldDefinition::fixed(DataType::Numeric, 10),
291    78 => FieldDefinition::fixed(DataType::Numeric, 10),
292    79 => FieldDefinition::fixed(DataType::Numeric, 10),
293    80 => FieldDefinition::fixed(DataType::Numeric, 10),
294    81 => FieldDefinition::fixed(DataType::Numeric, 10),
295    82 => FieldDefinition::fixed(DataType::Numeric, 12),
296    83 => FieldDefinition::fixed(DataType::Numeric, 12),
297    84 => FieldDefinition::fixed(DataType::Numeric, 12),
298    85 => FieldDefinition::fixed(DataType::Numeric, 12),
299    86 => FieldDefinition::fixed(DataType::Numeric, 16),
300    87 => FieldDefinition::fixed(DataType::Numeric, 16),
301    88 => FieldDefinition::fixed(DataType::Numeric, 16),
302    89 => FieldDefinition::fixed(DataType::Numeric, 16),
303    90 => FieldDefinition::fixed(DataType::Numeric, 42),
304    91 => FieldDefinition::fixed(DataType::Alphanumeric, 1),
305    92 => FieldDefinition::fixed(DataType::Alphanumeric, 2),
306    93 => FieldDefinition::fixed(DataType::Alphanumeric, 5),
307    94 => FieldDefinition::fixed(DataType::Alphanumeric, 7),
308    95 => FieldDefinition::fixed(DataType::Alphanumeric, 42),
309    96 => FieldDefinition::fixed(DataType::Binary, 8),
310    97 => FieldDefinition::fixed(DataType::Numeric, 16),
311    98 => FieldDefinition::fixed(DataType::AlphanumericSpecial, 25),
312    99 => FieldDefinition::llvar(DataType::Numeric, 11),
313    100 => FieldDefinition::llvar(DataType::Numeric, 11),
314    101 => FieldDefinition::llvar(DataType::AlphanumericSpecial, 17),
315    102 => FieldDefinition::llvar(DataType::AlphanumericSpecial, 28),
316    103 => FieldDefinition::llvar(DataType::AlphanumericSpecial, 28),
317    104 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 100),
318    105 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
319    106 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
320    107 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
321    108 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
322    109 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
323    110 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
324    111 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
325    112 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
326    113 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
327    114 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
328    115 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
329    116 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
330    117 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
331    118 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
332    119 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
333    120 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
334    121 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
335    122 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
336    123 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
337    124 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 255),
338    125 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 50),
339    126 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 6),
340    127 => FieldDefinition::lllvar(DataType::AlphanumericSpecial, 999),
341    128 => FieldDefinition::fixed(DataType::Binary, 8),
342};
343
344/// Trait for ISO 8583 specification versions
345pub trait IsoSpec {
346    /// Static field definition table
347    const TABLE: &'static [Option<FieldDefinition>];
348
349    /// Get field definition by number (O(1) lookup)
350    #[inline]
351    fn get_field(number: u8) -> Option<&'static FieldDefinition> {
352        if (number as usize) < Self::TABLE.len() {
353            Self::TABLE[number as usize].as_ref()
354        } else {
355            None
356        }
357    }
358}
359
360/// ISO 8583:1987 Specification
361pub struct Iso1987;
362
363impl IsoSpec for Iso1987 {
364    const TABLE: &'static [Option<FieldDefinition>] = &ISO8583_1987_TABLE;
365}
366
367#[cfg(test)]
368mod tests {
369    use super::*;
370
371    #[test]
372    fn test_field_lookup() {
373        // Field 2 - PAN
374        let field2 = Iso1987::get_field(2).unwrap();
375        assert_eq!(field2.data_type, DataType::Numeric);
376        assert_eq!(field2.length_type, LengthType::Llvar);
377        assert_eq!(field2.max_len, 19);
378
379        // Field 4 - Amount
380        let field4 = Iso1987::get_field(4).unwrap();
381        assert_eq!(field4.data_type, DataType::Numeric);
382        assert_eq!(field4.length_type, LengthType::Fixed);
383        assert_eq!(field4.max_len, 12);
384    }
385
386    #[test]
387    fn test_invalid_field() {
388        assert!(Iso1987::get_field(0).is_none());
389        assert!(Iso1987::get_field(200).is_none());
390    }
391
392    #[test]
393    fn test_zero_overhead() {
394        // Verify that FieldDefinition is small
395        assert_eq!(core::mem::size_of::<FieldDefinition>(), 4);
396
397        // Verify enums are single byte
398        assert_eq!(core::mem::size_of::<DataType>(), 1);
399        assert_eq!(core::mem::size_of::<LengthType>(), 1);
400    }
401}