@@ -9,6 +9,7 @@ use crate::util::*;
9
9
use crate :: { as_mut, as_ref, cstr, ffi, safe_str} ;
10
10
use anyhow:: { anyhow, Context } ;
11
11
use libc:: { c_char, c_int, c_uint, EXIT_FAILURE , EXIT_SUCCESS } ;
12
+ use std:: ops:: Drop ;
12
13
13
14
/*===============================================================================================
14
15
* # Re-Exports
@@ -110,8 +111,7 @@ pub unsafe extern "C" fn message_get_description(
110
111
params: [ message] ,
111
112
op: {
112
113
let message = as_ref!( message) ;
113
- let description = string:: into_leaked_cstring( & message. description) ?;
114
- Ok ( description)
114
+ Ok ( string:: to_c( & message. description) ? as * const c_char)
115
115
} ,
116
116
fail: {
117
117
ptr:: null_to:: <c_char>( )
@@ -222,15 +222,14 @@ pub unsafe extern "C" fn message_find_metadata(
222
222
name: "message_find_metadata" ,
223
223
params: [ message, key] ,
224
224
op: {
225
+ // Reconstitute the message.
225
226
let message = as_ref!( message) ;
227
+ // Safely get a Rust String out of the key.
226
228
let key = safe_str!( key) ;
227
-
228
- match message. metadata. get( key) {
229
- None => Ok ( ptr:: null_to:: <c_char>( ) ) ,
230
- Some ( value) => {
231
- Ok ( string:: into_leaked_cstring( value) ?)
232
- } ,
233
- }
229
+ // Get the value, if present, for that key.
230
+ let value = message. metadata. get( key) . ok_or( anyhow:: anyhow!( "invalid metadata key" ) ) ?;
231
+ // Leak the string to the C-side.
232
+ Ok ( string:: to_c( value) ? as * const c_char)
234
233
} ,
235
234
fail: {
236
235
ptr:: null_to:: <c_char>( )
@@ -276,14 +275,13 @@ pub unsafe extern "C" fn message_insert_metadata(
276
275
}
277
276
}
278
277
279
- /*
280
- /// Get a copy of the metadata list from this message.
281
- /// It is in the form of a list of (key, value) pairs,
282
- /// in an unspecified order.
283
- /// The returned structure must be deleted with `metadata_list_delete`.
278
+ /// Get an iterator over the metadata of a message.
284
279
///
285
- /// Since it is a copy, the returned structure may safely outlive
286
- /// the `Message`.
280
+ /// This iterator carries a pointer to the message, and must
281
+ /// not outlive the message.
282
+ ///
283
+ /// The message metadata also must not be modified during iteration. If it is,
284
+ /// the old iterator must be deleted and a new iterator created.
287
285
///
288
286
/// # Errors
289
287
///
@@ -294,23 +292,165 @@ pub unsafe extern "C" fn message_insert_metadata(
294
292
#[ no_mangle]
295
293
#[ allow( clippy:: missing_safety_doc) ]
296
294
#[ allow( clippy:: or_fun_call) ]
297
- pub unsafe extern "C" fn message_get_metadata_list (
295
+ pub unsafe extern "C" fn message_get_metadata_iter (
298
296
message : * mut Message ,
299
297
) -> * mut MetadataIterator {
300
298
ffi ! {
301
- name: "message_get_metadata_list ",
299
+ name: "message_get_metadata_iter " ,
302
300
params: [ message] ,
303
301
op: {
304
302
let message = as_mut!( message) ;
305
303
306
- todo!()
304
+ let iter = MetadataIterator {
305
+ keys: message. metadata. keys( ) . cloned( ) . collect( ) ,
306
+ current: 0 ,
307
+ message: message as * const Message ,
308
+ } ;
309
+
310
+ Ok ( ptr:: raw_to( iter) )
311
+ } ,
312
+ fail: {
313
+ ptr:: null_mut_to:: <MetadataIterator >( )
314
+ }
315
+ }
316
+ }
317
+
318
+ /// Get the next key and value out of the iterator, if possible
319
+ ///
320
+ /// Returns a pointer to a heap allocated array of 2 elements, the pointer to the
321
+ /// key string on the heap, and the pointer to the value string on the heap.
322
+ ///
323
+ /// The user needs to free both the contained strings and the array.
324
+ #[ no_mangle]
325
+ #[ allow( clippy:: missing_safety_doc) ]
326
+ #[ allow( clippy:: or_fun_call) ]
327
+ pub unsafe extern "C" fn metadata_iter_next (
328
+ iter : * mut MetadataIterator ,
329
+ ) -> * mut MetadataPair {
330
+ ffi ! {
331
+ name: "metadata_iter_next" ,
332
+ params: [ iter] ,
333
+ op: {
334
+ // Reconstitute the iterator.
335
+ let iter = as_mut!( iter) ;
336
+
337
+ // Reconstitute the message.
338
+ let message = as_ref!( iter. message) ;
339
+
340
+ // Get the current key from the iterator.
341
+ let key = iter. next( ) . ok_or( anyhow:: anyhow!( "iter past the end of metadata" ) ) ?;
342
+
343
+ // Get the value for the current key.
344
+ let ( key, value) = message. metadata. get_key_value( key) . ok_or( anyhow:: anyhow!( "iter provided invalid metadata key" ) ) ?;
345
+
346
+ // Package up for return.
347
+ let pair = MetadataPair :: new( key, value) ?;
348
+
349
+ // Leak the value out to the C-side.
350
+ Ok ( ptr:: raw_to( pair) )
307
351
} ,
308
352
fail: {
309
- ptr::null_to ::<MetadataIterator >()
353
+ ptr:: null_mut_to :: <MetadataPair >( )
310
354
}
311
355
}
312
356
}
313
- */
357
+
358
+ /// Free the metadata iterator when you're done using it.
359
+ #[ no_mangle]
360
+ #[ allow( clippy:: missing_safety_doc) ]
361
+ pub unsafe extern "C" fn metadata_iter_delete (
362
+ iter : * mut MetadataIterator ,
363
+ ) -> c_int {
364
+ ffi ! {
365
+ name: "metadata_iter_delete" ,
366
+ params: [ iter] ,
367
+ op: {
368
+ ptr:: drop_raw( iter) ;
369
+ Ok ( EXIT_SUCCESS )
370
+ } ,
371
+ fail: {
372
+ EXIT_FAILURE
373
+ }
374
+ }
375
+ }
376
+
377
+ /// Free a pair of key and value returned from `message_next_metadata_iter`.
378
+ #[ no_mangle]
379
+ #[ allow( clippy:: missing_safety_doc) ]
380
+ pub unsafe extern "C" fn metadata_pair_delete (
381
+ pair : * mut MetadataPair ,
382
+ ) -> c_int {
383
+ ffi ! {
384
+ name: "metadata_pair_delete" ,
385
+ params: [ pair] ,
386
+ op: {
387
+ ptr:: drop_raw( pair) ;
388
+ Ok ( EXIT_SUCCESS )
389
+ } ,
390
+ fail: {
391
+ EXIT_FAILURE
392
+ }
393
+ }
394
+ }
395
+
396
+ /// An iterator that enables FFI iteration over metadata by putting all the keys on the heap
397
+ /// and tracking which one we're currently at.
398
+ ///
399
+ /// This assumes no mutation of the underlying metadata happens while the iterator is live.
400
+ #[ derive( Debug ) ]
401
+ pub struct MetadataIterator {
402
+ /// The metadata keys
403
+ keys : Vec < String > ,
404
+ /// The current key
405
+ current : usize ,
406
+ /// Pointer to the message.
407
+ message : * const Message ,
408
+ }
409
+
410
+ impl MetadataIterator {
411
+ fn next ( & mut self ) -> Option < & String > {
412
+ let idx = self . current ;
413
+ self . current += 1 ;
414
+ self . keys . get ( idx)
415
+ }
416
+ }
417
+
418
+ /// A single key-value pair exported to the C-side.
419
+ #[ derive( Debug ) ]
420
+ #[ repr( C ) ]
421
+ #[ allow( missing_copy_implementations) ]
422
+ pub struct MetadataPair {
423
+ key : * const c_char ,
424
+ value : * const c_char ,
425
+ }
426
+
427
+ impl MetadataPair {
428
+ fn new ( key : & str , value : & str ) -> anyhow:: Result < MetadataPair > {
429
+ Ok ( MetadataPair {
430
+ key : string:: to_c ( key) ? as * const c_char ,
431
+ value : string:: to_c ( value) ? as * const c_char ,
432
+ } )
433
+ }
434
+ }
435
+
436
+ // Ensure that the owned strings are freed when the pair is dropped.
437
+ //
438
+ // Notice that we're casting from a `*const c_char` to a `*mut c_char`.
439
+ // This may seem wrong, but is safe so long as it doesn't violate Rust's
440
+ // guarantees around immutable references, which this doesn't. In this case,
441
+ // the underlying data came from `CString::into_raw` which takes ownership
442
+ // of the `CString` and hands it off via a `*mut pointer`. We cast that pointer
443
+ // back to `*const` to limit the C-side from doing any shenanigans, since the
444
+ // pointed-to values live inside of the `Message` metadata `HashMap`, but
445
+ // cast back to `*mut` here so we can free the memory.
446
+ //
447
+ // The discussion here helps explain: https://github.com/rust-lang/rust-clippy/issues/4774
448
+ impl Drop for MetadataPair {
449
+ fn drop ( & mut self ) {
450
+ string:: string_delete ( self . key as * mut c_char ) ;
451
+ string:: string_delete ( self . value as * mut c_char ) ;
452
+ }
453
+ }
314
454
315
455
/*===============================================================================================
316
456
* # Status Types
0 commit comments