Mesh Access Module (ID 10)
Purpose
The MeshAccessModule with ModuleId 10 provides a simple way of accessing the mesh through a smartphone, it also provides a way to connect to other meshes or access other devices that support the functionality. It works over unencrypted GAP connections and uses a custom encryption on top to bypass operating system limitations of different devices.
Functionality
The MeshAccessModule works in conjunction with the MeshAccessConnection. The module provides the necessary services while the connection handles an individual connection after it was set up.
The MeshAccessConnection is able to handle standard mesh packets - including packet splitting - and will relay these packets to receivers in the mesh if security permits it (if the partner authenticated itself with the correct key during the initial encryption procedure). A MeshAccessConnection performs first a security handshake before data can be transmitted. After the handshake passes, a node may transmit information about its cluster and update this information once the cluster changes. Cf. ClusterInfoUpdate packet for more information.
There are different types of connection keys used to authenticate when connecting with a node. The keyId is then sent through the mesh depending on the packet type to authenticate the user over the mesh.
If parts of the documentation are obscure, it is advisable to take a look at the thoroughly documented relution/fruitymesh repository at the classes MeshAccessConnection.cpp and MeshAccessModule.cpp.
Connection Keys
For the different key types used, refer to the encryption types in the Specification.
Virtual Partner
Ids Because mesh packets need a sender and receiver id, it would not be possible to connect to another mesh as the nodeIds might clash. When a node connects to another device using a MeshAccessConnection, it will assign a virtualPartnerId to the other device, which is unique in the mesh. This partnerId is temporary (only while the connection lasts) and can be used to address the device during its connection.
The device does not get to know this virtual id and can operate with its own nodeId while using the connection. The node with modify received packets and will replace the devices id by the temporary id. It will also inspect packets before sending them to the other device and will translate the virtual id back into the id of the device before transmitting the packet over the MeshAccessConnection.
Tunnel Types
A node that connects to another mesh will need to specify the tunnelType, it can be on of the following:
-
MESH_ACCESS_TUNNEL_TYPE_PEER_TO_PEER = 0
: The two nodes can communicate directly, but broadcast messages will not be relayed to the mesh on either side. -
MESH_ACCESS_TUNNEL_TYPE_REMOTE_MESH = 1
: Each node can be accessed in the remote mesh from the local node, but cannot relay packets from the remote mesh to the local mesh. -
MESH_ACCESS_TUNNEL_TYPE_LOCAL_MESH = 2
: All nodes from the local mesh can send and receive data to/from the node but can’t send data to the remote mesh.
The tunnel type PEER_TO_PEER is always used when fmKeyId NODE_KEY is specified while establishing a MeshAccessConnection. It will silently override any other tunnel type (such as REMOTE_MESH or LOCAL_MESH ) when establishing the connection.
|
Authorization
Depending on the key type, certain messages are not allowed to be sent through a MeshAccessConnection. The key type can be any of the EncryptionKeys. To find out which key provides which level of access check CheckmeshAccessPacketAuthorization function. It return access level base on key type. Assuming the key is correct set of access right will be granted for the request with that key. The MeshAccessConnection passes on an incoming message to all modules. The modules can then decide if they want to whitelist or blacklist a message. It is also possible to restrict the message to be sent to the local node only. Blacklisting always has preference over whitelisting. If a message got blacklisted by a module, it cannot get whitelisted by another module. The reason behind is that someone who knows e.g. NodeKey should be able to communicate with given node but not with other nodes from that network. For that use case NetworkKey also exists that allows to communicate with whole network. But this access can be limited and granted to e.g. only 1 person.
MeshAccess Broadcast Message
The Mesh Configuration and Access Service is broadcasted by active nodes that provide mesh access to smartphones or other devices through GATT. The MeshAccessBroadcast also has a scan response that reports the serial number of the node as the "Short Local Name". This is helpful for identifying devices when using 3rd party applications.
Bytes | Type | Description |
---|---|---|
3 |
Flags |
(len, type, flags byte) |
4 |
16bit Service UUID complete list |
(len, type 0x03, 2 byte UUID 0xFE12) |
4 |
Service Data header |
(len, type 0x15, 2 byte UUID 0xFE12) |
1 |
messageType |
0x03 Mesh Configuration and Access Service (with a new service version, the messageType can be changed. |
1 |
reserved |
Must be 0 |
2 |
networkId |
Mesh network ID |
1 bit |
isEnrolled |
1 if the node is enrolled, 0 otherwise |
1 bit |
isSink |
1 if the node is communicating with a MeshGateway, 0 otherwise |
1 bit |
isZeroKeyConnectable |
1 if the node is connectable using the zero key, 0 otherwise |
1 bit |
hasFreeInConnection |
1 if the node currently has unused incoming connections, 0 otherwise |
1 bit |
interestedInConnection |
1 if asset wants to send a component_sense message, 0 otherwise |
3 bit |
reserved |
Must be 0 |
4 |
serialNumberIndex |
cf. Specification |
1 |
moduleId0 |
ID of a module that is available over the mesh access connection (0 for invalid module) |
1 |
moduleId1 |
ID of a module that is available over the mesh access connection (0 for invalid module) |
1 |
moduleId2 |
ID of a module that is available over the mesh access connection (0 for invalid module) |
Everything below is optional! Older versions of BlueRange Mesh (< 0.8.5000) may not support it. |
||
1 |
The device type of the sender. |
|
7 |
Reserved |
Mesh access emergency connect mode
It is possible to disable the normal advertising of a node if it has an active mesh connection. This can be necessary for increasing energy efficiency or free radio time. In this case the node advertises with an interval of 2 seconds and the meshAccessBroadcastMessage type is EMERGENCY_MESH_ACCESS. Through this it is possible to connect to the node and enroll it without access to the connected network.
GATT Service
The Mesh Access Service is offered under a different UUID (a 128-bit UUID) in order to seperate different services from each other.
-
Base Service UUID 00000001-ACCE-423C-93FD-0C07A0051858
-
RX Characteristic Handle: 00000002-ACCE-423C-93FD-0C07A0051858
-
TX Characteristic Handle: 00000003-ACCE-423C-93FD-0C07A0051858
After a connection is made, it is necessary to register notifications on the TX characteristic in order to receive data from the node. Do not send any data before notifications are enabled!
Encryption Handshake
To establish a connection, the following steps need to be performed:
-
Central connects to peripheral
-
Central discovers the MeshAccessService of the peripheral with its rx/tx characteristics and the cccd of the tx characteristic
-
Central enables notifications on cccd of tx characteristic
-
The peripheral will notice the enabled notification and will instantiate a MeshAccessConnection throught the ResolverConnections
-
-
Central starts handshake by requesting a nonce
-
Peripheral anwers with ANonce
-
Central answers with SNonce in an encrypted packet (enables auto encrypt/decrypt)
-
Peripheral checks encrypted packet, sends encrypted HandshakeDone packet and enables auto encrypt/decrypt
Encryption and MIC calculation uses three AES encryptions at the moment to prevent a discovered packet forgery attack under certain conditions. Future versions of the handshake may employ different encryption.
The Encryption Handshake is explained in a lot more detail with example data as part of the MeshAccessConnection documentation. |
Encryption
Once a connection is set to encrypted state - during the initial encryption handshake - all messages must be encrypted with a trailing Message Integrity Check (MIC). The data has the following format:
Bytes | Type | Name | Description |
---|---|---|---|
1…16 |
u8[] |
encryptedData |
Encrypted data that must be decrypted first, using the key determined during the handshake together with the decryptionNonce. |
4 |
u32 |
mic |
Message integrity check that protects the message against forgery or replay attacks, added at the end of the variable sized encryptedData field. |
Because an encrypted packet has only 16 bytes of payload, message splitting must account for this. A connection with an MTU of 20 will first split packets into chunks of 20 bytes (2 byte splitting overhead, 18 byte content). After encryption is activated, the chunks have a size of 16 bytes.
-
Encryption is done by generating a key stream with the encryptionNonce. A 16-byte plaintext is created with 0x00 padding and the encryptionNonce is copied into the first 8 bytes. This plaintext is encrypted using the sessionEncryptionKey to produce a key stream.
-
Next, data to be sent is XOR-ed with the key stream. The data can be from 1 to 16 bytes long.
-
The last 4 bytes of the encryptionNonce (encryptionNonce[1]) are used as a counter and are now incremented.
-
A new key stream is generated with the increased nonce as explained above.
-
This key stream is again XOR-ed with the plaintext data to be sent.
-
The resulting cipher text is encrypted once more. The first 4 bytes can now be used as a MIC.
If the first message were to be encrypted with a nonce of 1, then the mic would have been generated with a nonce of 2. The next message to be sent must by encrypted with a nonce of 3.
Session Key Generation
A session key (sessionKey) is generated by creating a 16-byte plaintext message padded with 0x00. The first two bytes (1-2) must contain the nodeId of the central device. Bytes 3-10 must contain the nonce. This plaintext is then encrypted using the chosen key. In case the key is a user key, the key must first be derived from the userBaseKey. This works by creating a 0x00 padded 16-byte cleartext, storing the keyId in the first 4 bytes of the message and encrypting the cleartext with the userBaseKey. The resulting ciphertext is the derived user key.
Terminal Commands
Connection Establishment via BLE Address
Instructs a node to build a MeshAccessConneciton to another node. The connection state will be notified back to the requester. Refer to Specification for the key types.
//Establish a connection to another device using a MeshAccessConnection
action [nodeId] ma connect [bleAddress] {keyId=FM_NODE_KEY} {keyHex=<same as Local Key>} {tunnelType=PEER_TO_PEER} {requestHandle=0}
//E.g. Connect to device 00:11:.. with node key 11:22:...
action this ma connect 00:11:22:33:44:55 1 11:22:33:44:11:22:33:44:11:22:33:44:11:22:33:44
The node responds with information about the connection state changes. In this message, the node provides the virtual partner ID that was assigned to the node connected over the MeshAccessConnection.
//Example response where nodeId 1 is now connected and handshaked with another node
{"nodeId":1,"type":"ma_conn_state","module":10,"requestHandle":0,"partnerId":2001,"state":4}
Connection Establishment via Serial Number
Instructs a node to build a MeshAccessConneciton to another node. The connection state will be notified back to the requester. Refer to Specification for the key types. If no BLE address is given, the node will first scan for broadcast messages and will try to connect after it receives a matching one. If the BLE address is given, the connection will be established without any additional scanning, which is faster. Even if the BLE address given does not match, the node will still try to scan for the serial number as a fallback.
//Establish a connection to another device using a MeshAccessConnection
action [nodeId] ma serial_connect [serial number] [keyId] [key] [nodeId_of_partner_after_connect] [initial_keep_alive] {requestHandle=0} {bleAddress=""} {forceMode=0}
//E.g. Connect to device BBBBQ with node key 00:11:22:...
action 6 ma serial_connect BBBBQ 1 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF 33012 20 13
//Same as above but with the BLE address given as well
action 6 ma serial_connect BBBBQ 1 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF 33012 20 13 AA:BB:CC:DD:EE:FF
//Same as above but with a hint to the node that the connection should be established faster and more reliably (forceMode)
action 6 ma serial_connect BBBBQ 1 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF 33012 20 13 AA:BB:CC:DD:EE:FF 1
serial number
is the serial number of the other device that the connection will be established to.
keyId
should start at 1.
nodeId_of_partner_after_connect
must be inside the range of organization wide unique nodeIds [33000, 57999].
initial_keep_alive
is merely a suggestion to the node. There are a lot of cases where a connection can also be prematurely disconnected.
forceMode
is merely a suggestion to the node. The node is supposed to use parameters for the connection which lead to faster connections and connect more reliably.
Once the connection was established or cancelled, it is answered with the following JSON:
{"type":"serial_connect_response","module":10,"nodeId":6,"requestHandle":13,"code":0,"partnerId":33012}
where code can have the following values:
Value | Name | Description |
---|---|---|
0 |
SUCCESS |
The Connection was successfully opened. |
1 |
TIMEOUT_REACHED |
It was impossible to build a connection due to a timeout. |
2 |
OVERWRITTEN_BY_OTHER_REQUEST |
A node never tries to build a connection to more than one node. If the connection is currently in the process of trying to connect to a serial number, but is then interfered by another serial connect message, the first connect attempt is cancelled, notifying the requester with this error code. NOTE: This code is not sent if both serial connect messages contain the same values. In such a case, the only thing that changes is that the timeout of the connection is replenished. |
If the connection state changes, the sender of this message is informed about the new state with this message. In this message, the node provides the virtual partner ID that was assigned to the node connected over the MeshAccessConnection.
//Example response where nodeId 1 is now connected and handshaked with another node
{"nodeId":1,"type":"ma_conn_state","module":10,"requestHandle":0,"partnerId":2001,"state":4}
Disconnection
Disconnect from a device if it is connected via a MeshAccessConnection to that node.
//Disconnect a previously connected MeshAccessConnection
action [nodeId] ma disconnect [bleAddress] {requestHandle}
//E.g. disconnect device 00:11:... if connected to this node
action this ma disconnect 00:11:22:33:44:55
Messages
Message Types
#define MESSAGE_TYPE_ENCRYPT_CUSTOM_START 25
#define MESSAGE_TYPE_ENCRYPT_CUSTOM_ANONCE 26
#define MESSAGE_TYPE_ENCRYPT_CUSTOM_SNONCE 27
#define MESSAGE_TYPE_ENCRYPT_CUSTOM_DONE 28
Start Handshake
The central starts the encryption process by sending the following unencrypted packet:
Bytes | Type | Name | Description |
---|---|---|---|
1 |
u8 |
messageType |
|
2 |
u16 |
senderId |
Either a nodeId in the own mesh, or in case of a
smartphone, this must be |
2 |
u16 |
receiverId |
Set to 0 or if known, the ID of the partner |
1 |
u8 |
version |
Set to 1 |
4 |
u32 |
keyId |
Set to the keyId that should be used for this connection |
2 bit |
u8:2 |
tunnelType |
Tunnel type that should be used for this connection, cf. TunnelType. The invalid type must not be sent. E.g., if a Smartphone connects to a mesh, it should use |
6 bit |
u8:6 |
reserved |
Handshake ANonce
The peripheral will generate a random nonce with a length of 8 bytes and answer with an unencrypted packet. The peripheral can also start to generate the session decryption key at this time (cf. Session Key Generation generation chapter). After sending this packet, the peripheral only accepts encrypted packets.
Bytes | Type | Name | Description |
---|---|---|---|
1 |
u8 |
messageType |
|
2 |
u16 |
senderId |
nodeId of the peripheral in the mesh |
2 |
u16 |
receiverId |
Replay of the central id. |
4 |
u32 |
anonce[0] |
First part of the ANonce |
4 |
u32 |
anonce[1] |
Second part of the Anonce |
Handshake SNonce
The central must now generate a random 8 byte nonce as well. It is then able to calculate both session keys, the key for encryption and the key for decryption. It will then send the following packet, but in encrypted form. The ANonce is used to generate the session encryption key for sending packets and the SNonce is used to calculate the session decryption key for receiving packets.
Bytes | Type | Name | Description |
---|---|---|---|
1 |
u8 |
messageType |
|
2 |
u16 |
senderId |
Sender ID |
2 |
u16 |
receiverId |
Receiver ID |
4 |
u32 |
snonce[0] |
First part of the SNonce |
4 |
u32 |
snonce[1] |
Second part of the SNonce |
Handshake Done
The peripheral answers with the final handshake packet to confirm that the handshake was completed successfully. This packet is encrypted before transmission.
Bytes | Type | Name | Description |
---|---|---|---|
1 |
u8 |
messageType |
|
2 |
u16 |
senderId |
Sender ID |
2 |
u16 |
receiverId |
Receiver ID |
1 |
u8 |
status |
0: OK |
Dead Data
If a connection receives undecryptable data it informs the connection partner by sending this unencrypted message and resets the handshake. After this happens, the connection does not process any data other than a new handshake. To send more data, the handshake has to be completed successfully. This is done in case the Nonces run out of sync between the connection partners. This can happen if the stack, e.g. of a mobile device silently drops packets and does not inform the application.
Bytes | Type | Name | Description |
---|---|---|---|
1 |
u8 |
messageType |
|
2 |
u16 |
senderId |
Sender ID |
2 |
u16 |
receiverId |
Receiver ID |
8 |
u8[8] |
magic number |
0xDE:AD:DA:DA:00:FF:77:33 |