1use std::io::{self, Read, Write};
14use std::sync::Arc;
15use std::sync::atomic::{AtomicBool, Ordering};
16use std::thread;
17use std::time::{Duration, Instant};
18
19use serde::{Deserialize, Serialize};
20use serialport::SerialPort;
21
22use crate::transport::{NetEvent, Writer, WriterKind};
23
24const INIT_TIMEOUT: Duration = Duration::from_secs(5);
26const DIAL_TIMEOUT: Duration = Duration::from_secs(70);
28const GUARD_TIME: Duration = Duration::from_millis(1100);
30const POLL: Duration = Duration::from_millis(100);
32
33#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(rename_all = "lowercase")]
35pub enum FlowControl {
36 #[default]
38 Hardware,
39 Software,
41 None,
42}
43
44impl FlowControl {
45 pub const ALL: [FlowControl; 3] = [FlowControl::Hardware, FlowControl::Software, FlowControl::None];
46
47 pub fn name(self) -> &'static str {
48 match self {
49 FlowControl::Hardware => "RTS/CTS",
50 FlowControl::Software => "XON/XOFF",
51 FlowControl::None => "none",
52 }
53 }
54
55 fn serial(self) -> serialport::FlowControl {
56 match self {
57 FlowControl::Hardware => serialport::FlowControl::Hardware,
58 FlowControl::Software => serialport::FlowControl::Software,
59 FlowControl::None => serialport::FlowControl::None,
60 }
61 }
62}
63
64#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
66#[serde(default)]
67pub struct ModemConfig {
68 pub device: String,
70 pub speed: u32,
72 pub flow: FlowControl,
73 pub init: String,
75 pub dial: String,
77}
78
79impl Default for ModemConfig {
80 fn default() -> Self {
81 ModemConfig {
82 device: String::new(),
83 speed: 115_200,
84 flow: FlowControl::Hardware,
85 init: "ATZ".into(),
86 dial: "ATDT".into(),
87 }
88 }
89}
90
91pub const SPEEDS: [u32; 8] = [1200, 2400, 9600, 19_200, 38_400, 57_600, 115_200, 230_400];
93
94pub fn ports() -> Vec<String> {
96 serialport::available_ports().map(|ps| ps.into_iter().map(|p| p.port_name).collect()).unwrap_or_default()
97}
98
99#[derive(Debug, PartialEq, Eq)]
101enum Verdict {
102 Connect,
103 Failed(&'static str),
104}
105
106const RESULTS: [(&str, Option<&str>); 8] = [
108 ("CONNECT", None),
109 ("BUSY", Some("the line is busy (BUSY)")),
110 ("NO CARRIER", Some("no answer from a modem (NO CARRIER)")),
111 ("NO DIALTONE", Some("no dial tone (NO DIALTONE)")),
112 ("NO DIAL TONE", Some("no dial tone (NO DIAL TONE)")),
113 ("NO ANSWER", Some("no answer (NO ANSWER)")),
114 ("DELAYED", Some("the modem refuses to dial again so soon (DELAYED)")),
115 ("ERROR", Some("the modem did not take the dial command (ERROR)")),
116];
117
118fn verdict(said: &str) -> Option<Verdict> {
121 said.split(['\r', '\n']).rev().skip(1).find_map(|line| {
122 let line = line.trim();
123 RESULTS.iter().find(|(code, _)| line.starts_with(code)).map(|(_, why)| match why {
124 None => Verdict::Connect,
125 Some(why) => Verdict::Failed(why),
126 })
127 })
128}
129
130pub fn spawn_modem<F>(number: String, config: ModemConfig, sink: F)
133where
134 F: Fn(NetEvent) + Send + 'static,
135{
136 thread::spawn(move || {
137 let failure = run(&number, &config, &sink).err();
138 sink(NetEvent::Closed(failure.map(|e| e.to_string())));
139 });
140}
141
142fn run<F: Fn(NetEvent)>(number: &str, config: &ModemConfig, sink: &F) -> Result<(), String> {
143 let device = config.device.trim();
144 if device.is_empty() {
145 return Err("choose the modem's serial port first: Settings (Alt-O), the Modem tab".into());
146 }
147 if number.trim().is_empty() {
148 return Err("the entry has no phone number: put it in \"Address\"".into());
149 }
150 let mut port = serialport::new(device, config.speed)
151 .flow_control(config.flow.serial())
152 .timeout(POLL)
153 .open()
154 .map_err(|e| format!("opening the modem on {device}: {e}"))?;
155 let _ = port.write_data_terminal_ready(true);
156 let _ = port.write_request_to_send(true);
157
158 let mut said = String::new();
159 if !config.init.trim().is_empty() {
160 send(&mut port, &format!("{}\r", config.init.trim()))?;
161 let answer =
162 wait(&mut port, sink, INIT_TIMEOUT, &mut said, |s| s.lines().any(|l| matches!(l.trim(), "OK" | "ERROR")))?;
163 if !answer {
164 return Err(format!("the modem on {device} did not answer \"{}\"", config.init.trim()));
165 }
166 if said.lines().any(|l| l.trim() == "ERROR") {
167 return Err(format!("the modem did not take the init string \"{}\" (ERROR)", config.init.trim()));
168 }
169 }
170 sink(NetEvent::Notice(format!("Dialing {number}...")));
171 said.clear();
172 send(&mut port, &format!("{}{}\r", config.dial.trim(), number.trim()))?;
173 let answered = wait(&mut port, sink, DIAL_TIMEOUT, &mut said, |s| verdict(s).is_some())?;
174 match verdict(&said) {
175 Some(Verdict::Connect) => {}
176 Some(Verdict::Failed(why)) => return Err(format!("connecting to {number}: {why}")),
177 None if !answered => {
178 let _ = port.write_all(b"\r");
180 return Err(format!("connecting to {number}: no answer in {} seconds", DIAL_TIMEOUT.as_secs()));
181 }
182 None => unreachable!("waited for a verdict"),
183 }
184
185 let stop = Arc::new(AtomicBool::new(false));
186 let writer = port.try_clone().map_err(|e| format!("using the modem: {e}"))?;
187 sink(NetEvent::Connected(Writer(WriterKind::Serial(std::sync::Mutex::new(writer), stop.clone()))));
188 pass_through(&mut port, sink, &stop)
189}
190
191fn send(port: &mut Box<dyn SerialPort>, text: &str) -> Result<(), String> {
192 port.write_all(text.as_bytes()).map_err(|e| format!("talking to the modem: {e}"))
193}
194
195fn wait<F: Fn(NetEvent)>(
198 port: &mut Box<dyn SerialPort>,
199 sink: &F,
200 limit: Duration,
201 said: &mut String,
202 done: impl Fn(&str) -> bool,
203) -> Result<bool, String> {
204 let deadline = Instant::now() + limit;
205 let mut buf = [0u8; 1024];
206 while Instant::now() < deadline {
207 match port.read(&mut buf) {
208 Ok(0) => {}
209 Ok(n) => {
210 sink(NetEvent::Data(buf[..n].to_vec()));
211 said.push_str(&String::from_utf8_lossy(&buf[..n]));
212 if done(said) {
213 return Ok(true);
214 }
215 }
216 Err(e) if e.kind() == io::ErrorKind::TimedOut || e.kind() == io::ErrorKind::Interrupted => {}
217 Err(e) => return Err(format!("reading from the modem: {e}")),
218 }
219 }
220 Ok(false)
221}
222
223fn pass_through<F: Fn(NetEvent)>(port: &mut Box<dyn SerialPort>, sink: &F, stop: &AtomicBool) -> Result<(), String> {
226 let mut carrier_seen = port.read_carrier_detect().unwrap_or(false);
229 let mut tail: Vec<u8> = Vec::new();
230 let mut buf = vec![0u8; 16 * 1024];
231 loop {
232 if stop.load(Ordering::Relaxed) {
233 hang_up(port);
234 return Ok(());
235 }
236 match port.read(&mut buf) {
237 Ok(0) => {}
238 Ok(n) => {
239 sink(NetEvent::Data(buf[..n].to_vec()));
240 if !carrier_seen {
241 tail.extend_from_slice(&buf[..n]);
242 if crate::transfer::find(&tail, b"NO CARRIER").is_some() {
243 return Ok(());
244 }
245 tail.drain(..tail.len().saturating_sub(16));
246 }
247 }
248 Err(e) if e.kind() == io::ErrorKind::TimedOut || e.kind() == io::ErrorKind::Interrupted => {}
249 Err(e) => return Err(format!("the modem: {e}")),
250 }
251 match port.read_carrier_detect() {
252 Ok(true) => carrier_seen = true,
253 Ok(false) if carrier_seen => return Ok(()),
254 _ => {}
255 }
256 }
257}
258
259fn hang_up(port: &mut Box<dyn SerialPort>) {
262 let _ = port.write_data_terminal_ready(false);
263 thread::sleep(Duration::from_millis(500));
264 if matches!(port.read_carrier_detect(), Ok(false)) {
265 return;
266 }
267 let _ = port.write_data_terminal_ready(true);
268 thread::sleep(GUARD_TIME);
269 let _ = port.write_all(b"+++");
270 thread::sleep(GUARD_TIME);
271 let _ = port.write_all(b"ATH0\r");
272 thread::sleep(Duration::from_millis(200));
273}
274
275pub(crate) fn write(port: &std::sync::Mutex<Box<dyn SerialPort>>, data: &[u8]) -> io::Result<()> {
277 port.lock().unwrap_or_else(|e| e.into_inner()).write_all(data)
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn result_codes() {
286 assert_eq!(verdict("ATDT5551234\r\r\nCONNECT 33600\r\n"), Some(Verdict::Connect));
287 assert_eq!(verdict("ATDT5551234\r\r\nBUSY\r\n"), Some(Verdict::Failed("the line is busy (BUSY)")));
288 assert_eq!(verdict("ATDT5551234\r\r\nCONNE"), None);
290 assert_eq!(verdict("ATDT5551234\r\r\n"), None);
291 assert!(matches!(verdict("\r\nNO DIALTONE\r\n"), Some(Verdict::Failed(_))));
292 }
293}