Skip to main content

line_discipline/testing/
mod.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::*;
6use serde::Deserialize;
7use starnix_uapi::errors::Errno;
8use std::collections::HashMap;
9
10#[derive(Deserialize, Debug)]
11struct Scenario {
12    name: String,
13    initial_termios: TermiosConfig,
14    events: Vec<Event>,
15    #[allow(dead_code)]
16    final_termios: TermiosConfig,
17}
18
19#[derive(Deserialize, Debug)]
20struct TermiosConfig {
21    #[serde(default)]
22    c_iflag: Vec<String>,
23    #[serde(default)]
24    c_oflag: Vec<String>,
25    #[serde(default)]
26    c_lflag: Vec<String>,
27    #[allow(dead_code)]
28    c_cflag: Option<u32>, // Keeping cflag simple for now or assume default
29    c_cc: Option<HashMap<String, u8>>,
30}
31
32#[derive(Deserialize, Debug)]
33#[serde(untagged)]
34enum TraceData {
35    Bytes(Vec<u8>),
36    String(String),
37}
38
39impl TraceData {
40    fn to_bytes(&self) -> Vec<u8> {
41        match self {
42            TraceData::Bytes(b) => b.clone(),
43            TraceData::String(s) => s.as_bytes().to_vec(),
44        }
45    }
46}
47
48#[derive(Deserialize, Debug)]
49#[serde(tag = "type")]
50enum Event {
51    #[serde(rename = "write_to_master")]
52    WriteToMaster { data: TraceData },
53    #[serde(rename = "read_from_master")]
54    ReadFromMaster { data: TraceData },
55    #[serde(rename = "read_from_slave")]
56    ReadFromSlave { data: TraceData },
57    #[serde(rename = "write_to_slave")]
58    WriteToSlave { data: TraceData },
59    #[serde(rename = "write_to_slave_blocked")]
60    WriteToSlaveBlocked { data: TraceData },
61    #[serde(rename = "write_to_slave_unexpected_success")]
62    WriteToSlaveUnexpectedSuccess { data: TraceData },
63    #[serde(rename = "set_packet_mode")]
64    SetPacketMode { enabled: bool },
65    #[serde(rename = "set_termios")]
66    SetTermios { termios: TermiosConfig },
67    #[serde(rename = "flush")]
68    Flush { side: String, queue_selector: String },
69    #[serde(rename = "wait_until_readable")]
70    WaitUntilReadable { side: String },
71}
72
73struct TestBuffer {
74    data: Vec<u8>,
75}
76
77impl TestBuffer {
78    fn new(data: Vec<u8>) -> Self {
79        Self { data }
80    }
81}
82
83impl InputBuffer for TestBuffer {
84    fn available(&self) -> usize {
85        self.data.len()
86    }
87    fn read_to_vec_exact(&mut self, size: usize) -> Result<Vec<u8>, Errno> {
88        if size > self.data.len() {
89            return error!(EAGAIN);
90        }
91        let result = self.data.drain(0..size).collect();
92        Ok(result)
93    }
94}
95
96struct TestOutputBuffer {
97    data: Vec<u8>,
98}
99
100impl TestOutputBuffer {
101    fn new() -> Self {
102        Self { data: vec![] }
103    }
104}
105
106impl OutputBuffer for TestOutputBuffer {
107    fn write(&mut self, data: &[u8]) -> Result<usize, Errno> {
108        self.data.extend_from_slice(data);
109        Ok(data.len())
110    }
111}
112
113fn parse_flags(flags: &[String], mapping: &[(u32, &str)]) -> u32 {
114    let mut result = 0;
115    for flag in flags {
116        if let Some((val, _)) = mapping.iter().find(|(_, name)| name == flag) {
117            result |= val;
118        } else {
119            panic!("Unknown flag {}", flag);
120        }
121    }
122    result
123}
124
125fn get_iflag_mapping() -> Vec<(u32, &'static str)> {
126    vec![
127        (starnix_uapi::IGNBRK, "IGNBRK"),
128        (starnix_uapi::BRKINT, "BRKINT"),
129        (starnix_uapi::IGNPAR, "IGNPAR"),
130        (starnix_uapi::PARMRK, "PARMRK"),
131        (starnix_uapi::INPCK, "INPCK"),
132        (starnix_uapi::ISTRIP, "ISTRIP"),
133        (starnix_uapi::INLCR, "INLCR"),
134        (starnix_uapi::IGNCR, "IGNCR"),
135        (starnix_uapi::ICRNL, "ICRNL"),
136        (starnix_uapi::IUCLC, "IUCLC"),
137        (starnix_uapi::IXON, "IXON"),
138        (starnix_uapi::IXANY, "IXANY"),
139        (starnix_uapi::IXOFF, "IXOFF"),
140        (starnix_uapi::IMAXBEL, "IMAXBEL"),
141        (starnix_uapi::IUTF8, "IUTF8"),
142    ]
143}
144
145fn get_oflag_mapping() -> Vec<(u32, &'static str)> {
146    vec![
147        (starnix_uapi::OPOST, "OPOST"),
148        (starnix_uapi::OLCUC, "OLCUC"),
149        (starnix_uapi::ONLCR, "ONLCR"),
150        (starnix_uapi::OCRNL, "OCRNL"),
151        (starnix_uapi::ONOCR, "ONOCR"),
152        (starnix_uapi::ONLRET, "ONLRET"),
153        (starnix_uapi::OFILL, "OFILL"),
154        (starnix_uapi::OFDEL, "OFDEL"),
155        (starnix_uapi::XTABS, "XTABS"),
156    ]
157}
158
159fn get_lflag_mapping() -> Vec<(u32, &'static str)> {
160    vec![
161        (starnix_uapi::ISIG, "ISIG"),
162        (starnix_uapi::ICANON, "ICANON"),
163        (starnix_uapi::XCASE, "XCASE"),
164        (starnix_uapi::ECHO, "ECHO"),
165        (starnix_uapi::ECHOE, "ECHOE"),
166        (starnix_uapi::ECHOK, "ECHOK"),
167        (starnix_uapi::ECHONL, "ECHONL"),
168        (starnix_uapi::ECHOCTL, "ECHOCTL"),
169        (starnix_uapi::ECHOPRT, "ECHOPRT"),
170        (starnix_uapi::ECHOKE, "ECHOKE"),
171        (starnix_uapi::FLUSHO, "FLUSHO"),
172        (starnix_uapi::NOFLSH, "NOFLSH"),
173        (starnix_uapi::TOSTOP, "TOSTOP"),
174        (starnix_uapi::PENDIN, "PENDIN"),
175        (starnix_uapi::IEXTEN, "IEXTEN"),
176    ]
177}
178
179fn get_cc_mapping() -> HashMap<&'static str, usize> {
180    let mut m = HashMap::new();
181    m.insert("VMIN", starnix_uapi::VMIN as usize);
182    m.insert("VTIME", starnix_uapi::VTIME as usize);
183    m.insert("VINTR", starnix_uapi::VINTR as usize);
184    m.insert("VQUIT", starnix_uapi::VQUIT as usize);
185    m.insert("VERASE", starnix_uapi::VERASE as usize);
186    m.insert("VKILL", starnix_uapi::VKILL as usize);
187    m.insert("VEOF", starnix_uapi::VEOF as usize);
188    m.insert("VSTART", starnix_uapi::VSTART as usize);
189    m.insert("VSTOP", starnix_uapi::VSTOP as usize);
190    m.insert("VSUSP", starnix_uapi::VSUSP as usize);
191    m.insert("VEOL", starnix_uapi::VEOL as usize);
192    m.insert("VREPRINT", starnix_uapi::VREPRINT as usize);
193    m.insert("VDISCARD", starnix_uapi::VDISCARD as usize);
194    m.insert("VWERASE", starnix_uapi::VWERASE as usize);
195    m.insert("VLNEXT", starnix_uapi::VLNEXT as usize);
196    m.insert("VEOL2", starnix_uapi::VEOL2 as usize);
197    m
198}
199
200pub fn test_replay_trace(name: &str, json_data: &str) {
201    println!("Running trace: {}", name);
202    let scenario: Scenario = serde_json::from_str(json_data).unwrap_or_else(|e| {
203        panic!("Failed to parse trace {}: {}", name, e);
204    });
205    run_scenario(scenario);
206}
207
208fn run_scenario(scenario: Scenario) {
209    let iflags = get_iflag_mapping();
210    let oflags = get_oflag_mapping();
211    let lflags = get_lflag_mapping();
212
213    let mut ld = LineDiscipline::default();
214    ld.main_open();
215    ld.replica_open();
216
217    // Set initial termios
218    let mut termios = crate::get_default_termios();
219    termios.c_iflag = parse_flags(&scenario.initial_termios.c_iflag, &iflags);
220    termios.c_oflag = parse_flags(&scenario.initial_termios.c_oflag, &oflags);
221    termios.c_lflag = parse_flags(&scenario.initial_termios.c_lflag, &lflags);
222
223    if let Some(cc) = &scenario.initial_termios.c_cc {
224        let mapping = get_cc_mapping();
225        for (name, &val) in cc {
226            if let Some(&idx) = mapping.get(name.as_str()) {
227                if idx < termios.c_cc.len() {
228                    termios.c_cc[idx] = val;
229                }
230            } else {
231                // Decide if panic or warn. Let's panic for correctness.
232                panic!("Unknown c_cc name {}", name);
233            }
234        }
235    }
236
237    let _ = ld.set_termios(termios);
238
239    for event in scenario.events {
240        match event {
241            Event::WriteToMaster { data } => {
242                let mut buffer = TestBuffer::new(data.to_bytes());
243                let _ = ld.main_write(&mut buffer).expect("main_write failed");
244            }
245            Event::ReadFromMaster { data } => {
246                let mut buffer = TestOutputBuffer::new();
247                loop {
248                    match ld.main_read(&mut buffer) {
249                        Ok(_) => {}
250                        Err(e) if e == (error!(EAGAIN) as Result<(), Errno>).unwrap_err() => {
251                            break;
252                        }
253                        Err(e) => panic!("main_read failed: {:?}", e),
254                    }
255                }
256                assert_eq!(
257                    String::from_utf8_lossy(&buffer.data),
258                    String::from_utf8_lossy(&data.to_bytes()),
259                    "ReadFromMaster mismatch in {}",
260                    scenario.name
261                );
262            }
263            Event::ReadFromSlave { data } => {
264                let mut buffer = TestOutputBuffer::new();
265                loop {
266                    match ld.replica_read(&mut buffer) {
267                        Ok(_) => {}
268                        Err(e) if e == (error!(EAGAIN) as Result<(), Errno>).unwrap_err() => {
269                            break;
270                        }
271                        Err(e) => panic!("replica_read failed: {:?}", e),
272                    }
273                }
274                assert_eq!(
275                    String::from_utf8_lossy(&buffer.data),
276                    String::from_utf8_lossy(&data.to_bytes()),
277                    "ReadFromSlave mismatch in {}",
278                    scenario.name
279                );
280            }
281            Event::WriteToSlave { data } => {
282                let mut buffer = TestBuffer::new(data.to_bytes());
283                let _ = ld.replica_write(&mut buffer).expect("replica_write failed");
284            }
285            Event::WriteToSlaveBlocked { data } => {
286                let mut buffer = TestBuffer::new(data.to_bytes());
287                let result = ld.replica_write(&mut buffer);
288                assert!(
289                    result.is_err(),
290                    "Expected replica_write to block/fail in {}, but it succeeded",
291                    scenario.name
292                );
293                assert_eq!(result, error!(EAGAIN), "Expected EAGAIN in {}", scenario.name);
294            }
295            Event::WriteToSlaveUnexpectedSuccess { data } => {
296                // This event means the trace generator expected it to block but it didn't.
297                // It effectively means "WriteToSlave".
298                // However, for strictness, maybe we should warn?
299                // But if it's in the trace as "Success", we replay it as success.
300                let mut buffer = TestBuffer::new(data.to_bytes());
301                let _ = ld
302                    .replica_write(&mut buffer)
303                    .expect("replica_write failed (unexpected success case)");
304            }
305            Event::SetPacketMode { enabled } => {
306                ld.set_packet_mode(enabled);
307            }
308            Event::SetTermios { termios: ref termios_config } => {
309                let mut termios = crate::get_default_termios();
310                termios.c_iflag = parse_flags(&termios_config.c_iflag, &iflags);
311                termios.c_oflag = parse_flags(&termios_config.c_oflag, &oflags);
312                termios.c_lflag = parse_flags(&termios_config.c_lflag, &lflags);
313                if let Some(cc) = &termios_config.c_cc {
314                    let mapping = get_cc_mapping();
315                    for (name, &val) in cc {
316                        if let Some(&idx) = mapping.get(name.as_str()) {
317                            if idx < termios.c_cc.len() {
318                                termios.c_cc[idx] = val;
319                            }
320                        }
321                    }
322                }
323                let _ = ld.set_termios(termios);
324            }
325            Event::Flush { side, queue_selector } => {
326                let is_main = match side.as_str() {
327                    "main" => true,
328                    "replica" => false,
329                    _ => panic!("Unknown side {}", side),
330                };
331                let queue_selector_val = match queue_selector.as_str() {
332                    "TCIFLUSH" => starnix_uapi::TCIFLUSH,
333                    "TCOFLUSH" => starnix_uapi::TCOFLUSH,
334                    "TCIOFLUSH" => starnix_uapi::TCIOFLUSH,
335                    _ => panic!("Unknown queue_selector {}", queue_selector),
336                };
337                ld.flush(is_main, queue_selector_val).expect("flush failed");
338            }
339            Event::WaitUntilReadable { side } => {
340                // Line discipline read and write operations are blocking, so once the write has
341                // returned, the data is already available in the read queue.
342                //
343                // Instead, we simply assert that there is data to read.
344                struct ZeroSizedBuffer {}
345                impl OutputBuffer for ZeroSizedBuffer {
346                    fn write(&mut self, _data: &[u8]) -> Result<usize, Errno> {
347                        Ok(0)
348                    }
349                }
350                let mut buf = ZeroSizedBuffer {};
351
352                match side.as_str() {
353                    "main" => {
354                        assert_eq!(Ok(0), ld.main_read(&mut buf));
355                    }
356                    "replica" => {
357                        assert_eq!(Ok(0), ld.replica_read(&mut buf));
358                    }
359                    _ => panic!("Unknown side {}", side),
360                };
361            }
362        }
363    }
364}