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}