ble mesh tutorial – BLE Mesh Protocol Stack

EmbeddedPathashala · BLE Mesh Series

ble mesh tutorial – BLE Mesh Protocol Stack
Part 2 — Layers Deep Dive

How each layer processes data, what gets encrypted where, and how BlueZ implements the mesh stack via D-Bus.

2
Encryption Layers
29B
Max Adv Payload
2
Bearer Types

Key Concepts in This Post

Network PDU Access PDU Transport PDU NetKey / AppKey MIC Authentication Segmentation SAR Advertising Bearer GATT Bearer BlueZ D-Bus Mesh API

How Data Flows Through the Stack

When a light switch node wants to turn off a light bulb node across the building, the “turn off” command travels down the 8 layers of the sender’s stack, getting wrapped (encapsulated) at each layer, transmitted over BLE advertising, then unwrapped back up the layers of the receiver’s stack.

Think of it like posting a letter: your message goes inside an envelope (Access layer), the envelope goes into a padded bag (Transport), the bag gets a shipping label (Network), and the postal service (Bearer) carries it. The receiving end reverses this process.

📦 PDU Encapsulation Diagram

Each layer wraps the data from the layer above it. The innermost box is the raw application data; the outermost is what travels over BLE radio.

🔒 Network PDU (NetKey encrypted)

Lower Transport PDU (segments if needed)

🔒 Upper Transport PDU (AppKey encrypted + MIC)

Access PDU (OpCode + Parameters)

Application Data

e.g. 0x8202 (Generic OnOff SET)

← Outer wrapper (up to 29 bytes BLE adv payload)Inner data →

📡 L2 — Bearer Layer

How network messages physically travel between nodes

The bearer layer is the transport vehicle for mesh network messages. Two bearers are defined:

📳 Advertising Bearer
  • Uses BLE advertising channels 37, 38, 39
  • No connection required
  • All mesh nodes implement this
  • Enables broadcast/relay behaviour
  • Uses AD Type: 0x2A (Mesh Message)
📱 GATT Bearer
  • Uses a standard BLE GATT connection
  • For legacy devices (smartphones, tablets)
  • Uses Mesh Proxy Service (UUID 0x1828)
  • Allows phones without native mesh to participate
  • Acts as a proxy to the advertising bearer
BlueZ — Check Bearer / Mesh Proxy Service
# Check if BlueZ mesh daemon is running
systemctl status bluetooth-meshd

# Verify BlueZ version (mesh support requires 5.50+)
bluetoothctl --version

# Scan for a Mesh Proxy node advertising GATT Bearer
# Mesh Proxy Service UUID: 0x1828
sudo hcitool lescan --duplicates &
sudo hcidump -i hci0 -X | grep -A5 "1828"

🌐 L3 — Network Layer

Addressing, relay decisions, network-layer encryption

The network layer wraps the transport PDU into a Network PDU. It adds source/destination addresses and a TTL (Time To Live) field, then encrypts and authenticates using the Network Key (NetKey).

Field Size Purpose
IVI 1 bit IV Index LSB (replay protection)
NID 7 bits Network ID derived from NetKey
CTL 1 bit 0 = access message, 1 = control message
TTL 7 bits Time To Live — decremented by each relay
SEQ 24 bits Sequence number (replay attack protection)
SRC 16 bits Source unicast address
DST 16 bits Destination address (unicast/group/all)
TransportPDU 1–16 B Encrypted transport layer payload
BlueZ — Network Layer via D-Bus (org.bluez.mesh.Network1)
# BlueZ exposes the mesh network via D-Bus
# Object path: /org/bluez/mesh
# Interface: org.bluez.mesh.Network1

# Inspect available D-Bus mesh objects
dbus-send --system --dest=org.bluez.mesh   --print-reply /org/bluez/mesh   org.freedesktop.DBus.Introspectable.Introspect

# List mesh network D-Bus properties
gdbus introspect --system   --dest org.bluez.mesh   --object-path /org/bluez/mesh

✂ L4 — Lower Transport Layer (SAR)

Segmentation and reassembly for large messages

BLE advertising payloads are tiny — at most ~29 usable bytes in the mesh network PDU. The lower transport layer handles Segmentation and Reassembly (SAR) so larger messages can be sent reliably.

Large Message → Segmented into Multiple PDUs

Upper Transport PDU
(up to 380 bytes)

SEG 0 | SRC | SEQ | Data[0..11]
SEG 1 | SRC | SEQ | Data[12..23]
SEG 2 | SRC | SEQ | Data[24..35]
… more segments …

Each segment fits in one BLE advertising PDU. Destination reassembles them in order.

🔒 L5 — Upper Transport Layer (AppKey Encryption)

Application data encryption and the Friend feature

This is where your application payload gets encrypted using the Application Key (AppKey). The result includes an AES-CCM Message Integrity Check (MIC) to authenticate the data. A relay node forwarding this message can never read the payload — it only has the NetKey, not the AppKey.

