@ -1,26 +1,42 @@
# MeshCore Device Communication Protocol Guide
# Companion Protocol
This document provides a comprehensive guide for communicating with MeshCore devices over Bluetooth Low Energy (BLE). It is platform-agnostic and can be used for Android, iOS, Python, JavaScript, or any other platform that supports BLE.
- **Last Updated** : 2026-01-03
- **Protocol Version** : Companion Firmware v1.12.0+
## ⚠️ Important Security Note
> NOTE: This document is still in development. Some information may be inaccurate.
**All secrets, hashes, and cryptographic values shown in this guide are EXAMPLE VALUES ONLY and are NOT real secrets.**
This document provides a comprehensive guide for communicating with MeshCore devices over Bluetooth Low Energy (BLE).
- The secret `9b647d242d6e1c5883fde0c5cf5c4c5e` used in examples is a made-up example value
It is platform-agnostic and can be used for Android, iOS, Python, JavaScript, or any other platform that supports BLE.
- All hex values, public keys, and hashes in examples are for demonstration purposes only
- **Never use example secrets in production** - always generate new cryptographically secure random secrets
## Official Libraries
- This guide is for protocol documentation only - implement proper security practices in your actual implementation
Please see the following repos for existing MeshCore Companion Protocol libraries.
- JavaScript: [https://github.com/meshcore-dev/meshcore.js ](https://github.com/meshcore-dev/meshcore.js )
- Python: [https://github.com/meshcore-dev/meshcore_py ](https://github.com/meshcore-dev/meshcore_py )
## Important Security Note
All secrets, hashes, and cryptographic values shown in this guide are example values only.
- All hex values, public keys and hashes are for demonstration purposes only
- Never use example secrets in production
- Always generate new cryptographically secure random secrets
- Please implement proper security practices in your implementation
- This guide is for protocol documentation only
## Table of Contents
## Table of Contents
1. [BLE Connection ](#ble-connection )
1. [BLE Connection ](#ble-connection )
2. [Protocol Overview ](#protocol-overview )
2. [Packet Structure ](#packet-structure )
3. [Commands ](#commands )
3. [Commands ](#commands )
4. [Channel Management ](#channel-management )
4. [Channel Management ](#channel-management )
5. [Secret Generation and QR Codes ](#secret-generation-and-qr-codes )
5. [Message Handling ](#message-handling )
6. [Message Handling ](#message-handling )
6. [Response Parsing ](#response-parsing )
7. [Response Parsing ](#response-parsing )
7. [Example Implementation Flow ](#example-implementation-flow )
8. [Example Implementation Flow ](#example-implementation-flow )
8. [Best Practices ](#best-practices )
9. [Troubleshooting ](#troubleshooting )
---
---
@ -28,17 +44,17 @@ This document provides a comprehensive guide for communicating with MeshCore dev
### Service and Characteristics
### Service and Characteristics
MeshCore devices expose a BLE service with the following UUIDs:
MeshCore Companion devices expose a BLE service with the following UUIDs:
- **Service UUID** : `0000ff00-0000-1000-8000-00805f9b34fb `
- **Service UUID** : `6E400001-B5A3-F393-E0A9-E50E24DCCA9E `
- **RX Characteristic** (Device → Client): `0000ff01-0000-1000-8000-00805f9b34fb `
- **RX Characteristic** (App → Firmware): `6E400002-B5A3-F393-E0A9-E50E24DCCA9E `
- **TX Characteristic** (Client → Device): `0000ff02-0000-1000-8000-00805f9b34fb `
- **TX Characteristic** (Firmware → App): `6E400003-B5A3-F393-E0A9-E50E24DCCA9E `
### Connection Steps
### Connection Steps
1. **Scan for Devices**
1. **Scan for Devices**
- Scan for BLE devices advertising the MeshCore s ervice UUID
- Scan for BLE devices advertising the MeshCore S ervice UUID
- Filter by device name (typically contains "MeshCore" or similar )
- Optionally filter by device name (typically contains "MeshCore" prefix )
- Note the device MAC address for reconnection
- Note the device MAC address for reconnection
2. **Connect to GATT**
2. **Connect to GATT**
@ -46,41 +62,41 @@ MeshCore devices expose a BLE service with the following UUIDs:
- Wait for connection to be established
- Wait for connection to be established
3. **Discover Services and Characteristics**
3. **Discover Services and Characteristics**
- Discover the service with UUID `0000ff00-0000-1000-8000-00805f9b34fb`
- Discover the service with UUID `6E400001-B5A3-F393-E0A9-E50E24DCCA9E`
- Discover RX characteristic (`0000ff01-...`) for receiving data
- Discover the RX characteristic `6E400002-B5A3-F393-E0A9-E50E24DCCA9E`
- Discover TX characteristic (`0000ff02-...`) for sending commands
- Your app writes to this, the firmware reads from this
- Discover the TX characteristic `6E400003-B5A3-F393-E0A9-E50E24DCCA9E`
- The firmware writes to this, your app reads from this
4. **Enable Notifications**
4. **Enable Notifications**
- Subscribe to notifications on the RX characteristic
- Subscribe to notifications on the TX characteristic to receive data from the firmware
- Enable notifications/indications to receive data from the device
- On some platforms, you may need to write to a descriptor (e.g., `0x2902` ) with value `0x01` or `0x02`
5. **Send Initial Commands**
- Send `CMD_APP_START` to identify your app to firmware and get radio settings
5. **Send AppStart Command**
- Send `CMD_DEVICE_QEURY` to fetch device info and negotiate supported protocol versions
- Send the app start command (see [Commands ](#commands )) to initialize communication
- Send `CMD_SET_DEVICE_TIME` to set the firmware clock
- Wait for OK response before sending other commands
- Send `CMD_GET_CONTACTS` to fetch all contacts
- Send `CMD_GET_CHANNEL` multiple times to fetch all channel slots
### Connection State Management
- Send `CMD_SYNC_NEXT_MESSAGE` to fetch the next message stored in firmware
- Setup listeners for push codes, such as `PUSH_CODE_MSG_WAITING` or `PUSH_CODE_ADVERT`
- **Disconnected** : No connection established
- See [Commands ](#commands ) section for information on other commands
- **Connecting** : Connection attempt in progress
- **Connected** : GATT connection established, ready for commands
- **Error** : Connection failed or lost
**Note**: MeshCore devices may disconnect after periods of inactivity. Implement auto-reconnect logic with exponential backoff.
**Note**: MeshCore devices may disconnect after periods of inactivity. Implement auto-reconnect logic with exponential backoff.
### BLE Write Type
### BLE Write Type
When writing commands to the T X characteristic, specify the write type:
When writing commands to the R X characteristic, specify the write type:
- **Write with Response** (default): Waits for acknowledgment from device
- **Write with Response** (default): Waits for acknowledgment from device
- **Write without Response** : Faster but no acknowledgment
- **Write without Response** : Faster but no acknowledgment
**Platform-specific**:
**Platform-specific**:
- **Android** : Use `BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT` or `WRITE_TYPE_NO_RESPONSE`
- **Android** : Use `BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT` or `WRITE_TYPE_NO_RESPONSE`
- **iOS** : Use `CBCharacteristicWriteType.withResponse` or `.withoutResponse`
- **iOS** : Use `CBCharacteristicWriteType.withResponse` or `.withoutResponse`
- **Python (bleak)** : Use `write_gatt_char()` with `response=True` or `False`
- **Python (bleak)** : Use `write_gatt_char()` with `response=True` or `False`
**Recommendation**: Use write with response for reliability, especially for critical commands like `SET_CHANNEL` .
**Recommendation**: Use write with response for reliability.
### MTU (Maximum Transmission Unit)
### MTU (Maximum Transmission Unit)
@ -91,118 +107,48 @@ The default BLE MTU is 23 bytes (20 bytes payload). For larger commands like `SE
- iOS: `peripheral.maximumWriteValueLength(for:)`
- iOS: `peripheral.maximumWriteValueLength(for:)`
- Python (bleak): MTU is negotiated automatically
- Python (bleak): MTU is negotiated automatically
2. **Handle Chunking** : If MTU is small, commands may be split automatically by the BLE stack
### Command Sequencing
- Ensure all chunks are sent before waiting for response
- Responses may also arrive in chunks - buffer until complete
### Command Sequencing and Timing
**Critical**: Commands must be sent in the correct sequence:
**Critical**: Commands must be sent in the correct sequence:
1. **After Connection** :
1. **After Connection** :
- Wait for GATT connection established
- Wait for BLE connection to be established
- Wait for services/characteristics discovered
- Wait for services/characteristics to be discovered
- Wait for notifications enabled (descriptor write complete)
- Wait for notifications to be enabled
- **Wait 200-1000ms** for device to be ready (some devices need initialization time)
- Now you can safely send commands to the firmware
- Send `APP_START` command
- **Wait for `PACKET_OK` response** before sending any other commands
2. **Command-Response Matching** :
2. **Command-Response Matching** :
- Send one command at a time
- Send one command at a time
- Wait for response before sending next command
- Wait for a response before sending another command
- Use timeout (typically 5 seconds)
- Use a timeout (typically 5 seconds)
- Match response to command by:
- Match response to command by type (e.g: `CMD_GET_CHANNEL` → `RESP_CODE_CHANNEL_INFO` )
- Command type (e.g., `GET_CHANNEL` → `PACKET_CHANNEL_INFO` )
- Sequence number (if implemented)
- First-in-first-out queue
3. **Timing Considerations** :
- Minimum delay between commands: 50-100ms
- After `APP_START` : Wait 200-500ms before next command
- After `SET_CHANNEL` : Wait 500-1000ms for channel to be created
- After enabling notifications: Wait 200ms before sending commands
**Example Flow**:
```python
# 1. Connect and discover
await connect_to_device(device)
await discover_services()
await enable_notifications()
await asyncio.sleep(0.2) # Wait for device ready
# 2. Send AppStart
send_command(build_app_start())
response = await wait_for_response(PACKET_OK, timeout=5.0)
if response.type != PACKET_OK:
raise Exception("AppStart failed")
# 3. Now safe to send other commands
await asyncio.sleep(0.1) # Small delay between commands
send_command(build_device_query())
response = await wait_for_response(PACKET_DEVICE_INFO, timeout=5.0)
```
### Command Queue Management
### Command Queue Management
For reliable operation, implement a command queue:
For reliable operation, implement a command queue.
**Queue Structure**:
1. **Queue Structure** :
- Maintain a queue of pending commands
- Maintain a queue of pending commands
- Track which command is currently waiting for response
- Track which command is currently waiting for a response
- Only send next command after receiving response or timeout
- Only send next command after receiving response or timeout
2. **Implementation** :
**Error Handling**:
```python
class CommandQueue:
def __init__ (self):
self.queue = []
self.waiting_for_response = False
self.current_command = None
async def send_command(self, command, expected_response_type, timeout=5.0):
if self.waiting_for_response:
# Queue the command
self.queue.append((command, expected_response_type, timeout))
return
self.waiting_for_response = True
self.current_command = (command, expected_response_type, timeout)
# Send command
await write_to_tx_characteristic(command)
# Wait for response
response = await wait_for_response(expected_response_type, timeout)
self.waiting_for_response = False
- On timeout, clear current command, process next in queue
self.current_command = None
- On error, log error, clear current command, process next
# Process next queued command
if self.queue:
next_cmd, next_type, next_timeout = self.queue.pop(0)
await self.send_command(next_cmd, next_type, next_timeout)
return response
```
3. **Error Handling** :
- On timeout: Clear current command, process next in queue
- On error: Log error, clear current command, process next
- Don't block queue on single command failure
---
---
## Protocol Overview
## Packet Structure
The MeshCore protocol uses a binary format with the following structure:
The MeshCore protocol uses a binary format with the following structure:
- **Commands** : Sent from client to device via T X characteristic
- **Commands** : Sent from app to firmware via RX characteristic
- **Responses** : Received from device via RX characteristic (notifications)
- **Responses** : Received from firmware via TX characteristic notifications
- **All multi-byte integers** : Little-endian byte order
- **All multi-byte integers** : Little-endian byte order (except CayenneLPP which is Big-endian)
- **All strings** : UTF-8 encoding
- **All strings** : UTF-8 encoding
### Packet Structure
Most packets follow this format:
Most packets follow this format:
```
```
[Packet Type (1 byte)] [Data (variable length)]
[Packet Type (1 byte)] [Data (variable length)]
@ -283,7 +229,7 @@ Byte 1: Channel Index (0-7)
Byte 0: 0x20
Byte 0: 0x20
Byte 1: Channel Index (0-7)
Byte 1: Channel Index (0-7)
Bytes 2-33: Channel Name (32 bytes, UTF-8, null-padded)
Bytes 2-33: Channel Name (32 bytes, UTF-8, null-padded)
Bytes 34-65: Secret (32 bytes, see [Secret Generation ](#secret-generation ) )
Bytes 34-65: Secret (32 bytes)
```
```
**Total Length**: 66 bytes
**Total Length**: 66 bytes
@ -298,7 +244,7 @@ Bytes 34-65: Secret (32 bytes, see [Secret Generation](#secret-generation))
- Padded with null bytes (0x00) if shorter
- Padded with null bytes (0x00) if shorter
**Secret Field** (32 bytes):
**Secret Field** (32 bytes):
- For **private channels** : 32-byte secret (see [Secret Generation ](#secret-generation ))
- For **private channels** : 32-byte secret
- For **public channels** : All zeros (0x00)
- For **public channels** : All zeros (0x00)
**Example** (create channel "YourChannelName" at index 1 with secret):
**Example** (create channel "YourChannelName" at index 1 with secret):
@ -380,171 +326,34 @@ Byte 0: 0x14
### Channel Types
### Channel Types
1. **Public Channels** (Index 0)
1. **Public Channel**
- No secret required
- Uses a publicly known 16-byte key: `8b3387e9c5cdea6ac9e5edbaa115cd72`
- Anyone with the channel name can join
- Anyone can join this channel, messages should be considered public
- Use for open communication
- Used as the default public group chat
2. **Hashtag Channels**
2. **Private Channels** (Indices 1-7)
- Uses a secret key derived from the channel name
- Require a 16-byte secret
- It is the first 16 bytes of `sha256("#test")`
- Secret is expanded to 32 bytes using SHA-512 (see [Secret Generation ](#secret-generation ))
- For example hashtag channel `#test` has the key: `9cd8fcf22a47333b591d96a2b848b73f`
- Only devices with the secret can access the channel
- Used as a topic based public group chat, separate from the default public channel
3. **Private Channels**
- Uses a randomly generated 16-byte secret key
- Messages should be considered private between those that know the secret
- Users should keep the key secret, and only share with those you want to communicate with
- Used as a secure private group chat
### Channel Lifecycle
### Channel Lifecycle
1. **Create Channel** :
1. **Set Channel** :
- Choose an available index (1-7 for private channels)
- Fetch all channel slots, and find one with empty name and all-zero secret
- Generate or provide a 16-byte secret
- Generate or provide a 16-byte secret
- Send `SET_CHANNEL` command with name and secret
- Send `CMD_SET_CHANNEL` with name and secret
- **Store the secret locally** (device does not return it)
2. **Get Channel** :
- Send `CMD_GET_CHANNEL` with channel index
2. **Query Channel** :
- Parse `RESP_CODE_CHANNEL_INFO` response
- Send `GET_CHANNEL` command with channel index
- Parse `PACKET_CHANNEL_INFO` response
- Note: Secret will be null in response (security feature)
3. **Delete Channel** :
3. **Delete Channel** :
- Send `SET_CHANNEL` command with empty name and all-zero secret
- Send `CMD_SET_CHANNEL` with empty name and all-zero secret
- Or overwrite with a new channel
- Or overwrite with a new channel
### Channel Index Management
- **Index 0** : Reserved for public channels
- **Indices 1-7** : Available for private channels
- If a channel exists at index 0 but should be private, migrate it to index 1-7
---
## Secret Generation and QR Codes
### Secret Generation
For private channels, generate a cryptographically secure 16-byte secret:
**Pseudocode**:
```python
import secrets
# Generate 16 random bytes
secret_bytes = secrets.token_bytes(16)
# Convert to hex string for storage/sharing
secret_hex = secret_bytes.hex() # 32 hex characters
```
**Important**: Use a cryptographically secure random number generator (CSPRNG). Do not use predictable values.
### Secret Expansion
When sending the secret to the device via `SET_CHANNEL` , the 16-byte secret must be expanded to 32 bytes:
**Process**:
1. Take the 16-byte secret
2. Compute SHA-512 hash: `hash = SHA-512(secret)`
3. Use the first 32 bytes of the hash as the secret field in the command
**Pseudocode**:
```python
import hashlib
secret_16_bytes = ... # Your 16-byte secret
sha512_hash = hashlib.sha512(secret_16_bytes).digest() # 64 bytes
secret_32_bytes = sha512_hash[:32] # First 32 bytes
```
This matches MeshCore's ED25519 key expansion method.
### QR Code Format
QR codes for sharing channel secrets use the following format:
**URL Scheme**:
```
meshcore://channel/add?name=< ChannelName > & secret=< 32HexChars >
```
**Parameters**:
- `name` : Channel name (URL-encoded if needed)
- `secret` : 32-character hexadecimal representation of the 16-byte secret
**Example** (using example secret - NOT a real secret):
```
meshcore://channel/add?name=YourChannelName& secret=9b647d242d6e1c5883fde0c5cf5c4c5e
```
**Alternative Formats** (for backward compatibility):
1. **JSON Format** :
```json
{
"name": "YourChannelName",
"secret": "9b647d242d6e1c5883fde0c5cf5c4c5e"
}
```
*Note: The secret value above is an example only - generate your own secure random secret.*
2. **Plain Hex** (32 hex characters):
```
9b647d242d6e1c5883fde0c5cf5c4c5e
```
*Note: This is an example hex value - always generate your own cryptographically secure random secret.*
### QR Code Generation
**Steps**:
1. Generate or use existing 16-byte secret
2. Convert to 32-character hex string (lowercase)
3. URL-encode the channel name
4. Construct the `meshcore://` URL
5. Generate QR code from the URL string
**Example** (Python with `qrcode` library):
```python
import qrcode
from urllib.parse import quote
import secrets
channel_name = "YourChannelName"
# Generate a real cryptographically secure secret (NOT the example value)
secret_bytes = secrets.token_bytes(16)
secret_hex = secret_bytes.hex() # This will be a different value each time
# Example value shown in documentation: "9b647d242d6e1c5883fde0c5cf5c4c5e"
# DO NOT use the example value - always generate your own!
url = f"meshcore://channel/add?name={quote(channel_name)}& secret={secret_hex}"
qr = qrcode.QRCode(version=1, box_size=10, border=5)
qr.add_data(url)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
img.save("channel_qr.png")
```
### QR Code Scanning
When scanning a QR code:
1. **Parse URL Format** :
- Extract `name` and `secret` query parameters
- Validate secret is 32 hex characters
2. **Parse JSON Format** :
- Parse JSON object
- Extract `name` and `secret` fields
3. **Parse Plain Hex** :
- Extract only hex characters (0-9, a-f, A-F)
- Validate length is 32 characters
- Convert to lowercase
4. **Validate Secret** :
- Must be exactly 32 hex characters (16 bytes)
- Convert hex string to bytes
5. **Create Channel** :
- Use extracted name and secret
- Send `SET_CHANNEL` command
---
---
## Message Handling
## Message Handling
@ -694,7 +503,7 @@ Use the `SEND_CHANNEL_MESSAGE` command (see [Commands](#commands)).
### Packet Types
### Packet Types
| Value | Name | Description |
| Value | Name | Description |
|-------|------|-------------|
|-------|---------------------------- |------------------ -------------|
| 0x00 | PACKET_OK | Command succeeded |
| 0x00 | PACKET_OK | Command succeeded |
| 0x01 | PACKET_ERROR | Command failed |
| 0x01 | PACKET_ERROR | Command failed |
| 0x02 | PACKET_CONTACT_START | Start of contact list |
| 0x02 | PACKET_CONTACT_START | Start of contact list |
@ -1081,33 +890,6 @@ def on_notification_received(data):
send_command(tx_char, build_get_message())
send_command(tx_char, build_get_message())
```
```
### QR Code Sharing
```python
import secrets
from urllib.parse import quote
# 1. Generate QR code data
channel_name = "YourChannelName"
# Generate a real secret (NOT the example value from documentation)
secret_bytes = secrets.token_bytes(16)
secret_hex = secret_bytes.hex()
# Example value in documentation: "9b647d242d6e1c5883fde0c5cf5c4c5e"
# DO NOT use example values - always generate your own secure random secrets!
url = f"meshcore://channel/add?name={quote(channel_name)}& secret={secret_hex}"
# 2. Generate QR code image
qr = qrcode.QRCode(version=1, box_size=10, border=5)
qr.add_data(url)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
# 3. Display or save QR code
img.save("channel_qr.png")
```
---
---
## Best Practices
## Best Practices
@ -1121,81 +903,37 @@ img.save("channel_qr.png")
- Always use cryptographically secure random number generators
- Always use cryptographically secure random number generators
- Store secrets securely (encrypted storage)
- Store secrets securely (encrypted storage)
- Never log or transmit secrets in plain text
- Never log or transmit secrets in plain text
- Device does not return secrets - you must store them locally
3. **Message Handling** :
3. **Message Handling** :
- Poll `GET_MESSAGE` periodically or when `PACKET_MESSAGES_WAITING` is received
- Send `CMD_SYNC_NEXT_MESSAGE` when `PUSH_CODE_MSG_WAITING` is received
- Handle message chunking for long messages (>133 characters)
- Implement message deduplication to avoid display the same message twice
- Implement message deduplication to avoid processing the same message twice
4. **Error Handling** :
- Implement timeouts for all commands (typically 5 seconds)
- Handle `PACKET_ERROR` responses appropriately
- Log errors for debugging but don't expose sensitive information
5. **Channel Management** :
- Avoid using channel index 0 for private channels
- Migrate channels from index 0 to 1-7 if needed
- Query channels after connection to discover existing channels
---
## Platform-Specific Notes
### Android
- Use `BluetoothGatt` API
- Request `BLUETOOTH_CONNECT` and `BLUETOOTH_SCAN` permissions (Android 12+)
- Enable notifications by writing to descriptor `0x2902` with value `0x01` or `0x02`
### iOS
4. **Channel Management** :
- Use `CoreBluetooth` framework
- Fetch all channel slots even if you encounter an empty slot
- Implement `CBPeripheralDelegate` for notifications
- Ideally save new channels into the first empty slot
- Request Bluetooth permissions in Info.plist
### Python
5. **Error Handling** :
- Use `bleak` library for cross-platform BLE support
- Implement timeouts for all commands (typically 5 seconds)
- Handle async/await for BLE operations
- Handle `RESP_CODE_ERR` responses appropriately
- Use `asyncio` for command-response patterns
### JavaScript/Node.js
- Use `noble` or `@abandonware/noble` for BLE
- Handle callbacks or promises for async operations
- Use `Buffer` for binary data manipulation
---
---
## Troubleshooting
## Troubleshooting
### Connection Issues
### Connection Issues
- **Device not found** : Ensure device is powered on and advertising
- **Device not found** : Ensure device is powered on and advertising
- **Connection timeout** : Check Bluetooth permissions and device proximity
- **Connection timeout** : Check Bluetooth permissions and device proximity
- **GATT errors** : Ensure proper service/characteristic discovery
- **GATT errors** : Ensure proper service/characteristic discovery
### Command Issues
### Command Issues
- **No response** : Verify notifications are enabled, check connection state
- **No response** : Verify notifications are enabled, check connection state
- **Error responses** : Verify command format, check channel index validity
- **Error responses** : Verify command format and check error code
- **Timeout** : Increase timeout value or check device responsiveness
- **Timeout** : Increase timeout value or try again
### Message Issues
### Message Issues
- **Messages not received** : Poll `GET_MESSAGE` command periodically
- **Duplicate messages** : Implement message deduplication using timestamps/hashes
- **Message truncation** : Split long messages into chunks
### Secret/Channel Issues
- **Secret not working** : Verify secret expansion (SHA-512) is correct
- **Channel not found** : Query channels after connection to discover existing channels
- **Channel index 0** : Migrate to index 1-7 for private channels
---
## References
- MeshCore Python implementation: `meshcore_py-main/src/meshcore/`
- BLE GATT Specification: Bluetooth SIG Core Specification
- ED25519 Key Expansion: RFC 8032
---
**Last Updated**: 2025-01-01
**Protocol Version**: Based on MeshCore v1.36.0+
- **Messages not received** : Poll `GET_MESSAGE` command periodically
- **Duplicate messages** : Implement message deduplication using timestamp/content as a unique id
- **Message truncation** : Send long messages as separate shorter messages