From 5cfcf5d6e2386f2ed2de14f8fe72be3e2306d2eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pascal=20St=C3=B6rzbach?= Date: Fri, 17 Nov 2023 19:43:45 +0100 Subject: [PATCH] feat: Network api (#15) This PR adds a binding to the wasm network api introduced with SU11. --- msfs/Cargo.toml | 1 + msfs/src/bindgen_support/wrapper.h | 1 + msfs/src/lib.rs | 4 + msfs/src/network.rs | 207 +++++++++++++++++++++++++++++ 4 files changed, 213 insertions(+) create mode 100644 msfs/src/network.rs diff --git a/msfs/Cargo.toml b/msfs/Cargo.toml index 5138f60397..f63080851c 100644 --- a/msfs/Cargo.toml +++ b/msfs/Cargo.toml @@ -9,6 +9,7 @@ license = "MIT" [dependencies] msfs_derive = { path = "../msfs_derive", version = "0.2.0" } futures = "0.3" +libc = "0.2" [build-dependencies] bindgen = "0.66" diff --git a/msfs/src/bindgen_support/wrapper.h b/msfs/src/bindgen_support/wrapper.h index fdb04a802b..8167c0cbe0 100644 --- a/msfs/src/bindgen_support/wrapper.h +++ b/msfs/src/bindgen_support/wrapper.h @@ -3,5 +3,6 @@ #include #include #include +#include #include #include diff --git a/msfs/src/lib.rs b/msfs/src/lib.rs index 0ed83920f7..535c20ea39 100644 --- a/msfs/src/lib.rs +++ b/msfs/src/lib.rs @@ -5,6 +5,7 @@ //! - MSFS Gauge API //! - SimConnect API //! - NanoVG API +//! - Networking API //! //! ## Building //! @@ -39,5 +40,8 @@ pub mod legacy; #[cfg(any(target_arch = "wasm32", doc))] pub mod nvg; +#[cfg(any(target_arch = "wasm32", doc))] +pub mod network; + #[doc(hidden)] pub mod executor; diff --git a/msfs/src/network.rs b/msfs/src/network.rs new file mode 100644 index 0000000000..29ef62395b --- /dev/null +++ b/msfs/src/network.rs @@ -0,0 +1,207 @@ +//! Bindings to the networking API. It can be used to do HTTPS requests. + +use crate::sys; +use std::{ + ffi::{self, CStr, CString}, + ptr, slice, +}; + +type NetworkCallback = Box; + +/// A builder to build network requests +pub struct NetworkRequestBuilder<'a> { + url: CString, + headers: Vec, + data: Option<&'a mut [u8]>, + callback: Option>, +} +impl<'a> NetworkRequestBuilder<'a> { + /// Create a new network request + pub fn new(url: &str) -> Option { + Some(Self { + url: CString::new(url).ok()?, + headers: vec![], + data: None, + callback: None, + }) + } + + /// Set a HTTP header + pub fn with_header(mut self, header: &str) -> Option { + self.headers.push(CString::new(header).ok()?); + Some(self) + } + + /// Set the data to be sent + pub fn with_data(mut self, data: &'a mut [u8]) -> Self { + self.data = Some(data); + self + } + + /// Set a callback which will be called after the request finished or failed. + /// The parameters are the network request and the http status code (negative if failed) + pub fn with_callback(mut self, callback: impl FnOnce(NetworkRequest, i32) + 'static) -> Self { + self.callback = Some(Box::new(Box::new(callback))); + self + } + + /// Do HTTP GET request + pub fn get(self) -> Option { + self.do_request(None, sys::fsNetworkHttpRequestGet) + } + + /// Do HTTP POST request + pub fn post(self, post_field: &str) -> Option { + let post_field = CString::new(post_field).unwrap(); + self.do_request(Some(post_field), sys::fsNetworkHttpRequestPost) + } + + /// Do HTTP PUT request + pub fn put(self) -> Option { + self.do_request(None, sys::fsNetworkHttpRequestPut) + } + + fn do_request( + mut self, + post_field: Option, + request: unsafe extern "C" fn( + *const ::std::os::raw::c_char, + *mut sys::FsNetworkHttpRequestParam, + sys::HttpRequestCallback, + *mut ::std::os::raw::c_void, + ) -> sys::FsNetworkRequestId, + ) -> Option { + // SAFETY: we need a *mut i8 for the FsNetworkHttpRequestParam struct but this should be fine. + let raw_post_field = + post_field.map_or(ptr::null_mut(), |f| f.as_c_str().as_ptr() as *mut i8); + // SAFETY: Because the struct in the C code is not defined as const char* we need to cast + // the *const into *mut which should be safe because the function should not change it anyway + let mut headers = self + .headers + .iter_mut() + .map(|h| h.as_ptr() as *mut i8) + .collect::>(); + let data_len = self.data.as_ref().map_or(0, |d| d.len()); + let mut params = sys::FsNetworkHttpRequestParam { + postField: raw_post_field, + headerOptions: headers.as_mut_ptr(), + headerOptionsSize: headers.len() as std::os::raw::c_uint, + data: self + .data + .as_mut() + .map_or(ptr::null_mut(), |d| d.as_mut_ptr()), + dataSize: data_len as std::os::raw::c_uint, + }; + let callback_data = self.callback.map_or(ptr::null_mut(), Box::into_raw) as *mut _; + let request_id = unsafe { + request( + self.url.as_ptr(), + &mut params as *mut sys::FsNetworkHttpRequestParam, + Some(Self::c_wrapper), + callback_data, + ) + }; + if request_id == 0 { + // Free the callback + let _: Box = unsafe { Box::from_raw(callback_data as *mut _) }; + None + } else { + Some(NetworkRequest(request_id)) + } + } + + extern "C" fn c_wrapper( + request_id: sys::FsNetworkRequestId, + status_code: i32, + user_data: *mut ffi::c_void, + ) { + if !user_data.is_null() { + let callback: Box = unsafe { Box::from_raw(user_data as *mut _) }; + callback(NetworkRequest(request_id), status_code); + } + } +} + +/// The states in which a network request can be in +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum NetworkRequestState { + Invalid, + New, + WaitingForData, + DataReady, + Failed, +} +impl From for NetworkRequestState { + fn from(value: sys::FsNetworkHttpRequestState) -> Self { + match value { + sys::FsNetworkHttpRequestState_FS_NETWORK_HTTP_REQUEST_STATE_INVALID => Self::Invalid, + sys::FsNetworkHttpRequestState_FS_NETWORK_HTTP_REQUEST_STATE_NEW => Self::New, + sys::FsNetworkHttpRequestState_FS_NETWORK_HTTP_REQUEST_STATE_WAITING_FOR_DATA => { + Self::WaitingForData + } + sys::FsNetworkHttpRequestState_FS_NETWORK_HTTP_REQUEST_STATE_DATA_READY => { + Self::DataReady + } + sys::FsNetworkHttpRequestState_FS_NETWORK_HTTP_REQUEST_STATE_FAILED => Self::Failed, + _ => panic!("Unknown request state"), + } + } +} + +/// Network request handle +#[derive(Clone, Copy)] +pub struct NetworkRequest(sys::FsNetworkRequestId); +impl NetworkRequest { + /// Cancel a network request + pub fn cancel(&self) -> bool { + unsafe { sys::fsNetworkHttpCancelRequest(self.0) } + } + + /// Get the size of the data + pub fn data_size(&self) -> usize { + unsafe { sys::fsNetworkHttpRequestGetDataSize(self.0) as usize } + } + + /// Get the data + pub fn data(&self) -> Option> { + let data_size = self.data_size(); + if data_size == 0 { + return None; + } + unsafe { + let data = sys::fsNetworkHttpRequestGetData(self.0); + if data.is_null() { + None + } else { + let result = slice::from_raw_parts(data, data_size).to_owned(); + libc::free(data as *mut ffi::c_void); + Some(result) + } + } + } + + /// Get the HTTP status code or negative if the request failed + pub fn error_code(&self) -> i32 { + unsafe { sys::fsNetworkHttpRequestGetErrorCode(self.0) } + } + + /// Get the current state of the request + pub fn state(&self) -> NetworkRequestState { + unsafe { sys::fsNetworkHttpRequestGetState(self.0).into() } + } + + /// Get a specific header section + pub fn header_section(&self, section: &str) -> Option { + let section = CString::new(section).ok()?; + unsafe { + let a = sys::fsNetworkHttpRequestGetHeaderSection(self.0, section.as_ptr()); + if a.is_null() { + None + } else { + let result = CStr::from_ptr(a).to_str().ok().map(|s| s.to_owned()); + libc::free(a as *mut ffi::c_void); + result + } + } + } +}