Skip to content

Commit f92128f

Browse files
committed
A little refactor to web sockets
1 parent 539f91f commit f92128f

File tree

7 files changed

+508
-589
lines changed

7 files changed

+508
-589
lines changed

rust/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ pub mod type_printer;
8484
pub mod types;
8585
pub mod update;
8686
pub mod variable;
87-
pub mod websocketprovider;
87+
pub mod websocket;
8888
pub mod worker_thread;
8989
pub mod workflow;
9090

rust/src/websocket.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//! Interface for registering new websocket providers
2+
//!
3+
//! WARNING: Do _not_ use this for anything other than provider registration. If you need to open a
4+
//! websocket connection use a real websocket library.
5+
6+
mod client;
7+
mod provider;
8+
9+
pub use client::*;
10+
pub use provider::*;

rust/src/websocket/client.rs

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
use crate::rc::{Ref, RefCountable};
2+
use crate::string::{BnStrCompatible, BnString};
3+
use binaryninjacore_sys::*;
4+
use std::ffi::{c_char, c_void, CStr};
5+
use std::ptr::NonNull;
6+
7+
pub trait WebsocketClientCallback: Sync + Send {
8+
fn connected(&mut self) -> bool;
9+
10+
fn disconnected(&mut self);
11+
12+
fn error(&mut self, msg: &str);
13+
14+
fn read(&mut self, data: &[u8]) -> bool;
15+
}
16+
17+
pub trait WebsocketClient: Sync + Send {
18+
/// Called to construct this client object with the given core object.
19+
fn from_core(core: Ref<CoreWebsocketClient>) -> Self;
20+
21+
fn connect<I, K, V>(&self, host: &str, headers: I) -> bool
22+
where
23+
I: IntoIterator<Item = (K, V)>,
24+
K: BnStrCompatible,
25+
V: BnStrCompatible;
26+
27+
fn write(&self, data: &[u8]) -> bool;
28+
29+
fn disconnect(&self) -> bool;
30+
}
31+
32+
/// Implements a websocket client.
33+
#[repr(transparent)]
34+
pub struct CoreWebsocketClient {
35+
pub(crate) handle: NonNull<BNWebsocketClient>,
36+
}
37+
38+
impl CoreWebsocketClient {
39+
pub(crate) unsafe fn ref_from_raw(handle: NonNull<BNWebsocketClient>) -> Ref<Self> {
40+
Ref::new(Self { handle })
41+
}
42+
43+
#[allow(clippy::mut_from_ref)]
44+
pub(crate) unsafe fn as_raw(&self) -> &mut BNWebsocketClient {
45+
&mut *self.handle.as_ptr()
46+
}
47+
48+
/// Initializes the web socket connection.
49+
///
50+
/// Connect to a given url, asynchronously. The connection will be run in a
51+
/// separate thread managed by the websocket provider.
52+
///
53+
/// Callbacks will be called **on the thread of the connection**, so be sure
54+
/// to ExecuteOnMainThread any long-running or gui operations in the callbacks.
55+
///
56+
/// If the connection succeeds, [WebsocketClientCallback::connected] will be called. On normal
57+
/// termination, [WebsocketClientCallback::disconnected] will be called.
58+
///
59+
/// If the connection succeeds, but later fails, [WebsocketClientCallback::disconnected] will not
60+
/// be called, and [WebsocketClientCallback::error] will be called instead.
61+
///
62+
/// If the connection fails, neither [WebsocketClientCallback::connected] nor
63+
/// [WebsocketClientCallback::disconnected] will be called, and [WebsocketClientCallback::error]
64+
/// will be called instead.
65+
///
66+
/// If [WebsocketClientCallback::connected] or [WebsocketClientCallback::read] return false, the
67+
/// connection will be aborted.
68+
///
69+
/// * `host` - Full url with scheme, domain, optionally port, and path
70+
/// * `headers` - HTTP header keys and values
71+
/// * `callback` - Callbacks for various websocket events
72+
pub fn initialize_connection<I, K, V, C>(
73+
&self,
74+
host: &str,
75+
headers: I,
76+
callbacks: &mut C,
77+
) -> bool
78+
where
79+
I: IntoIterator<Item = (K, V)>,
80+
K: BnStrCompatible,
81+
V: BnStrCompatible,
82+
C: WebsocketClientCallback,
83+
{
84+
let url = host.into_bytes_with_nul();
85+
let (header_keys, header_values): (Vec<K::Result>, Vec<V::Result>) = headers
86+
.into_iter()
87+
.map(|(k, v)| (k.into_bytes_with_nul(), v.into_bytes_with_nul()))
88+
.unzip();
89+
let header_keys: Vec<*const c_char> = header_keys
90+
.iter()
91+
.map(|k| k.as_ref().as_ptr() as *const c_char)
92+
.collect();
93+
let header_values: Vec<*const c_char> = header_values
94+
.iter()
95+
.map(|v| v.as_ref().as_ptr() as *const c_char)
96+
.collect();
97+
// SAFETY: This context will only be live for the duration of BNConnectWebsocketClient
98+
// SAFETY: Any subsequent call to BNConnectWebsocketClient will write over the context.
99+
let mut output_callbacks = BNWebsocketClientOutputCallbacks {
100+
context: callbacks as *mut C as *mut c_void,
101+
connectedCallback: Some(cb_connected::<C>),
102+
disconnectedCallback: Some(cb_disconnected::<C>),
103+
errorCallback: Some(cb_error::<C>),
104+
readCallback: Some(cb_read::<C>),
105+
};
106+
unsafe {
107+
BNConnectWebsocketClient(
108+
self.handle.as_ptr(),
109+
url.as_ptr() as *const c_char,
110+
header_keys.len().try_into().unwrap(),
111+
header_keys.as_ptr(),
112+
header_values.as_ptr(),
113+
&mut output_callbacks,
114+
)
115+
}
116+
}
117+
118+
/// Call the connect callback function, forward the callback returned value
119+
pub fn notify_connected(&self) -> bool {
120+
unsafe { BNNotifyWebsocketClientConnect(self.handle.as_ptr()) }
121+
}
122+
123+
/// Notify the callback function of a disconnect,
124+
///
125+
/// NOTE: This does not actually disconnect, use the [Self::disconnect] function for that.
126+
pub fn notify_disconnected(&self) {
127+
unsafe { BNNotifyWebsocketClientDisconnect(self.handle.as_ptr()) }
128+
}
129+
130+
/// Call the error callback function
131+
pub fn notify_error(&self, msg: &str) {
132+
let error = msg.into_bytes_with_nul();
133+
unsafe {
134+
BNNotifyWebsocketClientError(self.handle.as_ptr(), error.as_ptr() as *const c_char)
135+
}
136+
}
137+
138+
/// Call the read callback function, forward the callback returned value
139+
pub fn notify_read(&self, data: &[u8]) -> bool {
140+
unsafe {
141+
BNNotifyWebsocketClientReadData(
142+
self.handle.as_ptr(),
143+
data.as_ptr() as *mut _,
144+
data.len().try_into().unwrap(),
145+
)
146+
}
147+
}
148+
149+
pub fn write(&self, data: &[u8]) -> bool {
150+
let len = u64::try_from(data.len()).unwrap();
151+
unsafe { BNWriteWebsocketClientData(self.as_raw(), data.as_ptr(), len) != 0 }
152+
}
153+
154+
pub fn disconnect(&self) -> bool {
155+
unsafe { BNDisconnectWebsocketClient(self.as_raw()) }
156+
}
157+
}
158+
159+
unsafe impl Sync for CoreWebsocketClient {}
160+
unsafe impl Send for CoreWebsocketClient {}
161+
162+
impl ToOwned for CoreWebsocketClient {
163+
type Owned = Ref<Self>;
164+
165+
fn to_owned(&self) -> Self::Owned {
166+
unsafe { RefCountable::inc_ref(self) }
167+
}
168+
}
169+
170+
unsafe impl RefCountable for CoreWebsocketClient {
171+
unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
172+
let result = BNNewWebsocketClientReference(handle.as_raw());
173+
unsafe { Self::ref_from_raw(NonNull::new(result).unwrap()) }
174+
}
175+
176+
unsafe fn dec_ref(handle: &Self) {
177+
BNFreeWebsocketClient(handle.as_raw())
178+
}
179+
}
180+
181+
pub(crate) unsafe extern "C" fn cb_destroy_client<W: WebsocketClient>(ctxt: *mut c_void) {
182+
let _ = Box::from_raw(ctxt as *mut W);
183+
}
184+
185+
pub(crate) unsafe extern "C" fn cb_connect<W: WebsocketClient>(
186+
ctxt: *mut c_void,
187+
host: *const c_char,
188+
header_count: u64,
189+
header_keys: *const *const c_char,
190+
header_values: *const *const c_char,
191+
) -> bool {
192+
let ctxt: &mut W = &mut *(ctxt as *mut W);
193+
let host = CStr::from_ptr(host);
194+
// SAFETY BnString and *mut c_char are transparent
195+
let header_count = usize::try_from(header_count).unwrap();
196+
let header_keys = core::slice::from_raw_parts(header_keys as *const BnString, header_count);
197+
let header_values = core::slice::from_raw_parts(header_values as *const BnString, header_count);
198+
let header_keys_str = header_keys.iter().map(|s| s.to_string_lossy());
199+
let header_values_str = header_values.iter().map(|s| s.to_string_lossy());
200+
let header = header_keys_str.zip(header_values_str);
201+
ctxt.connect(&host.to_string_lossy(), header)
202+
}
203+
204+
pub(crate) unsafe extern "C" fn cb_write<W: WebsocketClient>(
205+
data: *const u8,
206+
len: u64,
207+
ctxt: *mut c_void,
208+
) -> bool {
209+
let ctxt: &mut W = &mut *(ctxt as *mut W);
210+
let len = usize::try_from(len).unwrap();
211+
let data = core::slice::from_raw_parts(data, len);
212+
ctxt.write(data)
213+
}
214+
215+
pub(crate) unsafe extern "C" fn cb_disconnect<W: WebsocketClient>(ctxt: *mut c_void) -> bool {
216+
let ctxt: &mut W = &mut *(ctxt as *mut W);
217+
ctxt.disconnect()
218+
}
219+
220+
unsafe extern "C" fn cb_connected<W: WebsocketClientCallback>(ctxt: *mut c_void) -> bool {
221+
let ctxt: &mut W = &mut *(ctxt as *mut W);
222+
ctxt.connected()
223+
}
224+
225+
unsafe extern "C" fn cb_disconnected<W: WebsocketClientCallback>(ctxt: *mut c_void) {
226+
let ctxt: &mut W = &mut *(ctxt as *mut W);
227+
ctxt.disconnected()
228+
}
229+
230+
unsafe extern "C" fn cb_error<W: WebsocketClientCallback>(msg: *const c_char, ctxt: *mut c_void) {
231+
let ctxt: &mut W = &mut *(ctxt as *mut W);
232+
let msg = CStr::from_ptr(msg);
233+
ctxt.error(&msg.to_string_lossy())
234+
}
235+
236+
unsafe extern "C" fn cb_read<W: WebsocketClientCallback>(
237+
data: *mut u8,
238+
len: u64,
239+
ctxt: *mut c_void,
240+
) -> bool {
241+
let ctxt: &mut W = &mut *(ctxt as *mut W);
242+
let len = usize::try_from(len).unwrap();
243+
let data = core::slice::from_raw_parts_mut(data, len);
244+
ctxt.read(data)
245+
}

