Skip to content

Commit de63f2c

Browse files
committed
feat(mesh): add BLE advertisement mesh bearer scaffold
1 parent 439b87b commit de63f2c

6 files changed

Lines changed: 399 additions & 6 deletions

File tree

src/mesh/Router.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
#if !MESHTASTIC_EXCLUDE_MQTT
1515
#include "mqtt/MQTT.h"
1616
#endif
17+
#if HAS_BLE_MESH_ADVERTISING
18+
#include "mesh/ble/BleAdvertisementMesh.h"
19+
#endif
1720
#include "Default.h"
1821
#if ARCH_PORTDUINO
1922
#include "Throttle.h"
@@ -381,6 +384,13 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
381384
}
382385
#endif
383386

387+
#if HAS_BLE_MESH_ADVERTISING
388+
if (bleAdvertisementMesh &&
389+
config.network.enabled_protocols & meshtastic_Config_NetworkConfig_ProtocolFlags_BLE_ADVERTISEMENT_BROADCAST) {
390+
bleAdvertisementMesh->onSend(p);
391+
}
392+
#endif
393+
384394
assert(iface); // This should have been detected already in sendLocal (or we just received a packet from outside)
385395
return iface->send(p);
386396
}
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
#include "mesh/ble/BleAdvertisementMesh.h"
2+
#include "mesh/mesh-pb-constants.h"
3+
4+
#if HAS_BLE_MESH_ADVERTISING
5+
#include "Router.h"
6+
#include "configuration.h"
7+
#include "main.h"
8+
#endif
9+
#include <pb_decode.h>
10+
#include <pb_encode.h>
11+
#include <string.h>
12+
13+
namespace
14+
{
15+
uint16_t crc16ccitt(const uint8_t *data, size_t length)
16+
{
17+
uint16_t crc = 0xffff;
18+
for (size_t i = 0; i < length; i++) {
19+
crc ^= static_cast<uint16_t>(data[i]) << 8;
20+
for (uint8_t bit = 0; bit < 8; bit++) {
21+
crc = (crc & 0x8000) ? static_cast<uint16_t>((crc << 1) ^ 0x1021) : static_cast<uint16_t>(crc << 1);
22+
}
23+
}
24+
return crc;
25+
}
26+
27+
void putLe16(uint8_t *p, uint16_t v)
28+
{
29+
p[0] = static_cast<uint8_t>(v);
30+
p[1] = static_cast<uint8_t>(v >> 8);
31+
}
32+
33+
void putLe32(uint8_t *p, uint32_t v)
34+
{
35+
p[0] = static_cast<uint8_t>(v);
36+
p[1] = static_cast<uint8_t>(v >> 8);
37+
p[2] = static_cast<uint8_t>(v >> 16);
38+
p[3] = static_cast<uint8_t>(v >> 24);
39+
}
40+
41+
uint16_t getLe16(const uint8_t *p)
42+
{
43+
return static_cast<uint16_t>(p[0]) | (static_cast<uint16_t>(p[1]) << 8);
44+
}
45+
46+
uint32_t getLe32(const uint8_t *p)
47+
{
48+
return static_cast<uint32_t>(p[0]) | (static_cast<uint32_t>(p[1]) << 8) | (static_cast<uint32_t>(p[2]) << 16) |
49+
(static_cast<uint32_t>(p[3]) << 24);
50+
}
51+
} // namespace
52+
53+
BleAdvertisementMeshCodec::BleAdvertisementMeshCodec() {}
54+
55+
bool BleAdvertisementMeshCodec::forEachFrame(const meshtastic_MeshPacket *mp, EmitFrame emit, void *context)
56+
{
57+
if (!mp || !emit || mp->which_payload_variant != meshtastic_MeshPacket_encrypted_tag) {
58+
return false;
59+
}
60+
61+
uint8_t encoded[meshtastic_MeshPacket_size] = {0};
62+
size_t encodedLength = pb_encode_to_bytes(encoded, sizeof(encoded), &meshtastic_MeshPacket_msg, mp);
63+
if (encodedLength == 0) {
64+
return false;
65+
}
66+
67+
uint8_t fragmentCount = (encodedLength + MAX_FRAGMENT_PAYLOAD_SIZE - 1) / MAX_FRAGMENT_PAYLOAD_SIZE;
68+
if (fragmentCount == 0 || fragmentCount > MAX_FRAGMENTS) {
69+
return false;
70+
}
71+
72+
uint16_t crc = crc16ccitt(encoded, encodedLength);
73+
for (uint8_t fragment = 0; fragment < fragmentCount; fragment++) {
74+
size_t offset = fragment * MAX_FRAGMENT_PAYLOAD_SIZE;
75+
size_t remaining = encodedLength - offset;
76+
uint8_t payloadLength =
77+
static_cast<uint8_t>(remaining > MAX_FRAGMENT_PAYLOAD_SIZE ? MAX_FRAGMENT_PAYLOAD_SIZE : remaining);
78+
79+
uint8_t frame[MAX_FRAME_SIZE] = {0};
80+
frame[0] = FRAME_MAGIC_0;
81+
frame[1] = FRAME_MAGIC_1;
82+
frame[2] = FRAME_VERSION;
83+
frame[3] = fragment;
84+
frame[4] = fragmentCount;
85+
frame[5] = payloadLength;
86+
putLe32(&frame[6], mp->from);
87+
putLe32(&frame[10], mp->id);
88+
putLe16(&frame[14], crc);
89+
memcpy(&frame[FRAME_HEADER_SIZE], &encoded[offset], payloadLength);
90+
91+
if (!emit(frame, FRAME_HEADER_SIZE + payloadLength, context)) {
92+
return false;
93+
}
94+
}
95+
96+
return true;
97+
}
98+
99+
void BleAdvertisementMeshCodec::reset(uint32_t from, uint32_t packetId, uint16_t crc, uint8_t fragmentCount)
100+
{
101+
currentFrom = from;
102+
currentPacketId = packetId;
103+
currentCrc = crc;
104+
expectedFragments = fragmentCount;
105+
lastFragmentLength = 0;
106+
receivedMask = 0;
107+
memset(encodedPacket, 0, sizeof(encodedPacket));
108+
}
109+
110+
bool BleAdvertisementMeshCodec::receiveFrame(const uint8_t *frame, size_t frameLength, meshtastic_MeshPacket *out)
111+
{
112+
if (!frame || !out || frameLength < FRAME_HEADER_SIZE || frameLength > MAX_FRAME_SIZE || frame[0] != FRAME_MAGIC_0 ||
113+
frame[1] != FRAME_MAGIC_1 || frame[2] != FRAME_VERSION) {
114+
return false;
115+
}
116+
117+
uint8_t fragment = frame[3];
118+
uint8_t fragmentCount = frame[4];
119+
uint8_t payloadLength = frame[5];
120+
uint32_t from = getLe32(&frame[6]);
121+
uint32_t packetId = getLe32(&frame[10]);
122+
uint16_t crc = getLe16(&frame[14]);
123+
124+
if (fragmentCount == 0 || fragmentCount > MAX_FRAGMENTS || fragment >= fragmentCount ||
125+
payloadLength > MAX_FRAGMENT_PAYLOAD_SIZE || frameLength != FRAME_HEADER_SIZE + payloadLength) {
126+
return false;
127+
}
128+
129+
size_t offset = fragment * MAX_FRAGMENT_PAYLOAD_SIZE;
130+
if (offset + payloadLength > sizeof(encodedPacket)) {
131+
return false;
132+
}
133+
134+
if (from != currentFrom || packetId != currentPacketId || crc != currentCrc || fragmentCount != expectedFragments) {
135+
reset(from, packetId, crc, fragmentCount);
136+
}
137+
138+
memcpy(&encodedPacket[offset], &frame[FRAME_HEADER_SIZE], payloadLength);
139+
receivedMask |= 1ULL << fragment;
140+
if (fragment == fragmentCount - 1) {
141+
lastFragmentLength = payloadLength;
142+
}
143+
144+
uint64_t completeMask = fragmentCount == 64 ? UINT64_MAX : ((1ULL << fragmentCount) - 1);
145+
if (receivedMask != completeMask || lastFragmentLength == 0) {
146+
return false;
147+
}
148+
149+
size_t encodedLength = (fragmentCount - 1) * MAX_FRAGMENT_PAYLOAD_SIZE + lastFragmentLength;
150+
if (crc16ccitt(encodedPacket, encodedLength) != currentCrc) {
151+
reset(0, 0, 0, 0);
152+
return false;
153+
}
154+
155+
memset(out, 0, sizeof(*out));
156+
bool decoded = pb_decode_from_bytes(encodedPacket, encodedLength, &meshtastic_MeshPacket_msg, out);
157+
reset(0, 0, 0, 0);
158+
return decoded;
159+
}
160+
161+
#if HAS_BLE_MESH_ADVERTISING
162+
163+
BleAdvertisementMesh *bleAdvertisementMesh = nullptr;
164+
165+
namespace
166+
{
167+
struct PlatformEmitContext
168+
{
169+
BleAdvertisementMesh::PlatformAdvertiseFrame advertiseFrame;
170+
};
171+
172+
bool emitPlatformFrame(const uint8_t *frame, size_t frameLength, void *context)
173+
{
174+
auto *platformContext = static_cast<PlatformEmitContext *>(context);
175+
return platformContext && platformContext->advertiseFrame && platformContext->advertiseFrame(frame, frameLength);
176+
}
177+
} // namespace
178+
179+
bool BleAdvertisementMesh::onSend(const meshtastic_MeshPacket *mp)
180+
{
181+
if (!mp || !advertiseFrame ||
182+
mp->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_BLE_ADVERTISEMENT) {
183+
return false;
184+
}
185+
186+
LOG_DEBUG("Broadcasting packet over BLE advertisements (id=%u)", mp->id);
187+
PlatformEmitContext context = {advertiseFrame};
188+
return BleAdvertisementMeshCodec::forEachFrame(mp, emitPlatformFrame, &context);
189+
}
190+
191+
void BleAdvertisementMesh::onAdvertisement(const uint8_t *frame, size_t frameLength)
192+
{
193+
meshtastic_MeshPacket mp = {};
194+
if (!codec.receiveFrame(frame, frameLength, &mp)) {
195+
return;
196+
}
197+
198+
if (router && mp.which_payload_variant == meshtastic_MeshPacket_encrypted_tag) {
199+
mp.transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_BLE_ADVERTISEMENT;
200+
UniquePacketPoolPacket p = packetPool.allocUniqueCopy(mp);
201+
p->rx_snr = 0;
202+
p->rx_rssi = 0;
203+
router->enqueueReceivedMessage(p.release());
204+
}
205+
}
206+
207+
#endif // HAS_BLE_MESH_ADVERTISING
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#pragma once
2+
3+
#include "mesh/generated/meshtastic/mesh.pb.h"
4+
#include <stddef.h>
5+
#include <stdint.h>
6+
7+
/**
8+
* BLE advertisement bearer for encrypted MeshPacket protobufs.
9+
*
10+
* The platform BLE layer is intentionally kept outside this codec. ESP32 and
11+
* nRF52 implementations can carry these frames in service data, manufacturer
12+
* data, or an extended advertising set without changing router semantics.
13+
*/
14+
class BleAdvertisementMeshCodec
15+
{
16+
public:
17+
static constexpr uint8_t FRAME_MAGIC_0 = 'M';
18+
static constexpr uint8_t FRAME_MAGIC_1 = 'T';
19+
static constexpr uint8_t FRAME_VERSION = 1;
20+
// Legacy BLE advertisements are 31 bytes including AD type overhead. A
21+
// manufacturer-data bearer leaves 27 bytes for our frame after the 16-bit
22+
// company id.
23+
static constexpr size_t MAX_FRAME_SIZE = 27;
24+
static constexpr size_t FRAME_HEADER_SIZE = 16;
25+
static constexpr size_t MAX_FRAGMENT_PAYLOAD_SIZE = MAX_FRAME_SIZE - FRAME_HEADER_SIZE;
26+
static constexpr uint8_t MAX_FRAGMENTS = 64;
27+
28+
typedef bool (*EmitFrame)(const uint8_t *frame, size_t frameLength, void *context);
29+
30+
BleAdvertisementMeshCodec();
31+
32+
/**
33+
* Encode an encrypted MeshPacket into one or more BLE ADV frames.
34+
*/
35+
static bool forEachFrame(const meshtastic_MeshPacket *mp, EmitFrame emit, void *context);
36+
37+
/**
38+
* Accept one BLE ADV frame. Returns true when a complete MeshPacket has
39+
* been reassembled into out.
40+
*/
41+
bool receiveFrame(const uint8_t *frame, size_t frameLength, meshtastic_MeshPacket *out);
42+
43+
private:
44+
uint32_t currentFrom = 0;
45+
uint32_t currentPacketId = 0;
46+
uint16_t currentCrc = 0;
47+
uint8_t expectedFragments = 0;
48+
uint8_t lastFragmentLength = 0;
49+
uint64_t receivedMask = 0;
50+
uint8_t encodedPacket[meshtastic_MeshPacket_size] = {0};
51+
52+
void reset(uint32_t from, uint32_t packetId, uint16_t crc, uint8_t fragmentCount);
53+
};
54+
55+
#if HAS_BLE_MESH_ADVERTISING
56+
57+
#include "MeshTypes.h"
58+
59+
class BleAdvertisementMesh
60+
{
61+
public:
62+
typedef bool (*PlatformAdvertiseFrame)(const uint8_t *frame, size_t frameLength);
63+
64+
explicit BleAdvertisementMesh(PlatformAdvertiseFrame advertiseFrame) : advertiseFrame(advertiseFrame) {}
65+
66+
bool onSend(const meshtastic_MeshPacket *mp);
67+
void onAdvertisement(const uint8_t *frame, size_t frameLength);
68+
69+
private:
70+
PlatformAdvertiseFrame advertiseFrame = nullptr;
71+
BleAdvertisementMeshCodec codec;
72+
};
73+
74+
extern BleAdvertisementMesh *bleAdvertisementMesh;
75+
76+
#endif // HAS_BLE_MESH_ADVERTISING

