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}
70
71struct TestBuffer {
72    data: Vec<u8>,
73}
74
75impl TestBuffer {
76    fn new(data: Vec<u8>) -> Self {
77        Self { data }
78    }
79}
80
81impl InputBuffer for TestBuffer {
82    fn available(&self) -> usize {
83        self.data.len()
84    }
85    fn read_to_vec_exact(&mut self, size: usize) -> Result<Vec<u8>, Errno> {
86        if size > self.data.len() {
87            return error!(EAGAIN);
88        }
89        let result = self.data.drain(0..size).collect();
90        Ok(result)
91    }
92}
93
94struct TestOutputBuffer {
95    data: Vec<u8>,
96}
97
98impl TestOutputBuffer {
99    fn new() -> Self {
100        Self { data: vec![] }
101    }
102}
103
104impl OutputBuffer for TestOutputBuffer {
105    fn write(&mut self, data: &[u8]) -> Result<usize, Errno> {
106        self.data.extend_from_slice(data);
107        Ok(data.len())
108    }
109}
110
111fn parse_flags(flags: &[String], mapping: &[(u32, &str)]) -> u32 {
112    let mut result = 0;
113    for flag in flags {
114        if let Some((val, _)) = mapping.iter().find(|(_, name)| name == flag) {
115            result |= val;
116        } else {
117            panic!("Unknown flag {}", flag);
118        }
119    }
120    result
121}
122
123fn get_iflag_mapping() -> Vec<(u32, &'static str)> {
124    vec![
125        (starnix_uapi::IGNBRK, "IGNBRK"),
126        (starnix_uapi::BRKINT, "BRKINT"),
127        (starnix_uapi::IGNPAR, "IGNPAR"),
128        (starnix_uapi::PARMRK, "PARMRK"),
129        (starnix_uapi::INPCK, "INPCK"),
130        (starnix_uapi::ISTRIP, "ISTRIP"),
131        (starnix_uapi::INLCR, "INLCR"),
132        (starnix_uapi::IGNCR, "IGNCR"),
133        (starnix_uapi::ICRNL, "ICRNL"),
134        (starnix_uapi::IUCLC, "IUCLC"),
135        (starnix_uapi::IXON, "IXON"),
136        (starnix_uapi::IXANY, "IXANY"),
137        (starnix_uapi::IXOFF, "IXOFF"),
138        (starnix_uapi::IMAXBEL, "IMAXBEL"),
139        (starnix_uapi::IUTF8, "IUTF8"),
140    ]
141}
142
143fn get_oflag_mapping() -> Vec<(u32, &'static str)> {
144    vec![
145        (starnix_uapi::OPOST, "OPOST"),
146        (starnix_uapi::OLCUC, "OLCUC"),
147        (starnix_uapi::ONLCR, "ONLCR"),
148        (starnix_uapi::OCRNL, "OCRNL"),
149        (starnix_uapi::ONOCR, "ONOCR"),
150        (starnix_uapi::ONLRET, "ONLRET"),
151        (starnix_uapi::OFILL, "OFILL"),
152        (starnix_uapi::OFDEL, "OFDEL"),
153        (starnix_uapi::XTABS, "XTABS"),
154    ]
155}
156
157fn get_lflag_mapping() -> Vec<(u32, &'static str)> {
158    vec![
159        (starnix_uapi::ISIG, "ISIG"),
160        (starnix_uapi::ICANON, "ICANON"),
161        (starnix_uapi::XCASE, "XCASE"),
162        (starnix_uapi::ECHO, "ECHO"),
163        (starnix_uapi::ECHOE, "ECHOE"),
164        (starnix_uapi::ECHOK, "ECHOK"),
165        (starnix_uapi::ECHONL, "ECHONL"),
166        (starnix_uapi::ECHOCTL, "ECHOCTL"),
167        (starnix_uapi::ECHOPRT, "ECHOPRT"),
168        (starnix_uapi::ECHOKE, "ECHOKE"),
169        (starnix_uapi::FLUSHO, "FLUSHO"),
170        (starnix_uapi::NOFLSH, "NOFLSH"),
171        (starnix_uapi::TOSTOP, "TOSTOP"),
172        (starnix_uapi::PENDIN, "PENDIN"),
173        (starnix_uapi::IEXTEN, "IEXTEN"),
174    ]
175}
176
177fn get_cc_mapping() -> HashMap<&'static str, usize> {
178    let mut m = HashMap::new();
179    m.insert("VMIN", starnix_uapi::VMIN as usize);
180    m.insert("VTIME", starnix_uapi::VTIME as usize);
181    m.insert("VINTR", starnix_uapi::VINTR as usize);
182    m.insert("VQUIT", starnix_uapi::VQUIT as usize);
183    m.insert("VERASE", starnix_uapi::VERASE as usize);
184    m.insert("VKILL", starnix_uapi::VKILL as usize);
185    m.insert("VEOF", starnix_uapi::VEOF as usize);
186    m.insert("VSTART", starnix_uapi::VSTART as usize);
187    m.insert("VSTOP", starnix_uapi::VSTOP as usize);
188    m.insert("VSUSP", starnix_uapi::VSUSP as usize);
189    m.insert("VEOL", starnix_uapi::VEOL as usize);
190    m.insert("VREPRINT", starnix_uapi::VREPRINT as usize);
191    m.insert("VDISCARD", starnix_uapi::VDISCARD as usize);
192    m.insert("VWERASE", starnix_uapi::VWERASE as usize);
193    m.insert("VLNEXT", starnix_uapi::VLNEXT as usize);
194    m.insert("VEOL2", starnix_uapi::VEOL2 as usize);
195    m
196}
197
198pub fn test_replay_trace(name: &str, json_data: &str) {
199    println!("Running trace: {}", name);
200    let scenario: Scenario = serde_json::from_str(json_data).unwrap_or_else(|e| {
201        panic!("Failed to parse trace {}: {}", name, e);
202    });
203    run_scenario(scenario);
204}
205
206fn run_scenario(scenario: Scenario) {
207    let iflags = get_iflag_mapping();
208    let oflags = get_oflag_mapping();
209    let lflags = get_lflag_mapping();
210
211    let mut ld = LineDiscipline::default();
212    ld.main_open();
213    ld.replica_open();
214
215    // Set initial termios
216    let mut termios = crate::get_default_termios();
217    termios.c_iflag = parse_flags(&scenario.initial_termios.c_iflag, &iflags);
218    termios.c_oflag = parse_flags(&scenario.initial_termios.c_oflag, &oflags);
219    termios.c_lflag = parse_flags(&scenario.initial_termios.c_lflag, &lflags);
220
221    if let Some(cc) = &scenario.initial_termios.c_cc {
222        let mapping = get_cc_mapping();
223        for (name, &val) in cc {
224            if let Some(&idx) = mapping.get(name.as_str()) {
225                if idx < termios.c_cc.len() {
226                    termios.c_cc[idx] = val;
227                }
228            } else {
229                // Decide if panic or warn. Let's panic for correctness.
230                panic!("Unknown c_cc name {}", name);
231            }
232        }
233    }
234
235    let _ = ld.set_termios(termios);
236
237    for event in scenario.events {
238        match event {
239            Event::WriteToMaster { data } => {
240                let mut buffer = TestBuffer::new(data.to_bytes());
241                let _ = ld.main_write(&mut buffer).expect("main_write failed");
242            }
243            Event::ReadFromMaster { data } => {
244                let mut buffer = TestOutputBuffer::new();
245                loop {
246                    match ld.main_read(&mut buffer) {
247                        Ok(_) => {}
248                        Err(e) if e == (error!(EAGAIN) as Result<(), Errno>).unwrap_err() => {
249                            break;
250                        }
251                        Err(e) => panic!("main_read failed: {:?}", e),
252                    }
253                }
254                assert_eq!(
255                    String::from_utf8_lossy(&buffer.data),
256                    String::from_utf8_lossy(&data.to_bytes()),
257                    "ReadFromMaster mismatch in {}",
258                    scenario.name
259                );
260            }
261            Event::ReadFromSlave { data } => {
262                let mut buffer = TestOutputBuffer::new();
263                loop {
264                    match ld.replica_read(&mut buffer) {
265                        Ok(_) => {}
266                        Err(e) if e == (error!(EAGAIN) as Result<(), Errno>).unwrap_err() => {
267                            break;
268                        }
269                        Err(e) => panic!("replica_read failed: {:?}", e),
270                    }
271                }
272                assert_eq!(
273                    String::from_utf8_lossy(&buffer.data),
274                    String::from_utf8_lossy(&data.to_bytes()),
275                    "ReadFromSlave mismatch in {}",
276                    scenario.name
277                );
278            }
279            Event::WriteToSlave { data } => {
280                let mut buffer = TestBuffer::new(data.to_bytes());
281                let _ = ld.replica_write(&mut buffer).expect("replica_write failed");
282            }
283            Event::WriteToSlaveBlocked { data } => {
284                let mut buffer = TestBuffer::new(data.to_bytes());
285                let result = ld.replica_write(&mut buffer);
286                assert!(
287                    result.is_err(),
288                    "Expected replica_write to block/fail in {}, but it succeeded",
289                    scenario.name
290                );
291                assert_eq!(result, error!(EAGAIN), "Expected EAGAIN in {}", scenario.name);
292            }
293            Event::WriteToSlaveUnexpectedSuccess { data } => {
294                // This event means the trace generator expected it to block but it didn't.
295                // It effectively means "WriteToSlave".
296                // However, for strictness, maybe we should warn?
297                // But if it's in the trace as "Success", we replay it as success.
298                let mut buffer = TestBuffer::new(data.to_bytes());
299                let _ = ld
300                    .replica_write(&mut buffer)
301                    .expect("replica_write failed (unexpected success case)");
302            }
303            Event::SetPacketMode { enabled } => {
304                ld.set_packet_mode(enabled);
305            }
306            Event::SetTermios { termios: ref termios_config } => {
307                let mut termios = crate::get_default_termios();
308                termios.c_iflag = parse_flags(&termios_config.c_iflag, &iflags);
309                termios.c_oflag = parse_flags(&termios_config.c_oflag, &oflags);
310                termios.c_lflag = parse_flags(&termios_config.c_lflag, &lflags);
311                if let Some(cc) = &termios_config.c_cc {
312                    let mapping = get_cc_mapping();
313                    for (name, &val) in cc {
314                        if let Some(&idx) = mapping.get(name.as_str()) {
315                            if idx < termios.c_cc.len() {
316                                termios.c_cc[idx] = val;
317                            }
318                        }
319                    }
320                }
321                let _ = ld.set_termios(termios);
322            }
323            Event::Flush { side, queue_selector } => {
324                let is_main = match side.as_str() {
325                    "main" => true,
326                    "replica" => false,
327                    _ => panic!("Unknown side {}", side),
328                };
329                let queue_selector_val = match queue_selector.as_str() {
330                    "TCIFLUSH" => starnix_uapi::TCIFLUSH,
331                    "TCOFLUSH" => starnix_uapi::TCOFLUSH,
332                    "TCIOFLUSH" => starnix_uapi::TCIOFLUSH,
333                    _ => panic!("Unknown queue_selector {}", queue_selector),
334                };
335                ld.flush(is_main, queue_selector_val).expect("flush failed");
336            }
337        }
338    }
339}