1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
use std::{mem::ManuallyDrop, ptr::NonNull};
use open62541_sys::{
UA_KeyValueMap, UA_KeyValueMap_contains, UA_KeyValueMap_delete, UA_KeyValueMap_get,
UA_KeyValueMap_new, UA_KeyValueMap_remove, UA_KeyValueMap_set,
};
use crate::{DataType, Error, ua};
/// Wrapper for [`UA_KeyValueMap`] from [`open62541_sys`].
#[derive(Debug)]
pub struct KeyValueMap(NonNull<UA_KeyValueMap>);
impl KeyValueMap {
/// Creates wrapper initialized with defaults.
#[must_use]
pub(crate) fn init() -> Self {
// PANIC: The only possible errors here are out-of-memory.
let key_value_map =
NonNull::new(unsafe { UA_KeyValueMap_new() }).expect("should create key value map");
Self(key_value_map)
}
/// Creates new map from existing elements.
///
/// This copies over the elements from the given slice. The map will own the copies, and clean
/// up when it is dropped. The original elements in the slice are left untouched.
///
/// # Panics
///
/// Enough memory must be available to allocate map.
#[must_use]
pub fn from_slice(slice: &[(&ua::QualifiedName, &ua::Variant)]) -> Self {
let mut key_value_map = Self::init();
for (key, value) in slice {
key_value_map.set(key, value);
}
key_value_map
}
/// Checks whether map has key.
#[must_use]
pub fn contains(&self, key: &ua::QualifiedName) -> bool {
unsafe {
UA_KeyValueMap_contains(
self.as_ptr(),
// SAFETY: `UA_KeyValueMap_contains()` reads the key but does not take ownership.
DataType::to_raw_copy(key),
)
}
}
/// Gets key's value from map.
#[must_use]
pub fn get(&self, key: &ua::QualifiedName) -> Option<&ua::Variant> {
let variant = unsafe {
UA_KeyValueMap_get(
self.as_ptr(),
// SAFETY: `UA_KeyValueMap_get()` reads the key but does not take ownership.
DataType::to_raw_copy(key),
)
};
// SAFETY: Pointer is either null or a valid reference (to a variant value with the lifetime
// of `self`).
unsafe { variant.as_ref() }.map(ua::Variant::raw_ref)
}
/// Sets key's value in map.
///
/// This replaces a previously set value for this key.
///
/// # Panics
///
/// Enough memory must be available to add value to map.
pub fn set(&mut self, key: &ua::QualifiedName, value: &ua::Variant) {
let status_code = ua::StatusCode::new(unsafe {
UA_KeyValueMap_set(
self.as_mut_ptr(),
// SAFETY: `UA_KeyValueMap_set()` reads the key but does not take ownership. In both
// cases (key already exists or is inserted for the first time), an internal copy is
// made before inserting into the data structure.
DataType::to_raw_copy(key),
value.as_ptr(),
)
});
Error::verify_good(&status_code).expect("should add value");
}
/// Removes key's value from map.
///
/// This returns `true` if the key was removed, and `false` if the key did not exist.
pub fn remove(&mut self, key: &ua::QualifiedName) -> bool {
let status_code = ua::StatusCode::new(unsafe {
UA_KeyValueMap_remove(
self.as_mut_ptr(),
// SAFETY: `UA_KeyValueMap_remove()` reads the key but does not take ownership.
DataType::to_raw_copy(key),
)
});
if status_code == ua::StatusCode::GOOD {
true
} else if status_code == ua::StatusCode::BADNOTFOUND {
false
} else {
// PANIC: Function returns no other status codes (the other code `BADINVALIDARGUMENT` is
// only returned when we pass in a null-pointer for the map).
unreachable!("failed to remove key: {status_code}");
}
}
/// Gives up ownership and returns value.
#[expect(dead_code, reason = "unused for now")]
#[expect(clippy::allow_attributes, reason = "non-static condition")]
#[allow(clippy::missing_const_for_fn, reason = "unsupported before Rust 1.87")]
pub(crate) fn into_raw(self) -> *mut UA_KeyValueMap {
// Use `ManuallyDrop` to avoid double-free even when added code might cause panic. See
// documentation of `mem::forget()` for details.
let this = ManuallyDrop::new(self);
// Return pointer to caller who becomes the owner of the object.
this.0.as_ptr()
}
/// Returns const pointer to value.
///
/// # Safety
///
/// The value is owned by `Self`. Ownership must not be given away, in whole or in parts. This
/// may happen when `open62541` functions are called that take ownership of values by pointer.
#[must_use]
pub(crate) const unsafe fn as_ptr(&self) -> *const UA_KeyValueMap {
self.0.as_ptr()
}
/// Returns mutable pointer to value.
///
/// # Safety
///
/// The value is owned by `Self`. Ownership must not be given away, in whole or in parts. This
/// may happen when `open62541` functions are called that take ownership of values by pointer.
#[must_use]
#[expect(clippy::allow_attributes, reason = "non-static condition")]
#[allow(clippy::missing_const_for_fn, reason = "unsupported before Rust 1.87")]
pub(crate) unsafe fn as_mut_ptr(&mut self) -> *mut UA_KeyValueMap {
self.0.as_ptr()
}
}
// SAFETY: Key-value maps in `open62541` can be sent across thread boundaries.
unsafe impl Send for KeyValueMap {}
// SAFETY: References to [`KeyValueMap`] may be sent across threads. There is nothing that prevents
// this.
unsafe impl Sync for KeyValueMap {}
impl Drop for KeyValueMap {
fn drop(&mut self) {
unsafe { UA_KeyValueMap_delete(self.0.as_mut()) };
}
}
#[cfg(test)]
mod tests {
use std::thread;
use super::*;
#[test]
fn operations() {
// Map can be created from initial values.
let mut key_value_map = KeyValueMap::from_slice(&[
(
&ua::QualifiedName::new(1, "lorem"),
&ua::Variant::scalar(ua::UInt16::new(123)),
),
(
&ua::QualifiedName::new(2, "ipsum"),
&ua::Variant::scalar(ua::String::new("dolor").expect("create string")),
),
]);
// Presence of values can be checked.
assert!(key_value_map.contains(&ua::QualifiedName::new(1, "lorem")));
assert!(key_value_map.contains(&ua::QualifiedName::new(2, "ipsum")));
assert!(!key_value_map.contains(&ua::QualifiedName::new(3, "ipsum")));
// Existing value can be returned.
assert_eq!(
key_value_map
.get(&ua::QualifiedName::new(1, "lorem"))
.expect("value `1:lorem` should exist")
.as_scalar(),
Some(&ua::UInt16::new(123))
);
// Non-existent values are handled.
assert_eq!(key_value_map.get(&ua::QualifiedName::new(1, "dolor")), None);
assert!(!key_value_map.contains(&ua::QualifiedName::new(1, "dolor")));
// New value can be added, other values are untouched.
key_value_map.set(
&ua::QualifiedName::new(3, "ipsum"),
&ua::Variant::scalar(ua::Float::new(9.87)),
);
assert_eq!(
key_value_map
.get(&ua::QualifiedName::new(3, "ipsum"))
.expect("value `3:ipsum` should exist")
.as_scalar(),
Some(&ua::Float::new(9.87))
);
assert!(key_value_map.contains(&ua::QualifiedName::new(3, "ipsum")));
assert_eq!(
key_value_map
.get(&ua::QualifiedName::new(1, "lorem"))
.expect("value `1:lorem` should exist")
.as_scalar(),
Some(&ua::UInt16::new(123))
);
assert!(key_value_map.contains(&ua::QualifiedName::new(2, "ipsum")));
}
#[test]
fn remove_key() {
let mut key_value_map = KeyValueMap::from_slice(&[
(
&ua::QualifiedName::new(1, "lorem"),
&ua::Variant::scalar(ua::UInt16::new(123)),
),
(
&ua::QualifiedName::new(2, "ipsum"),
&ua::Variant::scalar(ua::String::new("dolor").expect("create string")),
),
]);
// Value can be removed, other values are untouched.
let was_removed = key_value_map.remove(&ua::QualifiedName::new(1, "lorem"));
assert!(was_removed);
assert_eq!(key_value_map.get(&ua::QualifiedName::new(1, "lorem")), None);
assert!(!key_value_map.contains(&ua::QualifiedName::new(1, "lorem")));
assert_eq!(
key_value_map
.get(&ua::QualifiedName::new(2, "ipsum"))
.expect("value `2:ipsum` should exist")
.as_scalar(),
Some(&ua::String::new("dolor").expect("create string"))
);
assert!(key_value_map.contains(&ua::QualifiedName::new(2, "ipsum")));
// Removing non-existent value is handled.
let was_removed = key_value_map.remove(&ua::QualifiedName::new(1, "lorem"));
assert!(!was_removed);
// Value can be set again.
key_value_map.set(
&ua::QualifiedName::new(1, "lorem"),
&ua::Variant::scalar(ua::Float::new(1.23)),
);
assert_eq!(
key_value_map
.get(&ua::QualifiedName::new(1, "lorem"))
.expect("value `1:lorem` should exist")
.as_scalar(),
Some(&ua::Float::new(1.23))
);
// Existing value can be overwritten.
key_value_map.set(
&ua::QualifiedName::new(1, "lorem"),
&ua::Variant::scalar(ua::Double::new(3.21)),
);
assert_eq!(
key_value_map
.get(&ua::QualifiedName::new(1, "lorem"))
.expect("value `1:lorem` should exist")
.as_scalar(),
Some(&ua::Double::new(3.21))
);
}
#[test]
fn send_sync_key_value_map() {
let key_value_map = ua::KeyValueMap::from_slice(&[(
&ua::QualifiedName::ns0("key"),
&ua::Variant::scalar(ua::Double::new(1.23)),
)]);
// References to key-value map can be accessed in different threads.
thread::scope(|scope| {
scope.spawn(|| {
let _ = &key_value_map;
});
});
// Ownership of key-value map can be passed to different thread.
thread::spawn(move || {
drop(key_value_map);
})
.join()
.expect("join thread");
}
}