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:
Warren
2026-07-01 09:06:41 +08:00
commit 13438289fe
28 changed files with 4615 additions and 0 deletions

103
src/fax/class1/session.rs Normal file
View 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;
}
}
}
}