- Modem driver (serialport) with AT command interface - Class 1 fax protocol (T.30 session, HDLC framing) - T.4/T.6 image codec (fax crate integration) - Document conversion (image to fax, TIFF-F output) - REST API (axum) for fax job management - SQLite/InMemory job queue - CLI interface (detect/serve/send/test) - Integration tests passing
104 lines
3.2 KiB
Rust
104 lines
3.2 KiB
Rust
/// T.30 session state machine for Group 3 fax.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum T30State {
|
|
Idle,
|
|
Dialing,
|
|
HandshakeTransmitting, // Phase B - sending DIS/DCS
|
|
HandshakeReceiving, // Phase B - receiving DIS/DCS
|
|
DcsSent, // DCS has been sent, waiting for CFR
|
|
CfrReceived, // Received CFR, ready to send pages
|
|
PageTransmitting, // Phase C - sending page data
|
|
PageReceiving,
|
|
PostPageHandshake, // Phase D - MCF, EOP, MPS
|
|
Disconnecting, // Phase E - DCN
|
|
Completed,
|
|
Error,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum T30Event {
|
|
Connected,
|
|
DisReceived,
|
|
DisSent,
|
|
DcsReceived,
|
|
DcsSent,
|
|
TcfSent,
|
|
TcfReceived,
|
|
CfrReceived,
|
|
CfrSent,
|
|
PageSent,
|
|
PageReceived,
|
|
McfReceived,
|
|
McfSent,
|
|
EopReceived,
|
|
EopSent,
|
|
DcnReceived,
|
|
DcnSent,
|
|
Timeout,
|
|
Error,
|
|
Retrain,
|
|
}
|
|
|
|
pub struct T30Session {
|
|
state: T30State,
|
|
retry_count: u8,
|
|
max_retries: u8,
|
|
}
|
|
|
|
impl T30Session {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
state: T30State::Idle,
|
|
retry_count: 0,
|
|
max_retries: 3,
|
|
}
|
|
}
|
|
|
|
pub fn state(&self) -> T30State {
|
|
self.state
|
|
}
|
|
|
|
pub fn start_receive(&mut self) {
|
|
self.state = T30State::HandshakeReceiving;
|
|
}
|
|
|
|
pub fn transition(&mut self, event: T30Event) {
|
|
match (self.state, event) {
|
|
// Send flow
|
|
(T30State::Idle, T30Event::Connected) => {
|
|
// Default to transmitting; caller can override with start_receive()
|
|
self.state = T30State::HandshakeTransmitting;
|
|
}
|
|
(T30State::HandshakeTransmitting, T30Event::DisReceived) => {
|
|
self.state = T30State::DcsSent;
|
|
}
|
|
(T30State::DcsSent, T30Event::CfrReceived) => self.state = T30State::PageTransmitting,
|
|
(T30State::PageTransmitting, T30Event::PageSent) => self.state = T30State::PostPageHandshake,
|
|
(T30State::PostPageHandshake, T30Event::McfReceived) => self.state = T30State::Disconnecting,
|
|
(T30State::PostPageHandshake, T30Event::EopReceived) => self.state = T30State::Disconnecting,
|
|
(T30State::Disconnecting, T30Event::DcnSent) => self.state = T30State::Completed,
|
|
|
|
// Receive flow
|
|
(T30State::HandshakeReceiving, T30Event::DcsReceived) => self.state = T30State::PageReceiving,
|
|
(T30State::PageReceiving, T30Event::PageReceived) => self.state = T30State::PostPageHandshake,
|
|
(T30State::PostPageHandshake, T30Event::McfSent) => self.state = T30State::Disconnecting,
|
|
(T30State::Disconnecting, T30Event::DcnReceived) => self.state = T30State::Completed,
|
|
|
|
// Error handling
|
|
(_, T30Event::Timeout) | (_, T30Event::Error) => {
|
|
if self.retry_count < self.max_retries {
|
|
self.retry_count += 1;
|
|
// Stay in same state for retry
|
|
} else {
|
|
self.state = T30State::Error;
|
|
}
|
|
}
|
|
|
|
_ => {
|
|
// Unexpected transition - log and set error
|
|
self.state = T30State::Error;
|
|
}
|
|
}
|
|
}
|
|
}
|