ble-reticulum/BLE_PROTOCOL_v0.3.0.md

230 lines
8.3 KiB
Markdown
Raw Normal View History

# BLE-Reticulum Protocol Specification v0.3.0
**Version**: 0.3.0
**Date**: December 2025
**Status**: Draft
**Backwards Compatible With**: v2.2
## 1. Overview
This document specifies the v0.3.0 extension to the BLE-Reticulum protocol. This version adds **capability advertisement** to support devices that can only operate in peripheral mode (e.g., ESP32-S3).
### 1.1 Problem Statement
The v2.2 protocol uses MAC address sorting to determine connection direction: the device with the numerically lower MAC address initiates the connection (acts as BLE central). However, some hardware platforms (notably ESP32-S3) cannot reliably operate as BLE central due to stack limitations.
When such a device has a lower MAC address than a peer, neither device initiates a connection:
- The peripheral-only device cannot initiate (hardware limitation)
- The peer waits for the lower-MAC device to initiate (per v2.2 protocol)
### 1.2 Solution
v0.3.0 introduces **capability flags** in the advertising packet via BLE manufacturer-specific data. Devices advertise their role capabilities, allowing the connection direction logic to be overridden when one device is peripheral-only.
## 2. Manufacturer-Specific Data Format
### 2.1 Advertising Data Structure
v0.3.0 devices include manufacturer-specific data in their advertising packet:
```
AD Type: 0xFF (Manufacturer Specific Data)
Length: 5 bytes (1 type + 4 data)
Data Format (4 bytes):
┌─────────┬─────────┬─────────┬─────────┐
│ Byte 0 │ Byte 1 │ Byte 2 │ Byte 3 │
├─────────┼─────────┼─────────┼─────────┤
│ CID Low │ CID High│ Version │ Flags │
└─────────┴─────────┴─────────┴─────────┘
CID (Bytes 0-1): Company ID, little-endian
0xFFFF = Reserved for testing (Bluetooth SIG)
Version (Byte 2): Protocol version
0x03 = v0.3.0
Flags (Byte 3): Capability flags
Bit 0: PERIPHERAL_ONLY (1 = cannot act as central)
Bit 1: Reserved (CENTRAL_ONLY, future use)
Bits 2-7: Reserved (must be 0)
```
### 2.2 Example Values
| Device Type | CID | Version | Flags | Raw Bytes |
|-------------|-----|---------|-------|-----------|
| Dual-mode (full capability) | 0xFFFF | 0x03 | 0x00 | `FF FF 03 00` |
| Peripheral-only (ESP32-S3) | 0xFFFF | 0x03 | 0x01 | `FF FF 03 01` |
### 2.3 Advertising Packet Layout
The v0.3.0 advertising packet extends v2.2:
```
Main Advertising Packet (31 bytes max):
├── Flags (3 bytes)
├── Complete 128-bit Service UUID (18 bytes)
│ └── 37145b00-442d-4a94-917f-8f42c5da28e3
├── Manufacturer Data (5 bytes) ← NEW in v0.3.0
│ ├── AD Type 0xFF (1 byte)
│ └── Data (4 bytes): CID + Version + Flags
└── Remaining: 5 bytes available
Scan Response Packet (31 bytes max):
└── Device Name: "RNS-{identity}" (variable)
```
## 3. Connection Direction Logic
### 3.1 Decision Algorithm
```
FUNCTION shouldInitiateConnection(local_device, peer_device):
local_peripheral_only = local_device.flags & PERIPHERAL_ONLY
peer_peripheral_only = peer_device.flags & PERIPHERAL_ONLY
# Case 1: Peer is peripheral-only, we are not
IF peer_peripheral_only AND NOT local_peripheral_only:
RETURN TRUE # We MUST initiate (peer cannot)
# Case 2: We are peripheral-only, peer is not
IF local_peripheral_only AND NOT peer_peripheral_only:
RETURN FALSE # Peer MUST initiate (we cannot)
# Case 3: Both are peripheral-only (deadlock)
IF peer_peripheral_only AND local_peripheral_only:
LOG_WARNING("Both devices peripheral-only, connection impossible")
RETURN FALSE # Neither can initiate
# Case 4: Both have full capability - use v2.2 MAC sorting
RETURN local_device.mac < peer_device.mac
```
### 3.2 Capability Detection
When a device does not advertise manufacturer data (v2.2 device):
- Assume `has_capability_data = false`
- Assume `capability_flags = 0x00` (full capability)
- Fall back to v2.2 MAC sorting
When manufacturer data is present:
- Verify Company ID = 0xFFFF
- Verify Version >= 0x03
- Extract capability flags from byte 3
## 4. Backwards Compatibility
### 4.1 Compatibility Matrix
| Our Device | Peer Device | Connection Decision | Result |
|------------|-------------|---------------------|--------|
| v0.3.0 dual | v0.3.0 dual | MAC sorting | Works |
| v0.3.0 dual | v0.3.0 P-only | We initiate | Works |
| v0.3.0 P-only | v0.3.0 dual | Peer initiates | Works |
| v0.3.0 dual | v0.2.x | MAC sorting | Works |
| v0.3.0 P-only | v0.2.x (lower MAC) | v0.2.x initiates | Works |
| v0.3.0 P-only | v0.2.x (higher MAC) | Neither initiates | **Fails** |
| v0.3.0 P-only | v0.3.0 P-only | Neither initiates | **Fails** |
### 4.2 Known Limitations
1. **v0.3.0 peripheral-only ↔ v0.2.x with higher MAC**: No connection possible. The v0.2.x device uses MAC sorting and waits for the lower-MAC device (the v0.3.0 P-only) to initiate.
**Mitigation**: Upgrade the v0.2.x device to v0.3.0.
2. **Two peripheral-only devices**: Connection impossible as neither can initiate.
**Mitigation**: Ensure at least one device in the mesh has full capability.
## 5. GATT Service (Unchanged from v2.2)
The GATT service structure remains unchanged:
```
Reticulum Service: 37145b00-442d-4a94-917f-8f42c5da28e3
├── RX Characteristic: 37145b00-442d-4a94-917f-8f42c5da28e5
│ └── Properties: WRITE, WRITE_WITHOUT_RESPONSE
├── TX Characteristic: 37145b00-442d-4a94-917f-8f42c5da28e4
│ └── Properties: READ, NOTIFY
│ └── CCCD: 00002902-0000-1000-8000-00805f9b34fb
└── Identity Characteristic: 37145b00-442d-4a94-917f-8f42c5da28e6
└── Properties: READ
└── Value: 16-byte identity hash
```
## 6. Implementation Notes
### 6.1 NimBLE (ESP32)
```cpp
// Setting manufacturer data
uint8_t mfr_data[4] = {0xFF, 0xFF, 0x03, peripheral_only ? 0x01 : 0x00};
advertising->setManufacturerData(mfr_data, sizeof(mfr_data));
// Parsing manufacturer data
if (device->haveManufacturerData()) {
std::string data = device->getManufacturerData();
if (data.size() >= 4) {
uint16_t cid = (uint8_t)data[0] | ((uint8_t)data[1] << 8);
if (cid == 0xFFFF && data[2] >= 0x03) {
capability_flags = data[3];
}
}
}
```
### 6.2 Android
```kotlin
// Setting manufacturer data (note: Android API excludes CID in the byte array)
val mfrData = byteArrayOf(0x03.toByte(), flags.toByte())
advertiseData.addManufacturerData(0xFFFF, mfrData)
// Parsing manufacturer data
val mfrData = scanRecord.getManufacturerSpecificData(0xFFFF)
if (mfrData != null && mfrData.size >= 2 && mfrData[0] >= 0x03.toByte()) {
capabilityFlags = mfrData[1].toInt() and 0xFF
}
```
### 6.3 Python (BlueZ/Bleak)
```python
# Setting manufacturer data in advertisement
# BlueZ uses D-Bus ManufacturerData property
manufacturer_data = {0xFFFF: bytes([0x03, 0x01 if peripheral_only else 0x00])}
# Parsing manufacturer data from scan
mfr_data = device.metadata.get("manufacturer_data", {}).get(0xFFFF)
if mfr_data and len(mfr_data) >= 2 and mfr_data[0] >= 0x03:
capability_flags = mfr_data[1]
```
## 7. Future Extensions
Reserved capability flag bits for potential future use:
| Bit | Name | Description |
|-----|------|-------------|
| 0 | PERIPHERAL_ONLY | Cannot act as BLE central |
| 1 | CENTRAL_ONLY | Cannot act as BLE peripheral |
| 2 | HIGH_BANDWIDTH | Supports extended MTU/PHY |
| 3 | RELAY_CAPABLE | Can relay packets in mesh |
| 4-7 | Reserved | Must be 0 |
## 8. Version History
| Version | Date | Changes |
|---------|------|---------|
| v2.0 | Oct 2025 | Identity characteristic for peer identification |
| v2.1 | Oct 2025 | (Deprecated) Identity in device name |
| v2.2 | Nov 2025 | Identity handshake protocol, identity-based keying |
| v0.3.0 | Dec 2025 | Capability advertisement for peripheral-only devices |
## 9. References
- [BLE-Reticulum Protocol v2.2](BLE_PROTOCOL_v2.2.md) - Full protocol specification
- [Bluetooth Core Specification](https://www.bluetooth.com/specifications/specs/core-specification/) - BLE advertising data format
- [Bluetooth Assigned Numbers](https://www.bluetooth.com/specifications/assigned-numbers/) - Company IDs