Skip to content

Commit 2859c5b

Browse files
pinkisemilsniklasberglund
authored andcommitted
Improve error handling
1 parent d7ef378 commit 2859c5b

File tree

1 file changed

+101
-44
lines changed

1 file changed

+101
-44
lines changed

mullvad-api/src/ios_ffi.rs

+101-44
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
use std::{net::SocketAddr, sync::Arc};
1+
use std::{ffi::CString, net::SocketAddr, ptr, sync::Arc};
22

33
use crate::{
44
rest::{self, MullvadRestHandle},
55
AccountsProxy, DevicesProxy,
66
};
77

88
#[derive(Debug, PartialEq)]
9-
#[repr(i32)]
10-
pub enum FfiError {
9+
#[repr(C)]
10+
pub enum MullvadApiErrorKind {
1111
NoError = 0,
1212
StringParsing = -1,
1313
SocketAddressParsing = -2,
@@ -16,6 +16,47 @@ pub enum FfiError {
1616
BufferTooSmall = -5,
1717
}
1818

19+
/// MullvadApiErrorKind contains a description and an error kind. If the error kind is
20+
/// `MullvadApiErrorKind` is NoError, the pointer will be nil.
21+
#[repr(C)]
22+
pub struct MullvadApiError {
23+
description: *mut i8,
24+
kind: MullvadApiErrorKind,
25+
}
26+
27+
impl MullvadApiError {
28+
fn new(kind: MullvadApiErrorKind, error: &dyn std::error::Error) -> Self {
29+
let description = CString::new(format!("{error:?}")).unwrap_or_default();
30+
Self {
31+
description: description.into_raw(),
32+
kind,
33+
}
34+
}
35+
36+
fn api_err(error: &rest::Error) -> Self {
37+
Self::new(MullvadApiErrorKind::BadResponse, error)
38+
}
39+
40+
fn with_str(kind: MullvadApiErrorKind, description: &str) -> Self {
41+
let description = CString::new(description).unwrap_or_default();
42+
Self {
43+
description: description.into_raw(),
44+
kind,
45+
}
46+
}
47+
48+
fn ok() -> MullvadApiError {
49+
Self {
50+
description: CString::new("").unwrap().into_raw(),
51+
kind: MullvadApiErrorKind::NoError,
52+
}
53+
}
54+
55+
fn drop(self) {
56+
let _ = unsafe { CString::from_raw(self.description) };
57+
}
58+
}
59+
1960
/// IosMullvadApiClient is an FFI interface to our `mullvad-api`. It is a thread-safe to accessing
2061
/// our API.
2162
#[derive(Clone)]
@@ -79,23 +120,35 @@ pub extern "C" fn mullvad_api_initialize_api_runtime(
79120
api_address_len: usize,
80121
hostname: *const u8,
81122
hostname_len: usize,
82-
) -> FfiError {
123+
) -> MullvadApiError {
83124
let Some(addr_str) = (unsafe { string_from_raw_ptr(api_address_ptr, api_address_len) }) else {
84-
return FfiError::StringParsing;
125+
return MullvadApiError::with_str(
126+
MullvadApiErrorKind::StringParsing,
127+
"Failed to parse API socket address string",
128+
);
85129
};
86130
let Some(api_hostname) = (unsafe { string_from_raw_ptr(hostname, hostname_len) }) else {
87-
return FfiError::StringParsing;
131+
return MullvadApiError::with_str(
132+
MullvadApiErrorKind::StringParsing,
133+
"Failed to parse API host name",
134+
);
88135
};
89136

90137
let Ok(api_address): Result<SocketAddr, _> = addr_str.parse() else {
91-
return FfiError::SocketAddressParsing;
138+
return MullvadApiError::with_str(
139+
MullvadApiErrorKind::SocketAddressParsing,
140+
"Failed to parse API socket address",
141+
);
92142
};
93143

94144
let mut runtime_builder = tokio::runtime::Builder::new_multi_thread();
95145

96146
runtime_builder.worker_threads(2).enable_all();
97-
let Ok(tokio_runtime) = runtime_builder.build() else {
98-
return FfiError::AsyncRuntimeInitialization;
147+
let tokio_runtime = match runtime_builder.build() {
148+
Ok(runtime) => runtime,
149+
Err(err) => {
150+
return MullvadApiError::new(MullvadApiErrorKind::AsyncRuntimeInitialization, &err);
151+
}
99152
};
100153

101154
// It is imperative that the REST runtime is created within an async context, otherwise
@@ -116,18 +169,21 @@ pub extern "C" fn mullvad_api_initialize_api_runtime(
116169
std::ptr::write(context_ptr, context);
117170
}
118171

119-
FfiError::NoError
172+
MullvadApiError::ok()
120173
}
121174

122175
#[no_mangle]
123176
pub extern "C" fn mullvad_api_remove_all_devices(
124177
context: IosMullvadApiClient,
125178
account_str_ptr: *const u8,
126179
account_str_len: usize,
127-
) -> FfiError {
180+
) -> MullvadApiError {
128181
let ctx = unsafe { context.from_raw() };
129182
let Some(account) = (unsafe { string_from_raw_ptr(account_str_ptr, account_str_len) }) else {
130-
return FfiError::StringParsing;
183+
return MullvadApiError::with_str(
184+
MullvadApiErrorKind::StringParsing,
185+
"Failed to parse account number",
186+
);
131187
};
132188

133189
let runtime = ctx.tokio_handle();
@@ -141,8 +197,8 @@ pub extern "C" fn mullvad_api_remove_all_devices(
141197
});
142198

143199
match result {
144-
Ok(()) => FfiError::NoError,
145-
Err(_err) => FfiError::BadResponse,
200+
Ok(()) => MullvadApiError::ok(),
201+
Err(err) => MullvadApiError::api_err(&err),
146202
}
147203
}
148204

@@ -152,9 +208,12 @@ pub extern "C" fn mullvad_api_get_expiry(
152208
account_str_ptr: *const u8,
153209
account_str_len: usize,
154210
expiry_unix_timestamp: *mut i64,
155-
) -> FfiError {
211+
) -> MullvadApiError {
156212
let Some(account) = (unsafe { string_from_raw_ptr(account_str_ptr, account_str_len) }) else {
157-
return FfiError::StringParsing;
213+
return MullvadApiError::with_str(
214+
MullvadApiErrorKind::StringParsing,
215+
"Failed to parse account number",
216+
);
158217
};
159218

160219
let ctx = unsafe { context.from_raw() };
@@ -174,9 +233,9 @@ pub extern "C" fn mullvad_api_get_expiry(
174233
unsafe {
175234
std::ptr::write(expiry_unix_timestamp, expiry);
176235
}
177-
FfiError::NoError
236+
MullvadApiError::ok()
178237
}
179-
Err(_err) => FfiError::BadResponse,
238+
Err(err) => MullvadApiError::api_err(&err),
180239
}
181240
}
182241

@@ -189,10 +248,14 @@ pub extern "C" fn mullvad_api_add_device(
189248
account_str_ptr: *const u8,
190249
account_str_len: usize,
191250
public_key_ptr: *const u8,
192-
) -> FfiError {
251+
) -> MullvadApiError {
193252
let Some(account) = (unsafe { string_from_raw_ptr(account_str_ptr, account_str_len) }) else {
194-
return FfiError::StringParsing;
253+
return MullvadApiError::with_str(
254+
MullvadApiErrorKind::StringParsing,
255+
"Failed to parse account number",
256+
);
195257
};
258+
196259
let public_key_bytes: [u8; 32] = unsafe { std::ptr::read(public_key_ptr as *const _) };
197260
let public_key = public_key_bytes.into();
198261

@@ -207,8 +270,8 @@ pub extern "C" fn mullvad_api_add_device(
207270
});
208271

209272
match result {
210-
Ok(_result) => FfiError::NoError,
211-
Err(_err) => FfiError::BadResponse,
273+
Ok(_result) => MullvadApiError::ok(),
274+
Err(err) => MullvadApiError::api_err(&err),
212275
}
213276
}
214277

