zlink_core/connection/write_connection.rs
1//! Contains connection related API.
2
3use core::fmt::Debug;
4
5#[cfg(feature = "std")]
6use alloc::collections::VecDeque;
7use alloc::vec::Vec;
8use serde::Serialize;
9
10use super::{BUFFER_SIZE, Call, Reply, socket::WriteHalf};
11
12#[cfg(feature = "std")]
13use std::os::fd::OwnedFd;
14
15/// A connection.
16///
17/// The low-level API to send messages.
18///
19/// # Cancel safety
20///
21/// All async methods of this type are cancel safe unless explicitly stated otherwise in its
22/// documentation.
23#[derive(Debug)]
24pub struct WriteConnection<Write: WriteHalf> {
25 pub(super) socket: Write,
26 pub(super) buffer: Vec<u8>,
27 pub(super) pos: usize,
28 id: usize,
29 #[cfg(feature = "std")]
30 pending_fds: VecDeque<MessageFds>,
31 #[cfg(all(feature = "std", target_os = "linux"))]
32 credentials: Option<crate::connection::PassedCredentials>,
33 // On macOS, SCM_RIGHTS does not properly hold a reference to the underlying file description
34 // when the sender closes the FD in the same process before the receiver calls `recvmsg`. The
35 // receiver ends up with a stale FD. We work around this by keeping sent FDs alive until the
36 // read half confirms it has called `recvmsg` for them via `drain_held_fds`.
37 #[cfg(all(feature = "std", target_os = "macos"))]
38 pub(super) held_fds: VecDeque<Vec<OwnedFd>>,
39}
40
41impl<Write: WriteHalf> WriteConnection<Write> {
42 /// Create a new connection.
43 pub(super) fn new(socket: Write, id: usize) -> Self {
44 Self {
45 socket,
46 id,
47 buffer: alloc::vec![0; BUFFER_SIZE],
48 pos: 0,
49 #[cfg(feature = "std")]
50 pending_fds: VecDeque::new(),
51 #[cfg(all(feature = "std", target_os = "linux"))]
52 credentials: None,
53 #[cfg(all(feature = "std", target_os = "macos"))]
54 held_fds: VecDeque::new(),
55 }
56 }
57
58 /// The unique identifier of the connection.
59 #[inline]
60 pub fn id(&self) -> usize {
61 self.id
62 }
63
64 /// Sends a method call.
65 ///
66 /// The generic `Method` is the type of the method name and its input parameters. This should be
67 /// a type that can serialize itself to a complete method call message, i-e an object containing
68 /// `method` and `parameter` fields. This can be easily achieved using the `serde::Serialize`
69 /// derive:
70 ///
71 /// ```rust
72 /// use serde::{Serialize, Deserialize};
73 /// use serde_prefix_all::prefix_all;
74 ///
75 /// #[prefix_all("org.example.ftl.")]
76 /// #[derive(Debug, Serialize, Deserialize)]
77 /// #[serde(tag = "method", content = "parameters")]
78 /// enum MyMethods<'m> {
79 /// // The name needs to be the fully-qualified name of the error.
80 /// Alpha { param1: u32, param2: &'m str},
81 /// Bravo,
82 /// Charlie { param1: &'m str },
83 /// }
84 /// ```
85 ///
86 /// The `fds` parameter contains file descriptors to send along with the call.
87 pub async fn send_call<Method>(
88 &mut self,
89 call: &Call<Method>,
90 #[cfg(feature = "std")] fds: Vec<OwnedFd>,
91 ) -> crate::Result<()>
92 where
93 Method: Serialize + Debug,
94 {
95 trace!("connection {}: sending call: {:?}", self.id, call);
96 #[cfg(feature = "std")]
97 {
98 self.write(call, fds).await
99 }
100 #[cfg(not(feature = "std"))]
101 {
102 self.write(call).await
103 }
104 }
105
106 /// Send a reply over the socket.
107 ///
108 /// The generic parameter `Params` is the type of the successful reply. This should be a type
109 /// that can serialize itself as the `parameters` field of the reply.
110 ///
111 /// The `fds` parameter contains file descriptors to send along with the reply.
112 pub async fn send_reply<Params>(
113 &mut self,
114 reply: &Reply<Params>,
115 #[cfg(feature = "std")] fds: Vec<OwnedFd>,
116 ) -> crate::Result<()>
117 where
118 Params: Serialize + Debug,
119 {
120 trace!("connection {}: sending reply: {:?}", self.id, reply);
121 #[cfg(feature = "std")]
122 {
123 self.write(reply, fds).await
124 }
125 #[cfg(not(feature = "std"))]
126 {
127 self.write(reply).await
128 }
129 }
130
131 /// Send an error reply over the socket.
132 ///
133 /// The generic parameter `ReplyError` is the type of the error reply. This should be a type
134 /// that can serialize itself to the whole reply object, containing `error` and `parameter`
135 /// fields. This can be easily achieved using the `serde::Serialize` derive (See the code
136 /// snippet in [`super::ReadConnection::receive_reply`] documentation for an example).
137 ///
138 /// The `fds` parameter contains file descriptors to send along with the error.
139 pub async fn send_error<ReplyError>(
140 &mut self,
141 error: &ReplyError,
142 #[cfg(feature = "std")] fds: Vec<OwnedFd>,
143 ) -> crate::Result<()>
144 where
145 ReplyError: Serialize + Debug,
146 {
147 trace!("connection {}: sending error: {:?}", self.id, error);
148 #[cfg(feature = "std")]
149 {
150 self.write(error, fds).await
151 }
152 #[cfg(not(feature = "std"))]
153 {
154 self.write(error).await
155 }
156 }
157
158 /// Enqueue a call to be sent over the socket.
159 ///
160 /// Similar to [`WriteConnection::send_call`], except that the call is not sent immediately but
161 /// enqueued for later sending. This is useful when you want to send multiple calls in a
162 /// batch.
163 ///
164 /// The `fds` parameter contains file descriptors to send along with the call.
165 pub fn enqueue_call<Method>(
166 &mut self,
167 call: &Call<Method>,
168 #[cfg(feature = "std")] fds: Vec<OwnedFd>,
169 ) -> crate::Result<()>
170 where
171 Method: Serialize + Debug,
172 {
173 trace!("connection {}: enqueuing call: {:?}", self.id, call);
174 #[cfg(feature = "std")]
175 {
176 self.enqueue(call, fds)
177 }
178 #[cfg(not(feature = "std"))]
179 {
180 self.enqueue(call)
181 }
182 }
183
184 /// Send out the enqueued calls.
185 pub async fn flush(&mut self) -> crate::Result<()> {
186 if self.pos == 0 {
187 return Ok(());
188 }
189
190 #[allow(unused_mut)]
191 let mut sent_pos = 0;
192
193 #[cfg(feature = "std")]
194 {
195 // While there are FDs, send one message at a time.
196 while !self.pending_fds.is_empty() {
197 // Get the first FD entry.
198 let pending = self.pending_fds.front().unwrap();
199 let fd_offset = pending.offset;
200 let msg_len = pending.len;
201
202 // If there are bytes before the FD message, send them first without FDs.
203 if sent_pos < fd_offset {
204 trace!(
205 "connection {}: flushing {} bytes before FD message",
206 self.id,
207 fd_offset - sent_pos
208 );
209 self.socket
210 .write(
211 &self.buffer[sent_pos..fd_offset],
212 &[] as &[OwnedFd],
213 #[cfg(target_os = "linux")]
214 self.credentials.as_ref(),
215 )
216 .await?;
217 }
218
219 // Send this message with its FDs.
220 let msg_end = fd_offset + msg_len;
221 let MessageFds {
222 fds,
223 offset: _,
224 len: _,
225 } = self.pending_fds.pop_front().unwrap();
226 trace!(
227 "connection {}: flushing {} bytes with {} FDs",
228 self.id,
229 msg_len,
230 fds.len()
231 );
232 self.socket
233 .write(
234 &self.buffer[fd_offset..msg_end],
235 &fds,
236 #[cfg(target_os = "linux")]
237 self.credentials.as_ref(),
238 )
239 .await?;
240 sent_pos = msg_end;
241
242 // On macOS, keep sent FDs alive until the read half confirms receipt via
243 // `recvmsg`. See the comment on the `held_fds` field for details.
244 #[cfg(target_os = "macos")]
245 self.held_fds.push_back(fds);
246 }
247 }
248
249 // No more FDs, send all remaining bytes at once.
250 if sent_pos < self.pos {
251 trace!(
252 "connection {}: flushing {} bytes",
253 self.id,
254 self.pos - sent_pos
255 );
256 #[cfg(feature = "std")]
257 {
258 self.socket
259 .write(
260 &self.buffer[sent_pos..self.pos],
261 &[] as &[OwnedFd],
262 #[cfg(target_os = "linux")]
263 self.credentials.as_ref(),
264 )
265 .await?;
266 }
267 #[cfg(not(feature = "std"))]
268 {
269 self.socket.write(&self.buffer[sent_pos..self.pos]).await?;
270 }
271 }
272
273 self.pos = 0;
274 Ok(())
275 }
276
277 /// The underlying write half of the socket.
278 pub fn write_half(&self) -> &Write {
279 &self.socket
280 }
281
282 /// Set the credentials to send with all subsequent writes.
283 ///
284 /// This is only available for `std` feature and `linux` target.
285 #[cfg(all(feature = "std", target_os = "linux"))]
286 pub fn set_credentials(&mut self, credentials: Option<crate::connection::PassedCredentials>) {
287 self.credentials = credentials;
288 }
289
290 pub(super) async fn write<T>(
291 &mut self,
292 value: &T,
293 #[cfg(feature = "std")] fds: Vec<OwnedFd>,
294 ) -> crate::Result<()>
295 where
296 T: Serialize + ?Sized + Debug,
297 {
298 #[cfg(feature = "std")]
299 {
300 self.enqueue(value, fds)?;
301 }
302 #[cfg(not(feature = "std"))]
303 {
304 self.enqueue(value)?;
305 }
306 self.flush().await
307 }
308
309 pub(super) fn enqueue<T>(
310 &mut self,
311 value: &T,
312 #[cfg(feature = "std")] fds: Vec<OwnedFd>,
313 ) -> crate::Result<()>
314 where
315 T: Serialize + ?Sized + Debug,
316 {
317 #[cfg(feature = "std")]
318 let start_pos = self.pos;
319
320 let len = loop {
321 match crate::json_ser::to_slice(value, &mut self.buffer[self.pos..]) {
322 Ok(len) => break len,
323 Err(crate::json_ser::Error::BufferTooSmall) => {
324 // Buffer too small, grow it and retry
325 self.grow_buffer()?;
326 }
327 Err(crate::json_ser::Error::KeyMustBeAString) => {
328 // Actual serialization error
329 // Convert to serde_json::Error for public API
330 return Err(crate::Error::Json(serde::ser::Error::custom(
331 "key must be a string",
332 )));
333 }
334 }
335 };
336
337 // Add null terminator after this message.
338 if self.pos + len == self.buffer.len() {
339 self.grow_buffer()?;
340 }
341 self.buffer[self.pos + len] = b'\0';
342 self.pos += len + 1;
343
344 // Store FDs with message offset and length.
345 #[cfg(feature = "std")]
346 if !fds.is_empty() {
347 self.pending_fds.push_back(MessageFds {
348 offset: start_pos,
349 len: len + 1, // Include null terminator.
350 fds,
351 });
352 }
353
354 Ok(())
355 }
356
357 fn grow_buffer(&mut self) -> crate::Result<()> {
358 if self.buffer.len() >= super::MAX_BUFFER_SIZE {
359 return Err(crate::Error::BufferOverflow);
360 }
361
362 self.buffer.extend_from_slice(&[0; BUFFER_SIZE]);
363
364 Ok(())
365 }
366}
367
368/// Information about file descriptors pending to be sent with a message.
369#[cfg(feature = "std")]
370#[derive(Debug)]
371struct MessageFds {
372 /// File descriptors to send with this message.
373 fds: Vec<OwnedFd>,
374 /// Buffer offset where the message starts.
375 offset: usize,
376 /// Length of the message including the null terminator.
377 len: usize,
378}