Skip to main content

rpc_api/
call_buf.rs

1//! Outgoing call payload with the wire prefix reserved in front of it.
2
3use std::fmt;
4
5/// Payload of an outgoing RPC call, with room reserved ahead of it for the
6/// record mark and call header the driver writes when it sends the call.
7///
8/// Encoding arguments straight into a `CallBuf` lets the driver frame the
9/// call in place: it fills the reserved prefix and hands the buffer to the
10/// socket without copying or shifting the payload. Callers read only the
11/// payload; [`append_with`](Self::append_with) hands a serializer the
12/// record buffer to extend in place, with the prefix kept reserved.
13pub struct CallBuf {
14    /// `buf[..PREFIX_LEN]` is reserved; `buf[PREFIX_LEN..]` is the payload.
15    buf: Vec<u8>,
16}
17
18impl CallBuf {
19    /// Bytes reserved ahead of the payload: a 4-byte record mark and the
20    /// 40-byte RFC 5531 CALL header with `AUTH_NONE` credentials and
21    /// verifier. The driver only ever sends that header, so this is a
22    /// constant of the wire format.
23    pub const PREFIX_LEN: usize = 4 + 40;
24
25    /// Buffer with an empty payload.
26    ///
27    /// Prefer `RpcSender::call_buf`, which recycles a spent buffer instead
28    /// of allocating. There is deliberately no `Default`: a buffer should
29    /// come from a sender unless there is none.
30    #[allow(clippy::new_without_default)]
31    pub fn new() -> Self {
32        Self::from_spare(Vec::new())
33    }
34
35    /// Reuse a spent buffer's allocation: its contents are discarded and the
36    /// prefix re-reserved.
37    ///
38    /// This is driver plumbing for recycling record buffers, not part of the
39    /// supported API; callers get recycled buffers through
40    /// `RpcSender::call_buf`.
41    #[doc(hidden)]
42    pub fn from_spare(mut spare: Vec<u8>) -> Self {
43        spare.clear();
44        spare.resize(Self::PREFIX_LEN, 0);
45        Self { buf: spare }
46    }
47
48    /// The complete record buffer, reserved prefix first.
49    ///
50    /// This is driver plumbing for framing the call, not part of the
51    /// supported API.
52    #[doc(hidden)]
53    pub fn into_vec(self) -> Vec<u8> {
54        self.buf
55    }
56
57    /// Append to the payload through the record buffer itself.
58    ///
59    /// `f` receives the whole record `Vec<u8>`, the reserved prefix followed
60    /// by the payload so far, appends to it, and its return value is passed
61    /// back. Generated protocol clients serialize their argument tuple this
62    /// way, straight into the buffer with the XDR serializer they were
63    /// generated against, so the driver sends the record without copying it
64    /// and this crate carries no serializer dependency of its own.
65    ///
66    /// `f` must only append. The buffer is kept at least
67    /// [`PREFIX_LEN`](Self::PREFIX_LEN) long afterwards, so an `f` that
68    /// shortens it cannot unreserve the prefix; the driver overwrites the
69    /// prefix bytes when it frames the call, so their contents never matter.
70    pub fn append_with<R>(&mut self, f: impl FnOnce(&mut Vec<u8>) -> R) -> R {
71        let result = f(&mut self.buf);
72        if self.buf.len() < Self::PREFIX_LEN {
73            self.buf.resize(Self::PREFIX_LEN, 0);
74        }
75        result
76    }
77
78    /// The payload bytes.
79    pub fn payload(&self) -> &[u8] {
80        &self.buf[Self::PREFIX_LEN..]
81    }
82
83    /// Append `bytes` to the payload.
84    pub fn extend_from_slice(&mut self, bytes: &[u8]) {
85        self.buf.extend_from_slice(bytes);
86    }
87}
88
89impl fmt::Debug for CallBuf {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        f.debug_struct("CallBuf")
92            .field("payload_len", &self.payload().len())
93            .finish()
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    fn call_with(payload: &[u8]) -> CallBuf {
102        let mut call = CallBuf::new();
103        call.extend_from_slice(payload);
104        call
105    }
106
107    #[test]
108    fn new_has_empty_payload_and_full_prefix() {
109        let buf = CallBuf::new();
110        assert!(buf.payload().is_empty());
111        assert_eq!(buf.into_vec().len(), CallBuf::PREFIX_LEN);
112    }
113
114    #[test]
115    fn payload_sits_after_prefix() {
116        let mut buf = call_with(b"abc");
117        buf.extend_from_slice(b"def");
118        assert_eq!(buf.payload(), b"abcdef");
119
120        let vec = buf.into_vec();
121        assert_eq!(vec.len(), CallBuf::PREFIX_LEN + 6);
122        assert_eq!(&vec[CallBuf::PREFIX_LEN..], b"abcdef");
123    }
124
125    #[test]
126    fn append_with_appends_after_prefix_and_returns_value() {
127        let mut buf = call_with(b"pre");
128        let len = buf.append_with(|vec| {
129            vec.extend_from_slice(b"fix");
130            vec.len()
131        });
132        assert_eq!(len, CallBuf::PREFIX_LEN + 6);
133        assert_eq!(buf.payload(), b"prefix");
134    }
135
136    #[test]
137    fn append_with_keeps_prefix_reserved() {
138        let mut buf = call_with(b"abc");
139        buf.append_with(Vec::clear);
140        assert!(buf.payload().is_empty());
141        assert_eq!(buf.into_vec().len(), CallBuf::PREFIX_LEN);
142    }
143
144    #[test]
145    fn from_spare_reuses_allocation_and_reserves_prefix() {
146        let spare = Vec::with_capacity(1024);
147        let ptr = spare.as_ptr();
148        let mut buf = CallBuf::from_spare(spare);
149        assert!(buf.payload().is_empty());
150        buf.extend_from_slice(&[1; 512]);
151        let vec = buf.into_vec();
152        assert_eq!(vec.as_ptr(), ptr);
153        assert_eq!(vec.len(), CallBuf::PREFIX_LEN + 512);
154
155        let dirty = vec![7; 100];
156        assert!(CallBuf::from_spare(dirty).payload().is_empty());
157    }
158}