Skip to main content

libmagic_rs/evaluator/operators/
bitwise.rs

1// Copyright (c) 2025-2026 the libmagic-rs contributors
2// SPDX-License-Identifier: Apache-2.0
3
4//! Bitwise operators for magic rule evaluation
5
6use crate::evaluator::operators::equality::apply_equal;
7use crate::parser::ast::Value;
8
9/// Apply bitwise AND with mask for masked comparison
10///
11/// Applies a bitmask to the left value, then checks equality with the right value.
12/// This is used for `BitwiseAndMask(mask)` operator evaluation in magic rules.
13/// Only works with integer types (Uint and Int), returns `false` for other types.
14///
15/// # Arguments
16///
17/// * `mask` - The bitmask to apply to the left value
18/// * `left` - The left-hand side value (typically from file data)
19/// * `right` - The right-hand side value (typically from magic rule)
20///
21/// # Returns
22///
23/// `true` if the masked left value equals the right value, `false` otherwise
24///
25/// # Examples
26///
27/// ```
28/// use libmagic_rs::parser::ast::Value;
29/// use libmagic_rs::evaluator::operators::apply_bitwise_and_mask;
30///
31/// // Mask 0xFF applied to 0x1234 gives 0x34, compared with 0x34
32/// assert!(apply_bitwise_and_mask(0xFF, &Value::Uint(0x1234), &Value::Uint(0x34)));
33///
34/// // Mask 0xFF applied to 0x1234 gives 0x34, not equal to 0x12
35/// assert!(!apply_bitwise_and_mask(0xFF, &Value::Uint(0x1234), &Value::Uint(0x12)));
36///
37/// // Non-integer types return false
38/// assert!(!apply_bitwise_and_mask(0xFF, &Value::String("test".to_string()), &Value::Uint(0x01)));
39/// ```
40#[must_use]
41pub fn apply_bitwise_and_mask(mask: u64, left: &Value, right: &Value) -> bool {
42    let masked_left = match left {
43        Value::Uint(val) => Value::Uint(val & mask),
44        Value::Int(val) => {
45            // Convert u64 mask to i64, using bitwise representation for values > i64::MAX
46            let i64_mask =
47                i64::try_from(mask).unwrap_or_else(|_| i64::from_ne_bytes(mask.to_ne_bytes()));
48            Value::Int(val & i64_mask)
49        }
50        _ => return false, // Can't apply bitwise operations to non-numeric values
51    };
52    apply_equal(&masked_left, right)
53}
54
55/// Apply bitwise AND operation for pattern matching
56///
57/// Performs bitwise AND operation between two integer values for pattern matching.
58/// This is commonly used in magic rules to check if specific bits are set in a value.
59/// Only works with integer types (Uint and Int), returns `false` for other types.
60///
61/// # Arguments
62///
63/// * `left` - The left-hand side value (typically from file data)
64/// * `right` - The right-hand side value (typically the mask from magic rule)
65///
66/// # Returns
67///
68/// `true` if the bitwise AND result is non-zero, `false` otherwise or for non-integer types
69///
70/// # Examples
71///
72/// ```
73/// use libmagic_rs::parser::ast::Value;
74/// use libmagic_rs::evaluator::operators::apply_bitwise_and;
75///
76/// // Check if bit 0 is set
77/// assert!(apply_bitwise_and(&Value::Uint(0x01), &Value::Uint(0x01)));
78/// assert!(!apply_bitwise_and(&Value::Uint(0x02), &Value::Uint(0x01)));
79///
80/// // Check multiple bits
81/// assert!(apply_bitwise_and(&Value::Uint(0xFF), &Value::Uint(0x0F)));
82/// assert!(!apply_bitwise_and(&Value::Uint(0xF0), &Value::Uint(0x0F)));
83///
84/// // Works with signed integers too
85/// assert!(apply_bitwise_and(&Value::Int(-1), &Value::Int(0x01)));
86///
87/// // Non-integer types return false
88/// assert!(!apply_bitwise_and(&Value::String("test".to_string()), &Value::Uint(0x01)));
89/// ```
90#[must_use]
91pub fn apply_bitwise_and(left: &Value, right: &Value) -> bool {
92    match (left, right) {
93        // Unsigned integer bitwise AND
94        (Value::Uint(a), Value::Uint(b)) => (a & b) != 0,
95
96        // Signed integer bitwise AND (cast to unsigned for bitwise operations)
97        #[allow(clippy::cast_sign_loss)]
98        (Value::Int(a), Value::Int(b)) => ((*a as u64) & (*b as u64)) != 0,
99
100        // Mixed signed/unsigned integer bitwise AND
101        #[allow(clippy::cast_sign_loss)]
102        (Value::Uint(a), Value::Int(b)) => (a & (*b as u64)) != 0,
103        #[allow(clippy::cast_sign_loss)]
104        (Value::Int(a), Value::Uint(b)) => ((*a as u64) & b) != 0,
105
106        // Non-integer types cannot perform bitwise AND
107        _ => false,
108    }
109}
110
111/// Apply bitwise XOR operation for pattern matching
112///
113/// Performs bitwise XOR between two integer values. Returns `true` if the result is non-zero.
114/// Only works with integer types (Uint and Int), returns `false` for other types.
115///
116/// # Arguments
117///
118/// * `left` - The left-hand side value (typically from file data)
119/// * `right` - The right-hand side value (typically from magic rule)
120///
121/// # Returns
122///
123/// `true` if the bitwise XOR result is non-zero, `false` otherwise or for non-integer types
124///
125/// # Examples
126///
127/// ```
128/// use libmagic_rs::parser::ast::Value;
129/// use libmagic_rs::evaluator::operators::apply_bitwise_xor;
130///
131/// // XOR of different values is non-zero (true)
132/// assert!(apply_bitwise_xor(&Value::Uint(0xFF), &Value::Uint(0x0F)));
133///
134/// // XOR of same values is zero (false)
135/// assert!(!apply_bitwise_xor(&Value::Uint(42), &Value::Uint(42)));
136///
137/// // Non-integer types return false
138/// assert!(!apply_bitwise_xor(
139///     &Value::String("test".to_string()),
140///     &Value::Uint(0x01),
141/// ));
142/// ```
143#[must_use]
144pub fn apply_bitwise_xor(left: &Value, right: &Value) -> bool {
145    match (left, right) {
146        (Value::Uint(a), Value::Uint(b)) => (a ^ b) != 0,
147        #[allow(clippy::cast_sign_loss)]
148        (Value::Int(a), Value::Int(b)) => ((*a as u64) ^ (*b as u64)) != 0,
149        #[allow(clippy::cast_sign_loss)]
150        (Value::Uint(a), Value::Int(b)) => (a ^ (*b as u64)) != 0,
151        #[allow(clippy::cast_sign_loss)]
152        (Value::Int(a), Value::Uint(b)) => ((*a as u64) ^ b) != 0,
153        _ => false,
154    }
155}
156
157/// Apply bitwise NOT then compare with right value
158///
159/// Computes bitwise complement of the left (file) value, then checks equality with the right
160/// (magic rule) value. Unlike `&` and `^` which test whether a bitwise result is non-zero,
161/// `~` compares the complement against a specific expected value.
162/// Only works with integer types, returns `false` for other types.
163///
164/// # Arguments
165///
166/// * `left` - The left-hand side value (typically from file data)
167/// * `right` - The right-hand side value to compare `!left` against
168///
169/// # Returns
170///
171/// `true` if `!left == right`, `false` otherwise or for non-integer types
172///
173/// # Examples
174///
175/// ```
176/// use libmagic_rs::parser::ast::Value;
177/// use libmagic_rs::evaluator::operators::apply_bitwise_not;
178///
179/// // NOT of 0 is all bits set (u64::MAX)
180/// assert!(apply_bitwise_not(&Value::Uint(0), &Value::Uint(u64::MAX)));
181///
182/// // NOT of -1 (all bits set) is 0
183/// assert!(apply_bitwise_not(&Value::Int(-1), &Value::Int(0)));
184///
185/// // Non-integer types return false
186/// assert!(!apply_bitwise_not(&Value::Bytes(vec![0xff]), &Value::Uint(0)));
187/// ```
188#[must_use]
189pub fn apply_bitwise_not(left: &Value, right: &Value) -> bool {
190    apply_bitwise_not_with_width(left, right, None)
191}
192
193/// Apply bitwise NOT with type-aware bit-width masking
194///
195/// When `bit_width` is provided, the complement is masked to the type's natural width.
196/// For example, a `ubyte` (8-bit) NOT of `0x00` yields `0xFF`, not `u64::MAX`.
197/// Without a bit width, the full 64-bit complement is used.
198///
199/// # Arguments
200///
201/// * `left` - The left-hand side value (typically from file data)
202/// * `right` - The right-hand side value to compare `!left` against
203/// * `bit_width` - Optional bit width for masking (8, 16, 32, or 64)
204///
205/// # Returns
206///
207/// `true` if the width-masked complement of `left` equals `right`
208#[must_use]
209pub fn apply_bitwise_not_with_width(left: &Value, right: &Value, bit_width: Option<u32>) -> bool {
210    let complemented = match (left, bit_width) {
211        (Value::Uint(val), Some(width)) if width < 64 => {
212            let mask = (1u64 << width) - 1;
213            Value::Uint(!val & mask)
214        }
215        (Value::Uint(val), _) => Value::Uint(!val),
216        (Value::Int(val), _) => Value::Int(!*val),
217        _ => return false,
218    };
219    apply_equal(&complemented, right)
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225
226    #[test]
227    fn test_apply_bitwise_and_uint_basic() {
228        // Basic bit checking
229        assert!(apply_bitwise_and(&Value::Uint(0x01), &Value::Uint(0x01))); // Bit 0 set
230        assert!(!apply_bitwise_and(&Value::Uint(0x02), &Value::Uint(0x01))); // Bit 0 not set
231        assert!(apply_bitwise_and(&Value::Uint(0x03), &Value::Uint(0x01))); // Bit 0 set among others
232    }
233
234    #[test]
235    fn test_apply_bitwise_and_uint_multiple_bits() {
236        // Multiple bit patterns
237        assert!(apply_bitwise_and(&Value::Uint(0xFF), &Value::Uint(0x0F))); // Any of lower 4 bits
238        assert!(!apply_bitwise_and(&Value::Uint(0xF0), &Value::Uint(0x0F))); // None of lower 4 bits
239        assert!(!apply_bitwise_and(&Value::Uint(0xAA), &Value::Uint(0x55))); // No overlap (0xAA = 10101010, 0x55 = 01010101)
240        assert!(apply_bitwise_and(&Value::Uint(0xAA), &Value::Uint(0xAA))); // Same pattern
241    }
242
243    #[test]
244    fn test_apply_bitwise_and_uint_edge_cases() {
245        // Zero cases
246        assert!(!apply_bitwise_and(&Value::Uint(0), &Value::Uint(0xFF))); // Zero & anything = 0
247        assert!(!apply_bitwise_and(&Value::Uint(0xFF), &Value::Uint(0))); // Anything & zero = 0
248        assert!(!apply_bitwise_and(&Value::Uint(0), &Value::Uint(0))); // Zero & zero = 0
249
250        // Maximum values
251        assert!(apply_bitwise_and(&Value::Uint(u64::MAX), &Value::Uint(1))); // Max & 1
252        assert!(apply_bitwise_and(
253            &Value::Uint(u64::MAX),
254            &Value::Uint(u64::MAX)
255        )); // Max & Max
256    }
257
258    #[test]
259    fn test_apply_bitwise_and_uint_specific_patterns() {
260        // Common magic number patterns
261        assert!(apply_bitwise_and(
262            &Value::Uint(0x7F45_4C46),
263            &Value::Uint(0xFF00_0000)
264        )); // ELF magic high byte
265        assert!(apply_bitwise_and(
266            &Value::Uint(0x504B_0304),
267            &Value::Uint(0xFFFF_0000)
268        )); // ZIP magic high word
269        assert!(!apply_bitwise_and(
270            &Value::Uint(0x1234_5678),
271            &Value::Uint(0x0000_0001)
272        )); // Bit 0 not set
273    }
274
275    #[test]
276    fn test_apply_bitwise_and_int_basic() {
277        // Basic signed integer bitwise AND
278        assert!(apply_bitwise_and(&Value::Int(1), &Value::Int(1))); // Positive & positive
279        assert!(!apply_bitwise_and(&Value::Int(2), &Value::Int(1))); // Different bits
280        assert!(apply_bitwise_and(&Value::Int(3), &Value::Int(1))); // Multiple bits, one matches
281    }
282
283    #[test]
284    fn test_apply_bitwise_and_int_negative() {
285        // Negative number bitwise AND (uses two's complement)
286        assert!(apply_bitwise_and(&Value::Int(-1), &Value::Int(1))); // -1 has all bits set
287        assert!(apply_bitwise_and(&Value::Int(-2), &Value::Int(2))); // -2 & 2 should have bit 1 set
288        assert!(!apply_bitwise_and(&Value::Int(-2), &Value::Int(1))); // -2 & 1 should be 0 (bit 0 not set in -2)
289    }
290
291    #[test]
292    fn test_apply_bitwise_and_int_zero() {
293        // Zero cases with signed integers
294        assert!(!apply_bitwise_and(&Value::Int(0), &Value::Int(0xFF))); // Zero & anything = 0
295        assert!(!apply_bitwise_and(&Value::Int(0xFF), &Value::Int(0))); // Anything & zero = 0
296        assert!(!apply_bitwise_and(&Value::Int(0), &Value::Int(0))); // Zero & zero = 0
297    }
298
299    #[test]
300    fn test_apply_bitwise_and_int_extreme_values() {
301        // Extreme signed integer values
302        assert!(apply_bitwise_and(&Value::Int(i64::MAX), &Value::Int(1))); // Max positive & 1
303        assert!(apply_bitwise_and(
304            &Value::Int(i64::MIN),
305            &Value::Int(i64::MIN)
306        )); // Min & Min
307        assert!(apply_bitwise_and(&Value::Int(i64::MIN), &Value::Int(-1))); // Min & -1 (all bits set)
308    }
309
310    #[test]
311    fn test_apply_bitwise_and_mixed_int_uint() {
312        // Mixed signed/unsigned operations
313        assert!(apply_bitwise_and(&Value::Uint(0xFF), &Value::Int(0x0F))); // Uint & Int
314        assert!(apply_bitwise_and(&Value::Int(0xFF), &Value::Uint(0x0F))); // Int & Uint
315        assert!(!apply_bitwise_and(&Value::Uint(0xF0), &Value::Int(0x0F))); // No overlap
316        assert!(!apply_bitwise_and(&Value::Int(0xF0), &Value::Uint(0x0F))); // No overlap
317    }
318
319    #[test]
320    fn test_apply_bitwise_and_mixed_negative_uint() {
321        // Negative int with uint (negative numbers have high bits set)
322        assert!(apply_bitwise_and(&Value::Int(-1), &Value::Uint(1))); // -1 & 1
323        assert!(apply_bitwise_and(&Value::Uint(1), &Value::Int(-1))); // 1 & -1
324        assert!(!apply_bitwise_and(&Value::Int(-2), &Value::Uint(1))); // -2 & 1 (bit 0 not set in -2)
325        assert!(!apply_bitwise_and(&Value::Uint(1), &Value::Int(-2))); // 1 & -2
326    }
327
328    #[test]
329    fn test_apply_bitwise_and_non_integer_types() {
330        // Non-integer types should return false
331        assert!(!apply_bitwise_and(
332            &Value::String("test".to_string()),
333            &Value::Uint(0x01)
334        ));
335        assert!(!apply_bitwise_and(
336            &Value::Uint(0x01),
337            &Value::String("test".to_string())
338        ));
339        assert!(!apply_bitwise_and(
340            &Value::Bytes(vec![1]),
341            &Value::Uint(0x01)
342        ));
343        assert!(!apply_bitwise_and(
344            &Value::Uint(0x01),
345            &Value::Bytes(vec![1])
346        ));
347        assert!(!apply_bitwise_and(
348            &Value::String("a".to_string()),
349            &Value::String("b".to_string())
350        ));
351        assert!(!apply_bitwise_and(
352            &Value::Bytes(vec![1]),
353            &Value::Bytes(vec![1])
354        ));
355    }
356
357    #[test]
358    fn test_apply_bitwise_and_all_non_integer_combinations() {
359        let non_integer_values = [Value::String("test".to_string()), Value::Bytes(vec![42])];
360
361        let integer_values = [Value::Uint(42), Value::Int(42)];
362
363        // Test all combinations of non-integer with integer
364        for non_int in &non_integer_values {
365            for int_val in &integer_values {
366                assert!(
367                    !apply_bitwise_and(non_int, int_val),
368                    "Non-integer & integer should be false: {non_int:?} & {int_val:?}"
369                );
370                assert!(
371                    !apply_bitwise_and(int_val, non_int),
372                    "Integer & non-integer should be false: {int_val:?} & {non_int:?}"
373                );
374            }
375        }
376
377        // Test all combinations of non-integer with non-integer
378        for left in &non_integer_values {
379            for right in &non_integer_values {
380                assert!(
381                    !apply_bitwise_and(left, right),
382                    "Non-integer & non-integer should be false: {left:?} & {right:?}"
383                );
384            }
385        }
386    }
387
388    #[test]
389    fn test_apply_bitwise_and_bit_patterns() {
390        // Test specific bit patterns commonly used in magic rules
391        let test_cases = vec![
392            // (value, mask, expected)
393            (0b0000_0001_u64, 0b0000_0001_u64, true), // Bit 0 set
394            (0b0000_0010_u64, 0b0000_0001_u64, false), // Bit 0 not set
395            (0b0000_0011_u64, 0b0000_0001_u64, true), // Bit 0 set among others
396            (0b1111_1111_u64, 0b0000_1111_u64, true), // Any of lower 4 bits
397            (0b1111_0000_u64, 0b0000_1111_u64, false), // None of lower 4 bits
398            (0b1010_1010_u64, 0b0101_0101_u64, false), // No overlap
399            (0b1010_1010_u64, 0b1010_1010_u64, true), // Perfect match
400            (0b1111_1111_u64, 0b0000_0000_u64, false), // Mask is zero
401            (0b0000_0000_u64, 0b1111_1111_u64, false), // Value is zero
402        ];
403
404        for (value, mask, expected) in test_cases {
405            assert_eq!(
406                apply_bitwise_and(&Value::Uint(value), &Value::Uint(mask)),
407                expected,
408                "apply_bitwise_and(0b{value:08b}, 0b{mask:08b}) should be {expected}"
409            );
410        }
411    }
412
413    #[test]
414    fn test_apply_bitwise_and_magic_file_patterns() {
415        // Test patterns commonly found in magic files
416
417        // ELF magic number (0x7F454C46) - check if it's an ELF file
418        let elf_magic = Value::Uint(0x7F45_4C46);
419        let elf_mask = Value::Uint(0xFFFF_FFFF);
420        assert!(apply_bitwise_and(&elf_magic, &elf_mask));
421
422        // Check specific bytes in ELF magic
423        assert!(apply_bitwise_and(&elf_magic, &Value::Uint(0x7F00_0000))); // First byte
424        assert!(apply_bitwise_and(&elf_magic, &Value::Uint(0x0045_0000))); // Second byte 'E'
425        assert!(apply_bitwise_and(&elf_magic, &Value::Uint(0x0000_4C00))); // Third byte 'L'
426        assert!(apply_bitwise_and(&elf_magic, &Value::Uint(0x0000_0046))); // Fourth byte 'F'
427
428        // ZIP magic number (0x504B0304) - check if it's a ZIP file
429        let zip_magic = Value::Uint(0x504B_0304);
430        assert!(apply_bitwise_and(&zip_magic, &Value::Uint(0x504B_0000))); // PK signature
431        assert!(!apply_bitwise_and(&zip_magic, &Value::Uint(0x0000_0001))); // Bit 0 not set
432
433        // PDF magic (%PDF) - first few bytes
434        let pdf_magic = Value::Uint(0x2550_4446); // "%PDF" as uint32
435        assert!(apply_bitwise_and(&pdf_magic, &Value::Uint(0xFF00_0000))); // '%' character
436        assert!(apply_bitwise_and(&pdf_magic, &Value::Uint(0x00FF_0000))); // 'P' character
437    }
438
439    #[test]
440    fn test_apply_bitwise_and_symmetry() {
441        // Test that bitwise AND is commutative for integer types
442        let test_cases = vec![
443            (Value::Uint(0xFF), Value::Uint(0x0F)),
444            (Value::Int(42), Value::Int(24)),
445            (Value::Uint(0xAAAA), Value::Int(0x5555)),
446            (Value::Int(-1), Value::Uint(1)),
447        ];
448
449        for (left, right) in test_cases {
450            let left_to_right = apply_bitwise_and(&left, &right);
451            let right_to_left = apply_bitwise_and(&right, &left);
452            assert_eq!(
453                left_to_right, right_to_left,
454                "Bitwise AND should be commutative: {left:?} & {right:?}"
455            );
456        }
457    }
458
459    #[test]
460    fn test_apply_bitwise_and_associativity_concept() {
461        // While we can't test true associativity with binary function,
462        // we can test that the operation behaves consistently
463        let value = Value::Uint(0b1111_0000);
464        let mask1 = Value::Uint(0b1100_0000);
465        let mask2 = Value::Uint(0b0011_0000);
466        let combined_mask = Value::Uint(0b1111_0000);
467
468        // (value & mask1) should be true if any bits match
469        assert!(apply_bitwise_and(&value, &mask1));
470        assert!(apply_bitwise_and(&value, &mask2));
471        assert!(apply_bitwise_and(&value, &combined_mask));
472    }
473
474    #[test]
475    fn test_apply_bitwise_xor_uint() {
476        assert!(apply_bitwise_xor(&Value::Uint(0xFF), &Value::Uint(0x0F)));
477        assert!(!apply_bitwise_xor(&Value::Uint(0xFF), &Value::Uint(0xFF)));
478        assert!(apply_bitwise_xor(&Value::Uint(1), &Value::Uint(2)));
479        assert!(!apply_bitwise_xor(&Value::Uint(0), &Value::Uint(0)));
480    }
481
482    #[test]
483    fn test_apply_bitwise_xor_int() {
484        assert!(apply_bitwise_xor(&Value::Int(0xFF), &Value::Int(0x0F)));
485        assert!(!apply_bitwise_xor(&Value::Int(42), &Value::Int(42)));
486        assert!(apply_bitwise_xor(&Value::Int(-1), &Value::Int(0)));
487    }
488
489    #[test]
490    fn test_apply_bitwise_xor_cross_type() {
491        assert!(apply_bitwise_xor(&Value::Uint(0xFF), &Value::Int(0x0F)));
492        assert!(apply_bitwise_xor(&Value::Int(0xFF), &Value::Uint(0x0F)));
493        assert!(!apply_bitwise_xor(&Value::Uint(42), &Value::Int(42)));
494    }
495
496    #[test]
497    fn test_apply_bitwise_xor_same_value() {
498        assert!(!apply_bitwise_xor(&Value::Uint(100), &Value::Uint(100)));
499        assert!(!apply_bitwise_xor(&Value::Int(-1), &Value::Int(-1)));
500    }
501
502    #[test]
503    fn test_apply_bitwise_xor_non_numeric() {
504        assert!(!apply_bitwise_xor(
505            &Value::Bytes(vec![1, 2]),
506            &Value::Uint(1)
507        ));
508        assert!(!apply_bitwise_xor(
509            &Value::String("x".to_string()),
510            &Value::Uint(0xFF)
511        ));
512    }
513
514    #[test]
515    fn test_apply_bitwise_not_uint() {
516        assert!(apply_bitwise_not(&Value::Uint(0), &Value::Uint(u64::MAX)));
517        assert!(apply_bitwise_not(&Value::Uint(u64::MAX), &Value::Uint(0)));
518        assert!(!apply_bitwise_not(&Value::Uint(0xFF), &Value::Uint(0)));
519    }
520
521    #[test]
522    fn test_apply_bitwise_not_int() {
523        assert!(apply_bitwise_not(&Value::Int(0), &Value::Int(-1)));
524        assert!(apply_bitwise_not(&Value::Int(-1), &Value::Int(0)));
525    }
526
527    #[test]
528    fn test_apply_bitwise_not_all_bits_set() {
529        assert!(apply_bitwise_not(
530            &Value::Uint(0xFFFF_FFFF_FFFF_FFFF),
531            &Value::Uint(0)
532        ));
533    }
534
535    #[test]
536    fn test_apply_bitwise_not_non_numeric() {
537        assert!(!apply_bitwise_not(
538            &Value::Bytes(vec![0xff]),
539            &Value::Uint(0)
540        ));
541        assert!(!apply_bitwise_not(
542            &Value::String("x".to_string()),
543            &Value::Uint(0)
544        ));
545    }
546
547    #[test]
548    fn test_apply_bitwise_not_with_byte_width() {
549        // At byte width (8 bits), ~0x00 = 0xFF
550        assert!(apply_bitwise_not_with_width(
551            &Value::Uint(0x00),
552            &Value::Uint(0xFF),
553            Some(8)
554        ));
555        // At byte width, ~0xFF = 0x00
556        assert!(apply_bitwise_not_with_width(
557            &Value::Uint(0xFF),
558            &Value::Uint(0x00),
559            Some(8)
560        ));
561        // At byte width, ~0x42 = 0xBD
562        assert!(apply_bitwise_not_with_width(
563            &Value::Uint(0x42),
564            &Value::Uint(0xBD),
565            Some(8)
566        ));
567    }
568
569    #[test]
570    fn test_apply_bitwise_not_with_short_width() {
571        // At short width (16 bits), ~0x0000 = 0xFFFF
572        assert!(apply_bitwise_not_with_width(
573            &Value::Uint(0x0000),
574            &Value::Uint(0xFFFF),
575            Some(16)
576        ));
577        // At short width, ~0x1234 = 0xEDCB
578        assert!(apply_bitwise_not_with_width(
579            &Value::Uint(0x1234),
580            &Value::Uint(0xEDCB),
581            Some(16)
582        ));
583    }
584
585    #[test]
586    fn test_apply_bitwise_not_with_long_width() {
587        // At long width (32 bits), ~0x00000000 = 0xFFFFFFFF
588        assert!(apply_bitwise_not_with_width(
589            &Value::Uint(0x0000_0000),
590            &Value::Uint(0xFFFF_FFFF),
591            Some(32)
592        ));
593    }
594
595    #[test]
596    fn test_apply_bitwise_not_with_quad_width() {
597        // At quad width (64 bits), ~0 = u64::MAX (no masking needed)
598        assert!(apply_bitwise_not_with_width(
599            &Value::Uint(0),
600            &Value::Uint(u64::MAX),
601            Some(64)
602        ));
603    }
604
605    #[test]
606    fn test_apply_bitwise_not_with_no_width() {
607        // No width specified: full 64-bit complement (same as apply_bitwise_not)
608        assert!(apply_bitwise_not_with_width(
609            &Value::Uint(0),
610            &Value::Uint(u64::MAX),
611            None
612        ));
613    }
614}