🔐 Application Encryption
  • Algorithm: AES-CCM-128
  • Key: AppKey (64 bytes)
  • Nonce: IV Index + SEQ + SRC
  • MIC: 4 or 8 bytes
📞 Friend Feature
  • Low Power Nodes (LPN) sleep most of the time
  • Friend node buffers messages for LPN
  • LPN polls Friend when it wakes up
  • Control messages managed here
BlueZ — Sending a Mesh Message via D-Bus (org.bluez.mesh.Node1)
# BlueZ mesh node: org.bluez.mesh.Node1
# Method: Send(source, destination, key_index, data)

# Using Python + dbus to send a mesh message
import dbus

bus = dbus.SystemBus()
mesh_node = bus.get_object(
    'org.bluez.mesh',
    '/org/bluez/mesh/node/AABBCCDDEEFF0011'
)
iface = dbus.Interface(mesh_node, 'org.bluez.mesh.Node1')

# Send Generic OnOff SET message
# dst=0xC000 (group addr), app_key_idx=0, data=[opcode, value, tid]
iface.Send(
    0x0001,           # source element address
    0xC000,           # destination group address
    0,                # app key index
    [0x82, 0x02, 0x01, 0x01]  # Generic OnOff SET (ON)
)

📋 L6 — Access Layer (OpCodes & Application Format)

Defines the format of application data

The access layer defines the structure of the application payload before encryption. Every message has an OpCode (1–3 bytes) that identifies the operation, followed by its parameters.

OpCode (Hex) Message Payload
0x8201 Generic OnOff GET None
0x8202 Generic OnOff SET OnOff (1B) + TID (1B) + [Transition]
0x8204 Generic OnOff Status PresentOnOff (1B) + [TargetOnOff]
0x824B Generic Level SET Level (2B) + TID (1B)

The access layer verifies the application key context before passing messages upward — ensuring a lighting AppKey cannot accidentally unlock a door lock message.

🌟 L7 & L8 — Foundation Model & Model Layers

Where your application logic lives

Foundation Model Layer

Built-in models required for managing the mesh network. Every mesh node implements these.

  • Configuration Server — accepts config from Config Client
  • Configuration Client — configures other nodes
  • Health Server/Client — fault reporting
Model Layer (Application)

Bluetooth SIG-defined models for common use cases. Vendor models also allowed.

  • Generic OnOff Server/Client
  • Light Lightness Server
  • Sensor Server/Client
  • Scene Server
BlueZ — Registering a Mesh Application with Model (org.bluez.mesh.Application1)
# BlueZ mesh application exposes D-Bus objects:
#   /app                 -> org.bluez.mesh.Application1
#   /app/ele0            -> org.bluez.mesh.Element1
#   (element has models listed as properties)

import dbus
import dbus.service
import dbus.mainloop.glib
from gi.repository import GLib

MESH_APP_IFACE   = 'org.bluez.mesh.Application1'
MESH_ELEM_IFACE  = 'org.bluez.mesh.Element1'

class MeshElement(dbus.service.Object):
    def __init__(self, bus, path, index, models):
        super().__init__(bus, path)
        self._index  = index
        self._models = models

    @dbus.service.method(dbus.PROPERTIES_IFACE,
                         in_signature='ss', out_signature='v')
    def Get(self, iface, prop):
        if iface != MESH_ELEM_IFACE:
            raise dbus.exceptions.DBusException('InvalidInterface')
        if prop == 'Index':
            return dbus.Byte(self._index)
        if prop == 'Models':
            # List SIG model IDs for this element
            # 0x1000 = Generic OnOff Server
            return dbus.Array(
                [dbus.UInt16(m) for m in self._models],
                signature='q'
            )
        raise dbus.exceptions.DBusException('InvalidProperty')

# Create element 0 with Generic OnOff Server model
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus      = dbus.SystemBus()
element0 = MeshElement(bus, '/app/ele0', 0, [0x1000])

📋 Layer Summary Reference

# Layer Encryption Key BlueZ Interface Job
8 Model None Application code User scenario logic
7 Foundation Model DevKey meshctl / cfgclient Network config & management
6 Access None (controls AppKey) Node1.Send() OpCode + parameter format
5 Upper Transport AES-CCM (AppKey) meshd internal Encrypt payload + MIC
4 Lower Transport None meshd internal Segmentation & reassembly
3 Network AES-CCM (NetKey) Network1 D-Bus Address, TTL, relay
2 Bearer None bluetooth-meshd Adv / GATT transport
1 BLE Core Link-layer (optional) hci0 / bluetoothd Radio, packets, channels

Up Next: How Mesh Messages Actually Travel

Part 3 covers the managed-flood relay mechanism, TTL countdown, network message cache, and subnets — with BlueZ meshd setup included.

Next: Part 3 → Mesh Network Operation ← Part 1: Introduction

Leave a Reply

Your email address will not be published. Required fields are marked *