add bindings for hkdf and update panic handler

Change-Id: Ic0149a69cc27727c2302bc2a90d03839fd5637b5
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/57545
Commit-Queue: Bob Beck <bbe@google.com>
Reviewed-by: Bob Beck <bbe@google.com>
diff --git a/rust/bssl-crypto/src/aes.rs b/rust/bssl-crypto/src/aes.rs
index 0a354db..c2eb4412 100644
--- a/rust/bssl-crypto/src/aes.rs
+++ b/rust/bssl-crypto/src/aes.rs
@@ -79,18 +79,14 @@
     // - key is guaranteed to point to bits/8 bytes determined by the len() * 8 used below.
     // - bits is always a valid AES key size, as defined by the new_aes_* fns defined on the public
     //   key structs.
-    // The expect will never be hit since input key is always a valid AES key size.
-    #[allow(clippy::expect_used)]
-    unsafe {
+    let result = unsafe {
         bssl_sys::AES_set_encrypt_key(
             key.as_ptr(),
             key.len() as core::ffi::c_uint * 8,
             enc_key_uninit.as_mut_ptr(),
         )
-    }
-    .eq(&0)
-    .then_some(())
-    .expect("bssl_sys::AES_set_encrypt_key unexpectedly failed");
+    };
+    assert_eq!(result, 0, "Error occurred in bssl_sys::AES_set_encrypt_key");
 
     // Safety:
     // - since we have checked above that initialization succeeded, this will never be UB
@@ -108,18 +104,14 @@
     // - key is guaranteed to point to bits/8 bytes determined by the len() * 8 used below.
     // - bits is always a valid AES key size, as defined by the new_aes_* fns defined on the public
     //   key structs.
-    // The expect will never be hit since input key is always a valid AES key size.
-    #[allow(clippy::expect_used)]
-    unsafe {
+    let result = unsafe {
         bssl_sys::AES_set_decrypt_key(
             key.as_ptr(),
             key.len() as core::ffi::c_uint * 8,
             dec_key_uninit.as_mut_ptr(),
         )
-    }
-    .eq(&0)
-    .then_some(())
-    .expect("bssl_sys::AES_set_decrypt_key unexpectedly failed");
+    };
+    assert_eq!(result, 0, "Error occurred in bssl_sys::AES_set_decrypt_key");
 
     // Safety:
     // - Since we have checked above that initialization succeeded, this will never be UB.
diff --git a/rust/bssl-crypto/src/digest.rs b/rust/bssl-crypto/src/digest.rs
index cecdfdf..4f19419 100644
--- a/rust/bssl-crypto/src/digest.rs
+++ b/rust/bssl-crypto/src/digest.rs
@@ -26,19 +26,24 @@
 /// A reference to an [`Md`], which abstracts the details of a specific hash function allowing code
 /// to deal with the concept of a "hash function" without needing to know exactly which hash function
 /// it is.
-pub(crate) struct MdRef;
+pub struct MdRef;
 
 unsafe impl ForeignTypeRef for MdRef {
     type CType = bssl_sys::EVP_MD;
 }
 
 /// Used internally to get a BoringSSL internal MD
