tokio_uring_xitca/buf/bounded.rs
1use super::{IoBuf, IoBufMut, Slice};
2
3use core::{ops, ptr, slice};
4
5/// A possibly bounded view into an owned [`IoBuf`] buffer.
6///
7/// Because buffers are passed by ownership to the runtime, Rust's slice API
8/// (`&buf[..]`) cannot be used. Instead, `tokio-uring` provides an owned slice
9/// API: [`.slice()`]. The method takes ownership of the buffer and returns a
10/// [`Slice`] value that tracks the requested range.
11///
12/// This trait provides a generic way to use buffers and `Slice` views
13/// into such buffers with `io-uring` operations.
14///
15/// [`.slice()`]: BoundedBuf::slice
16pub trait BoundedBuf: Unpin + 'static {
17 /// The type of the underlying buffer.
18 type Buf: IoBuf;
19
20 /// The type representing the range bounds of the view.
21 type Bounds: ops::RangeBounds<usize>;
22
23 /// Returns a view of the buffer with the specified range.
24 ///
25 /// This method is similar to Rust's slicing (`&buf[..]`), but takes
26 /// ownership of the buffer. The range bounds are specified against
27 /// the possibly offset beginning of the `self` view into the buffer
28 /// and the end bound, if specified, must not exceed the view's total size.
29 /// Note that the range may extend into the uninitialized part of the
30 /// buffer, but it must start (if so bounded) in the initialized part
31 /// or immediately adjacent to it.
32 ///
33 /// # Panics
34 ///
35 /// If the range is invalid with regard to the recipient's total size or
36 /// the length of its initialized part, the implementation of this method
37 /// should panic.
38 ///
39 /// # Examples
40 ///
41 /// ```
42 /// use tokio_uring_xitca::buf::BoundedBuf;
43 ///
44 /// let buf = b"hello world".to_vec();
45 /// let slice = buf.slice(5..10);
46 /// assert_eq!(&slice[..], b" worl");
47 /// let slice = slice.slice(1..3);
48 /// assert_eq!(&slice[..], b"wo");
49 /// ```
50 fn slice(self, range: impl ops::RangeBounds<usize>) -> Slice<Self::Buf>;
51
52 /// Returns a `Slice` with the view's full range.
53 ///
54 /// This method is to be used by the `tokio-uring` runtime and it is not
55 /// expected for users to call it directly.
56 fn slice_full(self) -> Slice<Self::Buf>;
57
58 /// Gets a reference to the underlying buffer.
59 fn get_buf(&self) -> &Self::Buf;
60
61 /// Returns the range bounds for this view.
62 fn bounds(&self) -> Self::Bounds;
63
64 /// Constructs a view from an underlying buffer and range bounds.
65 fn from_buf_bounds(buf: Self::Buf, bounds: Self::Bounds) -> Self;
66
67 /// Like [`IoBuf::stable_ptr`],
68 /// but possibly offset to the view's starting position.
69 fn stable_ptr(&self) -> *const u8;
70
71 /// Number of initialized bytes available via this view.
72 fn bytes_init(&self) -> usize;
73
74 /// Total size of the view, including uninitialized memory, if any.
75 fn bytes_total(&self) -> usize;
76
77 /// Returns a shared reference to the initialized portion of the buffer.
78 fn chunk(&self) -> &[u8] {
79 // Safety: BoundedBuf implementor guarantees stable_ptr points to valid
80 // memory and bytes_init bytes starting from that pointer are initialized.
81 unsafe { slice::from_raw_parts(self.stable_ptr(), self.bytes_init()) }
82 }
83}
84
85impl<T: IoBuf> BoundedBuf for T {
86 type Buf = Self;
87 type Bounds = ops::RangeFull;
88
89 fn slice(self, range: impl ops::RangeBounds<usize>) -> Slice<Self> {
90 use ops::Bound;
91
92 let begin = match range.start_bound() {
93 Bound::Included(&n) => n,
94 Bound::Excluded(&n) => n.checked_add(1).expect("out of range"),
95 Bound::Unbounded => 0,
96 };
97
98 assert!(begin < self.bytes_total());
99
100 let end = match range.end_bound() {
101 Bound::Included(&n) => n.checked_add(1).expect("out of range"),
102 Bound::Excluded(&n) => n,
103 Bound::Unbounded => self.bytes_total(),
104 };
105
106 assert!(end <= self.bytes_total());
107 assert!(begin <= self.bytes_init());
108
109 Slice::new(self, begin, end)
110 }
111
112 fn slice_full(self) -> Slice<Self> {
113 let end = self.bytes_total();
114 Slice::new(self, 0, end)
115 }
116
117 fn get_buf(&self) -> &Self {
118 self
119 }
120
121 fn bounds(&self) -> Self::Bounds {
122 ..
123 }
124
125 fn from_buf_bounds(buf: Self, _: ops::RangeFull) -> Self {
126 buf
127 }
128
129 fn stable_ptr(&self) -> *const u8 {
130 IoBuf::stable_ptr(self)
131 }
132
133 fn bytes_init(&self) -> usize {
134 IoBuf::bytes_init(self)
135 }
136
137 fn bytes_total(&self) -> usize {
138 IoBuf::bytes_total(self)
139 }
140}
141
142/// A possibly bounded view into an owned [`IoBufMut`] buffer.
143///
144/// This trait provides a generic way to use mutable buffers and `Slice` views
145/// into such buffers with `io-uring` operations.
146pub trait BoundedBufMut: BoundedBuf<Buf = Self::BufMut> {
147 /// The type of the underlying buffer.
148 type BufMut: IoBufMut;
149
150 /// Like [`IoBufMut::stable_mut_ptr`],
151 /// but possibly offset to the view's starting position.
152 fn stable_mut_ptr(&mut self) -> *mut u8;
153
154 /// Like [`IoBufMut::set_init`],
155 /// but the position is possibly offset to the view's starting position.
156 ///
157 /// # Safety
158 ///
159 /// The caller must ensure that all bytes starting at `stable_mut_ptr()` up
160 /// to `pos` are initialized and owned by the buffer.
161 unsafe fn set_init(&mut self, pos: usize);
162
163 /// Copies the given byte slice into the buffer, starting at
164 /// this view's offset.
165 ///
166 /// # Panics
167 ///
168 /// If the slice's length exceeds the destination's remaining capacity,
169 /// this method panics.
170 fn put_slice(&mut self, src: &[u8]) {
171 let init = self.bytes_init();
172 assert!(self.bytes_total() - init >= src.len());
173
174 // Safety:
175 // dst pointer validity is ensured by stable_mut_ptr, offset by
176 // bytes_init() to write after already-initialized data;
177 // the length is checked to not exceed the remaining capacity;
178 // src (immutable) and dst (mutable) cannot point to overlapping memory;
179 // after copying, the new initialized watermark is set accordingly.
180 unsafe {
181 let dst = self.stable_mut_ptr().add(init);
182 ptr::copy_nonoverlapping(src.as_ptr(), dst, src.len());
183 self.set_init(init + src.len());
184 }
185 }
186}
187
188impl<T: IoBufMut> BoundedBufMut for T {
189 type BufMut = T;
190
191 fn stable_mut_ptr(&mut self) -> *mut u8 {
192 IoBufMut::stable_mut_ptr(self)
193 }
194
195 unsafe fn set_init(&mut self, pos: usize) {
196 // # Safety
197 //
198 // implementor of T must make sure it's soundness
199 unsafe { IoBufMut::set_init(self, pos) }
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 #[test]
208 fn put_slice_appends() {
209 let mut buf = Vec::with_capacity(64);
210 buf.put_slice(b"hello");
211 assert_eq!(&buf, b"hello");
212
213 buf.put_slice(b" world");
214 assert_eq!(&buf, b"hello world");
215 }
216
217 #[test]
218 fn put_slice_empty() {
219 let mut buf = Vec::with_capacity(16);
220 buf.put_slice(b"");
221 assert!(buf.is_empty());
222
223 buf.put_slice(b"abc");
224 assert_eq!(&buf, b"abc");
225
226 buf.put_slice(b"");
227 assert_eq!(&buf, b"abc");
228 }
229
230 #[test]
231 fn put_slice_fills_capacity() {
232 let mut buf = Vec::with_capacity(5);
233 buf.put_slice(b"ab");
234 buf.put_slice(b"cde");
235 assert_eq!(&buf, b"abcde");
236 assert_eq!(buf.len(), 5);
237 }
238
239 #[test]
240 #[should_panic]
241 fn put_slice_exceeds_capacity() {
242 let mut buf = Vec::with_capacity(4);
243 buf.put_slice(b"abcde");
244 }
245
246 #[test]
247 fn chunk_returns_initialized() {
248 let buf = b"hello".to_vec();
249 assert_eq!(buf.chunk(), b"hello");
250 }
251
252 #[test]
253 fn chunk_empty() {
254 let buf = Vec::<u8>::with_capacity(16);
255 assert_eq!(buf.chunk(), b"");
256 }
257
258 #[test]
259 fn chunk_after_put_slice() {
260 let mut buf = Vec::with_capacity(32);
261 buf.put_slice(b"foo");
262 buf.put_slice(b"bar");
263 assert_eq!(buf.chunk(), b"foobar");
264 }
265}