Skip to content

Conversation

@PeterGRutherford
Copy link
Contributor

  • Created lib-network/src/mtu.rs with centralized MTU constants for all protocols

    • BLE: 23/247/512, chunk=200
    • Bluetooth Classic: 1000
    • LoRaWAN: 242
    • WiFi Direct: 1400
    • UDP: 1400, QUIC: 1200, Mesh: 65536
    • Added Protocol enum with mtu(), chunk_size(), negotiated_mtu() methods
  • Created lib-network/src/fragmentation.rs with unified fragmentation logic

    • fragment_message() for splitting payloads
    • reassemble_message() for combining fragments
    • FragmentReassembler for stateful streaming reassembly
    • Handles out-of-order fragments, duplicates, concurrent messages
  • Updated 10 files to use centralized constants:

    • lib-network: blockchain_sync, bluetooth (gatt/mod/classic), lorawan, lorawan_hardware, discovery
    • lib-storage: dht/transport
    • zhtp: bluetooth_le, web4_stub
  • Removed all hardcoded MTU values (247, 242, 1400, 1000, 512, 23)

  • Added backward-compatible deprecated aliases in blockchain_sync

  • All 16 tests passing, code compiles successfully

Copilot AI review requested due to automatic review settings December 23, 2025 04:22
@PeterGRutherford PeterGRutherford linked an issue Dec 23, 2025 that may be closed by this pull request
5 tasks
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request centralizes MTU (Maximum Transmission Unit) constants and fragmentation logic across the codebase to eliminate hardcoded values and provide a unified approach to message fragmentation across different network protocols (BLE, Bluetooth Classic, LoRaWAN, WiFi Direct, UDP, QUIC, and Mesh).