-pub(crate) trait Md {
-    /// gets a reference to a message digest algorithm to be used by the hkdf implementation
+pub trait Md {
+    /// The output size of the hash operation.
+    const OUTPUT_SIZE: usize;
+
+    /// Gets a reference to a message digest algorithm to be used by the hkdf implementation.
     fn get_md() -> &'static MdRef;
 }
 
 impl Md for Sha256 {
+    const OUTPUT_SIZE: usize = bssl_sys::SHA256_DIGEST_LENGTH as usize;
+
     fn get_md() -> &'static MdRef {
         // Safety:
         // - this always returns a valid pointer to an EVP_MD
@@ -47,6 +52,8 @@
 }
 
 impl Md for Sha512 {
+    const OUTPUT_SIZE: usize = bssl_sys::SHA512_DIGEST_LENGTH as usize;
+
     fn get_md() -> &'static MdRef {
         // Safety:
         // - this always returns a valid pointer to an EVP_MD
diff --git a/rust/bssl-crypto/src/hkdf.rs b/rust/bssl-crypto/src/hkdf.rs
new file mode 100644
index 0000000..db6c5b6
--- /dev/null
+++ b/rust/bssl-crypto/src/hkdf.rs
@@ -0,0 +1,308 @@
+/* Copyright (c) 2023, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+use crate::digest::Md;
+use crate::digest::{Sha256, Sha512};
+use crate::{CSlice, CSliceMut, ForeignTypeRef};
+use core::marker::PhantomData;
+
+/// Implementation of HKDF-SHA-256
+pub type HkdfSha256 = Hkdf<Sha256>;
+
+/// Implementation of HKDF-SHA-512
+pub type HkdfSha512 = Hkdf<Sha512>;
+
+/// Error type returned from the hkdf expand operations when the output key material has
+/// an invalid length
+#[derive(Debug)]
+pub struct InvalidLength;
+
+/// Implementation of hkdf operations which are generic over a provided hashing functions. Type
+/// aliases are provided above for convenience of commonly used hashes
+pub struct Hkdf<M: Md> {
+    salt: Option<Vec<u8>>,
+    ikm: Vec<u8>,
+    _marker: PhantomData<M>,
+}
+
+impl<M: Md> Hkdf<M> {
+    /// The max length of the output key material used for expanding
+    pub const MAX_OUTPUT_LENGTH: usize = M::OUTPUT_SIZE * 255;
+
+    /// Creates a new instance of an hkdf from a salt and key material
+    pub fn new(salt: Option<&[u8]>, ikm: &[u8]) -> Self {
+        Self {
+            salt: salt.map(Vec::from),
+            ikm: Vec::from(ikm),
+            _marker: PhantomData::default(),
+        }
+    }
+
+    /// The RFC5869 HKDF-Expand operation. The info argument for the expand is set to
+    /// the concatenation of all the elements of info_components. Returns InvalidLength if the
+    /// output is too large or panics if there is an allocation failure.
+    pub fn expand_multi_info(
+        &self,
+        info_components: &[&[u8]],
+        okm: &mut [u8],
+    ) -> Result<(), InvalidLength> {
+        self.expand(&info_components.concat(), okm)
+    }
+
+    /// The RFC5869 HKDF-Expand operation. Returns InvalidLength if the output is too large, or
+    /// panics if there is an allocation failure
+    pub fn expand(&self, info: &[u8], okm: &mut [u8]) -> Result<(), InvalidLength> {
+        // extract the salt bytes from the option, or empty slice if option is None
+        let salt = self.salt.as_deref().unwrap_or_default();
+
+        //validate the output size
+        (okm.len() <= Self::MAX_OUTPUT_LENGTH && !okm.is_empty())
+            .then(|| {
+                let mut okm_cslice = CSliceMut::from(okm);
+
+                // Safety:
+                // - We validate the output length above, so invalid length errors will never be hit
+                // which leaves allocation failures as the only possible error case, in which case
+                // we panic immediately
+                let result = unsafe {
+                    bssl_sys::HKDF(
+                        okm_cslice.as_mut_ptr(),
+                        okm_cslice.len(),
+                        M::get_md().as_ptr(),
+                        CSlice::from(self.ikm.as_slice()).as_ptr(),
+                        self.ikm.as_slice().len(),
+                        CSlice::from(salt).as_ptr(),
+                        salt.len(),
+                        CSlice::from(info).as_ptr(),
+                        info.len(),
+                    )
+                };
+                assert!(result > 0, "Allocation failure in bssl_sys::HKDF");
+            })
+            .ok_or(InvalidLength)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::hkdf::{HkdfSha256, HkdfSha512};
+    use core::iter;
+    use hex_literal::hex;
+
+    struct Test<'a> {
+        ikm: &'a [u8],
+        salt: &'a [u8],
+        info: &'a [u8],
+        okm: &'a [u8],
+    }
+
+    #[test]
+    fn hkdf_sha_256_test() {
+        let ikm = hex!("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
+        let salt = hex!("000102030405060708090a0b0c");
+        let info = hex!("f0f1f2f3f4f5f6f7f8f9");
+
+        let hk = HkdfSha256::new(Some(&salt[..]), &ikm);
+        let mut okm = [0u8; 42];
+        hk.expand(&info, &mut okm)
+            .expect("42 is a valid length for Sha256 to output");
+
+        let expected = hex!(
+            "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865"
+        );
+        assert_eq!(okm, expected);
+    }
+
+    #[test]
+    fn hkdf_sha512_test() {
+        let ikm = hex!("5d3db20e8238a90b62a600fa57fdb318");
+        let salt = hex!("1d6f3b38a1e607b5e6bcd4af1800a9d3");
+        let info = hex!("2bc5f39032b6fc87da69ba8711ce735b169646fd");
+
+        let hk = HkdfSha512::new(Some(&salt[..]), &ikm);
+        let mut okm = [0u8; 42];
+        hk.expand(&info, &mut okm).expect("Should succeed");
+
+        let expected = hex!(
+            "8c3cf7122dcb5eb7efaf02718f1faf70bca20dcb75070e9d0871a413a6c05fc195a75aa9ffc349d70aae"
+        );
+        assert_eq!(okm, expected);
+    }
+
+    // Test Vectors from https://tools.ietf.org/html/rfc5869.
+    #[test]
+    fn test_rfc5869_sha256() {
+        let tests = [
+            Test {
+                // Test Case 1
+                ikm: &hex!("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
+                salt: &hex!("000102030405060708090a0b0c"),
+                info: &hex!("f0f1f2f3f4f5f6f7f8f9"),
+                okm: &hex!(
+                    "
+                    3cb25f25faacd57a90434f64d0362f2a
+                    2d2d0a90cf1a5a4c5db02d56ecc4c5bf
+                    34007208d5b887185865
+                "
+                ),
+            },
+            Test {
+                // Test Case 2
+                ikm: &hex!(
+                    "
+                    000102030405060708090a0b0c0d0e0f
+                    101112131415161718191a1b1c1d1e1f
+                    202122232425262728292a2b2c2d2e2f
+                    303132333435363738393a3b3c3d3e3f
+                    404142434445464748494a4b4c4d4e4f
+                "
+                ),
+                salt: &hex!(
+                    "
+                    606162636465666768696a6b6c6d6e6f
+                    707172737475767778797a7b7c7d7e7f
+                    808182838485868788898a8b8c8d8e8f
+                    909192939495969798999a9b9c9d9e9f
+                    a0a1a2a3a4a5a6a7a8a9aaabacadaeaf
+                "
+                ),
+                info: &hex!(
+                    "
+                    b0b1b2b3b4b5b6b7b8b9babbbcbdbebf
+                    c0c1c2c3c4c5c6c7c8c9cacbcccdcecf
+                    d0d1d2d3d4d5d6d7d8d9dadbdcdddedf
+                    e0e1e2e3e4e5e6e7e8e9eaebecedeeef
+                    f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff
+                "
+                ),
+                okm: &hex!(
+                    "
+                    b11e398dc80327a1c8e7f78c596a4934
+                    4f012eda2d4efad8a050cc4c19afa97c
+                    59045a99cac7827271cb41c65e590e09
+                    da3275600c2f09b8367793a9aca3db71
+                    cc30c58179ec3e87c14c01d5c1f3434f
+                    1d87
+                "
+                ),
+            },
+            Test {
+                // Test Case 3
+                ikm: &hex!("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
+                salt: &hex!(""),
+                info: &hex!(""),
+                okm: &hex!(
+                    "
+                    8da4e775a563c18f715f802a063c5a31
+                    b8a11f5c5ee1879ec3454e5f3c738d2d
+                    9d201395faa4b61a96c8
+                "
+                ),
+            },
+        ];
+        for Test {
+            ikm,
+            salt,
+            info,
+            okm,
+        } in tests.iter()
+        {
+            let salt = if salt.is_empty() {
+                None
+            } else {
+                Some(&salt[..])
+            };
+            let hkdf = HkdfSha256::new(salt, ikm);
+            let mut okm2 = vec![0u8; okm.len()];
+            assert!(hkdf.expand(&info[..], &mut okm2).is_ok());
+            assert_eq!(okm2[..], okm[..]);
+        }
+    }
+
+    #[test]
+    fn test_lengths() {
+        let hkdf = HkdfSha256::new(None, &[]);
+        let mut longest = vec![0u8; HkdfSha256::MAX_OUTPUT_LENGTH];
+        assert!(hkdf.expand(&[], &mut longest).is_ok());
+        // start at 1 since 0 is an invalid length
+        let lengths = 1..HkdfSha256::MAX_OUTPUT_LENGTH + 1;
+
+        for length in lengths {
+            let mut okm = vec![0u8; length];
+
+            assert!(hkdf.expand(&[], &mut okm).is_ok());
+            assert_eq!(okm.len(), length);
+            assert_eq!(okm[..], longest[..length]);
+        }
+    }
+
+    #[test]
+    fn test_max_length() {
+        let hkdf = HkdfSha256::new(Some(&[]), &[]);
+        let mut okm = vec![0u8; HkdfSha256::MAX_OUTPUT_LENGTH];
+        assert!(hkdf.expand(&[], &mut okm).is_ok());
+    }
+
+    #[test]
+    fn test_max_length_exceeded() {
+        let hkdf = HkdfSha256::new(Some(&[]), &[]);
+        let mut okm = vec![0u8; HkdfSha256::MAX_OUTPUT_LENGTH + 1];
+        assert!(hkdf.expand(&[], &mut okm).is_err());
+    }
+
+    #[test]
+    fn test_unsupported_length() {
+        let hkdf = HkdfSha256::new(Some(&[]), &[]);
+        let mut okm = vec![0u8; 90000];
+        assert!(hkdf.expand(&[], &mut okm).is_err());
+    }
+
+    #[test]
+    fn test_expand_multi_info() {
+        let info_components = &[
+            &b"09090909090909090909090909090909090909090909"[..],
+            &b"8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a"[..],
+            &b"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0"[..],
+            &b"4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4"[..],
+            &b"1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d"[..],
+        ];
+
+        let hkdf = HkdfSha256::new(None, b"some ikm here");
+
+        // Compute HKDF-Expand on the concatenation of all the info components
+        let mut oneshot_res = [0u8; 16];
+        hkdf.expand(&info_components.concat(), &mut oneshot_res)
+            .unwrap();
+
+        // Now iteratively join the components of info_components until it's all 1 component. The value
+        // of HKDF-Expand should be the same throughout
+        let mut num_concatted = 0;
+        let mut info_head = Vec::new();
+
+        while num_concatted < info_components.len() {
+            info_head.extend(info_components[num_concatted]);
+
+            // Build the new input to be the info head followed by the remaining components
+            let input: Vec<&[u8]> = iter::once(info_head.as_slice())
+                .chain(info_components.iter().cloned().skip(num_concatted + 1))
+                .collect();
+
+            // Compute and compare to the one-shot answer
+            let mut multipart_res = [0u8; 16];
+            hkdf.expand_multi_info(&input, &mut multipart_res).unwrap();
+            assert_eq!(multipart_res, oneshot_res);
+            num_concatted += 1;
+        }
+    }
+}
diff --git a/rust/bssl-crypto/src/hmac.rs b/rust/bssl-crypto/src/hmac.rs
index 3933966..3da5f31 100644
--- a/rust/bssl-crypto/src/hmac.rs
+++ b/rust/bssl-crypto/src/hmac.rs
@@ -14,7 +14,7 @@
  */
 use crate::{
     digest::{Md, Sha256, Sha512},
-    CSlice, ForeignTypeRef as _, PanicResultHandler,
+    CSlice, ForeignTypeRef as _,
 };
 use core::{
     ffi::{c_uint, c_void},
@@ -22,25 +22,25 @@
     ptr,
 };
 
-/// Computes the HMAC-SHA-256 of `data` as a one-shot operation.
+/// Computes the HMAC-SHA256 of `data` as a one-shot operation.
 ///
 /// Calculates the HMAC of data, using the given `key` and returns the result.
 /// It returns the computed hmac.
 /// Can panic if memory allocation fails in the underlying BoringSSL code.
-pub fn hmac_sha_256(key: &[u8], data: &[u8]) -> [u8; 32] {
+pub fn hmac_sha256(key: &[u8], data: &[u8]) -> [u8; 32] {
     hmac::<32, Sha256>(key, data)
 }
 
-/// Computes the HMAC-SHA-512 of `data` as a one-shot operation.
+/// Computes the HMAC-SHA512 of `data` as a one-shot operation.
 ///
 /// Calculates the HMAC of data, using the given `key` and returns the result.
 /// It returns the computed hmac.
 /// Can panic if memory allocation fails in the underlying BoringSSL code.
-pub fn hmac_sha_512(key: &[u8], data: &[u8]) -> [u8; 64] {
+pub fn hmac_sha512(key: &[u8], data: &[u8]) -> [u8; 64] {
     hmac::<64, Sha512>(key, data)
 }
 
-/// The BoringSSL HMAC-SHA-256 implementation. The operations may panic if memory allocation fails
+/// The BoringSSL HMAC-SHA256 implementation. The operations may panic if memory allocation fails
 /// in BoringSSL.
 pub struct HmacSha256(Hmac<32, Sha256>);
 
@@ -81,7 +81,7 @@
     }
 }
 
-/// The BoringSSL HMAC-SHA-512 implementation. The operations may panic if memory allocation fails
+/// The BoringSSL HMAC-SHA512 implementation. The operations may panic if memory allocation fails
 /// in BoringSSL.
 pub struct HmacSha512(Hmac<64, Sha512>);
 
@@ -139,7 +139,7 @@
     // Safety:
     // - buf always contains N bytes of space
     // - If NULL is returned on error we panic immediately
-    unsafe {
+    let result = unsafe {
         bssl_sys::HMAC(
             M::get_md().as_ptr(),
             CSlice::from(key).as_ptr(),
@@ -149,8 +149,8 @@
             out.as_mut_ptr(),
             &mut size as *mut c_uint,
         )
-    }
-    .panic_if_error();
+    };
+    assert!(!result.is_null(), "Result of bssl_sys::HMAC was null");
 
     out
 }
@@ -177,7 +177,10 @@
         // Safety:
         // - HMAC_CTX_new panics if allocation fails
         let ctx = unsafe { bssl_sys::HMAC_CTX_new() };
-        ctx.panic_if_error();
+        assert!(
+            !ctx.is_null(),
+            "result of bssl_sys::HMAC_CTX_new() was null"
+        );
 
         // Safety:
         // - HMAC_Init_ex must be called with a context previously created with HMAC_CTX_new,
@@ -185,7 +188,7 @@
         // - HMAC_Init_ex may return an error if key is null but the md is different from
         //   before. This is avoided here since key is guaranteed to be non-null.
         // - HMAC_Init_ex returns 0 on allocation failure in which case we panic
-        unsafe {
+        let result = unsafe {
             bssl_sys::HMAC_Init_ex(
                 ctx,
                 CSlice::from(key).as_ptr() as *const c_void,
@@ -193,8 +196,8 @@
                 M::get_md().as_ptr(),
                 ptr::null_mut(),
             )
-        }
-        .panic_if_error();
+        };
+        assert!(result > 0, "Allocation failure in bssl_sys::HMAC_Init_ex");
 
         Self {
             ctx,
@@ -204,11 +207,11 @@
 
     /// Update state using the provided data, can be called repeatedly.
     fn update(&mut self, data: &[u8]) {
-        unsafe {
+        let result = unsafe {
             // Safety: HMAC_Update will always return 1, in case it doesnt we panic
             bssl_sys::HMAC_Update(self.ctx, data.as_ptr(), data.len())
-        }
-        .panic_if_error()
+        };
+        assert_eq!(result, 1, "failure in bssl_sys::HMAC_Update");
     }
 
     /// Obtain the hmac computation consuming the hmac instance.
@@ -219,8 +222,9 @@
         // - hmac has a fixed size output of N which will never exceed the length of an N
         // length array
         // - on allocation failure we panic
-        unsafe { bssl_sys::HMAC_Final(self.ctx, buf.as_mut_ptr(), &mut size as *mut c_uint) }
-            .panic_if_error();
+        let result =
+            unsafe { bssl_sys::HMAC_Final(self.ctx, buf.as_mut_ptr(), &mut size as *mut c_uint) };
+        assert!(result > 0, "Allocation failure in bssl_sys::HMAC_Final");
         buf
     }
 
@@ -338,7 +342,7 @@
         ];
         let key: [u8; 20] = [0x0b; 20];
         let data = b"Hi There";
