Skip to main content

idakit_sys/
bytes.rs

1//! The `get_bytes_into` read-into twin, the `set_cmt` comment write, and the
2//! item-classification / pattern-search flag constants (`bytes.hpp`).
3
4use std::ffi::{c_char, c_int, c_void};
5
6use bitflags::bitflags;
7
8use crate::Address;
9
10// raw bytes
11unsafe extern "C" {
12    /// Read `size` bytes starting at `address` into `buf`; returns the count read, or negative
13    /// on failure.
14    #[link_name = "idakit_get_bytes"]
15    pub fn get_bytes_into(address: Address, buf: *mut c_void, size: usize) -> i64;
16}
17
18bitflags! {
19    /// `bin_search` flag bits from `bytes.hpp` (IDA 9.3): `BIN_SEARCH_CASE`/`BIN_SEARCH_BITMASK`.
20    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
21    #[doc(alias("BIN_SEARCH_CASE", "BIN_SEARCH_BITMASK"))]
22    pub struct BinSearchFlags: c_int {
23        /// Match `"..."` string literals case-sensitively (`BIN_SEARCH_CASE`).
24        #[doc(alias("BIN_SEARCH_CASE"))]
25        const CASE = 0x01;
26        /// Match under a strict bit mask rather than byte-granular wildcards (`BIN_SEARCH_BITMASK`).
27        #[doc(alias("BIN_SEARCH_BITMASK"))]
28        const BITMASK = 0x20;
29    }
30}
31
32/// Class mask over an address's flags word (`MS_CLS` in `bytes.hpp`); the masked value equals
33/// [`FF_CODE`] for an instruction or [`FF_DATA`] for data.
34///
35/// `get_flags` returns IDA's full `flags_t`, a much wider bitfield idakit does not otherwise
36/// model; `MS_CLS`/`FF_CODE`/`FF_DATA` stay plain masked-equality constants rather than a
37/// bitflags type, since they classify one two-valued field within that word, not an OR-able flag
38/// set of their own.
39pub const MS_CLS: u64 = 0x0000_0600;
40/// [`MS_CLS`]-masked flag value marking an address as the head of an instruction.
41pub const FF_CODE: u64 = 0x0000_0600;
42/// [`MS_CLS`]-masked flag value marking an address as the head of a data item.
43pub const FF_DATA: u64 = 0x0000_0400;
44
45// comment write (plain libida `set_cmt`).
46unsafe extern "C" {
47    /// Set the comment at `address` (repeatable when `rptble`); returns whether it succeeded.
48    pub fn set_cmt(address: Address, comm: *const c_char, rptble: bool) -> bool;
49}
50
51#[cfg(test)]
52mod tests {
53    use assert2::assert;
54    use proptest::prelude::*;
55    use rstest::rstest;
56
57    use super::*;
58
59    #[rstest]
60    #[case::case(BinSearchFlags::CASE, 0x01)]
61    #[case::bitmask(BinSearchFlags::BITMASK, 0x20)]
62    fn flags_pin_the_raw_sdk_values(#[case] flag: BinSearchFlags, #[case] raw: c_int) {
63        assert!(flag.bits() == raw);
64    }
65
66    #[rstest]
67    #[case::ms_cls(MS_CLS, 0x0000_0600)]
68    #[case::ff_code(FF_CODE, 0x0000_0600)]
69    #[case::ff_data(FF_DATA, 0x0000_0400)]
70    fn classification_consts_pin_the_raw_sdk_values(#[case] value: u64, #[case] raw: u64) {
71        assert!(value == raw);
72    }
73
74    #[test]
75    fn ff_code_and_ff_data_are_distinct_under_the_class_mask() {
76        assert!(FF_CODE & MS_CLS == FF_CODE);
77        assert!(FF_DATA & MS_CLS == FF_DATA);
78        assert!(FF_CODE != FF_DATA);
79    }
80
81    proptest! {
82        #[test]
83        fn from_bits_retain_round_trips_every_bit_pattern(raw: c_int) {
84            prop_assert_eq!(BinSearchFlags::from_bits_retain(raw).bits(), raw);
85        }
86
87        #[test]
88        fn union_and_intersection_are_raw_bitwise_ops(a: c_int, b: c_int) {
89            let (fa, fb) = (BinSearchFlags::from_bits_retain(a), BinSearchFlags::from_bits_retain(b));
90            prop_assert_eq!((fa | fb).bits(), a | b);
91            prop_assert_eq!((fa & fb).bits(), a & b);
92        }
93
94        // complement() truncates to the known-flag mask, unlike union/intersection.
95        #[test]
96        fn complement_truncates_to_the_known_flag_mask(a: c_int) {
97            let fa = BinSearchFlags::from_bits_retain(a);
98            prop_assert_eq!((!fa).bits(), !a & BinSearchFlags::all().bits());
99        }
100    }
101}