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
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
//! Integration tests for `MeshNode::send_subprotocol` error /
//! fast-path behavior and receive-loop robustness against
//! malformed subprotocol frames.
//!
//! Pins the invariants from `TEST_COVERAGE_PLAN.md` §P1-1. Happy
//! paths (migration, capability-announcement, rendezvous) are
//! covered by their respective integration suites; this file
//! covers:
//!
//! - `send_subprotocol` to an unknown peer returns a clean
//! `Connection("unknown peer")` error without touching session
//! state, and does so consistently across N concurrent tasks
//! (no lock-contention-induced Ok, no panic, no corruption).
//! - `send_subprotocol` to a `block_peer`'d address returns
//! `Ok(())` silently (partition-filter short-circuit — already
//! documented behavior, pinned here so it doesn't regress).
//! - An empty payload still produces a valid wire packet (no
//! panic in `build_subprotocol`'s event-frame assembly).
//! - A reserved-range (`0x0001..0x03FF`) subprotocol ID is
//! accepted by the send path — the sender doesn't police IDs;
//! the receiver drops unknown IDs silently.
//! - A vendor-range (`0x1000..0xEFFF`) subprotocol ID round-trips
//! without dispatch-branch panics.
//! - An unknown subprotocol ID sent to a running mesh receiver
//! does not crash the receive loop — it falls through to the
//! standard event path, which treats the payload as a generic
//! event frame.
//!
//! Run: `cargo test --features net --test send_subprotocol_malformed`
#![cfg(feature = "net")]
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use net::adapter::net::{EntityKeypair, MeshNode, MeshNodeConfig, SocketBufferConfig};
use net::error::AdapterError;
const TEST_BUFFER_SIZE: usize = 256 * 1024;
const PSK: [u8; 32] = [0x42u8; 32];
fn test_config() -> MeshNodeConfig {
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
let mut cfg = MeshNodeConfig::new(addr, PSK)
.with_heartbeat_interval(Duration::from_millis(200))
.with_session_timeout(Duration::from_secs(5))
.with_handshake(3, Duration::from_secs(2));
cfg.socket_buffers = SocketBufferConfig {
send_buffer_size: TEST_BUFFER_SIZE,
recv_buffer_size: TEST_BUFFER_SIZE,
};
cfg
}
async fn build_node() -> Arc<MeshNode> {
Arc::new(
MeshNode::new(EntityKeypair::generate(), test_config())
.await
.expect("MeshNode::new"),
)
}
async fn connect_pair(a: &Arc<MeshNode>, b: &Arc<MeshNode>) {
let a_id = a.node_id();
let b_pub = *b.public_key();
let b_addr = b.local_addr();
let b_id = b.node_id();
let b_clone = b.clone();
let accept = tokio::spawn(async move { b_clone.accept(a_id).await });
a.connect(b_addr, &b_pub, b_id)
.await
.expect("connect failed");
accept
.await
.expect("accept task panicked")
.expect("accept failed");
}
// ─────────────────────────────────────────────────────────────
// SEND-SIDE INVARIANTS
// ─────────────────────────────────────────────────────────────
/// A freshly-built mesh with no connected peers returns a clean
/// `Connection("unknown peer")` error for every send — not a
/// panic, not a silent Ok. Pins the addr-to-node lookup's
/// ok_or-else path.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn send_to_unknown_peer_returns_clean_error() {
let a = build_node().await;
// No connect_pair call — A has no peers, addr_to_node is empty.
let bogus: SocketAddr = "127.0.0.1:1".parse().unwrap();
let err = a
.send_subprotocol(bogus, 0x0500, b"payload")
.await
.expect_err("send to unknown peer must fail");
match err {
AdapterError::Connection(msg) => {
assert!(
msg.contains("unknown peer"),
"error message should mention unknown peer; got {msg:?}",
);
}
other => panic!("expected Connection error; got {other:?}"),
}
}
/// Sixteen concurrent tasks all sending to an unknown peer
/// should all receive the same clean `Connection("unknown peer")`
/// error. Guards against a future refactor that accidentally
/// uses `unwrap` on the addr lookup, or serializes the send
/// path through a mutex whose contention would hide the error.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn concurrent_send_to_unknown_peer_is_consistently_errored() {
let a = build_node().await;
let bogus: SocketAddr = "127.0.0.1:2".parse().unwrap();
let mut handles = Vec::with_capacity(16);
for _ in 0..16 {
let a_clone = a.clone();
handles.push(tokio::spawn(async move {
a_clone.send_subprotocol(bogus, 0x0500, b"payload").await
}));
}
let mut ok_count = 0;
let mut err_count = 0;
for h in handles {
match h.await.expect("task panicked") {
Ok(()) => ok_count += 1,
Err(AdapterError::Connection(msg)) => {
assert!(
msg.contains("unknown peer"),
"every concurrent failure must be the unknown-peer error; got {msg:?}",
);
err_count += 1;
}
Err(other) => panic!("unexpected error variant: {other:?}"),
}
}
assert_eq!(
ok_count, 0,
"no concurrent send should have spuriously succeeded"
);
assert_eq!(
err_count, 16,
"every concurrent send should have errored cleanly"
);
}
/// `block_peer(addr)` adds the addr to the partition filter.
/// `send_subprotocol` against a partition-filtered addr returns
/// `Ok(())` silently — the network partition simulation pretends
/// the packet was sent but the peer didn't get it. Pin this
/// behavior so a future "surface partition-filter drops as
/// errors" change is a deliberate, visible choice.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn send_to_partition_filtered_peer_returns_ok_silently() {
let a = build_node().await;
let b = build_node().await;
connect_pair(&a, &b).await;
a.start();
b.start();
let b_addr = b.local_addr();
a.block_peer(b_addr);
assert!(a.is_blocked(&b_addr));
// Send to the blocked peer — returns Ok, but nothing reaches B.
let res = a.send_subprotocol(b_addr, 0x0500, b"payload").await;
assert!(
res.is_ok(),
"send to partition-filtered peer must be a silent Ok; got {res:?}",
);
}
/// An empty payload produces a valid subprotocol packet. Guards
/// the event-frame assembly against a panic on `events = vec![]`
/// or the zero-length Bytes case.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn send_empty_payload_does_not_panic() {
let a = build_node().await;
let b = build_node().await;
connect_pair(&a, &b).await;
a.start();
b.start();
let b_addr = b.local_addr();
a.send_subprotocol(b_addr, 0x0500, b"")
.await
.expect("empty payload send should succeed");
}
/// A reserved-range (`0x0001..0x03FF`) subprotocol ID reaches the
/// wire — the sender does NOT police IDs. Plan-level contract:
/// the Subprotocol ID Space table in the main README documents
/// `0x0001..0x03FF` as "Reserved for core", but that's an
/// allocation convention, not a dispatch-time check. The receiver
/// is where unknown IDs get routed to the fall-through event
/// path. Pinning "send accepts any u16" here.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn send_with_reserved_subprotocol_id_succeeds() {
let a = build_node().await;
let b = build_node().await;
connect_pair(&a, &b).await;
a.start();
b.start();
let b_addr = b.local_addr();
// 0x0002 — reserved for core, no dispatch handler.
a.send_subprotocol(b_addr, 0x0002, b"payload")
.await
.expect("reserved-range ID must not error at send time");
}
/// A vendor-range (`0x1000..0xEFFF`) subprotocol ID is the
/// documented space for third-party extensions. Sender accepts
/// it cleanly; receiver falls through to the generic event path.
/// Pins "third-party subprotocol allocation" as a first-class
/// supported workflow.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn send_with_vendor_range_subprotocol_id_succeeds() {
let a = build_node().await;
let b = build_node().await;
connect_pair(&a, &b).await;
a.start();
b.start();
let b_addr = b.local_addr();
// 0x7777 — arbitrary vendor pick.
a.send_subprotocol(b_addr, 0x7777, b"vendor payload")
.await
.expect("vendor-range ID must not error at send time");
}
/// The upper u16 range (`0xF000..=0xFFFF`) is documented as
/// experimental/ephemeral. Also exercises the boundary case
/// `u16::MAX` so the `subprotocol_id as u64` coercion at the
/// send-path stream-id step is pinned against a future refactor
/// that might introduce signed arithmetic.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn send_with_max_subprotocol_id_succeeds() {
let a = build_node().await;
let b = build_node().await;
connect_pair(&a, &b).await;
a.start();
b.start();
let b_addr = b.local_addr();
a.send_subprotocol(b_addr, u16::MAX, b"edge")
.await
.expect("u16::MAX subprotocol ID must not overflow or panic");
}
// ─────────────────────────────────────────────────────────────
// RECEIVE-SIDE INVARIANTS
// ─────────────────────────────────────────────────────────────
/// Sending to a running receiver with an unknown subprotocol ID
/// must not crash the receive loop. The dispatch chain's
/// fall-through is the generic event-frame path which queues
/// the payload into a shard. The receiver stays alive + healthy.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn receiver_survives_unknown_subprotocol_id() {
let a = build_node().await;
let b = build_node().await;
connect_pair(&a, &b).await;
a.start();
b.start();
let b_addr = b.local_addr();
// Send several unknown-ID packets in a row. None should
// crash B's recv loop.
for id in [0x0002u16, 0x03FF, 0x1234, 0x7FFF, 0xEFFF] {
a.send_subprotocol(b_addr, id, b"junk")
.await
.expect("send with unknown ID must not error");
}
// Give B's recv loop a few scheduler ticks to process the
// five packets. If any of them had panicked the loop would
// be dead; we verify the peer is still accepting new sends.
tokio::time::sleep(Duration::from_millis(100)).await;
// Prove B's recv loop is *actually processing* packets, not
// just that A's UDP send returns Ok. (Cubic-flagged P2:
// `send_subprotocol(...).await` only confirms A enqueued to
// its local socket — a dead recv loop on B would pass that
// check.) Drive a real capability announcement from A and
// verify B's capability index observes it — that requires
// B's recv loop + dispatcher + capability handler all to be
// alive.
let a_id = a.node_id();
a.announce_capabilities(net::adapter::net::behavior::capability::CapabilitySet::new())
.await
.expect("real announce after junk barrage");
let mut propagated = false;
for _ in 0..40 {
if b.test_capability_fold_has(a_id) {
propagated = true;
break;
}
tokio::time::sleep(Duration::from_millis(25)).await;
}
assert!(
propagated,
"B's receive loop must survive the unknown-ID barrage and still \
dispatch a follow-up capability announcement — if B's recv loop \
had died this would time out",
);
}
/// A malformed CapabilityAnnouncement payload — wrong magic, wrong
/// length, corrupt signature bytes — arrives on `0x0C00`
/// (capability-announcement) and must be silently dropped by
/// the handler. Pinning that the decoder failure path doesn't
/// poison B's capability index or kill the recv loop.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn receiver_drops_malformed_capability_announcement_without_panic() {
let a = build_node().await;
let b = build_node().await;
connect_pair(&a, &b).await;
a.start();
b.start();
let b_addr = b.local_addr();
// CapabilityAnnouncement on 0x0C00. Payload is deliberately
// junk — wrong magic, wrong length, not a valid postcard-
// encoded announcement. The handler's decode step fails and
// the packet must be dropped silently.
a.send_subprotocol(b_addr, 0x0C00, b"not-an-announcement")
.await
.expect("send should succeed");
a.send_subprotocol(b_addr, 0x0C00, &[0u8; 3])
.await
.expect("send should succeed on tiny payload");
a.send_subprotocol(b_addr, 0x0C00, &[0xFFu8; 4096])
.await
.expect("send should succeed on large junk payload");
tokio::time::sleep(Duration::from_millis(100)).await;
// B's capability index should be empty (no valid announcements
// arrived). The malformed payloads must not have poisoned it
// with a bogus entry.
let a_id = a.node_id();
assert!(
!b.test_capability_fold_has(a_id),
"malformed announcement must not populate B's capability index",
);
// And B is still responsive — next real announcement from A
// lands normally.
a.announce_capabilities(net::adapter::net::behavior::capability::CapabilitySet::new())
.await
.expect("A real announce");
let mut propagated = false;
for _ in 0..40 {
if b.test_capability_fold_has(a_id) {
propagated = true;
break;
}
tokio::time::sleep(Duration::from_millis(25)).await;
}
assert!(
propagated,
"after malformed-payload barrage, B should still index a valid announcement",
);
}