-        let hmac_result = hmac_sha_256(&key, data);
+        let hmac_result = hmac_sha256(&key, data);
         assert_eq!(&hmac_result, &expected_hmac);
     }
 
diff --git a/rust/bssl-crypto/src/lib.rs b/rust/bssl-crypto/src/lib.rs
index 2c8ac72..9c4a214 100644
--- a/rust/bssl-crypto/src/lib.rs
+++ b/rust/bssl-crypto/src/lib.rs
@@ -24,46 +24,30 @@
 //! Rust boringssl binding
 
 extern crate core;
-use core::ops::Not;
 
 /// BoringSSL implemented plain aes operations.
 pub mod aes;
 
-/// BoringSSL implemented hmac operations.
-pub mod hmac;
-
 /// BoringSSL implemented hash functions.
 pub mod digest;
 
-/// Used for handling result types from C APIs.
-trait PanicResultHandler {
-    /// Panics if a C api returns an invalid result
-    /// Used for APIs which return error codes for allocation failures.
-    fn panic_if_error(&self);
-}
+/// BoringSSL implemented hkdf operations.
+pub mod hkdf;
 
-impl PanicResultHandler for i32 {
-    /// BoringSSL APIs return 1 on success or 0 on allocation failure.
-    #[allow(clippy::expect_used)]
-    fn panic_if_error(&self) {
-        self.gt(&0).then_some(()).expect("allocation failed!")
-    }
-}
+/// BoringSSL implemented hmac operations.
+pub mod hmac;
 
