swift_mt_message/headers/mod.rs
1//! # SWIFT Message Headers and Trailers
2//!
3//! ## Purpose
4//! Comprehensive header and trailer structures for SWIFT MT messages, implementing the complete
5//! SWIFT FIN block structure including Basic Header (Block 1), Application Header (Block 2),
6//! User Header (Block 3), and Trailer (Block 5).
7//!
8//! ## Block Structure
9//! - **Block 1**: Basic Header - Sender identification and routing information
10//! - **Block 2**: Application Header - Message type and delivery information
11//! - **Block 3**: User Header - Optional user-defined fields and references
12//! - **Block 5**: Trailer - Optional authentication and delivery confirmation
13//!
14//! ## Features
15//! - **Complete SWIFT Compliance**: Follows SWIFT User Handbook specifications
16//! - **Type-Safe Parsing**: Strongly-typed header structures with validation
17//! - **Authentication Support**: MAC and authentication key handling
18//! - **Sample Generation**: Realistic header generation for testing
19//! - **Network Validation**: BIC validation and routing verification
20
21use crate::errors::{ParseError, Result};
22use serde::{Deserialize, Serialize};
23
24/// **Basic Header (Block 1): SWIFT Message Identification and Routing**
25///
26/// ## Purpose
27/// The Basic Header constitutes the first mandatory block of every SWIFT message, providing
28/// essential identification, routing, and sequencing information. This header enables the
29/// SWIFT network to authenticate the sender, route the message appropriately, and maintain
30/// message sequence integrity across the global financial messaging infrastructure.
31///
32/// ## Format Specification
33/// - **Block Format**: `{1:F01SSSSSSSSSCCC0000NNNNNN}`
34/// - **Total Length**: Exactly 25 characters
35/// - **Structure**: Application ID + Service ID + LT Address + Session + Sequence
36/// - **Character Set**: Alphanumeric, uppercase only
37///
38/// ## Business Context Applications
39/// - **Message Authentication**: Sender identification and verification
40/// - **Network Routing**: Logical terminal addressing for message delivery
41/// - **Session Management**: Message sequencing within communication sessions
42/// - **Audit Trail**: Complete message tracking and reconciliation
43///
44/// ## Component Breakdown
45/// ### Application Identifier (1 character)
46/// - **F**: FIN application (Financial messages)
47/// - **A**: GPA application (General Purpose Application)
48/// - **L**: GPA application (for certain message types)
49/// - **Usage**: Determines message processing rules and validation
50///
51/// ### Service Identifier (2 characters)
52/// - **01**: FIN service (standard financial messages)
53/// - **03**: FIN Copy service (third-party copy)
54/// - **05**: GPA service
55/// - **21**: ACK/NAK service
56///
57/// ### Logical Terminal Address (12 characters)
58/// - **BIC Code**: First 8 characters (institution identifier)
59/// - **Terminal Code**: Next 1 character (logical terminal)
60/// - **Branch Code**: Last 3 characters (XXX for head office)
61/// - **Format**: Must be valid SWIFT-connected BIC
62///
63/// ## Network Validation Requirements
64/// - **BIC Validation**: Must be active SWIFT participant
65/// - **Service Compatibility**: Service ID must match message type
66/// - **Session Validity**: Session number must be within valid range
67/// - **Sequence Continuity**: Sequence numbers must be sequential
68///
69/// ## Session and Sequence Management
70/// ### Session Number (4 digits)
71/// - **Range**: 0000-9999
72/// - **Purpose**: Groups related messages within a session
73/// - **Reset**: Can be reset based on bilateral agreement
74/// - **Tracking**: Used for message reconciliation
75///
76/// ### Sequence Number (6 digits)
77/// - **Range**: 000000-999999
78/// - **Increment**: Sequential within session
79/// - **Uniqueness**: Combined with session ensures message uniqueness
80/// - **Recovery**: Critical for message recovery procedures
81///
82/// ## Regional Considerations
83/// - **Time Zones**: LT address determines processing time zone
84/// - **Operating Hours**: Service availability based on regional center
85/// - **Holiday Schedules**: Regional holiday impacts on processing
86/// - **Regulatory Compliance**: Regional reporting requirements
87///
88/// ## Error Prevention Guidelines
89/// - **BIC Accuracy**: Verify sender BIC is authorized for service
90/// - **Sequence Management**: Maintain strict sequence number control
91/// - **Session Coordination**: Coordinate session numbers with correspondent
92/// - **Format Compliance**: Ensure exact 25-character length
93///
94/// ## Security and Authentication
95/// - **Sender Authentication**: BIC must match authenticated connection
96/// - **Message Integrity**: Header contributes to message MAC calculation
97/// - **Non-repudiation**: Sender identification cannot be disputed
98/// - **Audit Trail**: Complete tracking from sender to receiver
99///
100/// ## Integration with Other Blocks
101/// - **Block 2**: Message type and routing continuation
102/// - **Block 3**: Optional user header for enhanced services
103/// - **Block 4**: Message text validated based on Block 1 service
104/// - **Block 5**: Trailer with checksums and authentication
105///
106/// ## Compliance Framework
107/// - **SWIFT Standards**: Full compliance with FIN interface standards
108/// - **Service Level Agreements**: Performance guarantees based on service
109/// - **Regulatory Reporting**: Sender identification for compliance
110/// - **Audit Requirements**: Complete message trail maintenance
111///
112/// ## Best Practices
113/// - **BIC Management**: Keep BIC directory updated
114/// - **Sequence Control**: Implement robust sequence management
115/// - **Session Planning**: Plan session number allocation
116/// - **Error Recovery**: Implement sequence gap detection
117///
118/// ## See Also
119/// - SWIFT FIN Interface Standards: Block 1 Specifications
120/// - BIC Directory: Valid SWIFT Participant Codes
121/// - Session Management Guide: Best Practices
122/// - Message Sequencing: Control and Recovery Procedures
123#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
124pub struct BasicHeader {
125 /// Application identifier
126 ///
127 /// Format: 1!a - Single alphabetic character
128 /// Values: F (FIN), A (GPA), L (GPA legacy)
129 /// Determines message processing rules and validation requirements
130 pub application_id: String,
131
132 /// Service identifier
133 ///
134 /// Format: 2!n - Two numeric characters
135 /// Common values: 01 (FIN), 03 (FIN Copy), 05 (GPA), 21 (ACK/NAK)
136 /// Specifies the SWIFT service type for message handling
137 pub service_id: String,
138
139 /// Logical Terminal (LT) address
140 ///
141 /// Format: 12!c - 12 alphanumeric characters
142 /// Structure: 8-char BIC + 1-char terminal + 3-char branch
143 /// Uniquely identifies the sending terminal in SWIFT network
144 pub logical_terminal: String,
145
146 /// Sender BIC extracted from logical terminal
147 ///
148 /// Format: 8!c - First 8 characters of logical terminal
149 /// The sending institution's Bank Identifier Code
150 pub sender_bic: String,
151
152 /// Session number
153 ///
154 /// Format: 4!n - Four numeric digits (0000-9999)
155 /// Groups related messages within a communication session
156 /// Used for message reconciliation and recovery procedures
157 pub session_number: String,
158
159 /// Sequence number
160 ///
161 /// Format: 6!n - Six numeric digits (000000-999999)
162 /// Sequential message counter within session
163 /// Critical for message ordering and duplicate detection
164 pub sequence_number: String,
165}
166
167impl BasicHeader {
168 /// Parse basic header from block 1 string
169 pub fn parse(block1: &str) -> Result<Self> {
170 if block1.len() < 21 {
171 return Err(ParseError::InvalidBlockStructure {
172 block: "1".to_string(),
173 message: format!(
174 "Block 1 too short: expected at least 21 characters, got {}",
175 block1.len()
176 ),
177 });
178 }
179
180 let application_id = block1[0..1].to_string();
181 let service_id = block1[1..3].to_string();
182 let logical_terminal = block1[3..15].to_string();
183 let session_number = block1[15..19].to_string();
184 let sequence_number = block1[19..].to_string();
185 let sender_bic = logical_terminal[0..8].to_string();
186
187 Ok(BasicHeader {
188 application_id,
189 service_id,
190 logical_terminal,
191 sender_bic,
192 session_number,
193 sequence_number,
194 })
195 }
196}
197
198impl std::fmt::Display for BasicHeader {
199 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200 write!(
201 f,
202 "{}{}{}{}{}",
203 self.application_id,
204 self.service_id,
205 self.logical_terminal,
206 self.session_number,
207 self.sequence_number
208 )
209 }
210}
211
212/// **Input Application Header: Message Sent to SWIFT Network**
213///
214/// ## Purpose
215/// Represents messages being sent to the SWIFT network for processing and delivery.
216/// Contains routing information, priority settings, and delivery monitoring options.
217///
218/// ## Format Specification
219/// - **Format**: `{2:I103DDDDDDDDDDDDP[M][OOO]}`
220/// - **Length**: 17-21 characters
221/// - **Components**: Direction (I) + Type + Destination + Priority + Optional monitoring/obsolescence
222#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
223pub struct InputApplicationHeader {
224 /// Message type
225 ///
226 /// Format: 3!n - Three numeric digits
227 /// Examples: 103 (Customer Transfer), 202 (Bank Transfer), 940 (Statement)
228 pub message_type: String,
229
230 /// Destination address
231 ///
232 /// Format: 12!c - 12 alphanumeric characters
233 /// Structure: 8-char BIC + 1-char terminal + 3-char branch
234 pub destination_address: String,
235
236 /// Receiver BIC extracted from destination address
237 ///
238 /// Format: 8!c - First 8 characters of destination
239 pub receiver_bic: String,
240
241 /// Message priority
242 ///
243 /// Format: 1!a - Single alphabetic character
244 /// Values: U (Urgent), N (Normal), S (System)
245 pub priority: String,
246
247 /// Delivery monitoring option
248 ///
249 /// Format: 1!n - Single numeric digit
250 /// Values: 1 (Non-delivery warning), 2 (Delivery notification), 3 (Both)
251 #[serde(skip_serializing_if = "Option::is_none")]
252 pub delivery_monitoring: Option<String>,
253
254 /// Obsolescence period
255 ///
256 /// Format: 3!n - Three numeric digits
257 /// Range: 003-999 (units of 5 minutes)
258 #[serde(skip_serializing_if = "Option::is_none")]
259 pub obsolescence_period: Option<String>,
260}
261
262/// **Output Application Header: Message Delivered from SWIFT Network**
263///
264/// ## Purpose
265/// Represents messages delivered from the SWIFT network to the receiver.
266/// Contains complete Message Input Reference (MIR), timing information, and delivery confirmation.
267///
268/// ## Format Specification
269/// - **Format**: `{2:O103HHMMYYYYMMDDDDDDDDDDDDDDNNNNSSSSSSYYYYMMDDHHMMP}`
270/// - **Length**: 46-47 characters
271/// - **Components**: Direction (O) + Type + Input time + MIR + Output date/time + Priority
272#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
273pub struct OutputApplicationHeader {
274 /// Message type
275 ///
276 /// Format: 3!n - Three numeric digits
277 /// Examples: 103 (Customer Transfer), 202 (Bank Transfer), 940 (Statement)
278 pub message_type: String,
279
280 /// Input time
281 ///
282 /// Format: HHMM - The hour and minute when the sender sent the message
283 pub input_time: String,
284
285 /// MIR (Message Input Reference)
286 ///
287 /// Complete MIR structure including date, LT address, session, and sequence
288 pub mir: MessageInputReference,
289
290 /// Output date
291 ///
292 /// Format: YYMMDD - The output date local to the receiver
293 pub output_date: String,
294
295 /// Output time
296 ///
297 /// Format: HHMM - The output time local to the receiver
298 pub output_time: String,
299
300 /// Message priority
301 ///
302 /// Format: 1!a - Single alphabetic character
303 /// Values: U (Urgent), N (Normal), S (System)
304 #[serde(skip_serializing_if = "Option::is_none")]
305 pub priority: Option<String>,
306}
307
308/// **Application Header (Block 2): Message Type and Routing Information**
309///
310/// ## Purpose
311/// The Application Header provides essential message type identification and routing
312/// information, enabling the SWIFT network to properly categorize, prioritize, and
313/// deliver messages. This header determines processing rules, delivery monitoring,
314/// and time-critical handling requirements for financial messages.
315///
316/// ## Format Specification
317/// - **Input Format**: `{2:I103DDDDDDDDDDDDP[M][OOO]}`
318/// - **Output Format**: `{2:O103HHMMYYYYMMDDDDDDDDDDDDDDNNNNSSSSSSYYYYMMDDHHMMP}`
319/// - **Direction Dependent**: Structure varies for input (I) vs output (O)
320/// - **Variable Length**: 17-21 characters for input, 46-47 for output
321///
322/// ## Business Context Applications
323/// - **Message Classification**: Determines processing rules by message type
324/// - **Priority Handling**: Urgent vs normal message processing
325/// - **Delivery Assurance**: Monitoring and non-delivery notifications
326/// - **Time Management**: Obsolescence period for time-sensitive messages
327///
328/// ## Direction Indicator
329/// ### Input Messages (I)
330/// - **Sender Perspective**: Messages being sent to SWIFT
331/// - **Validation**: Full message validation applied
332/// - **Routing**: To destination specified in header
333/// - **Storage**: Stored in sender's message archive
334///
335/// ### Output Messages (O)
336/// - **Receiver Perspective**: Messages delivered from SWIFT
337/// - **Delivery**: Includes delivery timestamp and MIR
338/// - **Status**: Confirmed successful delivery
339/// - **Archive**: Stored in receiver's message archive
340///
341/// ## Message Type Classification
342/// ### Category 1: Customer Payments (MT 1nn)
343/// - **MT 103**: Single Customer Credit Transfer
344/// - **MT 110**: Advice of Cheque
345/// - **Priority**: Often urgent for same-day value
346///
347/// ### Category 2: Bank Transfers (MT 2nn)
348/// - **MT 202**: General Financial Institution Transfer
349/// - **MT 202COV**: Cover Payment
350/// - **Priority**: High priority for bank liquidity
351///
352/// ### Category 9: Balance and Status (MT 9nn)
353/// - **MT 940**: Customer Statement
354/// - **MT 950**: Statement Message
355/// - **Priority**: Normal, end-of-day processing
356///
357/// ## Priority Management
358/// ### Urgent Priority (U)
359/// - **Processing**: Immediate, ahead of normal messages
360/// - **Use Cases**: Time-critical payments, cut-off deadlines
361/// - **Delivery**: Fastest available route
362/// - **Cost**: Premium pricing may apply
363///
364/// ### Normal Priority (N)
365/// - **Processing**: Standard queue processing
366/// - **Use Cases**: Regular payments and messages
367/// - **Delivery**: Standard delivery timeframes
368/// - **Cost**: Standard message pricing
369///
370/// ### System Priority (S)
371/// - **Processing**: System-generated messages
372/// - **Use Cases**: ACKs, NAKs, system notifications
373/// - **Delivery**: Highest priority delivery
374/// - **Access**: Reserved for SWIFT system use
375///
376/// ## Delivery Monitoring Options
377/// ### Non-Delivery Warning (1)
378/// - **Timeout**: Warning if not delivered within set time
379/// - **Action**: Sender notified of delivery delay
380/// - **Use Case**: Important but not critical messages
381///
382/// ### Delivery Notification (3)
383/// - **Confirmation**: Positive delivery confirmation required
384/// - **Notification**: MT 011 sent upon successful delivery
385/// - **Use Case**: Critical messages requiring confirmation
386///
387/// ### No Monitoring (blank)
388/// - **Standard**: Default delivery without monitoring
389/// - **Notification**: No delivery status updates
390/// - **Use Case**: Routine, non-critical messages
391///
392/// ## Obsolescence Period
393/// - **Format**: 3 numeric digits (003-999)
394/// - **Unit**: 5-minute intervals
395/// - **Maximum**: 999 = 83 hours
396/// - **Purpose**: Message validity timeout
397/// - **Action**: Automatic cancellation if not delivered
398///
399/// ## Network Validation Requirements
400/// - **BIC Validation**: Destination must be valid SWIFT participant
401/// - **Message Type**: Must be valid for sender's subscription
402/// - **Priority Rules**: Certain messages restricted to normal priority
403/// - **Monitoring Compatibility**: Not all messages support monitoring
404///
405/// ## Regional Considerations
406/// - **Cut-off Times**: Regional deadlines for urgent messages
407/// - **Processing Windows**: Regional operating hours impact
408/// - **Holiday Handling**: Regional holidays affect delivery
409/// - **Regulatory Priority**: Some regions mandate priority levels
410///
411/// ## Error Prevention Guidelines
412/// - **BIC Verification**: Confirm destination BIC is reachable
413/// - **Type Validation**: Ensure message type is authorized
414/// - **Priority Selection**: Use appropriate priority level
415/// - **Monitoring Choice**: Select monitoring based on criticality
416///
417/// ## Integration with Other Blocks
418/// - **Block 1**: Sender identification coordination
419/// - **Block 3**: Service options based on message type
420/// - **Block 4**: Content validation per message type
421/// - **Block 5**: Delivery confirmations and status
422///
423/// ## Compliance Framework
424/// - **Message Standards**: Type-specific validation rules
425/// - **Priority Policies**: Fair use of urgent priority
426/// - **Delivery SLAs**: Service level compliance
427/// - **Audit Trail**: Complete routing documentation
428///
429/// ## Processing Impact
430/// - **Queue Position**: Priority determines processing order
431/// - **Validation Depth**: Message type determines checks
432/// - **Routing Path**: Optimal path based on priority
433/// - **Cost Calculation**: Priority affects message pricing
434///
435/// ## Best Practices
436/// - **Priority Discipline**: Reserve urgent for true urgency
437/// - **Monitoring Selection**: Match monitoring to risk level
438/// - **Type Accuracy**: Ensure correct message type selection
439/// - **Destination Validation**: Verify BIC before sending
440///
441/// ## See Also
442/// - SWIFT FIN User Handbook: Block 2 Specifications
443/// - Message Type Catalog: Complete MT Message List
444/// - Priority Guidelines: Best Practices for Priority Selection
445/// - Delivery Monitoring: Service Options and Usage
446#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
447#[serde(tag = "direction")]
448pub enum ApplicationHeader {
449 /// Input message sent to SWIFT network
450 #[serde(rename = "I")]
451 Input(InputApplicationHeader),
452
453 /// Output message delivered from SWIFT network
454 #[serde(rename = "O")]
455 Output(OutputApplicationHeader),
456}
457
458impl ApplicationHeader {
459 /// Parse application header from block 2 string
460 pub fn parse(block2: &str) -> Result<Self> {
461 if block2.len() < 4 {
462 return Err(ParseError::InvalidBlockStructure {
463 block: "2".to_string(),
464 message: format!(
465 "Block 2 too short: expected at least 4 characters, got {}",
466 block2.len()
467 ),
468 });
469 }
470
471 let direction = &block2[0..1];
472 let message_type = block2[1..4].to_string();
473
474 match direction {
475 "I" => {
476 // Input format: {2:I103DDDDDDDDDDDDP[M][OOO]}
477 // I + 103 + 12-char destination + priority + optional monitoring + optional obsolescence
478 if block2.len() < 17 {
479 return Err(ParseError::InvalidBlockStructure {
480 block: "2".to_string(),
481 message: format!(
482 "Input Block 2 too short: expected at least 17 characters, got {}",
483 block2.len()
484 ),
485 });
486 }
487
488 let destination_address = block2[4..16].to_string();
489 let priority = block2[16..17].to_string();
490 let receiver_bic = destination_address[0..8].to_string();
491
492 let delivery_monitoring = if block2.len() >= 18 {
493 Some(block2[17..18].to_string())
494 } else {
495 None
496 };
497
498 let obsolescence_period = if block2.len() >= 21 {
499 Some(block2[18..21].to_string())
500 } else {
501 None
502 };
503
504 Ok(ApplicationHeader::Input(InputApplicationHeader {
505 message_type,
506 destination_address,
507 receiver_bic,
508 priority,
509 delivery_monitoring,
510 obsolescence_period,
511 }))
512 }
513 "O" => {
514 // Output format: {2:O103HHMMYYYYMMDDDDDDDDDDDDDDNNNNSSSSSSYYYYMMDDHHMMP}
515 // Components:
516 // O (1) + message_type (3) + input_time (4) + mir (28) + output_date (6) + output_time (4) + priority (1)
517 // MIR consists of: date (6) + lt_address (12) + session (4) + sequence (6) = 28 chars
518 // Total: 1 + 3 + 4 + 28 + 6 + 4 = 46 characters minimum (priority optional)
519
520 if block2.len() < 46 {
521 return Err(ParseError::InvalidBlockStructure {
522 block: "2".to_string(),
523 message: format!(
524 "Output Block 2 too short: expected at least 46 characters, got {}",
525 block2.len()
526 ),
527 });
528 }
529
530 // Parse Output format components according to SWIFT specification:
531 let input_time = block2[4..8].to_string(); // HHMM
532
533 // MIR (Message Input Reference) components
534 let mir_date = block2[8..14].to_string(); // YYMMDD
535 let mir_lt_address = block2[14..26].to_string(); // 12 characters (BIC8 + LT + Branch)
536 let mir_session = block2[26..30].to_string(); // 4 digits
537 let mir_sequence = block2[30..36].to_string(); // 6 digits
538
539 let output_date = block2[36..42].to_string(); // YYMMDD
540 let output_time = block2[42..46].to_string(); // HHMM
541
542 let priority = if block2.len() >= 47 {
543 Some(block2[46..47].to_string())
544 } else {
545 None
546 };
547
548 // Create MIR structure
549 let mir = MessageInputReference {
550 date: mir_date,
551 lt_identifier: mir_lt_address.clone(),
552 branch_code: if mir_lt_address.len() >= 12 {
553 mir_lt_address[9..12].to_string()
554 } else {
555 "XXX".to_string()
556 },
557 session_number: mir_session,
558 sequence_number: mir_sequence,
559 };
560
561 Ok(ApplicationHeader::Output(OutputApplicationHeader {
562 message_type,
563 input_time,
564 mir,
565 output_date,
566 output_time,
567 priority,
568 }))
569 }
570 _ => Err(ParseError::InvalidBlockStructure {
571 block: "2".to_string(),
572 message: format!(
573 "Invalid direction indicator: expected 'I' or 'O', got '{}'",
574 direction
575 ),
576 }),
577 }
578 }
579
580 /// Get the message type regardless of direction
581 pub fn message_type(&self) -> &str {
582 match self {
583 ApplicationHeader::Input(header) => &header.message_type,
584 ApplicationHeader::Output(header) => &header.message_type,
585 }
586 }
587
588 /// Get the priority if available
589 pub fn priority(&self) -> Option<&str> {
590 match self {
591 ApplicationHeader::Input(header) => Some(&header.priority),
592 ApplicationHeader::Output(header) => header.priority.as_deref(),
593 }
594 }
595}
596
597impl std::fmt::Display for ApplicationHeader {
598 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
599 match self {
600 ApplicationHeader::Input(header) => {
601 // Input format: I + message_type + destination_address + priority [+ monitoring] [+ obsolescence]
602 let mut result = format!(
603 "I{}{}{}",
604 header.message_type, header.destination_address, header.priority
605 );
606
607 if let Some(ref delivery_monitoring) = header.delivery_monitoring {
608 result.push_str(delivery_monitoring);
609 }
610
611 if let Some(ref obsolescence_period) = header.obsolescence_period {
612 result.push_str(obsolescence_period);
613 }
614
615 write!(f, "{result}")
616 }
617 ApplicationHeader::Output(header) => {
618 // Output format: O + message_type + input_time + MIR + output_date + output_time + priority
619 // MIR = date + lt_address + session + sequence
620 let mut result = format!(
621 "O{}{}{}{}{}{}{}{}",
622 header.message_type,
623 header.input_time,
624 header.mir.date,
625 header.mir.lt_identifier,
626 header.mir.session_number,
627 header.mir.sequence_number,
628 header.output_date,
629 header.output_time,
630 );
631
632 if let Some(ref priority) = header.priority {
633 result.push_str(priority);
634 }
635
636 write!(f, "{result}")
637 }
638 }
639 }
640}
641
642impl std::fmt::Display for InputApplicationHeader {
643 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
644 let mut result = format!(
645 "I{}{}{}",
646 self.message_type, self.destination_address, self.priority
647 );
648
649 if let Some(ref delivery_monitoring) = self.delivery_monitoring {
650 result.push_str(delivery_monitoring);
651 }
652
653 if let Some(ref obsolescence_period) = self.obsolescence_period {
654 result.push_str(obsolescence_period);
655 }
656
657 write!(f, "{result}")
658 }
659}
660
661impl std::fmt::Display for OutputApplicationHeader {
662 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
663 let mut result = format!(
664 "O{}{}{}{}{}{}{}{}",
665 self.message_type,
666 self.input_time,
667 self.mir.date,
668 self.mir.lt_identifier,
669 self.mir.session_number,
670 self.mir.sequence_number,
671 self.output_date,
672 self.output_time,
673 );
674
675 if let Some(ref priority) = self.priority {
676 result.push_str(priority);
677 }
678
679 write!(f, "{result}")
680 }
681}
682
683/// **User Header (Block 3): Extended Service Options and Controls**
684///
685/// ## Purpose
686/// The User Header provides optional extended functionality for SWIFT messages,
687/// enabling advanced services, enhanced straight-through processing (STP),
688/// compliance controls, and end-to-end transaction tracking. This header has
689/// become increasingly important for regulatory compliance, particularly with
690/// SWIFT gpi tracking and enhanced payment transparency requirements.
691///
692/// ## Format Specification
693/// - **Block Format**: `{3:{tag:value}{tag:value}...}`
694/// - **Tag Format**: Numeric tags with structured values
695/// - **Nesting**: Tags enclosed in curly braces
696/// - **Optional Block**: Entire block 3 may be omitted
697///
698/// ## Business Context Applications
699/// - **SWIFT gpi Tracking**: End-to-end payment tracking via UETR
700/// - **STP Enhancement**: Validation flags for automated processing
701/// - **Regulatory Compliance**: Sanctions screening and payment controls
702/// - **Service Enhancement**: Additional service options and features
703///
704/// ## Critical Tags for Modern Payments
705/// ### Tag 121: Unique End-to-End Transaction Reference (UETR)
706/// - **Format**: 36 characters UUID (8-4-4-4-12 format)
707/// - **Purpose**: Global payment tracking across entire payment chain
708/// - **Mandatory**: Required for SWIFT gpi participant banks
709/// - **Persistence**: Must remain unchanged through payment lifecycle
710///
711/// ### Tag 119: Validation Flag
712/// - **STP**: Straight-Through Processing capability
713/// - **REMIT**: Remittance information present
714/// - **COV**: Cover payment indicator
715/// - **RFDD**: Request for Direct Debit
716///
717/// ## Service Identifiers
718/// ### Tag 103: Service Identifier
719/// - **Purpose**: Identifies specific SWIFT services
720/// - **Values**: Service-specific codes (e.g., "FIN")
721/// - **Usage**: Mainly for FIN Copy service
722/// - **Processing**: Affects message routing and copying
723///
724/// ### Tag 111: Service Type Identifier
725/// - **Format**: 3 numeric digits
726/// - **Purpose**: Sub-categorizes service types
727/// - **Common**: "001" for standard processing
728/// - **Impact**: May affect fee calculation
729///
730/// ## Message Control and Reference
731/// ### Tag 108: Message User Reference (MUR)
732/// - **Format**: Up to 16 characters
733/// - **Purpose**: Sender's unique reference
734/// - **Usage**: Transaction tracking and reconciliation
735/// - **Uniqueness**: Should be unique per sender
736///
737/// ### Tag 113: Banking Priority
738/// - **Format**: 4 characters
739/// - **Values**: NORM, HIGH, URGP
740/// - **Purpose**: Internal bank priority handling
741/// - **Note**: Different from network priority
742///
743/// ## Compliance and Screening Tags
744/// ### Tag 433: Sanctions Screening Information
745/// - **AOK**: All OK - Passed screening
746/// - **FPO**: False Positive Override
747/// - **NOK**: Not OK - Requires review
748/// - **Additional**: Optional 20 character details
749///
750/// ### Tag 434: Payment Controls Information
751/// - **Format**: 3-letter code + optional details
752/// - **Purpose**: Payment control status
753/// - **Usage**: Compliance and regulatory controls
754/// - **Processing**: May trigger manual review
755///
756/// ## FIN Copy Service Tags
757/// ### Tag 115: Addressee Information
758/// - **Format**: Up to 32 characters
759/// - **Purpose**: Third-party copy recipient
760/// - **Service**: FIN Copy service only
761/// - **Delivery**: Additional message copy sent
762///
763/// ### Tag 165: Payment Release Information
764/// - **Format**: 3-char code + optional 34 chars
765/// - **Service**: FINInform service
766/// - **Purpose**: Payment release notifications
767/// - **Usage**: Corporate payment factories
768///
769/// ## Message Recovery Tags (MIRS)
770/// ### Tag 106: Message Input Reference (MIR)
771/// - **Format**: 28 characters structured
772/// - **Components**: Date + LT + Session + Sequence
773/// - **Purpose**: Original message reference
774/// - **Usage**: Message recovery and reconciliation
775///
776/// ### Tag 423: Balance Checkpoint
777/// - **Format**: YYMMDDHHMMSS\[ss\]
778/// - **Purpose**: Balance snapshot timing
779/// - **Service**: MIRS recovery service
780/// - **Precision**: Optional hundredths of second
781///
782/// ### Tag 424: Related Reference
783/// - **Format**: Up to 16 characters
784/// - **Purpose**: Links related messages
785/// - **Usage**: Message chains and corrections
786/// - **Service**: MIRS functionality
787///
788/// ## Network Validation Requirements
789/// - **Tag Compatibility**: Some tags require specific services
790/// - **Value Validation**: Each tag has specific format rules
791/// - **Service Subscription**: Tags available per service agreement
792/// - **Mandatory Combinations**: Some tags require others
793///
794/// ## Regional and Regulatory Impact
795/// - **SWIFT gpi**: Tag 121 mandatory for participants
796/// - **EU Regulations**: Enhanced screening requirements
797/// - **US Compliance**: Specific control requirements
798/// - **Local Rules**: Additional regional tag usage
799///
800/// ## STP Processing Impact
801/// ### Validation Flag Effects
802/// - **STP Flag**: Enables full automation
803/// - **Format Restrictions**: Stricter field validation
804/// - **Character Sets**: Limited to STP-safe characters
805/// - **Processing Speed**: Faster automated handling
806///
807/// ## Error Prevention Guidelines
808/// - **UETR Format**: Ensure valid UUID format
809/// - **Service Compatibility**: Verify tag availability
810/// - **Value Formats**: Follow exact specifications
811/// - **Mandatory Rules**: Include required combinations
812///
813/// ## Integration with Other Blocks
814/// - **Block 1**: Service must match subscription
815/// - **Block 2**: Message type affects available tags
816/// - **Block 4**: Validation flags affect field rules
817/// - **Block 5**: Some tags reflected in trailer
818///
819/// ## Compliance Framework
820/// - **Regulatory Mandates**: Screening and control requirements
821/// - **Audit Trail**: Enhanced tracking via UETR
822/// - **Service Agreements**: Tag usage per agreement
823/// - **Privacy Rules**: Data handling requirements
824///
825/// ## Best Practices
826/// - **UETR Generation**: Use proper UUID libraries
827/// - **Reference Uniqueness**: Ensure MUR uniqueness
828/// - **Screening Accuracy**: Accurate screening codes
829/// - **Service Alignment**: Use appropriate service tags
830///
831/// ## Future Evolution
832/// - **ISO 20022 Alignment**: Mapping considerations
833/// - **Enhanced Tracking**: Additional tracking features
834/// - **Compliance Evolution**: New regulatory tags
835/// - **Service Innovation**: New service identifiers
836///
837/// ## See Also
838/// - SWIFT FIN User Handbook: Block 3 Tag Specifications
839/// - SWIFT gpi Standards: UETR Implementation Guide
840/// - STP Guidelines: Validation Flag Requirements
841/// - Compliance Framework: Screening Tag Usage
842#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
843pub struct UserHeader {
844 /// Tag 103 - Service Identifier (3!a) - Mandatory for FINcopy Service
845 #[serde(skip_serializing_if = "Option::is_none")]
846 pub service_identifier: Option<String>,
847
848 /// Tag 113 - Banking Priority (4!x) - Optional
849 #[serde(skip_serializing_if = "Option::is_none")]
850 pub banking_priority: Option<String>,
851
852 /// Tag 108 - Message User Reference (16!x) - Optional
853 #[serde(skip_serializing_if = "Option::is_none")]
854 pub message_user_reference: Option<String>,
855
856 /// Tag 119 - Validation Flag (8c) - Optional (STP, REMIT, RFDD, COV)
857 #[serde(skip_serializing_if = "Option::is_none")]
858 pub validation_flag: Option<String>,
859
860 /// Tag 423 - Balance checkpoint date and time (YYMMDDHHMMSS\[ss\]) - Optional (MIRS only)
861 #[serde(skip_serializing_if = "Option::is_none")]
862 pub balance_checkpoint: Option<BalanceCheckpoint>,
863
864 /// Tag 106 - Message Input Reference MIR (28c) - Optional (MIRS only)
865 #[serde(skip_serializing_if = "Option::is_none")]
866 pub message_input_reference: Option<MessageInputReference>,
867
868 /// Tag 424 - Related reference (16x) - Optional (MIRS only)
869 #[serde(skip_serializing_if = "Option::is_none")]
870 pub related_reference: Option<String>,
871
872 /// Tag 111 - Service type identifier (3!n) - Optional
873 #[serde(skip_serializing_if = "Option::is_none")]
874 pub service_type_identifier: Option<String>,
875
876 /// Tag 121 - Unique end-to-end transaction reference (UUID format) - Mandatory for GPI
877 #[serde(skip_serializing_if = "Option::is_none")]
878 pub unique_end_to_end_reference: Option<String>,
879
880 /// Tag 115 - Addressee Information (32x) - Optional (FINCopy only)
881 #[serde(skip_serializing_if = "Option::is_none")]
882 pub addressee_information: Option<String>,
883
884 /// Tag 165 - Payment release information receiver (3!c/34x) - Optional (FINInform only)
885 #[serde(skip_serializing_if = "Option::is_none")]
886 pub payment_release_information: Option<PaymentReleaseInfo>,
887
888 /// Tag 433 - Sanctions screening information (3!a/\[20x\]) - Optional
889 #[serde(skip_serializing_if = "Option::is_none")]
890 pub sanctions_screening_info: Option<SanctionsScreeningInfo>,
891
892 /// Tag 434 - Payment controls information (3!a/\[20x\]) - Optional
893 #[serde(skip_serializing_if = "Option::is_none")]
894 pub payment_controls_info: Option<PaymentControlsInfo>,
895}
896
897/// Balance checkpoint structure for Tag 423
898#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
899pub struct BalanceCheckpoint {
900 pub date: String, // YYMMDD
901 pub time: String, // HHMMSS
902
903 #[serde(skip_serializing_if = "Option::is_none")]
904 pub hundredths_of_second: Option<String>, // ss (optional)
905}
906
907/// Message Input Reference structure for Tag 106
908#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
909pub struct MessageInputReference {
910 pub date: String, // YYMMDD
911 pub lt_identifier: String, // 12 characters
912 pub branch_code: String, // 3!c
913 pub session_number: String, // 4!n
914 pub sequence_number: String, // 6!n
915}
916
917/// Payment release information structure for Tag 165
918#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
919pub struct PaymentReleaseInfo {
920 pub code: String, // 3!c
921
922 #[serde(skip_serializing_if = "Option::is_none")]
923 pub additional_info: Option<String>, // 34x (optional)
924}
925
926/// Sanctions screening information structure for Tag 433
927#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
928pub struct SanctionsScreeningInfo {
929 pub code_word: String, // 3!a (AOK, FPO, NOK)
930
931 #[serde(skip_serializing_if = "Option::is_none")]
932 pub additional_info: Option<String>, // 20x (optional)
933}
934
935/// Payment controls information structure for Tag 434
936#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
937pub struct PaymentControlsInfo {
938 pub code_word: String, // 3!a
939
940 #[serde(skip_serializing_if = "Option::is_none")]
941 pub additional_info: Option<String>, // 20x (optional)
942}
943
944impl UserHeader {
945 /// Parse user header from block 3 string using structured parsing
946 pub fn parse(block3: &str) -> Result<Self> {
947 let mut user_header = UserHeader::default();
948
949 // Parse nested tags in format {tag:value}
950 // Simple parsing for now - more sophisticated regex parsing can be added later
951 if block3.contains("{103:")
952 && let Some(start) = block3.find("{103:")
953 && let Some(end) = block3[start..].find('}')
954 {
955 user_header.service_identifier = Some(block3[start + 5..start + end].to_string());
956 }
957
958 if block3.contains("{113:")
959 && let Some(start) = block3.find("{113:")
960 && let Some(end) = block3[start..].find('}')
961 {
962 user_header.banking_priority = Some(block3[start + 5..start + end].to_string());
963 }
964
965 if block3.contains("{108:")
966 && let Some(start) = block3.find("{108:")
967 && let Some(end) = block3[start..].find('}')
968 {
969 user_header.message_user_reference = Some(block3[start + 5..start + end].to_string());
970 }
971
972 if block3.contains("{119:")
973 && let Some(start) = block3.find("{119:")
974 && let Some(end) = block3[start..].find('}')
975 {
976 user_header.validation_flag = Some(block3[start + 5..start + end].to_string());
977 }
978
979 if block3.contains("{423:")
980 && let Some(start) = block3.find("{423:")
981 && let Some(end) = block3[start..].find('}')
982 {
983 let value = &block3[start + 5..start + end];
984 user_header.balance_checkpoint = Self::parse_balance_checkpoint(value);
985 }
986
987 if block3.contains("{106:")
988 && let Some(start) = block3.find("{106:")
989 && let Some(end) = block3[start..].find('}')
990 {
991 let value = &block3[start + 5..start + end];
992 user_header.message_input_reference = Self::parse_message_input_reference(value);
993 }
994
995 if block3.contains("{424:")
996 && let Some(start) = block3.find("{424:")
997 && let Some(end) = block3[start..].find('}')
998 {
999 user_header.related_reference = Some(block3[start + 5..start + end].to_string());
1000 }
1001
1002 if block3.contains("{111:")
1003 && let Some(start) = block3.find("{111:")
1004 && let Some(end) = block3[start..].find('}')
1005 {
1006 user_header.service_type_identifier = Some(block3[start + 5..start + end].to_string());
1007 }
1008
1009 if block3.contains("{121:")
1010 && let Some(start) = block3.find("{121:")
1011 && let Some(end) = block3[start..].find('}')
1012 {
1013 user_header.unique_end_to_end_reference =
1014 Some(block3[start + 5..start + end].to_string());
1015 }
1016
1017 if block3.contains("{115:")
1018 && let Some(start) = block3.find("{115:")
1019 && let Some(end) = block3[start..].find('}')
1020 {
1021 user_header.addressee_information = Some(block3[start + 5..start + end].to_string());
1022 }
1023
1024 if block3.contains("{165:")
1025 && let Some(start) = block3.find("{165:")
1026 && let Some(end) = block3[start..].find('}')
1027 {
1028 let value = &block3[start + 5..start + end];
1029 user_header.payment_release_information = Self::parse_payment_release_info(value);
1030 }
1031
1032 if block3.contains("{433:")
1033 && let Some(start) = block3.find("{433:")
1034 && let Some(end) = block3[start..].find('}')
1035 {
1036 let value = &block3[start + 5..start + end];
1037 user_header.sanctions_screening_info = Self::parse_sanctions_screening_info(value);
1038 }
1039
1040 if block3.contains("{434:")
1041 && let Some(start) = block3.find("{434:")
1042 && let Some(end) = block3[start..].find('}')
1043 {
1044 let value = &block3[start + 5..start + end];
1045 user_header.payment_controls_info = Self::parse_payment_controls_info(value);
1046 }
1047
1048 Ok(user_header)
1049 }
1050
1051 /// Parse balance checkpoint from tag value
1052 fn parse_balance_checkpoint(value: &str) -> Option<BalanceCheckpoint> {
1053 if value.len() >= 12 {
1054 Some(BalanceCheckpoint {
1055 date: value[0..6].to_string(),
1056 time: value[6..12].to_string(),
1057 hundredths_of_second: if value.len() > 12 {
1058 Some(value[12..].to_string())
1059 } else {
1060 None
1061 },
1062 })
1063 } else {
1064 None
1065 }
1066 }
1067
1068 /// Parse message input reference from tag value
1069 fn parse_message_input_reference(value: &str) -> Option<MessageInputReference> {
1070 if value.len() >= 28 {
1071 Some(MessageInputReference {
1072 date: value[0..6].to_string(),
1073 lt_identifier: value[6..18].to_string(),
1074 branch_code: value[18..21].to_string(),
1075 session_number: value[21..25].to_string(),
1076 sequence_number: value[25..].to_string(),
1077 })
1078 } else {
1079 None
1080 }
1081 }
1082
1083 /// Parse payment release info from tag value
1084 fn parse_payment_release_info(value: &str) -> Option<PaymentReleaseInfo> {
1085 if value.len() >= 3 {
1086 let code = value[0..3].to_string();
1087 let additional_info = if value.len() > 4 && value.chars().nth(3) == Some('/') {
1088 Some(value[4..].to_string())
1089 } else {
1090 None
1091 };
1092 Some(PaymentReleaseInfo {
1093 code,
1094 additional_info,
1095 })
1096 } else {
1097 None
1098 }
1099 }
1100
1101 /// Parse sanctions screening info from tag value
1102 fn parse_sanctions_screening_info(value: &str) -> Option<SanctionsScreeningInfo> {
1103 if value.len() >= 3 {
1104 let code_word = value[0..3].to_string();
1105 let additional_info = if value.len() > 4 && value.chars().nth(3) == Some('/') {
1106 Some(value[4..].to_string())
1107 } else {
1108 None
1109 };
1110 Some(SanctionsScreeningInfo {
1111 code_word,
1112 additional_info,
1113 })
1114 } else {
1115 None
1116 }
1117 }
1118
1119 /// Parse payment controls info from tag value
1120 fn parse_payment_controls_info(value: &str) -> Option<PaymentControlsInfo> {
1121 if value.len() >= 3 {
1122 let code_word = value[0..3].to_string();
1123 let additional_info = if value.len() > 4 && value.chars().nth(3) == Some('/') {
1124 Some(value[4..].to_string())
1125 } else {
1126 None
1127 };
1128 Some(PaymentControlsInfo {
1129 code_word,
1130 additional_info,
1131 })
1132 } else {
1133 None
1134 }
1135 }
1136}
1137
1138impl std::fmt::Display for UserHeader {
1139 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1140 let mut result = String::new();
1141
1142 if let Some(ref service_id) = self.service_identifier {
1143 result.push_str(&format!("{{103:{service_id}}}"));
1144 }
1145
1146 if let Some(ref banking_priority) = self.banking_priority {
1147 result.push_str(&format!("{{113:{banking_priority}}}"));
1148 }
1149
1150 if let Some(ref message_user_ref) = self.message_user_reference {
1151 result.push_str(&format!("{{108:{message_user_ref}}}"));
1152 }
1153
1154 if let Some(ref validation_flag) = self.validation_flag {
1155 result.push_str(&format!("{{119:{validation_flag}}}"));
1156 }
1157
1158 if let Some(ref unique_end_to_end_ref) = self.unique_end_to_end_reference {
1159 result.push_str(&format!("{{121:{unique_end_to_end_ref}}}"));
1160 }
1161
1162 if let Some(ref service_type_id) = self.service_type_identifier {
1163 result.push_str(&format!("{{111:{service_type_id}}}"));
1164 }
1165
1166 if let Some(ref payment_controls) = self.payment_controls_info {
1167 let mut value = payment_controls.code_word.clone();
1168 if let Some(ref additional) = payment_controls.additional_info {
1169 value.push('/');
1170 value.push_str(additional);
1171 }
1172 result.push_str(&format!("{{434:{value}}}"));
1173 }
1174
1175 if let Some(ref payment_release) = self.payment_release_information {
1176 let mut value = payment_release.code.clone();
1177 if let Some(ref additional) = payment_release.additional_info {
1178 value.push('/');
1179 value.push_str(additional);
1180 }
1181 result.push_str(&format!("{{165:{value}}}"));
1182 }
1183
1184 if let Some(ref sanctions) = self.sanctions_screening_info {
1185 let mut value = sanctions.code_word.clone();
1186 if let Some(ref additional) = sanctions.additional_info {
1187 value.push('/');
1188 value.push_str(additional);
1189 }
1190 result.push_str(&format!("{{433:{value}}}"));
1191 }
1192
1193 write!(f, "{result}")
1194 }
1195}
1196
1197/// **Trailer (Block 5): Message Security and Control Information**
1198///
1199/// ## Purpose
1200/// The Trailer block provides essential security, authentication, and control
1201/// information for SWIFT messages. It ensures message integrity through checksums,
1202/// enables duplicate detection, supports message authentication, and provides
1203/// system-level control information critical for secure and reliable message delivery.
1204///
1205/// ## Format Specification
1206/// - **Block Format**: `{5:{tag:value}{tag:value}...}`
1207/// - **Tag Types**: Three-letter tags with optional values
1208/// - **Security Tags**: MAC and CHK for authentication
1209/// - **Control Tags**: Various operational controls
1210///
1211/// ## Business Context Applications
1212/// - **Message Integrity**: Checksum validation for data integrity
1213/// - **Security Authentication**: MAC for message authentication
1214/// - **Duplicate Detection**: Prevention of duplicate processing
1215/// - **Operational Control**: Test messages and system controls
1216///
1217/// ## Security and Authentication Tags
1218/// ### CHK: Checksum (Mandatory)
1219/// - **Format**: 12 hexadecimal characters
1220/// - **Calculation**: Algorithm-based on message content
1221/// - **Purpose**: Detect transmission errors
1222/// - **Validation**: Automatic by SWIFT network
1223/// - **Failure Action**: Message rejection
1224///
1225/// ### MAC: Message Authentication Code
1226/// - **Format**: Variable length hexadecimal
1227/// - **Algorithm**: Agreed between parties
1228/// - **Purpose**: Authenticate message origin
1229/// - **Usage**: High-value or sensitive messages
1230/// - **Bilateral**: Requires key exchange
1231///
1232/// ## Duplicate Control Tags
1233/// ### PDM: Possible Duplicate Message
1234/// - **Format**: Optional time + MOR
1235/// - **Purpose**: Warn of possible duplicate
1236/// - **Action**: Receiver should check for duplicates
1237/// - **Components**: Time (HHMM) + Message Output Reference
1238///
1239/// ### PDE: Possible Duplicate Emission
1240/// - **Format**: Optional time + MIR
1241/// - **Purpose**: Sender suspects duplicate sent
1242/// - **Usage**: Network recovery scenarios
1243/// - **Components**: Time (HHMM) + Message Input Reference
1244///
1245/// ## Operational Control Tags
1246/// ### TNG: Test and Training
1247/// - **Format**: Empty tag (presence only)
1248/// - **Purpose**: Identifies test messages
1249/// - **Processing**: Should not affect production
1250/// - **Usage**: Testing and training environments
1251/// - **Warning**: Must not be processed as live
1252///
1253/// ### DLM: Delayed Message
1254/// - **Format**: Empty tag (presence only)
1255/// - **Purpose**: Indicates delayed transmission
1256/// - **Cause**: Network issues or recovery
1257/// - **Action**: Check value dates and cut-offs
1258///
1259/// ## Reference and Tracking Tags
1260/// ### MRF: Message Reference
1261/// - **Format**: Date + Time + MIR
1262/// - **Purpose**: Reference related messages
1263/// - **Usage**: Corrections and cancellations
1264/// - **Components**: YYMMDD + HHMM + full MIR
1265///
1266/// ### SYS: System Originated Message
1267/// - **Format**: Optional time + MIR
1268/// - **Purpose**: System-generated messages
1269/// - **Examples**: Automatic responses
1270/// - **Processing**: May have special handling
1271///
1272/// ## Message Reference Structures
1273/// ### Message Input Reference (MIR)
1274/// - **Date**: YYMMDD format
1275/// - **LT Identifier**: 12-character sending LT
1276/// - **Session**: 4-digit session number
1277/// - **Sequence**: 6-digit sequence number
1278/// - **Usage**: Unique message identification
1279///
1280/// ### Message Output Reference (MOR)
1281/// - **Format**: Same structure as MIR
1282/// - **Perspective**: Receiver's reference
1283/// - **Purpose**: Delivery confirmation
1284/// - **Tracking**: End-to-end message tracking
1285///
1286/// ## Network Validation Requirements
1287/// - **CHK Mandatory**: All messages must have checksum
1288/// - **Tag Order**: Specific ordering requirements
1289/// - **Format Compliance**: Exact format specifications
1290/// - **Value Validation**: Tag-specific validations
1291///
1292/// ## Security Considerations
1293/// ### Checksum Protection
1294/// - **Coverage**: Entire message content
1295/// - **Algorithm**: SWIFT-specified calculation
1296/// - **Tampering**: Detects any modification
1297/// - **Reliability**: Very low false positive rate
1298///
1299/// ### MAC Authentication
1300/// - **Bilateral Agreement**: Key management required
1301/// - **Algorithm Choice**: Per agreement
1302/// - **Non-repudiation**: Proves message origin
1303/// - **Legal Standing**: Admissible evidence
1304///
1305/// ## Duplicate Detection Mechanisms
1306/// ### System Design
1307/// - **Detection Window**: Configurable timeframe
1308/// - **Reference Tracking**: MIR/MOR correlation
1309/// - **Recovery Support**: Post-incident reconciliation
1310/// - **Audit Trail**: Complete duplicate history
1311///
1312/// ### Processing Rules
1313/// - **PDM Messages**: Manual review recommended
1314/// - **Duplicate Window**: Typically 24-48 hours
1315/// - **Action Required**: Verify before processing
1316/// - **Documentation**: Record resolution actions
1317///
1318/// ## Operational Guidelines
1319/// ### Test Message Handling
1320/// - **TNG Identification**: Clear test marking
1321/// - **Environment Separation**: Test vs production
1322/// - **Processing Prevention**: Automatic filtering
1323/// - **Audit Exclusion**: Separate test reporting
1324///
1325/// ### Delayed Message Processing
1326/// - **DLM Recognition**: Special handling required
1327/// - **Value Date Check**: Verify still valid
1328/// - **Cut-off Impact**: May miss deadlines
1329/// - **Notification**: Alert relevant parties
1330///
1331/// ## Error Prevention Guidelines
1332/// - **CHK Calculation**: Automatic by system
1333/// - **Tag Formatting**: Follow exact specifications
1334/// - **Reference Accuracy**: Verify MIR/MOR format
1335/// - **Test Separation**: Clear test identification
1336///
1337/// ## Integration with Other Blocks
1338/// - **Block 1-4**: Content for checksum calculation
1339/// - **Block 1**: Session/sequence for references
1340/// - **Block 2**: Message type affects trailer options
1341/// - **Block 3**: Some services require specific tags
1342///
1343/// ## Compliance Framework
1344/// - **Security Standards**: Cryptographic requirements
1345/// - **Audit Requirements**: Trailer preservation
1346/// - **Legal Admissibility**: Authentication standards
1347/// - **Regulatory Compliance**: Security controls
1348///
1349/// ## Recovery and Reconciliation
1350/// ### Message Recovery
1351/// - **Reference Tracking**: Via MIR/MOR
1352/// - **Duplicate Resolution**: PDM/PDE handling
1353/// - **Audit Support**: Complete tag history
1354/// - **Dispute Resolution**: Authentication proof
1355///
1356/// ### System Recovery
1357/// - **Checkpoint References**: Recovery points
1358/// - **Sequence Verification**: Gap detection
1359/// - **Duplicate Prevention**: During recovery
1360/// - **Integrity Validation**: CHK verification
1361///
1362/// ## Best Practices
1363/// - **Security First**: Always validate CHK
1364/// - **MAC Usage**: For high-value messages
1365/// - **Duplicate Vigilance**: Check PDM warnings
1366/// - **Test Clarity**: Clearly mark test messages
1367///
1368/// ## See Also
1369/// - SWIFT FIN Security Guide: Authentication Standards
1370/// - Checksum Algorithms: Technical Specifications
1371/// - Duplicate Detection: Best Practices Guide
1372/// - MAC Implementation: Bilateral Agreement Templates
1373#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
1374pub struct Trailer {
1375 /// CHK - Checksum (12!h) - Mandatory
1376 #[serde(skip_serializing_if = "Option::is_none")]
1377 pub checksum: Option<String>,
1378
1379 /// TNG - Test & Training Message - Optional (empty tag)
1380 #[serde(skip_serializing_if = "Option::is_none")]
1381 pub test_and_training: Option<bool>,
1382
1383 /// PDE - Possible Duplicate Emission - Optional
1384 #[serde(skip_serializing_if = "Option::is_none")]
1385 pub possible_duplicate_emission: Option<PossibleDuplicateEmission>,
1386
1387 /// DLM - Delayed Message - Optional (empty tag)
1388 #[serde(skip_serializing_if = "Option::is_none")]
1389 pub delayed_message: Option<bool>,
1390
1391 /// MRF - Message Reference - Optional
1392 #[serde(skip_serializing_if = "Option::is_none")]
1393 pub message_reference: Option<MessageReference>,
1394
1395 /// PDM - Possible Duplicate Message - Optional
1396 #[serde(skip_serializing_if = "Option::is_none")]
1397 pub possible_duplicate_message: Option<PossibleDuplicateMessage>,
1398
1399 /// SYS - System Originated Message - Optional
1400 #[serde(skip_serializing_if = "Option::is_none")]
1401 pub system_originated_message: Option<SystemOriginatedMessage>,
1402
1403 /// MAC - Message Authentication Code - Optional
1404 #[serde(skip_serializing_if = "Option::is_none")]
1405 pub mac: Option<String>,
1406}
1407
1408/// Possible Duplicate Emission structure for PDE tag
1409#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1410pub struct PossibleDuplicateEmission {
1411 #[serde(skip_serializing_if = "Option::is_none")]
1412 pub time: Option<String>, // HHMM (optional)
1413
1414 #[serde(skip_serializing_if = "Option::is_none")]
1415 pub message_input_reference: Option<MessageInputReference>, // MIR (optional)
1416}
1417
1418/// Message Reference structure for MRF tag
1419#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1420pub struct MessageReference {
1421 pub date: String, // YYMMDD
1422 pub full_time: String, // HHMM
1423 pub message_input_reference: MessageInputReference, // MIR
1424}
1425
1426/// Possible Duplicate Message structure for PDM tag
1427#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1428pub struct PossibleDuplicateMessage {
1429 #[serde(skip_serializing_if = "Option::is_none")]
1430 pub time: Option<String>, // HHMM (optional)
1431
1432 #[serde(skip_serializing_if = "Option::is_none")]
1433 pub message_output_reference: Option<MessageOutputReference>, // MOR (optional)
1434}
1435
1436/// Message Output Reference structure (similar to MIR but for output)
1437#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1438pub struct MessageOutputReference {
1439 pub date: String, // YYMMDD
1440 pub lt_identifier: String, // 12 characters
1441 pub branch_code: String, // 3!c
1442 pub session_number: String, // 4!n
1443 pub sequence_number: String, // 6!n
1444}
1445
1446/// System Originated Message structure for SYS tag
1447#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1448pub struct SystemOriginatedMessage {
1449 #[serde(skip_serializing_if = "Option::is_none")]
1450 pub time: Option<String>, // HHMM (optional)
1451
1452 #[serde(skip_serializing_if = "Option::is_none")]
1453 pub message_input_reference: Option<MessageInputReference>, // MIR (optional)
1454}
1455
1456impl Trailer {
1457 /// Parse trailer from block 5 string using structured parsing
1458 pub fn parse(block5: &str) -> Result<Self> {
1459 let mut trailer = Trailer::default();
1460
1461 // Extract common tags if present
1462 if block5.contains("{CHK:")
1463 && let Some(start) = block5.find("{CHK:")
1464 && let Some(end) = block5[start..].find('}')
1465 {
1466 trailer.checksum = Some(block5[start + 5..start + end].to_string());
1467 }
1468
1469 if block5.contains("{TNG}") {
1470 trailer.test_and_training = Some(true);
1471 }
1472
1473 if block5.contains("{DLM}") {
1474 trailer.delayed_message = Some(true);
1475 }
1476
1477 if block5.contains("{MAC:")
1478 && let Some(start) = block5.find("{MAC:")
1479 && let Some(end) = block5[start..].find('}')
1480 {
1481 trailer.mac = Some(block5[start + 5..start + end].to_string());
1482 }
1483
1484 // More complex parsing for structured tags can be added here
1485 // For now, implementing basic tag extraction
1486
1487 Ok(trailer)
1488 }
1489}
1490
1491impl std::fmt::Display for Trailer {
1492 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1493 let mut result = String::new();
1494
1495 if let Some(ref checksum) = self.checksum {
1496 result.push_str(&format!("{{CHK:{checksum}}}"));
1497 }
1498
1499 if let Some(true) = self.test_and_training {
1500 result.push_str("{TNG}");
1501 }
1502
1503 if let Some(true) = self.delayed_message {
1504 result.push_str("{DLM}");
1505 }
1506
1507 if let Some(ref possible_duplicate_emission) = self.possible_duplicate_emission {
1508 result.push_str(&format!(
1509 "{{PDE:{}}}",
1510 possible_duplicate_emission.time.as_deref().unwrap_or("")
1511 ));
1512 }
1513
1514 if let Some(ref message_reference) = self.message_reference {
1515 result.push_str(&format!("{{MRF:{}}}", message_reference.date));
1516 }
1517
1518 if let Some(ref mac) = self.mac {
1519 result.push_str(&format!("{{MAC:{mac}}}"));
1520 }
1521
1522 write!(f, "{result}")
1523 }
1524}
1525
1526#[cfg(test)]
1527mod tests {
1528 use super::*;
1529
1530 #[test]
1531 fn test_application_header_input_parsing() {
1532 // Test Input message format parsing
1533 let block2 = "I103DEUTDEFFAXXXN";
1534 let header = ApplicationHeader::parse(block2).unwrap();
1535
1536 match header {
1537 ApplicationHeader::Input(input) => {
1538 assert_eq!(input.message_type, "103");
1539 assert_eq!(input.destination_address, "DEUTDEFFAXXX");
1540 assert_eq!(input.receiver_bic, "DEUTDEFF");
1541 assert_eq!(input.priority, "N");
1542 assert_eq!(input.delivery_monitoring, None);
1543 assert_eq!(input.obsolescence_period, None);
1544 }
1545 ApplicationHeader::Output(_) => panic!("Expected Input header, got Output"),
1546 }
1547 }
1548
1549 #[test]
1550 fn test_application_header_input_parsing_with_monitoring() {
1551 // Test Input message with delivery monitoring
1552 let block2 = "I103DEUTDEFFAXXXU3003";
1553 let header = ApplicationHeader::parse(block2).unwrap();
1554
1555 match header {
1556 ApplicationHeader::Input(input) => {
1557 assert_eq!(input.message_type, "103");
1558 assert_eq!(input.destination_address, "DEUTDEFFAXXX");
1559 assert_eq!(input.receiver_bic, "DEUTDEFF");
1560 assert_eq!(input.priority, "U");
1561 assert_eq!(input.delivery_monitoring, Some("3".to_string()));
1562 assert_eq!(input.obsolescence_period, Some("003".to_string()));
1563 }
1564 ApplicationHeader::Output(_) => panic!("Expected Input header, got Output"),
1565 }
1566 }
1567
1568 #[test]
1569 fn test_application_header_output_parsing() {
1570 // Test Output message format parsing - the exact case from the issue
1571 let block2 = "O1031535051028DEUTDEFFAXXX08264556280510281535N";
1572 let header = ApplicationHeader::parse(block2).unwrap();
1573
1574 match header {
1575 ApplicationHeader::Output(output) => {
1576 assert_eq!(output.message_type, "103");
1577 assert_eq!(output.input_time, "1535");
1578 assert_eq!(output.output_date, "051028");
1579 assert_eq!(output.output_time, "1535");
1580 assert_eq!(output.priority, Some("N".to_string()));
1581
1582 // Check MIR structure
1583 assert_eq!(output.mir.date, "051028");
1584 assert_eq!(output.mir.lt_identifier, "DEUTDEFFAXXX");
1585 assert_eq!(output.mir.branch_code, "XXX");
1586 assert_eq!(output.mir.session_number, "0826");
1587 assert_eq!(output.mir.sequence_number, "455628");
1588 }
1589 ApplicationHeader::Input(_) => panic!("Expected Output header, got Input"),
1590 }
1591 }
1592
1593 #[test]
1594 fn test_application_header_output_parsing_different_message_type() {
1595 // Test another Output message format
1596 let block2 = "O2021245051028CHASUS33AXXX08264556280510281245U";
1597 let header = ApplicationHeader::parse(block2).unwrap();
1598
1599 match header {
1600 ApplicationHeader::Output(output) => {
1601 assert_eq!(output.message_type, "202");
1602 assert_eq!(output.mir.lt_identifier, "CHASUS33AXXX");
1603 assert_eq!(output.priority, Some("U".to_string()));
1604 }
1605 ApplicationHeader::Input(_) => panic!("Expected Output header, got Input"),
1606 }
1607 }
1608
1609 #[test]
1610 fn test_application_header_invalid_direction() {
1611 let block2 = "X103DEUTDEFFAXXXN";
1612 let result = ApplicationHeader::parse(block2);
1613
1614 assert!(result.is_err());
1615 if let Err(ParseError::InvalidBlockStructure { message, .. }) = result {
1616 assert!(message.contains("Invalid direction indicator"));
1617 } else {
1618 panic!("Expected InvalidBlockStructure error");
1619 }
1620 }
1621
1622 #[test]
1623 fn test_application_header_input_too_short() {
1624 let block2 = "I103DEUTDEF"; // Too short for Input format
1625 let result = ApplicationHeader::parse(block2);
1626
1627 assert!(result.is_err());
1628 }
1629
1630 #[test]
1631 fn test_application_header_output_too_short() {
1632 let block2 = "O103153505102"; // Too short for Output format (13 characters)
1633 let result = ApplicationHeader::parse(block2);
1634
1635 assert!(result.is_err());
1636 if let Err(ParseError::InvalidBlockStructure { message, .. }) = result {
1637 // This will now hit the Output-specific check since initial check is for 4 chars
1638 assert!(message.contains("Output Block 2 too short: expected at least 46 characters"));
1639 } else {
1640 panic!("Expected InvalidBlockStructure error");
1641 }
1642 }
1643
1644 #[test]
1645 fn test_application_header_output_minimum_length_but_still_too_short() {
1646 // This has 17 characters so it passes initial check but fails Output-specific check
1647 let block2 = "O10315350510280DE"; // 17 characters, but Output needs 46
1648 let result = ApplicationHeader::parse(block2);
1649
1650 assert!(result.is_err());
1651 if let Err(ParseError::InvalidBlockStructure { message, .. }) = result {
1652 assert!(message.contains("Output Block 2 too short: expected at least 46 characters"));
1653 } else {
1654 panic!("Expected InvalidBlockStructure error");
1655 }
1656 }
1657
1658 #[test]
1659 fn test_basic_header_parsing() {
1660 let block1 = "F01DEUTDEFFAXXX0000123456";
1661 let header = BasicHeader::parse(block1).unwrap();
1662
1663 assert_eq!(header.application_id, "F");
1664 assert_eq!(header.service_id, "01");
1665 assert_eq!(header.logical_terminal, "DEUTDEFFAXXX");
1666 assert_eq!(header.sender_bic, "DEUTDEFF");
1667 assert_eq!(header.session_number, "0000");
1668 assert_eq!(header.sequence_number, "123456");
1669 }
1670
1671 #[test]
1672 fn test_application_header_input_display() {
1673 let header = ApplicationHeader::Input(InputApplicationHeader {
1674 message_type: "103".to_string(),
1675 destination_address: "DEUTDEFFAXXX".to_string(),
1676 receiver_bic: "DEUTDEFF".to_string(),
1677 priority: "U".to_string(),
1678 delivery_monitoring: Some("3".to_string()),
1679 obsolescence_period: Some("003".to_string()),
1680 });
1681
1682 assert_eq!(header.to_string(), "I103DEUTDEFFAXXXU3003");
1683 }
1684
1685 #[test]
1686 fn test_application_header_output_display() {
1687 let mir = MessageInputReference {
1688 date: "051028".to_string(),
1689 lt_identifier: "DEUTDEFFAXXX".to_string(),
1690 branch_code: "XXX".to_string(),
1691 session_number: "0826".to_string(),
1692 sequence_number: "455628".to_string(),
1693 };
1694
1695 let header = ApplicationHeader::Output(OutputApplicationHeader {
1696 message_type: "103".to_string(),
1697 input_time: "1535".to_string(),
1698 mir,
1699 output_date: "051028".to_string(),
1700 output_time: "1535".to_string(),
1701 priority: Some("N".to_string()),
1702 });
1703
1704 assert_eq!(
1705 header.to_string(),
1706 "O1031535051028DEUTDEFFAXXX08264556280510281535N"
1707 );
1708 }
1709
1710 #[test]
1711 fn test_application_header_helper_methods() {
1712 let input_header = ApplicationHeader::Input(InputApplicationHeader {
1713 message_type: "103".to_string(),
1714 destination_address: "DEUTDEFFAXXX".to_string(),
1715 receiver_bic: "DEUTDEFF".to_string(),
1716 priority: "U".to_string(),
1717 delivery_monitoring: None,
1718 obsolescence_period: None,
1719 });
1720
1721 assert_eq!(input_header.message_type(), "103");
1722 assert_eq!(input_header.priority(), Some("U"));
1723
1724 let mir = MessageInputReference {
1725 date: "051028".to_string(),
1726 lt_identifier: "DEUTDEFFAXXX".to_string(),
1727 branch_code: "XXX".to_string(),
1728 session_number: "0826".to_string(),
1729 sequence_number: "455628".to_string(),
1730 };
1731
1732 let output_header = ApplicationHeader::Output(OutputApplicationHeader {
1733 message_type: "202".to_string(),
1734 input_time: "1535".to_string(),
1735 mir,
1736 output_date: "051028".to_string(),
1737 output_time: "1535".to_string(),
1738 priority: Some("N".to_string()),
1739 });
1740
1741 assert_eq!(output_header.message_type(), "202");
1742 assert_eq!(output_header.priority(), Some("N"));
1743 }
1744}