pamoja_ffi/mesh.rs
1//! The C ABI for mesh packet framing.
2//!
3//! These functions wrap [`pamoja_mesh`] for callers that reach the SDK through the
4//! flat C boundary: an addressed packet that hops node to node across a radio mesh,
5//! and the duplicate suppressor that stops a flood from circulating forever.
6//!
7//! A frame carries a payload, so it crosses as an opaque handle like every other
8//! payload-bearing type here. The duplicate cache is sized when it is built rather
9//! than by the const generic the Rust crate uses, since a const generic cannot
10//! cross a C ABI at all; [`PAMOJA_MESH_SEEN_DEFAULT_CAPACITY`] is what a caller
11//! with no reason to choose should pass.
12
13use std::ptr;
14
15use pamoja_mesh::{crc16, DynamicSeenCache, Frame, MeshError};
16
17use crate::{read_bytes, set_last_error, PamojaStatus};
18
19/// The largest mesh frame, in bytes, including its header and checksum.
20pub const PAMOJA_MESH_FRAME_MAX: usize = 250;
21
22/// The length of a mesh frame header, in bytes.
23pub const PAMOJA_MESH_HEADER_LEN: usize = 12;
24
25/// The bytes a frame spends on its header and checksum together.
26pub const PAMOJA_MESH_OVERHEAD: usize = 14;
27
28/// The largest payload a single mesh frame can carry, in bytes.
29pub const PAMOJA_MESH_PAYLOAD_MAX: usize = 236;
30
31/// The mesh protocol version this build speaks.
32pub const PAMOJA_MESH_VERSION: u8 = 1;
33
34/// The hop limit a frame starts with unless one is set.
35pub const PAMOJA_MESH_DEFAULT_HOP_LIMIT: u8 = 3;
36
37/// The destination address that means every node.
38pub const PAMOJA_MESH_BROADCAST: u32 = 0xFFFF_FFFF;
39
40/// A reasonable duplicate-cache size for a caller with no reason to choose one.
41pub const PAMOJA_MESH_SEEN_DEFAULT_CAPACITY: usize = 64;
42
43/// An opaque handle to a mesh frame.
44///
45/// Read it with the `pamoja_mesh_frame_*` calls, then release it with
46/// [`pamoja_mesh_frame_free`].
47pub struct PamojaMeshFrame {
48 frame: Frame,
49}
50
51/// An opaque handle to a cache of recently seen packets.
52///
53/// Feed it every frame a node receives; it answers whether that packet is new, so
54/// a node relays each packet once however many copies reach it. Release it with
55/// [`pamoja_mesh_seen_free`].
56pub struct PamojaSeenCache {
57 seen: DynamicSeenCache,
58}
59
60/// Builds a mesh frame addressed to one node.
61///
62/// # Arguments
63///
64/// * `src` - the address of this node.
65/// * `dst` - the address the frame is for, or [`PAMOJA_MESH_BROADCAST`].
66/// * `id` - the sequence number identifying this packet from this source.
67/// * `payload` - the bytes to carry.
68/// * `payload_len` - the payload length in bytes.
69/// * `out_frame` - receives the new frame.
70///
71/// # Returns
72///
73/// [`PamojaStatus::Ok`] on success, with `*out_frame` set to a handle the caller
74/// must release with [`pamoja_mesh_frame_free`], or
75/// [`PamojaStatus::InvalidArgument`] if the payload is larger than
76/// [`PAMOJA_MESH_PAYLOAD_MAX`].
77///
78/// # Safety
79///
80/// `payload` must point to at least `payload_len` readable bytes when that length
81/// is non-zero, and `out_frame` must point to a writable `*mut PamojaMeshFrame`.
82#[no_mangle]
83pub unsafe extern "C" fn pamoja_mesh_frame_new(
84 src: u32,
85 dst: u32,
86 id: u16,
87 payload: *const u8,
88 payload_len: usize,
89 out_frame: *mut *mut PamojaMeshFrame,
90) -> PamojaStatus {
91 build(payload, payload_len, out_frame, |bytes| {
92 Frame::new(src, dst, id, bytes)
93 })
94}
95
96/// Builds a mesh frame addressed to every node.
97///
98/// # Arguments
99///
100/// * `src` - the address of this node.
101/// * `id` - the sequence number identifying this packet from this source.
102/// * `payload` - the bytes to carry.
103/// * `payload_len` - the payload length in bytes.
104/// * `out_frame` - receives the new frame.
105///
106/// # Returns
107///
108/// [`PamojaStatus::Ok`] on success, with `*out_frame` set to a handle the caller
109/// must release with [`pamoja_mesh_frame_free`], or
110/// [`PamojaStatus::InvalidArgument`] if the payload is too long.
111///
112/// # Safety
113///
114/// `payload` must point to at least `payload_len` readable bytes when that length
115/// is non-zero, and `out_frame` must point to a writable `*mut PamojaMeshFrame`.
116#[no_mangle]
117pub unsafe extern "C" fn pamoja_mesh_frame_broadcast(
118 src: u32,
119 id: u16,
120 payload: *const u8,
121 payload_len: usize,
122 out_frame: *mut *mut PamojaMeshFrame,
123) -> PamojaStatus {
124 build(payload, payload_len, out_frame, |bytes| {
125 Frame::broadcast(src, id, bytes)
126 })
127}
128
129/// Parses a frame received off a radio.
130///
131/// # Arguments
132///
133/// * `bytes` - the frame exactly as it arrived.
134/// * `bytes_len` - its length.
135/// * `out_frame` - receives the parsed frame.
136///
137/// # Returns
138///
139/// [`PamojaStatus::Ok`] on success, with `*out_frame` set to a handle the caller
140/// must release with [`pamoja_mesh_frame_free`], or [`PamojaStatus::Codec`] if the
141/// frame is truncated, of an unknown version, or fails its checksum.
142///
143/// # Safety
144///
145/// `bytes` must point to at least `bytes_len` readable bytes when that length is
146/// non-zero, and `out_frame` must point to a writable `*mut PamojaMeshFrame`.
147#[no_mangle]
148pub unsafe extern "C" fn pamoja_mesh_frame_parse(
149 bytes: *const u8,
150 bytes_len: usize,
151 out_frame: *mut *mut PamojaMeshFrame,
152) -> PamojaStatus {
153 build(bytes, bytes_len, out_frame, Frame::parse)
154}
155
156/// Sets the number of relays a frame may still take.
157///
158/// # Arguments
159///
160/// * `frame` - the frame to adjust.
161/// * `hop_limit` - the new hop limit.
162///
163/// # Safety
164///
165/// `frame` must be a live handle from a call that produced one, or null.
166#[no_mangle]
167pub unsafe extern "C" fn pamoja_mesh_frame_set_hop_limit(
168 frame: *mut PamojaMeshFrame,
169 hop_limit: u8,
170) {
171 if frame.is_null() {
172 return;
173 }
174 let frame = &mut *frame;
175 frame.frame = frame.frame.with_hop_limit(hop_limit);
176}
177
178/// Returns the protocol version a frame declares.
179///
180/// # Returns
181///
182/// The version, or 0 if `frame` is null.
183///
184/// # Safety
185///
186/// `frame` must be a live handle from a call that produced one, or null.
187#[no_mangle]
188pub unsafe extern "C" fn pamoja_mesh_frame_version(frame: *const PamojaMeshFrame) -> u8 {
189 if frame.is_null() {
190 return 0;
191 }
192 (*frame).frame.version()
193}
194
195/// Returns the address of the node a frame came from.
196///
197/// # Returns
198///
199/// The source address, or 0 if `frame` is null.
200///
201/// # Safety
202///
203/// `frame` must be a live handle from a call that produced one, or null.
204#[no_mangle]
205pub unsafe extern "C" fn pamoja_mesh_frame_src(frame: *const PamojaMeshFrame) -> u32 {
206 if frame.is_null() {
207 return 0;
208 }
209 (*frame).frame.src()
210}
211
212/// Returns the address a frame is addressed to.
213///
214/// # Returns
215///
216/// The destination address, or 0 if `frame` is null.
217///
218/// # Safety
219///
220/// `frame` must be a live handle from a call that produced one, or null.
221#[no_mangle]
222pub unsafe extern "C" fn pamoja_mesh_frame_dst(frame: *const PamojaMeshFrame) -> u32 {
223 if frame.is_null() {
224 return 0;
225 }
226 (*frame).frame.dst()
227}
228
229/// Returns the sequence number that identifies a packet from its source.
230///
231/// # Returns
232///
233/// The sequence number, or 0 if `frame` is null.
234///
235/// # Safety
236///
237/// `frame` must be a live handle from a call that produced one, or null.
238#[no_mangle]
239pub unsafe extern "C" fn pamoja_mesh_frame_id(frame: *const PamojaMeshFrame) -> u16 {
240 if frame.is_null() {
241 return 0;
242 }
243 (*frame).frame.id()
244}
245
246/// Returns how many further relays a frame may take.
247///
248/// # Returns
249///
250/// The hop limit, or 0 if `frame` is null.
251///
252/// # Safety
253///
254/// `frame` must be a live handle from a call that produced one, or null.
255#[no_mangle]
256pub unsafe extern "C" fn pamoja_mesh_frame_hop_limit(frame: *const PamojaMeshFrame) -> u8 {
257 if frame.is_null() {
258 return 0;
259 }
260 (*frame).frame.hop_limit()
261}
262
263/// Reports whether a frame is addressed to every node.
264///
265/// # Returns
266///
267/// `true` for a broadcast, or `false` if `frame` is null.
268///
269/// # Safety
270///
271/// `frame` must be a live handle from a call that produced one, or null.
272#[no_mangle]
273pub unsafe extern "C" fn pamoja_mesh_frame_is_broadcast(frame: *const PamojaMeshFrame) -> bool {
274 !frame.is_null() && (*frame).frame.is_broadcast()
275}
276
277/// Returns a pointer to the payload a frame carries.
278///
279/// Use [`pamoja_mesh_frame_payload_len`] for the length. The pointer is valid
280/// until the frame is freed.
281///
282/// # Returns
283///
284/// A pointer to the payload, or null if `frame` is null or the payload is empty.
285///
286/// # Safety
287///
288/// `frame` must be a live handle from a call that produced one, or null.
289#[no_mangle]
290pub unsafe extern "C" fn pamoja_mesh_frame_payload(frame: *const PamojaMeshFrame) -> *const u8 {
291 if frame.is_null() {
292 return ptr::null();
293 }
294 let payload = (*frame).frame.payload();
295 if payload.is_empty() {
296 ptr::null()
297 } else {
298 payload.as_ptr()
299 }
300}
301
302/// Returns the length in bytes of the payload a frame carries.
303///
304/// # Returns
305///
306/// The payload length, or 0 if `frame` is null.
307///
308/// # Safety
309///
310/// `frame` must be a live handle from a call that produced one, or null.
311#[no_mangle]
312pub unsafe extern "C" fn pamoja_mesh_frame_payload_len(frame: *const PamojaMeshFrame) -> usize {
313 if frame.is_null() {
314 return 0;
315 }
316 (*frame).frame.payload().len()
317}
318
319/// Returns a pointer to the whole frame as it goes on the air.
320///
321/// Use [`pamoja_mesh_frame_bytes_len`] for the length. The pointer is valid until
322/// the frame is freed.
323///
324/// # Returns
325///
326/// A pointer to the encoded frame, or null if `frame` is null.
327///
328/// # Safety
329///
330/// `frame` must be a live handle from a call that produced one, or null.
331#[no_mangle]
332pub unsafe extern "C" fn pamoja_mesh_frame_bytes(frame: *const PamojaMeshFrame) -> *const u8 {
333 if frame.is_null() {
334 return ptr::null();
335 }
336 (*frame).frame.as_bytes().as_ptr()
337}
338
339/// Returns the length in bytes of the whole frame.
340///
341/// # Returns
342///
343/// The encoded length, or 0 if `frame` is null.
344///
345/// # Safety
346///
347/// `frame` must be a live handle from a call that produced one, or null.
348#[no_mangle]
349pub unsafe extern "C" fn pamoja_mesh_frame_bytes_len(frame: *const PamojaMeshFrame) -> usize {
350 if frame.is_null() {
351 return 0;
352 }
353 (*frame).frame.as_bytes().len()
354}
355
356/// Returns the same frame with one hop spent, ready to forward.
357///
358/// # Arguments
359///
360/// * `frame` - the frame just received.
361/// * `out_frame` - receives the frame to forward.
362///
363/// # Returns
364///
365/// `true` when the frame still had a hop to spend, with `*out_frame` set to a
366/// handle the caller must release with [`pamoja_mesh_frame_free`], or `false` when
367/// its hops have run out and it must not be relayed further.
368///
369/// # Safety
370///
371/// `frame` must be a live handle from a call that produced one, or null, and
372/// `out_frame` must point to a writable `*mut PamojaMeshFrame`.
373#[no_mangle]
374pub unsafe extern "C" fn pamoja_mesh_frame_relayed(
375 frame: *const PamojaMeshFrame,
376 out_frame: *mut *mut PamojaMeshFrame,
377) -> bool {
378 if out_frame.is_null() {
379 return false;
380 }
381 let slot = &mut *out_frame;
382 *slot = ptr::null_mut();
383 if frame.is_null() {
384 return false;
385 }
386 match (*frame).frame.relayed() {
387 Some(forwarded) => {
388 *slot = Box::into_raw(Box::new(PamojaMeshFrame { frame: forwarded }));
389 true
390 }
391 None => false,
392 }
393}
394
395/// Releases a mesh frame handle.
396///
397/// Passing null is a no-op.
398///
399/// # Safety
400///
401/// `frame` must be a handle from a call that produced one and that has not already
402/// been freed, or null. After this call it must not be used again.
403#[no_mangle]
404pub unsafe extern "C" fn pamoja_mesh_frame_free(frame: *mut PamojaMeshFrame) {
405 if !frame.is_null() {
406 drop(Box::from_raw(frame));
407 }
408}
409
410/// Computes the CRC-16 a mesh frame carries.
411///
412/// # Arguments
413///
414/// * `data` - the bytes the checksum covers.
415/// * `data_len` - their length.
416///
417/// # Returns
418///
419/// The checksum, or 0 if `data` is null with a non-zero length.
420///
421/// # Safety
422///
423/// `data` must point to at least `data_len` readable bytes when that length is
424/// non-zero.
425#[no_mangle]
426pub unsafe extern "C" fn pamoja_mesh_crc16(data: *const u8, data_len: usize) -> u16 {
427 match read_bytes(data, data_len) {
428 Ok(bytes) => crc16(&bytes),
429 Err(_) => 0,
430 }
431}
432
433/// Creates an empty duplicate cache.
434///
435/// # Arguments
436///
437/// * `capacity` - how many recently seen packets to remember; pass
438/// [`PAMOJA_MESH_SEEN_DEFAULT_CAPACITY`] when there is no reason to choose. A
439/// capacity of zero remembers nothing, so every copy of a packet is relayed.
440///
441/// # Returns
442///
443/// A handle the caller must release with [`pamoja_mesh_seen_free`].
444#[no_mangle]
445pub extern "C" fn pamoja_mesh_seen_new(capacity: usize) -> *mut PamojaSeenCache {
446 Box::into_raw(Box::new(PamojaSeenCache {
447 seen: DynamicSeenCache::new(capacity),
448 }))
449}
450
451/// Reports whether a packet is currently remembered, without recording it.
452///
453/// # Arguments
454///
455/// * `cache` - the duplicate cache.
456/// * `src` - the address the packet came from.
457/// * `id` - the sequence number the packet carries.
458///
459/// # Returns
460///
461/// `true` if the packet has been seen recently, or `false` if `cache` is null.
462///
463/// # Safety
464///
465/// `cache` must be a live handle from [`pamoja_mesh_seen_new`], or null.
466#[no_mangle]
467pub unsafe extern "C" fn pamoja_mesh_seen_contains(
468 cache: *const PamojaSeenCache,
469 src: u32,
470 id: u16,
471) -> bool {
472 !cache.is_null() && (*cache).seen.contains((src, id))
473}
474
475/// Records a packet and reports whether it was new.
476///
477/// # Arguments
478///
479/// * `cache` - the duplicate cache.
480/// * `src` - the address the packet came from.
481/// * `id` - the sequence number the packet carries.
482///
483/// # Returns
484///
485/// `true` if the packet had not been seen, which is when a node should act on it
486/// and relay it, or `false` for a duplicate or a null cache.
487///
488/// # Safety
489///
490/// `cache` must be a live handle from [`pamoja_mesh_seen_new`], or null.
491#[no_mangle]
492pub unsafe extern "C" fn pamoja_mesh_seen_record(
493 cache: *mut PamojaSeenCache,
494 src: u32,
495 id: u16,
496) -> bool {
497 if cache.is_null() {
498 return false;
499 }
500 (*cache).seen.record((src, id))
501}
502
503/// Returns how many packets a duplicate cache remembers.
504///
505/// # Returns
506///
507/// The capacity it was created with, or 0 if `cache` is null.
508///
509/// # Safety
510///
511/// `cache` must be a live handle from [`pamoja_mesh_seen_new`], or null.
512#[no_mangle]
513pub unsafe extern "C" fn pamoja_mesh_seen_capacity(cache: *const PamojaSeenCache) -> usize {
514 if cache.is_null() {
515 return 0;
516 }
517 (*cache).seen.capacity()
518}
519
520/// Releases a duplicate cache handle.
521///
522/// Passing null is a no-op.
523///
524/// # Safety
525///
526/// `cache` must be a handle from [`pamoja_mesh_seen_new`] that has not already
527/// been freed, or null. After this call it must not be used again.
528#[no_mangle]
529pub unsafe extern "C" fn pamoja_mesh_seen_free(cache: *mut PamojaSeenCache) {
530 if !cache.is_null() {
531 drop(Box::from_raw(cache));
532 }
533}
534
535/// Runs a frame constructor over a borrowed buffer and hands back a handle.
536///
537/// # Safety
538///
539/// `bytes` must point to at least `len` readable bytes when that length is
540/// non-zero, and `out_frame` must point to a writable `*mut PamojaMeshFrame`.
541unsafe fn build(
542 bytes: *const u8,
543 len: usize,
544 out_frame: *mut *mut PamojaMeshFrame,
545 construct: impl FnOnce(&[u8]) -> Result<Frame, MeshError>,
546) -> PamojaStatus {
547 if out_frame.is_null() {
548 set_last_error("out_frame must not be null".to_owned());
549 return PamojaStatus::InvalidArgument;
550 }
551 let slot = &mut *out_frame;
552 *slot = ptr::null_mut();
553
554 let bytes = match read_bytes(bytes, len) {
555 Ok(bytes) => bytes,
556 Err(status) => return status,
557 };
558 match construct(&bytes) {
559 Ok(frame) => {
560 *slot = Box::into_raw(Box::new(PamojaMeshFrame { frame }));
561 PamojaStatus::Ok
562 }
563 Err(error) => failed(error),
564 }
565}
566
567/// Records a mesh error and classifies it.
568///
569/// # Arguments
570///
571/// * `error` - the failure the mesh crate reported.
572///
573/// # Returns
574///
575/// [`PamojaStatus::InvalidArgument`] when the caller asked for something no frame
576/// can hold, and [`PamojaStatus::Codec`] when a received frame could not be read.
577fn failed(error: MeshError) -> PamojaStatus {
578 set_last_error(error.to_string());
579 match error {
580 MeshError::PayloadTooLong | MeshError::FrameTooLong => PamojaStatus::InvalidArgument,
581 _ => PamojaStatus::Codec,
582 }
583}
584
585#[cfg(test)]
586mod tests {
587 use super::*;
588
589 #[test]
590 fn the_constants_match_the_mesh_crate() {
591 assert_eq!(PAMOJA_MESH_FRAME_MAX, Frame::MAX_LEN);
592 assert_eq!(PAMOJA_MESH_HEADER_LEN, Frame::HEADER_LEN);
593 assert_eq!(PAMOJA_MESH_OVERHEAD, Frame::OVERHEAD);
594 assert_eq!(PAMOJA_MESH_PAYLOAD_MAX, Frame::MAX_PAYLOAD);
595 assert_eq!(PAMOJA_MESH_VERSION, Frame::VERSION);
596 assert_eq!(PAMOJA_MESH_DEFAULT_HOP_LIMIT, Frame::DEFAULT_HOP_LIMIT);
597 assert_eq!(PAMOJA_MESH_BROADCAST, pamoja_mesh::BROADCAST);
598 }
599
600 #[test]
601 fn a_broadcast_round_trips_through_the_boundary() {
602 let payload = b"level=high";
603 let mut frame = ptr::null_mut();
604 // Safety: the payload and out-pointer are both valid.
605 unsafe {
606 assert_eq!(
607 pamoja_mesh_frame_broadcast(
608 0x1234_5678,
609 1,
610 payload.as_ptr(),
611 payload.len(),
612 &mut frame
613 ),
614 PamojaStatus::Ok
615 );
616 let on_air = std::slice::from_raw_parts(
617 pamoja_mesh_frame_bytes(frame),
618 pamoja_mesh_frame_bytes_len(frame),
619 )
620 .to_vec();
621 pamoja_mesh_frame_free(frame);
622
623 let mut received = ptr::null_mut();
624 assert_eq!(
625 pamoja_mesh_frame_parse(on_air.as_ptr(), on_air.len(), &mut received),
626 PamojaStatus::Ok
627 );
628 assert!(pamoja_mesh_frame_is_broadcast(received));
629 assert_eq!(pamoja_mesh_frame_src(received), 0x1234_5678);
630 assert_eq!(pamoja_mesh_frame_id(received), 1);
631 assert_eq!(pamoja_mesh_frame_version(received), PAMOJA_MESH_VERSION);
632 let recovered = std::slice::from_raw_parts(
633 pamoja_mesh_frame_payload(received),
634 pamoja_mesh_frame_payload_len(received),
635 );
636 assert_eq!(recovered, payload);
637 pamoja_mesh_frame_free(received);
638 }
639 }
640
641 #[test]
642 fn a_corrupt_frame_is_refused() {
643 let payload = b"reading";
644 let mut frame = ptr::null_mut();
645 // Safety: the payload and out-pointer are both valid.
646 unsafe {
647 assert_eq!(
648 pamoja_mesh_frame_new(1, 2, 7, payload.as_ptr(), payload.len(), &mut frame),
649 PamojaStatus::Ok
650 );
651 let mut on_air = std::slice::from_raw_parts(
652 pamoja_mesh_frame_bytes(frame),
653 pamoja_mesh_frame_bytes_len(frame),
654 )
655 .to_vec();
656 pamoja_mesh_frame_free(frame);
657 on_air[PAMOJA_MESH_HEADER_LEN] ^= 0xFF;
658
659 let mut received = ptr::null_mut();
660 assert_eq!(
661 pamoja_mesh_frame_parse(on_air.as_ptr(), on_air.len(), &mut received),
662 PamojaStatus::Codec
663 );
664 assert!(received.is_null());
665 }
666 }
667
668 #[test]
669 fn a_frame_stops_relaying_when_its_hops_run_out() {
670 let mut frame = ptr::null_mut();
671 // Safety: the out-pointers are valid.
672 unsafe {
673 assert_eq!(
674 pamoja_mesh_frame_broadcast(9, 1, ptr::null(), 0, &mut frame),
675 PamojaStatus::Ok
676 );
677 assert_eq!(pamoja_mesh_frame_payload_len(frame), 0);
678 assert!(pamoja_mesh_frame_payload(frame).is_null());
679 pamoja_mesh_frame_set_hop_limit(frame, 1);
680
681 let mut once = ptr::null_mut();
682 assert!(pamoja_mesh_frame_relayed(frame, &mut once));
683 assert_eq!(pamoja_mesh_frame_hop_limit(once), 0);
684
685 let mut twice = ptr::null_mut();
686 assert!(!pamoja_mesh_frame_relayed(once, &mut twice));
687 assert!(twice.is_null());
688
689 pamoja_mesh_frame_free(once);
690 pamoja_mesh_frame_free(frame);
691 }
692 }
693
694 #[test]
695 fn the_cache_recognises_a_packet_it_has_already_seen() {
696 let cache = pamoja_mesh_seen_new(PAMOJA_MESH_SEEN_DEFAULT_CAPACITY);
697 // Safety: the cache handle was just created.
698 unsafe {
699 assert!(!pamoja_mesh_seen_contains(cache, 0x42, 1));
700 assert!(pamoja_mesh_seen_record(cache, 0x42, 1));
701 assert!(pamoja_mesh_seen_contains(cache, 0x42, 1));
702 assert!(!pamoja_mesh_seen_record(cache, 0x42, 1));
703 assert!(pamoja_mesh_seen_record(cache, 0x42, 2));
704 assert_eq!(
705 pamoja_mesh_seen_capacity(cache),
706 PAMOJA_MESH_SEEN_DEFAULT_CAPACITY
707 );
708 pamoja_mesh_seen_free(cache);
709
710 // A cache sized by the caller evicts at the size it was given.
711 let small = pamoja_mesh_seen_new(2);
712 assert_eq!(pamoja_mesh_seen_capacity(small), 2);
713 assert!(pamoja_mesh_seen_record(small, 1, 1));
714 assert!(pamoja_mesh_seen_record(small, 1, 2));
715 assert!(pamoja_mesh_seen_record(small, 1, 3));
716 assert!(!pamoja_mesh_seen_contains(small, 1, 1));
717 pamoja_mesh_seen_free(small);
718 }
719 }
720
721 #[test]
722 fn the_checksum_matches_the_mesh_crate() {
723 let data = b"level=high";
724 // Safety: the buffer is valid for its length.
725 let checksum = unsafe { pamoja_mesh_crc16(data.as_ptr(), data.len()) };
726 assert_eq!(checksum, crc16(data));
727 }
728
729 #[test]
730 fn null_handles_are_tolerated() {
731 // Safety: every call below is documented to accept null.
732 unsafe {
733 assert_eq!(pamoja_mesh_frame_src(ptr::null()), 0);
734 assert_eq!(pamoja_mesh_frame_dst(ptr::null()), 0);
735 assert!(!pamoja_mesh_frame_is_broadcast(ptr::null()));
736 assert!(pamoja_mesh_frame_bytes(ptr::null()).is_null());
737 pamoja_mesh_frame_set_hop_limit(ptr::null_mut(), 3);
738 pamoja_mesh_frame_free(ptr::null_mut());
739 assert!(!pamoja_mesh_seen_record(ptr::null_mut(), 1, 1));
740 pamoja_mesh_seen_free(ptr::null_mut());
741 }
742 }
743}