dynamic-loader-cache 0.2.3

Reader of the dynamic loader shared libraries cache
Documentation
use core::fmt;
use core::mem::size_of;
use std::io::{Cursor, Write};
use std::path::Path;

use nom::number::Endianness;
use proptest::prelude::*;

use super::{
    Bucket, Cache, ParsedHeader, MAGIC_BE32, MAGIC_BE64, MAGIC_LE32, MAGIC_LE64, VERSION_2_BE32,
    VERSION_2_BE64, VERSION_2_LE32, VERSION_2_LE64,
};

/// Header of the hints file.
#[repr(C)]
struct Header<ULong: Sized> {
    magic: ULong,
    /// Interface version number.
    version: ULong,
    /// Location of hash table.
    hash_table: ULong,
    /// Number of buckets in hash_table.
    bucket_count: ULong,
    /// Location of strings.
    string_table: ULong,
    /// Size of strings.
    string_table_size: ULong,
    /// End of hints (max offset in file).
    end_of_hints: ULong,
    /// Colon-separated list of search dirs.
    dir_list: ULong,
}

fn print_cache(cache: &Cache) {
    eprintln!();
    for e in cache.iter().unwrap() {
        let e = e.unwrap();
        eprintln!(
            "    {} => {}",
            e.file_name.to_string_lossy(),
            e.full_path.display()
        );
    }
}

#[test]
fn load() {
    let cache = Cache::load("tests/ld.so.hints/ld.so.hints").unwrap();
    print_cache(&cache);
}

#[test]
fn parse_empty() {
    ParsedHeader::parse(Path::new("empty"), &[]).unwrap_err();
}

prop_compose! {
    // TODO(KAT): Fix first, and then document.
    fn header_components_32()
        (size in proptest::num::u16::ANY)
        (
            hash_table in 0_u32..=u32::from(size),
            bucket_count in 0_u32..=(u32::from(size) / size_of::<Bucket>() as u32),
            string_table in 0_u32..=u32::from(size),
            string_table_size in 0_u32..=u32::from(size),
            end_of_hints in 0_u32..=u32::from(size),
            bytes in prop::collection::vec(0_u8.., 0..=(2 * usize::from(size)))
        )
        -> (u32, u32, u32, u32, u32, Vec<u8>)
    {
        (hash_table, bucket_count, string_table, string_table_size, end_of_hints, bytes)
    }
}

prop_compose! {
    // TODO(KAT): Fix first, and then document.
    fn header_components_64()
        (size in proptest::num::u16::ANY)
        (
            hash_table in 0_u64..=u64::from(size),
            bucket_count in 0_u64..=(u64::from(size) / size_of::<Bucket>() as u64),
            string_table in 0_u64..=u64::from(size),
            string_table_size in 0_u64..=u64::from(size),
            end_of_hints in 0_u64..=u64::from(size),
            bytes in prop::collection::vec(0_u8.., 0..=(2 * usize::from(size)))
        )
        -> (u64, u64, u64, u64, u64, Vec<u8>)
    {
        (hash_table, bucket_count, string_table, string_table_size, end_of_hints, bytes)
    }
}

proptest! {
    #[test]
    fn load_random_32(
        (hash_table, bucket_count, string_table, string_table_size, end_of_hints, bytes)
            in header_components_32(),
        dir_list in proptest::num::u32::ANY,
    ) {
        load_random(
            Endianness::Little, u32::to_le_bytes, &MAGIC_LE32,
            &VERSION_2_LE32, hash_table, bucket_count, string_table,
            string_table_size, end_of_hints, dir_list, &bytes)?;

        load_random(
            Endianness::Big, u32::to_be_bytes, &MAGIC_BE32,
            &VERSION_2_BE32, hash_table, bucket_count, string_table,
            string_table_size, end_of_hints, dir_list, &bytes)?;
    }

    #[test]
    fn load_random_64(
        (hash_table, bucket_count, string_table, string_table_size, end_of_hints, bytes)
            in header_components_64(),
        dir_list in proptest::num::u64::ANY,
    ) {
        load_random(
            Endianness::Little, u64::to_le_bytes, &MAGIC_LE64,
            &VERSION_2_LE64, hash_table, bucket_count, string_table,
            string_table_size, end_of_hints, dir_list, &bytes)?;

        load_random(
            Endianness::Big, u64::to_be_bytes, &MAGIC_BE64,
            &VERSION_2_BE64, hash_table, bucket_count, string_table,
            string_table_size, end_of_hints, dir_list, &bytes)?;
    }
}

#[allow(clippy::too_many_arguments)]
fn load_random<T, const N: usize>(
    byte_order: Endianness,
    long_to_bytes: fn(T) -> [u8; N],
    magic: &[u8],
    version: &[u8],
    hash_table: T,
    bucket_count: T,
    string_table: T,
    string_table_size: T,
    end_of_hints: T,
    dir_list: T,
    bytes: &[u8],
) -> Result<(), TestCaseError>
where
    T: fmt::Debug + Copy + Into<u64>,
{
    let mut cursor = Cursor::new(Vec::<u8>::with_capacity(
        size_of::<Header<T>>() + bytes.len(),
    ));
    cursor.write_all(magic)?;
    cursor.write_all(version)?;
    cursor.write_all(&long_to_bytes(hash_table))?;
    cursor.write_all(&long_to_bytes(bucket_count))?;
    cursor.write_all(&long_to_bytes(string_table))?;
    cursor.write_all(&long_to_bytes(string_table_size))?;
    cursor.write_all(&long_to_bytes(end_of_hints))?;
    cursor.write_all(&long_to_bytes(dir_list))?;
    cursor.write_all(bytes)?;
    let bytes = cursor.into_inner();

    let Ok(p_header) = ParsedHeader::parse(Path::new("random"), &bytes) else {
        return Ok(());
    };

    prop_assert_eq!(p_header.byte_order, byte_order);

    prop_assert_eq!(p_header.hash_table, hash_table.into());
    prop_assert_eq!(p_header.bucket_count, bucket_count.into());
    prop_assert_eq!(p_header.string_table, string_table.into());
    prop_assert_eq!(p_header.string_table_size, string_table_size.into());

    // TODO(KAT): Test iterators.

    Ok(())
}