1use std::ffi::CString;
2use std::marker::PhantomData;
3
4use crate::ffi::bookmarks::{
5 idalib_bookmarks_t_erase, idalib_bookmarks_t_find_index, idalib_bookmarks_t_get,
6 idalib_bookmarks_t_get_desc, idalib_bookmarks_t_mark, idalib_bookmarks_t_size,
7};
8use crate::ffi::BADADDR;
9
10use crate::idb::IDB;
11use crate::{Address, IDAError};
12
13pub type BookmarkIndex = u32;
14
15const BOOKMARKS_BAD_INDEX: BookmarkIndex = 0xffffffff; pub struct Bookmarks<'a> {
18 _marker: PhantomData<&'a IDB>,
19}
20
21impl<'a> Bookmarks<'a> {
22 pub(crate) fn new(_: &'a IDB) -> Self {
23 Self {
24 _marker: PhantomData,
25 }
26 }
27
28 pub fn mark(&self, ea: Address, desc: impl AsRef<str>) -> Result<BookmarkIndex, IDAError> {
29 self.mark_with(ea, self.len(), desc)
30 }
31
32 pub fn mark_with(
39 &self,
40 ea: Address,
41 idx: BookmarkIndex,
42 desc: impl AsRef<str>,
43 ) -> Result<BookmarkIndex, IDAError> {
44 let desc = CString::new(desc.as_ref()).map_err(IDAError::ffi)?;
45
46 let slot = unsafe { idalib_bookmarks_t_mark(ea.into(), idx.into(), desc.as_ptr()) };
47
48 if slot != BOOKMARKS_BAD_INDEX {
49 Ok(slot)
50 } else {
51 Err(IDAError::ffi_with(format!(
52 "failed to set bookmark at address {ea:#x}, index {idx}"
53 )))
54 }
55 }
56
57 pub fn get_description(&self, ea: Address) -> Option<String> {
58 self.get_description_by_index(self.find_index(ea)?)
59 }
60
61 pub fn get_address(&self, idx: BookmarkIndex) -> Option<Address> {
63 let addr = unsafe { idalib_bookmarks_t_get(idx.into()) };
64
65 if addr == BADADDR {
66 None
67 } else {
68 Some(addr.into())
69 }
70 }
71
72 pub fn get_description_by_index(&self, idx: BookmarkIndex) -> Option<String> {
74 let s = unsafe { idalib_bookmarks_t_get_desc(idx.into()) };
75
76 if s.is_empty() {
77 None
78 } else {
79 Some(s)
80 }
81 }
82
83 pub fn erase(&self, ea: Address) -> Result<(), IDAError> {
84 self.erase_by_index(self.find_index(ea).ok_or(IDAError::ffi_with(format!(
85 "failed to find bookmark index for address {ea:#x}"
86 )))?)
87 }
88
89 pub fn erase_by_index(&self, idx: BookmarkIndex) -> Result<(), IDAError> {
93 if idx >= self.len() {
95 return Err(IDAError::ffi_with(format!(
96 "failed to erase bookmark at index {idx}"
97 )));
98 }
99 if unsafe { idalib_bookmarks_t_erase(idx.into()) } {
100 Ok(())
101 } else {
102 Err(IDAError::ffi_with(format!(
103 "failed to erase bookmark at index {idx}"
104 )))
105 }
106 }
107
108 pub fn find_index(&self, ea: Address) -> Option<BookmarkIndex> {
109 let index = unsafe { idalib_bookmarks_t_find_index(ea.into()) };
110
111 if index != BOOKMARKS_BAD_INDEX {
112 Some(index)
113 } else {
114 None
115 }
116 }
117
118 pub fn len(&self) -> BookmarkIndex {
120 unsafe { idalib_bookmarks_t_size() }
121 }
122
123 pub fn is_empty(&self) -> bool {
124 self.len() == 0
125 }
126}