use std::collections::VecDeque;
use async_trait::async_trait;
use crate::error::{Ecr17Error, Result};
#[cfg(feature = "tokio-transport")]
pub mod tcp;
#[async_trait]
pub trait Transport: Send {
async fn connect(&mut self) -> Result<()>;
async fn disconnect(&mut self);
fn is_connected(&self) -> bool;
async fn send(&mut self, bytes: &[u8]) -> Result<()>;
async fn recv(&mut self) -> Result<Vec<u8>>;
}
#[derive(Debug)]
pub struct FakeTransport {
connected: bool,
disconnect_on_request: bool,
sent: Vec<Vec<u8>>,
responses: VecDeque<Vec<u8>>,
pending: VecDeque<Vec<u8>>,
}
impl Default for FakeTransport {
fn default() -> Self {
Self::new()
}
}
impl FakeTransport {
pub const STX: u8 = 0x02;
#[must_use]
pub fn new() -> Self {
Self {
connected: true,
disconnect_on_request: false,
sent: Vec::new(),
responses: VecDeque::new(),
pending: VecDeque::new(),
}
}
pub fn enqueue_response(&mut self, bytes: Vec<u8>) {
self.responses.push_back(bytes);
}
pub fn disconnect_on_next_request(&mut self) {
self.disconnect_on_request = true;
}
pub fn rearm(&mut self) {
self.disconnect_on_request = false;
self.connected = true;
}
#[must_use]
pub fn sent_frames(&self) -> &[Vec<u8>] {
&self.sent
}
#[must_use]
pub fn application_request_count(&self) -> usize {
self.sent
.iter()
.filter(|f| f.first() == Some(&Self::STX))
.count()
}
}
#[async_trait]
impl Transport for FakeTransport {
async fn connect(&mut self) -> Result<()> {
self.connected = true;
self.disconnect_on_request = false;
Ok(())
}
async fn disconnect(&mut self) {
self.connected = false;
}
fn is_connected(&self) -> bool {
self.connected
}
async fn send(&mut self, bytes: &[u8]) -> Result<()> {
if !self.connected {
return Err(Ecr17Error::Disconnected);
}
self.sent.push(bytes.to_vec());
let is_application_request = bytes.first() == Some(&Self::STX);
if is_application_request {
if self.disconnect_on_request {
self.connected = false; } else if let Some(next) = self.responses.pop_front() {
self.pending.push_back(next);
}
}
Ok(())
}
async fn recv(&mut self) -> Result<Vec<u8>> {
if let Some(bytes) = self.pending.pop_front() {
return Ok(bytes);
}
if !self.connected {
return Err(Ecr17Error::Disconnected);
}
std::future::pending::<()>().await;
unreachable!("pending() never resolves")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::codec::{PacketCodec, ACK};
use crate::lrc::LrcMode;
fn ack_frame() -> Vec<u8> {
PacketCodec::new(LrcMode::Std).encode_control(ACK)
}
#[tokio::test]
async fn delivers_enqueued_response_on_application_send() {
let mut t = FakeTransport::new();
t.enqueue_response(ack_frame());
t.send(&[FakeTransport::STX, b'X']).await.unwrap();
assert_eq!(t.recv().await.unwrap(), ack_frame());
assert_eq!(t.application_request_count(), 1);
}
#[tokio::test]
async fn control_send_does_not_consume_a_response() {
let mut t = FakeTransport::new();
t.enqueue_response(ack_frame());
t.send(&ack_frame()).await.unwrap(); assert_eq!(t.application_request_count(), 0);
t.send(&[FakeTransport::STX]).await.unwrap();
assert_eq!(t.recv().await.unwrap(), ack_frame());
}
#[tokio::test]
async fn send_and_recv_require_a_connection() {
let mut t = FakeTransport::new();
assert!(t.is_connected()); t.disconnect().await;
assert!(!t.is_connected());
assert_eq!(
t.send(&[FakeTransport::STX]).await,
Err(Ecr17Error::Disconnected)
);
assert_eq!(t.recv().await, Err(Ecr17Error::Disconnected));
t.connect().await.unwrap();
t.enqueue_response(ack_frame());
t.send(&[FakeTransport::STX]).await.unwrap();
assert_eq!(t.recv().await.unwrap(), ack_frame());
}
#[tokio::test]
async fn disconnect_on_next_request_makes_recv_error() {
let mut t = FakeTransport::new();
t.disconnect_on_next_request();
t.send(&[FakeTransport::STX, b'P']).await.unwrap();
assert!(!t.is_connected());
assert_eq!(t.recv().await, Err(Ecr17Error::Disconnected));
t.rearm();
t.enqueue_response(ack_frame());
t.send(&[FakeTransport::STX]).await.unwrap();
assert_eq!(t.recv().await.unwrap(), ack_frame());
}
}