confium_net_tcp/
transport.rs1use std::io::Read;
14use std::io::Write;
15use std::net::IpAddr;
16use std::net::Shutdown;
17use std::net::SocketAddr;
18use std::net::TcpStream;
19
20use url::Url;
21
22use confium_net::Listener;
23use confium_net::Result;
24use confium_net::Transport;
25use confium_net::error::ClosedSnafu;
26use confium_net::error::IoSnafu;
27use confium_net::error::MalformedUrlSnafu;
28use confium_net::registry::TransportKind;
29use snafu::IntoError;
30
31pub(crate) const MAX_FRAME_LEN: u32 = 8 * 1024 * 1024;
35
36pub(crate) fn write_frame<W: Write>(w: &mut W, data: &[u8]) -> std::io::Result<()> {
42 let len = u32::try_from(data.len()).map_err(|_| {
43 std::io::Error::new(
44 std::io::ErrorKind::InvalidInput,
45 "frame exceeds 4 GiB length-prefix limit",
46 )
47 })?;
48 w.write_all(&len.to_be_bytes())?;
49 w.write_all(data)?;
50 w.flush()?;
51 Ok(())
52}
53
54pub(crate) fn read_frame<R: Read>(r: &mut R, buf: &mut [u8]) -> std::io::Result<Option<usize>> {
68 let mut prefix = [0u8; 4];
69 if !fill_exact(r, &mut prefix)? {
70 return Ok(None);
72 }
73 let len = u32::from_be_bytes(prefix);
74 if len > MAX_FRAME_LEN {
75 return Err(std::io::Error::new(
76 std::io::ErrorKind::InvalidData,
77 format!("frame length {len} exceeds maximum {MAX_FRAME_LEN}"),
78 ));
79 }
80 let len = len as usize;
81 let n = std::cmp::min(len, buf.len());
82 read_exact(r, &mut buf[..n])?;
84 let mut remaining = len - n;
87 let mut sink = [0u8; 4096];
88 while remaining > 0 {
89 let want = remaining.min(sink.len());
90 match r.read(&mut sink[..want])? {
91 0 => {
92 return Err(std::io::Error::new(
93 std::io::ErrorKind::UnexpectedEof,
94 "stream closed mid-frame",
95 ));
96 }
97 got => remaining -= got,
98 }
99 }
100 Ok(Some(n))
101}
102
103fn fill_exact<R: Read>(r: &mut R, buf: &mut [u8]) -> std::io::Result<bool> {
108 let mut filled = 0;
109 while filled < buf.len() {
110 match r.read(&mut buf[filled..])? {
111 0 => {
112 if filled == 0 {
113 return Ok(false);
114 }
115 return Err(std::io::Error::new(
116 std::io::ErrorKind::UnexpectedEof,
117 "stream closed inside length prefix",
118 ));
119 }
120 n => filled += n,
121 }
122 }
123 Ok(true)
124}
125
126fn read_exact<R: Read>(r: &mut R, buf: &mut [u8]) -> std::io::Result<()> {
129 let mut filled = 0;
130 while filled < buf.len() {
131 match r.read(&mut buf[filled..])? {
132 0 => {
133 return Err(std::io::Error::new(
134 std::io::ErrorKind::UnexpectedEof,
135 "stream closed mid-frame",
136 ));
137 }
138 n => filled += n,
139 }
140 }
141 Ok(())
142}
143
144pub struct TcpTransport {
150 stream: Option<TcpStream>,
151}
152
153impl TcpTransport {
154 pub(crate) fn from_stream(stream: TcpStream) -> Self {
157 Self {
158 stream: Some(stream),
159 }
160 }
161
162 pub(crate) fn connect(scheme: &str, host: &str, port: u16) -> std::io::Result<Self> {
165 let stream = match address_family(scheme) {
166 Some(false) => {
167 let ip: IpAddr = host
169 .parse()
170 .map_err(|_| invalid_host(host, "not an IPv4 literal"))?;
171 if !ip.is_ipv4() {
172 return Err(invalid_host(host, "not an IPv4 literal"));
173 }
174 TcpStream::connect(SocketAddr::new(ip, port))?
175 }
176 Some(true) => {
177 let ip: IpAddr = host
179 .parse()
180 .map_err(|_| invalid_host(host, "not an IPv6 literal"))?;
181 if !ip.is_ipv6() {
182 return Err(invalid_host(host, "not an IPv6 literal"));
183 }
184 TcpStream::connect(SocketAddr::new(ip, port))?
185 }
186 None => {
187 TcpStream::connect((host, port))?
191 }
192 };
193 stream.set_nodelay(true).ok();
198 Ok(Self::from_stream(stream))
199 }
200}
201
202impl Transport for TcpTransport {
203 fn send(&mut self, data: &[u8]) -> Result<()> {
204 let stream = match &mut self.stream {
205 Some(s) => s,
206 None => return ClosedSnafu.fail(),
207 };
208 write_frame(stream, data).map_err(io_to_closed)
209 }
210
211 fn recv(&mut self, buf: &mut [u8]) -> Result<usize> {
212 let stream = match &mut self.stream {
213 Some(s) => s,
214 None => return ClosedSnafu.fail(),
215 };
216 match read_frame(stream, buf) {
217 Ok(Some(n)) => Ok(n),
218 Ok(None) => ClosedSnafu.fail(),
220 Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => ClosedSnafu.fail(),
221 Err(e) => Err(io_to_closed(e)),
222 }
223 }
224
225 fn close(&mut self) -> Result<()> {
226 if let Some(stream) = self.stream.take() {
227 stream.shutdown(Shutdown::Both).ok();
229 }
230 Ok(())
231 }
232}
233
234impl Drop for TcpTransport {
235 fn drop(&mut self) {
236 if let Some(stream) = self.stream.take() {
237 stream.shutdown(Shutdown::Both).ok();
238 }
239 }
240}
241
242pub struct TcpTransportKind;
246
247impl TransportKind for TcpTransportKind {
248 fn schemes(&self) -> &'static [&'static str] {
249 &["tcp", "tcp4", "tcp6"]
250 }
251
252 fn connect(&self, url: &Url) -> Result<Box<dyn Transport>> {
253 let scheme = url.scheme();
254 let (host, port) = host_port(url, scheme)?;
255 match TcpTransport::connect(scheme, host, port) {
256 Ok(t) => Ok(Box::new(t)),
257 Err(e) => Err(IoSnafu.into_error(e)),
258 }
259 }
260
261 fn listen(&self, url: &Url) -> Result<Box<dyn Listener>> {
262 let scheme = url.scheme();
263 let (host, port) = host_port(url, scheme)?;
264 match crate::listener::TcpListener::bind(scheme, host, port) {
265 Ok(l) => Ok(Box::new(l)),
266 Err(e) => Err(IoSnafu.into_error(e)),
267 }
268 }
269}
270
271pub(crate) fn host_port<'a>(url: &'a Url, scheme: &str) -> Result<(&'a str, u16)> {
276 let host = url.host_str().unwrap_or("");
277 if host.is_empty() {
278 return MalformedUrlSnafu {
279 scheme,
280 url: url.to_string(),
281 reason: "missing host (use tcp://<host>:<port>)",
282 }
283 .fail();
284 }
285 let port = match url.port() {
286 Some(p) => p,
287 None => {
288 return MalformedUrlSnafu {
289 scheme,
290 url: url.to_string(),
291 reason: "missing port (use tcp://<host>:<port>)",
292 }
293 .fail();
294 }
295 };
296 Ok((host, port))
297}
298
299pub(crate) fn address_family(scheme: &str) -> Option<bool> {
302 match scheme {
303 "tcp4" => Some(false),
304 "tcp6" => Some(true),
305 _ => None,
306 }
307}
308
309fn invalid_host(host: &str, reason: &str) -> std::io::Error {
310 std::io::Error::new(
311 std::io::ErrorKind::InvalidInput,
312 format!("invalid host '{host}': {reason}"),
313 )
314}
315
316pub(crate) fn io_to_closed(_: std::io::Error) -> confium_net::Error {
322 ClosedSnafu.build()
323}