Skip to main content

rustls_ffi/
rslice.rs

1use std::marker::PhantomData;
2use std::ptr::null;
3use std::{fmt, slice, str};
4
5use libc::{c_char, size_t};
6
7/// A read-only view on a Rust byte slice.
8///
9/// This is used to pass data from rustls-ffi to callback functions provided
10/// by the user of the API.
11/// `len` indicates the number of bytes than can be safely read.
12///
13/// The memory exposed is available as specified by the function
14/// using this in its signature. For instance, when this is a parameter to a
15/// callback, the lifetime will usually be the duration of the callback.
16/// Functions that receive one of these must not dereference the data pointer
17/// beyond the allowed lifetime.
18#[repr(C)]
19pub struct rustls_slice_bytes<'a> {
20    pub data: *const u8,
21    pub len: size_t,
22    phantom: PhantomData<&'a [u8]>,
23}
24
25impl<'a> Default for rustls_slice_bytes<'a> {
26    fn default() -> rustls_slice_bytes<'a> {
27        Self {
28            data: &[0u8; 0] as *const u8,
29            len: 0,
30            phantom: PhantomData,
31        }
32    }
33}
34
35impl<'a> From<&'a [u8]> for rustls_slice_bytes<'a> {
36    fn from(s: &[u8]) -> Self {
37        rustls_slice_bytes {
38            data: s.as_ptr(),
39            len: s.len(),
40            phantom: PhantomData,
41        }
42    }
43}
44
45#[test]
46fn test_rustls_slice_bytes() {
47    let bytes = b"abcd";
48    let rsb: rustls_slice_bytes = bytes.as_ref().into();
49    unsafe {
50        assert_eq!(*rsb.data, b'a');
51        assert_eq!(*rsb.data.offset(3), b'd');
52        assert_eq!(rsb.len, 4);
53    }
54}
55
56/// A read-only view of a slice of Rust byte slices.
57///
58/// This is used to pass data from rustls-ffi to callback functions provided
59/// by the user of the API. Because Vec and slice are not `#[repr(C)]`, we
60/// provide access via a pointer to an opaque struct and an accessor method
61/// that acts on that struct to get entries of type `rustls_slice_bytes`.
62/// Internally, the pointee is a `&[&[u8]]`.
63///
64/// The memory exposed is available as specified by the function
65/// using this in its signature. For instance, when this is a parameter to a
66/// callback, the lifetime will usually be the duration of the callback.
67/// Functions that receive one of these must not call its methods beyond the
68/// allowed lifetime.
69pub struct rustls_slice_slice_bytes<'a> {
70    pub(crate) inner: &'a [&'a [u8]],
71}
72
73/// Return the length of the outer slice. If the input pointer is NULL,
74/// returns 0.
75#[no_mangle]
76pub extern "C" fn rustls_slice_slice_bytes_len(input: *const rustls_slice_slice_bytes) -> size_t {
77    match unsafe { input.as_ref() } {
78        Some(c) => c.inner.len(),
79        None => 0,
80    }
81}
82
83/// Retrieve the nth element from the input slice of slices.
84///
85/// If the input pointer is NULL, or n is greater than the length
86/// of the `rustls_slice_slice_bytes`, returns rustls_slice_bytes{NULL, 0}.
87#[no_mangle]
88pub extern "C" fn rustls_slice_slice_bytes_get(
89    input: *const rustls_slice_slice_bytes,
90    n: size_t,
91) -> rustls_slice_bytes {
92    let input = {
93        match unsafe { input.as_ref() } {
94            Some(c) => c,
95            None => {
96                return rustls_slice_bytes {
97                    data: null(),
98                    len: 0,
99                    phantom: PhantomData,
100                };
101            }
102        }
103    };
104    match input.inner.get(n) {
105        Some(rsb) => (*rsb).into(),
106        None => rustls_slice_bytes {
107            data: null(),
108            len: 0,
109            phantom: PhantomData,
110        },
111    }
112}
113
114#[test]
115fn test_rustls_slice_slice_bytes() {
116    let many_bytes: Vec<&[u8]> = vec![b"abcd", b"", b"xyz"];
117    let rssb = rustls_slice_slice_bytes { inner: &many_bytes };
118
119    assert_eq!(rustls_slice_slice_bytes_len(&rssb), 3);
120
121    assert_eq!(rustls_slice_slice_bytes_get(&rssb, 0).len, 4);
122    assert_eq!(rustls_slice_slice_bytes_get(&rssb, 1).len, 0);
123    assert_ne!(rustls_slice_slice_bytes_get(&rssb, 1).data, null());
124    assert_eq!(rustls_slice_slice_bytes_get(&rssb, 2).len, 3);
125    assert_eq!(rustls_slice_slice_bytes_get(&rssb, 3).len, 0);
126    assert_eq!(rustls_slice_slice_bytes_get(&rssb, 3).data, null());
127
128    unsafe {
129        assert_eq!(*rustls_slice_slice_bytes_get(&rssb, 0).data, b'a');
130        assert_eq!(*rustls_slice_slice_bytes_get(&rssb, 0).data.offset(3), b'd');
131        assert_eq!(*rustls_slice_slice_bytes_get(&rssb, 2).data, b'x');
132        assert_eq!(*rustls_slice_slice_bytes_get(&rssb, 2).data.offset(2), b'z');
133    }
134}
135
136/// A read-only view on a Rust `&str`.
137///
138/// The contents are guaranteed to be valid UTF-8.
139///
140/// As an additional guarantee on top of Rust's normal UTF-8 guarantee,
141/// a `rustls_str` is guaranteed not to contain internal NUL bytes, so it is
142/// safe to interpolate into a C string or compare using strncmp. Keep in mind
143/// that it is not NUL-terminated.
144///
145/// The memory exposed is available as specified by the function
146/// using this in its signature. For instance, when this is a parameter to a
147/// callback, the lifetime will usually be the duration of the callback.
148/// Functions that receive one of these must not dereference the data pointer
149/// beyond the allowed lifetime.
150#[repr(C)]
151pub struct rustls_str<'a> {
152    pub data: *const c_char,
153    pub len: size_t,
154    phantom: PhantomData<&'a str>,
155}
156
157/// NulByte represents an error converting `&str` to `rustls_str` when the &str
158/// contains a NUL.
159#[derive(Debug)]
160pub struct NulByte {}
161
162impl<'a> TryFrom<&'a str> for rustls_str<'a> {
163    type Error = NulByte;
164
165    fn try_from(s: &str) -> Result<Self, Self::Error> {
166        if s.contains('\0') {
167            return Err(NulByte {});
168        }
169        Ok(rustls_str {
170            data: s.as_ptr() as *const c_char,
171            len: s.len(),
172            phantom: PhantomData,
173        })
174    }
175}
176
177impl Default for rustls_str<'_> {
178    fn default() -> rustls_str<'static> {
179        Self::from_str_unchecked("")
180    }
181}
182
183/// rustls_str represents a string that can be passed to C code.
184///
185/// The string should not have any internal NUL bytes and is not NUL terminated.
186/// C code should not create rustls_str objects, they should only be created in Rust
187/// code.
188impl rustls_str<'_> {
189    pub fn from_str_unchecked(s: &'static str) -> rustls_str<'static> {
190        rustls_str {
191            data: s.as_ptr() as *const _,
192            len: s.len(),
193            phantom: PhantomData,
194        }
195    }
196
197    /// Change a rustls_str's lifetime to 'static.
198    ///
199    /// This doesn't actually change how long the pointed-to data lives, but
200    /// is necessary when returning a rustls_str (as opposed to passing it
201    /// into a callback), because Rust can't figure out the "real" lifetime.
202    ///
203    /// # Safety
204    ///
205    /// The caller is responsible for requiring (usually via
206    /// documentation) that nothing uses the resulting rustls_str past its
207    /// actual validity period. The validity period is somewhat ill-defined
208    /// at present, but the Stacked Borrows experiment provides one definition,
209    /// by which a shared reference is valid until a mutable reference (to
210    /// the object or a parent object) is created.
211    pub unsafe fn into_static(self) -> rustls_str<'static> {
212        std::mem::transmute(self)
213    }
214
215    /// Change a rustls_str back to a &str.
216    ///
217    /// # Safety
218    ///
219    /// The caller must ensure the rustls_str data is valid utf8
220    pub unsafe fn to_str(&self) -> &str {
221        str::from_utf8_unchecked(slice::from_raw_parts(self.data as *const u8, self.len))
222    }
223}
224
225// If the assertion about Rust code being the only creator of rustls_str objects
226// changes, you must change this Debug impl, since the assertion in it no longer
227// holds.
228impl fmt::Debug for rustls_str<'_> {
229    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230        let raw = unsafe {
231            // Despite the use of "unsafe", we know that this is safe because:
232            // - self.data is a u8, and single bytes are aligned
233            // - the entire memory range is a single allocated object, because
234            // we always build rustls_str objects from a slice (though this may
235            // change if we accept rustls_str objects from C code)
236            // - all values are properly initialized because we only init
237            // rustls_str objects inside of Rust code
238            // - not larger than isize::MAX because, again, it's coming from Rust
239            slice::from_raw_parts(self.data as *const u8, self.len)
240        };
241        let s = str::from_utf8(raw).unwrap_or("%!(ERROR)");
242        f.debug_struct("rustls_str")
243            .field("data", &s)
244            .field("len", &self.len)
245            .finish()
246    }
247}
248
249#[test]
250fn test_rustls_str() {
251    let s = "abcd";
252    let rs: rustls_str = s.try_into().unwrap();
253    assert_eq!(rs.len, 4);
254    unsafe {
255        assert_eq!(*rs.data, 'a' as c_char);
256        assert_eq!(*rs.data.offset(3), 'd' as c_char);
257    }
258    let rs = unsafe { rs.to_str() };
259    assert_eq!(rs, s);
260}
261
262#[cfg(test)]
263mod tests {
264    use crate::rslice::*;
265
266    #[test]
267    fn test_rustls_str_debug() {
268        let s = "abcd";
269        let rs: rustls_str = s.try_into().unwrap();
270        assert_eq!(format!("{rs:?}"), r#"rustls_str { data: "abcd", len: 4 }"#);
271    }
272}
273
274#[test]
275fn test_rustls_str_rejects_nul() {
276    assert!(matches!(rustls_str::try_from("\0"), Err(NulByte {})));
277    assert!(matches!(rustls_str::try_from("abc\0"), Err(NulByte {})));
278    assert!(matches!(rustls_str::try_from("ab\0cd"), Err(NulByte {})));
279}
280
281/// A read-only view of a slice of multiple Rust `&str`'s (that is, multiple
282/// strings).
283///
284/// Like `rustls_str`, this guarantees that each string contains
285/// UTF-8 and no NUL bytes. Strings are not NUL-terminated.
286///
287/// This is used to pass data from rustls-ffi to callback functions provided
288/// by the user of the API. Because Vec and slice are not `#[repr(C)]`, we
289/// can't provide a straightforward `data` and `len` structure. Instead, we
290/// provide access via a pointer to an opaque struct and accessor methods.
291/// Internally, the pointee is a `&[&str]`.
292///
293/// The memory exposed is available as specified by the function
294/// using this in its signature. For instance, when this is a parameter to a
295/// callback, the lifetime will usually be the duration of the callback.
296/// Functions that receive one of these must not call its methods beyond the
297/// allowed lifetime.
298pub struct rustls_slice_str<'a> {
299    pub(crate) inner: &'a [&'a str],
300}
301
302/// Return the length of the outer slice.
303///
304/// If the input pointer is NULL, returns 0.
305#[no_mangle]
306pub extern "C" fn rustls_slice_str_len(input: *const rustls_slice_str) -> size_t {
307    unsafe {
308        match input.as_ref() {
309            Some(c) => c.inner.len(),
310            None => 0,
311        }
312    }
313}
314
315/// Retrieve the nth element from the input slice of `&str`s.
316///
317/// If the input pointer is NULL, or n is greater than the length of the
318/// rustls_slice_str, returns rustls_str{NULL, 0}.
319#[no_mangle]
320pub extern "C" fn rustls_slice_str_get(input: *const rustls_slice_str, n: size_t) -> rustls_str {
321    let input: &rustls_slice_str = unsafe {
322        match input.as_ref() {
323            Some(c) => c,
324            None => {
325                return rustls_str {
326                    data: null(),
327                    len: 0,
328                    phantom: PhantomData,
329                };
330            }
331        }
332    };
333    input
334        .inner
335        .get(n)
336        .and_then(|&s| s.try_into().ok())
337        .unwrap_or(rustls_str {
338            data: null(),
339            len: 0,
340            phantom: PhantomData,
341        })
342}
343
344#[test]
345fn test_rustls_slice_str() {
346    let many_strings = vec!["abcd", "", "xyz"];
347    let rss = rustls_slice_str {
348        inner: &many_strings,
349    };
350
351    assert_eq!(rustls_slice_str_len(&rss), 3);
352
353    assert_eq!(rustls_slice_str_get(&rss, 0).len, 4);
354    assert_eq!(rustls_slice_str_get(&rss, 1).len, 0);
355    assert_ne!(rustls_slice_str_get(&rss, 1).data, null());
356    assert_eq!(rustls_slice_str_get(&rss, 2).len, 3);
357    assert_eq!(rustls_slice_str_get(&rss, 3).len, 0);
358    assert_eq!(rustls_slice_str_get(&rss, 3).data, null());
359
360    unsafe {
361        assert_eq!(*rustls_slice_str_get(&rss, 0).data, 'a' as c_char);
362        assert_eq!(*rustls_slice_str_get(&rss, 0).data.offset(3), 'd' as c_char);
363        assert_eq!(*rustls_slice_str_get(&rss, 2).data, 'x' as c_char);
364        assert_eq!(*rustls_slice_str_get(&rss, 2).data.offset(2), 'z' as c_char);
365    }
366}
367
368/// A read-only view on a Rust slice of 16-bit integers in platform endianness.
369///
370/// This is used to pass data from rustls-ffi to callback functions provided
371/// by the user of the API.
372/// `len` indicates the number of bytes than can be safely read.
373///
374/// The memory exposed is available as specified by the function
375/// using this in its signature. For instance, when this is a parameter to a
376/// callback, the lifetime will usually be the duration of the callback.
377/// Functions that receive one of these must not dereference the data pointer
378/// beyond the allowed lifetime.
379#[repr(C)]
380pub struct rustls_slice_u16<'a> {
381    pub data: *const u16,
382    pub len: size_t,
383    phantom: PhantomData<&'a [u16]>,
384}
385
386impl<'a> From<&'a [u16]> for rustls_slice_u16<'a> {
387    fn from(s: &[u16]) -> Self {
388        rustls_slice_u16 {
389            data: s.as_ptr(),
390            len: s.len(),
391            phantom: PhantomData,
392        }
393    }
394}
395
396#[test]
397fn test_rustls_slice_u16() {
398    let u16s = vec![101, 314, 2718];
399    let rsu: rustls_slice_u16 = (&*u16s).into();
400    assert_eq!(rsu.len, 3);
401    unsafe {
402        assert_eq!(*rsu.data, 101);
403        assert_eq!(*rsu.data.offset(1), 314);
404        assert_eq!(*rsu.data.offset(2), 2718);
405    }
406}