1use std::collections::VecDeque;
16use std::time::{Duration, Instant};
17
18use crate::codec::{DecodedPacket, PacketCodec, PacketType, ACK, ETX, NAK, SOH, STX};
19use crate::error::{Ecr17Error, Result};
20use crate::lrc::LrcMode;
21use crate::transport::Transport;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub struct SessionConfig {
26 pub lrc_mode: LrcMode,
28 pub ack_timeout_ms: u64,
30 pub response_timeout_ms: u64,
32 pub retry_count: u32,
34 pub retry_delay_ms: u64,
36 pub receipt_drain_ms: u64,
39}
40
41impl Default for SessionConfig {
42 fn default() -> Self {
43 Self {
44 lrc_mode: LrcMode::Std,
45 ack_timeout_ms: 2000,
46 response_timeout_ms: 60_000,
47 retry_count: 3,
48 retry_delay_ms: 200,
49 receipt_drain_ms: 0,
50 }
51 }
52}
53
54type EventCallback = Box<dyn Fn(String) + Send + 'static>;
55
56enum WaitOutcome {
58 Frame(DecodedPacket),
59 Timeout,
60 TransportError(Ecr17Error),
63}
64
65pub struct Ecr17Session<T: Transport> {
67 transport: T,
68 config: SessionConfig,
69 codec: PacketCodec,
70 rx_buffer: VecDeque<u8>,
73 pending_result: Option<DecodedPacket>,
77 on_progress: Option<EventCallback>,
78 on_receipt_line: Option<EventCallback>,
79}
80
81impl<T: Transport> std::fmt::Debug for Ecr17Session<T> {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 f.debug_struct("Ecr17Session")
84 .field("config", &self.config)
85 .field("rx_buffered", &self.rx_buffer.len())
86 .field("has_pending_result", &self.pending_result.is_some())
87 .finish_non_exhaustive()
88 }
89}
90
91impl<T: Transport> Ecr17Session<T> {
92 pub fn new(transport: T, config: SessionConfig) -> Self {
94 let codec = PacketCodec::new(config.lrc_mode);
95 Self {
96 transport,
97 config,
98 codec,
99 rx_buffer: VecDeque::new(),
100 pending_result: None,
101 on_progress: None,
102 on_receipt_line: None,
103 }
104 }
105
106 pub fn set_on_progress(&mut self, cb: impl Fn(String) + Send + 'static) {
108 self.on_progress = Some(Box::new(cb));
109 }
110
111 pub fn set_on_receipt_line(&mut self, cb: impl Fn(String) + Send + 'static) {
113 self.on_receipt_line = Some(Box::new(cb));
114 }
115
116 pub fn transport(&self) -> &T {
119 &self.transport
120 }
121
122 pub fn transport_mut(&mut self) -> &mut T {
124 &mut self.transport
125 }
126
127 pub async fn connect(&mut self) -> Result<()> {
129 self.transport.connect().await
130 }
131
132 pub async fn disconnect(&mut self) {
134 self.transport.disconnect().await;
135 }
136
137 pub fn is_connected(&self) -> bool {
139 self.transport.is_connected()
140 }
141
142 pub async fn exchange(&mut self, request_payload: &str) -> Result<DecodedPacket> {
146 self.reset_for_new_transaction();
147 self.ack_handshake(request_payload).await?;
148 self.wait_for_result().await
149 }
150
151 pub async fn exchange_with_additional_data(
155 &mut self,
156 request_payload: &str,
157 additional_payload: &str,
158 ) -> Result<DecodedPacket> {
159 self.reset_for_new_transaction();
160 self.ack_handshake(request_payload).await?;
161 if self.pending_result.is_none() && self.rx_buffer.is_empty() {
167 self.ack_handshake(additional_payload).await?;
168 }
169 self.wait_for_result().await
170 }
171
172 pub async fn send_ack_only(&mut self, request_payload: &str) -> Result<()> {
176 self.reset_for_new_transaction();
177 self.ack_handshake(request_payload).await
178 }
179
180 fn reset_for_new_transaction(&mut self) {
185 self.rx_buffer.clear();
186 self.pending_result = None;
187 }
188
189 async fn ack_handshake(&mut self, request_payload: &str) -> Result<()> {
191 let frame = self.codec.encode_application(request_payload.as_bytes());
192 self.transport.send(&frame).await?;
193 let ack_timeout = Duration::from_millis(self.config.ack_timeout_ms);
194 let mut attempts: u32 = 1;
195 let mut deadline = Instant::now() + ack_timeout;
196
197 loop {
198 let remaining = deadline.saturating_duration_since(Instant::now());
199 if remaining.is_zero() {
200 if attempts > self.config.retry_count {
201 return Err(Ecr17Error::AckTimeout { attempts });
202 }
203 self.retransmit(&frame, &mut attempts, &mut deadline, ack_timeout)
204 .await?;
205 continue;
206 }
207 match self.wait_for_frame(remaining).await {
208 WaitOutcome::Frame(pkt) => match pkt.packet_type {
209 PacketType::Ack => return Ok(()),
210 PacketType::Nak => {
211 if attempts > self.config.retry_count {
212 return Err(Ecr17Error::Nak { attempts });
213 }
214 self.retransmit(&frame, &mut attempts, &mut deadline, ack_timeout)
215 .await?;
216 }
217 PacketType::Application => {
218 self.pending_result = Some(pkt);
220 return Ok(());
221 }
222 _ => {}
224 },
225 WaitOutcome::TransportError(e) => return Err(e),
226 WaitOutcome::Timeout => {}
228 }
229 }
230 }
231
232 async fn retransmit(
233 &mut self,
234 frame: &[u8],
235 attempts: &mut u32,
236 deadline: &mut Instant,
237 ack_timeout: Duration,
238 ) -> Result<()> {
239 tokio::time::sleep(Duration::from_millis(self.config.retry_delay_ms)).await;
240 self.transport.send(frame).await?;
241 *attempts += 1;
242 *deadline = Instant::now() + ack_timeout;
243 Ok(())
244 }
245
246 async fn wait_for_result(&mut self) -> Result<DecodedPacket> {
249 let deadline = Instant::now() + Duration::from_millis(self.config.response_timeout_ms);
250 loop {
251 let pkt = if let Some(p) = self.pending_result.take() {
252 p
253 } else {
254 let remaining = deadline.saturating_duration_since(Instant::now());
255 if remaining.is_zero() {
256 return Err(Ecr17Error::ResponseTimeout);
257 }
258 match self.wait_for_frame(remaining).await {
259 WaitOutcome::Frame(p) => p,
260 WaitOutcome::TransportError(e) => return Err(e),
261 WaitOutcome::Timeout => continue,
262 }
263 };
264 match pkt.packet_type {
265 PacketType::Progress => self.emit_progress(&pkt.payload),
266 PacketType::Application => {
267 if !pkt.valid_lrc {
268 self.send_control(NAK).await;
269 } else {
270 self.send_control(ACK).await;
271 if Self::is_receipt(&pkt.payload) {
272 self.emit_receipt(&pkt.payload);
273 } else {
274 self.drain_receipts().await;
275 return Ok(pkt);
276 }
277 }
278 }
279 PacketType::Ack | PacketType::Nak => {}
281 PacketType::Unknown => self.send_control(NAK).await,
282 }
283 }
284 }
285
286 async fn drain_receipts(&mut self) {
289 if self.config.receipt_drain_ms == 0 {
290 return;
291 }
292 let drain = Duration::from_millis(self.config.receipt_drain_ms);
293 loop {
294 match self.wait_for_frame(drain).await {
295 WaitOutcome::Frame(pkt) => match pkt.packet_type {
296 PacketType::Application => {
297 if pkt.valid_lrc {
298 self.send_control(ACK).await;
299 if Self::is_receipt(&pkt.payload) {
300 self.emit_receipt(&pkt.payload);
301 }
302 } else {
303 self.send_control(NAK).await;
304 }
305 }
306 PacketType::Progress => self.emit_progress(&pkt.payload),
307 _ => {}
308 },
309 WaitOutcome::Timeout | WaitOutcome::TransportError(_) => return,
311 }
312 }
313 }
314
315 fn extract_frame(&mut self) -> Option<Vec<u8>> {
318 while let Some(&first) = self.rx_buffer.front() {
319 if first == ACK || first == NAK {
320 if self.rx_buffer.len() < 3 {
321 return None; }
323 if self.rx_buffer[1] != ETX
330 || self.rx_buffer[2] != self.codec.lrc_mode().compute(&[first])
331 {
332 self.rx_buffer.pop_front();
333 continue;
334 }
335 return Some(self.rx_buffer.drain(0..3).collect());
336 }
337 if first == STX {
338 let Some(etx) = self.rx_buffer.iter().position(|&b| b == ETX) else {
339 return None; };
341 if etx + 1 >= self.rx_buffer.len() {
342 return None; }
344 return Some(self.rx_buffer.drain(0..=etx + 1).collect());
345 }
346 if first == SOH {
347 let Some(eot) = self.rx_buffer.iter().position(|&b| b == crate::codec::EOT) else {
348 return None; };
350 return Some(self.rx_buffer.drain(0..=eot).collect());
351 }
352 self.rx_buffer.pop_front();
354 }
355 None
356 }
357
358 async fn wait_for_frame(&mut self, timeout: Duration) -> WaitOutcome {
360 let deadline = Instant::now() + timeout;
361 loop {
362 if let Some(frame) = self.extract_frame() {
363 return WaitOutcome::Frame(self.codec.decode(&frame));
364 }
365 let remaining = deadline.saturating_duration_since(Instant::now());
366 if remaining.is_zero() {
367 return WaitOutcome::Timeout;
368 }
369 match tokio::time::timeout(remaining, self.transport.recv()).await {
370 Ok(Ok(bytes)) => self.rx_buffer.extend(bytes),
371 Ok(Err(err)) => return WaitOutcome::TransportError(err),
372 Err(_elapsed) => return WaitOutcome::Timeout,
373 }
374 }
375 }
376
377 async fn send_control(&mut self, control: u8) {
378 let frame = self.codec.encode_control(control);
379 let _ = self.transport.send(&frame).await;
384 }
385
386 fn emit_progress(&self, payload: &[u8]) {
387 if let Some(cb) = &self.on_progress {
388 cb(String::from_utf8_lossy(payload).into_owned());
389 }
390 }
391
392 fn emit_receipt(&self, payload: &[u8]) {
393 if let Some(cb) = &self.on_receipt_line {
394 cb(String::from_utf8_lossy(payload).into_owned());
395 }
396 }
397
398 fn is_receipt(payload: &[u8]) -> bool {
400 payload.get(9) == Some(&b'S')
401 }
402}
403
404#[cfg(test)]
405mod tests {
406 use super::*;
407 use crate::transport::FakeTransport;
408 use std::sync::{Arc, Mutex};
409
410 fn fast_config() -> SessionConfig {
411 SessionConfig {
412 lrc_mode: LrcMode::Std,
413 ack_timeout_ms: 40,
414 response_timeout_ms: 40,
415 retry_count: 2,
416 retry_delay_ms: 1,
417 receipt_drain_ms: 0,
418 }
419 }
420
421 fn codec() -> PacketCodec {
422 PacketCodec::new(LrcMode::Std)
423 }
424
425 fn concat(mut a: Vec<u8>, b: Vec<u8>) -> Vec<u8> {
426 a.extend(b);
427 a
428 }
429
430 fn progress_frame(msg20: &str) -> Vec<u8> {
431 let mut f = vec![SOH];
432 f.extend_from_slice(msg20.as_bytes());
433 f.push(crate::codec::EOT);
434 f
435 }
436
437 const RESULT: &str = "123456780E0000DATA"; const RECEIPT: &str = "123456780SLINE 1"; fn sent_any(t: &FakeTransport, lead: u8) -> bool {
441 t.sent_frames().iter().any(|f| f.first() == Some(&lead))
442 }
443
444 #[tokio::test]
445 async fn happy_path_returns_result_and_acks() {
446 let c = codec();
447 let mut t = FakeTransport::new();
448 t.enqueue_response(concat(
449 c.encode_control(ACK),
450 c.encode_application(RESULT.as_bytes()),
451 ));
452 let mut session = Ecr17Session::new(t, fast_config());
453
454 let result = session.exchange("123456780P...").await.unwrap();
455 assert_eq!(result.packet_type, PacketType::Application);
456 assert!(result.valid_lrc);
457 assert_eq!(result.payload, RESULT.as_bytes());
458 assert_eq!(session.transport().application_request_count(), 1);
459 assert!(sent_any(session.transport(), ACK));
460 }
461
462 #[tokio::test]
465 async fn stray_ack_byte_is_resynced_not_a_false_ack() {
466 let c = codec();
467 let mut response = vec![ACK, 0xFF]; response.extend(c.encode_control(ACK)); response.extend(c.encode_application(RESULT.as_bytes()));
470 let mut t = FakeTransport::new();
471 t.enqueue_response(response);
472 let mut session = Ecr17Session::new(t, fast_config());
473
474 let result = session.exchange("123456780P...").await.unwrap();
475 assert_eq!(result.payload, RESULT.as_bytes());
476 }
477
478 #[tokio::test]
481 async fn control_frame_with_bad_lrc_is_resynced() {
482 let c = codec();
483 let mut response = vec![ACK, ETX, 0x00]; response.extend(c.encode_control(ACK)); response.extend(c.encode_application(RESULT.as_bytes()));
486 let mut t = FakeTransport::new();
487 t.enqueue_response(response);
488 let mut session = Ecr17Session::new(t, fast_config());
489
490 let result = session.exchange("123456780P...").await.unwrap();
491 assert_eq!(result.payload, RESULT.as_bytes());
492 }
493
494 #[tokio::test]
495 async fn nak_triggers_retransmit_then_succeeds() {
496 let c = codec();
497 let mut t = FakeTransport::new();
498 t.enqueue_response(c.encode_control(NAK)); t.enqueue_response(concat(
500 c.encode_control(ACK),
501 c.encode_application(RESULT.as_bytes()),
502 ));
503 let mut session = Ecr17Session::new(t, fast_config());
504
505 let result = session.exchange("123456780P...").await.unwrap();
506 assert_eq!(result.payload, RESULT.as_bytes());
507 assert_eq!(session.transport().application_request_count(), 2); }
509
510 #[tokio::test]
511 async fn ack_timeout_exhausts_retries_then_errors() {
512 let mut session = Ecr17Session::new(FakeTransport::new(), fast_config());
513 let err = session.exchange("123456780P...").await.unwrap_err();
514 assert!(matches!(err, Ecr17Error::AckTimeout { attempts: 3 }));
515 assert_eq!(session.transport().application_request_count(), 3); }
517
518 #[tokio::test]
519 async fn bad_lrc_response_sends_nak() {
520 let c = codec();
521 let mut t = FakeTransport::new();
522 let mut bad = c.encode_application(RESULT.as_bytes());
523 *bad.last_mut().unwrap() ^= 0xFF; t.enqueue_response(concat(c.encode_control(ACK), bad));
525 let mut session = Ecr17Session::new(t, fast_config());
526
527 let err = session.exchange("123456780P...").await.unwrap_err();
528 assert!(matches!(err, Ecr17Error::ResponseTimeout));
529 assert!(sent_any(session.transport(), NAK));
530 }
531
532 #[tokio::test]
533 async fn progress_messages_forwarded() {
534 let c = codec();
535 let mut response = c.encode_control(ACK);
536 response.extend(progress_frame("ATTENDERE PREGO "));
537 response.extend(c.encode_application(RESULT.as_bytes()));
538 let mut t = FakeTransport::new();
539 t.enqueue_response(response);
540
541 let progress = Arc::new(Mutex::new(Vec::<String>::new()));
542 let sink = Arc::clone(&progress);
543 let mut session = Ecr17Session::new(t, fast_config());
544 session.set_on_progress(move |m| sink.lock().unwrap().push(m));
545
546 let result = session.exchange("123456780P...").await.unwrap();
547 assert_eq!(result.payload, RESULT.as_bytes());
548 assert_eq!(
549 *progress.lock().unwrap(),
550 vec!["ATTENDERE PREGO ".to_string()]
551 );
552 }
553
554 #[tokio::test]
555 async fn receipt_lines_forwarded_before_result() {
556 let c = codec();
557 let mut response = c.encode_control(ACK);
558 response.extend(c.encode_application(RECEIPT.as_bytes()));
559 response.extend(c.encode_application(RESULT.as_bytes()));
560 let mut t = FakeTransport::new();
561 t.enqueue_response(response);
562
563 let receipts = Arc::new(Mutex::new(Vec::<String>::new()));
564 let sink = Arc::clone(&receipts);
565 let mut session = Ecr17Session::new(t, fast_config());
566 session.set_on_receipt_line(move |l| sink.lock().unwrap().push(l));
567
568 let result = session.exchange("123456780P...").await.unwrap();
569 assert_eq!(result.payload, RESULT.as_bytes());
570 assert_eq!(*receipts.lock().unwrap(), vec![RECEIPT.to_string()]);
571 }
572
573 #[tokio::test]
575 async fn result_before_ack_is_not_lost() {
576 let c = codec();
577 let mut t = FakeTransport::new();
578 t.enqueue_response(c.encode_application(RESULT.as_bytes())); let mut session = Ecr17Session::new(t, fast_config());
580
581 let result = session.exchange("123456780P...").await.unwrap();
582 assert_eq!(result.payload, RESULT.as_bytes());
583 assert_eq!(session.transport().application_request_count(), 1); assert!(sent_any(session.transport(), ACK));
585 }
586
587 #[tokio::test]
588 async fn response_timeout_after_ack_errors() {
589 let c = codec();
590 let mut t = FakeTransport::new();
591 t.enqueue_response(c.encode_control(ACK)); let mut session = Ecr17Session::new(t, fast_config());
593 assert!(matches!(
594 session.exchange("123456780P...").await,
595 Err(Ecr17Error::ResponseTimeout)
596 ));
597 }
598
599 #[tokio::test]
600 async fn disconnect_during_exchange_errors() {
601 let mut t = FakeTransport::new();
602 t.disconnect_on_next_request();
603 let mut session = Ecr17Session::new(t, fast_config());
604 assert!(matches!(
605 session.exchange("123456780P...").await,
606 Err(Ecr17Error::Disconnected)
607 ));
608 }
609
610 #[tokio::test]
612 async fn recovers_and_succeeds_after_reconnect() {
613 let mut t = FakeTransport::new();
614 t.disconnect_on_next_request();
615 let mut session = Ecr17Session::new(t, fast_config());
616
617 assert!(matches!(
618 session.exchange("123456780P...").await,
619 Err(Ecr17Error::Disconnected)
620 ));
621
622 let c = codec();
623 session.transport_mut().rearm();
624 session.transport_mut().enqueue_response(concat(
625 c.encode_control(ACK),
626 c.encode_application(RESULT.as_bytes()),
627 ));
628
629 let result = session.exchange("123456780P...").await.unwrap();
630 assert_eq!(result.payload, RESULT.as_bytes());
631 }
632
633 #[tokio::test]
634 async fn send_ack_only_returns_on_ack() {
635 let c = codec();
636 let mut t = FakeTransport::new();
637 t.enqueue_response(c.encode_control(ACK));
638 let mut session = Ecr17Session::new(t, fast_config());
639 session.send_ack_only("123456780E1").await.unwrap();
640 assert_eq!(session.transport().application_request_count(), 1);
641 }
642
643 #[tokio::test]
644 async fn send_ack_only_retransmits_on_nak() {
645 let c = codec();
646 let mut t = FakeTransport::new();
647 t.enqueue_response(c.encode_control(NAK));
648 t.enqueue_response(c.encode_control(ACK));
649 let mut session = Ecr17Session::new(t, fast_config());
650 session.send_ack_only("123456780E0").await.unwrap();
651 assert_eq!(session.transport().application_request_count(), 2);
652 }
653
654 #[tokio::test]
655 async fn send_ack_only_times_out() {
656 let mut session = Ecr17Session::new(FakeTransport::new(), fast_config());
657 assert!(matches!(
658 session.send_ack_only("123456780E1").await,
659 Err(Ecr17Error::AckTimeout { .. })
660 ));
661 }
662
663 #[tokio::test]
664 async fn exchange_with_additional_data_sends_two_requests() {
665 let c = codec();
666 let mut t = FakeTransport::new();
667 t.enqueue_response(c.encode_control(ACK)); t.enqueue_response(concat(
669 c.encode_control(ACK),
670 c.encode_application(RESULT.as_bytes()),
671 ));
672 let mut session = Ecr17Session::new(t, fast_config());
673
674 let result = session
675 .exchange_with_additional_data("123456780P...", "123456780U...")
676 .await
677 .unwrap();
678 assert_eq!(result.payload, RESULT.as_bytes());
679 assert_eq!(session.transport().application_request_count(), 2); }
681
682 #[tokio::test]
685 async fn exchange_with_additional_data_skips_u_when_result_already_arrived() {
686 let c = codec();
687 let mut t = FakeTransport::new();
688 t.enqueue_response(concat(
689 c.encode_control(ACK),
690 c.encode_application(RESULT.as_bytes()),
691 ));
692 let mut session = Ecr17Session::new(t, fast_config());
693
694 let result = session
695 .exchange_with_additional_data("123456780P...", "123456780U...")
696 .await
697 .unwrap();
698 assert_eq!(result.payload, RESULT.as_bytes());
699 assert_eq!(session.transport().application_request_count(), 1); }
701
702 #[tokio::test]
703 async fn receipt_drain_forwards_receipts_after_result() {
704 let c = codec();
705 let mut response = c.encode_control(ACK);
706 response.extend(c.encode_application(RESULT.as_bytes())); response.extend(c.encode_application(RECEIPT.as_bytes())); let mut t = FakeTransport::new();
709 t.enqueue_response(response);
710
711 let mut cfg = fast_config();
712 cfg.receipt_drain_ms = 30;
713 let receipts = Arc::new(Mutex::new(Vec::<String>::new()));
714 let sink = Arc::clone(&receipts);
715 let mut session = Ecr17Session::new(t, cfg);
716 session.set_on_receipt_line(move |l| sink.lock().unwrap().push(l));
717
718 let result = session.exchange("123456780P...").await.unwrap();
719 assert_eq!(result.payload, RESULT.as_bytes());
720 assert_eq!(*receipts.lock().unwrap(), vec![RECEIPT.to_string()]);
721 }
722}