src/mesh/generated/meshtastic/config.pb.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,9 @@ typedef enum _meshtastic_Config_NetworkConfig_ProtocolFlags {
171171
/* Do not broadcast packets over any network protocol */
172172
meshtastic_Config_NetworkConfig_ProtocolFlags_NO_BROADCAST = 0,
173173
/* Enable broadcasting packets via UDP over the local network */
174-
meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST = 1
174+
meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST = 1,
175+
/* Enable broadcasting packets over Bluetooth LE advertisements. */
176+
meshtastic_Config_NetworkConfig_ProtocolFlags_BLE_ADVERTISEMENT_BROADCAST = 2
175177
} meshtastic_Config_NetworkConfig_ProtocolFlags;
176178

177179
/* Deprecated in 2.7.4: Unused */
@@ -678,8 +680,8 @@ extern "C" {
678680
#define _meshtastic_Config_NetworkConfig_AddressMode_ARRAYSIZE ((meshtastic_Config_NetworkConfig_AddressMode)(meshtastic_Config_NetworkConfig_AddressMode_STATIC+1))
679681

680682
#define _meshtastic_Config_NetworkConfig_ProtocolFlags_MIN meshtastic_Config_NetworkConfig_ProtocolFlags_NO_BROADCAST
681-
#define _meshtastic_Config_NetworkConfig_ProtocolFlags_MAX meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST
682-
#define _meshtastic_Config_NetworkConfig_ProtocolFlags_ARRAYSIZE ((meshtastic_Config_NetworkConfig_ProtocolFlags)(meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST+1))
683+
#define _meshtastic_Config_NetworkConfig_ProtocolFlags_MAX meshtastic_Config_NetworkConfig_ProtocolFlags_BLE_ADVERTISEMENT_BROADCAST
684+
#define _meshtastic_Config_NetworkConfig_ProtocolFlags_ARRAYSIZE ((meshtastic_Config_NetworkConfig_ProtocolFlags)(meshtastic_Config_NetworkConfig_ProtocolFlags_BLE_ADVERTISEMENT_BROADCAST+1))
683685

684686
#define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED
685687
#define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MAX meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED

src/mesh/generated/meshtastic/mesh.pb.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -611,7 +611,9 @@ typedef enum _meshtastic_MeshPacket_TransportMechanism {
611611
/* Arrived via Multicast UDP */
612612
meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MULTICAST_UDP = 6,
613613
/* Arrived via API connection */
614-
meshtastic_MeshPacket_TransportMechanism_TRANSPORT_API = 7
614+
meshtastic_MeshPacket_TransportMechanism_TRANSPORT_API = 7,
615+
/* Arrived via Bluetooth LE advertisements */
616+
meshtastic_MeshPacket_TransportMechanism_TRANSPORT_BLE_ADVERTISEMENT = 8
615617
} meshtastic_MeshPacket_TransportMechanism;
616618

617619
/* Log levels, chosen to match python logging conventions. */
@@ -1449,8 +1451,8 @@ extern "C" {
14491451
#define _meshtastic_MeshPacket_Delayed_ARRAYSIZE ((meshtastic_MeshPacket_Delayed)(meshtastic_MeshPacket_Delayed_DELAYED_DIRECT+1))
14501452

14511453
#define _meshtastic_MeshPacket_TransportMechanism_MIN meshtastic_MeshPacket_TransportMechanism_TRANSPORT_INTERNAL
1452-
#define _meshtastic_MeshPacket_TransportMechanism_MAX meshtastic_MeshPacket_TransportMechanism_TRANSPORT_API
1453-
#define _meshtastic_MeshPacket_TransportMechanism_ARRAYSIZE ((meshtastic_MeshPacket_TransportMechanism)(meshtastic_MeshPacket_TransportMechanism_TRANSPORT_API+1))
1454+
#define _meshtastic_MeshPacket_TransportMechanism_MAX meshtastic_MeshPacket_TransportMechanism_TRANSPORT_BLE_ADVERTISEMENT
1455+
#define _meshtastic_MeshPacket_TransportMechanism_ARRAYSIZE ((meshtastic_MeshPacket_TransportMechanism)(meshtastic_MeshPacket_TransportMechanism_TRANSPORT_BLE_ADVERTISEMENT+1))
14541456

14551457
#define _meshtastic_LogRecord_Level_MIN meshtastic_LogRecord_Level_UNSET
14561458
#define _meshtastic_LogRecord_Level_MAX meshtastic_LogRecord_Level_CRITICAL

0 commit comments

Comments
 (0)