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    c_iflag: Vec<String>,
22    c_oflag: Vec<String>,
23    c_lflag: Vec<String>,
24    #[allow(dead_code)]
25    c_cflag: Option<u32>, // Keeping cflag simple for now or assume default
26    c_cc: Option<HashMap<String, u8>>,
27}
28
29#[derive(Deserialize, Debug)]
30#[serde(untagged)]
31enum TraceData {
32    Bytes(Vec<u8>),
33    String(String),
34}
35
36impl TraceData {
37    fn to_bytes(&self) -> Vec<u8> {
38        match self {
39            TraceData::Bytes(b) => b.clone(),
40            TraceData::String(s) => s.as_bytes().to_vec(),
41        }
42    }
43}
44
45#[derive(Deserialize, Debug)]
46#[serde(tag = "type")]
47enum Event {
48    #[serde(rename = "write_to_master")]
49    WriteToMaster { data: TraceData },
50    #[serde(rename = "read_from_master")]
51    ReadFromMaster { data: TraceData },
52    #[serde(rename = "read_from_slave")]
53    ReadFromSlave { data: TraceData },
54    #[serde(rename = "write_to_slave")]
55    WriteToSlave { data: TraceData },
56    #[serde(rename = "write_to_slave_blocked")]
57    WriteToSlaveBlocked { data: TraceData },
58    #[serde(rename = "write_to_slave_unexpected_success")]
59    WriteToSlaveUnexpectedSuccess { data: TraceData },
60}
61
62struct TestBuffer {
63    data: Vec<u8>,
64}
65
66impl TestBuffer {
67    fn new(data: Vec<u8>) -> Self {
68        Self { data }
69    }
70}
71
72impl InputBuffer for TestBuffer {
73    fn available(&self) -> usize {
74        self.data.len()
75    }
76    fn read_to_vec_exact(&mut self, size: usize) -> Result<Vec<u8>, Errno> {
77        if size > self.data.len() {
78            return error!(EAGAIN);
79        }
80        let result = self.data.drain(0..size).collect();
81        Ok(result)
82    }
83}
84
85struct TestOutputBuffer {
86    data: Vec<u8>,
87}
88
89impl TestOutputBuffer {
90    fn new() -> Self {
91        Self { data: vec![] }
92    }
93}
94
95impl OutputBuffer for TestOutputBuffer {
96    fn write(&mut self, data: &[u8]) -> Result<usize, Errno> {
97        self.data.extend_from_slice(data);
98        Ok(data.len())
99    }
100}
101
102fn parse_flags(flags: &[String], mapping: &[(u32, &str)]) -> u32 {
103    let mut result = 0;
104    for flag in flags {
105        if let Some((val, _)) = mapping.iter().find(|(_, name)| name == flag) {
106            result |= val;
107        } else {
108            panic!("Unknown flag {}", flag);
109        }
110    }
111    result
112}
113
114fn get_iflag_mapping() -> Vec<(u32, &'static str)> {
115    vec![
116        (starnix_uapi::IGNBRK, "IGNBRK"),
117        (starnix_uapi::BRKINT, "BRKINT"),
118        (starnix_uapi::IGNPAR, "IGNPAR"),
119        (starnix_uapi::PARMRK, "PARMRK"),
120        (starnix_uapi::INPCK, "INPCK"),
121        (starnix_uapi::ISTRIP, "ISTRIP"),
122        (starnix_uapi::INLCR, "INLCR"),
123        (starnix_uapi::IGNCR, "IGNCR"),
124        (starnix_uapi::ICRNL, "ICRNL"),
125        (starnix_uapi::IUCLC, "IUCLC"),
126        (starnix_uapi::IXON, "IXON"),
127        (starnix_uapi::IXANY, "IXANY"),
128        (starnix_uapi::IXOFF, "IXOFF"),
129        (starnix_uapi::IMAXBEL, "IMAXBEL"),
130        (starnix_uapi::IUTF8, "IUTF8"),
131    ]
132}
133
134fn get_oflag_mapping() -> Vec<(u32, &'static str)> {
135    vec![
136        (starnix_uapi::OPOST, "OPOST"),
137        (starnix_uapi::OLCUC, "OLCUC"),
138        (starnix_uapi::ONLCR, "ONLCR"),
139        (starnix_uapi::OCRNL, "OCRNL"),
140        (starnix_uapi::ONOCR, "ONOCR"),
141        (starnix_uapi::ONLRET, "ONLRET"),
142        (starnix_uapi::OFILL, "OFILL"),
143        (starnix_uapi::OFDEL, "OFDEL"),
144        (starnix_uapi::XTABS, "XTABS"),
145    ]
146}
147
148fn get_lflag_mapping() -> Vec<(u32, &'static str)> {
149    vec![
150        (starnix_uapi::ISIG, "ISIG"),
151        (starnix_uapi::ICANON, "ICANON"),
152        (starnix_uapi::XCASE, "XCASE"),
153        (starnix_uapi::ECHO, "ECHO"),
154        (starnix_uapi::ECHOE, "ECHOE"),
155        (starnix_uapi::ECHOK, "ECHOK"),
156        (starnix_uapi::ECHONL, "ECHONL"),
157        (starnix_uapi::ECHOCTL, "ECHOCTL"),
158        (starnix_uapi::ECHOPRT, "ECHOPRT"),
159        (starnix_uapi::ECHOKE, "ECHOKE"),
160        (starnix_uapi::FLUSHO, "FLUSHO"),
161        (starnix_uapi::NOFLSH, "NOFLSH"),
162        (starnix_uapi::TOSTOP, "TOSTOP"),
163        (starnix_uapi::PENDIN, "PENDIN"),
164        (starnix_uapi::IEXTEN, "IEXTEN"),
165    ]
166}
167
168fn get_cc_mapping() -> HashMap<&'static str, usize> {
169    let mut m = HashMap::new();
170    m.insert("VMIN", starnix_uapi::VMIN as usize);
171    m.insert("VTIME", starnix_uapi::VTIME as usize);
172    m.insert("VINTR", starnix_uapi::VINTR as usize);
173    m.insert("VQUIT", starnix_uapi::VQUIT as usize);
174    m.insert("VERASE", starnix_uapi::VERASE as usize);
175    m.insert("VKILL", starnix_uapi::VKILL as usize);
176    m.insert("VEOF", starnix_uapi::VEOF as usize);
177    m.insert("VSTART", starnix_uapi::VSTART as usize);
178    m.insert("VSTOP", starnix_uapi::VSTOP as usize);
179    m.insert("VSUSP", starnix_uapi::VSUSP as usize);
180    m.insert("VEOL", starnix_uapi::VEOL as usize);
181    m.insert("VREPRINT", starnix_uapi::VREPRINT as usize);
182    m.insert("VDISCARD", starnix_uapi::VDISCARD as usize);
183    m.insert("VWERASE", starnix_uapi::VWERASE as usize);
184    m.insert("VLNEXT", starnix_uapi::VLNEXT as usize);
185    m.insert("VEOL2", starnix_uapi::VEOL2 as usize);
186    m
187}
188
189pub fn test_replay_trace(name: &str, json_data: &str) {
190    println!("Running trace: {}", name);
191    let scenario: Scenario = serde_json::from_str(json_data).unwrap_or_else(|e| {
192        panic!("Failed to parse trace {}: {}", name, e);
193    });
194    run_scenario(scenario);
195}
196
197fn run_scenario(scenario: Scenario) {
198    let iflags = get_iflag_mapping();
199    let oflags = get_oflag_mapping();
200    let lflags = get_lflag_mapping();
201
202    let mut ld = LineDiscipline::default();
203    ld.main_open();
204    ld.replica_open();
205
206    // Set initial termios
207    let mut termios = crate::get_default_termios();
208    termios.c_iflag = parse_flags(&scenario.initial_termios.c_iflag, &iflags);
209    termios.c_oflag = parse_flags(&scenario.initial_termios.c_oflag, &oflags);
210    termios.c_lflag = parse_flags(&scenario.initial_termios.c_lflag, &lflags);
211
212    if let Some(cc) = &scenario.initial_termios.c_cc {
213        let mapping = get_cc_mapping();
214        for (name, &val) in cc {
215            if let Some(&idx) = mapping.get(name.as_str()) {
216                if idx < termios.c_cc.len() {
217                    termios.c_cc[idx] = val;
218                }
219            } else {
220                // Decide if panic or warn. Let's panic for correctness.
221                panic!("Unknown c_cc name {}", name);
222            }
223        }
224    }
225
226    let _ = ld.set_termios(termios);
227
228    for event in scenario.events {
229        match event {
230            Event::WriteToMaster { data } => {
231                let mut buffer = TestBuffer::new(data.to_bytes());
232                let _ = ld.main_write(&mut buffer).expect("main_write failed");
233            }
234            Event::ReadFromMaster { data } => {
235                let mut buffer = TestOutputBuffer::new();
236                loop {
237                    match ld.main_read(&mut buffer) {
238                        Ok(_) => {}
239                        Err(e) if e == (error!(EAGAIN) as Result<(), Errno>).unwrap_err() => {
240                            break;
241                        }
242                        Err(e) => panic!("main_read failed: {:?}", e),
243                    }
244                }
245                assert_eq!(
246                    String::from_utf8_lossy(&buffer.data),
247                    String::from_utf8_lossy(&data.to_bytes()),
248                    "ReadFromMaster mismatch in {}",
249                    scenario.name
250                );
251            }
252            Event::ReadFromSlave { data } => {
253                let mut buffer = TestOutputBuffer::new();
254                loop {
255                    match ld.replica_read(&mut buffer) {
256                        Ok(_) => {}
257                        Err(e) if e == (error!(EAGAIN) as Result<(), Errno>).unwrap_err() => {
258                            break;
259                        }
260                        Err(e) => panic!("replica_read failed: {:?}", e),
261                    }
262                }
263                assert_eq!(
264                    String::from_utf8_lossy(&buffer.data),
265                    String::from_utf8_lossy(&data.to_bytes()),
266                    "ReadFromSlave mismatch in {}",
267                    scenario.name
268                );
269            }
270            Event::WriteToSlave { data } => {
271                let mut buffer = TestBuffer::new(data.to_bytes());
272                let _ = ld.replica_write(&mut buffer).expect("replica_write failed");
273            }
274            Event::WriteToSlaveBlocked { data } => {
275                let mut buffer = TestBuffer::new(data.to_bytes());
276                let result = ld.replica_write(&mut buffer);
277                assert!(
278                    result.is_err(),
279                    "Expected replica_write to block/fail in {}, but it succeeded",
280                    scenario.name
281                );
282                assert_eq!(result, error!(EAGAIN), "Expected EAGAIN in {}", scenario.name);
283            }
284            Event::WriteToSlaveUnexpectedSuccess { data } => {
285                // This event means the trace generator expected it to block but it didn't.
286                // It effectively means "WriteToSlave".
287                // However, for strictness, maybe we should warn?
288                // But if it's in the trace as "Success", we replay it as success.
289                let mut buffer = TestBuffer::new(data.to_bytes());
290                let _ = ld
291                    .replica_write(&mut buffer)
292                    .expect("replica_write failed (unexpected success case)");
293            }
294        }
295    }
296}