blob: f0a7fca9089bf1b5c7898e862de809e98222dcf0 [file] [log] [blame]
// Copyright (C) 2024 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Low-level utilities shared across multiple GBL libraries.
#![cfg_attr(not(test), no_std)]
use core::{cmp::min, str::from_utf8};
use liberror::{Error, Result};
use safemath::SafeNum;
/// Returns the largest aligned subslice.
///
/// This function drops as many bytes as needed from the front of the given slice to ensure the
/// result is properly-aligned. It does not truncate bytes from the end, so the resulting size may
/// not be a multiple of `alignment`.
///
/// If the next `alignment` boundary would be directly following the last byte, this returns the
/// 0-length slice at that alignment rather than an error, to match standard slicing behavior.
///
/// # Arguments
/// * `bytes`: the byte slice to align
/// * `alignment`: the desired starting alignment
///
/// # Returns
/// * The subslice on success
/// * [Error::ArithmeticOverflow] if `bytes` overflows when finding the next `alignment`
/// * [Error::BufferTooSmall] if `bytes` is not large enough to reach the next `alignment`. The
/// error will contain the size that would have been needed to reach `alignment`.
pub fn aligned_subslice<T>(bytes: &mut [u8], alignment: T) -> Result<&mut [u8]>
where
T: Copy + Into<SafeNum>,
{
let addr = bytes.as_ptr() as usize;
let aligned_offset = (SafeNum::from(addr).round_up(alignment) - addr).try_into()?;
Ok(bytes.get_mut(aligned_offset..).ok_or(Error::BufferTooSmall(Some(aligned_offset)))?)
}
/// A helper for getting the offset of the first byte with and aligned address.
///
/// # Arguments
/// * `bytes`: the byte slice
/// * `alignment`: the desired starting alignment.
///
/// # Returns
///
/// * Returns Ok(offset) on success, Err() on integer overflow.
pub fn aligned_offset<T>(buffer: &[u8], alignment: T) -> Result<usize>
where
T: Copy + Into<SafeNum>,
{
let addr = SafeNum::from(buffer.as_ptr() as usize);
(addr.round_up(alignment) - addr).try_into().map_err(From::from)
}
/// A helper data structure for writing formatted string to fixed size bytes array.
#[derive(Debug)]
pub struct FormattedBytes<T>(T, usize);
impl<T: AsMut<[u8]> + AsRef<[u8]>> FormattedBytes<T> {
/// Create an instance.
pub fn new(buf: T) -> Self {
Self(buf, 0)
}
/// Get the size of content.
pub fn size(&self) -> usize {
self.1
}
/// Appends the given `bytes` to the contents.
///
/// If `bytes` exceeds the remaining buffer space, any excess bytes are discarded.
///
/// Returns the resulting contents.
pub fn append(&mut self, bytes: &[u8]) -> &mut [u8] {
let buf = &mut self.0.as_mut()[self.1..];
// Only write as much as the size of the bytes buffer. Additional write is silently
// ignored.
let to_write = min(buf.len(), bytes.len());
buf[..to_write].clone_from_slice(&bytes[..to_write]);
self.1 += to_write;
&mut self.0.as_mut()[..self.1]
}
/// Converts to string.
pub fn to_str(&self) -> &str {
from_utf8(&self.0.as_ref()[..self.1]).unwrap_or("")
}
}
impl<T: AsMut<[u8]> + AsRef<[u8]>> core::fmt::Write for FormattedBytes<T> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
self.append(s.as_bytes());
Ok(())
}
}
/// A convenient macro that behaves similar to snprintf in C.
///
/// Panics if the written string is not UTF-8.
#[macro_export]
macro_rules! snprintf {
( $arr:expr, $( $x:expr ),* ) => {
{
let mut bytes = $crate::FormattedBytes::new(&mut $arr[..]);
core::fmt::Write::write_fmt(&mut bytes, core::format_args!($($x,)*)).unwrap();
let size = bytes.size();
core::str::from_utf8(&$arr[..size]).unwrap()
}
};
}
#[cfg(test)]
mod test {
use super::*;
// A byte array that's always at least 8-byte aligned for testing.
#[repr(align(8))]
struct AlignedBytes<const N: usize>([u8; N]);
#[test]
fn aligned_subslice_already_aligned() {
let mut bytes = AlignedBytes([0u8; 16]);
let bytes = &mut bytes.0;
// AlignedBytes is `align(8)`, so must be 1/2/4/8-aligned.
assert_eq!(aligned_subslice(bytes, 1).unwrap().as_ptr_range(), bytes.as_ptr_range());
assert_eq!(aligned_subslice(bytes, 2).unwrap().as_ptr_range(), bytes.as_ptr_range());
assert_eq!(aligned_subslice(bytes, 4).unwrap().as_ptr_range(), bytes.as_ptr_range());
assert_eq!(aligned_subslice(bytes, 8).unwrap().as_ptr_range(), bytes.as_ptr_range());
}
#[test]
fn aligned_subslice_unaligned() {
let mut bytes = AlignedBytes([0u8; 16]);
let bytes = &mut bytes.0;
// AlignedBytes is 8-aligned, so offsetting by <8 should snap to the next 8-alignment.
assert_eq!(
aligned_subslice(&mut bytes[1..], 8).unwrap().as_ptr_range(),
bytes[8..].as_ptr_range()
);
assert_eq!(
aligned_subslice(&mut bytes[4..], 8).unwrap().as_ptr_range(),
bytes[8..].as_ptr_range()
);
assert_eq!(
aligned_subslice(&mut bytes[7..], 8).unwrap().as_ptr_range(),
bytes[8..].as_ptr_range()
);
}
#[test]
fn aligned_subslice_empty_slice() {
let mut bytes = AlignedBytes([0u8; 16]);
let bytes = &mut bytes.0;
// If the next alignment is just past the input, return the empty slice.
assert_eq!(
aligned_subslice(&mut bytes[9..], 8).unwrap().as_ptr_range(),
bytes[16..].as_ptr_range()
);
}
#[test]
fn aligned_subslice_buffer_overflow() {
let mut bytes = AlignedBytes([0u8; 7]); // 7 bytes; can't reach the next 8-alignment.
let bytes = &mut bytes.0;
assert_eq!(aligned_subslice(&mut bytes[1..], 8), Err(Error::BufferTooSmall(Some(7))));
assert_eq!(aligned_subslice(&mut bytes[6..], 8), Err(Error::BufferTooSmall(Some(2))));
}
#[test]
fn aligned_subslice_alignment_overflow() {
let mut bytes = AlignedBytes([0u8; 16]);
let bytes = &mut bytes.0;
assert!(matches!(aligned_subslice(bytes, SafeNum::MAX), Err(Error::ArithmeticOverflow(_))));
}
#[test]
fn test_formatted_bytes() {
let mut bytes = [0u8; 4];
assert_eq!(snprintf!(bytes, "abcde"), "abcd");
assert_eq!(&bytes, b"abcd");
}
}