Skip to main content

qemu_command_builder/args/
incoming.rs

1use std::path::PathBuf;
2use std::str::FromStr;
3
4use bon::Builder;
5use proptest_derive::Arbitrary;
6
7use crate::common::OnOff;
8use crate::parsers::{ARG_INCOMING, DELIM_COMMA};
9use crate::to_command::{ToArg, ToCommand};
10
11#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
12pub struct Tcp {
13    host: Option<String>,
14    port: u16,
15    to: Option<u16>,
16    ipv4: Option<OnOff>,
17    ipv6: Option<OnOff>,
18}
19
20#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
21pub struct Rdma {
22    host: String,
23    port: u16,
24    ipv4: Option<OnOff>,
25    ipv6: Option<OnOff>,
26}
27
28#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
29pub struct File {
30    filename: PathBuf,
31    offset: Option<String>,
32}
33
34#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
35pub enum Incoming {
36    Tcp(Tcp),
37    Rdma(Rdma),
38    Unix(PathBuf),
39    Fd(String),
40    File(File),
41    Exec(String),
42    Channel(String),
43    Defer,
44}
45
46impl ToCommand for Incoming {
47    fn command(&self) -> String {
48        ARG_INCOMING.to_string()
49    }
50    fn to_args(&self) -> Vec<String> {
51        match self {
52            Incoming::Tcp(tcp) => {
53                let mut args = vec![];
54                if let Some(host) = &tcp.host {
55                    args.push(format!("tcp:{}:{}", host, tcp.port));
56                } else {
57                    args.push(format!("tcp::{}", tcp.port));
58                }
59                if let Some(to) = &tcp.to {
60                    args.push(format!("to={}", to));
61                }
62                if let Some(ipv4) = &tcp.ipv4 {
63                    args.push(format!("ipv4={}", ipv4.to_arg()));
64                }
65                if let Some(ipv6) = &tcp.ipv6 {
66                    args.push(format!("ipv6={}", ipv6.to_arg()));
67                }
68                vec![args.join(DELIM_COMMA)]
69            }
70            Incoming::Rdma(rdma) => {
71                let mut args = vec![];
72                args.push(format!("rdma:{}:{}", rdma.host, rdma.port));
73                if let Some(ipv4) = &rdma.ipv4 {
74                    args.push(format!("ipv4={}", ipv4.to_arg()));
75                }
76                if let Some(ipv6) = &rdma.ipv6 {
77                    args.push(format!("ipv6={}", ipv6.to_arg()));
78                }
79                vec![args.join(DELIM_COMMA)]
80            }
81            Incoming::Unix(unix) => {
82                vec![format!("unix:{}", unix.display())]
83            }
84            Incoming::Fd(fd) => {
85                vec![format!("fd:{}", fd)]
86            }
87            Incoming::File(file) => {
88                let mut args = vec![format!("file:{}", file.filename.display())];
89                if let Some(offset) = &file.offset {
90                    args.push(format!("offset={}", offset));
91                }
92                vec![args.join(DELIM_COMMA)]
93            }
94            Incoming::Exec(exec) => {
95                vec![format!("exec:{}", exec)]
96            }
97            Incoming::Channel(chrono) => {
98                vec![format!("channel:{}", chrono)]
99            }
100            Incoming::Defer => {
101                vec!["defer".to_string()]
102            }
103        }
104    }
105}
106
107impl FromStr for Incoming {
108    type Err = String;
109
110    fn from_str(s: &str) -> Result<Self, Self::Err> {
111        if s == "defer" {
112            return Ok(Self::Defer);
113        }
114        if let Some(rest) = s.strip_prefix("tcp:") {
115            let mut parts = rest.split(DELIM_COMMA);
116            let endpoint = parts.next().ok_or_else(|| "invalid incoming tcp endpoint".to_string())?;
117            let (host, port) = parse_optional_host_port(endpoint)?;
118            let mut to = None;
119            let mut ipv4 = None;
120            let mut ipv6 = None;
121            for part in parts {
122                let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid incoming tcp option: {part}"))?;
123                match key {
124                    "to" => to = Some(value.parse::<u16>().map_err(|e| e.to_string())?),
125                    "ipv4" => ipv4 = Some(value.parse::<OnOff>().map_err(|_| format!("invalid ipv4 value: {value}"))?),
126                    "ipv6" => ipv6 = Some(value.parse::<OnOff>().map_err(|_| format!("invalid ipv6 value: {value}"))?),
127                    other => return Err(format!("unsupported incoming tcp option: {other}")),
128                }
129            }
130            return Ok(Self::Tcp(Tcp { host, port, to, ipv4, ipv6 }));
131        }
132        if let Some(rest) = s.strip_prefix("rdma:") {
133            let mut parts = rest.split(DELIM_COMMA);
134            let endpoint = parts.next().ok_or_else(|| "invalid incoming rdma endpoint".to_string())?;
135            let (host, port) = endpoint.rsplit_once(':').ok_or_else(|| format!("invalid rdma endpoint: {endpoint}"))?;
136            let mut ipv4 = None;
137            let mut ipv6 = None;
138            for part in parts {
139                let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid incoming rdma option: {part}"))?;
140                match key {
141                    "ipv4" => ipv4 = Some(value.parse::<OnOff>().map_err(|_| format!("invalid ipv4 value: {value}"))?),
142                    "ipv6" => ipv6 = Some(value.parse::<OnOff>().map_err(|_| format!("invalid ipv6 value: {value}"))?),
143                    other => return Err(format!("unsupported incoming rdma option: {other}")),
144                }
145            }
146            return Ok(Self::Rdma(Rdma {
147                host: host.to_string(),
148                port: port.parse::<u16>().map_err(|e| e.to_string())?,
149                ipv4,
150                ipv6,
151            }));
152        }
153        if let Some(path) = s.strip_prefix("unix:") {
154            return Ok(Self::Unix(PathBuf::from(path)));
155        }
156        if let Some(fd) = s.strip_prefix("fd:") {
157            return Ok(Self::Fd(fd.to_string()));
158        }
159        if let Some(rest) = s.strip_prefix("file:") {
160            let mut parts = rest.split(DELIM_COMMA);
161            let filename = PathBuf::from(parts.next().ok_or_else(|| "invalid incoming file endpoint".to_string())?);
162            let mut offset = None;
163            for part in parts {
164                let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid incoming file option: {part}"))?;
165                match key {
166                    "offset" => offset = Some(value.to_string()),
167                    other => return Err(format!("unsupported incoming file option: {other}")),
168                }
169            }
170            return Ok(Self::File(File { filename, offset }));
171        }
172        if let Some(exec) = s.strip_prefix("exec:") {
173            return Ok(Self::Exec(exec.to_string()));
174        }
175        if s.starts_with('{') || s.contains("addr.") || s.contains(',') {
176            return Ok(Self::Channel(s.to_string()));
177        }
178        Ok(Self::Channel(s.to_string()))
179    }
180}
181
182fn parse_optional_host_port(value: &str) -> Result<(Option<String>, u16), String> {
183    let (host, port) = value.rsplit_once(':').ok_or_else(|| format!("invalid host:port endpoint: {value}"))?;
184    let host = if host.is_empty() { None } else { Some(host.to_string()) };
185    Ok((host, port.parse::<u16>().map_err(|e| e.to_string())?))
186}