// Copyright 2023 Google LLC
//
// 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.

//! Types for creating arenas used in deserialization of np_adv. This implementation is purpose-made
//! for deserializing in `np_adv` and is not intended for general use as an arena.

use crate::extended::BLE_ADV_SVC_CONTENT_LEN;

/// Create a [`DeserializationArena`] suitable for use with deserializing an advertisement.
#[macro_export]
macro_rules! deserialization_arena {
    // Trick borrowed from `pin!`: In an argument to a braced constructor, if we take a reference to
    // a temporary value, that value will be upgraded to live for the scope of the enclosing block,
    // avoiding another `let` binding for the buffer which is normally required to keep the buffer
    // on the stack.
    // Reference: https://doc.rust-lang.org/reference/destructors.html#temporary-lifetime-extension
    () => {
        $crate::deserialization_arena::DeserializationArena {
            buffer: &mut $crate::deserialization_arena::DeserializationArena::new_buffer(),
        }
    };
}

/// A simple allocator that simply keeps splitting the given slice on allocation. Use the
/// [`deserialization_arena!`][crate::deserialization_arena!] macro to create an instance.
pub(crate) struct DeserializationArenaAllocator<'a> {
    #[doc(hidden)]
    slice: &'a mut [u8],
}

impl<'a> DeserializationArenaAllocator<'a> {
    /// Allocates `len` bytes from the slice given upon construction. In the expected use case, the
    /// allocated slice should be written to with actual data, overwriting what's contained in
    /// there. While reading from the allocated slice without first writing to it is safe in the
    /// Rust memory-safety sense, the returned slice contains "garbage" data from the slice given
    /// during construction.
    ///
    /// Returns an error with [`ArenaOutOfSpace`] if the remaining slice is not long enough to
    /// allocate `len` bytes.
    pub fn allocate(&mut self, len: u8) -> Result<&'a mut [u8], ArenaOutOfSpace> {
        if usize::from(len) > self.slice.len() {
            return Err(ArenaOutOfSpace);
        }
        let (allocated, remaining) = core::mem::take(&mut self.slice).split_at_mut(len.into());
        self.slice = remaining;
        // Note: the returned data is logically garbage. While it's deterministic (not UB),
        // semantically this should be treated as a write only slice.
        Ok(allocated)
    }
}

/// A simple allocator that simply keeps splitting the given slice on allocation. Use the
/// [`deserialization_arena!`][crate::deserialization_arena!] macro to create an instance.
pub struct DeserializationArena<'a> {
    #[doc(hidden)] // Exposed for use by `deserialization_arena!` only.
    pub buffer: &'a mut [u8; BLE_ADV_SVC_CONTENT_LEN],
}

impl<'a> DeserializationArena<'a> {
    #[doc(hidden)] // Exposed for use by `deserialization_arena!` only.
    pub fn new_buffer() -> [u8; BLE_ADV_SVC_CONTENT_LEN] {
        [0; BLE_ADV_SVC_CONTENT_LEN]
    }

    /// Convert this arena into an allocator that can start allocating.
    pub(crate) fn into_allocator(self) -> DeserializationArenaAllocator<'a> {
        DeserializationArenaAllocator { slice: self.buffer }
    }
}

/// Error indicating that the arena has ran out of space, and deserialization cannot proceed. This
/// should never happen if the arena is created with [`crate::deserialization_arena!`], since the
/// total size of decrypted sections should be less than the size of the incoming BLE advertisement.
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct ArenaOutOfSpace;

#[cfg(test)]
mod test {
    use crate::{deserialization_arena::ArenaOutOfSpace, extended::BLE_ADV_SVC_CONTENT_LEN};

    #[test]
    fn test_creation() {
        assert_eq!(BLE_ADV_SVC_CONTENT_LEN, deserialization_arena!().buffer.len());
    }

    #[test]
    fn test_allocation() {
        let arena = deserialization_arena!();
        let mut allocator = arena.into_allocator();
        assert_eq!(Ok(&mut [0_u8; 100][..]), allocator.allocate(100));
        assert_eq!(BLE_ADV_SVC_CONTENT_LEN - 100, allocator.slice.len());
    }

    #[test]
    fn test_allocation_overflow() {
        let arena = deserialization_arena!();
        let mut allocator = arena.into_allocator();
        assert_eq!(Err(ArenaOutOfSpace), allocator.allocate(u8::MAX));
    }

    #[test]
    fn test_allocation_twice_overflow() {
        let arena = deserialization_arena!();
        let mut allocator = arena.into_allocator();
        assert_eq!(Ok(&mut [0_u8; 128][..]), allocator.allocate(128));
        assert_eq!(Err(ArenaOutOfSpace), allocator.allocate(128));
    }
}
