msfs/
network.rs

1//! Bindings to the networking API. It can be used to do HTTPS requests.
2
3use crate::sys;
4use std::{
5    ffi::{self, CStr, CString},
6    ptr, slice,
7};
8
9type NetworkCallback = Box<dyn FnOnce(NetworkRequest, i32)>;
10
11/// A builder to build network requests
12pub struct NetworkRequestBuilder<'a> {
13    url: CString,
14    headers: Vec<CString>,
15    data: Option<&'a mut [u8]>,
16    callback: Option<Box<NetworkCallback>>,
17}
18impl<'a> NetworkRequestBuilder<'a> {
19    /// Create a new network request
20    pub fn new(url: &str) -> Option<Self> {
21        Some(Self {
22            url: CString::new(url).ok()?,
23            headers: vec![],
24            data: None,
25            callback: None,
26        })
27    }
28
29    /// Set a HTTP header
30    pub fn with_header(mut self, header: &str) -> Option<Self> {
31        self.headers.push(CString::new(header).ok()?);
32        Some(self)
33    }
34
35    /// Set the data to be sent
36    pub fn with_data(mut self, data: &'a mut [u8]) -> Self {
37        self.data = Some(data);
38        self
39    }
40
41    /// Set a callback which will be called after the request finished or failed.
42    /// The parameters are the network request and the http status code (negative if failed)
43    pub fn with_callback(mut self, callback: impl FnOnce(NetworkRequest, i32) + 'static) -> Self {
44        self.callback = Some(Box::new(Box::new(callback)));
45        self
46    }
47
48    /// Do HTTP GET request
49    pub fn get(self) -> Option<NetworkRequest> {
50        self.do_request(None, sys::fsNetworkHttpRequestGet)
51    }
52
53    /// Do HTTP POST request
54    pub fn post(self, post_field: &str) -> Option<NetworkRequest> {
55        let post_field = CString::new(post_field).unwrap();
56        self.do_request(Some(post_field), sys::fsNetworkHttpRequestPost)
57    }
58
59    /// Do HTTP PUT request
60    pub fn put(self) -> Option<NetworkRequest> {
61        self.do_request(None, sys::fsNetworkHttpRequestPut)
62    }
63
64    fn do_request(
65        mut self,
66        post_field: Option<CString>,
67        request: unsafe extern "C" fn(
68            *const ::std::os::raw::c_char,
69            *mut sys::FsNetworkHttpRequestParam,
70            sys::HttpRequestCallback,
71            *mut ::std::os::raw::c_void,
72        ) -> sys::FsNetworkRequestId,
73    ) -> Option<NetworkRequest> {
74        // SAFETY: we need a *mut i8 for the FsNetworkHttpRequestParam struct but this should be fine.
75        let raw_post_field = post_field
76            .as_ref()
77            .map_or(ptr::null_mut(), |f| f.as_c_str().as_ptr() as *mut i8);
78
79        // SAFETY: Because the struct in the C code is not defined as const char* we need to cast
80        // the *const into *mut which should be safe because the function should not change it anyway
81        let mut headers = self
82            .headers
83            .iter_mut()
84            .map(|h| h.as_ptr() as *mut i8)
85            .collect::<Vec<_>>();
86        let data_len = self.data.as_ref().map_or(0, |d| d.len());
87        let mut params = sys::FsNetworkHttpRequestParam {
88            postField: raw_post_field,
89            headerOptions: headers.as_mut_ptr(),
90            headerOptionsSize: headers.len() as std::os::raw::c_uint,
91            data: self
92                .data
93                .as_mut()
94                .map_or(ptr::null_mut(), |d| d.as_mut_ptr()),
95            dataSize: data_len as std::os::raw::c_uint,
96        };
97        let callback_data = self.callback.map_or(ptr::null_mut(), Box::into_raw) as *mut _;
98        let request_id = unsafe {
99            request(
100                self.url.as_ptr(),
101                &mut params as *mut sys::FsNetworkHttpRequestParam,
102                Some(Self::c_wrapper),
103                callback_data,
104            )
105        };
106
107        if request_id == 0 {
108            // Free the callback
109            let _: Box<NetworkCallback> = unsafe { Box::from_raw(callback_data as *mut _) };
110            None
111        } else {
112            Some(NetworkRequest(request_id))
113        }
114    }
115
116    extern "C" fn c_wrapper(
117        request_id: sys::FsNetworkRequestId,
118        status_code: i32,
119        user_data: *mut ffi::c_void,
120    ) {
121        if !user_data.is_null() {
122            let callback: Box<NetworkCallback> = unsafe { Box::from_raw(user_data as *mut _) };
123            callback(NetworkRequest(request_id), status_code);
124        }
125    }
126}
127
128/// The states in which a network request can be in
129#[derive(Debug, Clone, Copy, PartialEq, Eq)]
130pub enum NetworkRequestState {
131    Invalid,
132    New,
133    WaitingForData,
134    DataReady,
135    Failed,
136}
137impl From<sys::FsNetworkHttpRequestState> for NetworkRequestState {
138    fn from(value: sys::FsNetworkHttpRequestState) -> Self {
139        match value {
140            sys::FsNetworkHttpRequestState_FS_NETWORK_HTTP_REQUEST_STATE_INVALID => Self::Invalid,
141            sys::FsNetworkHttpRequestState_FS_NETWORK_HTTP_REQUEST_STATE_NEW => Self::New,
142            sys::FsNetworkHttpRequestState_FS_NETWORK_HTTP_REQUEST_STATE_WAITING_FOR_DATA => {
143                Self::WaitingForData
144            }
145            sys::FsNetworkHttpRequestState_FS_NETWORK_HTTP_REQUEST_STATE_DATA_READY => {
146                Self::DataReady
147            }
148            sys::FsNetworkHttpRequestState_FS_NETWORK_HTTP_REQUEST_STATE_FAILED => Self::Failed,
149            _ => panic!("Unknown request state"),
150        }
151    }
152}
153
154/// Network request handle
155#[derive(Clone, Copy)]
156pub struct NetworkRequest(sys::FsNetworkRequestId);
157impl NetworkRequest {
158    /// Cancel a network request
159    pub fn cancel(&self) -> bool {
160        unsafe { sys::fsNetworkHttpCancelRequest(self.0) }
161    }
162
163    /// Get the size of the data
164    pub fn data_size(&self) -> usize {
165        unsafe { sys::fsNetworkHttpRequestGetDataSize(self.0) as usize }
166    }
167
168    /// Get the data
169    pub fn data(&self) -> Option<Vec<u8>> {
170        let data_size = self.data_size();
171        if data_size == 0 {
172            return None;
173        }
174        unsafe {
175            let data = sys::fsNetworkHttpRequestGetData(self.0);
176            if data.is_null() {
177                None
178            } else {
179                let result = slice::from_raw_parts(data, data_size).to_owned();
180                libc::free(data as *mut ffi::c_void);
181                Some(result)
182            }
183        }
184    }
185
186    /// Get the HTTP status code or negative if the request failed
187    pub fn error_code(&self) -> i32 {
188        unsafe { sys::fsNetworkHttpRequestGetErrorCode(self.0) }
189    }
190
191    /// Get the current state of the request
192    pub fn state(&self) -> NetworkRequestState {
193        unsafe { sys::fsNetworkHttpRequestGetState(self.0).into() }
194    }
195
196    /// Get a specific header section
197    pub fn header_section(&self, section: &str) -> Option<String> {
198        let section = CString::new(section).ok()?;
199        unsafe {
200            let a = sys::fsNetworkHttpRequestGetHeaderSection(self.0, section.as_ptr());
201            if a.is_null() {
202                None
203            } else {
204                let result = CStr::from_ptr(a).to_str().ok().map(|s| s.to_owned());
205                libc::free(a as *mut ffi::c_void);
206                result
207            }
208        }
209    }
210}