//! Macro definitions.

/// Generates a mask the width of the given unsigned integer type `$uint` if the input value is
/// non-zero.
///
/// Uses `core::hint::black_box` to coerce our desired codegen based on real-world observations
/// of the assembly generated by Rust/LLVM.
///
/// Implemented as a macro instead of a generic function because it uses functionality for which
/// there aren't available `core` traits, e.g. `wrapping_neg`.
///
/// See also:
/// - CVE-2026-23519
/// - RustCrypto/utils#1332
macro_rules! masknz {
    ($value:tt : $uint:ident) => {{
        let mut value: $uint = $value;
        value |= value.wrapping_neg(); // has MSB `1` if non-zero, `0` if zero

        // use `black_box` to obscure we're computing a 1-bit value
        core::hint::black_box(
            value >> ($uint::BITS - 1), // Extract MSB
        )
        .wrapping_neg() // Generate $uint::MAX mask if `black_box` outputs `1`
    }};
}

#[cfg(test)]
mod tests {
    // Spot check up to a given limit
    const TEST_LIMIT: u8 = 128;

    macro_rules! masknz_test {
        ( $($name:ident : $uint:ident),+ ) => {
            $(
                #[test]
                fn $name() {
                    assert_eq!(masknz!(0: $uint), 0);

                    // Test lower values
                    for i in 1..=$uint::from(TEST_LIMIT) {
                        assert_eq!(masknz!(i: $uint), $uint::MAX);
                    }

                    // Test upper values
                    for i in ($uint::MAX - $uint::from(TEST_LIMIT))..=$uint::MAX {
                        assert_eq!(masknz!(i: $uint), $uint::MAX);
                    }
                }
            )+
        }
    }

    // Ensure the macro works with any types we might use it with (we only use u8, u32, and u64)
    masknz_test!(
        masknz_u8: u8,
        masknz_u16: u16,
        masknz_u32: u32,
        masknz_u64: u64,
        masknz_u128: u128,
        masknz_usize: usize
    );
}
