Initial commit: telfax - Rust Fax Server
- 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
This commit is contained in:
103
src/fax/class1/session.rs
Normal file
103
src/fax/class1/session.rs
Normal file
@@ -0,0 +1,103 @@
|
||||
/// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user