Bluetooth Mesh: How Messages Travel Through the Stack

 

Bluetooth Mesh: How Messages Travel Through the Stack

A practical walkthrough of Mesh message flow — from Bearer to Application layer — with BlueZ examples

📡
3 Layer Groups
Bearer / Network / Upper
🔄
SAR + Friend
Segmentation & Queuing
🔐
Dual Encryption
Network + Application
📦
Foundation Models
Config & Management

What is Bluetooth Mesh?

Bluetooth Mesh is a networking standard built on top of Bluetooth LE. Instead of one device talking to one other device (like classic BLE), mesh lets many devices talk to many devices — think smart lighting, industrial sensors, or building automation.

The interesting part is how a single message gets from your phone app all the way to a smart bulb on the other side of the building. It travels through a strict set of layers, each one doing a specific job. That’s what this tutorial covers.

Keywords:

Bluetooth Mesh Message Flow Bearer Layer Network Layer Upper Transport Access Layer Foundation Models SAR Segmentation Friend Node TTL Replay Protection BlueZ

1. The Big Picture — Layers in Bluetooth Mesh

Before diving into message flow, you need to know the layers. Think of them like floors in a building — a message enters from the ground floor (Bearer) and walks up to the top floor (Application/Model).

Bluetooth Mesh Layer Stack
Application / Model Layer

Your app logic — lights, sensors, switches
Foundation Model Layer

Network config & management messages
Access Layer

Payload formatting, AppKey, Opcode routing
Upper Transport Layer

Transport encryption, replay protection, Friend/Control
Lower Transport Layer

SAR (segmentation and reassembly), ACK/NAK
Network Layer

Network encryption, TTL, relay, routing
Bearer Layer

Advertising Bearer (ADV) & GATT Bearer

When a message is received, it flows bottom-up — from Bearer → Network → Transport → Access → Model.
When a message is sent, it flows top-down — from Model → Access → Transport → Network → Bearer.

2. Lower Layers — How a Message Enters the Node

2.1 Bearer Layer

The Bearer layer is the physical entry point. In practice, there are two bearers:

  • Advertising Bearer (ADV) — uses standard BLE advertising packets. Most mesh nodes use this.
  • GATT Bearer — used by phones or tablets that can’t do raw advertising but can connect via GATT (like a provisioning app).

A packet arrives at the bearer and passes through an interface input filter. The filter decides: is this a Mesh Message, a Mesh Beacon, or a Bearer Control message? Each goes on a different path.

2.2 Network Layer

Once past the bearer, the network layer takes over. Here’s what happens step by step:

Network Layer — Receive Flow
📥 Packet arrives from Bearer
🔐 Network Authentication + Decryption (NetKey)
❓ Is this node the destination (Local)?
YES — Local
Pass up to Lower Transport
NO — Relay
Check TTL ≥ 2 → Decrement TTL → Re-encrypt → Transmit out

TTL (Time To Live) is the mesh equivalent of IP’s hop count. Every relay node decrements TTL by 1. When TTL drops below 2, the packet is dropped — this prevents infinite forwarding loops in the mesh.

2.3 Lower Transport Layer — SAR

Mesh PDUs are small (max ~15 bytes of application data in a single segment). If your message is bigger, the Lower Transport layer splits it using SAR — Segmentation and Reassembly.

SAR: Sending a large message
Upper Transport PDU
(e.g. 40 bytes)
Segment 0 (SEG=1, SeqZero, SegO=0, SegN=2)
Segment 1 (SEG=1, SeqZero, SegO=1, SegN=2)
Segment 2 (SEG=1, SeqZero, SegO=2, SegN=2)
3 Network PDUs
transmitted over air

Receiver reassembles all segments. ACK/NAK sent by Lower Transport to confirm.

If a node has a Friend node, messages destined for a low-power node (LPN) get queued in the Friend’s queue instead of being broadcast immediately. The LPN wakes up, sends a Friend Poll, and the Friend node delivers queued messages.

