1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
|
use crate::rc::{Ref, RefCountable};
use crate::string::{BnString, IntoCStr};
use binaryninjacore_sys::*;
use std::ffi::{c_char, c_void, CStr};
use std::marker::PhantomData;
use std::ops::Deref;
use std::ptr::NonNull;
pub trait WebsocketClientCallback: Sync + Send {
/// Receive a notification that the websocket connection has been connected successfully.
///
/// Return `false` if you would like to terminate the connection early.
fn connected(&self) -> bool;
/// Receive a notification that the websocket connection has been terminated.
///
/// For implementations, you must call this at the end of the websocket connection lifecycle
/// even if you notify the client of an error.
fn disconnected(&self);
/// Receive an error from the websocket connection.
///
/// For implementations, you typically write the data to an interior mutable buffer on the instance.
fn error(&self, msg: &str);
/// Receive data from the websocket connection.
///
/// For implementations, you typically write the data to an interior mutable buffer on the instance.
fn read(&self, data: &[u8]) -> bool;
}
pub trait WebsocketClient: Sync + Send {
/// Called to construct this client object with the given core object.
fn from_core(core: Ref<CoreWebsocketClient>) -> Self;
fn connect<I>(&self, host: &str, headers: I) -> bool
where
I: IntoIterator<Item = (String, String)>;
fn write(&self, data: &[u8]) -> bool;
fn disconnect(&self) -> bool;
}
/// Represents a live websocket connection.
///
/// This manages the lifetime of the callback, ensuring it outlives the connection.
pub struct ActiveConnection<'a, C: WebsocketClientCallback> {
pub client: Ref<CoreWebsocketClient>,
_callback: PhantomData<&'a mut C>,
}
impl<'a, C: WebsocketClientCallback> Deref for ActiveConnection<'a, C> {
type Target = CoreWebsocketClient;
fn deref(&self) -> &Self::Target {
&self.client
}
}
impl<'a, C: WebsocketClientCallback> Drop for ActiveConnection<'a, C> {
fn drop(&mut self) {
self.client.disconnect();
}
}
/// Implements a websocket client.
///
/// To connect, use [`Ref<CoreWebsocketClient>::connect`] which will return an [`ActiveConnection`]
/// which manages the lifecycle of the websocket connection.
#[repr(transparent)]
pub struct CoreWebsocketClient {
pub(crate) handle: NonNull<BNWebsocketClient>,
}
impl CoreWebsocketClient {
pub(crate) unsafe fn ref_from_raw(handle: NonNull<BNWebsocketClient>) -> Ref<Self> {
Ref::new(Self { handle })
}
#[allow(clippy::mut_from_ref)]
pub(crate) unsafe fn as_raw(&self) -> &mut BNWebsocketClient {
&mut *self.handle.as_ptr()
}
/// Call the connect callback function, forward the callback returned value
pub fn notify_connected(&self) -> bool {
unsafe { BNNotifyWebsocketClientConnect(self.handle.as_ptr()) }
}
/// Notify the callback function of a disconnect. This must be called at the end of an active
/// websocket connection lifecycle, to free resources.
///
/// NOTE: This does not actually disconnect, use the [Self::disconnect] function for that.
pub fn notify_disconnected(&self) {
unsafe { BNNotifyWebsocketClientDisconnect(self.handle.as_ptr()) }
}
/// Call the error callback function, this is not a terminating request you must use
/// [`CoreWebsocketClient::notify_disconnected`] to terminate the connection.
pub fn notify_error(&self, msg: &str) {
let error = msg.to_cstr();
unsafe { BNNotifyWebsocketClientError(self.handle.as_ptr(), error.as_ptr()) }
}
/// Call the read callback function, forward the callback returned value.
pub fn notify_read(&self, data: &[u8]) -> bool {
unsafe {
BNNotifyWebsocketClientReadData(
self.handle.as_ptr(),
data.as_ptr() as *mut _,
data.len().try_into().unwrap(),
)
}
}
pub fn write(&self, data: &[u8]) -> bool {
let len = u64::try_from(data.len()).unwrap();
unsafe { BNWriteWebsocketClientData(self.as_raw(), data.as_ptr(), len) != 0 }
}
pub fn disconnect(&self) -> bool {
unsafe { BNDisconnectWebsocketClient(self.as_raw()) }
}
}
impl Ref<CoreWebsocketClient> {
/// Initializes the web socket connection, returning the [`ActiveConnection`], once dropped the
/// connection will be disconnected.
///
/// Connect to a given url, asynchronously. The connection will be run in a
/// separate thread managed by the websocket provider.
///
/// Callbacks will be called **on the thread of the connection**, so be sure
/// to ExecuteOnMainThread any long-running or gui operations in the callbacks.
///
/// If the connection succeeds, [WebsocketClientCallback::connected] will be called. On normal
/// termination, [WebsocketClientCallback::disconnected] will be called.
///
/// If the connection succeeds but later fails, [`WebsocketClientCallback::error`] will be called
/// and shortly thereafter [`WebsocketClientCallback::disconnected`] will be called.
///
/// If [`WebsocketClientCallback::connected`] or [`WebsocketClientCallback::read`] return false, the
/// connection will be aborted.
///
/// * `host` - Full url with scheme, domain, optionally port, and path
/// * `headers` - HTTP header keys and values
/// * `callback` - Callbacks for various websocket events
#[must_use]
pub fn connect<'a, I, C>(
self,
host: &str,
headers: I,
callbacks: &'a C,
) -> Option<ActiveConnection<'a, C>>
where
I: IntoIterator<Item = (String, String)>,
C: WebsocketClientCallback,
{
let url = host.to_cstr();
let (header_keys, header_values): (Vec<_>, Vec<_>) = headers
.into_iter()
.map(|(k, v)| (k.to_cstr(), v.to_cstr()))
.unzip();
let header_keys: Vec<*const c_char> = header_keys.iter().map(|k| k.as_ptr()).collect();
let header_values: Vec<*const c_char> = header_values.iter().map(|v| v.as_ptr()).collect();
// SAFETY: This context will live for as long as the `ActiveConnection` is alive.
// SAFETY: Any subsequent call to BNConnectWebsocketClient will write over the context.
let mut output_callbacks = BNWebsocketClientOutputCallbacks {
context: callbacks as *const C as *mut C as *mut c_void,
connectedCallback: Some(cb_connected::<C>),
disconnectedCallback: Some(cb_disconnected::<C>),
errorCallback: Some(cb_error::<C>),
readCallback: Some(cb_read::<C>),
};
let success = unsafe {
BNConnectWebsocketClient(
self.handle.as_ptr(),
url.as_ptr(),
header_keys.len().try_into().unwrap(),
header_keys.as_ptr(),
header_values.as_ptr(),
&mut output_callbacks,
)
};
if success {
Some(ActiveConnection {
client: self,
_callback: PhantomData,
})
} else {
None
}
}
}
unsafe impl Sync for CoreWebsocketClient {}
unsafe impl Send for CoreWebsocketClient {}
impl ToOwned for CoreWebsocketClient {
type Owned = Ref<Self>;
fn to_owned(&self) -> Self::Owned {
unsafe { RefCountable::inc_ref(self) }
}
}
unsafe impl RefCountable for CoreWebsocketClient {
unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
let result = BNNewWebsocketClientReference(handle.as_raw());
unsafe { Self::ref_from_raw(NonNull::new(result).unwrap()) }
}
unsafe fn dec_ref(handle: &Self) {
BNFreeWebsocketClient(handle.as_raw())
}
}
pub(crate) unsafe extern "C" fn cb_destroy_client<W: WebsocketClient>(ctxt: *mut c_void) {
let _ = Box::from_raw(ctxt as *mut W);
}
pub(crate) unsafe extern "C" fn cb_connect<W: WebsocketClient>(
ctxt: *mut c_void,
host: *const c_char,
header_count: u64,
header_keys: *const *const c_char,
header_values: *const *const c_char,
) -> bool {
let ctxt: &mut W = &mut *(ctxt as *mut W);
let host = CStr::from_ptr(host);
// SAFETY BnString and *mut c_char are transparent
let header_count = usize::try_from(header_count).unwrap();
let header_keys = core::slice::from_raw_parts(header_keys as *const BnString, header_count);
let header_values = core::slice::from_raw_parts(header_values as *const BnString, header_count);
let header_keys_str = header_keys.iter().map(|s| s.to_string_lossy().to_string());
let header_values_str = header_values
.iter()
.map(|s| s.to_string_lossy().to_string());
let header = header_keys_str.zip(header_values_str);
ctxt.connect(&host.to_string_lossy(), header)
}
pub(crate) unsafe extern "C" fn cb_write<W: WebsocketClient>(
data: *const u8,
len: u64,
ctxt: *mut c_void,
) -> bool {
let ctxt: &mut W = &mut *(ctxt as *mut W);
let len = usize::try_from(len).unwrap();
let data = core::slice::from_raw_parts(data, len);
ctxt.write(data)
}
pub(crate) unsafe extern "C" fn cb_disconnect<W: WebsocketClient>(ctxt: *mut c_void) -> bool {
let ctxt: &mut W = &mut *(ctxt as *mut W);
ctxt.disconnect()
}
unsafe extern "C" fn cb_connected<W: WebsocketClientCallback>(ctxt: *mut c_void) -> bool {
let ctxt: &mut W = &mut *(ctxt as *mut W);
ctxt.connected()
}
unsafe extern "C" fn cb_disconnected<W: WebsocketClientCallback>(ctxt: *mut c_void) {
let ctxt: &mut W = &mut *(ctxt as *mut W);
ctxt.disconnected()
}
unsafe extern "C" fn cb_error<W: WebsocketClientCallback>(msg: *const c_char, ctxt: *mut c_void) {
let ctxt: &mut W = &mut *(ctxt as *mut W);
let msg = CStr::from_ptr(msg);
ctxt.error(&msg.to_string_lossy())
}
unsafe extern "C" fn cb_read<W: WebsocketClientCallback>(
data: *mut u8,
len: u64,
ctxt: *mut c_void,
) -> bool {
let ctxt: &mut W = &mut *(ctxt as *mut W);
let len = usize::try_from(len).unwrap();
let data = core::slice::from_raw_parts_mut(data, len);
ctxt.read(data)
}
|