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}