rust/src/websocket/provider.rs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref};
2+
use crate::string::{BnStrCompatible, BnString};
3+
use crate::websocket::client;
4+
use crate::websocket::client::{CoreWebsocketClient, WebsocketClient};
5+
use binaryninjacore_sys::*;
6+
use std::ffi::{c_char, c_void};
7+
use std::mem::MaybeUninit;
8+
use std::ptr::NonNull;
9+
10+
pub fn register_websocket_provider<W>(name: &str) -> &'static mut W
11+
where
12+
W: WebsocketProvider,
13+
{
14+
let name = name.into_bytes_with_nul();
15+
let provider_uninit = MaybeUninit::uninit();
16+
// SAFETY: Websocket provider is never freed
17+
let leaked_provider = Box::leak(Box::new(provider_uninit));
18+
let result = unsafe {
19+
BNRegisterWebsocketProvider(
20+
name.as_ptr() as *const c_char,
21+
&mut BNWebsocketProviderCallbacks {
22+
context: leaked_provider as *mut _ as *mut c_void,
23+
createClient: Some(cb_create_client::<W>),
24+
},
25+
)
26+
};
27+
28+
let provider_core = unsafe { CoreWebsocketProvider::from_raw(NonNull::new(result).unwrap()) };
29+
// We now have the core provider so we can actually construct the object.
30+
leaked_provider.write(W::from_core(provider_core));
31+
unsafe { leaked_provider.assume_init_mut() }
32+
}
33+
34+
pub trait WebsocketProvider: Sync + Send + Sized {
35+
type Client: WebsocketClient;
36+
37+
fn handle(&self) -> CoreWebsocketProvider;
38+
39+
/// Called to construct this provider object with the given core object.
40+
fn from_core(core: CoreWebsocketProvider) -> Self;
41+
42+
/// Create a new instance of the websocket client.
43+
fn create_client(&self) -> Result<Ref<CoreWebsocketClient>, ()> {
44+
let client_uninit = MaybeUninit::uninit();
45+
// SAFETY: Websocket client is freed by cb_destroy_client
46+
let leaked_client = Box::leak(Box::new(client_uninit));
47+
let mut callbacks = BNWebsocketClientCallbacks {
48+
context: leaked_client as *mut _ as *mut c_void,
49+
connect: Some(client::cb_connect::<Self::Client>),
50+
destroyClient: Some(client::cb_destroy_client::<Self::Client>),
51+
disconnect: Some(client::cb_disconnect::<Self::Client>),
52+
write: Some(client::cb_write::<Self::Client>),
53+
};
54+
let client_ptr =
55+
unsafe { BNInitWebsocketClient(self.handle().handle.as_ptr(), &mut callbacks) };
56+
// TODO: If possible pass a sensible error back...
57+
let client_ptr = NonNull::new(client_ptr).ok_or(())?;
58+
let client_ref = unsafe { CoreWebsocketClient::ref_from_raw(client_ptr) };
59+
// We now have the core client so we can actually construct the object.
60+
leaked_client.write(Self::Client::from_core(client_ref.clone()));
61+
Ok(client_ref)
62+
}
63+
}
64+
65+
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
66+
#[repr(transparent)]
67+
pub struct CoreWebsocketProvider {
68+
handle: NonNull<BNWebsocketProvider>,
69+
}
70+
71+
impl CoreWebsocketProvider {
72+
pub(crate) unsafe fn from_raw(handle: NonNull<BNWebsocketProvider>) -> Self {
73+
Self { handle }
74+
}
75+
76+
pub fn all() -> Array<Self> {
77+
let mut count = 0;
78+
let result = unsafe { BNGetWebsocketProviderList(&mut count) };
79+
assert!(!result.is_null());
80+
unsafe { Array::new(result, count, ()) }
81+
}
82+
83+
pub fn by_name<S: BnStrCompatible>(name: S) -> Option<CoreWebsocketProvider> {
84+
let name = name.into_bytes_with_nul();
85+
let result =
86+
unsafe { BNGetWebsocketProviderByName(name.as_ref().as_ptr() as *const c_char) };
87+
NonNull::new(result).map(|h| unsafe { Self::from_raw(h) })
88+
}
89+
90+
pub fn name(&self) -> BnString {
91+
let result = unsafe { BNGetWebsocketProviderName(self.handle.as_ptr()) };
92+
assert!(!result.is_null());
93+
unsafe { BnString::from_raw(result) }
94+
}
95+
}
96+
97+
unsafe impl Sync for CoreWebsocketProvider {}
98+
unsafe impl Send for CoreWebsocketProvider {}
99+
100+
impl CoreArrayProvider for CoreWebsocketProvider {
101+
type Raw = *mut BNWebsocketProvider;
102+
type Context = ();
103+
type Wrapped<'a> = Self;
104+
}
105+
106+
unsafe impl CoreArrayProviderInner for CoreWebsocketProvider {
107+
unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) {
108+
BNFreeWebsocketProviderList(raw)
109+
}
110+
111+
unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> {
112+
let handle = NonNull::new(*raw).unwrap();
113+
Self::from_raw(handle)
114+
}
115+
}
116+
117+
unsafe extern "C" fn cb_create_client<W: WebsocketProvider>(
118+
ctxt: *mut c_void,
119+
) -> *mut BNWebsocketClient {
120+
let ctxt: &mut W = &mut *(ctxt as *mut W);
121+
match ctxt.create_client() {
122+
Ok(owned_client) => {
123+
// SAFETY: The caller is assumed to have picked up this ref.
124+
Ref::into_raw(owned_client).handle.as_ptr()
125+
}
126+
Err(_) => std::ptr::null_mut(),
127+
}
128+
}

0 commit comments

Comments
 (0)