-impl<T> PanicResultHandler for *mut T {
-    /// Boringssl APIs return NULL on allocation failure for APIs that return a CTX.
-    #[allow(clippy::expect_used)]
-    fn panic_if_error(&self) {
-        self.is_null()
-            .not()
-            .then_some(())
-            .expect("allocation failed!")
-    }
-}
-
+/// This is a helper struct which provides functions for passing slices over FFI.
 struct CSlice<'a>(&'a [u8]);
 
+impl<'a> From<&'a [u8]> for CSlice<'a> {
+    fn from(value: &'a [u8]) -> Self {
+        Self(value)
+    }
+}
+
 impl CSlice<'_> {
+    /// Returns a raw pointer to the value, which is safe to pass over FFI.
     pub fn as_ptr<T>(&self) -> *const T {
         if self.0.is_empty() {
             std::ptr::null()
@@ -73,8 +57,26 @@
     }
 }
 
-impl<'a> From<&'a [u8]> for CSlice<'a> {
-    fn from(value: &'a [u8]) -> Self {
+/// This is a helper struct which provides functions for passing mutable slices over FFI.
+struct CSliceMut<'a>(&'a mut [u8]);
+
+impl CSliceMut<'_> {
+    /// Returns a raw pointer to the value, which is safe to pass over FFI.
+    pub fn as_mut_ptr<T>(&mut self) -> *mut T {
+        if self.0.is_empty() {
+            std::ptr::null_mut()
+        } else {
+            self.0.as_mut_ptr() as *mut T
+        }
+    }
+
+    pub fn len(&self) -> usize {
+        self.0.len()
+    }
+}
+
+impl<'a> From<&'a mut [u8]> for CSliceMut<'a> {
+    fn from(value: &'a mut [u8]) -> Self {
         Self(value)
     }
 }