Skip to main content

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}