use crate::errors::RtcResult;
use std::collections::VecDeque;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum T30Phase {
Idle,
CallingToneSent,
CalledToneReceived,
Premessage,
Training,
ReadyToTransmit,
TransmittingPage,
PostPage,
ReadyToReceive,
ReceivingPage,
Disconnecting,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum T30Resolution {
Standard, Fine, SuperFine, }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HdlcFrameType {
Dis = 0x01,
Dcs = 0x02,
Csi = 0x03,
Nsf = 0x04,
Cfr = 0x05,
Tcf = 0x06,
Tsi = 0x07,
Sub = 0x08,
Sep = 0x09,
Pwd = 0x0A,
Ftt = 0x0B,
Eop = 0x10,
Mcf = 0x11,
Mps = 0x12,
Eom = 0x13,
Pps = 0x14,
Ppr = 0x15,
Eor = 0x16,
EorMsg = 0x17,
Trc = 0x18,
Dcn = 0x19,
Pause = 0x1A,
Continue = 0x1B,
Rtn = 0x1C,
}
#[derive(Debug, Clone)]
pub struct T30FaxConfig {
pub max_bitrate: u32,
pub resolutions: Vec<T30Resolution>,
pub ecm_supported: bool,
pub local_id: String,
}
impl Default for T30FaxConfig {
fn default() -> Self {
Self {
max_bitrate: 14400,
resolutions: vec![T30Resolution::Standard, T30Resolution::Fine],
ecm_supported: true,
local_id: String::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct T30Session {
pub phase: T30Phase,
pub local_config: T30FaxConfig,
pub remote_config: Option<T30FaxConfig>,
pub page_number: u32,
pub page_data: Vec<u8>,
pub hdlc_buffer: Vec<u8>,
pub events: VecDeque<T30Event>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum T30Event {
PhaseChange(T30Phase, T30Phase),
RemoteIdentification { id: String },
LocalIdentification { id: String },
PageTransferred { page: u32, size: usize },
PageReceived { page: u32, size: usize },
Disconnected,
Error(String),
}
impl T30Session {
pub fn new(local_config: T30FaxConfig) -> Self {
Self {
phase: T30Phase::Idle,
local_config,
remote_config: None,
page_number: 0,
page_data: Vec::new(),
hdlc_buffer: Vec::new(),
events: VecDeque::new(),
}
}
pub fn start_calling(&mut self) {
self.change_phase(T30Phase::CallingToneSent);
}
pub fn start_called(&mut self) {
self.change_phase(T30Phase::CalledToneReceived);
}
pub fn receive_dis(&mut self, data: &[u8]) -> RtcResult<()> {
if self.phase == T30Phase::CalledToneReceived || self.phase == T30Phase::Premessage {
if data.len() >= 2 {
let _dis_bits = (data[0] as u16) | ((data[1] as u16) << 8);
let ecm_supported = (data[0] & 0x80) != 0;
let _fine = (data[1] & 0x01) != 0;
let _super_fine = (data[1] & 0x02) != 0;
let _max_rate_bits = (data[1] >> 3) & 0x07;
self.remote_config = Some(T30FaxConfig {
max_bitrate: Self::dis_bitrate(data[1]),
resolutions: vec![T30Resolution::Standard],
ecm_supported,
local_id: String::new(),
});
self.change_phase(T30Phase::Premessage);
}
}
Ok(())
}
pub fn send_dcs(&mut self) -> Vec<u8> {
let mut dcs = vec![0x00, 0x00];
let remote_supports_ecm = self
.remote_config
.as_ref()
.map(|r| r.ecm_supported)
.unwrap_or(false);
if self.local_config.ecm_supported && remote_supports_ecm {
dcs[0] |= 0x80; }
dcs
}
pub fn confirm_receipt(&mut self) {
if self.phase == T30Phase::Training {
self.change_phase(T30Phase::ReadyToTransmit);
let page_size = self.page_data.len();
self.page_number += 1;
self.events.push_back(T30Event::PageTransferred {
page: self.page_number,
size: page_size,
});
}
}
pub fn end_of_page(&mut self) {
if self.phase == T30Phase::TransmittingPage {
self.change_phase(T30Phase::PostPage);
}
}
pub fn receive_mcf(&mut self) {
if self.phase == T30Phase::PostPage {
self.change_phase(T30Phase::Disconnecting);
}
}
pub fn send_dcn(&mut self) {
self.change_phase(T30Phase::Disconnecting);
self.events.push_back(T30Event::Disconnected);
}
pub fn receive_page_data(&mut self, data: &[u8]) {
self.page_data.extend_from_slice(data);
}
pub fn complete_page_reception(&mut self) {
let size = self.page_data.len();
self.page_number += 1;
self.events.push_back(T30Event::PageReceived {
page: self.page_number,
size,
});
self.page_data.clear();
}
pub fn reset(&mut self) {
self.phase = T30Phase::Idle;
self.remote_config = None;
self.page_number = 0;
self.page_data.clear();
self.hdlc_buffer.clear();
}
pub fn change_phase(&mut self, new_phase: T30Phase) {
if self.phase != new_phase {
let old = self.phase;
self.phase = new_phase;
self.events.push_back(T30Event::PhaseChange(old, new_phase));
}
}
fn dis_bitrate(data_byte: u8) -> u32 {
match (data_byte >> 3) & 0x07 {
0 => 2400,
1 => 4800,
2 => 9600,
3 => 12000,
4 => 14400,
_ => 2400,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_t30_session_initial_state() {
let session = T30Session::new(T30FaxConfig::default());
assert_eq!(session.phase, T30Phase::Idle);
assert_eq!(session.page_number, 0);
assert!(session.page_data.is_empty());
}
#[test]
fn test_t30_start_calling() {
let mut session = T30Session::new(T30FaxConfig::default());
session.start_calling();
assert_eq!(session.phase, T30Phase::CallingToneSent);
}
#[test]
fn test_t30_start_called() {
let mut session = T30Session::new(T30FaxConfig::default());
session.start_called();
assert_eq!(session.phase, T30Phase::CalledToneReceived);
}
#[test]
fn test_t30_receive_dis() {
let config = T30FaxConfig {
ecm_supported: true,
..T30FaxConfig::default()
};
let mut session = T30Session::new(config);
session.start_called();
let dis_data = vec![0x80, 0x20 | 0x01];
session.receive_dis(&dis_data).unwrap();
assert_eq!(session.phase, T30Phase::Premessage);
assert!(session.remote_config.is_some());
let remote = session.remote_config.as_ref().unwrap();
assert!(remote.ecm_supported);
}
#[test]
fn test_t30_send_dcs() {
let mut session = T30Session::new(T30FaxConfig {
ecm_supported: true,
..T30FaxConfig::default()
});
session.start_called();
session.remote_config = Some(T30FaxConfig {
ecm_supported: true,
..T30FaxConfig::default()
});
let dcs = session.send_dcs();
assert_eq!(dcs.len(), 2);
assert!(dcs[0] & 0x80 != 0); }
#[test]
fn test_t30_confirm_receipt() {
let mut session = T30Session::new(T30FaxConfig::default());
session.page_data = vec![0x00; 100];
session.change_phase(T30Phase::Training);
session.confirm_receipt();
assert_eq!(session.phase, T30Phase::ReadyToTransmit);
assert_eq!(session.page_number, 1);
let event = session.events.back().unwrap();
match event {
T30Event::PageTransferred { page, size } => {
assert_eq!(*page, 1);
assert_eq!(*size, 100);
}
_ => panic!("expected PageTransferred event"),
}
}
#[test]
fn test_t30_end_of_page_and_mcf() {
let mut session = T30Session::new(T30FaxConfig::default());
session.change_phase(T30Phase::TransmittingPage);
session.end_of_page();
assert_eq!(session.phase, T30Phase::PostPage);
session.receive_mcf();
assert_eq!(session.phase, T30Phase::Disconnecting);
}
#[test]
fn test_t30_send_dcn() {
let mut session = T30Session::new(T30FaxConfig::default());
session.send_dcn();
assert_eq!(session.phase, T30Phase::Disconnecting);
let event = session.events.back().unwrap();
assert_eq!(*event, T30Event::Disconnected);
}
#[test]
fn test_t30_receive_page_data() {
let mut session = T30Session::new(T30FaxConfig::default());
assert!(session.page_data.is_empty());
session.receive_page_data(&[0x00, 0x01, 0x02]);
assert_eq!(session.page_data.len(), 3);
session.complete_page_reception();
assert_eq!(session.page_number, 1);
assert!(session.page_data.is_empty());
let event = session.events.back().unwrap();
match event {
T30Event::PageReceived { page, size } => {
assert_eq!(*page, 1);
assert_eq!(*size, 3);
}
_ => panic!("expected PageReceived event"),
}
}
#[test]
fn test_t30_full_session_flow() {
let config = T30FaxConfig {
ecm_supported: true,
local_id: "TEST001".to_string(),
..T30FaxConfig::default()
};
let mut session = T30Session::new(config);
session.start_calling();
assert_eq!(session.phase, T30Phase::CallingToneSent);
let mut called_session = T30Session::new(T30FaxConfig::default());
called_session.start_called();
let dis_data = vec![0x00, 0x00]; called_session.receive_dis(&dis_data).unwrap();
assert_eq!(called_session.phase, T30Phase::Premessage);
let _dcs = called_session.send_dcs();
called_session.change_phase(T30Phase::Training);
called_session.confirm_receipt();
assert_eq!(called_session.phase, T30Phase::ReadyToTransmit);
called_session.change_phase(T30Phase::TransmittingPage);
called_session.end_of_page();
assert_eq!(called_session.phase, T30Phase::PostPage);
called_session.receive_mcf();
assert_eq!(called_session.phase, T30Phase::Disconnecting);
called_session.send_dcn();
assert_eq!(called_session.phase, T30Phase::Disconnecting);
}
#[test]
fn test_t30_reset() {
let mut session = T30Session::new(T30FaxConfig::default());
session.start_calling();
session.page_number = 5;
session.page_data = vec![0x00; 100];
session.remote_config = Some(T30FaxConfig::default());
session.reset();
assert_eq!(session.phase, T30Phase::Idle);
assert_eq!(session.page_number, 0);
assert!(session.page_data.is_empty());
assert!(session.remote_config.is_none());
}
#[test]
fn test_t30_events_logged_on_phase_transitions() {
let mut session = T30Session::new(T30FaxConfig::default());
session.start_calling();
session.start_called();
let events: Vec<_> = session.events.iter().collect();
assert_eq!(events.len(), 2);
match events[0] {
T30Event::PhaseChange(T30Phase::Idle, T30Phase::CallingToneSent) => {}
_ => panic!("unexpected event"),
}
match events[1] {
T30Event::PhaseChange(T30Phase::CallingToneSent, T30Phase::CalledToneReceived) => {}
_ => panic!("unexpected event"),
}
}
}