mqtt_message_pack A Dart package providing low-level MQTT 3.x and 5.0 packet construction and parsing utilities, along with a set of message‐type classes for building MQTT client payloads. It is a direct port of a PHP MQTT library into idiomatic Dart, supporting both TCP and WebSocket clients.
Table of Contents
- Table of Contents
- Features
- Getting Started
- Project Structure
- Usage
- API Reference Overview
- Testing
- Contributing
- License
Features
- MQTT 3.1.1 & MQTT 5.0 Support Build and parse packets for both versions, with automatic handling of protocol‐level upgrades if MQTT 5.0 properties are present.
- Low‐Level Packet Encoding/Decoding
Classes to translate high‐level Dart objects into raw
Uint8List
byte buffers for publishing to a broker. - Message‐Type Abstractions
Each MQTT control packet (CONNECT, PUBLISH, SUBACK, etc.) has a corresponding Dart class exposing typed getters/setters and a
getContents({bool getAsMap = false})
method for structure or raw bytes. - Property Encoding for MQTT 5.0
Automatic encoding/decoding of variable‐length properties (e.g.
sessionExpiryInterval
,reasonString
,userProperty
, etc.). - Extensible & Framework‐Neutral
No built‐in networking—use your preferred socket or WebSocket library to send/receive the byte buffers generated by
Pack.*
.
Getting Started
Prerequisites
- Dart SDK ≥ 2.17.0 < 4.0.0
- (Optional) A Dart HTTP/WebSocket or TCP client library to actually send/receive MQTT packets.
Installation
Add mqtt_basics_and_client
to your pubspec.yaml
dependencies:
dependencies:
mqtt_basics_and_client:
git:
url: https://github.com/yourusername/mqtt_basics_and_client.git
ref: main
Then run:
dart pub get
Project Structure
mqtt_basics_and_client/
├── lib/
│ ├── client/
│ │ ├── messages/
│ │ │ ├── abstract_message.dart
│ │ │ ├── auth.dart
│ │ │ ├── connack.dart
│ │ │ ├── disconnect.dart
│ │ │ ├── pingresp.dart
│ │ │ ├── puback.dart
│ │ │ ├── pubcomp.dart
│ │ │ ├── pubrec.dart
│ │ │ ├── pubrel.dart
│ │ │ ├── publish.dart
│ │ │ ├── suback.dart
│ │ │ ├── unsuback.dart
│ │ │ └── will.dart
│ │ └── packet/
│ │ └── pack.dart
│ ├── core/
│ │ ├── constants.dart
│ │ ├── exceptions/
│ │ │ ├── connect_exception.dart
│ │ │ ├── invalid_argument_exception.dart
│ │ │ ├── length_exception.dart
│ │ │ ├── protocol_exception.dart
│ │ │ └── runtime_exception.dart
│ │ ├── hex/
│ │ │ ├── property.dart
│ │ │ └── reason_code.dart
│ │ └── protocol/
│ │ ├── protocol_interface.dart
│ │ └── types.dart
│ └── utils/
│ ├── common.dart
│ └── pack_tool.dart
├── test/
│ └── mqtt_basics_and_client_test.dart
├── pubspec.yaml
└── README.md
-
lib/client/messages/
: Dart classes for each MQTT packet type (CONNECT, PUBLISH, etc.) -
lib/client/packet/pack.dart
: High‐level static methods (Pack.connect
,Pack.publish
, etc.) that delegate toPackTool.encode*
. -
lib/core/
:constants.dart
: MQTT protocol constants (QoS values, control packet types, etc.).exceptions/
: Custom exception types (e.g.ProtocolException
,InvalidArgumentException
).hex/
: Hex‐valued constants for MQTT 5.0 properties and reason codes.protocol/
: Core protocol constants and an abstract interface for future extension.
-
lib/utils/pack_tool.dart
: Low‐level helpers for encoding strings, integers, variable‐byte integers, and MQTT 5.0 properties. -
lib/utils/common.dart
: Byte‐level debugging utility to print ASCII/hex representation of binary strings. -
test/
: Comprehensive unit tests covering utility functions,getContents(getAsMap: true)
, and raw packet encoding.
Usage
You can use mqtt_basics_and_client
to build raw MQTT packets and send them over any socket or WebSocket. Below are common examples.
Constructing Packets
All message classes inherit from AbstractMessage
and implement:
/// Returns either:
/// - a `Map<String, dynamic>` if `getAsMap == true`
/// - a `Uint8List` (raw bytes) if `getAsMap == false`
dynamic getContents({bool getAsMap = false});
When getAsMap == true
, you receive a structured map—handy for debugging or custom serialization. When getAsMap == false
, you receive a Uint8List
ready to write directly to a TCP/WebSocket stream.
CONNECT
import 'dart:typed_data';
import 'package:mqtt_basics_and_client/client/packet/pack.dart';
import 'package:mqtt_basics_and_client/core/constants.dart';
void main() {
// Build a CONNECT packet: MQTT 3.1.1, clientId="dartClient", clean session
final connectMap = {
'protocol_name': mqttProtocolName,
'protocol_level': mqttProtocolLevel311,
'clean_session': true,
'client_id': 'dartClient',
'keep_alive': 60,
// For MQTT 5.0, you can add 'properties', 'will', 'user_name', 'password', etc.
};
// Get raw bytes
Uint8List connectPacket = Pack.connect(connectMap);
// Now send `connectPacket` to your TCP socket
}
PUBLISH
import 'dart:typed_data';
import 'package:mqtt_basics_and_client/client/messages/publish.dart';
import 'package:mqtt_basics_and_client/client/packet/pack.dart';
void main() {
final publishMsg = Publish()
..setTopic('test/topic')
..setMessage('Hello from Dart!')
..setQos(1)
..setMessageId(42)
..setRetain(0)
..setDup(0)
..setProperty('payloadFormatIndicator', 1); // MQTT 5.0 only
// Get raw bytes
Uint8List publishPacket = Pack.publish(publishMsg.getContents(getAsMap: false));
// Send `publishPacket` over your socket
}
QoS Flow (PUBREC, PUBREL, PUBCOMP)
// PUBREC (receiver → sender after QoS 2 PUBLISH)
final pubRecMsg = PubRec()
..setMessageId(42)
..setCode(0) // 0 = Success
..setProperty('reasonString', 'Received');
Uint8List pubRecPacket = Pack.pubRec(pubRecMsg.getContents(getAsMap: false));
// PUBREL (sender → receiver)
final pubRelMsg = PubRel()
..setMessageId(42)
..setCode(0); // Success
Uint8List pubRelPacket = Pack.pubRel(pubRelMsg.getContents(getAsMap: false));
// PUBCOMP (receiver → sender)
final pubCompMsg = PubComp()
..setMessageId(42)
..setCode(0); // Success
Uint8List pubCompPacket = Pack.pubComp(pubCompMsg.getContents(getAsMap: false));
SUBSCRIBE / SUBACK
// SUBSCRIBE at QoS 1: topic="cmd/#"
final subscribeMap = {
'type': 8, // Types.subscribe = 8
'message_id': 101,
'topics': ['cmd/#'], // currently assume one topic; extend as needed
'qos': [1], // parallel list of QoS values
'properties': <String, dynamic>{} // MQTT 5.0 optional
};
// Encode via a custom helper or extend PackTool to support encodeSubscribe()
// SUBACK (broker → client)
final subAckMsg = SubAck()
..setMessageId(101)
..setCodes([0x01]) // Granted QoS 1
..setProperty('subscriptionIdentifier', 1);
Uint8List subAckPacket = Pack.subAck(subAckMsg.getContents(getAsMap: false));
Note: Subscribe encoding is not provided out of the box. You can adapt
PackTool.encode*
logic forSUBSCRIBE
using string/topic lists and property mappings.
UNSUBSCRIBE / UNSUBACK
// UNSUBACK (broker → client)
final unsubAckMsg = UnSubAck()
..setMessageId(101)
..setCodes([0x00]) // Success (0x00 = Success, 0x80 = Failure)
..setProperty('reasonString', 'Unsubscribed');
Uint8List unsubAckPacket = Pack.unsubAck(unsubAckMsg.getContents(getAsMap: false));
PINGREQ / PINGRESP
- PINGREQ: client sends
[0xC0, 0x00]
(fixed two‐byte header) - PINGRESP: built as:
// PINGRESP (broker → client)
Uint8List pingRespPacket = Pack.pingResp({});
// pingRespPacket == Uint8List.fromList([0xD0, 0x00]);
DISCONNECT
final disconnectMsg = DisConnect()
..setCode(0x00) // Normal disconnection
..setProperty('reasonString', 'Goodbye');
Uint8List disconnectPacket = Pack.disconnect(disconnectMsg.getContents(getAsMap: false));
AUTH (MQTT 5.0)
final authMsg = Auth()
..setCode(0x18) // Continue authentication
..setProperty('authenticationMethod', 'token');
Uint8List authPacket = Pack.auth(authMsg.getContents(getAsMap: false));
Decoding Incoming Packets
If you receive a raw Uint8List
buffer from a broker, you can:
-
Inspect the first byte to extract packet type:
int packetType = data[0] >> 4; // Upper 4 bits of first byte
-
Parse Remaining Length (variable‐byte integer) using
PackTool.decodeVariableByteInteger(data, 1)
. -
Use your own
UnPack
orUnPackV5
logic (not included in this repo, but easily portable from PHP port) to extract fields.
This package focuses on packet construction; you can re‐implement
UnPack
classes similarly if you need a full decoder.
API Reference Overview
Core Utilities (PackTool
)
-
encodeString(String str) → Uint8List
Prefixes length (2 bytes) then UTF-8 bytes ofstr
. -
encodeStringPair(String key, String value) → Uint8List
Two successive length‐prefixed strings (key, then value). -
encodeInt16(int) → Uint8List
&encodeInt32(int) → Uint8List
Big‐endian 16‐bit or 32‐bit integer. -
encodeVariableByteInteger(int) → Uint8List
MQTT’s VB‐I format (up to four bytes). -
decodeVariableByteInteger(Uint8List, int offset) → Map<String, dynamic>
Decodes a VB‐I starting atoffset
, returns{ 'value': int, 'offset': newOffset }
. -
encodeProperties(Map<String, dynamic>) → Uint8List
Prepend VB‐I length, then[ keyLength, keyBytes, valueLength, valueBytes ]…
for each property. -
(Future) Add
encodeWill
,encodeSub
, etc., if you want to unify PUBLISH/SUBSCRIBE encoding under one tool.
Packet Builders (Pack
)
Each static method in Pack
produces the variable header + payload bytes. You will still need to prepend the appropriate fixed header (control packet type + flags, remaining length) before sending over TCP/WebSocket. Typical flow:
-
Build variable header & payload with
Pack.*(map)
. -
Determine remaining length (
remainingLength = variableHeaderBytes.length
). -
Build fixed header:
int packetTypeAndFlags = (controlPacketType << 4) | flags; Uint8List remLenBytes = PackTool.encodeVariableByteInteger(remainingLength);
-
Concatenate
[packetTypeAndFlags] + remLenBytes + variableHeaderBytes
.
For example, for a QoS 1 PUBLISH (controlPacketType = 3
, flags = (qos << 1)
):
final varHeaderPayload = Pack.publish(publishMap);
final remaining = PackTool.encodeVariableByteInteger(varHeaderPayload.length);
final fixedHeader = Uint8List.fromList([ (3 << 4) | (1 << 1), ...remaining ]);
Uint8List fullPacket = Uint8List.fromList([...fixedHeader, ...varHeaderPayload]);
Message Classes (AbstractMessage
& subclasses)
Every message class extends AbstractMessage
and must override:
dynamic getContents({bool getAsMap = false});
- Returns a
Map<String, dynamic>
structure ifgetAsMap == true
. - Returns a
Uint8List
by calling the correspondingPack.*
method ifgetAsMap == false
.
Subclasses:
Auth
(MQTT 5.0 only)ConnAck
DisConnect
PingResp
PubAck
,PubRec
,PubRel
,PubComp
(QoS flows)Publish
SubAck
,UnSubAck
Will
(embedded in CONNECT payload)
Testing
A comprehensive test suite is provided in test/mqtt_basics_and_client_test.dart
. It includes:
-
PackTool
Utility TestsencodeString
,encodeVariableByteInteger
,encodeProperties
, and nowdecodeVariableByteInteger
.
-
Message Class Structure Tests
- Verifying that
getContents(getAsMap: true)
returns the correct map keys/values for each message type.
- Verifying that
-
Pack
Encoding Smoke Tests- Ensuring
Pack.pubAck
,Pack.disconnect
,Pack.publish
,Pack.connAck
, andPack.pingResp
each produce non-empty or properly structured bytes.
- Ensuring
Run all tests with:
dart test
Contributing
- Fork the repository
- Create a feature branch (
git checkout -b feature/your-feature
) - Implement your changes, ensuring new code is covered by tests
- Run
dart test
to verify - Submit a Pull Request with a clear description and relevant issue references
Please follow the existing coding style:
- Use two‐space indentation
- Keep line length around 80–100 characters
- Add doc comments (
/// …
) to any new public API
License
This project is licensed under the MIT License. See LICENSE for details.
Happy MQTT’ing with Dart!