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)
}
}