qemu_command_builder/args/
incoming.rs1use 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}