@@ -265,39 +328,28 @@ pub extern "C" fn mullvad_api_create_account(
265328
#[no_mangle]
266329
pub extern "C" fn mullvad_api_delete_account(
267330
context: IosMullvadApiClient,
268-
account_str_ptr: *mut u8,
269-
account_str_len: *mut usize,
331+
account_str_ptr: *const u8,
332+
account_str_len: usize,
270333
) -> MullvadApiError {
271334
let ctx = unsafe { context.from_raw() };
272335
let runtime = ctx.tokio_handle();
273-
let buffer_len = unsafe { ptr::read(account_str_len) };
336+
337+
let Some(account) = (unsafe { string_from_raw_ptr(account_str_ptr, account_str_len) }) else {
338+
return MullvadApiError::with_str(
339+
MullvadApiErrorKind::StringParsing,
340+
"Failed to parse account number",
341+
);
342+
};
274343

275344
let accounts_proxy = ctx.accounts_proxy();
276345

277346
let result: Result<_, rest::Error> = runtime.block_on(async move {
278-
let new_account = accounts_proxy.create_account().await?;
347+
let new_account = accounts_proxy.delete_account(account).await?;
279348
Ok(new_account)
280349
});
281350

282351
match result {
283-
Ok(new_account) => {
284-
let new_account_bytes = new_account.into_bytes();
285-
if new_account_bytes.len() > buffer_len {
286-
return MullvadApiError::with_str(
287-
MullvadApiErrorKind::BufferTooSmall,
288-
"Buffer for account number is too small",
289-
);
290-
}
291-
unsafe {
292-
ptr::copy(
293-
new_account_bytes.as_ptr(),
294-
account_str_ptr,
295-
new_account_bytes.len(),
296-
);
297-
}
298-
299-
MullvadApiError::ok()
300-
}
352+
Ok(()) => MullvadApiError::ok(),
301353
Err(err) => MullvadApiError::api_err(&err),
302354
}
303355
}
@@ -315,3 +367,8 @@ unsafe fn string_from_raw_ptr(ptr: *const u8, size: usize) -> Option<String> {
315367

316368
String::from_utf8(slice.to_vec()).ok()
317369
}
370+
371+
#[no_mangle]
372+
pub extern "C" fn mullvad_api_error_drop(error: MullvadApiError) {
373+
error.drop()
374+
}

0 commit comments

Comments
 (0)