3. Upper Layers — Decryption, Access, and Models

3.1 Upper Transport Layer

After SAR reassembly, the Upper Transport layer does two important things:

  • Transport Decryption — uses the AppKey (for application messages) or the DevKey (for configuration messages) to decrypt the payload.
  • Replay Protection — checks the sequence number. If this sequence number was seen before, the message is dropped. This blocks replay attacks.

Upper Transport: Access vs Control routing
Reassembled PDU from Lower Transport
❓ Access message or Control message?
ACCESS
Decrypt with AppKey/DevKey → Pass to Access Layer
CONTROL
Upper Transport Control Behaviors (Heartbeat, Friend management)

3.2 Access Layer

The access layer is the gatekeeper before your application model sees data. Three checks happen here:

🛡️

Replay Protection

Duplicate/old messages are dropped using SeqNum + IV Index

🔍

Payload Parsing

Opcode is read to determine which model should handle this message

🔑

Permissions Check

Does the receiving model have the matching AppKey bound? If not, drop.

3.3 Node-level vs Element-level Access Processing

A mesh node can have multiple elements (think of each element as an addressable endpoint — a single bulb in a fixture). Here’s how the access layer routes to the right element:

Node-level Access Layer Routing
DST = Unicast?
Message addressed to a specific element address
→ Deliver to Destination Element only
DST = all-nodes?
Broadcast address 0xFFFF
→ Deliver to Primary Element
DST on Subscription List?
Group or virtual address
→ Deliver to all matching Elements
or Drop if no match

3.4 Foundation Model vs Application Model

Once the opcode is parsed, the message goes to either:

  • Foundation Model — handles network configuration messages (add/remove AppKeys, set subscriptions, set publication). These are sent using the DevKey, not AppKey.
  • Application Model — your actual application logic (Generic OnOff, Light Lightness, Sensor, Vendor models, etc.).

4. Foundation Models — Configuring the Mesh Network

Foundation Models are built-in models that every mesh node must support. They are the remote control for the network itself. A Provisioner (like a phone app) uses them to set up a node after provisioning.

4.1 Two Foundation Models

⚙️ Configuration Server
  • Stores AppKeys, NetKeys
  • Manages model subscriptions
  • Manages publication addresses
  • Controls relay, proxy, friend features
  • Lives on every node
📱 Configuration Client
  • Sends config commands to servers
  • Lives on the Provisioner (phone/gateway)
  • Uses DevKey for all messages
  • Gets/sets state on remote nodes
  • Reads Composition Data

4.2 Log Field Transformation

Foundation models often use small 1-byte fields to represent large 2-byte values. They use a logarithmic compression: the 1-byte log field stores the value n such that 2(n-1) ≤ actual value.

For example, a Publish Period of 320ms (0x0140) can’t fit in one byte. But log field value 0x09 means “the value is somewhere between 0x0100 and 0x01FF” — good enough for period encoding.

Log Field (1 byte) Actual 2-byte Value Range Human Meaning
0x01 0x0001 Exactly 1
0x02 0x0002 – 0x0003 2 or 3
0x05 0x0010 – 0x001F 16 to 31
0x09 0x0100 – 0x01FF 256 to 511 ms (e.g., publish period)
0x10 0x8000 – 0xFFFF Maximum range

5. BlueZ Code Snippets — Seeing Mesh in Action

BlueZ includes a mesh daemon (bluetooth-meshd) and a test tool (mesh-cfgclient). Here’s how to observe and interact with mesh message flow on Linux.

5.1 Start the Mesh Daemon

# Start bluetoothd first (if not already running)
sudo bluetoothd -d &

# Start the mesh daemon with debug output
sudo bluetooth-meshd --debug

# You will see lines like:
# mesh/net.c: Network authenticate & decrypt
# mesh/transport.c: Upper transport decrypt
# mesh/access.c: Replay protection check

5.2 Provision a Node using mesh-cfgclient

# Run the interactive config client
mesh-cfgclient

