pw_bytes/
pw_bytes.rs

1// Copyright 2024 The Pigweed Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4// use this file except in compliance with the License. You may obtain a copy of
5// the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations under
13// the License.
14
15//! pw_bytes is a collection of utilities for manipulating binary data.
16//!
17//! # Features
18//! pw_bytes contains the follow features:
19//! * macros for concatenating `const [u8]`s and `&'static str`s.
20//!
21//! # Examples
22//! ```
23//! use pw_bytes::concat_const_u8_slices;
24//!
25//! // Concatenate two slices.
26//! const SLICE_A: &[u8] = b"abc";
27//! const SLICE_B: &[u8] = b"def";
28//! const SLICE_AB: &[u8] = concat_const_u8_slices!(SLICE_A, SLICE_B);
29//! assert_eq!(SLICE_AB, b"abcdef");
30//! ```
31//!
32//! ```
33//! use pw_bytes::concat_static_strs;
34//!
35//! // Concatenate two strings.
36//! const STR_A: &'static str = "abc";
37//! const STR_B: &'static str = "def";
38//! const STR_AB: &'static str = concat_static_strs!(STR_A, STR_B);
39//! assert_eq!(STR_AB, "abcdef");
40//!
41//! ```
42#![no_std]
43#![deny(missing_docs)]
44
45/// Concatenates multiple `const [u8]`s into one.
46///
47/// Returns a `const [u8]`
48#[macro_export]
49macro_rules! concat_const_u8_slices {
50  ($($slice:expr),+) => {{
51      // Calculate the length of the resulting array.  Because `+` is not a
52      // valid `MacroRepSep` (see https://doc.rust-lang.org/reference/macros-by-example.html#macros-by-example),
53      // we must precede the variadic expansion with a 0 and embed the + in
54      // the expansion itself.
55      const TOTAL_LEN: usize = 0 $(+ $slice.len())+;
56      const ARRAY: [u8; TOTAL_LEN] = {
57          let mut array = [0u8; TOTAL_LEN];
58          let mut array_index = 0;
59
60          // For each input slice, copy its contents into `array`.
61          $({
62              // Using while loop as for loops are not allowed in `const` expressions
63              let mut slice_index = 0;
64              while slice_index < $slice.len() {
65                  array[array_index] = $slice[slice_index];
66                  array_index += 1;
67                  slice_index += 1;
68              }
69          })+;
70
71          array
72      };
73      &ARRAY
74  }}
75}
76
77/// Concatenates multiple `const &'static str`s into one.
78///
79/// Returns a `const &'static str`
80#[macro_export]
81macro_rules! concat_static_strs {
82  ($($string:expr),+) => {{
83    // Safety: we're building a byte array of known valid utf8 strings so the
84    // resulting string is guaranteed to be valid utf8.
85    unsafe{
86      core::str::from_utf8_unchecked($crate::concat_const_u8_slices!($($string.as_bytes()),+))
87    }
88  }}
89}
90
91#[cfg(test)]
92mod tests {
93    #[test]
94    fn one_const_slice_concatenates_correctly() {
95        const SLICE_A: &[u8] = b"abc";
96        const SLICE_A_PRIME: &[u8] = concat_const_u8_slices!(SLICE_A);
97        assert_eq!(SLICE_A_PRIME, b"abc");
98    }
99
100    #[test]
101    fn two_const_slices_concatenates_correctly() {
102        const SLICE_A: &[u8] = b"abc";
103        const SLICE_B: &[u8] = b"def";
104        const SLICE_AB: &[u8] = concat_const_u8_slices!(SLICE_A, SLICE_B);
105        assert_eq!(SLICE_AB, b"abcdef");
106    }
107
108    #[test]
109    fn three_const_slices_concatenates_correctly() {
110        const SLICE_A: &[u8] = b"abc";
111        const SLICE_B: &[u8] = b"def";
112        const SLICE_C: &[u8] = b"ghi";
113        const SLICE_ABC: &[u8] = concat_const_u8_slices!(SLICE_A, SLICE_B, SLICE_C);
114        assert_eq!(SLICE_ABC, b"abcdefghi");
115    }
116
117    #[test]
118    fn empty_first_const_slice_concatenates_correctly() {
119        const SLICE_A: &[u8] = b"";
120        const SLICE_B: &[u8] = b"def";
121        const SLICE_C: &[u8] = b"ghi";
122        const SLICE_ABC: &[u8] = concat_const_u8_slices!(SLICE_A, SLICE_B, SLICE_C);
123        assert_eq!(SLICE_ABC, b"defghi");
124    }
125
126    #[test]
127    fn empty_middle_const_slice_concatenates_correctly() {
128        const SLICE_A: &[u8] = b"abc";
129        const SLICE_B: &[u8] = b"";
130        const SLICE_C: &[u8] = b"ghi";
131        const SLICE_ABC: &[u8] = concat_const_u8_slices!(SLICE_A, SLICE_B, SLICE_C);
132        assert_eq!(SLICE_ABC, b"abcghi");
133    }
134
135    #[test]
136    fn empty_last_const_slice_concatenates_correctly() {
137        const SLICE_A: &[u8] = b"abc";
138        const SLICE_B: &[u8] = b"def";
139        const SLICE_C: &[u8] = b"";
140        const SLICE_ABC: &[u8] = concat_const_u8_slices!(SLICE_A, SLICE_B, SLICE_C);
141        assert_eq!(SLICE_ABC, b"abcdef");
142    }
143
144    #[test]
145    // Since `concat_static_strs!` uses `concat_const_u8_slices!`, we rely on
146    // the exhaustive tests above for testing edge conditions.
147    fn strings_concatenates_correctly() {
148        const STR_A: &str = "abc";
149        const STR_B: &str = "def";
150        const STR_AB: &str = concat_static_strs!(STR_A, STR_B);
151        assert_eq!(STR_AB, "abcdef");
152    }
153}