1
- use std:: { net:: SocketAddr , sync:: Arc } ;
1
+ use std:: { ffi :: CString , net:: SocketAddr , ptr , sync:: Arc } ;
2
2
3
3
use crate :: {
4
4
rest:: { self , MullvadRestHandle } ,
5
5
AccountsProxy , DevicesProxy ,
6
6
} ;
7
7
8
8
#[ derive( Debug , PartialEq ) ]
9
- #[ repr( i32 ) ]
10
- pub enum FfiError {
9
+ #[ repr( C ) ]
10
+ pub enum MullvadApiErrorKind {
11
11
NoError = 0 ,
12
12
StringParsing = -1 ,
13
13
SocketAddressParsing = -2 ,
@@ -16,6 +16,47 @@ pub enum FfiError {
16
16
BufferTooSmall = -5 ,
17
17
}
18
18
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
+
19
60
/// IosMullvadApiClient is an FFI interface to our `mullvad-api`. It is a thread-safe to accessing
20
61
/// our API.
21
62
#[ derive( Clone ) ]
@@ -79,23 +120,35 @@ pub extern "C" fn mullvad_api_initialize_api_runtime(
79
120
api_address_len : usize ,
80
121
hostname : * const u8 ,
81
122
hostname_len : usize ,
82
- ) -> FfiError {
123
+ ) -> MullvadApiError {
83
124
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
+ ) ;
85
129
} ;
86
130
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
+ ) ;
88
135
} ;
89
136
90
137
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
+ ) ;
92
142
} ;
93
143
94
144
let mut runtime_builder = tokio:: runtime:: Builder :: new_multi_thread ( ) ;
95
145
96
146
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
+ }
99
152
} ;
100
153
101
154
// 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(
116
169
std:: ptr:: write ( context_ptr, context) ;
117
170
}
118
171
119
- FfiError :: NoError
172
+ MullvadApiError :: ok ( )
120
173
}
121
174
122
175
#[ no_mangle]
123
176
pub extern "C" fn mullvad_api_remove_all_devices (
124
177
context : IosMullvadApiClient ,
125
178
account_str_ptr : * const u8 ,
126
179
account_str_len : usize ,
127
- ) -> FfiError {
180
+ ) -> MullvadApiError {
128
181
let ctx = unsafe { context. from_raw ( ) } ;
129
182
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
+ ) ;
131
187
} ;
132
188
133
189
let runtime = ctx. tokio_handle ( ) ;
@@ -141,8 +197,8 @@ pub extern "C" fn mullvad_api_remove_all_devices(
141
197
} ) ;
142
198
143
199
match result {
144
- Ok ( ( ) ) => FfiError :: NoError ,
145
- Err ( _err ) => FfiError :: BadResponse ,
200
+ Ok ( ( ) ) => MullvadApiError :: ok ( ) ,
201
+ Err ( err ) => MullvadApiError :: api_err ( & err ) ,
146
202
}
147
203
}
148
204
@@ -152,9 +208,12 @@ pub extern "C" fn mullvad_api_get_expiry(
152
208
account_str_ptr : * const u8 ,
153
209
account_str_len : usize ,
154
210
expiry_unix_timestamp : * mut i64 ,
155
- ) -> FfiError {
211
+ ) -> MullvadApiError {
156
212
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
+ ) ;
158
217
} ;
159
218
160
219
let ctx = unsafe { context. from_raw ( ) } ;
@@ -174,9 +233,9 @@ pub extern "C" fn mullvad_api_get_expiry(
174
233
unsafe {
175
234
std:: ptr:: write ( expiry_unix_timestamp, expiry) ;
176
235
}
177
- FfiError :: NoError
236
+ MullvadApiError :: ok ( )
178
237
}
179
- Err ( _err ) => FfiError :: BadResponse ,
238
+ Err ( err ) => MullvadApiError :: api_err ( & err ) ,
180
239
}
181
240
}
182
241
@@ -189,10 +248,14 @@ pub extern "C" fn mullvad_api_add_device(
189
248
account_str_ptr : * const u8 ,
190
249
account_str_len : usize ,
191
250
public_key_ptr : * const u8 ,
192
- ) -> FfiError {
251
+ ) -> MullvadApiError {
193
252
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
+ ) ;
195
257
} ;
258
+
196
259
let public_key_bytes: [ u8 ; 32 ] = unsafe { std:: ptr:: read ( public_key_ptr as * const _ ) } ;
197
260
let public_key = public_key_bytes. into ( ) ;
198
261
@@ -207,8 +270,8 @@ pub extern "C" fn mullvad_api_add_device(
207
270
} ) ;
208
271
209
272
match result {
210
- Ok ( _result) => FfiError :: NoError ,
211
- Err ( _err ) => FfiError :: BadResponse ,
273
+ Ok ( _result) => MullvadApiError :: ok ( ) ,
274
+ Err ( err ) => MullvadApiError :: api_err ( & err ) ,
212
275
}
213
276
}
214
277
@@ -265,39 +328,28 @@ pub extern "C" fn mullvad_api_create_account(
265
328
#[ no_mangle]
266
329
pub extern "C" fn mullvad_api_delete_account (
267
330
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 ,
270
333
) -> MullvadApiError {
271
334
let ctx = unsafe { context. from_raw ( ) } ;
272
335
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
+ } ;
274
343
275
344
let accounts_proxy = ctx. accounts_proxy ( ) ;
276
345
277
346
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 ?;
279
348
Ok ( new_account)
280
349
} ) ;
281
350
282
351
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 ( ) ,
301
353
Err ( err) => MullvadApiError :: api_err ( & err) ,
302
354
}
303
355
}
@@ -315,3 +367,8 @@ unsafe fn string_from_raw_ptr(ptr: *const u8, size: usize) -> Option<String> {
315
367
316
368
String :: from_utf8 ( slice. to_vec ( ) ) . ok ( )
317
369
}
370
+
371
+ #[ no_mangle]
372
+ pub extern "C" fn mullvad_api_error_drop ( error : MullvadApiError ) {
373
+ error. drop ( )
374
+ }
0 commit comments