# Inside the client shell:

# Discover unprovisioned beacons
discover-unprovisioned on

# Start provisioning (replace UUID with your device's UUID)
provision 0011223344556677889900aabbccddeeff

# After provisioning completes, you get a unicast address (e.g., 0x0002)
# Now you can configure the node's Foundation Model (Config Server)

5.3 Add AppKey to a Node (Foundation Model: Config AppKey Add)

# Still inside mesh-cfgclient shell
# Target node address 0x0002, NetKey index 0, AppKey index 0
appkey-add 0x0002 0 0

# This sends a "Config AppKey Add" message to the Config Server
# on the target node using that node's DevKey for encryption.
# On success you get: AppKey Add Status: Success

5.4 Bind AppKey to a Model on the Node

# Bind AppKey 0 to the Generic OnOff Server model (Model ID: 0x1000)
# on element 0 of node 0x0002
bind 0x0002 0 0x1000 0

# This sends Config Model App Bind message
# On success: Model App Bind Status: Success

5.5 Set a Subscription (Group Address)

# Subscribe element 0, model 0x1000 on node 0x0002
# to group address 0xC000
sub-add 0x0002 0 0xC000 0x1000

# Now if you send a Generic OnOff Set to group 0xC000,
# this node will receive and process it.
# This is how you control multiple lights with one command.

5.6 Watching TTL and Relay in Action (btmon)

# In a separate terminal, run btmon to see raw HCI/ADV packets
sudo btmon

# Look for Mesh ADV packets. You will see the same PDU
# appearing multiple times as relay nodes forward it,
# each time with TTL decremented by 1.

# Example output snippet:
# HCI Event: LE Meta (0x3e) plen 43
#   LE Advertising Report (0x02)
#   Type: Non connectable undirected (0x03)
#   Data length: 29
#   Mesh Message (type 0x2a)
#   ...

5.7 Python: Send a Generic OnOff Set via BlueZ D-Bus Mesh API

import dbus

# Connect to the BlueZ mesh application over D-Bus
bus = dbus.SystemBus()

# Get the mesh network manager
mesh_mgr = dbus.Interface(
    bus.get_object("org.bluez.mesh", "/org/bluez/mesh"),
    "org.bluez.mesh.Network1"
)

# Send message to group address 0xC000 (all lights)
# AppKey index 0, TTL 5
# Generic OnOff Set: Opcode 0x8202, OnOff=1 (ON), TID=0x01
app_key_index = 0
destination   = 0xC000
ttl           = 5
payload       = bytes([0x82, 0x02, 0x01, 0x01])  # GenOnOff Set ON

# Call Send on your model's element interface
element = dbus.Interface(
    bus.get_object("org.bluez.mesh", "/my/app/element0"),
    "org.bluez.mesh.Element1"
)
element.Send(destination, app_key_index, payload)

print("Generic OnOff SET (ON) sent to group 0xC000")

6. Quick Reference — Q&A and Debug Tips

Question Answer
What key encrypts Foundation Model messages? DevKey — unique per node, used only for config traffic
What key encrypts application model messages? AppKey — shared among a group of nodes in an application
What happens when TTL = 1? The node processes it locally but does NOT relay it further
What stops a message looping forever in the mesh? TTL decrement + Replay Protection (SeqNum cache)
Where does SAR happen? Lower Transport Layer — splits/reassembles large PDUs
What is the Friend Node used for? Queues messages for a Low Power Node (LPN) so the LPN can sleep and poll when ready
What does “Replay Protection” mean? Caching the last SeqNum seen from each SRC address — if the same or older SeqNum arrives again, drop it
Why does mesh use two separate keys (NetKey and AppKey)? NetKey lets relay nodes forward packets without reading application data. AppKey is only known to nodes that need the actual content. Separation of roles = better security.

Continue Your Bluetooth Learning

Explore our full Bluetooth Classic & BLE series on EmbeddedPathashala

Bluetooth Classic Series BLE Deep Dive

Leave a Reply

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