rustler/types/
binary.rs

1//! Safe wrappers around Erlang binaries.
2//!
3//! Rustler provides three binary types: [`Binary`], [`NewBinary`] and
4//! [`OwnedBinary`]. All represent a contiguous region `u8`s, and they all use
5//! the Erlang allocator. The primary difference between them is their ownership
6//! semantics.
7//!
8//! The _owned_ in `OwnedBinary` refers to the fact that it owns the binary it
9//! wraps. The _owner_ of an `OwnedBinary` is free to modify its contents. Ownership
10//! lasts until it is dropped or consumed by converting it into a regular
11//! `Binary`. An `OwnedBinary` cannot be copied or cloned and is thus always moved.
12//!
13//! The `Binary` type is an immutable shared-reference to a binary. `Binary`s are
14//! cheap to copy: all copies of a `Binary` point to the original `Binary`'s
15//! data. Additionally, a `Binary`'s lifetime is tied to that of the NIF's [`Env`],
16//! preventing outstanding references to the data after a NIF returns.
17//!
18//! `NewBinary` is a way of creating a `Binary` without going via `OwnedBinary`.
19//! This can improve performance, since `NewBinary`s can be allocated on the
20//! heap if they are small. Unlike `OwnedBinary`, `NewBinary`s lifetime is tied
21//! to that of the NIF's [`Env`]. `NewBinary` must be converted to a `Binary`
22//! or directly to a `Term` before it can be passed to Erlang.
23//!
24//! # Examples
25//!
26//! Constructing an `OwnedBinary`:
27//!
28//! ```no_run
29//! # use rustler::OwnedBinary;
30//! {
31//!     let mut bin = OwnedBinary::new(5).expect("allocation failed");
32//!     bin.as_mut_slice().copy_from_slice("hello".as_bytes());
33//! } // <- `bin` is dropped here
34//! ```
35//!
36//! The following NIF takes a binary as its only parameter and returns a new binary
37//! where each element is exclusive-or'ed with a constant:
38//!
39//! ```no_run
40//! # use rustler::{Env, OwnedBinary, Binary, NifResult, Error};
41//! #[rustler::nif]
42//! fn xor_example<'a>(env: Env<'a>, bin: Binary<'a>) -> NifResult<Binary<'a>> {
43//!     let mut owned: OwnedBinary = bin.to_owned().ok_or(Error::Term(Box::new("no mem")))?;
44//!     for byte in owned.as_mut_slice() {
45//!         *byte ^= 0xAA;
46//!     }
47//!
48//!     // Ownership of `owned`'s data is transferred to `env` on the
49//!     // following line, so no additional heap allocations are incurred.
50//!     Ok(Binary::from_owned(owned, env))
51//! }
52//! ```
53//!
54//! The contents of a newly-allocated `OwnedBinary` is not initialized to any
55//! particular value. If your usage of the binary requires the it's data to be
56//! zeroed, for example, then you must explicit zero it. In this example, we
57//! manually zeroize the binary before passing it as slice to a third party
58//! function.
59//!
60//! ```no_run
61//! # fn some_third_party_api(buf: &mut [u8]) {
62//! #     for elem in buf {
63//! #         if *elem == 0 { *elem = 1 } else { panic!("Not a zero!") }
64//! #     }
65//! # }
66//! # use rustler::{Env, OwnedBinary, Binary, NifResult, Error};
67//! #[rustler::nif]
68//! fn wrapper_for_some_<'a>(env: Env<'a>) -> NifResult<Binary<'a>> {
69//!     let mut owned = OwnedBinary::new(100).ok_or(Error::Term(Box::new("no mem")))?;
70//!     for byte in owned.as_mut_slice() {
71//!         *byte = 0;
72//!     }
73//!
74//!     // Some third party API which requires the slice to be all zeros on entry.
75//!     some_third_party_api(owned.as_mut_slice());
76//!
77//!     // The imaginary API call presumedly filled in our binary with meaningful
78//!     // data, so let's return it.
79//!     Ok(Binary::from_owned(owned, env))
80//! }
81//!
82//! ```
83//!
84//! [`Binary`]: struct.Binary.html
85//! [`Env`]: ../../env/struct.Env.html
86//! [`OwnedBinary`]: struct.OwnedBinary.html
87
88use crate::{
89    sys::{
90        enif_inspect_binary, enif_inspect_iolist_as_binary, enif_make_binary, enif_make_sub_binary,
91        enif_release_binary,
92    },
93    wrapper::binary::{alloc, new_binary, realloc, ErlNifBinary},
94    Decoder, Encoder, Env, Error, NifResult, Term,
95};
96use std::{
97    borrow::{Borrow, BorrowMut},
98    hash::{Hash, Hasher},
99    io::Write,
100    mem::MaybeUninit,
101    ops::{Deref, DerefMut},
102};
103
104/// An mutable smart-pointer to an Erlang binary.
105///
106/// See [module-level doc](index.html) for more information.
107pub struct OwnedBinary(ErlNifBinary);
108
109impl OwnedBinary {
110    pub unsafe fn from_raw(inner: ErlNifBinary) -> OwnedBinary {
111        OwnedBinary(inner)
112    }
113
114    /// Allocates a new `OwnedBinary` with size `size`.
115    ///
116    /// Memory is not initialized. If uninitialized memory is undesirable, set it
117    /// manually.
118    ///
119    /// # Errors
120    ///
121    /// If allocation fails, `None` is returned.
122    pub fn new(size: usize) -> Option<OwnedBinary> {
123        unsafe { alloc(size) }.map(OwnedBinary)
124    }
125
126    /// Copies `src`'s data into a new `OwnedBinary`.
127    ///
128    /// # Errors
129    ///
130    /// If allocation fails, `None` is returned.
131    pub fn from_unowned(src: &Binary) -> Option<OwnedBinary> {
132        OwnedBinary::new(src.len()).map(|mut b| {
133            b.as_mut_slice().copy_from_slice(src);
134            b
135        })
136    }
137
138    /// Attempts to reallocate `self` with the new size.
139    ///
140    /// Memory outside the range of the original binary will not be initialized. If
141    /// uninitialized memory is undesirable, set it manually.
142    ///
143    /// # Errors
144    ///
145    /// If reallocation fails, `false` is returned. Data remains intact on error.
146    #[must_use]
147    pub fn realloc(&mut self, size: usize) -> bool {
148        unsafe { realloc(&mut self.0, size) }
149    }
150
151    /// Attempts to reallocate `self` with the new size.
152    ///
153    /// If reallocation fails, it will perform a copy instead.
154    ///
155    /// Memory outside the range of the original binary will not be initialized. If
156    /// uninitialized memory is undesirable, set it manually.
157    pub fn realloc_or_copy(&mut self, size: usize) {
158        if !self.realloc(size) {
159            let mut new = OwnedBinary::new(size).unwrap();
160            if let Ok(num_written) = new.as_mut_slice().write(self.as_slice()) {
161                if !(num_written == self.len() || num_written == new.len()) {
162                    panic!("Could not copy binary");
163                }
164                ::std::mem::swap(&mut self.0, &mut new.0);
165            } else {
166                panic!("Could not copy binary");
167            }
168        }
169    }
170
171    /// Extracts a slice containing the entire binary.
172    pub fn as_slice(&self) -> &[u8] {
173        unsafe { ::std::slice::from_raw_parts(self.0.data, self.0.size) }
174    }
175
176    /// Extracts a mutable slice of the entire binary.
177    pub fn as_mut_slice(&mut self) -> &mut [u8] {
178        unsafe { ::std::slice::from_raw_parts_mut(self.0.data, self.0.size) }
179    }
180
181    /// Consumes `self` and returns an immutable `Binary`.
182    ///
183    /// This method is the mirror of [`Binary::from_owned`], and they can be used
184    /// interchangeably.
185    ///
186    /// [`Binary::from_owned`]: struct.Binary.html#method.from_owned
187    pub fn release(self, env: Env) -> Binary {
188        Binary::from_owned(self, env)
189    }
190}
191
192impl Borrow<[u8]> for OwnedBinary {
193    fn borrow(&self) -> &[u8] {
194        self.as_slice()
195    }
196}
197impl BorrowMut<[u8]> for OwnedBinary {
198    fn borrow_mut(&mut self) -> &mut [u8] {
199        self.as_mut_slice()
200    }
201}
202impl Deref for OwnedBinary {
203    type Target = [u8];
204    fn deref(&self) -> &[u8] {
205        self.as_slice()
206    }
207}
208impl DerefMut for OwnedBinary {
209    fn deref_mut(&mut self) -> &mut [u8] {
210        self.as_mut_slice()
211    }
212}
213impl Hash for OwnedBinary {
214    fn hash<H: Hasher>(&self, state: &mut H) {
215        self.as_slice().hash(state);
216    }
217}
218impl PartialEq for OwnedBinary {
219    fn eq(&self, other: &Self) -> bool {
220        self.as_slice() == other.as_slice()
221    }
222}
223impl Eq for OwnedBinary {}
224impl PartialEq<Binary<'_>> for OwnedBinary {
225    fn eq(&self, other: &Binary) -> bool {
226        self.as_slice() == other.as_slice()
227    }
228}
229
230impl Drop for OwnedBinary {
231    fn drop(&mut self) {
232        unsafe { enif_release_binary(&mut self.0) };
233    }
234}
235
236unsafe impl Send for OwnedBinary {}
237unsafe impl Sync for OwnedBinary {}
238
239impl FromIterator<u8> for OwnedBinary {
240    fn from_iter<T: IntoIterator<Item = u8>>(iter: T) -> Self {
241        let mut iter = iter.into_iter();
242        let (lower, upper) = iter.size_hint();
243        let mut bin = OwnedBinary::new(upper.unwrap_or(lower)).expect("Allocation failed");
244        let mut i = 0;
245        loop {
246            match iter.next() {
247                None => {
248                    if bin.len() != i {
249                        bin.realloc_or_copy(i);
250                    }
251                    return bin;
252                }
253                Some(x) => {
254                    if bin.len() <= i {
255                        bin.realloc_or_copy(i + i / 2 + 1);
256                    }
257                    bin.as_mut_slice()[i] = x;
258                    i += 1;
259                }
260            }
261        }
262    }
263}
264
265/// An immutable smart-pointer to an Erlang binary.
266///
267/// See [module-level doc](index.html) for more information.
268#[derive(Copy, Clone)]
269pub struct Binary<'a> {
270    buf: *const u8,
271    size: usize,
272    term: Term<'a>,
273}
274
275impl<'a> Binary<'a> {
276    /// Consumes `owned` and returns an immutable `Binary`.
277    #[inline]
278    pub fn from_owned(owned: OwnedBinary, env: Env<'a>) -> Self {
279        // We are transferring ownership of `owned`'s data to the
280        // environment. Therefore, we need to prevent `owned`'s destructor being
281        // called at the end of this scope. The least error-prone solution (compared
282        // to `mem::forget()`) is to wrap `owned` in a `ManuallyDrop` and EXPLICITLY
283        // NOT CALL `ManuallyDrop::drop()`.
284        let mut owned = std::mem::ManuallyDrop::new(owned);
285        let term = unsafe { Term::new(env, enif_make_binary(env.as_c_arg(), &mut owned.0)) };
286        Binary {
287            buf: owned.0.data,
288            size: owned.0.size,
289            term,
290        }
291    }
292
293    /// Copies `self`'s data into a new `OwnedBinary`.
294    ///
295    /// # Errors
296    ///
297    /// If allocation fails, an error will be returned.
298    #[allow(clippy::wrong_self_convention)]
299    #[inline]
300    pub fn to_owned(&self) -> Option<OwnedBinary> {
301        OwnedBinary::from_unowned(self)
302    }
303
304    /// Creates a `Binary` from `term`.
305    ///
306    /// # Errors
307    ///
308    /// If `term` is not a binary, an error will be returned.
309    #[inline]
310    pub fn from_term(term: Term<'a>) -> Result<Self, Error> {
311        let mut binary = MaybeUninit::uninit();
312        if unsafe {
313            enif_inspect_binary(
314                term.get_env().as_c_arg(),
315                term.as_c_arg(),
316                binary.as_mut_ptr(),
317            )
318        } == 0
319        {
320            return Err(Error::BadArg);
321        }
322
323        let binary = unsafe { binary.assume_init() };
324        Ok(Binary {
325            buf: binary.data,
326            size: binary.size,
327            term,
328        })
329    }
330
331    /// Creates a Binary from a `term` and the associated slice
332    ///
333    /// The `term` *must* be constructed from the given slice, it is not checked.
334    pub(crate) unsafe fn from_term_and_slice(term: Term<'a>, binary: &[u8]) -> Self {
335        Binary {
336            term,
337            buf: binary.as_ptr(),
338            size: binary.len(),
339        }
340    }
341
342    /// Creates a `Binary` from `term`.
343    ///
344    /// # Errors
345    ///
346    /// If `term` is not an `iolist`, an error will be returned.
347    #[inline]
348    pub fn from_iolist(term: Term<'a>) -> Result<Self, Error> {
349        let mut binary = MaybeUninit::uninit();
350        if unsafe {
351            enif_inspect_iolist_as_binary(
352                term.get_env().as_c_arg(),
353                term.as_c_arg(),
354                binary.as_mut_ptr(),
355            )
356        } == 0
357        {
358            return Err(Error::BadArg);
359        }
360
361        let binary = unsafe { binary.assume_init() };
362        Ok(Binary {
363            buf: binary.data,
364            size: binary.size,
365            term,
366        })
367    }
368
369    /// Returns an Erlang term representation of `self`.
370    #[allow(clippy::wrong_self_convention)]
371    #[inline]
372    pub fn to_term<'b>(&self, env: Env<'b>) -> Term<'b> {
373        self.term.in_env(env)
374    }
375
376    /// Extracts a slice containing the entire binary.
377    #[inline]
378    pub fn as_slice(&self) -> &'a [u8] {
379        unsafe { ::std::slice::from_raw_parts(self.buf, self.size) }
380    }
381
382    /// Returns a new view into the same binary.
383    ///
384    /// This method is analogous to subslicing (e.g. `some_data[offset..length]`) in
385    /// that it does not copy nor allocate data.
386    ///
387    /// # Errors
388    ///
389    /// If `offset + length` is out of bounds, an error will be returned.
390    #[inline]
391    pub fn make_subbinary(&self, offset: usize, length: usize) -> NifResult<Binary<'a>> {
392        let min_len = length.checked_add(offset);
393        if min_len.ok_or(Error::BadArg)? > self.size {
394            return Err(Error::BadArg);
395        }
396
397        Ok(unsafe { self.make_subbinary_unchecked(offset, length) })
398    }
399
400    /// Returns a new view into the same binary.
401    ///
402    /// This method is an unsafe variant of `Binary::make_subbinary` in that it does not check for
403    /// `offset + length < self.len()` and always returns a `Binary`.
404    ///
405    /// # Safety
406    ///
407    /// If `offset + length` is out of bounds, this call results in *undefined behavior*. The
408    /// caller has to ensure that `offset + length < self.len()`.
409    #[allow(unused_unsafe)]
410    pub unsafe fn make_subbinary_unchecked(&self, offset: usize, length: usize) -> Binary<'a> {
411        let raw_term = unsafe {
412            enif_make_sub_binary(
413                self.term.get_env().as_c_arg(),
414                self.term.as_c_arg(),
415                offset,
416                length,
417            )
418        };
419        let term = unsafe { Term::new(self.term.get_env(), raw_term) };
420
421        Binary {
422            buf: unsafe { self.buf.add(offset) },
423            size: length,
424            term,
425        }
426    }
427}
428
429impl Borrow<[u8]> for Binary<'_> {
430    fn borrow(&self) -> &[u8] {
431        self.as_slice()
432    }
433}
434impl Deref for Binary<'_> {
435    type Target = [u8];
436    fn deref(&self) -> &[u8] {
437        self.as_slice()
438    }
439}
440
441impl<'a> Decoder<'a> for Binary<'a> {
442    fn decode(term: Term<'a>) -> Result<Self, Error> {
443        Binary::from_term(term)
444    }
445}
446impl Encoder for Binary<'_> {
447    fn encode<'b>(&self, env: Env<'b>) -> Term<'b> {
448        self.to_term(env)
449    }
450}
451impl Hash for Binary<'_> {
452    fn hash<H: Hasher>(&self, state: &mut H) {
453        self.as_slice().hash(state);
454    }
455}
456impl PartialEq for Binary<'_> {
457    fn eq(&self, other: &Self) -> bool {
458        self.as_slice() == other.as_slice()
459    }
460}
461impl Eq for Binary<'_> {}
462impl PartialEq<OwnedBinary> for Binary<'_> {
463    fn eq(&self, other: &OwnedBinary) -> bool {
464        self.as_slice() == other.as_slice()
465    }
466}
467
468/// ## Binary terms
469impl<'a> Term<'a> {
470    pub fn into_binary(self) -> NifResult<Binary<'a>> {
471        Binary::from_term(self)
472    }
473}
474
475/// An newly-created, mutable Erlang binary.
476///
477/// See [module-level doc](index.html) for more information.
478pub struct NewBinary<'a> {
479    buf: *mut u8,
480    size: usize,
481    // safety: we must not expose `term` until it is no longer possible to get a
482    // &mut ref to `buf`.
483    term: Term<'a>,
484}
485
486impl<'a> NewBinary<'a> {
487    /// Allocates a new `NewBinary`
488    #[inline]
489    pub fn new(env: Env<'a>, size: usize) -> Self {
490        let (buf, term) = unsafe { new_binary(env, size) };
491        NewBinary { buf, term, size }
492    }
493    /// Extracts a slice containing the entire binary.
494    #[inline]
495    pub fn as_slice(&self) -> &[u8] {
496        unsafe { ::std::slice::from_raw_parts(self.buf, self.size) }
497    }
498
499    /// Extracts a mutable slice of the entire binary.
500    #[inline]
501    pub fn as_mut_slice(&mut self) -> &mut [u8] {
502        unsafe { ::std::slice::from_raw_parts_mut(self.buf, self.size) }
503    }
504}
505
506impl<'a> From<NewBinary<'a>> for Binary<'a> {
507    #[inline]
508    fn from(new_binary: NewBinary<'a>) -> Self {
509        Binary::from_term(new_binary.term).unwrap()
510    }
511}
512
513impl<'a> From<NewBinary<'a>> for Term<'a> {
514    #[inline]
515    fn from(new_binary: NewBinary<'a>) -> Self {
516        new_binary.term
517    }
518}
519
520impl Deref for NewBinary<'_> {
521    type Target = [u8];
522    #[inline]
523    fn deref(&self) -> &[u8] {
524        self.as_slice()
525    }
526}
527impl DerefMut for NewBinary<'_> {
528    #[inline]
529    fn deref_mut(&mut self) -> &mut [u8] {
530        self.as_mut_slice()
531    }
532}