Key changes:

  • Introduced lib-network/src/mtu.rs with protocol-specific MTU constants and a Protocol enum with helper methods
  • Created lib-network/src/fragmentation.rs with unified fragmentation/reassembly logic supporting out-of-order fragments and duplicate detection
  • Refactored 10 files to use centralized constants instead of hardcoded MTU values

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
lib-network/src/mtu.rs New module defining MTU constants for all protocols (BLE: 23/247/512, Bluetooth Classic: 1000, LoRaWAN: 242, WiFi/UDP: 1400, QUIC: 1200, Mesh: 65536) and Protocol enum with helper methods
lib-network/src/fragmentation.rs New module implementing protocol-agnostic fragmentation with 8-byte headers (message_id: u32, total_fragments: u16, fragment_index: u16) and stateful reassembly with DoS protection
lib-network/src/lib.rs Exports new MTU constants and fragmentation utilities for public API
lib-network/src/blockchain_sync/mod.rs Replaces local constants with imports from mtu module and adds deprecated aliases (incomplete)
lib-network/src/protocols/bluetooth/gatt.rs Refactors fragment_large_message and FragmentReassembler to use centralized fragmentation logic
lib-network/src/protocols/bluetooth/mod.rs Updates BLE message fragmentation to use centralized fragment_message function
lib-network/src/protocols/bluetooth/classic.rs Updates RFCOMM fragmentation to use centralized fragment_message function
lib-network/src/protocols/lorawan.rs Replaces hardcoded 242-byte payload limit with LORAWAN_MAX_PAYLOAD constant and uses centralized fragmentation
lib-network/src/discovery/lorawan_hardware.rs Replaces hardcoded max_payload_size values with LORAWAN_MAX_PAYLOAD constant
lib-storage/src/dht/transport.rs Updates mtu() method to use centralized MTU constants for all transport types
zhtp/src/web4_stub.rs Replaces hardcoded 1200 with QUIC_MTU constant
zhtp/src/server/protocols/bluetooth_le.rs Replaces hardcoded 247 with BLE_TYPICAL_MTU constant

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +128 to +131
pub fn cleanup_stale_fragments(&mut self, _message_id: u64) {
// Clear all pending (centralized reassembler doesn't track individual message cleanup)
self.inner.clear();
warn!("🗑️ Cleaned up all stale fragments");
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cleanup_stale_fragments method clears ALL pending fragments, not just the stale ones for a specific message_id. This is inconsistent with the method signature which accepts a message_id parameter that is now ignored. This could disrupt reassembly of valid in-progress messages.

Suggested change
pub fn cleanup_stale_fragments(&mut self, _message_id: u64) {
// Clear all pending (centralized reassembler doesn't track individual message cleanup)
self.inner.clear();
warn!("🗑️ Cleaned up all stale fragments");
pub fn cleanup_stale_fragments(&mut self, message_id: u64) {
// Centralized reassembler currently does not support per-message cleanup.
// To avoid disrupting reassembly of other in-progress messages, we only log for now.
debug!(
"Requested cleanup of stale fragments for message_id {}, \
but centralized reassembler does not support targeted cleanup; no action taken",
message_id
);

Copilot uses AI. Check for mistakes.
Comment on lines +54 to +62
/// Re-export chunk sizes for backward compatibility
#[deprecated(since = "0.1.0", note = "Use crate::mtu::BLE_CHUNK_SIZE instead")]
pub const BLE_CHUNK_SIZE_COMPAT: usize = BLE_CHUNK_SIZE;

#[deprecated(since = "0.1.0", note = "Use crate::mtu::BLUETOOTH_CLASSIC_CHUNK_SIZE instead")]
pub const CLASSIC_CHUNK_SIZE: usize = BLUETOOTH_CLASSIC_CHUNK_SIZE;

#[deprecated(since = "0.1.0", note = "Use crate::mtu::WIFI_DIRECT_CHUNK_SIZE instead")]
pub const WIFI_CHUNK_SIZE: usize = WIFI_DIRECT_CHUNK_SIZE;
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The backward compatibility aliases are incomplete. The old module exported BLE_CHUNK_SIZE and WIFI_CHUNK_SIZE as public constants, but the new code doesn't provide deprecated aliases for these names. Code importing blockchain_sync::BLE_CHUNK_SIZE or blockchain_sync::WIFI_CHUNK_SIZE will fail to compile. Add deprecated pub const definitions for these missing names.

Copilot uses AI. Check for mistakes.
Comment on lines 72 to 90
pub fn fragment_large_message(message_id: u64, data: &[u8], mtu: u16) -> Vec<Vec<u8>> {
const HEADER_SIZE: usize = 11; // message_id(8) + total_fragments(2) + sequence(1)
let max_data_per_fragment = (mtu as usize).saturating_sub(3 + HEADER_SIZE);
// ATT overhead is 3 bytes, leave room for our 8-byte header
let chunk_size = (mtu as usize).saturating_sub(3 + 8).max(20);

let chunks: Vec<&[u8]> = data.chunks(max_data_per_fragment).collect();
let total_fragments = chunks.len() as u16;
// Use centralized fragmentation (produces 8-byte headers)
let fragments = fragment_message(data, chunk_size);

chunks.into_iter().enumerate().map(|(index, chunk)| {
let mut fragment = Vec::with_capacity(HEADER_SIZE + chunk.len());
fragment.extend_from_slice(&message_id.to_le_bytes());
fragment.extend_from_slice(&total_fragments.to_le_bytes());
fragment.push(index as u8);
fragment.extend_from_slice(chunk);
fragment
}).collect()
// Convert to wire format
fragments.into_iter()
.map(|f| f.to_bytes())
.collect()
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wire format for fragments has changed incompatibly. The old format used 12-byte headers (message_id: u64, total_fragments: u16, fragment_index: u16), but the new centralized format uses 8-byte headers (message_id: u32, total_fragments: u16, fragment_index: u16). The GattMessage::from_raw parser at lines 239-246 still expects the old 12-byte format, which will fail to parse fragments created by the new fragment_large_message function. This breaks backward compatibility and interoperability between old and new clients.

Copilot uses AI. Check for mistakes.
Comment on lines 77 to 80
///
/// **REFACTORED**: Now uses centralized fragmentation with standardized 8-byte headers.
/// Returns Vec of wire-format fragments ready for GATT transmission.
pub fn fragment_large_message(message_id: u64, data: &[u8], mtu: u16) -> Vec<Vec<u8>> {
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The message_id parameter type changed from u64 to u32 in the FragmentHeader. The fragment_large_message function accepts a u64 message_id parameter, but the centralized fragment_message function ignores it and generates its own u32 message_id. This breaks backward compatibility and could cause reassembly failures if existing code relies on specific message IDs being preserved.

Suggested change
///
/// **REFACTORED**: Now uses centralized fragmentation with standardized 8-byte headers.
/// Returns Vec of wire-format fragments ready for GATT transmission.
pub fn fragment_large_message(message_id: u64, data: &[u8], mtu: u16) -> Vec<Vec<u8>> {
///
/// **REFACTORED**: Now uses centralized fragmentation with standardized 8-byte headers.
/// Returns Vec of wire-format fragments ready for GATT transmission.
///
/// Note: The `message_id` parameter is currently ignored. Message IDs are
/// generated internally by the centralized fragmentation logic. Callers
/// should not rely on a specific `message_id` being preserved.
pub fn fragment_large_message(_message_id: u64, data: &[u8], mtu: u16) -> Vec<Vec<u8>> {

Copilot uses AI. Check for mistakes.
}

/// Deserialize fragment from wire format
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function from_bytes should validate that fragment_index is less than total_fragments to prevent invalid fragment data from being processed.

Copilot uses AI. Check for mistakes.
Comment on lines +162 to +163
// Generate unique message ID (use hash of payload for determinism)
let message_id = payload.iter().fold(0u32, |acc, &b| acc.wrapping_add(b as u32));
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The message_id generation using a simple wrapping sum is not cryptographically secure and can easily produce collisions. Two different messages could get the same message_id, causing fragments to be mixed together during reassembly. Consider using a proper hash function or a sequential counter with some randomness.

Copilot uses AI. Check for mistakes.
Comment on lines +120 to 123
info!("✅ Reassembled message from {} fragments ({} bytes)",
self.inner.pending_count(), data.len());
}

Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pending_count() method is being called after result is returned from add_fragment, but at that point the message has already been removed from pending (line 312). This will always return 0 or the count of other messages, not the count of fragments in the just-completed message. The log message is misleading.

Suggested change
info!("✅ Reassembled message from {} fragments ({} bytes)",
self.inner.pending_count(), data.len());
}
info!("✅ Reassembled complete message ({} bytes)", data.len());
}

Copilot uses AI. Check for mistakes.
@umwelt
Copy link
Contributor

umwelt commented Dec 29, 2025

Hi Peter

Your PR #503 (centralize MTU constants and fragmentation logic) was excellent foundational work. However, it had 8 critical protocol violations that would cause failures in production:

  1. ❌ Message ID collisions - Identical payloads in same session get same ID
  2. ❌ No wire versioning - Header format change (11→8 bytes) breaks compatibility
  3. ❌ Global ID space - No session isolation
  4. ❌ Unbounded memory - Reassembler can grow without limit
  5. ❌ No timeout enforcement - Stale fragments accumulate forever
  6. ❌ Incomplete DoS protection - Only message count, not per-session caps
  7. ❌ Non-deterministic cleanup - Random FIFO eviction
  8. ❌ Missing test coverage - Critical scenarios untested

I created lib-network/src/fragmentation_v2.rs - a protocol-grade redesign that fixes all 8 violations:

✅ Session-scoped identity - (session_id, message_seq, fragment_index) = zero collisions
✅ Versioned headers - Explicit version: u8 field (FIRST byte, always safe to decode)
✅ Session binding - Every reassembler bound to exactly one SessionId
✅ Bounded memory - Hard limits on inflight messages, message size, fragment count
✅ Dual timeouts - initial_timeout (message never completes) + idle_timeout (no progress)
✅ Structural DoS protection - Per-session + per-message caps enforced before allocation
✅ Deterministic cleanup - Policy-driven expiry, 100% testable
✅ Comprehensive tests - 23 tests covering all edge cases (all passing)

🧪 Current Status

✅ lib-network/src/fragmentation_v2.rs - 1,200 LOC complete
✅ All 23 tests passing
✅ Compiles with full lib-network
✅ Zero breaking changes to existing code
✅ Ready for protocol layer integration

You Need to Implement

Verify the Implementation

All tests pass

cargo test --lib fragmentation_v2 --package lib-network

Verify build

cargo build --lib -p lib-network

Protocol Integration

Update your protocol layers to use v2. This is straightforward adapter work:

3 simple steps per protocol (BLE, QUIC, LoRaWAN, WiFi Direct):

  1. Surface SessionId from handshakes (already exists, just expose it)
    pub struct GattSession {
    pub session_id: SessionId, // ← ADD THIS
    pub message_seq: AtomicU32, // ← ADD THIS (per-session counter)
    }
  2. Create one reassembler per session
    let mut reassembler = FragmentReassemblerV2::new(
    session.session_id.clone(),
    ReassemblerConfig::from_protocol(Protocol::BluetoothLE),
    );
  3. Replace fragment operations
    // Sender (simple swap)
    let fragments = fragment_message_v2(msg_seq, &payload, chunk_size)?;

// Receiver (swap + add reassembler)
let fragment = FragmentV1::from_bytes(&bytes)?;
if let Some(complete) = reassembler.add_fragment(fragment)? {
handle_complete_message(complete);
}

Files to update:

  • lib-network/src/protocols/bluetooth/gatt.rs - BLE
  • lib-network/src/protocols/bluetooth/classic.rs - Bluetooth Classic
  • lib-network/src/protocols/quic_mesh.rs - QUIC
  • lib-network/src/protocols/lorawan.rs - LoRaWAN
  • lib-network/src/protocols/wifi_direct.rs - WiFi Direct

Testing

After integration, run this test scenario:
Send 3 identical 1KB messages in parallel
Deliver fragments in random order
→ All 3 should reassemble correctly (no collisions!)

All code is fully documented. Key types:

use lib_network::fragmentation_v2::{
FragmentReassemblerV2, // Session-bound reassembler
FragmentV1, // Wire-safe fragment with v1 header
ReassemblerConfig, // Per-protocol configuration
fragment_message_v2, // Sender-side fragmentation
Protocol, // Protocol enum (BLE, QUIC, etc)
};
use lib_network::protocols::types::SessionId;

// Module exports at lib-network/src/lib.rs
pub mod fragmentation_v2; // Ready to use

1 Review lib-network/src/fragmentation_v2.rs (detailed comments throughout)
2 Identify your session types and surface SessionId
3 Update BLE, QUIC, LoRaWAN, WiFi Direct protocols (3 changes each)
4 Run integration tests
5 Deprecate old v1 code (keep for backward compat)
6 Submit updated PR with v2 integration complete

The v2 module is self-contained and ready to go. You just need to wire it into your protocols. All the hard protocol design work is done.

Thanks

@umwelt umwelt self-requested a review December 29, 2025 18:58
Copy link
Contributor

@umwelt umwelt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Peter I leave a doc with requests changes: #503 (comment)

@umwelt umwelt added the CHANGES REQUESTED PR that is blocked for CR label Dec 29, 2025
@umwelt umwelt assigned umwelt and PeterGRutherford and unassigned umwelt Dec 29, 2025
- Created lib-network/src/mtu.rs with centralized MTU constants for all protocols
  - BLE: 23/247/512, chunk=200
  - Bluetooth Classic: 1000
  - LoRaWAN: 242
  - WiFi Direct: 1400
  - UDP: 1400, QUIC: 1200, Mesh: 65536
  - Added Protocol enum with mtu(), chunk_size(), negotiated_mtu() methods

- Created lib-network/src/fragmentation.rs with unified fragmentation logic
  - fragment_message() for splitting payloads
  - reassemble_message() for combining fragments
  - FragmentReassembler for stateful streaming reassembly
  - Handles out-of-order fragments, duplicates, concurrent messages

- Updated 10 files to use centralized constants:
  - lib-network: blockchain_sync, bluetooth (gatt/mod/classic), lorawan, lorawan_hardware, discovery
  - lib-storage: dht/transport
  - zhtp: bluetooth_le, web4_stub

- Removed all hardcoded MTU values (247, 242, 1400, 1000, 512, 23)
- Added backward-compatible deprecated aliases in blockchain_sync
- All 16 tests passing, code compiles successfully
@scasino983 scasino983 force-pushed the fix/standardize-mtu-handling-across-protocols branch from c717038 to 6f9b01f Compare January 4, 2026 06:31
@scasino983
Copy link
Contributor

Rebased onto latest development and resolved the conflict in lib-network/src/protocols/bluetooth/mod.rs. Branch force-pushed; mergeability should now be clear once checks run.

@scasino983 scasino983 requested a review from umwelt January 4, 2026 07:06
@scasino983
Copy link
Contributor

Fix for CI failure: moved MTU constants to lib-types, updated lib-storage to import from lib_types::mtu, and re-exported from lib-network. This should resolve the lib_network dependency error.

PeterGRutherford and others added 3 commits January 5, 2026 10:59
…ayers

- Add session-based fragmentation to BLE GATT (GattSession)
- Add session-based fragmentation to Bluetooth Classic (RfcommSession)
- Add session-based fragmentation to QUIC mesh (frag_session_id fields)
- Add session-based fragmentation to LoRaWAN (LoRaWANSession)
- Add session-based fragmentation to WiFi Direct (WiFiDirectSession)

Each protocol now uses:
- SessionId for message isolation (prevents collision)
- AtomicU32 for monotonic message sequence counters
- FragmentReassemblerV2 for robust out-of-order reassembly
- Protocol-specific ReassemblerConfig with appropriate limits

All v2 fragmentation tests pass (23/23), including:
- Concurrent identical payloads with different sequences
- Interleaved fragments from multiple messages
- Out-of-order fragment delivery
- Session binding and duplicate detection
@sonarqubecloud
Copy link

sonarqubecloud bot commented Jan 7, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CHANGES REQUESTED PR that is blocked for CR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ARCH-D-2.6] Standardize MTU Handling Across Protocols

4 participants