|
23 | 23 | //! integer, trimming leading zeros. All four take a [`HexCase`] to choose |
24 | 24 | //! between lowercase and uppercase digits. |
25 | 25 |
|
| 26 | +use crate::Result; |
| 27 | +use crate::error::_internal_err; |
| 28 | + |
26 | 29 | /// Case of the emitted hex digits. |
27 | 30 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
28 | 31 | pub enum HexCase { |
@@ -83,34 +86,37 @@ pub fn encode_bytes_into(bytes: &[u8], case: HexCase, out: &mut Vec<u8>) { |
83 | 86 |
|
84 | 87 | /// Writes the hex encoding of `bytes` into `out`. |
85 | 88 | /// |
86 | | -/// `out` must be exactly `2 * bytes.len()` bytes long. This is for callers |
87 | | -/// that already own a pre-sized buffer (for example a slice of a larger, |
88 | | -/// pre-allocated output array) and want to write directly into it rather |
89 | | -/// than appending to a `Vec`. |
| 89 | +/// This is for callers that already own a pre-sized buffer (for example a |
| 90 | +/// slice of a larger, pre-allocated output array) and want to write directly |
| 91 | +/// into it rather than appending to a `Vec`. |
90 | 92 | /// |
91 | | -/// The length requirement is only checked in debug builds, via |
92 | | -/// `debug_assert_eq!`. In release builds a mismatched `out` length is not |
93 | | -/// an error: only `min(bytes.len(), out.len() / 2)` bytes are encoded, so |
94 | | -/// an `out` that is too short silently drops the remaining input, and an |
95 | | -/// `out` that is too long is left with unwritten, stale bytes at the end. |
96 | | -/// Callers are responsible for sizing `out` correctly. |
| 93 | +/// Returns an internal error if `out` is not exactly `2 * bytes.len()` bytes |
| 94 | +/// long, rather than silently encoding only part of the input. |
97 | 95 | /// |
98 | 96 | /// # Example |
99 | 97 | /// |
100 | 98 | /// ``` |
101 | 99 | /// use datafusion_common::utils::hex::{HexCase, encode_bytes_to_slice}; |
102 | 100 | /// |
103 | 101 | /// let mut out = [0u8; 8]; |
104 | | -/// encode_bytes_to_slice(&[0xde, 0xad, 0xbe, 0xef], HexCase::Lower, &mut out); |
| 102 | +/// encode_bytes_to_slice(&[0xde, 0xad, 0xbe, 0xef], HexCase::Lower, &mut out)?; |
105 | 103 | /// assert_eq!(&out, b"deadbeef"); |
| 104 | +/// # Ok::<(), datafusion_common::DataFusionError>(()) |
106 | 105 | /// ``` |
107 | 106 | #[inline(always)] |
108 | | -pub fn encode_bytes_to_slice(bytes: &[u8], case: HexCase, out: &mut [u8]) { |
109 | | - debug_assert_eq!(out.len(), bytes.len() * 2); |
| 107 | +pub fn encode_bytes_to_slice(bytes: &[u8], case: HexCase, out: &mut [u8]) -> Result<()> { |
| 108 | + let expected = bytes.len() * 2; |
| 109 | + if out.len() != expected { |
| 110 | + return _internal_err!( |
| 111 | + "hex output buffer is {} bytes, expected {expected}", |
| 112 | + out.len() |
| 113 | + ); |
| 114 | + } |
110 | 115 | let lookup = case.lookup(); |
111 | 116 | for (&b, chunk) in bytes.iter().zip(out.chunks_exact_mut(2)) { |
112 | 117 | chunk.copy_from_slice(&lookup[b as usize]); |
113 | 118 | } |
| 119 | + Ok(()) |
114 | 120 | } |
115 | 121 |
|
116 | 122 | /// Returns the hex encoding of `bytes` as an owned `String`. |
@@ -162,10 +168,7 @@ pub fn encode_u64(v: u64, case: HexCase, buf: &mut [u8; 16]) -> &[u8] { |
162 | 168 | /// first digit. |
163 | 169 | /// |
164 | 170 | /// Split out from [`encode_u64`] so the mutable borrow of `buf` ends before the |
165 | | -/// returned slice reborrows it. The `v == 0` case returns early; if that early |
166 | | -/// return instead reborrowed `buf` as `&buf[..]` inline in [`encode_u64`], the |
167 | | -/// borrow checker would extend that reborrow over the rest of the function |
168 | | -/// body, conflicting with the mutable writes on the non-zero path below. |
| 171 | +/// returned slice reborrows it. |
169 | 172 | #[inline(always)] |
170 | 173 | fn write_digits(v: u64, case: HexCase, buf: &mut [u8; 16]) -> usize { |
171 | 174 | if v == 0 { |
@@ -300,30 +303,55 @@ mod tests { |
300 | 303 | } |
301 | 304 |
|
302 | 305 | #[test] |
303 | | - fn encode_bytes_to_slice_empty() { |
| 306 | + fn encode_bytes_to_slice_empty() -> Result<()> { |
304 | 307 | let mut out: [u8; 0] = []; |
305 | | - encode_bytes_to_slice(&[], HexCase::Lower, &mut out); |
| 308 | + encode_bytes_to_slice(&[], HexCase::Lower, &mut out)?; |
306 | 309 | assert_eq!(out, [] as [u8; 0]); |
| 310 | + Ok(()) |
307 | 311 | } |
308 | 312 |
|
309 | 313 | #[test] |
310 | | - fn encode_bytes_to_slice_examples() { |
| 314 | + fn encode_bytes_to_slice_examples() -> Result<()> { |
311 | 315 | let mut out = [0u8; 8]; |
312 | | - encode_bytes_to_slice(&[0xde, 0xad, 0xbe, 0xef], HexCase::Lower, &mut out); |
| 316 | + encode_bytes_to_slice(&[0xde, 0xad, 0xbe, 0xef], HexCase::Lower, &mut out)?; |
313 | 317 | assert_eq!(&out, b"deadbeef"); |
314 | 318 |
|
315 | 319 | let mut out = [0u8; 8]; |
316 | | - encode_bytes_to_slice(&[0xde, 0xad, 0xbe, 0xef], HexCase::Upper, &mut out); |
| 320 | + encode_bytes_to_slice(&[0xde, 0xad, 0xbe, 0xef], HexCase::Upper, &mut out)?; |
317 | 321 | assert_eq!(&out, b"DEADBEEF"); |
| 322 | + Ok(()) |
318 | 323 | } |
319 | 324 |
|
320 | 325 | #[test] |
321 | | - fn encode_bytes_to_slice_agrees_with_encode_bytes() { |
| 326 | + fn encode_bytes_to_slice_agrees_with_encode_bytes() -> Result<()> { |
322 | 327 | let bytes: Vec<u8> = (0..=255u8).collect(); |
323 | 328 | for case in [HexCase::Lower, HexCase::Upper] { |
324 | 329 | let mut out = vec![0u8; bytes.len() * 2]; |
325 | | - encode_bytes_to_slice(&bytes, case, &mut out); |
| 330 | + encode_bytes_to_slice(&bytes, case, &mut out)?; |
326 | 331 | assert_eq!(String::from_utf8(out).unwrap(), encode_bytes(&bytes, case)); |
327 | 332 | } |
| 333 | + Ok(()) |
| 334 | + } |
| 335 | + |
| 336 | + #[test] |
| 337 | + fn encode_bytes_to_slice_rejects_wrong_length() { |
| 338 | + // Too short: the old `debug_assert` let release builds silently drop |
| 339 | + // the remaining input. |
| 340 | + let mut short = [0u8; 6]; |
| 341 | + let err = |
| 342 | + encode_bytes_to_slice(&[0xde, 0xad, 0xbe, 0xef], HexCase::Lower, &mut short) |
| 343 | + .unwrap_err(); |
| 344 | + assert!( |
| 345 | + err.message() |
| 346 | + .contains("hex output buffer is 6 bytes, expected 8"), |
| 347 | + "unexpected message: {err}" |
| 348 | + ); |
| 349 | + |
| 350 | + // Too long: would have left stale bytes at the tail. |
| 351 | + let mut long = [0u8; 10]; |
| 352 | + assert!( |
| 353 | + encode_bytes_to_slice(&[0xde, 0xad, 0xbe, 0xef], HexCase::Lower, &mut long) |
| 354 | + .is_err() |
| 355 | + ); |
328 | 356 | } |
329 | 357 | } |
0 commit comments