diff --git a/include/zenoh_commons.h b/include/zenoh_commons.h index aecd40786..d0f4ee334 100644 --- a/include/zenoh_commons.h +++ b/include/zenoh_commons.h @@ -146,6 +146,59 @@ typedef struct z_bytes_t { size_t len; const uint8_t *start; } z_bytes_t; +/** + * The body of a loop over an attachment's key-value pairs. + * + * `key` and `value` are loaned to the body for the duration of a single call. + * `context` is passed transparently through the iteration driver. + * + * Returning `0` is treated as `continue`. + * Returning any other value is treated as `break`. + */ +typedef int8_t (*z_attachement_iter_body_t)(struct z_bytes_t key, + struct z_bytes_t value, + void *context); +/** + * The driver of a loop over an attachement's key-value pairs. + * + * This function is expected to call `loop_body` once for each key-value pair + * within `iterator`, passing `context`, and returning any non-zero value immediately (breaking iteration). + */ +typedef int8_t (*z_attachement_iter_driver_t)(void *iterator, + z_attachement_iter_body_t loop_body, + void *context); +/** + * The v-table for an attachement. + */ +typedef struct z_attachement_vtable_t { + /** + * See `z_attachement_iteration_driver_t`'s documentation. + */ + z_attachement_iter_driver_t iteration_driver; + /** + * Returns the number of key-value pairs within the attachement. + */ + uintptr_t (*len)(const void*); +} z_attachement_vtable_t; +/** + * A v-table based map of vector of bool to vector of bool. + * + * `vtable == NULL` marks the gravestone value, as this type is often optional. + * Users are encouraged to use `z_attachement_null` and `z_attachement_check` to interact. + */ +typedef struct z_attachement_t { + void *data; + const struct z_attachement_vtable_t *vtable; +} z_attachement_t; +/** + * A map of owned vector of bytes to owned vector of bytes. + * + * In Zenoh C, this map is backed by Rust's standard HashMap, with a DoS-resistant hasher + */ +typedef struct z_owned_bytes_map_t { + uint64_t _0[2]; + uintptr_t _1[4]; +} z_owned_bytes_map_t; /** * Represents a Zenoh ID. * @@ -331,6 +384,7 @@ typedef struct z_sample_t { const void *_zc_buf; enum z_sample_kind_t kind; struct z_timestamp_t timestamp; + struct z_attachement_t attachements; } z_sample_t; /** * A closure is a structure that contains all the elements for stateful, memory-leak-free callbacks. @@ -620,6 +674,7 @@ typedef struct z_put_options_t { struct z_encoding_t encoding; enum z_congestion_control_t congestion_control; enum z_priority_t priority; + struct z_attachement_t attachements; } z_put_options_t; /** * Represents the set of options that can be applied to a query reply, @@ -731,10 +786,66 @@ ZENOHC_API extern const char *Z_CONFIG_MULTICAST_IPV4_ADDRESS_KEY; ZENOHC_API extern const char *Z_CONFIG_SCOUTING_TIMEOUT_KEY; ZENOHC_API extern const char *Z_CONFIG_SCOUTING_DELAY_KEY; ZENOHC_API extern const char *Z_CONFIG_ADD_TIMESTAMP_KEY; +/** + * Returns the gravestone value for `z_attachement_t`. + */ +ZENOHC_API bool z_attachement_check(const struct z_attachement_t *this_); +/** + * Iterate over `this`'s key-value pairs, breaking if `body` returns a non-zero + * value for a key-value pair, and returning the latest return value. + * + * `context` is passed to `body` to allow stateful closures. + * + * This function takes no ownership whatsoever. + */ +ZENOHC_API +int8_t z_attachement_iterate(struct z_attachement_t this_, + z_attachement_iter_body_t body, + void *context); +/** + * Returns the number of key-value pairs in `this`. + */ +ZENOHC_API uintptr_t z_attachement_len(struct z_attachement_t this_); +/** + * Returns the gravestone value for `z_attachement_t`. + */ +ZENOHC_API struct z_attachement_t z_attachement_null(void); /** * Returns ``true`` if `b` is initialized. */ ZENOHC_API bool z_bytes_check(const struct z_bytes_t *b); +/** + * Aliases `this` into a generic `z_attachement_t`, allowing it to be passed to corresponding APIs. + */ +ZENOHC_API +struct z_attachement_t z_bytes_map_as_attachement(const struct z_owned_bytes_map_t *this_); +/** + * Returns `true` if the map is not in its gravestone state + */ +ZENOHC_API bool z_bytes_map_check(const struct z_owned_bytes_map_t *this_); +/** + * Destroys the map, resetting `this` to its gravestone value. + * + * This function is double-free safe, passing a pointer to the gravestone value will have no effect. + */ +ZENOHC_API void z_bytes_map_drop(struct z_owned_bytes_map_t *this_); +/** + * Associates `value` to `key` in the map, copying them to obtain ownership: `key` and `value` are not aliased past the function's return. + * + * Calling this with `NULL` or the gravestone value is undefined behaviour. + */ +ZENOHC_API +void z_bytes_map_insert_by_copy(const struct z_owned_bytes_map_t *this_, + struct z_bytes_t key, + struct z_bytes_t value); +/** + * Constructs a new map. + */ +ZENOHC_API struct z_owned_bytes_map_t z_bytes_map_new(void); +/** + * Constructs the gravestone value for `z_owned_bytes_map_t` + */ +ZENOHC_API struct z_owned_bytes_map_t z_bytes_map_null(void); /** * Closes a zenoh session. This drops and invalidates `session` for double-drop safety. * diff --git a/src/attachements.rs b/src/attachements.rs new file mode 100644 index 000000000..95df58eaf --- /dev/null +++ b/src/attachements.rs @@ -0,0 +1,201 @@ +use std::collections::HashMap; + +use libc::c_void; + +use crate::z_bytes_t; + +/// The body of a loop over an attachment's key-value pairs. +/// +/// `key` and `value` are loaned to the body for the duration of a single call. +/// `context` is passed transparently through the iteration driver. +/// +/// Returning `0` is treated as `continue`. +/// Returning any other value is treated as `break`. +pub type z_attachement_iter_body_t = + extern "C" fn(key: z_bytes_t, value: z_bytes_t, context: *mut c_void) -> i8; + +/// The driver of a loop over an attachement's key-value pairs. +/// +/// This function is expected to call `loop_body` once for each key-value pair +/// within `iterator`, passing `context`, and returning any non-zero value immediately (breaking iteration). +pub type z_attachement_iter_driver_t = extern "C" fn( + iterator: *mut c_void, + loop_body: z_attachement_iter_body_t, + context: *mut c_void, +) -> i8; + +/// The v-table for an attachement. +#[repr(C)] +pub struct z_attachement_vtable_t { + /// See `z_attachement_iteration_driver_t`'s documentation. + iteration_driver: z_attachement_iter_driver_t, + /// Returns the number of key-value pairs within the attachement. + len: extern "C" fn(*const c_void) -> usize, +} + +/// A v-table based map of vector of bool to vector of bool. +/// +/// `vtable == NULL` marks the gravestone value, as this type is often optional. +/// Users are encouraged to use `z_attachement_null` and `z_attachement_check` to interact. +#[repr(C)] +pub struct z_attachement_t { + data: *mut c_void, + vtable: Option<&'static z_attachement_vtable_t>, +} + +/// Returns the gravestone value for `z_attachement_t`. +#[no_mangle] +pub extern "C" fn z_attachement_check(this: &z_attachement_t) -> bool { + this.vtable.is_some() +} + +/// Returns the gravestone value for `z_attachement_t`. +#[no_mangle] +pub extern "C" fn z_attachement_null() -> z_attachement_t { + z_attachement_t { + data: core::ptr::null_mut(), + vtable: None, + } +} + +/// Iterate over `this`'s key-value pairs, breaking if `body` returns a non-zero +/// value for a key-value pair, and returning the latest return value. +/// +/// `context` is passed to `body` to allow stateful closures. +/// +/// This function takes no ownership whatsoever. +#[no_mangle] +pub extern "C" fn z_attachement_iterate( + this: z_attachement_t, + body: z_attachement_iter_body_t, + context: *mut c_void, +) -> i8 { + (this.vtable.unwrap().iteration_driver)(this.data, body, context) +} + +/// Returns the number of key-value pairs in `this`. +#[no_mangle] +pub extern "C" fn z_attachement_len(this: z_attachement_t) -> usize { + (this.vtable.unwrap().len)(this.data) +} + +/// A map of owned vector of bytes to owned vector of bytes. +/// +/// In Zenoh C, this map is backed by Rust's standard HashMap, with a DoS-resistant hasher +#[repr(C)] +pub struct z_owned_bytes_map_t { + _0: [u64; 2], + _1: [usize; 4], +} +impl core::ops::Deref for z_owned_bytes_map_t { + type Target = core::cell::UnsafeCell, Vec>>; + fn deref(&self) -> &Self::Target { + unsafe { core::mem::transmute(self) } + } +} + +/// Constructs a new map. +#[no_mangle] +pub extern "C" fn z_bytes_map_new() -> z_owned_bytes_map_t { + unsafe { core::mem::transmute(HashMap::, Vec>::new()) } +} + +/// Constructs the gravestone value for `z_owned_bytes_map_t` +#[no_mangle] +pub extern "C" fn z_bytes_map_null() -> z_owned_bytes_map_t { + z_owned_bytes_map_t { + _0: [0; 2], + _1: [0; 4], + } +} + +/// Returns `true` if the map is not in its gravestone state +#[no_mangle] +pub extern "C" fn z_bytes_map_check(this: &z_owned_bytes_map_t) -> bool { + this._0 != [0; 2] && this._1 != [0; 4] +} +/// Destroys the map, resetting `this` to its gravestone value. +/// +/// This function is double-free safe, passing a pointer to the gravestone value will have no effect. +#[no_mangle] +pub extern "C" fn z_bytes_map_drop(this: &mut z_owned_bytes_map_t) { + let this = core::mem::replace(this, z_bytes_map_null()); + if z_bytes_map_check(&this) { + core::mem::drop(unsafe { core::mem::transmute::<_, HashMap, Vec>>(this) }) + } +} + +/// Associates `value` to `key` in the map, copying them to obtain ownership: `key` and `value` are not aliased past the function's return. +/// +/// Calling this with `NULL` or the gravestone value is undefined behaviour. +#[no_mangle] +pub extern "C" fn z_bytes_map_insert_by_copy( + this: &z_owned_bytes_map_t, + key: z_bytes_t, + value: z_bytes_t, +) { + match (z_bytes_map_check(this), key.as_slice(), value.as_slice()) { + (true, Some(key), Some(value)) => { + unsafe { &mut *this.get() }.insert(key.to_owned(), value.to_owned()); + } + _ => { + todo!() + } + } +} + +/// Returns the number of key-value pairs in the map. +/// +/// Calling this with `NULL` or the gravestone value is undefined behaviour. +#[no_mangle] +extern "C" fn z_bytes_map_len(this: &z_owned_bytes_map_t) -> usize { + unsafe { &*this.get() }.len() +} + +/// Iterates over the key-value pairs in the map. +/// +/// `body` will be called once per pair, with `ctx` as its last argument. +/// If `body` returns a non-zero value, the iteration will stop immediately and the value will be returned. +/// Otherwise, this will return 0 once all pairs have been visited. +/// `body` is not given ownership of the key nor value, which alias the pairs in the map. +/// It is safe to keep these aliases until existing keys are modified/removed, or the map is destroyed. +/// Note that this map is unordered. +/// +/// Calling this with `NULL` or the gravestone value is undefined behaviour. +#[no_mangle] +extern "C" fn z_bytes_map_iter( + this: &z_owned_bytes_map_t, + body: z_attachement_iter_body_t, + ctx: *mut c_void, +) -> i8 { + for (key, value) in unsafe { &*this.get() }.iter() { + let result = body(key.as_slice().into(), value.as_slice().into(), ctx); + if result != 0 { + return result; + } + } + 0 +} + +const Z_BYTES_MAP_VTABLE: z_attachement_vtable_t = z_attachement_vtable_t { + len: unsafe { core::mem::transmute(z_bytes_map_len as extern "C" fn(_) -> usize) }, + iteration_driver: unsafe { + core::mem::transmute(z_bytes_map_iter as extern "C" fn(_, _, _) -> i8) + }, +}; + +/// Aliases `this` into a generic `z_attachement_t`, allowing it to be passed to corresponding APIs. +#[no_mangle] +pub extern "C" fn z_bytes_map_as_attachement(this: &z_owned_bytes_map_t) -> z_attachement_t { + if z_bytes_map_check(this) { + z_attachement_t { + data: this as *const z_owned_bytes_map_t as *mut _, + vtable: Some(&Z_BYTES_MAP_VTABLE), + } + } else { + z_attachement_t { + data: core::ptr::null_mut(), + vtable: None, + } + } +} diff --git a/src/collections.rs b/src/collections.rs index 76c02d403..80a649f74 100644 --- a/src/collections.rs +++ b/src/collections.rs @@ -23,6 +23,12 @@ pub struct z_bytes_t { } impl z_bytes_t { + pub fn as_slice(&self) -> Option<&[u8]> { + if self.start.is_null() { + return None; + } + Some(unsafe { core::slice::from_raw_parts(self.start, self.len) }) + } pub fn empty() -> Self { z_bytes_t { start: std::ptr::null(), diff --git a/src/commons.rs b/src/commons.rs index 908dfac76..db2526b76 100644 --- a/src/commons.rs +++ b/src/commons.rs @@ -12,6 +12,8 @@ // ZettaScale Zenoh team, // +use crate::attachements::z_attachement_null; +use crate::attachements::z_attachement_t; use crate::collections::*; use crate::keyexpr::*; use crate::z_id_t; @@ -202,6 +204,7 @@ pub struct z_sample_t<'a> { pub _zc_buf: &'a c_void, pub kind: z_sample_kind_t, pub timestamp: z_timestamp_t, + pub attachements: z_attachement_t, } impl<'a> z_sample_t<'a> { @@ -216,6 +219,7 @@ impl<'a> z_sample_t<'a> { _zc_buf: unsafe { std::mem::transmute(owner) }, kind: sample.kind.into(), timestamp: sample.timestamp.as_ref().into(), + attachements: z_attachement_null(), } } } diff --git a/src/lib.rs b/src/lib.rs index cc2a7bb3d..c07f1e63f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,6 +44,7 @@ mod closures; pub use closures::*; mod liveliness; pub use liveliness::*; +pub mod attachements; #[cfg(feature = "shared-memory")] mod shm; diff --git a/src/put.rs b/src/put.rs index 3efd007b0..bb8699bac 100644 --- a/src/put.rs +++ b/src/put.rs @@ -1,3 +1,5 @@ +use crate::attachements::z_attachement_null; +use crate::attachements::z_attachement_t; // // Copyright (c) 2017, 2022 ZettaScale Technology. // @@ -112,6 +114,7 @@ pub struct z_put_options_t { pub encoding: z_encoding_t, pub congestion_control: z_congestion_control_t, pub priority: z_priority_t, + pub attachements: z_attachement_t, } /// Constructs the default value for :c:type:`z_put_options_t`. @@ -122,6 +125,7 @@ pub unsafe extern "C" fn z_put_options_default() -> z_put_options_t { encoding: z_encoding_default(), congestion_control: CongestionControl::default().into(), priority: Priority::default().into(), + attachements: z_attachement_null(), } }