├── .gitattributes ├── sbom.yaml ├── example ├── screenshots │ ├── MQTT-1.webp │ ├── mqtt-ver.png │ └── mqtt-ver.webp ├── example.md ├── mqtt_client_universal.dart └── pem │ └── mosquitto.org.crt ├── test ├── issues │ ├── issue622 │ │ ├── issue622.zip │ │ └── issue622.dart │ ├── issue211.dart │ ├── issue209 │ │ ├── issue209-normal-nobroker.dart │ │ ├── pem │ │ │ └── mosquitto.org.crt │ │ ├── issue209-normal-noack.dart │ │ ├── issue209-normal-mosquitto.dart │ │ ├── issue209-normal-hive.dart │ │ ├── issue209-ws-mosquitto.dart │ │ ├── issue209-wss-mosquitto.dart │ │ ├── issue209-secure-mosquitto.dart │ │ └── issue209-normal-disconnectedbroker.dart │ ├── issue281.dart │ ├── issue56.dart │ ├── persistence │ │ ├── receiver_nosubscribe.dart │ │ ├── generator.dart │ │ ├── receiver_subscribe.dart │ │ └── receiver_reconnect.dart │ ├── issue53.dart │ ├── issue602 │ │ ├── main.dart │ │ ├── mqtt_test2.py │ │ └── mqtt_bench2.py │ ├── issue53a.dart │ ├── issue53b.dart │ ├── issue213.dart │ ├── issue81.dart │ ├── issue54.dart │ └── issue126.dart ├── pem │ ├── localhost.cert │ ├── localhost.key │ ├── self_signed.cert │ └── self_signed.key ├── support │ ├── mqtt_client_broker_test_publish.dart │ ├── mqtt_client_broker_test_ws_publish.dart │ ├── mqtt_client_broker_test_subscribe.dart │ ├── mqtt_client_broker_test_ws_subscribe.dart │ ├── mqtt_client_test_connection_handler.dart │ └── mqtt_client_broker_test_iotcore.dart ├── manual │ ├── mqtt_client_class.dart │ ├── mqtt_client_mosquitto_ws_browser_test_manual.dart │ └── mqtt_client_mosquitto_wss_browser_test_manual.dart └── mqtt_client_connection_autoreconnect_nobroker_test.dart ├── AUTHORS.md ├── lib ├── src │ ├── mqtt_client_environment.dart │ ├── observable │ │ ├── observable.dart │ │ └── src │ │ │ ├── records.dart │ │ │ ├── change_notifier.dart │ │ │ └── observable.dart │ ├── mqtt_client_protocol.dart │ ├── mqtt_client_subscription_status.dart │ ├── exception │ │ ├── mqtt_client_noconnection_exception.dart │ │ ├── mqtt_client_invalid_header_exception.dart │ │ ├── mqtt_client_invalid_message_exception.dart │ │ ├── mqtt_client_invalid_topic_exception.dart │ │ ├── mqtt_client_incorrect_instantiation_exception.dart │ │ ├── mqtt_client_invalid_payload_size_exception.dart │ │ ├── mqtt_client_connection_exception.dart │ │ └── mqtt_client_client_identifier_exception.dart │ ├── connectionhandling │ │ ├── mqtt_client_read_wrapper.dart │ │ ├── browser │ │ │ └── mqtt_client_mqtt_browser_connection_handler.dart │ │ ├── mqtt_client_connection_state.dart │ │ ├── server │ │ │ └── mqtt_client_mqtt_server_connection_handler.dart │ │ ├── mqtt_client_mqtt_connection_base.dart │ │ └── mqtt_client_imqtt_connection_handler.dart │ ├── mqtt_client_mqtt_received_message.dart │ ├── dataconvertors │ │ ├── mqtt_client_passthru_payload_convertor.dart │ │ ├── mqtt_client_ascii_payload_convertor.dart │ │ └── mqtt_client_payload_convertor.dart │ ├── messages │ │ ├── connect │ │ │ ├── mqtt_client_mqtt_connect_return_code.dart │ │ │ ├── mqtt_client_mqtt_connect_variable_header.dart │ │ │ └── mqtt_client_mqtt_connect_flags.dart │ │ ├── disconnect │ │ │ └── mqtt_client_mqtt_disconnect_message.dart │ │ ├── pingrequest │ │ │ └── mqtt_client_mqtt_ping_request_message.dart │ │ ├── pingresponse │ │ │ └── mqtt_client_mqtt_ping_response_message.dart │ │ ├── mqtt_client_mqtt_payload.dart │ │ ├── mqtt_client_mqtt_message_type.dart │ │ ├── subscribe │ │ │ ├── mqtt_client_mqtt_subscribe_variable_header.dart │ │ │ └── mqtt_client_mqtt_subscribe_payload.dart │ │ ├── subscribeack │ │ │ ├── mqtt_client_mqtt_subscribe_ack_variable_header.dart │ │ │ ├── mqtt_client_mqtt_subscribe_ack_payload.dart │ │ │ └── mqtt_client_mqtt_subscribe_ack_message.dart │ │ ├── publishack │ │ │ ├── mqtt_client_mqtt_publish_ack_variable_header.dart │ │ │ └── mqtt_client_mqtt_publish_ack_message.dart │ │ ├── publishrelease │ │ │ ├── mqtt_client_mqtt_publish_release_variable_header.dart │ │ │ └── mqtt_client_mqtt_publish_release_message.dart │ │ ├── unsubscribe │ │ │ ├── mqtt_client_mqtt_unsubscribe_variable_header.dart │ │ │ └── mqtt_client_mqtt_unsubscribe_payload.dart │ │ ├── unsubscribeack │ │ │ ├── mqtt_client_mqtt_unsubscribe_ack_variable_header.dart │ │ │ └── mqtt_client_mqtt_unsubscribe_ack_message.dart │ │ ├── publishcomplete │ │ │ ├── mqtt_client_mqtt_publish_complete_variable_header.dart │ │ │ └── mqtt_client_mqtt_publish_complete_message.dart │ │ ├── publishreceived │ │ │ ├── mqtt_client_mqtt_publish_received_variable_header.dart │ │ │ └── mqtt_client_mqtt_publish_received_message.dart │ │ ├── publish │ │ │ ├── mqtt_client_mqtt_publish_variable_header.dart │ │ │ └── mqtt_client_mqtt_publish_payload.dart │ │ ├── connectack │ │ │ ├── mqtt_client_mqtt_connect_ack_variable_header.dart │ │ │ └── mqtt_client_mqtt_connect_ack_message.dart │ │ ├── mqtt_client_mqtt_message.dart │ │ └── mqtt_client_mqtt_message_factory.dart │ ├── mqtt_client_ipublishing_manager.dart │ ├── mqtt_client_publication_topic.dart │ ├── mqtt_client_mqtt_qos.dart │ ├── mqtt_client_connection_status.dart │ ├── mqtt_client_message_identifier_dispenser.dart │ ├── utility │ │ ├── mqtt_client_logger.dart │ │ ├── mqtt_client_utilities.dart │ │ └── mqtt_client_payload_builder.dart │ ├── mqtt_client_constants.dart │ ├── mqtt_client_events.dart │ ├── mqtt_browser_client.dart │ ├── management │ │ └── mqtt_client_topic_filter.dart │ ├── encoding │ │ └── mqtt_client_mqtt_encoding.dart │ └── mqtt_client_topic.dart ├── mqtt_browser_client.dart └── mqtt_server_client.dart ├── .gitignore ├── .github └── workflows │ ├── ci.yml │ └── pana.yml ├── pubspec.yaml ├── analysis_options.yaml ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | coverage/* linguist-vendored -------------------------------------------------------------------------------- /sbom.yaml: -------------------------------------------------------------------------------- 1 | type: spdx 2 | 3 | spdx: 4 | SPDXFormat: 'tagvalue' -------------------------------------------------------------------------------- /example/screenshots/MQTT-1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shamblett/mqtt_client/HEAD/example/screenshots/MQTT-1.webp -------------------------------------------------------------------------------- /example/screenshots/mqtt-ver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shamblett/mqtt_client/HEAD/example/screenshots/mqtt-ver.png -------------------------------------------------------------------------------- /example/screenshots/mqtt-ver.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shamblett/mqtt_client/HEAD/example/screenshots/mqtt-ver.webp -------------------------------------------------------------------------------- /test/issues/issue622/issue622.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shamblett/mqtt_client/HEAD/test/issues/issue622/issue622.zip -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | Ported by Steve Hamblett from the original NMQTT implementation by 2 | Mark Allanson and contributors 3 | 4 | Yuriy Olkhovyy 5 | 6 | ### Observable package include 7 | 8 | Dart Team -------------------------------------------------------------------------------- /lib/src/mqtt_client_environment.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 09/06/2025 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../mqtt_client.dart'; 9 | 10 | /// Client Environment 11 | class MqttClientEnvironment { 12 | /// Browser or server client 13 | static bool isWebClient = false; 14 | } 15 | -------------------------------------------------------------------------------- /lib/src/observable/observable.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library; 6 | 7 | export 'src/change_notifier.dart' show ChangeNotifier; 8 | export 'src/observable.dart'; 9 | export 'src/records.dart' show ChangeRecord; 10 | -------------------------------------------------------------------------------- /lib/src/mqtt_client_protocol.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../mqtt_client.dart'; 9 | 10 | /// Protocol selection helper class, protocol defaults V3.1 11 | class Protocol { 12 | /// Version 13 | static int version = MqttClientConstants.mqttV31ProtocolVersion; 14 | 15 | /// Name 16 | static String name = MqttClientConstants.mqttV31ProtocolName; 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://www.dartlang.org/tools/private-files.html 2 | 3 | # Files and directories created by pub 4 | .packages 5 | .pub/ 6 | build/ 7 | # If you're building an application, you may want to check-in your pubspec.lock 8 | pubspec.lock 9 | 10 | # Directory created by dartdoc 11 | # If you don't generate documentation locally you can remove this line. 12 | doc/api/ 13 | 14 | # Dart 2 additions 15 | .dart_tool/ 16 | 17 | .idea 18 | .DS_Store 19 | .directory 20 | /test/issues/issue602/venv/ 21 | -------------------------------------------------------------------------------- /example/example.md: -------------------------------------------------------------------------------- 1 | The client can be configured as either a server or browser based client in many ways, using normal TCP sockets, secure TCP sockets or websockets as needed. 2 | Numerous examples of usage can be found in the examples directory [here](https://github.com/shamblett/mqtt_client/tree/master/example) 3 | 4 | To get started please refer to this [example](https://github.com/shamblett/mqtt_client/blob/master/example/mqtt_server_client.dart), showing how to configure a client for basic server side MQTT operation. 5 | -------------------------------------------------------------------------------- /test/issues/issue211.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | import 'package:mqtt_client/mqtt_server_client.dart'; 9 | 10 | void main() async { 11 | final mqttClient = MqttServerClient.withPort( 12 | 'ws://test.mosquitto.org', 13 | 'Unique_ID', 14 | 8080, 15 | maxConnectionAttempts: 1, 16 | ); 17 | mqttClient.useWebSocket = true; 18 | mqttclient.logging(on: false); 19 | await mqttClient.connect(); 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/mqtt_client_subscription_status.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../mqtt_client.dart'; 9 | 10 | /// Describes the status of a subscription 11 | enum MqttSubscriptionStatus { 12 | /// The subscription does not exist / is not known 13 | doesNotExist, 14 | 15 | /// The subscription is currently pending acknowledgement by a broker. 16 | pending, 17 | 18 | /// The subscription is currently active and messages will be received. 19 | active, 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/exception/mqtt_client_noconnection_exception.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../mqtt_client.dart'; 9 | 10 | /// Exception thrown when the client fails to connect 11 | class NoConnectionException implements Exception { 12 | late String _message; 13 | 14 | /// Construct 15 | NoConnectionException(String message) { 16 | _message = 'mqtt-client::NoConnectionException: $message'; 17 | } 18 | 19 | @override 20 | String toString() => _message; 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/connectionhandling/mqtt_client_read_wrapper.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 23/01/2020 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../mqtt_client.dart'; 9 | 10 | /// State and logic used to read from the underlying network stream. 11 | class ReadWrapper { 12 | /// The bytes associated with the message being read. 13 | List? messageBytes; 14 | 15 | /// Creates a new ReadWrapper that wraps the state used to read 16 | /// a message from a stream. 17 | ReadWrapper() { 18 | messageBytes = []; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/exception/mqtt_client_invalid_header_exception.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../mqtt_client.dart'; 9 | 10 | /// Exception thrown when processing a header that is invalid in some way. 11 | class InvalidHeaderException implements Exception { 12 | late String _message; 13 | 14 | /// Construct 15 | InvalidHeaderException(String text) { 16 | _message = 'mqtt-client::InvalidHeaderException: $text'; 17 | } 18 | 19 | @override 20 | String toString() => _message; 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/exception/mqtt_client_invalid_message_exception.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../mqtt_client.dart'; 9 | 10 | /// Exception thrown when processing a Message that is invalid in some way. 11 | class InvalidMessageException implements Exception { 12 | late String _message; 13 | 14 | /// Construct 15 | InvalidMessageException(String text) { 16 | _message = 'mqtt-client::InvalidMessageException: $text'; 17 | } 18 | 19 | @override 20 | String toString() => _message; 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/exception/mqtt_client_invalid_topic_exception.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../mqtt_client.dart'; 9 | 10 | /// Exception thrown when the topic of a message is invalid 11 | class InvalidTopicException implements Exception { 12 | late String _message; 13 | 14 | /// Construct 15 | InvalidTopicException(String message, String topic) { 16 | _message = 'mqtt-client::InvalidTopicException: Topic $topic is $message'; 17 | } 18 | 19 | @override 20 | String toString() => _message; 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/mqtt_client_mqtt_received_message.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../mqtt_client.dart'; 9 | 10 | /// Represents a MQTT message that has been received from a broker. 11 | class MqttReceivedMessage extends observe.ChangeRecord { 12 | /// The topic the message was received on. 13 | String topic; 14 | 15 | /// The payload of the message received. 16 | T payload; 17 | 18 | /// Initializes a new instance of an MqttReceivedMessage class. 19 | MqttReceivedMessage(this.topic, this.payload); 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/connectionhandling/browser/mqtt_client_mqtt_browser_connection_handler.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 22/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_browser_client.dart'; 9 | 10 | /// This class provides specific connection functionality 11 | /// for browser based connection handler implementations. 12 | abstract class MqttBrowserConnectionHandler extends MqttConnectionHandlerBase { 13 | /// Initializes a new instance of the [MqttBrowserConnectionHandler] class. 14 | MqttBrowserConnectionHandler( 15 | super.clientEventBus, { 16 | required int super.maxConnectionAttempts, 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/dataconvertors/mqtt_client_passthru_payload_convertor.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../mqtt_client.dart'; 9 | 10 | /// Acts as a pass through for the raw data without doing any conversion. 11 | class PassthruPayloadConverter implements PayloadConverter { 12 | /// Processes received data and returns it as a byte array. 13 | @override 14 | typed.Uint8Buffer convertFromBytes(typed.Uint8Buffer messageData) => 15 | messageData; 16 | 17 | /// Converts sent data from an object graph to a byte array. 18 | @override 19 | typed.Uint8Buffer convertToBytes(typed.Uint8Buffer data) => data; 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/exception/mqtt_client_incorrect_instantiation_exception.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 17/03/2020 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../mqtt_client.dart'; 9 | 10 | /// Exception thrown when a browser or server client is instantiated incorrectly. 11 | class IncorrectInstantiationException implements Exception { 12 | late String _message; 13 | 14 | /// Construct 15 | IncorrectInstantiationException() { 16 | _message = 17 | 'mqtt-client::ClientIncorrectInstantiationException: Incorrect instantiation, do not' 18 | 'instantiate MqttClient directly, use MqttServerClient or MqttBrowserClient'; 19 | } 20 | 21 | @override 22 | String toString() => _message; 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/exception/mqtt_client_invalid_payload_size_exception.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../mqtt_client.dart'; 9 | 10 | /// Exception that is thrown when the payload of a message 11 | /// is not the correct size. 12 | class InvalidPayloadSizeException implements Exception { 13 | late String _message; 14 | 15 | /// Construct 16 | InvalidPayloadSizeException(int size, int max) { 17 | _message = 18 | 'mqtt-client::InvalidPayloadSizeException: The size of the ' 19 | 'payload ($size bytes) must ' 20 | 'be equal to or greater than 0 and less than $max bytes'; 21 | } 22 | 23 | @override 24 | String toString() => _message; 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/messages/connect/mqtt_client_mqtt_connect_return_code.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_client.dart'; 9 | 10 | /// Enumeration of allowable connection request return codes from a broker. 11 | enum MqttConnectReturnCode { 12 | /// Connection accepted 13 | connectionAccepted, 14 | 15 | /// Invalid protocol version 16 | unacceptedProtocolVersion, 17 | 18 | /// Invalid client identifier 19 | identifierRejected, 20 | 21 | /// Broker unavailable 22 | brokerUnavailable, 23 | 24 | /// Invalid username or password 25 | badUsernameOrPassword, 26 | 27 | /// Not authorised 28 | notAuthorized, 29 | 30 | /// Default 31 | noneSpecified, 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/observable/src/records.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library; 6 | 7 | /// Result of a change to an observed object. 8 | class ChangeRecord { 9 | /// Signifies a change occurred, but without details of the specific change. 10 | /// 11 | /// May be used to produce lower-GC-pressure records where more verbose change 12 | /// records will not be used directly. 13 | static const List any = [ChangeRecord()]; 14 | 15 | /// Signifies no changes occurred. 16 | static const List none = []; 17 | 18 | /// Constructor 19 | const ChangeRecord(); 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/exception/mqtt_client_connection_exception.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../mqtt_client.dart'; 9 | 10 | /// Exception thrown when the connection state is incorrect. 11 | class ConnectionException implements Exception { 12 | late String _message; 13 | 14 | /// Construct 15 | ConnectionException(MqttConnectionState? state) { 16 | _message = 17 | 'mqtt-client::ConnectionException: The connection must be in ' 18 | 'the Connected state in order to perform this operation.'; 19 | if (null != state) { 20 | _message = '$_message Current state is ${state.toString().split('.')[1]}'; 21 | } 22 | } 23 | 24 | @override 25 | String toString() => _message; 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/exception/mqtt_client_client_identifier_exception.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../mqtt_client.dart'; 9 | 10 | /// Exception thrown when a client identifier included in a message is too long. 11 | class ClientIdentifierException implements Exception { 12 | late String _message; 13 | 14 | /// Construct 15 | ClientIdentifierException(String clientIdentifier) { 16 | _message = 17 | 'mqtt-client::ClientIdentifierException: Client id $clientIdentifier ' 18 | 'is too long at ${clientIdentifier.length}, ' 19 | 'Maximum ClientIdentifier length is ' 20 | '${MqttClientConstants.maxClientIdentifierLength}'; 21 | } 22 | 23 | @override 24 | String toString() => _message; 25 | } 26 | -------------------------------------------------------------------------------- /lib/mqtt_browser_client.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_browser_client 3 | * Author : S. Hamblett 4 | * Date : 20/01/2020 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | library; 9 | 10 | import 'dart:async'; 11 | import 'dart:js_interop'; 12 | import 'dart:typed_data'; 13 | import 'package:event_bus/event_bus.dart' as events; 14 | import 'package:typed_data/typed_data.dart' as typed; 15 | import 'package:web/web.dart'; 16 | import 'mqtt_client.dart'; 17 | 18 | part 'src/mqtt_browser_client.dart'; 19 | part 'src/connectionhandling/browser/mqtt_client_mqtt_browser_connection_handler.dart'; 20 | part 'src/connectionhandling/browser/mqtt_client_synchronous_mqtt_browser_connection_handler.dart'; 21 | part 'src/connectionhandling/browser/mqtt_client_mqtt_browser_ws_connection.dart'; 22 | part 'src/connectionhandling/browser/mqtt_client_mqtt_browser_connection.dart'; 23 | -------------------------------------------------------------------------------- /lib/src/messages/disconnect/mqtt_client_mqtt_disconnect_message.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 19/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_client.dart'; 9 | 10 | /// Implementation of an MQTT Disconnect Message. 11 | class MqttDisconnectMessage extends MqttMessage { 12 | /// Initializes a new instance of the MqttDisconnectMessage class. 13 | MqttDisconnectMessage() { 14 | header = MqttHeader().asType(MqttMessageType.disconnect); 15 | } 16 | 17 | /// Initializes a new instance of the MqttDisconnectMessage class. 18 | MqttDisconnectMessage.fromHeader(MqttHeader header) { 19 | this.header = header; 20 | } 21 | 22 | @override 23 | String toString() { 24 | final sb = StringBuffer(); 25 | sb.write(super.toString()); 26 | return sb.toString(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example/mqtt_client_universal.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 07/04/2021 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | // The following scheme can be used conditionally import either the server or browser client 9 | // automatically. 10 | // 11 | // import 'server.dart' if (dart.library.js_interop) 'browser.dart' as mqttsetup; 12 | // ... 13 | // var client = mqttsetup.setup(serverAddress, uniqueID, port); 14 | // 15 | // where server.dart will contain such as the following 16 | // 17 | // MqttClient setup(String serverAddress, String uniqueID, int port) { 18 | // return MqttServerClient.withPort(serverAddress, uniqueID, port); 19 | // } 20 | // 21 | // and browser.dart 22 | // 23 | // MqttClient setup(String serverAddress, String uniqueID, int port) { 24 | // return MqttBrowserClient.withPort(serverAddress, uniqueID, port); 25 | // } 26 | -------------------------------------------------------------------------------- /lib/src/messages/pingrequest/mqtt_client_mqtt_ping_request_message.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 19/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_client.dart'; 9 | 10 | /// Implementation of an MQTT ping Request Message. 11 | class MqttPingRequestMessage extends MqttMessage { 12 | /// Initializes a new instance of the MqttPingRequestMessage class. 13 | MqttPingRequestMessage() { 14 | header = MqttHeader().asType(MqttMessageType.pingRequest); 15 | } 16 | 17 | /// Initializes a new instance of the MqttPingRequestMessage class. 18 | MqttPingRequestMessage.fromHeader(MqttHeader header) { 19 | this.header = header; 20 | } 21 | 22 | @override 23 | String toString() { 24 | final sb = StringBuffer(); 25 | sb.write(super.toString()); 26 | return sb.toString(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/messages/pingresponse/mqtt_client_mqtt_ping_response_message.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 19/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_client.dart'; 9 | 10 | /// Implementation of an MQTT ping Request Message. 11 | class MqttPingResponseMessage extends MqttMessage { 12 | /// Initializes a new instance of the MqttPingResponseMessage class. 13 | MqttPingResponseMessage() { 14 | header = MqttHeader().asType(MqttMessageType.pingResponse); 15 | } 16 | 17 | /// Initializes a new instance of the MqttPingResponseMessage class. 18 | MqttPingResponseMessage.fromHeader(MqttHeader header) { 19 | this.header = header; 20 | } 21 | 22 | @override 23 | String toString() { 24 | final sb = StringBuffer(); 25 | sb.write(super.toString()); 26 | return sb.toString(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/mqtt_client_ipublishing_manager.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 30/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../mqtt_client.dart'; 9 | 10 | /// Interface that defines how the publishing manager publishes 11 | /// messages to the broker and how it passed on messages that are 12 | /// received from the broker. 13 | abstract class IPublishingManager { 14 | /// The message received event 15 | MessageReceived? publishEvent; 16 | 17 | /// Publish a message to the broker on the specified topic. 18 | /// The topic to send the message to 19 | /// The QOS to use when publishing the message. 20 | /// The message to send. 21 | /// The message identifier assigned to the message. 22 | int publish( 23 | PublicationTopic topic, 24 | MqttQos qualityOfService, 25 | typed.Uint8Buffer data, 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Build Status 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: dart-lang/setup-dart@v1 16 | 17 | - name: Install dependencies 18 | run: dart pub get 19 | 20 | - name: Verify formatting 21 | run: dart format --output=none --set-exit-if-changed . 22 | 23 | - name: Analyze project source 24 | run: dart analyze 25 | 26 | - name: Run tests with coverage 27 | run: dart run coverage:test_with_coverage 28 | 29 | - name: Upload to codecov.io 30 | uses: codecov/codecov-action@v2 31 | with: 32 | token: ${{ secrets.CODECOV_TOKEN }} 33 | file: coverage/lcov.info 34 | name: Upload to codecov.io 35 | verbose: true 36 | -------------------------------------------------------------------------------- /lib/src/dataconvertors/mqtt_client_ascii_payload_convertor.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../mqtt_client.dart'; 9 | 10 | /// Converts string data to and from the MQTT wire format 11 | class AsciiPayloadConverter implements PayloadConverter { 12 | /// Processes received data and returns it as a string. 13 | @override 14 | String convertFromBytes(typed.Uint8Buffer messageData) { 15 | const decoder = Utf8Decoder(); 16 | return decoder.convert(messageData.toList()); 17 | } 18 | 19 | /// Converts sent data from a string to a byte array. 20 | @override 21 | typed.Uint8Buffer convertToBytes(String data) { 22 | const encoder = Utf8Encoder(); 23 | final buff = typed.Uint8Buffer(); 24 | buff.addAll(encoder.convert(data)); 25 | return buff; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/messages/mqtt_client_mqtt_payload.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../mqtt_client.dart'; 9 | 10 | /// Represents the payload (Body) of an MQTT Message. 11 | abstract class MqttPayload { 12 | /// Initializes a new instance of the MqttPayload class. 13 | MqttPayload(); 14 | 15 | /// Initializes a new instance of the MqttPayload class. 16 | MqttPayload.fromMqttByteBuffer(MqttByteBuffer payloadStream) { 17 | readFrom(payloadStream); 18 | } 19 | 20 | /// Writes the payload to the supplied stream. 21 | /// A basic message has no Variable Header. 22 | void writeTo(MqttByteBuffer payloadStream); 23 | 24 | /// Creates a payload from the specified header stream. 25 | void readFrom(MqttByteBuffer payloadStream); 26 | 27 | /// Gets the length of the payload in bytes when written to a stream. 28 | int getWriteLength(); 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/mqtt_client_publication_topic.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../mqtt_client.dart'; 9 | 10 | /// Implementation of a Publication topic that performs additional validations 11 | /// of messages that are published. 12 | class PublicationTopic extends Topic { 13 | /// Construction 14 | PublicationTopic(String topic) 15 | : super(topic, [ 16 | Topic.validateMinLength, 17 | Topic.validateMaxLength, 18 | _validateWildcards, 19 | ]); 20 | 21 | /// Validates that the topic has no wildcards which are not allowed 22 | /// in publication topics. 23 | static void _validateWildcards(Topic topicInstance) { 24 | if (topicInstance.hasWildcards) { 25 | throw Exception( 26 | 'mqtt_client::PublicationTopic: Cannot publish to a topic that ' 27 | 'contains MQTT topic wildcards (# or +)', 28 | ); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/mqtt_client_mqtt_qos.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../mqtt_client.dart'; 9 | 10 | /// Enumeration of available QoS types. 11 | enum MqttQos { 12 | /// QOS Level 0 - Message is not guaranteed delivery. No retries are made 13 | /// to ensure delivery is successful. 14 | atMostOnce, 15 | 16 | /// QOS Level 1 - Message is guaranteed delivery. It will be delivered at 17 | /// least one time, but may be delivered more than once if network 18 | /// errors occur. 19 | atLeastOnce, 20 | 21 | /// QOS Level 2 - Message will be delivered once, and only once. 22 | /// Message will be retried until it is successfully sent. 23 | exactlyOnce, 24 | 25 | /// Reserved by the MQTT Spec. Currently unused from here on until the fail 26 | /// indicator below 27 | reserved1, 28 | 29 | /// Failure indication 30 | /// This is a QOS value of 128, used in a sub ack message to indicate failure 31 | /// to subscribe to a topic 32 | failure, 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/pana.yml: -------------------------------------------------------------------------------- 1 | name: Pana Analysis 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - development 7 | 8 | jobs: 9 | 10 | package-analysis: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - uses: axel-op/dart-package-analyzer@v3 18 | id: analysis # set an id for the current step 19 | with: 20 | githubToken: ${{ secrets.GITHUB_TOKEN }} 21 | 22 | # You can then use this id to retrieve the outputs in the next steps. 23 | # The following step shows how to exit the workflow with an error if a score is below 100: 24 | - name: Check scores 25 | env: 26 | # NB: "analysis" is the id set above. Replace it with the one you used if different. 27 | TOTAL: ${{ steps.analysis.outputs.total }} 28 | TOTAL_MAX: ${{ steps.analysis.outputs.total_max }} 29 | run: | 30 | PERCENTAGE=$(( $TOTAL * 100 / $TOTAL_MAX )) 31 | if (( $PERCENTAGE < 50 )) 32 | then 33 | echo Score too low! 34 | exit 1 35 | fi 36 | -------------------------------------------------------------------------------- /lib/src/mqtt_client_connection_status.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../mqtt_client.dart'; 9 | 10 | /// Records the status of the last connection attempt 11 | class MqttClientConnectionStatus { 12 | /// Connection state 13 | MqttConnectionState state = MqttConnectionState.disconnected; 14 | 15 | /// Return code 16 | MqttConnectReturnCode? returnCode = MqttConnectReturnCode.noneSpecified; 17 | 18 | /// Disconnection origin 19 | MqttDisconnectionOrigin disconnectionOrigin = MqttDisconnectionOrigin.none; 20 | 21 | /// The connect acknowledgement message from the broker for the current connection 22 | MqttConnectAckMessage? connectAckMessage; 23 | 24 | @override 25 | String toString() { 26 | final s = state.toString().split('.')[1]; 27 | final r = returnCode.toString().split('.')[1]; 28 | final t = disconnectionOrigin.toString().split('.')[1]; 29 | return 'Connection status is $s with return code of $r and a disconnection origin of $t'; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/issues/issue209/issue209-normal-nobroker.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | import 'dart:async'; 9 | import 'dart:io'; 10 | import 'package:mqtt_client/mqtt_client.dart'; 11 | import 'package:mqtt_client/mqtt_server_client.dart'; 12 | import 'package:test/test.dart'; 13 | 14 | Future main() async { 15 | test('Should try three times then fail', () async { 16 | final client = MqttServerClient.withPort( 17 | 'billy', 18 | 'client-id-123456789', 19 | 1883, 20 | ); 21 | client.autoReconnect = true; 22 | client.logging(on: false); 23 | 24 | // Main test starts here 25 | print('ISSUE: Main test start'); 26 | var exceptionOK = false; 27 | try { 28 | await client.connect(); 29 | } on SocketException catch (e) { 30 | exceptionOK = true; 31 | } 32 | expect(exceptionOK, isTrue); 33 | expect(client.connectionStatus.state, MqttConnectionState.faulted); 34 | print('ISSUE: Test complete'); 35 | }); 36 | 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /example/pem/mosquitto.org.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC8DCCAlmgAwIBAgIJAOD63PlXjJi8MA0GCSqGSIb3DQEBBQUAMIGQMQswCQYD 3 | VQQGEwJHQjEXMBUGA1UECAwOVW5pdGVkIEtpbmdkb20xDjAMBgNVBAcMBURlcmJ5 4 | MRIwEAYDVQQKDAlNb3NxdWl0dG8xCzAJBgNVBAsMAkNBMRYwFAYDVQQDDA1tb3Nx 5 | dWl0dG8ub3JnMR8wHQYJKoZIhvcNAQkBFhByb2dlckBhdGNob28ub3JnMB4XDTEy 6 | MDYyOTIyMTE1OVoXDTIyMDYyNzIyMTE1OVowgZAxCzAJBgNVBAYTAkdCMRcwFQYD 7 | VQQIDA5Vbml0ZWQgS2luZ2RvbTEOMAwGA1UEBwwFRGVyYnkxEjAQBgNVBAoMCU1v 8 | c3F1aXR0bzELMAkGA1UECwwCQ0ExFjAUBgNVBAMMDW1vc3F1aXR0by5vcmcxHzAd 9 | BgkqhkiG9w0BCQEWEHJvZ2VyQGF0Y2hvby5vcmcwgZ8wDQYJKoZIhvcNAQEBBQAD 10 | gY0AMIGJAoGBAMYkLmX7SqOT/jJCZoQ1NWdCrr/pq47m3xxyXcI+FLEmwbE3R9vM 11 | rE6sRbP2S89pfrCt7iuITXPKycpUcIU0mtcT1OqxGBV2lb6RaOT2gC5pxyGaFJ+h 12 | A+GIbdYKO3JprPxSBoRponZJvDGEZuM3N7p3S/lRoi7G5wG5mvUmaE5RAgMBAAGj 13 | UDBOMB0GA1UdDgQWBBTad2QneVztIPQzRRGj6ZHKqJTv5jAfBgNVHSMEGDAWgBTa 14 | d2QneVztIPQzRRGj6ZHKqJTv5jAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA 15 | A4GBAAqw1rK4NlRUCUBLhEFUQasjP7xfFqlVbE2cRy0Rs4o3KS0JwzQVBwG85xge 16 | REyPOFdGdhBY2P1FNRy0MDr6xr+D2ZOwxs63dG1nnAnWZg7qwoLgpZ4fESPD3PkA 17 | 1ZgKJc2zbSQ9fCPxt2W3mdVav66c6fsb7els2W2Iz7gERJSX 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /test/pem/localhost.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC+zCCAeOgAwIBAgIJAP0ZYn69jsl8MA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV 3 | BAMMCWxvY2FsaG9zdDAeFw0xNzEwMDYwODExNTNaFw0yNzEwMDQwODExNTNaMBQx 4 | EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC 5 | ggEBAKYdpoMzRDvCITh6zG/RdlfEeIaUgs7AAIkr4utRe6d4b/1vUu9Q4hkTHizs 6 | pnh60XsdGZaSvOJ4idw9xWM3nkipJrJEYBejWWBl0VdpKxinO1NWxffJibWPR/Du 7 | I6Tt0/aWk8mZj6Me1Bqdp0F8o/U7xHIeQmc+jb4RAIzocDdPe+xlkqq68H0CmgBd 8 | VbJTgC9wK0M89cxUCbC+ilqkuBz79tR4o44CQCvZi9uNEaj6acem+I5n8bGfY+F+ 9 | lTdHrd28SOW/qX2hPbgIyuSyCdhUMnNWdWCetCqJrhBq2acaQFZdzIoxmGTsQ6b3 10 | kaC4RRGZtdCbUbXFKXT3POYEMjsCAwEAAaNQME4wHQYDVR0OBBYEFLYtcFKysWTW 11 | yqzUxsrq0N0Sa5nZMB8GA1UdIwQYMBaAFLYtcFKysWTWyqzUxsrq0N0Sa5nZMAwG 12 | A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAKEbDSSn2KJDKRM6s0vPiwpF 13 | cXyH2I6hjzHy9O624j7+Zgz7aHUNSPEEYyi/DBgTDTYBQdFfVXuj+NUJM8xwcjC6 14 | 00xRA+V4WiK22b+rXLFSQh65fWQwELfBOuhBxPwGXW7gcSVTBIRWQy6nFfGDbP7H 15 | bRBOvsuEyELzXTrZctCzK8gH0B0LucQaB8slU68Dnp7fxG8Ect26evE+nHfSTiqJ 16 | 4KQ34xkLtuTIcLj9qWVBEegWiFSoi3EJotpH2lv+f9yUv3WfLS++6nc941J8efPQ 17 | PuJAvovnnZ9487ft1sD+gSdRqLXjs5sxwM54WQ1n1K+TB8lJSnz6++NJDknHHfY= 18 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /test/issues/issue209/pem/mosquitto.org.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC8DCCAlmgAwIBAgIJAOD63PlXjJi8MA0GCSqGSIb3DQEBBQUAMIGQMQswCQYD 3 | VQQGEwJHQjEXMBUGA1UECAwOVW5pdGVkIEtpbmdkb20xDjAMBgNVBAcMBURlcmJ5 4 | MRIwEAYDVQQKDAlNb3NxdWl0dG8xCzAJBgNVBAsMAkNBMRYwFAYDVQQDDA1tb3Nx 5 | dWl0dG8ub3JnMR8wHQYJKoZIhvcNAQkBFhByb2dlckBhdGNob28ub3JnMB4XDTEy 6 | MDYyOTIyMTE1OVoXDTIyMDYyNzIyMTE1OVowgZAxCzAJBgNVBAYTAkdCMRcwFQYD 7 | VQQIDA5Vbml0ZWQgS2luZ2RvbTEOMAwGA1UEBwwFRGVyYnkxEjAQBgNVBAoMCU1v 8 | c3F1aXR0bzELMAkGA1UECwwCQ0ExFjAUBgNVBAMMDW1vc3F1aXR0by5vcmcxHzAd 9 | BgkqhkiG9w0BCQEWEHJvZ2VyQGF0Y2hvby5vcmcwgZ8wDQYJKoZIhvcNAQEBBQAD 10 | gY0AMIGJAoGBAMYkLmX7SqOT/jJCZoQ1NWdCrr/pq47m3xxyXcI+FLEmwbE3R9vM 11 | rE6sRbP2S89pfrCt7iuITXPKycpUcIU0mtcT1OqxGBV2lb6RaOT2gC5pxyGaFJ+h 12 | A+GIbdYKO3JprPxSBoRponZJvDGEZuM3N7p3S/lRoi7G5wG5mvUmaE5RAgMBAAGj 13 | UDBOMB0GA1UdDgQWBBTad2QneVztIPQzRRGj6ZHKqJTv5jAfBgNVHSMEGDAWgBTa 14 | d2QneVztIPQzRRGj6ZHKqJTv5jAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA 15 | A4GBAAqw1rK4NlRUCUBLhEFUQasjP7xfFqlVbE2cRy0Rs4o3KS0JwzQVBwG85xge 16 | REyPOFdGdhBY2P1FNRy0MDr6xr+D2ZOwxs63dG1nnAnWZg7qwoLgpZ4fESPD3PkA 17 | 1ZgKJc2zbSQ9fCPxt2W3mdVav66c6fsb7els2W2Iz7gERJSX 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /lib/src/messages/mqtt_client_mqtt_message_type.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../mqtt_client.dart'; 9 | 10 | /// An enumeration of all available MQTT Message Types 11 | enum MqttMessageType { 12 | /// Reserved by the MQTT spec, should not be used. 13 | reserved1, 14 | 15 | /// Connect 16 | connect, 17 | 18 | /// Connect acknowledge 19 | connectAck, 20 | 21 | /// Publish 22 | publish, 23 | 24 | /// Publish acknowledge 25 | publishAck, 26 | 27 | /// Publish recieved 28 | publishReceived, 29 | 30 | /// Publish release 31 | publishRelease, 32 | 33 | /// Publish complete 34 | publishComplete, 35 | 36 | /// Subscribe 37 | subscribe, 38 | 39 | /// Subscribe acknowledge 40 | subscribeAck, 41 | 42 | /// Unsubscribe 43 | unsubscribe, 44 | 45 | /// Unsubscribe acknowledge 46 | unsubscribeAck, 47 | 48 | /// Ping request 49 | pingRequest, 50 | 51 | /// Ping response 52 | pingResponse, 53 | 54 | /// Disconnect 55 | disconnect, 56 | 57 | /// Reserved by the MQTT spec, should not be used. 58 | reserved2, 59 | } 60 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: mqtt_client 2 | description: A server and browser based MQTT client for Dart supporting normal, secure sockets and websockets. 3 | version: 10.11.3 4 | repository: https://github.com/shamblett/mqtt_client 5 | homepage: https://github.com/shamblett/mqtt_client 6 | 7 | funding: 8 | - https://www.darticulate.com/#funding 9 | 10 | environment: 11 | sdk: '>=3.8.0 <4.0.0' 12 | 13 | dependencies: 14 | typed_data: '^1.4.0' 15 | event_bus: '^2.0.1' 16 | path: '^1.9.1' 17 | crypto: '^3.0.6' 18 | meta: '^1.16.0' 19 | web: '>=0.5.0 <2.0.0' 20 | characters: '^1.4.0' 21 | 22 | dev_dependencies: 23 | test: '^1.28.0' 24 | lints: '^6.0.0' 25 | build_runner: '^2.10.4' 26 | build_test: '^3.5.4' 27 | build_web_compilers: '^4.1.0' 28 | mocktail: '^1.0.4' 29 | http: '^1.6.0' 30 | aws_signature_v4: '^0.6.9' 31 | aws_common: '^0.7.11' 32 | 33 | false_secrets: 34 | - /test/pem/self_signed.key 35 | - /test/pem/localhost.key 36 | 37 | topics: 38 | - iot 39 | - chat 40 | - mqtt 41 | - data 42 | 43 | screenshots: 44 | - description: 'MQTT Specification' 45 | path: example/screenshots/mqtt-ver.webp 46 | - description: 'MQTT IOT Usage' 47 | path: example/screenshots/MQTT-1.webp -------------------------------------------------------------------------------- /lib/mqtt_server_client.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_server_client 3 | * Author : S. Hamblett 4 | * Date : 20/01/2020 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | library; 9 | 10 | import 'dart:async'; 11 | import 'dart:convert'; 12 | import 'dart:io'; 13 | import 'dart:typed_data'; 14 | import 'package:crypto/crypto.dart'; 15 | import 'package:event_bus/event_bus.dart' as events; 16 | import 'package:meta/meta.dart'; 17 | import 'package:typed_data/typed_data.dart' as typed; 18 | import 'package:characters/characters.dart'; 19 | import 'mqtt_client.dart'; 20 | 21 | part 'src/connectionhandling/server/mqtt_client_mqtt_server_connection_handler.dart'; 22 | part 'src/connectionhandling/server/mqtt_client_mqtt_server_normal_connection.dart'; 23 | part 'src/connectionhandling/server/mqtt_client_mqtt_server_secure_connection.dart'; 24 | part 'src/connectionhandling/server/mqtt_client_mqtt_server_ws2_connection.dart'; 25 | part 'src/connectionhandling/server/mqtt_client_mqtt_server_ws_connection.dart'; 26 | part 'src/connectionhandling/server/mqtt_client_synchronous_mqtt_server_connection_handler.dart'; 27 | part 'src/connectionhandling/server/mqtt_client_mqtt_server_connection.dart'; 28 | part 'src/mqtt_server_client.dart'; 29 | -------------------------------------------------------------------------------- /lib/src/connectionhandling/mqtt_client_connection_state.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../mqtt_client.dart'; 9 | 10 | /// Enumeration that indicates the origin of a client disconnection 11 | enum MqttDisconnectionOrigin { 12 | /// Unsolicited, i.e. not requested by the client, 13 | /// for example a broker/network initiated disconnect 14 | unsolicited, 15 | 16 | /// Solicited, i.e. requested by the client, 17 | /// for example disconnect called on the client. 18 | solicited, 19 | 20 | /// None set 21 | none, 22 | } 23 | 24 | /// Enumeration that indicates various client connection states 25 | enum MqttConnectionState { 26 | /// The MQTT Connection is in the process of disconnecting from the broker. 27 | disconnecting, 28 | 29 | /// MQTT Connection is not currently connected to any broker. 30 | disconnected, 31 | 32 | /// The MQTT Connection is in the process of connecting to the broker. 33 | connecting, 34 | 35 | /// The MQTT Connection is currently connected to the broker. 36 | connected, 37 | 38 | /// The MQTT Connection is faulted and no longer communicating 39 | /// with the broker. 40 | faulted, 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/mqtt_client_message_identifier_dispenser.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 30/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../mqtt_client.dart'; 9 | 10 | /// Message identifier handling 11 | class MessageIdentifierDispenser { 12 | /// Maximum message identifier 13 | static const int maxMessageIdentifier = 32768; 14 | 15 | /// Initial value 16 | static const int initialValue = 0; 17 | 18 | /// Minimum message identifier 19 | static const int startMessageIdentifier = 1; 20 | 21 | static final MessageIdentifierDispenser _singleton = 22 | MessageIdentifierDispenser._internal(); 23 | 24 | // Message identifier, zero is forbidden 25 | int _mid = initialValue; 26 | 27 | /// Mid 28 | int get mid => _mid; 29 | 30 | MessageIdentifierDispenser._internal(); 31 | 32 | /// Factory constructor 33 | factory MessageIdentifierDispenser() => _singleton; 34 | 35 | /// Gets the next message identifier 36 | int getNextMessageIdentifier() { 37 | _mid++; 38 | if (_mid == maxMessageIdentifier) { 39 | _mid = startMessageIdentifier; 40 | } 41 | return mid; 42 | } 43 | 44 | /// Resets the mid 45 | void reset() { 46 | _mid = initialValue; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test/issues/issue281.dart: -------------------------------------------------------------------------------- 1 | import 'package:mqtt_client/mqtt_client.dart'; 2 | import 'package:mqtt_client/mqtt_server_client.dart'; 3 | 4 | Future main() async { 5 | final client = MqttServerClient('mqtt.hsl.fi', 'SJH-TEST'); 6 | client.keepAlivePeriod = 60; 7 | client.onConnected = onConnected; 8 | client.onDisconnected = onDisconnected; 9 | client.logging(on: false); 10 | client.setProtocolV311(); 11 | 12 | // let's connect to mqtt broker 13 | try { 14 | client.autoReconnect = true; 15 | await client.connect(); 16 | client.subscribe("/hfp/v2/journey/#", MqttQos.atMostOnce); 17 | client.updates!.listen((List>? c) { 18 | final recMess = c![0].payload as MqttPublishMessage; 19 | final pt = MqttPublishPayload.bytesToStringAsString( 20 | recMess.payload.message!, 21 | ); 22 | print( 23 | 'EXAMPLE::Change notification:: topic is <${c[0].topic}>, payload is <-- ${pt.length} -->', 24 | ); 25 | print(''); 26 | }); 27 | } on NoConnectionException catch (e) { 28 | print(e.toString()); 29 | } 30 | 31 | await MqttUtilities.asyncSleep(20); 32 | } 33 | 34 | void onDisconnected() { 35 | print('Disconnected'); 36 | } 37 | 38 | void onConnected() { 39 | print('Connected'); 40 | } 41 | -------------------------------------------------------------------------------- /test/issues/issue56.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'package:mqtt_client/mqtt_client.dart'; 4 | import 'package:mqtt_client/mqtt_server_client.dart'; 5 | 6 | Future main() async { 7 | const broker = 'mq.meeo.xyz'; 8 | const username = 'md-hi75gqj'; 9 | const password = 'user_K8SzwBbLqBEwfIqM'; 10 | final client = MqttServerClient(broker, ''); 11 | client.logging(on: false); 12 | client.onDisconnected = onDisconnected; 13 | final connMess = MqttConnectMessage() 14 | .withClientIdentifier('Mqtt_MyClientUniqueId') 15 | .keepAliveFor(20) // Must agree with the keep alive set above or not set 16 | .withWillTopic('willtopic') // If you set this you must set a will message 17 | .withWillMessage('My Will message') 18 | .startClean() // Non persistent session for testing 19 | .withWillQos(MqttQos.atLeastOnce); 20 | print('EXAMPLE::Mosquitto client connecting....'); 21 | client.connectionMessage = connMess; 22 | try { 23 | await client.connect(username, password); 24 | } on Exception catch (e) { 25 | print('ERROR: ${e.toString()}'); 26 | exit(-1); 27 | } 28 | 29 | return 0; 30 | } 31 | 32 | /// The unsolicited disconnect callback 33 | void onDisconnected() { 34 | print('EXAMPLE::OnDisconnected client callback - Client disconnection'); 35 | } 36 | -------------------------------------------------------------------------------- /test/issues/persistence/receiver_nosubscribe.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | import 'dart:async'; 9 | import 'package:mqtt_client/mqtt_client.dart'; 10 | import 'package:mqtt_client/mqtt_server_client.dart'; 11 | 12 | Future main() async { 13 | final client = MqttServerClient.withPort('localhost', 'SJHIssueRx', 1883); 14 | 15 | client.logging(on: false); 16 | client.setProtocolV311(); 17 | const topic = 'counter'; 18 | final connMess = MqttConnectMessage(); 19 | client.connectionMessage = connMess; 20 | 21 | print('ISSUE: Connecting'); 22 | await client.connect(); 23 | 24 | // Listen for the counter messages 25 | print('ISSUE::Listening......'); 26 | client.updates?.listen((List> c) { 27 | final recMess = c[0].payload as MqttPublishMessage; 28 | final payload = recMess.payload.message; 29 | if (payload != null) { 30 | final counterValue = payload[0]; 31 | print('ISSUE::Change notification:: counter received is $counterValue'); 32 | } else { 33 | print('ISSUE - ERROR payload is null'); 34 | } 35 | }); 36 | await MqttUtilities.asyncSleep(60); 37 | 38 | print('ISSUE: Test complete'); 39 | client.disconnect(); 40 | 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /test/issues/issue53.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:io'; 4 | import 'dart:math'; 5 | import 'package:mqtt_client/mqtt_client.dart'; 6 | import 'package:mqtt_client/mqtt_server_client.dart'; 7 | 8 | Future main() async { 9 | MqttServerClient client; 10 | 11 | const clientId = 'SJHMQTTTest_53'; 12 | client = MqttServerClient('test.mosquitto.org', clientId); 13 | client.setProtocolV311(); 14 | client.keepAlivePeriod = 20; 15 | client.port = 1883; 16 | client.logging(on: false); 17 | 18 | client.onDisconnected = () { 19 | print('==> Disconnected | Time: ${DateTime.now().toUtc()}'); 20 | client.disconnect(); 21 | exit(-1); 22 | }; 23 | 24 | client.connectionMessage = MqttConnectMessage().withClientIdentifier( 25 | clientId, 26 | ); 27 | 28 | client.connectionMessage.startClean(); 29 | 30 | await client.connect(); 31 | 32 | final builder = MqttClientPayloadBuilder(); 33 | builder.addString( 34 | json.encode({ 35 | 'type': 'msgText', 36 | 'data': 'message data', 37 | 'identifier': Random().nextInt(1000000), 38 | }), 39 | ); 40 | 41 | client.publishMessage('u\SJHTest', MqttQos.exactlyOnce, builder.payload); 42 | 43 | print('Sleeping...'); 44 | await MqttUtilities.asyncSleep(120); 45 | 46 | print('Client exiting'); 47 | 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /lib/src/messages/subscribe/mqtt_client_mqtt_subscribe_variable_header.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 19/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_client.dart'; 9 | 10 | /// Implementation of the variable header for an MQTT Subscribe message. 11 | class MqttSubscribeVariableHeader extends MqttVariableHeader { 12 | /// Initializes a new instance of the MqttSubscribeVariableHeader class. 13 | MqttSubscribeVariableHeader(); 14 | 15 | /// Initializes a new instance of the MqttSubscribeVariableHeader class. 16 | MqttSubscribeVariableHeader.fromByteBuffer(MqttByteBuffer headerStream) { 17 | readFrom(headerStream); 18 | } 19 | 20 | /// Creates a variable header from the specified header stream. 21 | @override 22 | void readFrom(MqttByteBuffer variableHeaderStream) { 23 | readMessageIdentifier(variableHeaderStream); 24 | } 25 | 26 | /// Writes the variable header to the supplied stream. 27 | @override 28 | void writeTo(MqttByteBuffer variableHeaderStream) { 29 | writeMessageIdentifier(variableHeaderStream); 30 | } 31 | 32 | /// Gets the length of the write data when WriteTo will be called. 33 | @override 34 | int getWriteLength() => 2; 35 | 36 | @override 37 | String toString() => 38 | 'Subscribe Variable Header: MessageIdentifier={$messageIdentifier}'; 39 | } 40 | -------------------------------------------------------------------------------- /test/support/mqtt_client_broker_test_publish.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 11/07/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | import 'dart:async'; 8 | import 'package:typed_data/typed_data.dart' as typed; 9 | import 'package:mqtt_client/mqtt_client.dart'; 10 | import 'package:mqtt_client/mqtt_server_client.dart'; 11 | 12 | Future main() async { 13 | // Create and connect the client 14 | final client = MqttServerClient('iot.eclipse.org', 'SJHMQTTClient'); 15 | client.logging(on: false); 16 | await client.connect(); 17 | if (client.connectionStatus!.state == MqttConnectionState.connected) { 18 | print('Mosquitto client connected'); 19 | } else { 20 | print( 21 | 'ERROR Mosquitto client connection failed - disconnecting, state is ${client.connectionStatus}', 22 | ); 23 | client.disconnect(); 24 | } 25 | // Publish a known topic 26 | const topic = 'Dart/SJH/mqtt_client'; 27 | final buff = typed.Uint8Buffer(5); 28 | buff[0] = 'h'.codeUnitAt(0); 29 | buff[1] = 'e'.codeUnitAt(0); 30 | buff[2] = 'l'.codeUnitAt(0); 31 | buff[3] = 'l'.codeUnitAt(0); 32 | buff[4] = 'o'.codeUnitAt(0); 33 | client.publishMessage(topic, MqttQos.exactlyOnce, buff); 34 | print('Sleeping....'); 35 | await MqttUtilities.asyncSleep(10); 36 | print('Disconnecting'); 37 | client.disconnect(); 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /lib/src/messages/subscribeack/mqtt_client_mqtt_subscribe_ack_variable_header.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 19/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_client.dart'; 9 | 10 | /// Implementation of the variable header for an MQTT Subscribe Ack message. 11 | class MqttSubscribeAckVariableHeader extends MqttVariableHeader { 12 | /// Initializes a new instance of the MqttSubscribeAckVariableHeader class. 13 | MqttSubscribeAckVariableHeader(); 14 | 15 | /// Initializes a new instance of the MqttSubscribeAckVariableHeader class. 16 | MqttSubscribeAckVariableHeader.fromByteBuffer(MqttByteBuffer headerStream) { 17 | readFrom(headerStream); 18 | } 19 | 20 | /// Creates a variable header from the specified header stream. 21 | @override 22 | void readFrom(MqttByteBuffer variableHeaderStream) { 23 | readMessageIdentifier(variableHeaderStream); 24 | } 25 | 26 | /// Writes the variable header to the supplied stream. 27 | @override 28 | void writeTo(MqttByteBuffer variableHeaderStream) { 29 | writeMessageIdentifier(variableHeaderStream); 30 | } 31 | 32 | /// Gets the length of the write data when WriteTo will be called. 33 | @override 34 | int getWriteLength() => 2; 35 | 36 | @override 37 | String toString() => 38 | 'SubscribeAck Variable Header: MessageIdentifier={$messageIdentifier}'; 39 | } 40 | -------------------------------------------------------------------------------- /lib/src/messages/publishack/mqtt_client_mqtt_publish_ack_variable_header.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 19/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_client.dart'; 9 | 10 | /// Implementation of the variable header for an MQTT Publish 11 | /// Acknowledgement message. 12 | class MqttPublishAckVariableHeader extends MqttVariableHeader { 13 | /// Initializes a new instance of the [MqttPublishAckVariableHeader] class. 14 | MqttPublishAckVariableHeader(); 15 | 16 | /// Initializes a new instance of the [MqttPublishAckVariableHeader] class. 17 | MqttPublishAckVariableHeader.fromByteBuffer(MqttByteBuffer headerStream) { 18 | readFrom(headerStream); 19 | } 20 | 21 | /// Creates a variable header from the specified header stream. 22 | @override 23 | void readFrom(MqttByteBuffer variableHeaderStream) { 24 | readMessageIdentifier(variableHeaderStream); 25 | } 26 | 27 | /// Writes the variable header to the supplied stream. 28 | @override 29 | void writeTo(MqttByteBuffer variableHeaderStream) { 30 | writeMessageIdentifier(variableHeaderStream); 31 | } 32 | 33 | /// Gets the length of the write data when WriteTo will be called. 34 | @override 35 | int getWriteLength() => 2; 36 | 37 | @override 38 | String toString() => 39 | 'PublishAck Variable Header: MessageIdentifier={$messageIdentifier}'; 40 | } 41 | -------------------------------------------------------------------------------- /lib/src/messages/publishrelease/mqtt_client_mqtt_publish_release_variable_header.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 19/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_client.dart'; 9 | 10 | /// Implementation of the variable header for an MQTT Publish Release message. 11 | class MqttPublishReleaseVariableHeader extends MqttVariableHeader { 12 | /// Initializes a new instance of the MqttPublishReleaseVariableHeader class. 13 | MqttPublishReleaseVariableHeader(); 14 | 15 | /// Initializes a new instance of the MqttPublishReleaseVariableHeader class. 16 | MqttPublishReleaseVariableHeader.fromByteBuffer(MqttByteBuffer headerStream) { 17 | readFrom(headerStream); 18 | } 19 | 20 | /// Creates a variable header from the specified header stream. 21 | @override 22 | void readFrom(MqttByteBuffer variableHeaderStream) { 23 | readMessageIdentifier(variableHeaderStream); 24 | } 25 | 26 | /// Writes the variable header to the supplied stream. 27 | @override 28 | void writeTo(MqttByteBuffer variableHeaderStream) { 29 | writeMessageIdentifier(variableHeaderStream); 30 | } 31 | 32 | /// Gets the length of the write data when WriteTo will be called. 33 | @override 34 | int getWriteLength() => 2; 35 | 36 | @override 37 | String toString() => 38 | 'PublishRelease Variable Header: MessageIdentifier={$messageIdentifier}'; 39 | } 40 | -------------------------------------------------------------------------------- /lib/src/messages/unsubscribe/mqtt_client_mqtt_unsubscribe_variable_header.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 19/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_client.dart'; 9 | 10 | /// Implementation of the variable header for an MQTT Unsubscribe message. 11 | class MqttUnsubscribeVariableHeader extends MqttVariableHeader { 12 | /// Initializes a new instance of the MqttUnsubscribeVariableHeader class. 13 | MqttUnsubscribeVariableHeader(); 14 | 15 | /// Initializes a new instance of the MqttUnsubscribeVariableHeader class. 16 | MqttUnsubscribeVariableHeader.fromByteBuffer(MqttByteBuffer headerStream) { 17 | readFrom(headerStream); 18 | } 19 | 20 | /// Creates a variable header from the specified header stream. 21 | @override 22 | void readFrom(MqttByteBuffer variableHeaderStream) { 23 | readMessageIdentifier(variableHeaderStream); 24 | } 25 | 26 | /// Writes the variable header to the supplied stream. 27 | @override 28 | void writeTo(MqttByteBuffer variableHeaderStream) { 29 | writeMessageIdentifier(variableHeaderStream); 30 | } 31 | 32 | /// Gets the length of the write data when WriteTo will be called. 33 | @override 34 | int getWriteLength() => 2; 35 | 36 | @override 37 | String toString() => 38 | 'Unsubscribe VariableHeader Variable Header: ' 39 | 'MessageIdentifier={$messageIdentifier}'; 40 | } 41 | -------------------------------------------------------------------------------- /lib/src/messages/unsubscribeack/mqtt_client_mqtt_unsubscribe_ack_variable_header.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 19/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_client.dart'; 9 | 10 | /// Implementation of the variable header for an MQTT Unsubscribe Ack message. 11 | class MqttUnsubscribeAckVariableHeader extends MqttVariableHeader { 12 | /// Initializes a new instance of the MqttUnsubscribeAckVariableHeader class. 13 | MqttUnsubscribeAckVariableHeader(); 14 | 15 | /// Initializes a new instance of the MqttUnsubscribeAckVariableHeader class. 16 | MqttUnsubscribeAckVariableHeader.fromByteBuffer(MqttByteBuffer headerStream) { 17 | readFrom(headerStream); 18 | } 19 | 20 | /// Creates a variable header from the specified header stream. 21 | @override 22 | void readFrom(MqttByteBuffer variableHeaderStream) { 23 | readMessageIdentifier(variableHeaderStream); 24 | } 25 | 26 | /// Writes the variable header to the supplied stream. 27 | @override 28 | void writeTo(MqttByteBuffer variableHeaderStream) { 29 | writeMessageIdentifier(variableHeaderStream); 30 | } 31 | 32 | /// Gets the length of the write data when WriteTo will be called. 33 | @override 34 | int getWriteLength() => 2; 35 | 36 | @override 37 | String toString() => 38 | 'UnsubscribeAck Variable Header: MessageIdentifier={$messageIdentifier}'; 39 | } 40 | -------------------------------------------------------------------------------- /test/support/mqtt_client_broker_test_ws_publish.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 11/07/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | import 'dart:async'; 8 | import 'package:typed_data/typed_data.dart' as typed; 9 | import 'package:mqtt_client/mqtt_client.dart'; 10 | import 'package:mqtt_client/mqtt_server_client.dart'; 11 | 12 | Future main() async { 13 | // Create and connect the client 14 | final client = MqttServerClient('ws://iot.eclipse.org/ws', 'SJHMQTTClient'); 15 | client.useWebSocket = true; 16 | client.port = 80; 17 | client.logging(on: false); 18 | await client.connect(); 19 | if (client.connectionStatus!.state == MqttConnectionState.connected) { 20 | print('Mosquitto client connected'); 21 | } else { 22 | print( 23 | 'ERROR Mosquitto client connection failed - disconnecting, state is ${client.connectionStatus}', 24 | ); 25 | client.disconnect(); 26 | } 27 | // Publish a known topic 28 | const topic = 'Dart/SJH/mqtt_client'; 29 | final buff = typed.Uint8Buffer(5); 30 | buff[0] = 'h'.codeUnitAt(0); 31 | buff[1] = 'e'.codeUnitAt(0); 32 | buff[2] = 'l'.codeUnitAt(0); 33 | buff[3] = 'l'.codeUnitAt(0); 34 | buff[4] = 'o'.codeUnitAt(0); 35 | client.publishMessage(topic, MqttQos.exactlyOnce, buff); 36 | print('Sleeping....'); 37 | await MqttUtilities.asyncSleep(10); 38 | print('Disconnecting'); 39 | client.disconnect(); 40 | return 0; 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/messages/publishcomplete/mqtt_client_mqtt_publish_complete_variable_header.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 19/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_client.dart'; 9 | 10 | /// Implementation of the variable header for an MQTT Publish Complete message. 11 | class MqttPublishCompleteVariableHeader extends MqttVariableHeader { 12 | /// Initializes a new instance of the MqttPublishCompleteVariableHeader class. 13 | MqttPublishCompleteVariableHeader(); 14 | 15 | /// Initializes a new instance of the MqttPublishCompleteVariableHeader class. 16 | MqttPublishCompleteVariableHeader.fromByteBuffer( 17 | MqttByteBuffer headerStream, 18 | ) { 19 | readFrom(headerStream); 20 | } 21 | 22 | /// Creates a variable header from the specified header stream. 23 | @override 24 | void readFrom(MqttByteBuffer variableHeaderStream) { 25 | readMessageIdentifier(variableHeaderStream); 26 | } 27 | 28 | /// Writes the variable header to the supplied stream. 29 | @override 30 | void writeTo(MqttByteBuffer variableHeaderStream) { 31 | writeMessageIdentifier(variableHeaderStream); 32 | } 33 | 34 | /// Gets the length of the write data when WriteTo will be called. 35 | @override 36 | int getWriteLength() => 2; 37 | 38 | @override 39 | String toString() => 40 | 'PublishComplete Variable Header: MessageIdentifier={$messageIdentifier}'; 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/messages/publishreceived/mqtt_client_mqtt_publish_received_variable_header.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 19/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_client.dart'; 9 | 10 | /// Implementation of the variable header for an MQTT Publish Received message. 11 | class MqttPublishReceivedVariableHeader extends MqttVariableHeader { 12 | /// Initializes a new instance of the MqttPublishCompleteVariableHeader class. 13 | MqttPublishReceivedVariableHeader(); 14 | 15 | /// Initializes a new instance of the MqttPublishReceivedVariableHeader class. 16 | MqttPublishReceivedVariableHeader.fromByteBuffer( 17 | MqttByteBuffer headerStream, 18 | ) { 19 | readFrom(headerStream); 20 | } 21 | 22 | /// Creates a variable header from the specified header stream. 23 | @override 24 | void readFrom(MqttByteBuffer variableHeaderStream) { 25 | readMessageIdentifier(variableHeaderStream); 26 | } 27 | 28 | /// Writes the variable header to the supplied stream. 29 | @override 30 | void writeTo(MqttByteBuffer variableHeaderStream) { 31 | writeMessageIdentifier(variableHeaderStream); 32 | } 33 | 34 | /// Gets the length of the write data when WriteTo will be called. 35 | @override 36 | int getWriteLength() => 2; 37 | 38 | @override 39 | String toString() => 40 | 'PublishReceived Variable Header: MessageIdentifier={$messageIdentifier}'; 41 | } 42 | -------------------------------------------------------------------------------- /test/issues/issue209/issue209-normal-noack.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | import 'dart:async'; 9 | import 'dart:io'; 10 | import 'package:mqtt_client/mqtt_client.dart'; 11 | import 'package:mqtt_client/mqtt_server_client.dart'; 12 | import 'package:test/test.dart'; 13 | 14 | Future main() async { 15 | test('Should try three times then fail', () async { 16 | final client = MqttServerClient.withPort( 17 | 'test.mosquitto.org', 18 | 'client-id-123456789', 19 | 1883, 20 | ); 21 | client.autoReconnect = true; 22 | client.logging(on: false); 23 | 24 | // Main test starts here 25 | print('ISSUE: Main test start'); 26 | var exceptionOK = false; 27 | try { 28 | await client.connect('user', 'password'); 29 | } on NoConnectionException catch (e) { 30 | expect( 31 | e.toString(), 32 | 'mqtt-client::NoConnectionException: The maximum allowed connection attempts ' 33 | '({3}) were exceeded. ' 34 | 'The broker is not responding to the connection request message ' 35 | 'correctly The return code is MqttConnectReturnCode.notAuthorized', 36 | ); 37 | exceptionOK = true; 38 | } 39 | expect(exceptionOK, isTrue); 40 | expect(client.connectionStatus.state, MqttConnectionState.faulted); 41 | print('ISSUE: Test complete'); 42 | }); 43 | 44 | return 0; 45 | } 46 | -------------------------------------------------------------------------------- /lib/src/dataconvertors/mqtt_client_payload_convertor.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../mqtt_client.dart'; 9 | 10 | /// Interface that defines the methods and properties that must be provided 11 | /// by classes that interpret and convert inbound and outbound 12 | /// published message data. 13 | /// 14 | /// Types that implement this interface should be aware that for the 15 | /// purposes of converting data from published messages 16 | /// (byte array to object model) that the MqttSubscriptionsManager 17 | /// creates a single instance of the data converter and uses it for 18 | /// all messages that are received. 19 | /// 20 | /// The same is true for the publishing of data to a broker. 21 | /// The PublishingManager will also cache instances of the converters 22 | /// until the MqttClient is disposed. 23 | /// This means, in both cases you can store state in the data 24 | /// converters if you wish, and that state will persist between messages 25 | /// received or published, but only a default empty constructor is 26 | /// supported. 27 | abstract class PayloadConverter { 28 | /// Converts received data from a raw byte array to an object graph. 29 | T convertFromBytes(typed.Uint8Buffer messageData); 30 | 31 | /// Converts sent data from an object graph to a byte array. 32 | typed.Uint8Buffer convertToBytes(T data); 33 | } 34 | -------------------------------------------------------------------------------- /lib/src/utility/mqtt_client_logger.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 28/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../mqtt_client.dart'; 9 | 10 | /// Library wide logging class 11 | class MqttLogger { 12 | /// Log or not 13 | static bool loggingOn = false; 14 | 15 | /// Unique per client identifier 16 | static int clientId = 0; 17 | 18 | /// Test output 19 | static String testOutput = ''; 20 | 21 | /// Log publish message payload data 22 | static bool logPayloads = true; 23 | 24 | static bool _testMode = false; 25 | 26 | /// Test mode 27 | static bool get testMode => _testMode; 28 | 29 | static set testMode(bool state) { 30 | _testMode = state; 31 | testOutput = ''; 32 | } 33 | 34 | /// Log method 35 | /// If the optimise parameter is supplied it must have a toString method, 36 | /// this allows large objects such as lots of payload data not to be 37 | /// converted into a string in the message parameter if logging is not enabled. 38 | static void log(String message, [dynamic optimise = false]) { 39 | if (loggingOn) { 40 | final now = DateTime.now(); 41 | var output = ''; 42 | output = optimise is bool 43 | ? '${clientId.toString()}-$now -- $message' 44 | : '${clientId.toString()}-$now -- $message$optimise'; 45 | if (testMode) { 46 | testOutput = output; 47 | } else { 48 | print(output); 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/support/mqtt_client_broker_test_subscribe.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 11/07/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | import 'dart:async'; 9 | import 'package:mqtt_client/mqtt_client.dart'; 10 | import 'package:mqtt_client/mqtt_server_client.dart'; 11 | 12 | Future main() async { 13 | // Create and connect the client 14 | final client = MqttServerClient('iot.eclipse.org', 'SJHMQTTClient'); 15 | client.logging(on: false); 16 | await client.connect(); 17 | if (client.connectionStatus!.state == MqttConnectionState.connected) { 18 | print('Mosquitto client connected'); 19 | } else { 20 | print( 21 | 'ERROR Mosquitto client connection failed - disconnecting, state is ${client.connectionStatus}', 22 | ); 23 | client.disconnect(); 24 | } 25 | // Subscribe to a known topic 26 | const topic = 'test/hw'; 27 | client.subscribe(topic, MqttQos.exactlyOnce); 28 | client.updates!.listen((List>? c) { 29 | final recMess = c![0].payload as MqttPublishMessage; 30 | final pt = MqttPublishPayload.bytesToStringAsString( 31 | recMess.payload.message, 32 | ); 33 | print('Change notification:: payload is <$pt> for topic <$topic>'); 34 | }); 35 | print('Sleeping....'); 36 | await MqttUtilities.asyncSleep(90); 37 | print('Unsubscribing'); 38 | client.unsubscribe(topic); 39 | await MqttUtilities.asyncSleep(2); 40 | print('Disconnecting'); 41 | client.disconnect(); 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /test/issues/persistence/generator.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'package:mqtt_client/mqtt_client.dart'; 4 | import 'package:mqtt_client/mqtt_server_client.dart'; 5 | 6 | const hostName = 'localhost'; 7 | 8 | Future main() async { 9 | final client = MqttServerClient.withPort(hostName, 'SJHIssueTx', 1883); 10 | client.logging(on: false); 11 | client.setProtocolV311(); 12 | final connMess = MqttConnectMessage(); 13 | client.connectionMessage = connMess; 14 | const topic = 'counter'; 15 | 16 | print('ISSUE:: client connecting....'); 17 | try { 18 | await client.connect(); 19 | } on Exception catch (e) { 20 | print('EXAMPLE::client exception - $e'); 21 | client.disconnect(); 22 | } 23 | 24 | /// Check we are connected 25 | if (client.connectionStatus!.state == MqttConnectionState.connected) { 26 | print('ISSUE:: client connected'); 27 | } else { 28 | print( 29 | 'ISSUE::ERROR client connection failed - disconnecting, state is ${client.connectionStatus!.state}', 30 | ); 31 | client.disconnect(); 32 | exit(-1); 33 | } 34 | 35 | // Send the counter values 36 | for (var x = 1; x < 100; x++) { 37 | await MqttUtilities.asyncSleep(1); 38 | final builder = MqttClientPayloadBuilder(); 39 | builder.addByte(x); 40 | print('ISSUE:: Publishing counter value ${builder.payload!}'); 41 | client.publishMessage(topic, MqttQos.atLeastOnce, builder.payload!); 42 | } 43 | 44 | await MqttUtilities.asyncSleep(2); 45 | print('ISSUE::Disconnecting'); 46 | client.disconnect(); 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /test/issues/persistence/receiver_subscribe.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | import 'dart:async'; 9 | import 'package:mqtt_client/mqtt_client.dart'; 10 | import 'package:mqtt_client/mqtt_server_client.dart'; 11 | 12 | Future main() async { 13 | final client = MqttServerClient.withPort('localhost', 'SJHIssueRx', 1883); 14 | 15 | client.logging(on: false); 16 | client.setProtocolV311(); 17 | const topic = 'counter'; 18 | final connMess = MqttConnectMessage(); 19 | client.connectionMessage = connMess; 20 | 21 | print('ISSUE: Connecting'); 22 | await client.connect(); 23 | 24 | // Subscribe to counter, Qos 1 25 | client.subscribe(topic, MqttQos.atLeastOnce); 26 | print( 27 | 'EXAMPLE:: Sleeping to allow the subscription acknowledges to be received....', 28 | ); 29 | await MqttUtilities.asyncSleep(2); 30 | 31 | // Listen for the counter messages 32 | print('ISSUE::Listening......'); 33 | client.updates?.listen((List> c) { 34 | final recMess = c[0].payload as MqttPublishMessage; 35 | final payload = recMess.payload.message; 36 | if (payload != null) { 37 | final counterValue = payload[0]; 38 | print('ISSUE::Change notification:: counter received is $counterValue'); 39 | } else { 40 | print('ISSUE - ERROR payload is null'); 41 | } 42 | }); 43 | await MqttUtilities.asyncSleep(60); 44 | 45 | print('ISSUE: Test complete'); 46 | client.disconnect(); 47 | 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /test/issues/issue602/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:mqtt_client/mqtt_client.dart'; 4 | import 'package:mqtt_client/mqtt_server_client.dart'; 5 | 6 | void main() async { 7 | final client = MqttServerClient('localhost', 'dart_client1111'); 8 | client.port = 1883; 9 | client.logging(on: false, logPayloads: false); 10 | final connMess = MqttConnectMessage(); 11 | 12 | client.connectionMessage = connMess; 13 | 14 | int i = 0; 15 | 16 | client.onConnected = () { 17 | print('Connected to broker'); 18 | client.subscribe('station1/all', MqttQos.atMostOnce); 19 | }; 20 | 21 | client.onDisconnected = () { 22 | print('Disconnected from broker'); 23 | }; 24 | 25 | try { 26 | print('Connecting...'); 27 | final connResult = await client.connect(); 28 | 29 | if (connResult?.state == MqttConnectionState.connected) { 30 | print('Connected successfully'); 31 | 32 | // Now it's safe to listen 33 | client.updates?.listen((List> c) { 34 | i++; 35 | print(i); 36 | 37 | // Optional: read message payload 38 | // final payload = 39 | // MqttPublishPayload.bytesToStringAsString(recMess.payload.message); 40 | // print('Message received: $payload'); 41 | }); 42 | } else { 43 | print('Connection failed - state is ${connResult?.state}'); 44 | client.disconnect(); 45 | } 46 | } catch (e) { 47 | print('Exception during connection: $e'); 48 | client.disconnect(); 49 | return; 50 | } 51 | 52 | // Keep running 53 | await Future.delayed(Duration(days: 365)); 54 | } 55 | -------------------------------------------------------------------------------- /test/issues/persistence/receiver_reconnect.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | import 'dart:async'; 9 | import 'package:mqtt_client/mqtt_client.dart'; 10 | import 'package:mqtt_client/mqtt_server_client.dart'; 11 | 12 | Future main() async { 13 | final client = MqttServerClient.withPort('localhost', 'SJHIssueRx', 1883); 14 | 15 | client.logging(on: false); 16 | client.setProtocolV311(); 17 | client.autoReconnect = true; 18 | const topic = 'counter'; 19 | final connMess = MqttConnectMessage(); 20 | client.connectionMessage = connMess; 21 | 22 | print('ISSUE: Connecting'); 23 | await client.connect(); 24 | 25 | // Subscribe to counter, Qos 1 26 | client.subscribe(topic, MqttQos.atLeastOnce); 27 | print( 28 | 'EXAMPLE:: Sleeping to allow the subscription acknowledges to be received....', 29 | ); 30 | await MqttUtilities.asyncSleep(2); 31 | 32 | // Listen for the counter messages 33 | print('ISSUE::Listening......'); 34 | client.updates?.listen((List> c) { 35 | final recMess = c[0].payload as MqttPublishMessage; 36 | final payload = recMess.payload.message; 37 | if (payload != null) { 38 | final counterValue = payload[0]; 39 | print('ISSUE::Change notification:: counter received is $counterValue'); 40 | } else { 41 | print('ISSUE - ERROR payload is null'); 42 | } 43 | }); 44 | 45 | await MqttUtilities.asyncSleep(60); 46 | 47 | print('ISSUE: Test complete'); 48 | client.disconnect(); 49 | 50 | return 0; 51 | } 52 | -------------------------------------------------------------------------------- /test/issues/issue602/mqtt_test2.py: -------------------------------------------------------------------------------- 1 | import json 2 | import random 3 | import time 4 | from datetime import datetime, timedelta, timezone 5 | import paho.mqtt.client as mqtt 6 | 7 | # MQTT Configuration 8 | MQTT_BROKER = "localhost" 9 | MQTT_PORT = 1883 10 | MQTT_TOPIC = "stations/tanks" 11 | 12 | # Data generation 13 | stations = 100 14 | tanks_per_station = 1000 15 | start_time = datetime.now(timezone.utc) 16 | 17 | data = [] 18 | 19 | for station_id in range(1, stations + 1): 20 | for tank_id in range(1, tanks_per_station + 1): 21 | entry = { 22 | "topic": f"station{station_id}/tank{tank_id}", 23 | "value": random.randint(1000, 10000), 24 | "timestamp": (start_time + timedelta(seconds=(station_id * tank_id) % 300)).isoformat() + "Z" 25 | } 26 | data.append(entry) 27 | 28 | # Convert to JSON string 29 | json_payload = json.dumps(data) 30 | payload_size = len(json_payload.encode("utf-8")) 31 | print(f"Generated {len(data)} records, payload size: {payload_size / 1024:.2f} KB") 32 | 33 | # MQTT Publish 34 | client = mqtt.Client() 35 | client.connect(MQTT_BROKER, MQTT_PORT, keepalive=60) 36 | client.loop_start() 37 | i=0 38 | try: 39 | while True: 40 | start_time = time.time() 41 | timestamp = time.time() 42 | 43 | # Publish all topics as fast as possible (non-blocking like JS) 44 | client.publish("station1/all", payload=json_payload, qos=0, retain=False) 45 | 46 | # Calculate how long the publishing took 47 | publish_time = time.time() - start_time 48 | i=i+1 49 | print(i) 50 | time.sleep(0.1) 51 | 52 | except KeyboardInterrupt: 53 | client.loop_stop() 54 | client.disconnect() 55 | 56 | -------------------------------------------------------------------------------- /test/issues/issue53a.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:math'; 4 | import 'package:mqtt_client/mqtt_client.dart'; 5 | import 'package:mqtt_client/mqtt_server_client.dart'; 6 | 7 | Future main() async { 8 | const clientId = '5bc71e3ea74ad804cc04a856'; 9 | const token = '2844865:94da2a801302660754642a85592f7755'; 10 | const id = '2844865'; 11 | 12 | final client = MqttServerClient('wss://m4.gap.im/mqtt', clientId); 13 | client.setProtocolV311(); 14 | client.keepAlivePeriod = 60; 15 | client.port = 443; 16 | client.useWebSocket = true; 17 | client.logging(on: false); 18 | 19 | client.onDisconnected = () { 20 | print('\n\n\n==> Disconnected | Time: ${DateTime.now().toUtc()}\n\n\n'); 21 | client.disconnect(); 22 | }; 23 | 24 | client.connectionMessage = MqttConnectMessage() 25 | .authenticateAs(id, token) 26 | .withClientIdentifier(clientId); 27 | 28 | client.connectionMessage.startClean(); 29 | 30 | await client 31 | .connect() 32 | .then((MqttClientConnectionStatus e) async { 33 | client.subscribe('u/$id', MqttQos.exactlyOnce); 34 | 35 | await MqttUtilities.asyncSleep(2); 36 | 37 | final builder = MqttClientPayloadBuilder(); 38 | builder.addString( 39 | json.encode({ 40 | 'type': 'msgText', 41 | 'data': 'TextMessage', 42 | 'identifier': Random().nextInt(1000000), 43 | }), 44 | ); 45 | 46 | client.publishMessage('u/$id', MqttQos.exactlyOnce, builder.payload); 47 | }) 48 | .catchError((e) { 49 | print('Connection failed'); 50 | }); 51 | 52 | print('Client exiting'); 53 | 54 | return 0; 55 | } 56 | -------------------------------------------------------------------------------- /test/support/mqtt_client_broker_test_ws_subscribe.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 11/07/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | import 'dart:async'; 9 | import 'package:mqtt_client/mqtt_client.dart'; 10 | import 'package:mqtt_client/mqtt_server_client.dart'; 11 | 12 | Future main() async { 13 | // Create and connect the client for websocket usage. The scheme must be ws:// otherwise 14 | // Dart IO will not create the websocket 15 | final client = MqttServerClient('ws://iot.eclipse.org/ws', 'SJHMQTTClient'); 16 | client.useWebSocket = true; 17 | client.port = 80; 18 | client.logging(on: false); 19 | await client.connect(); 20 | if (client.connectionStatus!.state == MqttConnectionState.connected) { 21 | print('Mosquitto client connected'); 22 | } else { 23 | print( 24 | 'ERROR Mosquitto client connection failed - disconnecting, state is ${client.connectionStatus}', 25 | ); 26 | client.disconnect(); 27 | } 28 | // Subscribe to a known topic 29 | const topic = 'test/hw'; 30 | client.subscribe(topic, MqttQos.exactlyOnce); 31 | client.updates!.listen((List>? c) { 32 | final recMess = c![0].payload as MqttPublishMessage; 33 | final pt = MqttPublishPayload.bytesToStringAsString( 34 | recMess.payload.message, 35 | ); 36 | print('Change notification:: payload is <$pt> for topic <$topic>'); 37 | }); 38 | print('Sleeping....'); 39 | await MqttUtilities.asyncSleep(90); 40 | print('Unsubscribing'); 41 | client.unsubscribe(topic); 42 | await MqttUtilities.asyncSleep(2); 43 | print('Disconnecting'); 44 | client.disconnect(); 45 | return 0; 46 | } 47 | -------------------------------------------------------------------------------- /test/pem/localhost.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAph2mgzNEO8IhOHrMb9F2V8R4hpSCzsAAiSvi61F7p3hv/W9S 3 | 71DiGRMeLOymeHrRex0ZlpK84niJ3D3FYzeeSKkmskRgF6NZYGXRV2krGKc7U1bF 4 | 98mJtY9H8O4jpO3T9paTyZmPox7UGp2nQXyj9TvEch5CZz6NvhEAjOhwN0977GWS 5 | qrrwfQKaAF1VslOAL3ArQzz1zFQJsL6KWqS4HPv21HijjgJAK9mL240RqPppx6b4 6 | jmfxsZ9j4X6VN0et3bxI5b+pfaE9uAjK5LIJ2FQyc1Z1YJ60KomuEGrZpxpAVl3M 7 | ijGYZOxDpveRoLhFEZm10JtRtcUpdPc85gQyOwIDAQABAoIBAB7/J2QR++h+t/43 8 | A9DVsBR+h9LtAV+c+jyeYNByOvWsBpGu5TXczOPK6nWNjx6qJE6pxm2PYgmMXoUn 9 | TZ7O8Q0z6xGWqquegn33YdHCwRTvl6VcasJq6/RiERWHCkWrT0MTI+6ZZfEVPpTc 10 | 2CnjHSEUjvqQZOmpBX/y4jxo3w+iyyPxz8rYh4jAEB7KDOLHDwPguxsKTSR1Arie 11 | poGgMdSA5egVH+BLJkWRVwvPDpHosSQ0Na1Di5FGdjeTWaDdRxYv9dMJqpRgcUlP 12 | /3nGpLoOaMMJUcxmZh5vkHWMh1HUXfBOVxD705Zo04tbXrsazAaHTagY7wagC5Rs 13 | OH9EnkECgYEA3NT0Tl4YYD00WmnUnNs9fd+169TxWh9aqZjcmWUkZ1Z/IwafCob0 14 | dNVVlIEv9fj9Pk9cYrEIjENO94lFXaOqucDD9aH45uFsxtK9JivT3r974xTUbKJc 15 | aV1FhgfNu/vh+PxvndwQDqkCitGj6f4LK7zpJvLHwfn3M4+PGzneQyECgYEAwJH9 16 | fLkmOrqHPlXIOJ+88JpMmL1KiWJySRhAab3SzmfZ9BWhqdQkaXULkBVNAIl3FmRF 17 | cCyE1FeZrm6XBHOgznGFVdxPojmAW09cf+d8Kp8yDqdAWOJC01tUd07IxAXJRXle 18 | AM6vdSFUK+x7PSfuEythXIh82dPl3Nl1LMyNJdsCgYEA1Rasc556eQs2JHRwk7c+ 19 | m+KtwdYl2mfc7UAyxdYCDbE6VwsBln/pDX/556XNKNXNUD32EMlxTR956IROKfBP 20 | QpTOwow8CFXHyAb8PAQYmp09Jz8nR3hYIde9yXpoPJfuUnsMHVCHMg5GBLwMJjNc 21 | hiTw+gNVXEkfD2LcnVH2teECgYBuNh+htkWO7xpgLrA82A9GlMip0gxKvLEz7FFR 22 | M3bm8sDm1UqE7Ak64eYQPGCpogMqKZEkNhZ/gPNPxbNgtNyfypDJJyHccpRnre/+ 23 | s/l4W0xG3qoRNM2SMX8SGWEF71nWDTQjuibpHn4R9XJC+gtjpTw/vU3XTVMNJtfN 24 | H/TYaQKBgQCVI4DdXv3qexr9WeJuW/FQk38jSehr0V4BLwtswHNNf//puXzrNlSN 25 | NVTI05f4NY/JWy+xzO5/o/rvrYXgeFGjaPEwly2p/sNUOtS93eo4e8d00ocAsfSy 26 | wULCGYhTqQd5hDA/sxXoMG3C/1uw+22AhihwNq2cvSNN6jPK9TQ3zA== 27 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /lib/src/connectionhandling/server/mqtt_client_mqtt_server_connection_handler.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 22/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_server_client.dart'; 9 | 10 | /// This class provides specific connection functionality 11 | /// for server based connections. 12 | abstract class MqttServerConnectionHandler extends MqttConnectionHandlerBase { 13 | /// Use a websocket rather than TCP 14 | bool useWebSocket = false; 15 | 16 | /// Alternate websocket implementation. 17 | /// 18 | /// The Amazon Web Services (AWS) IOT MQTT interface(and maybe others) 19 | /// has a bug that causes it not to connect if unexpected message headers are 20 | /// present in the initial GET message during the handshake. 21 | /// Since the httpclient classes insist on adding those headers, an alternate 22 | /// method is used to perform the handshake. 23 | /// After the handshake everything goes back to the normal websocket class. 24 | /// Only use this websocket implementation if you know it is needed 25 | /// by your broker. 26 | bool useAlternateWebSocketImplementation = false; 27 | 28 | /// If set use a secure connection, note TCP only, not websocket. 29 | bool secure = false; 30 | 31 | /// The security context for secure usage 32 | SecurityContext? securityContext; 33 | 34 | /// Socket options 35 | List socketOptions = []; 36 | 37 | /// Socket timeout duration. 38 | Duration? socketTimeout; 39 | 40 | /// Initializes a new instance of the [MqttServerConnectionHandler] class. 41 | MqttServerConnectionHandler( 42 | super.clientEventBus, { 43 | required super.maxConnectionAttempts, 44 | required this.socketOptions, 45 | required this.socketTimeout, 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /lib/src/messages/publishcomplete/mqtt_client_mqtt_publish_complete_message.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 19/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_client.dart'; 9 | 10 | /// Implementation of an MQTT Publish Complete Message. 11 | class MqttPublishCompleteMessage extends MqttMessage { 12 | /// Gets or sets the variable header contents. Contains extended 13 | /// metadata about the message 14 | late MqttPublishCompleteVariableHeader variableHeader; 15 | 16 | /// Initializes a new instance of the MqttPublishCompleteMessage class. 17 | MqttPublishCompleteMessage() { 18 | header = MqttHeader().asType(MqttMessageType.publishComplete); 19 | variableHeader = MqttPublishCompleteVariableHeader(); 20 | } 21 | 22 | /// Initializes a new instance of the MqttPublishCompleteMessage class. 23 | MqttPublishCompleteMessage.fromByteBuffer( 24 | MqttHeader header, 25 | MqttByteBuffer messageStream, 26 | ) { 27 | this.header = header; 28 | variableHeader = MqttPublishCompleteVariableHeader.fromByteBuffer( 29 | messageStream, 30 | ); 31 | } 32 | 33 | /// Writes the message to the supplied stream. 34 | @override 35 | void writeTo(MqttByteBuffer messageStream) { 36 | header!.writeTo(variableHeader.getWriteLength(), messageStream); 37 | variableHeader.writeTo(messageStream); 38 | } 39 | 40 | /// Sets the message identifier of the MqttMessage. 41 | MqttPublishCompleteMessage withMessageIdentifier(int? messageIdentifier) { 42 | variableHeader.messageIdentifier = messageIdentifier; 43 | return this; 44 | } 45 | 46 | @override 47 | String toString() { 48 | final sb = StringBuffer(); 49 | sb.write(super.toString()); 50 | sb.writeln(variableHeader.toString()); 51 | return sb.toString(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/src/messages/publishreceived/mqtt_client_mqtt_publish_received_message.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 19/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_client.dart'; 9 | 10 | /// Implementation of an MQTT Publish Received Message. 11 | class MqttPublishReceivedMessage extends MqttMessage { 12 | /// Gets or sets the variable header contents. Contains extended 13 | /// metadata about the message. 14 | late MqttPublishReceivedVariableHeader variableHeader; 15 | 16 | /// Initializes a new instance of the MqttPublishReceivedMessage class. 17 | MqttPublishReceivedMessage() { 18 | header = MqttHeader().asType(MqttMessageType.publishReceived); 19 | variableHeader = MqttPublishReceivedVariableHeader(); 20 | } 21 | 22 | /// Initializes a new instance of the MqttPublishReceivedMessage class. 23 | MqttPublishReceivedMessage.fromByteBuffer( 24 | MqttHeader header, 25 | MqttByteBuffer messageStream, 26 | ) { 27 | this.header = header; 28 | variableHeader = MqttPublishReceivedVariableHeader.fromByteBuffer( 29 | messageStream, 30 | ); 31 | } 32 | 33 | /// Writes the message to the supplied stream. 34 | @override 35 | void writeTo(MqttByteBuffer messageStream) { 36 | header!.writeTo(variableHeader.getWriteLength(), messageStream); 37 | variableHeader.writeTo(messageStream); 38 | } 39 | 40 | /// Sets the message identifier of the MqttMessage. 41 | MqttPublishReceivedMessage withMessageIdentifier(int? messageIdentifier) { 42 | variableHeader.messageIdentifier = messageIdentifier; 43 | return this; 44 | } 45 | 46 | @override 47 | String toString() { 48 | final sb = StringBuffer(); 49 | sb.write(super.toString()); 50 | sb.writeln(variableHeader.toString()); 51 | return sb.toString(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/src/messages/publishack/mqtt_client_mqtt_publish_ack_message.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 19/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_client.dart'; 9 | 10 | /// Implementation of an MQTT Publish Acknowledgement Message, used to ACK a 11 | /// publish message that has it's QOS set to AtLeast or Exactly Once. 12 | class MqttPublishAckMessage extends MqttMessage { 13 | /// Gets or sets the variable header contents. Contains extended 14 | /// metadata about the message 15 | late MqttPublishAckVariableHeader variableHeader; 16 | 17 | /// Initializes a new instance of the MqttPublishAckMessage class. 18 | MqttPublishAckMessage() { 19 | header = MqttHeader().asType(MqttMessageType.publishAck); 20 | variableHeader = MqttPublishAckVariableHeader(); 21 | } 22 | 23 | /// Initializes a new instance of the MqttPublishAckMessage class. 24 | MqttPublishAckMessage.fromByteBuffer( 25 | MqttHeader header, 26 | MqttByteBuffer messageStream, 27 | ) { 28 | this.header = header; 29 | variableHeader = MqttPublishAckVariableHeader.fromByteBuffer(messageStream); 30 | } 31 | 32 | /// Writes the message to the supplied stream. 33 | @override 34 | void writeTo(MqttByteBuffer messageStream) { 35 | header!.writeTo(variableHeader.getWriteLength(), messageStream); 36 | variableHeader.writeTo(messageStream); 37 | } 38 | 39 | /// Sets the message identifier of the MqttMessage. 40 | MqttPublishAckMessage withMessageIdentifier(int? messageIdentifier) { 41 | variableHeader.messageIdentifier = messageIdentifier; 42 | return this; 43 | } 44 | 45 | @override 46 | String toString() { 47 | final sb = StringBuffer(); 48 | sb.write(super.toString()); 49 | sb.writeln(variableHeader.toString()); 50 | return sb.toString(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/src/messages/publishrelease/mqtt_client_mqtt_publish_release_message.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 19/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_client.dart'; 9 | 10 | /// Implementation of an MQTT Publish Release Message. 11 | class MqttPublishReleaseMessage extends MqttMessage { 12 | /// Gets or sets the variable header contents. Contains extended 13 | /// metadata about the message. 14 | late MqttPublishReleaseVariableHeader variableHeader; 15 | 16 | /// Initializes a new instance of the MqttPublishReleaseMessage class. 17 | MqttPublishReleaseMessage() { 18 | header = MqttHeader().asType(MqttMessageType.publishRelease); 19 | // Qos is specified for this message 20 | header!.qos = MqttQos.atLeastOnce; 21 | variableHeader = MqttPublishReleaseVariableHeader(); 22 | } 23 | 24 | /// Initializes a new instance of the MqttPublishReleaseMessage class. 25 | MqttPublishReleaseMessage.fromByteBuffer( 26 | MqttHeader header, 27 | MqttByteBuffer messageStream, 28 | ) { 29 | this.header = header; 30 | variableHeader = MqttPublishReleaseVariableHeader.fromByteBuffer( 31 | messageStream, 32 | ); 33 | } 34 | 35 | /// Writes the message to the supplied stream. 36 | @override 37 | void writeTo(MqttByteBuffer messageStream) { 38 | header!.writeTo(variableHeader.getWriteLength(), messageStream); 39 | variableHeader.writeTo(messageStream); 40 | } 41 | 42 | /// Sets the message identifier of the MqttMessage. 43 | MqttPublishReleaseMessage withMessageIdentifier(int? messageIdentifier) { 44 | variableHeader.messageIdentifier = messageIdentifier; 45 | return this; 46 | } 47 | 48 | @override 49 | String toString() { 50 | final sb = StringBuffer(); 51 | sb.write(super.toString()); 52 | sb.writeln(variableHeader.toString()); 53 | return sb.toString(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/support/mqtt_client_test_connection_handler.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 27/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | import 'dart:async'; 8 | import 'package:mqtt_client/mqtt_client.dart'; 9 | import 'package:mqtt_client/mqtt_server_client.dart'; 10 | 11 | class TestConnectionHandlerNoSend extends MqttServerConnectionHandler { 12 | TestConnectionHandlerNoSend( 13 | super.clientEventBus, { 14 | super.maxConnectionAttempts, 15 | required super.socketOptions, 16 | super.socketTimeout, 17 | }); 18 | 19 | @override 20 | Future internalConnect( 21 | String? hostname, 22 | int? port, 23 | MqttConnectMessage? message, 24 | ) { 25 | final completer = Completer(); 26 | return completer.future; 27 | } 28 | 29 | @override 30 | MqttConnectionState disconnect() => 31 | connectionStatus.state = MqttConnectionState.disconnected; 32 | } 33 | 34 | class TestConnectionHandlerSend extends MqttServerConnectionHandler { 35 | TestConnectionHandlerSend( 36 | super.clientEventBus, { 37 | super.maxConnectionAttempts, 38 | required super.socketOptions, 39 | super.socketTimeout, 40 | }); 41 | 42 | List sentMessages = []; 43 | 44 | @override 45 | Future internalConnect( 46 | String? hostname, 47 | int? port, 48 | MqttConnectMessage? message, 49 | ) { 50 | final completer = Completer(); 51 | return completer.future; 52 | } 53 | 54 | @override 55 | MqttConnectionState disconnect() => 56 | connectionStatus.state = MqttConnectionState.disconnected; 57 | 58 | @override 59 | void sendMessage(MqttMessage? message) { 60 | print( 61 | 'TestConnectionHandlerNoSend::send, message is ${message.toString()}', 62 | ); 63 | sentMessages.add(message); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/src/messages/unsubscribeack/mqtt_client_mqtt_unsubscribe_ack_message.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 19/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_client.dart'; 9 | 10 | /// Implementation of an MQTT Unsubscribe Ack Message. 11 | class MqttUnsubscribeAckMessage extends MqttMessage { 12 | /// Gets or sets the variable header contents. Contains extended 13 | /// metadata about the message. 14 | late MqttUnsubscribeAckVariableHeader variableHeader; 15 | 16 | /// Initializes a new instance of the MqttUnsubscribeAckMessage class. 17 | MqttUnsubscribeAckMessage() { 18 | header = MqttHeader().asType(MqttMessageType.unsubscribeAck); 19 | variableHeader = MqttUnsubscribeAckVariableHeader(); 20 | } 21 | 22 | /// Initializes a new instance of the MqttUnsubscribeAckMessage class. 23 | MqttUnsubscribeAckMessage.fromByteBuffer( 24 | MqttHeader header, 25 | MqttByteBuffer messageStream, 26 | ) { 27 | this.header = header; 28 | readFrom(messageStream); 29 | } 30 | 31 | /// Writes the message to the supplied stream. 32 | @override 33 | void writeTo(MqttByteBuffer messageStream) { 34 | header!.writeTo(variableHeader.getWriteLength(), messageStream); 35 | variableHeader.writeTo(messageStream); 36 | } 37 | 38 | /// Reads a message from the supplied stream. 39 | @override 40 | void readFrom(MqttByteBuffer messageStream) { 41 | variableHeader = MqttUnsubscribeAckVariableHeader.fromByteBuffer( 42 | messageStream, 43 | ); 44 | } 45 | 46 | /// Sets the message identifier on the subscribe message. 47 | MqttUnsubscribeAckMessage withMessageIdentifier(int messageIdentifier) { 48 | variableHeader.messageIdentifier = messageIdentifier; 49 | return this; 50 | } 51 | 52 | @override 53 | String toString() { 54 | final sb = StringBuffer(); 55 | sb.write(super.toString()); 56 | sb.writeln(variableHeader.toString()); 57 | return sb.toString(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/issues/issue602/mqtt_bench2.py: -------------------------------------------------------------------------------- 1 | import time 2 | import paho.mqtt.client as mqtt 3 | 4 | BROKER = 'localhost' 5 | PORT = 1883 6 | TOPIC = "benchmark/test" 7 | EXPECTED_MESSAGES = 1000 # Should match publisher MESSAGE_COUNT 8 | 9 | received_count = 0 10 | total_bytes = 0 11 | start_time = None 12 | end_time = None 13 | 14 | def on_connect(client, userdata, flags, rc): 15 | if rc == 0: 16 | print("Connected to MQTT Broker") 17 | client.subscribe(TOPIC) 18 | else: 19 | print(f"Failed to connect, return code {rc}") 20 | 21 | def on_message(client, userdata, msg): 22 | global received_count, total_bytes, start_time, end_time 23 | 24 | if received_count == 0: 25 | start_time = time.time() 26 | 27 | received_count += 1 28 | total_bytes += len(msg.payload) 29 | 30 | if received_count == EXPECTED_MESSAGES: 31 | end_time = time.time() 32 | client.loop_stop() 33 | 34 | def benchmark_receiver(): 35 | global start_time, end_time, received_count, total_bytes 36 | 37 | client = mqtt.Client() 38 | client.on_connect = on_connect 39 | client.on_message = on_message 40 | 41 | client.connect(BROKER, PORT, keepalive=60) 42 | client.loop_start() 43 | 44 | # Wait max 20 seconds to receive all messages 45 | timeout = time.time() + 20 46 | while received_count < EXPECTED_MESSAGES and time.time() < timeout: 47 | time.sleep(0.01) 48 | 49 | if received_count < EXPECTED_MESSAGES: 50 | print(f"Timeout! Received only {received_count} messages.") 51 | else: 52 | duration = end_time - start_time 53 | mb_received = total_bytes / (1024 * 1024) 54 | print(f"All {received_count} messages received.") 55 | print(f"Total receiving time: {duration:.4f} seconds") 56 | print(f"Receiving speed: {received_count / duration:.2f} messages/second") 57 | print(f"Data receiving throughput: {mb_received / duration:.4f} MB/s") 58 | 59 | client.disconnect() 60 | 61 | if __name__ == "__main__": 62 | benchmark_receiver() 63 | -------------------------------------------------------------------------------- /test/pem/self_signed.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFazCCA1OgAwIBAgIUERygzr/2UDySa0TYn2KAhFSKBhMwDQYJKoZIhvcNAQEL 3 | BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM 4 | GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0xOTEwMTkwODMzNDBaFw0xOTEx 5 | MTgwODMzNDBaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw 6 | HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB 7 | AQUAA4ICDwAwggIKAoICAQDNTbksieMQukkTtztqeuLCoNWswyDnY8zpl70v/6JS 8 | NMJTpSLVehmwBXC0xrvYmQ6tacm/SCusGAPE2+iyMsnUvpK0kCqOMQrcNHv88T4t 9 | Q9r9a1C0BfkjkrRMrWJpDNmG+XeM2tHhc+ITuew32KZE2zR/wfCZkExlOVVrwYXU 10 | /+O1+30fQQlgNka936K8+YLzMmu5xbJnlrnCF2Jkvl6v9YInqe5wtH7OcLkz1P3a 11 | mYLNJSu2fWlNQFp3Sf/s0w0DPolBAUWdc6ahQ2rpWF2OxMaOgNB+0F9aLnmpnjj+ 12 | lsr6+Y2I5dllvT/QbfPB94YcwTGDA2cZKzR3SST48n9zTgjvwWhoFRBl8i3VpJPZ 13 | EwJuZnuh7UQK5JyullfLweuL+e33nnH5fhjcJaoBciwxZYASG7I3gq3c3CU85Yic 14 | 5kmZ6aNQWlw3tpfaKiOUEsnMwatLWuUZTGiePg9nluUh+W0wifyd+ilbD8jik223 15 | X6vTgZLinKu1kA3GO12g7+BVu9njdbDju+0JxIZCJyKW1T2WEGoYsih5aY3g+FD8 16 | hq4bLAdg5yplEFD5pcgrKo29rHSwV7/5UAtXwPCIaekRQpddg7dbp7VWS7GzWXOM 17 | EhED1U79ZcvNaisbIAuP0xabruQXoXJe2AZ8ASitSY8I/CFHrnS8TsrnHQTti2x0 18 | HQIDAQABo1MwUTAdBgNVHQ4EFgQUdO5ToWFc9RCTlAjEtclFZeh9kwEwHwYDVR0j 19 | BBgwFoAUdO5ToWFc9RCTlAjEtclFZeh9kwEwDwYDVR0TAQH/BAUwAwEB/zANBgkq 20 | hkiG9w0BAQsFAAOCAgEAFXGg0+1IjS035QawmTNYKecaEhvVL4pLFxAkHY1+uPLs 21 | ZAO0PjBmRXrrFsKOQfNVQt2ZagckJHklLsNpj1z9xh2oEj8EfUlrw+2TqtvJ0hHh 22 | O4o8PiqRoxLM1j370bb+9/C/dL1PtOB5YsoufV1mZWuhueHPGbxCHFwEDPDEUD3+ 23 | uWJnTW4HVUiuiVlCsBOs7o7bmbhUOEiNiLIJJOfVeRJ28Xmkm1rtjwG922cMX2N1 24 | fU8bsxrL1K/sXEH9ThZQ2AvLe7ZSgFJ8E3Lzr0TCB65zkjpcBFtRAnuz1mD2zpcR 25 | kJEiBjbZi5p5ogRi8yUncMQSCUU9eWRcWtIyFZibiWpo4er1fDvzcAsiEgZy1jOf 26 | e5q1SBWTA/VM2VWdZ8aao29mXIL5+MMjsuf4QlO033xiwvUrYvI23znuSRXHnA4p 27 | jGgS3dtMWpJRVKzaKkAK4mpsk203vt4o68jgCm+vRe1g4HXmljQFxzaSUUiTeOsd 28 | GrJ0DNTpKF+StEtSAPa65ol+b6yLQcwkk70No8b9GHu0Z0FhK+rsap23h8kTBEQk 29 | PfWa3OUo4WrGoFVC7BptaPR2ZpmDIWCtjqd0MFPNKwR6jG0lBob0xpRBumAslxB9 30 | bEk14lX2rqGXCoUBg3wxcZ7GMa/KucWeTTI/LMF1ekEsuZWY9Pw2ZYFvmG71M/A= 31 | -----END CERTIFICATE----- 32 | -------------------------------------------------------------------------------- /lib/src/messages/connect/mqtt_client_mqtt_connect_variable_header.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 12/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_client.dart'; 9 | 10 | /// Implementation of the variable header for an MQTT Connect message. 11 | class MqttConnectVariableHeader extends MqttVariableHeader { 12 | /// Initializes a new instance of the MqttConnectVariableHeader class. 13 | MqttConnectVariableHeader(); 14 | 15 | /// Initializes a new instance of the MqttConnectVariableHeader class. 16 | MqttConnectVariableHeader.fromByteBuffer(super.headerStream) 17 | : super.fromByteBuffer(); 18 | 19 | /// Creates a variable header from the specified header stream. 20 | @override 21 | void readFrom(MqttByteBuffer variableHeaderStream) { 22 | readProtocolName(variableHeaderStream); 23 | readProtocolVersion(variableHeaderStream); 24 | readConnectFlags(variableHeaderStream); 25 | readKeepAlive(variableHeaderStream); 26 | } 27 | 28 | /// Writes the variable header to the supplied stream. 29 | @override 30 | void writeTo(MqttByteBuffer variableHeaderStream) { 31 | writeProtocolName(variableHeaderStream); 32 | writeProtocolVersion(variableHeaderStream); 33 | writeConnectFlags(variableHeaderStream); 34 | writeKeepAlive(variableHeaderStream); 35 | } 36 | 37 | /// Gets the length of the write data when WriteTo will be called. 38 | @override 39 | int getWriteLength() { 40 | var headerLength = 0; 41 | final enc = MqttEncoding(); 42 | headerLength += enc.getByteCount(protocolName); 43 | headerLength += 1; // protocolVersion 44 | headerLength += MqttConnectFlags.getWriteLength(); 45 | headerLength += 2; // keepAlive 46 | return headerLength; 47 | } 48 | 49 | @override 50 | String toString() => 51 | 'Connect Variable Header: ProtocolName=$protocolName, ' 52 | 'ProtocolVersion=$protocolVersion, ' 53 | 'ConnectFlags=${connectFlags.toString()}, ' 54 | 'KeepAlive=$keepAlive'; 55 | } 56 | -------------------------------------------------------------------------------- /lib/src/utility/mqtt_client_utilities.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 28/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../mqtt_client.dart'; 9 | 10 | /// General library wide utilties 11 | class MqttUtilities { 12 | /// Sleep function that allows asynchronous activity to continue. 13 | /// Time units are seconds 14 | static Future asyncSleep(int seconds) => 15 | Future.delayed(Duration(seconds: seconds)); 16 | 17 | /// Qos conversion, always use this to get a Qos 18 | /// enumeration from a value 19 | static MqttQos getQosLevel(int value) { 20 | switch (value) { 21 | case 0: 22 | return MqttQos.atMostOnce; 23 | case 1: 24 | return MqttQos.atLeastOnce; 25 | case 2: 26 | return MqttQos.exactlyOnce; 27 | case 0x80: 28 | return MqttQos.failure; 29 | default: 30 | return MqttQos.reserved1; 31 | } 32 | } 33 | } 34 | 35 | /// Cancellable asynchronous sleep support class 36 | class MqttCancellableAsyncSleep { 37 | /// Millisecond timeout 38 | final int _timeout; 39 | 40 | /// The completer 41 | late Completer _completer; 42 | 43 | /// The timer 44 | late Timer _timer; 45 | 46 | /// Timer running flag 47 | bool _running = false; 48 | 49 | /// Timeout 50 | int get timeout => _timeout; 51 | 52 | /// Running 53 | bool get isRunning => _running; 54 | 55 | /// Timeout value in milliseconds 56 | MqttCancellableAsyncSleep(this._timeout); 57 | 58 | /// Start the timer 59 | Future sleep() { 60 | if (!_running) { 61 | _completer = Completer(); 62 | _timer = Timer(Duration(milliseconds: _timeout), _timerCallback); 63 | _running = true; 64 | } 65 | return _completer.future; 66 | } 67 | 68 | /// Cancel the timer 69 | void cancel() { 70 | if (_running) { 71 | _timer.cancel(); 72 | _running = false; 73 | _completer.complete(); 74 | } 75 | } 76 | 77 | /// The timer callback 78 | void _timerCallback() { 79 | _running = false; 80 | _completer.complete(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/src/messages/publish/mqtt_client_mqtt_publish_variable_header.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 19/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_client.dart'; 9 | 10 | /// Implementation of the variable header for an MQTT Connect message. 11 | class MqttPublishVariableHeader extends MqttVariableHeader { 12 | /// Standard header 13 | MqttHeader? header; 14 | 15 | /// Initializes a new instance of the MqttPublishVariableHeader class. 16 | MqttPublishVariableHeader(this.header); 17 | 18 | /// Initializes a new instance of the MqttPublishVariableHeader class. 19 | MqttPublishVariableHeader.fromByteBuffer( 20 | this.header, 21 | MqttByteBuffer variableHeaderStream, 22 | ) { 23 | readFrom(variableHeaderStream); 24 | } 25 | 26 | /// Creates a variable header from the specified header stream. 27 | @override 28 | void readFrom(MqttByteBuffer variableHeaderStream) { 29 | readTopicName(variableHeaderStream); 30 | if (header!.qos == MqttQos.atLeastOnce || 31 | header!.qos == MqttQos.exactlyOnce) { 32 | readMessageIdentifier(variableHeaderStream); 33 | } 34 | } 35 | 36 | /// Writes the variable header to the supplied stream. 37 | @override 38 | void writeTo(MqttByteBuffer variableHeaderStream) { 39 | writeTopicName(variableHeaderStream); 40 | if (header!.qos == MqttQos.atLeastOnce || 41 | header!.qos == MqttQos.exactlyOnce) { 42 | writeMessageIdentifier(variableHeaderStream); 43 | } 44 | } 45 | 46 | /// Gets the length of the write data when WriteTo will be called. 47 | @override 48 | int getWriteLength() { 49 | var headerLength = 0; 50 | final enc = MqttEncoding(); 51 | headerLength += enc.getByteCount(topicName); 52 | if (header!.qos == MqttQos.atLeastOnce || 53 | header!.qos == MqttQos.exactlyOnce) { 54 | headerLength += 2; 55 | } 56 | return headerLength; 57 | } 58 | 59 | @override 60 | String toString() => 61 | 'Publish Variable Header: TopicName={$topicName}, ' 62 | 'MessageIdentifier={$messageIdentifier}, VH Length={$length}'; 63 | } 64 | -------------------------------------------------------------------------------- /lib/src/mqtt_client_constants.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../mqtt_client.dart'; 9 | 10 | /// Library wide constants 11 | class MqttClientConstants { 12 | /// The Maximum allowed message size as defined by the MQTT v3 Spec (256MB). 13 | static const int maxMessageSize = 268435455; 14 | 15 | /// The Maximum allowed client identifier length as specified by the 3.1 16 | /// specification is 23 characters, however we allow more than 17 | /// this as this was relaxed in the 3.1.1 specification and 18 | /// most brokers now support lengths greater than 23 characters. 19 | static const int maxClientIdentifierLength = 65535; 20 | 21 | /// The default Mqtt port to connect to. 22 | static const int defaultMqttPort = 1883; 23 | 24 | /// The recommended length for usernames and passwords. 25 | static const int recommendedMaxUsernamePasswordLength = 12; 26 | 27 | /// Default keep alive in seconds. 28 | /// The default of 0 disables keep alive. 29 | static const int defaultKeepAlive = 0; 30 | 31 | /// Default maximum connection attempts 32 | static const int defaultMaxConnectionAttempts = 3; 33 | 34 | /// Default connection attempt timeout period, milliseconds, 35 | static const int defaultConnectionAttemptTimeoutPeriod = 5000; 36 | 37 | /// Protocol variants 38 | /// V3 39 | static const int mqttV31ProtocolVersion = 3; 40 | 41 | /// V3 name 42 | static const String mqttV31ProtocolName = 'MQIsdp'; 43 | 44 | /// V4 45 | static const int mqttV311ProtocolVersion = 4; 46 | 47 | /// V4 name 48 | static const String mqttV311ProtocolName = 'MQTT'; 49 | 50 | /// The default websocket subprotocol list 51 | static const List protocolsMultipleDefault = [ 52 | 'mqtt', 53 | 'mqttv3.1', 54 | 'mqttv3.11', 55 | ]; 56 | 57 | /// The default websocket subprotocol list for brokers who expect 58 | /// this field to be a single entry 59 | static const List protocolsSingleDefault = ['mqtt']; 60 | 61 | /// Seconds to milliseconds multiplier 62 | static const int millisecondsMultiplier = 1000; 63 | } 64 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file allows you to configure the Dart analyzer. 2 | # 3 | # The commented part below is just for inspiration. Read the guide here: 4 | # https://www.dartlang.org/guides/language/analysis-options 5 | 6 | include: package:lints/recommended.yaml 7 | 8 | analyzer: 9 | exclude: 10 | - test/issues/** 11 | 12 | linter: 13 | rules: 14 | - overridden_fields: false 15 | - always_declare_return_types 16 | - prefer_single_quotes 17 | - unawaited_futures 18 | 19 | dart_code_metrics: 20 | exclude: 21 | metrics: 22 | - test/** 23 | - example/** 24 | rules: 25 | - test/** 26 | - example/** 27 | rules: 28 | - newline-before-return : false 29 | - avoid-late-keyword : false 30 | - avoid-global-state : false 31 | - avoid-non-null-assertion : false 32 | - no-magic-number : false 33 | - avoid-substring : false 34 | - avoid-dynamic : false 35 | 36 | 37 | - avoid-cascade-after-if-null 38 | - avoid-casting-to-extension-type 39 | - avoid-collection-methods-with-unrelated-types 40 | - avoid-conditions-with-boolean-literals 41 | - avoid-double-slash-imports 42 | - avoid-duplicate-exports 43 | - avoid-duplicate-mixins 44 | - avoid-duplicate-named-imports 45 | - avoid-empty-spread 46 | - avoid-generics-shadowing 47 | - avoid-misused-set-literals 48 | - avoid-passing-async-when-sync-expected : false 49 | - avoid-passing-self-as-argument 50 | - avoid-redundant-async 51 | - avoid-self-assignment 52 | - avoid-self-compare 53 | - avoid-throw-in-catch-block 54 | - avoid-top-level-members-in-tests 55 | - avoid-unnecessary-conditionals 56 | - avoid-unnecessary-type-assertions 57 | - avoid-unnecessary-type-casts 58 | - avoid-unrelated-type-assertions 59 | - avoid-unused-parameters 60 | - binary-expression-operand-order 61 | - double-literal-format 62 | - member-ordering 63 | - missing-test-assertion 64 | - no-equal-then-else 65 | - prefer-commenting-analyzer-ignores 66 | - prefer-conditional-expressions 67 | - prefer-first 68 | - prefer-immediate-return 69 | - prefer-iterable-of 70 | - prefer-last 71 | - prefer-match-file-name : false 72 | - prefer-moving-to-variable : false 73 | - prefer-static-class 74 | - avoid-commented-out-code 75 | -------------------------------------------------------------------------------- /lib/src/mqtt_client_events.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 22/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../mqtt_client.dart'; 9 | 10 | /// The message available event raised by the Connection class 11 | class MessageAvailable { 12 | // The message associated with the event 13 | final MqttMessage? _message; 14 | 15 | /// Message 16 | MqttMessage? get message => _message; 17 | 18 | /// Constructor 19 | MessageAvailable(this._message); 20 | } 21 | 22 | /// The connect acknowledge message available event raised by the Connection class 23 | class ConnectAckMessageAvailable { 24 | // The message associated with the event 25 | final MqttMessage? _message; 26 | 27 | /// Message 28 | MqttMessage? get message => _message; 29 | 30 | /// Constructor 31 | ConnectAckMessageAvailable(this._message); 32 | } 33 | 34 | /// Message recieved class for publishing 35 | class MessageReceived { 36 | // The message associated with the event 37 | final MqttMessage _message; 38 | 39 | // The topic 40 | final PublicationTopic _topic; 41 | 42 | /// Message 43 | MqttMessage get message => _message; 44 | 45 | /// Topic 46 | PublicationTopic get topic => _topic; 47 | 48 | /// Constructor 49 | MessageReceived(this._topic, this._message); 50 | } 51 | 52 | /// Auto reconnect event 53 | class AutoReconnect { 54 | /// If set auto reconnect has been invoked through the client 55 | /// [doAutoReconnect] method, i.e. a user request. 56 | bool userRequested = false; 57 | 58 | /// True if the previous state was connected 59 | bool wasConnected = false; 60 | 61 | /// Constructor 62 | AutoReconnect({this.userRequested = false, this.wasConnected = false}); 63 | } 64 | 65 | /// Re subscribe event 66 | class Resubscribe { 67 | /// If set re subscribe has been triggered from auto reconnect. 68 | bool fromAutoReconnect = false; 69 | 70 | /// Constructor 71 | Resubscribe({this.fromAutoReconnect = false}); 72 | } 73 | 74 | /// Disconnect on keep alive on no ping response event 75 | class DisconnectOnNoPingResponse { 76 | /// Constructor 77 | DisconnectOnNoPingResponse(); 78 | } 79 | 80 | /// Disconnect on sent message failed event 81 | class DisconnectOnNoMessageSent { 82 | /// Constructor 83 | DisconnectOnNoMessageSent(); 84 | } 85 | -------------------------------------------------------------------------------- /test/issues/issue53b.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:math'; 4 | 5 | import 'package:mqtt_client/mqtt_client.dart'; 6 | import 'package:mqtt_client/mqtt_server_client.dart'; 7 | 8 | const clientId = '5bc71e3ea74ad804cc04a856'; 9 | const token = '2844865:b08b650bdef3774426b2b718f6ab2d6e'; 10 | const id = '2844865'; 11 | 12 | void main() async { 13 | Mqtt.init(); 14 | 15 | MqttClientConnectionStatus val = await Mqtt.connect(); 16 | print('===> Connection Result: $val'); 17 | if (val != null) { 18 | Mqtt.subscribe(); 19 | for (var i = 0; i <= 10; i++) { 20 | await Mqtt.subAndPub(); 21 | print('Publish Attempt $i ......\n'); 22 | await MqttUtilities.asyncSleep(2); 23 | } 24 | } 25 | } 26 | 27 | class Mqtt { 28 | static MqttServerClient client; 29 | 30 | static void init() { 31 | client = MqttServerClient('wss://m4.gap.im/mqtt', clientId); 32 | client.setProtocolV311(); 33 | client.keepAlivePeriod = 60; 34 | client.port = 443; 35 | client.useWebSocket = true; 36 | client.logging(on: false); 37 | 38 | client.onDisconnected = () { 39 | print('\n\n\n==> Disconnected | Time: ${DateTime.now().toUtc()}\n\n\n'); 40 | // client.disconnect(); 41 | }; 42 | 43 | client.connectionMessage = MqttConnectMessage() 44 | .authenticateAs(id, token) 45 | .withClientIdentifier(clientId); 46 | 47 | client.connectionMessage.startClean(); 48 | } 49 | 50 | static Future connect() async { 51 | MqttClientConnectionStatus status; 52 | try { 53 | status = await client.connect(); 54 | 55 | print('===> Connection Status: $status'); 56 | return status; 57 | } catch (e) { 58 | print(e); 59 | return status; 60 | } 61 | } 62 | 63 | static dynamic subscribe() { 64 | return client.subscribe('u/$id', MqttQos.exactlyOnce); 65 | } 66 | 67 | static void subAndPub() async { 68 | // await MqttUtilities.asyncSleep(1); 69 | 70 | // This Works! 71 | var builder1 = MqttClientPayloadBuilder(); 72 | builder1.addString( 73 | json.encode({ 74 | 'type': 'msgText', 75 | 'data': 'Works!', 76 | 'identifier': Random().nextInt(1000000), 77 | }), 78 | ); 79 | 80 | client.publishMessage('u/$id', MqttQos.exactlyOnce, builder1.payload); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /test/issues/issue213.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | import 'dart:async'; 9 | import 'dart:convert'; 10 | import 'dart:io'; 11 | import 'package:mqtt_client/mqtt_client.dart'; 12 | import 'package:mqtt_client/mqtt_server_client.dart'; 13 | import 'package:test/test.dart'; 14 | 15 | Future main() async { 16 | test('State preservation across instances', () async { 17 | var client = MqttServerClient.withPort( 18 | 'test.mosquitto.org', 19 | 'client-id-123456789', 20 | 1883, 21 | ); 22 | client.autoReconnect = true; 23 | client.logging(on: false); 24 | const topic = 'xd/+'; 25 | await client.connect(); 26 | expect(client.connectionStatus.state, MqttConnectionState.connected); 27 | print("TEST - First subscription"); 28 | var firstSub = client.subscribe(topic, MqttQos.exactlyOnce); 29 | await MqttUtilities.asyncSleep(2); 30 | expect(client.getSubscriptionsStatus(topic), MqttSubscriptionStatus.active); 31 | expect(client.autoReconnect, isTrue); 32 | 33 | // OK, reinstantiate the client and do some basic checks before we connect 34 | sleep(Duration(seconds: 5)); 35 | client = MqttServerClient.withPort( 36 | 'test.mosquitto.org', 37 | 'client-id-123456789', 38 | 1883, 39 | ); 40 | client.logging(on: false); 41 | client.autoReconnect = true; 42 | client.resubscribeOnAutoReconnect = false; 43 | expect(client.connectionStatus.state, MqttConnectionState.disconnected); 44 | expect(client.autoReconnect, isTrue); 45 | 46 | // Connect 47 | print("TEST - reconnecting new client"); 48 | await client.connect(); 49 | print("TEST - new client reconnected"); 50 | 51 | // Re check our state 52 | expect(client.connectionStatus.state, MqttConnectionState.connected); 53 | expect(client.autoReconnect, isTrue); 54 | expect( 55 | client.getSubscriptionsStatus(topic), 56 | MqttSubscriptionStatus.doesNotExist, 57 | ); 58 | 59 | print("TEST - Second subscription"); 60 | var secondSub = client.subscribe(topic, MqttQos.exactlyOnce); 61 | await MqttUtilities.asyncSleep(5); 62 | expect(client.getSubscriptionsStatus(topic), MqttSubscriptionStatus.active); 63 | }, timeout: Timeout.factor(2)); 64 | 65 | return 0; 66 | } 67 | -------------------------------------------------------------------------------- /test/issues/issue622/issue622.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | import 'package:mqtt_client/mqtt_server_client.dart'; 9 | 10 | void main() async { 11 | bool logging = true; 12 | 13 | final mqttClient1 = MqttServerClient( 14 | 'test.mosquitto.org', 15 | 'Unique_ID-1', 16 | maxConnectionAttempts: 1, 17 | ); 18 | mqttClient1.logging(on: logging); 19 | mqttClient1.keepAlivePeriod = 3; 20 | mqttClient1.onConnected = onConnected1; 21 | mqttClient1.onDisconnected = onDisconnected1; 22 | mqttClient1.pongCallback = onPong1; 23 | await mqttClient1.connect(); 24 | 25 | final mqttClient2 = MqttServerClient( 26 | 'test.mosquitto.org', 27 | 'Unique_ID-2', 28 | maxConnectionAttempts: 1, 29 | ); 30 | mqttClient2.logging(on: logging); 31 | mqttClient2.keepAlivePeriod = 3; 32 | mqttClient2.onConnected = onConnected2; 33 | mqttClient2.onDisconnected = onDisconnected2; 34 | mqttClient2.pongCallback = onPong2; 35 | await mqttClient2.connect(); 36 | 37 | final mqttClient3 = MqttServerClient( 38 | 'test.mosquitto.org', 39 | 'Unique_ID-3', 40 | maxConnectionAttempts: 1, 41 | ); 42 | mqttClient3.logging(on: logging); 43 | mqttClient3.keepAlivePeriod = 3; 44 | mqttClient3.onConnected = onConnected3; 45 | mqttClient3.onDisconnected = onDisconnected3; 46 | mqttClient3.pongCallback = onPong3; 47 | await mqttClient3.connect(); 48 | 49 | await Future.delayed(Duration(seconds: 15)); 50 | 51 | print('Disconnecting clients'); 52 | mqttClient1.disconnect(); 53 | mqttClient2.disconnect(); 54 | mqttClient3.disconnect(); 55 | } 56 | 57 | void onConnected1() { 58 | print('Client 1 connected'); 59 | } 60 | 61 | void onConnected2() { 62 | print('Client 2 connected'); 63 | } 64 | 65 | void onConnected3() { 66 | print('Client 3 connected'); 67 | } 68 | 69 | void onDisconnected1() { 70 | print('Client 1 Disconnected'); 71 | } 72 | 73 | void onDisconnected2() { 74 | print('Client 2 Disconnected'); 75 | } 76 | 77 | void onDisconnected3() { 78 | print('Client 3 Disconnected'); 79 | } 80 | 81 | void onPong1() { 82 | print('Client 1 pong received.'); 83 | } 84 | 85 | void onPong2() { 86 | print('Client 2 pong received.'); 87 | } 88 | 89 | void onPong3() { 90 | print('Client 3 pong received.'); 91 | } 92 | -------------------------------------------------------------------------------- /lib/src/messages/connectack/mqtt_client_mqtt_connect_ack_variable_header.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 15/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_client.dart'; 9 | 10 | /// Implementation of the variable header for an MQTT ConnectAck message. 11 | class MqttConnectAckVariableHeader extends MqttVariableHeader { 12 | // Session present flag. 13 | // Only available for the 3.1.1 protocol, for 3.1 this is always false. 14 | bool _sessionPresent = false; 15 | 16 | bool get sessionPresent => _sessionPresent; 17 | set sessionPresent(bool present) { 18 | if (Protocol.version == MqttClientConstants.mqttV311ProtocolVersion) { 19 | _sessionPresent = present; 20 | } 21 | } 22 | 23 | /// Initializes a new instance of the MqttConnectVariableHeader class. 24 | MqttConnectAckVariableHeader(); 25 | 26 | /// Initializes a new instance of the MqttConnectVariableHeader class. 27 | MqttConnectAckVariableHeader.fromByteBuffer(super.headerStream) 28 | : super.fromByteBuffer(); 29 | 30 | /// Writes the variable header for an MQTT Connect message to 31 | /// the supplied stream. 32 | @override 33 | void writeTo(MqttByteBuffer variableHeaderStream) { 34 | sessionPresent 35 | ? variableHeaderStream.writeByte(1) 36 | : variableHeaderStream.writeByte(0); 37 | writeReturnCode(variableHeaderStream); 38 | } 39 | 40 | /// Creates a variable header from the specified header stream. 41 | @override 42 | void readFrom(MqttByteBuffer variableHeaderStream) { 43 | final ackConnectFlags = variableHeaderStream.readByte(); 44 | if (Protocol.version == MqttClientConstants.mqttV311ProtocolVersion) { 45 | sessionPresent = ackConnectFlags == 1; 46 | } 47 | readReturnCode(variableHeaderStream); 48 | } 49 | 50 | /// Gets the length of the write data when WriteTo will be called. 51 | /// This method is overriden by the ConnectAckVariableHeader because the 52 | /// variable header of this message type, for some reason, contains an extra 53 | /// byte that is not present in the variable header spec, meaning we have to 54 | /// do some custom serialization and deserialization. 55 | @override 56 | int getWriteLength() => 2; 57 | 58 | @override 59 | String toString() => 60 | 'Connect Variable Header: SessionPresent={$sessionPresent}, ' 61 | 'ReturnCode={$returnCode}'; 62 | } 63 | -------------------------------------------------------------------------------- /test/manual/mqtt_client_class.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 11/07/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | @TestOn('linux') 8 | library; 9 | 10 | import 'dart:io'; 11 | import 'package:test/test.dart'; 12 | 13 | /// These tests check the mqtt client subscribe/publish functionality against a publicly 14 | /// available(Mosquitto) MQTT broker. 15 | /// The tests are restricted to a linux environment for no other reason than my windows development 16 | /// box is firewalled and the tests will fail, if you wish to run these tests on Windows please remove 17 | /// the TestOn annotation. The servers are pinged first just to ensure they are up before the tests are run. 18 | 19 | /// Helper function to ping a server 20 | bool pingServer(String server) { 21 | // Not on Travis 22 | var isDeclared = String.fromEnvironment('PUB_ENVIRONMENT').isNotEmpty; 23 | if (isDeclared) { 24 | print('PUB_ENVIRONMENT is declared'); 25 | const noPing = String.fromEnvironment('PUB_ENVIRONMENT'); 26 | if (noPing == 'travis') { 27 | print('Skipping broker tests, running on travis'); 28 | return true; 29 | } else { 30 | print('PUB_ENVIRONMENT is $noPing'); 31 | } 32 | } 33 | final result = Process.runSync('ping', ['-c3', server]); 34 | //Get the exit code from the new process. 35 | if (result.exitCode == 0) { 36 | return false; 37 | } else { 38 | print( 39 | 'Server - $server is dead, exit code is ${result.exitCode} - skipping', 40 | ); 41 | return true; 42 | } 43 | } 44 | 45 | void main() { 46 | final skipTests = pingServer('test.mosquitto.org'); 47 | test('Broker Subscribe', () { 48 | final result = Process.runSync('dart', [ 49 | 'test/mqtt_client_broker_test_subscribe.dart', 50 | ]); 51 | print('Broker Subscribe::stdout'); 52 | print(result.stdout.toString()); 53 | print('Broker Subscribe::stderr'); 54 | print(result.stderr.toString()); 55 | expect(result.exitCode, 0); 56 | }, skip: skipTests); 57 | 58 | test('Broker Publish', () { 59 | final result = Process.runSync('dart', [ 60 | 'test/mqtt_client_broker_test_publish.dart', 61 | ]); 62 | print('Broker Publish::stdout'); 63 | print(result.stdout.toString()); 64 | print('Broker Publish::stderr'); 65 | print(result.stderr.toString()); 66 | expect(result.exitCode, 0); 67 | }, skip: skipTests); 68 | } 69 | -------------------------------------------------------------------------------- /lib/src/mqtt_browser_client.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_browser_client 3 | * Author : S. Hamblett 4 | * Date : 21/01/2020 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../mqtt_browser_client.dart'; 9 | 10 | class MqttBrowserClient extends MqttClient { 11 | /// Max connection attempts 12 | final int maxConnectionAttempts; 13 | 14 | /// Initializes a new instance of the MqttServerClient class using the 15 | /// default Mqtt Port. 16 | /// The server hostname or URL to connect to 17 | /// The client identifier to use to connect with 18 | MqttBrowserClient( 19 | super.server, 20 | super.clientIdentifier, { 21 | this.maxConnectionAttempts = 22 | MqttClientConstants.defaultMaxConnectionAttempts, 23 | }); 24 | 25 | /// Initializes a new instance of the MqttServerClient class using 26 | /// the supplied Mqtt Port. 27 | /// The server hostname to connect to 28 | /// The client identifier to use to connect with 29 | /// The port to use 30 | MqttBrowserClient.withPort( 31 | super.server, 32 | super.clientIdentifier, 33 | super.port, { 34 | this.maxConnectionAttempts = 35 | MqttClientConstants.defaultMaxConnectionAttempts, 36 | }) : super.withPort(); 37 | 38 | /// Performs a connect to the message broker with an optional 39 | /// username and password for the purposes of authentication. 40 | /// If a username and password are supplied these will override 41 | /// any previously set in a supplied connection message so if you 42 | /// supply your own connection message and use the authenticateAs method to 43 | /// set these parameters do not set them again here. 44 | @override 45 | Future connect([ 46 | String? username, 47 | String? password, 48 | ]) async { 49 | // A browser client 50 | MqttClientEnvironment.isWebClient = true; 51 | instantiationCorrect = true; 52 | clientEventBus = events.EventBus(); 53 | clientEventBus?.on().listen( 54 | disconnectOnNoPingResponse, 55 | ); 56 | clientEventBus?.on().listen( 57 | disconnectOnNoMessageSent, 58 | ); 59 | connectionHandler = SynchronousMqttBrowserConnectionHandler( 60 | clientEventBus, 61 | maxConnectionAttempts: maxConnectionAttempts, 62 | reconnectTimePeriod: connectTimeoutPeriod, 63 | ); 64 | return await super.connect(username, password); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/src/management/mqtt_client_topic_filter.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 01/02/2019 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../mqtt_client.dart'; 9 | 10 | /// This class allows specific topics to be listened for. It essentially 11 | /// acts as a bandpass filter for the topics you are interested in if 12 | /// you subscribe to more than one topic or use wildcard topics. 13 | /// Simply construct it, and listen to its message stream rather than 14 | /// that of the client. Note this class will only filter valid receive topics 15 | /// so if you filter on wildcard topics for instance, which you should only 16 | /// subscribe to, it will always generate a no match. 17 | class MqttClientTopicFilter { 18 | final String _topic; 19 | 20 | late SubscriptionTopic _subscriptionTopic; 21 | 22 | final Stream>?>? _clientUpdates; 23 | 24 | late StreamController>> _updates; 25 | 26 | /// The topic on which to filter 27 | String get topic => _topic; 28 | 29 | /// The stream on which all matching topic updates are published to 30 | Stream>> get updates => 31 | _updates.stream; 32 | 33 | /// Construction 34 | MqttClientTopicFilter(this._topic, this._clientUpdates) { 35 | _subscriptionTopic = SubscriptionTopic(_topic); 36 | _clientUpdates!.listen(_topicIn); 37 | _updates = 38 | StreamController>>.broadcast( 39 | sync: true, 40 | ); 41 | } 42 | 43 | void _topicIn(List>? c) { 44 | String? lastTopic; 45 | try { 46 | // Pass through if we have a match 47 | final List> tmp = 48 | >[]; 49 | for (final message in c!) { 50 | lastTopic = message.topic; 51 | if (_subscriptionTopic.matches(PublicationTopic(message.topic))) { 52 | tmp.add(message); 53 | } 54 | } 55 | if (tmp.isNotEmpty) { 56 | _updates.add(tmp); 57 | } 58 | } on RangeError catch (e) { 59 | MqttLogger.log( 60 | 'MqttClientTopicFilter::_topicIn - cannot process ' 61 | 'received topic: $lastTopic', 62 | ); 63 | MqttLogger.log('MqttClientTopicFilter::_topicIn - exception is $e'); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/src/messages/subscribeack/mqtt_client_mqtt_subscribe_ack_payload.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 19/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_client.dart'; 9 | 10 | /// Class that contains details related to an MQTT Subscribe Ack 11 | /// messages payload. 12 | class MqttSubscribeAckPayload extends MqttPayload { 13 | /// Variable header 14 | MqttVariableHeader? variableHeader; 15 | 16 | /// Message header 17 | MqttHeader? header; 18 | 19 | /// The collection of Qos grants, Key is the topic, Value is the qos 20 | List qosGrants = []; 21 | 22 | /// Initializes a new instance of the MqttSubscribeAckPayload class. 23 | MqttSubscribeAckPayload(); 24 | 25 | /// Initializes a new instance of the MqttSubscribeAckPayload class. 26 | MqttSubscribeAckPayload.fromByteBuffer( 27 | this.header, 28 | this.variableHeader, 29 | MqttByteBuffer payloadStream, 30 | ) { 31 | readFrom(payloadStream); 32 | } 33 | 34 | /// Writes the payload to the supplied stream. 35 | @override 36 | void writeTo(MqttByteBuffer payloadStream) { 37 | for (final value in qosGrants) { 38 | payloadStream.writeByte(value.index); 39 | } 40 | } 41 | 42 | /// Creates a payload from the specified header stream. 43 | @override 44 | void readFrom(MqttByteBuffer payloadStream) { 45 | var payloadBytesRead = 0; 46 | final payloadLength = header!.messageSize - variableHeader!.length; 47 | // Read the qos grants from the message payload 48 | while (payloadBytesRead < payloadLength) { 49 | final granted = MqttUtilities.getQosLevel(payloadStream.readByte()); 50 | payloadBytesRead++; 51 | addGrant(granted); 52 | } 53 | } 54 | 55 | /// Gets the length of the payload in bytes when written to a stream. 56 | @override 57 | int getWriteLength() => qosGrants.length; 58 | 59 | /// Adds a new QosGrant to the collection of QosGrants 60 | void addGrant(MqttQos grantedQos) { 61 | qosGrants.add(grantedQos); 62 | } 63 | 64 | /// Clears the grants. 65 | void clearGrants() { 66 | qosGrants.clear(); 67 | } 68 | 69 | @override 70 | String toString() { 71 | final sb = StringBuffer(); 72 | sb.writeln('Payload: Qos grants [{${qosGrants.length}}]'); 73 | for (final value in qosGrants) { 74 | sb.writeln('{{ Grant={$value} }}'); 75 | } 76 | return sb.toString(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/src/messages/publish/mqtt_client_mqtt_publish_payload.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 19/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_client.dart'; 9 | 10 | /// Class that contains details related to an MQTT Connect messages payload 11 | class MqttPublishPayload extends MqttPayload { 12 | /// Message header 13 | MqttHeader? header; 14 | 15 | /// Variable header 16 | MqttPublishVariableHeader? variableHeader; 17 | 18 | /// The message that forms the payload of the publish message. 19 | late typed.Uint8Buffer message; 20 | 21 | /// Initializes a new instance of the MqttPublishPayload class. 22 | MqttPublishPayload() { 23 | message = typed.Uint8Buffer(); 24 | } 25 | 26 | /// Initializes a new instance of the MqttPublishPayload class. 27 | MqttPublishPayload.fromByteBuffer( 28 | this.header, 29 | this.variableHeader, 30 | MqttByteBuffer payloadStream, 31 | ) { 32 | readFrom(payloadStream); 33 | } 34 | 35 | /// Creates a payload from the specified header stream. 36 | @override 37 | void readFrom(MqttByteBuffer payloadStream) { 38 | // The payload of the publish message is not a string, just 39 | // a binary chunk of bytes. 40 | // The length of the bytes is the length specified in the header, 41 | // minus any bytes spent in the variable header. 42 | final messageBytes = header!.messageSize - variableHeader!.length; 43 | message = payloadStream.readPayload(messageBytes); 44 | } 45 | 46 | /// Writes the payload to the supplied stream. 47 | @override 48 | void writeTo(MqttByteBuffer payloadStream) { 49 | payloadStream.write(message); 50 | } 51 | 52 | /// Gets the length of the payload in bytes when written to a stream. 53 | @override 54 | int getWriteLength() => message.length; 55 | 56 | @override 57 | String toString() => 58 | 'Payload: {${message.length} bytes={${bytesToString(message)}'; 59 | 60 | /// Converts an array of bytes to a byte string. 61 | static String bytesToString(typed.Uint8Buffer message) { 62 | final sb = StringBuffer(); 63 | for (final b in message) { 64 | sb.write('<'); 65 | sb.write(b); 66 | sb.write('>'); 67 | } 68 | return sb.toString(); 69 | } 70 | 71 | /// Converts an array of bytes to a character string. 72 | static String bytesToStringAsString(typed.Uint8Buffer message) { 73 | return utf8.decode(message.toList()); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/src/messages/connectack/mqtt_client_mqtt_connect_ack_message.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 15/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_client.dart'; 9 | 10 | /// Message that indicates a connection acknowledgement. 11 | /// 12 | /// On successful connection the [MqttClientConnectionStatus] class is updated 13 | /// with this message as returned by the broker. 14 | class MqttConnectAckMessage extends MqttMessage { 15 | /// Gets or sets the variable header contents. Contains extended 16 | /// metadata about the message 17 | late MqttConnectAckVariableHeader variableHeader; 18 | 19 | /// Initializes a new instance of the MqttConnectAckMessage class. 20 | /// Only called via the MqttMessage.Create operation during processing 21 | /// of an Mqtt message stream. 22 | MqttConnectAckMessage() { 23 | header = MqttHeader().asType(MqttMessageType.connectAck); 24 | variableHeader = MqttConnectAckVariableHeader(); 25 | variableHeader.returnCode = MqttConnectReturnCode.connectionAccepted; 26 | } 27 | 28 | /// Initializes a new instance of the MqttConnectAckMessage class. 29 | MqttConnectAckMessage.fromByteBuffer( 30 | MqttHeader header, 31 | MqttByteBuffer messageStream, 32 | ) { 33 | this.header = header; 34 | readFrom(messageStream); 35 | } 36 | 37 | /// Reads a message from the supplied stream. 38 | @override 39 | void readFrom(MqttByteBuffer messageStream) { 40 | super.readFrom(messageStream); 41 | variableHeader = MqttConnectAckVariableHeader.fromByteBuffer(messageStream); 42 | } 43 | 44 | /// Writes a message to the supplied stream. 45 | @override 46 | void writeTo(MqttByteBuffer messageStream) { 47 | header!.writeTo(variableHeader.getWriteLength(), messageStream); 48 | variableHeader.writeTo(messageStream); 49 | } 50 | 51 | /// Sets the return code of the Variable Header. 52 | MqttConnectAckMessage withReturnCode(MqttConnectReturnCode returnCode) { 53 | variableHeader.returnCode = returnCode; 54 | return this; 55 | } 56 | 57 | /// Sets the session present flag. 58 | /// Can only be set if the protocol is 3.1.1 59 | MqttConnectAckMessage withSessionPresent(bool present) { 60 | variableHeader.sessionPresent = present; 61 | return this; 62 | } 63 | 64 | @override 65 | String toString() { 66 | final sb = StringBuffer(); 67 | sb.write(super.toString()); 68 | sb.writeln(variableHeader.toString()); 69 | return sb.toString(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/src/observable/src/change_notifier.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:meta/meta.dart'; 4 | 5 | import 'observable.dart'; 6 | import 'records.dart'; 7 | 8 | /// Supplies [changes] and various hooks to implement [Observable]. 9 | /// 10 | /// May use [notifyChange] to queue a change record; they are asynchronously 11 | /// delivered at the end of the VM turn. 12 | /// 13 | /// [ChangeNotifier] may be extended, mixed in, or used as a delegate. 14 | class ChangeNotifier implements Observable { 15 | late StreamController> _changes; 16 | 17 | bool _scheduled = false; 18 | List? _queue; 19 | 20 | /// Whether [changes] has at least one active listener. 21 | /// 22 | /// May be used to optimize whether to produce change records. 23 | @override 24 | bool get hasObservers => _changes.hasListener; 25 | 26 | /// Emits a list of changes when the state of the object changes. 27 | /// 28 | /// Changes should produced in order, if significant. 29 | @override 30 | Stream> get changes => 31 | (_changes = StreamController>.broadcast( 32 | sync: true, 33 | onListen: observed, 34 | onCancel: unobserved, 35 | )).stream; 36 | 37 | /// May override to be notified when [changes] is first observed. 38 | @override 39 | @mustCallSuper 40 | void observed() { 41 | return; 42 | } 43 | 44 | /// May override to be notified when [changes] is no longer observed. 45 | @override 46 | @mustCallSuper 47 | void unobserved() { 48 | _changes.close(); 49 | } 50 | 51 | /// If [hasObservers], synchronously emits [changes] that have been queued. 52 | /// 53 | /// Returns `true` if changes were emitted. 54 | @override 55 | @mustCallSuper 56 | bool deliverChanges() { 57 | List changes; 58 | if (_scheduled && hasObservers) { 59 | changes = ChangeRecord.any as List; 60 | _scheduled = false; 61 | _changes.add(changes); 62 | return true; 63 | } 64 | return false; 65 | } 66 | 67 | /// Schedules [change] to be delivered. 68 | /// 69 | /// If [change] is omitted then [ChangeRecord.any] will be sent. 70 | /// 71 | /// If there are no listeners to [changes], this method does nothing. 72 | @override 73 | void notifyChange([C? change]) { 74 | if (!hasObservers) { 75 | return; 76 | } 77 | if (change != null) { 78 | (_queue ??= []).add(change); 79 | } 80 | if (!_scheduled) { 81 | scheduleMicrotask(deliverChanges); 82 | _scheduled = true; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/src/encoding/mqtt_client_mqtt_encoding.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../mqtt_client.dart'; 9 | 10 | /// Encoding implementation that can encode and decode strings 11 | /// in the MQTT string format. 12 | /// 13 | /// The MQTT string format is simply a pascal string with ANSI character 14 | /// encoding. The first 2 bytes define the length of the string, and they 15 | /// are followed by the string itself. 16 | class MqttEncoding { 17 | /// Encodes all the characters in the specified string 18 | /// into a sequence of bytes. 19 | typed.Uint8Buffer getBytes(String s) { 20 | _validateString(s); 21 | final stringConverted = utf8.encoder.convert(s); 22 | if (stringConverted.length > 65535) { 23 | throw Exception( 24 | 'MqttUtf8Encoding::toUtf8 - UTF8 string length is invalid, length is ${stringConverted.length}', 25 | ); 26 | } 27 | final stringBytes = typed.Uint8Buffer(); 28 | stringBytes.add(stringConverted.length >> 8); 29 | stringBytes.add(stringConverted.length & 0xFF); 30 | stringBytes.addAll(stringConverted); 31 | return stringBytes; 32 | } 33 | 34 | /// Decodes the bytes in the specified byte array into a string. 35 | String getString(typed.Uint8Buffer bytes) => 36 | utf8.decoder.convert(bytes.toList()); 37 | 38 | /// When overridden in a derived class, calculates the number of characters 39 | /// produced by decoding all the bytes in the specified byte array. 40 | int getCharCount(typed.Uint8Buffer bytes) { 41 | if (bytes.length < 2) { 42 | throw Exception( 43 | 'mqtt_client::MQTTEncoding: Length byte array must comprise 2 bytes', 44 | ); 45 | } 46 | return (bytes.first << 8) + bytes[1]; 47 | } 48 | 49 | /// Calculates the number of bytes produced by encoding the 50 | /// characters in the specified. 51 | int getByteCount(String chars) => getBytes(chars).length; 52 | 53 | /// Validates the string to ensure it doesn't contain any characters 54 | /// invalid within the Mqtt string format. 55 | static void _validateString(String s) { 56 | for (var i = 0; i < s.length; i++) { 57 | if (Protocol.version == MqttClientConstants.mqttV31ProtocolVersion) { 58 | if (s.codeUnitAt(i) > 0x7F) { 59 | throw Exception( 60 | 'mqtt_client::MQTTEncoding: The input string has extended ' 61 | 'UTF characters, which are not supported', 62 | ); 63 | } 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/src/messages/subscribeack/mqtt_client_mqtt_subscribe_ack_message.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 19/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_client.dart'; 9 | 10 | /// Implementation of an MQTT Subscribe Ack Message. 11 | class MqttSubscribeAckMessage extends MqttMessage { 12 | /// Gets or sets the variable header contents. Contains extended 13 | /// metadata about the message. 14 | MqttSubscribeAckVariableHeader? variableHeader; 15 | 16 | /// Gets or sets the payload of the Mqtt Message. 17 | late MqttSubscribeAckPayload payload; 18 | 19 | /// Initializes a new instance of the MqttSubscribeAckMessage class. 20 | MqttSubscribeAckMessage() { 21 | header = MqttHeader().asType(MqttMessageType.subscribeAck); 22 | variableHeader = MqttSubscribeAckVariableHeader(); 23 | payload = MqttSubscribeAckPayload(); 24 | } 25 | 26 | /// Initializes a new instance of the MqttSubscribeAckMessage class. 27 | MqttSubscribeAckMessage.fromByteBuffer( 28 | MqttHeader header, 29 | MqttByteBuffer messageStream, 30 | ) { 31 | this.header = header; 32 | readFrom(messageStream); 33 | } 34 | 35 | /// Writes the message to the supplied stream. 36 | @override 37 | void writeTo(MqttByteBuffer messageStream) { 38 | header!.writeTo( 39 | variableHeader!.getWriteLength() + payload.getWriteLength(), 40 | messageStream, 41 | ); 42 | variableHeader!.writeTo(messageStream); 43 | payload.writeTo(messageStream); 44 | } 45 | 46 | /// Reads a message from the supplied stream. 47 | @override 48 | void readFrom(MqttByteBuffer messageStream) { 49 | variableHeader = MqttSubscribeAckVariableHeader.fromByteBuffer( 50 | messageStream, 51 | ); 52 | payload = MqttSubscribeAckPayload.fromByteBuffer( 53 | header, 54 | variableHeader, 55 | messageStream, 56 | ); 57 | } 58 | 59 | /// Sets the message identifier on the subscribe message. 60 | MqttSubscribeAckMessage withMessageIdentifier(int messageIdentifier) { 61 | variableHeader!.messageIdentifier = messageIdentifier; 62 | return this; 63 | } 64 | 65 | /// Adds a Qos grant to the message. 66 | MqttSubscribeAckMessage addQosGrant(MqttQos qosGranted) { 67 | payload.addGrant(qosGranted); 68 | return this; 69 | } 70 | 71 | @override 72 | String toString() { 73 | final sb = StringBuffer(); 74 | sb.write(super.toString()); 75 | sb.writeln(variableHeader.toString()); 76 | sb.writeln(payload.toString()); 77 | return sb.toString(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/src/messages/connect/mqtt_client_mqtt_connect_flags.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_client.dart'; 9 | 10 | /// Represents the connect flags part of the MQTT Variable Header 11 | class MqttConnectFlags { 12 | /// Reserved1 13 | bool reserved1 = false; 14 | 15 | /// Clean start 16 | bool cleanStart = false; 17 | 18 | /// Will 19 | bool willFlag = false; 20 | 21 | /// Will Qos 22 | MqttQos willQos = MqttQos.atMostOnce; 23 | 24 | /// Will retain 25 | bool willRetain = false; 26 | 27 | /// Password present 28 | bool passwordFlag = false; 29 | 30 | /// Username present 31 | bool usernameFlag = false; 32 | 33 | /// Initializes a new instance of the MqttConnectFlags class. 34 | MqttConnectFlags(); 35 | 36 | /// Initializes a new instance of the MqttConnectFlags class configured 37 | /// as per the supplied stream. 38 | MqttConnectFlags.fromByteBuffer(MqttByteBuffer connectFlagsStream) { 39 | readFrom(connectFlagsStream); 40 | } 41 | 42 | /// Return the connect flag value 43 | int connectFlagByte() => 44 | (reserved1 ? 1 : 0) | 45 | (cleanStart ? 1 : 0) << 1 | 46 | (willFlag ? 1 : 0) << 2 | 47 | (willQos.index) << 3 | 48 | (willRetain ? 1 : 0) << 5 | 49 | (passwordFlag ? 1 : 0) << 6 | 50 | (usernameFlag ? 1 : 0) << 7; 51 | 52 | /// Writes the connect flag byte to the supplied stream. 53 | void writeTo(MqttByteBuffer connectFlagsStream) { 54 | connectFlagsStream.writeByte(connectFlagByte()); 55 | } 56 | 57 | /// Reads the connect flags from the underlying stream. 58 | void readFrom(MqttByteBuffer stream) { 59 | final connectFlagsByte = stream.readByte(); 60 | 61 | reserved1 = (connectFlagsByte & 1) == 1; 62 | cleanStart = (connectFlagsByte & 2) == 2; 63 | willFlag = (connectFlagsByte & 4) == 4; 64 | willQos = MqttUtilities.getQosLevel((connectFlagsByte >> 3) & 3); 65 | willRetain = (connectFlagsByte & 32) == 32; 66 | passwordFlag = (connectFlagsByte & 64) == 64; 67 | usernameFlag = (connectFlagsByte & 128) == 128; 68 | } 69 | 70 | /// Gets the length of data written when WriteTo is called. 71 | static int getWriteLength() => 1; 72 | 73 | /// Returns a String that represents the current connect flag settings 74 | @override 75 | String toString() => 76 | 'Connect Flags: Reserved1=$reserved1, CleanStart=$cleanStart, ' 77 | 'WillFlag=$willFlag, WillQos=$willQos, WillRetain=$willRetain, ' 78 | 'PasswordFlag=$passwordFlag, UserNameFlag=$usernameFlag'; 79 | } 80 | -------------------------------------------------------------------------------- /lib/src/messages/mqtt_client_mqtt_message.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../mqtt_client.dart'; 9 | 10 | /// Represents an MQTT message that contains a fixed header, variable 11 | /// header and message body. 12 | /// 13 | /// Messages roughly look as follows. 14 | /// ---------------------------- 15 | /// | Header, 2-5 Bytes Length | 16 | /// ---------------------------- 17 | /// | Variable Header(VH) | 18 | /// | n Bytes Length | 19 | /// ---------------------------- 20 | /// | Message Payload | 21 | /// | 256MB minus VH Size | 22 | /// ---------------------------- 23 | 24 | class MqttMessage { 25 | /// The header of the MQTT Message. Contains metadata about the message 26 | MqttHeader? header; 27 | 28 | /// Initializes a new instance of the MqttMessage class. 29 | MqttMessage(); 30 | 31 | /// Initializes a new instance of the MqttMessage class. 32 | MqttMessage.fromHeader(MqttHeader this.header); 33 | 34 | /// Creates a new instance of an MQTT Message based on a raw message stream. 35 | static MqttMessage createFrom(MqttByteBuffer messageStream) { 36 | try { 37 | var header = MqttHeader(); 38 | // Pass the input stream sequentially through the component 39 | // deserialization(create) methods to build a full MqttMessage. 40 | header = MqttHeader.fromByteBuffer(messageStream); 41 | if (messageStream.availableBytes < header.messageSize) { 42 | messageStream.reset(); 43 | throw InvalidMessageException( 44 | 'Available bytes is less than the message size', 45 | ); 46 | } 47 | return MqttMessageFactory.getMessage(header, messageStream); 48 | } on Exception catch (e, stack) { 49 | Error.throwWithStackTrace( 50 | InvalidMessageException( 51 | 'The data provided in the message stream was not a ' 52 | 'valid MQTT Message, ' 53 | 'exception is $e', 54 | ), 55 | stack, 56 | ); 57 | } 58 | } 59 | 60 | /// Writes the message to the supplied stream. 61 | void writeTo(MqttByteBuffer messageStream) { 62 | header!.writeTo(0, messageStream); 63 | } 64 | 65 | /// Reads a message from the supplied stream. 66 | void readFrom(MqttByteBuffer _) { 67 | return; 68 | } 69 | 70 | @override 71 | String toString() { 72 | final sb = StringBuffer(); 73 | sb.write('MQTTMessage of type '); 74 | sb.writeln(header!.messageType.toString()); 75 | sb.writeln(header.toString()); 76 | return sb.toString(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/src/messages/unsubscribe/mqtt_client_mqtt_unsubscribe_payload.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 19/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_client.dart'; 9 | 10 | /// Class that contains details related to an MQTT Unsubscribe messages payload 11 | class MqttUnsubscribePayload extends MqttPayload { 12 | /// Variable header 13 | MqttVariableHeader? variableHeader; 14 | 15 | /// Message header 16 | MqttHeader? header; 17 | 18 | /// The collection of subscriptions. 19 | List subscriptions = []; 20 | 21 | /// Initializes a new instance of the MqttUnsubscribePayload class. 22 | MqttUnsubscribePayload(); 23 | 24 | /// Initializes a new instance of the MqttUnsubscribePayload class. 25 | MqttUnsubscribePayload.fromByteBuffer( 26 | this.header, 27 | this.variableHeader, 28 | MqttByteBuffer payloadStream, 29 | ) { 30 | readFrom(payloadStream); 31 | } 32 | 33 | /// Writes the payload to the supplied stream. 34 | @override 35 | void writeTo(MqttByteBuffer payloadStream) { 36 | subscriptions.forEach(payloadStream.writeMqttStringM); 37 | } 38 | 39 | /// Creates a payload from the specified header stream. 40 | @override 41 | void readFrom(MqttByteBuffer payloadStream) { 42 | var payloadBytesRead = 0; 43 | final payloadLength = header!.messageSize - variableHeader!.length; 44 | // Read all the topics and qos subscriptions from the message payload 45 | while (payloadBytesRead < payloadLength) { 46 | final topic = payloadStream.readMqttStringM(); 47 | payloadBytesRead += topic.length + 2; // +2 = Mqtt string length bytes 48 | addSubscription(topic); 49 | } 50 | } 51 | 52 | /// Gets the length of the payload in bytes when written to a stream. 53 | @override 54 | int getWriteLength() { 55 | var length = 0; 56 | final enc = MqttEncoding(); 57 | for (final subscription in subscriptions) { 58 | length += enc.getByteCount(subscription); 59 | } 60 | return length; 61 | } 62 | 63 | /// Adds a new subscription to the collection of subscriptions. 64 | void addSubscription(String topic) { 65 | subscriptions.add(topic); 66 | } 67 | 68 | /// Clears the subscriptions. 69 | void clearSubscriptions() { 70 | subscriptions.clear(); 71 | } 72 | 73 | @override 74 | String toString() { 75 | final sb = StringBuffer(); 76 | sb.writeln('Payload: Unsubscription [{${subscriptions.length}}]'); 77 | for (final subscription in subscriptions) { 78 | sb.writeln('{{ Topic={$subscription}}'); 79 | } 80 | return sb.toString(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /test/support/mqtt_client_broker_test_iotcore.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 02/10/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | import 'dart:async'; 9 | 10 | import 'package:mqtt_client/mqtt_client.dart'; 11 | import 'package:typed_data/typed_data.dart' as typed; 12 | import 'package:mqtt_client/mqtt_server_client.dart'; 13 | 14 | Future main() async { 15 | // Create and connect the client 16 | const url = 'mqtt.googleapis.com'; 17 | const port = 443; 18 | const clientId = 19 | 'projects/warm-actor-356/locations/europe-west1/registries/home-sensors/devices/dummy-sensor'; 20 | const username = 'unused'; 21 | const password = 22 | 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MDcyMTM0NzMsImlhdCI6MTUwNzIwOTg3MywiYXVkIjoid2FybS1hY3Rvci0zNTYifQ.NGLiu9svhI6BhGeodGfbBQGGRjiX4j-9bxQdWWYa_2LEjCHdbmDTQC6eHoDHf6nTMMADiQa3sKqD9cZ1gtdT-wfAzEqvJX1Hy5w0Ex8jqe_qidS8Iwtj1TVsvnlXr6OPyHuwW9hAcuOFdlNIXYqDyXDSFl--qa7HS1zqXEy9FMbg20Y8xNSMk1MLG22i8STvYrQmNfm-ib47WayUojllgy2ukMee_N67G2bXq91U3gU0YhlDX4_INjwSTaAtJ4p70Vvd21NFsVBaf0FdJAix5Zsdk165XXjLU6FsfOAzcdeiazzlPFTC-HvQ1eXz4BLn0AaMIFoOkwV9SgBuTdLX8IU3T2hKchtsNw4r5YJa8qw3hu-egsH8bHmSX1cVhjbdWHWihjOnJO_0ef8jWQ6K87Pwhjrc_mBaKo1REllvGV7bOgXoFXW2t1vnb4MtiC7ZpYo5bR9FUsbO_CVMNYHIld6YSmOeO6GCP7OF9kkhEeHGgIIFjsLiAQaqoTCm0EGTh8dTZoYnpv3mRrOw61BgTjPAFvP9OK0hDw4EWXwINoT1UTCQTXF1no_7TZn4wgy-Glx1RA_EGqgEuDSe77H5Oc0aQHj3c01mwlbHJxsmguhSWgdOdc1WPbXqYkJJhcQ-PUvCGuJL5Ut5500dBztdsYaVaRpReOstj0W-a2AF1nU'; 23 | final client = MqttServerClient(url, clientId); 24 | client.port = port; 25 | client.secure = true; 26 | // V3.1.1 for iot-core 27 | client.setProtocolV311(); 28 | //final String currDir = path.current + path.separator; 29 | //client.trustedCertPath = currDir + path.join('test', 'pem', 'roots.pem'); 30 | client.logging(on: false); 31 | await client.connect(username, password); 32 | if (client.connectionStatus!.state == MqttConnectionState.connected) { 33 | print('iotcore client connected'); 34 | } else { 35 | print( 36 | 'ERROR iotcore client connection failed - disconnecting, state is ${client.connectionStatus!.state}', 37 | ); 38 | client.disconnect(); 39 | } 40 | // Publish a known topic 41 | const topic = '/devices/dummy-sensor/events'; 42 | final buff = typed.Uint8Buffer(4); 43 | buff[0] = 'a'.codeUnitAt(0); 44 | buff[1] = 'b'.codeUnitAt(0); 45 | buff[2] = 'c'.codeUnitAt(0); 46 | buff[3] = 'd'.codeUnitAt(0); 47 | client.publishMessage(topic, MqttQos.exactlyOnce, buff); 48 | print('Sleeping....'); 49 | await MqttUtilities.asyncSleep(10); 50 | print('Disconnecting'); 51 | client.disconnect(); 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /lib/src/observable/src/observable.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library; 6 | 7 | import 'dart:async'; 8 | 9 | import 'package:meta/meta.dart'; 10 | 11 | import 'change_notifier.dart'; 12 | import 'records.dart'; 13 | 14 | /// Represents an object with observable state or properties. 15 | /// 16 | /// The interface does not require any specific technique to implement 17 | /// observability. You may implement it in the following ways: 18 | /// - Extend or mixin [ChangeNotifier] 19 | /// - Implement the interface yourself and provide your own implementation 20 | abstract class Observable { 21 | // To be removed when https://github.com/dart-lang/observable/issues/10 22 | final ChangeNotifier _delegate = ChangeNotifier(); 23 | 24 | /// Emits a list of changes when the state of the object changes. 25 | /// 26 | /// Changes should produced in order, if significant. 27 | Stream?> get changes => _delegate.changes; 28 | 29 | /// True if this object has any observers. 30 | @Deprecated('Use ChangeNotifier instead to have this method available') 31 | bool get hasObservers => _delegate.hasObservers; 32 | 33 | /// May override to be notified when [changes] is first observed. 34 | @protected 35 | @mustCallSuper 36 | @Deprecated('Use ChangeNotifier instead to have this method available') 37 | // REMOVE IGNORE when https://github.com/dart-lang/observable/issues/10 38 | void observed() => _delegate.observed(); 39 | 40 | /// May override to be notified when [changes] is no longer observed. 41 | @protected 42 | @mustCallSuper 43 | @Deprecated('Use ChangeNotifier instead to have this method available') 44 | // REMOVE IGNORE when https://github.com/dart-lang/observable/issues/10 45 | void unobserved() => _delegate.unobserved(); 46 | 47 | /// If [hasObservers], synchronously emits [changes] that have been queued. 48 | /// 49 | /// Returns `true` if changes were emitted. 50 | @Deprecated('Use ChangeNotifier instead to have this method available') 51 | // REMOVE IGNORE when https://github.com/dart-lang/observable/issues/10 52 | bool deliverChanges() => _delegate.deliverChanges(); 53 | 54 | /// Schedules [change] to be delivered. 55 | /// 56 | /// If [change] is omitted then [ChangeRecord.any] will be sent. 57 | /// 58 | /// If there are no listeners to [changes], this method does nothing. 59 | @Deprecated('Use ChangeNotifier instead to have this method available') 60 | void notifyChange([C? change]) => _delegate.notifyChange(change); 61 | } 62 | -------------------------------------------------------------------------------- /test/issues/issue209/issue209-normal-mosquitto.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | import 'dart:async'; 9 | import 'dart:convert'; 10 | import 'package:mqtt_client/mqtt_client.dart'; 11 | import 'package:mqtt_client/mqtt_server_client.dart'; 12 | import 'package:test/test.dart'; 13 | 14 | Future main() async { 15 | test('Should maintain subscriptions after autoReconnect', () async { 16 | final client = MqttServerClient.withPort( 17 | 'test.mosquitto.org', 18 | 'client-id-123456789', 19 | 1883, 20 | ); 21 | client.autoReconnect = true; 22 | client.logging(on: false); 23 | const topic = 'xd/+'; 24 | 25 | // Subscribe callback, we do the auto reconnect when we know we have subscribed 26 | // second time is from the resubscribe so re publish. 27 | var ignoreSubscribe = false; 28 | void subCB(subTopic) async { 29 | if (ignoreSubscribe) { 30 | print( 31 | 'ISSUE: Received re-subscribe callback for our topic - re publishing', 32 | ); 33 | client.publishMessage( 34 | 'xd/light', 35 | MqttQos.exactlyOnce, 36 | (MqttClientPayloadBuilder()..addUTF8String('xd')).payload, 37 | ); 38 | return; 39 | } 40 | if (topic == subTopic) { 41 | print( 42 | 'ISSUE: Received subscribe callback for our topic - auto reconnecting', 43 | ); 44 | client.doAutoReconnect(force: true); 45 | } else { 46 | print('ISSUE: Received subscribe callback for unknown topic $subTopic'); 47 | } 48 | ignoreSubscribe = true; 49 | print('ISSUE: Exiting subscribe callback'); 50 | } 51 | 52 | // Main test starts here 53 | print('ISSUE: Main test start'); 54 | client.onSubscribed = subCB; // Subscribe callback 55 | print('ISSUE: Connecting'); 56 | await client.connect(); 57 | client.subscribe(topic, MqttQos.exactlyOnce); 58 | 59 | // Now publish the message 60 | print('ISSUE: Publishing'); 61 | client.publishMessage( 62 | 'xd/light', 63 | MqttQos.exactlyOnce, 64 | (MqttClientPayloadBuilder()..addUTF8String('xd')).payload, 65 | ); 66 | 67 | // Listen for our responses. 68 | print('ISSUE: Listening >>>>'); 69 | final stream = client.updates 70 | .expand((event) sync* { 71 | for (var e in event) { 72 | MqttPublishMessage message = e.payload; 73 | yield utf8.decode(message.payload.message); 74 | } 75 | }) 76 | .timeout(Duration(seconds: 7)); 77 | 78 | expect(await stream.first, equals('xd')); 79 | print('ISSUE: Test complete'); 80 | }); 81 | 82 | return 0; 83 | } 84 | -------------------------------------------------------------------------------- /test/issues/issue209/issue209-normal-hive.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | import 'dart:async'; 9 | import 'dart:convert'; 10 | import 'package:mqtt_client/mqtt_client.dart'; 11 | import 'package:mqtt_client/mqtt_server_client.dart'; 12 | import 'package:test/test.dart'; 13 | 14 | Future main() async { 15 | test('Should maintain subscriptions after autoReconnect', () async { 16 | final client = MqttServerClient.withPort( 17 | 'broker.hivemq.com', 18 | 'client-id-123456789', 19 | 1883, 20 | ); 21 | client.autoReconnect = true; 22 | client.logging(on: false); 23 | const topic = 'xd/+'; 24 | 25 | // Subscribe callback, we do the auto reconnect when we know we have subscribed 26 | // second time is from the resubscribe so re publish. 27 | var ignoreSubscribe = false; 28 | void subCB(subTopic) async { 29 | if (ignoreSubscribe) { 30 | print( 31 | 'ISSUE: Received re-subscribe callback for our topic - re publishing', 32 | ); 33 | client.publishMessage( 34 | 'xd/light', 35 | MqttQos.exactlyOnce, 36 | (MqttClientPayloadBuilder()..addUTF8String('xd')).payload, 37 | ); 38 | return; 39 | } 40 | if (topic == subTopic) { 41 | print( 42 | 'ISSUE: Received subscribe callback for our topic - auto reconnecting', 43 | ); 44 | client.doAutoReconnect(force: true); 45 | } else { 46 | print('ISSUE: Received subscribe callback for unknown topic $subTopic'); 47 | } 48 | ignoreSubscribe = true; 49 | print('ISSUE: Exiting subscribe callback'); 50 | } 51 | 52 | // Main test starts here 53 | print('ISSUE: Main test start'); 54 | client.onSubscribed = subCB; // Subscribe callback 55 | print('ISSUE: Connecting'); 56 | await client.connect('user', 'password'); 57 | client.subscribe(topic, MqttQos.exactlyOnce); 58 | 59 | // Now publish the message 60 | print('ISSUE: Publishing'); 61 | client.publishMessage( 62 | 'xd/light', 63 | MqttQos.exactlyOnce, 64 | (MqttClientPayloadBuilder()..addUTF8String('xd')).payload, 65 | ); 66 | 67 | // Listen for our responses. 68 | print('ISSUE: Listening >>>>'); 69 | final stream = client.updates 70 | .expand((event) sync* { 71 | for (var e in event) { 72 | MqttPublishMessage message = e.payload; 73 | yield utf8.decode(message.payload.message); 74 | } 75 | }) 76 | .timeout(Duration(seconds: 7)); 77 | 78 | expect(await stream.first, equals('xd')); 79 | print('ISSUE: Test complete'); 80 | }); 81 | 82 | return 0; 83 | } 84 | -------------------------------------------------------------------------------- /test/mqtt_client_connection_autoreconnect_nobroker_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 27/03/2020 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | @TestOn('vm') 9 | library; 10 | 11 | import 'dart:io'; 12 | 13 | import 'package:mqtt_client/mqtt_client.dart'; 14 | import 'package:mqtt_client/mqtt_server_client.dart'; 15 | import 'package:test/test.dart'; 16 | import 'support/mqtt_client_mock_socket.dart'; 17 | 18 | void main() { 19 | // Test wide variables 20 | const testClientId = 'SJHMQTTClient'; 21 | 22 | test('Connected - Broker Disconnects Stays Inactive', () async { 23 | await IOOverrides.runZoned( 24 | () async { 25 | var autoReconnectCallbackCalled = false; 26 | var disconnectCallbackCalled = false; 27 | var connectionFailedCallbackCalled = false; 28 | 29 | void autoReconnect() { 30 | autoReconnectCallbackCalled = true; 31 | } 32 | 33 | void disconnect() { 34 | disconnectCallbackCalled = true; 35 | } 36 | 37 | void connectionFailed(int attempt) { 38 | connectionFailedCallbackCalled = true; 39 | } 40 | 41 | final client = MqttServerClient('localhost', testClientId); 42 | client.logging(on: false); 43 | client.keepAlivePeriod = 1; 44 | client.autoReconnect = true; 45 | final socketOption = RawSocketOption.fromInt(6, 0x10, 2); 46 | client.socketOptions.add(socketOption); 47 | client.onAutoReconnect = autoReconnect; 48 | client.onDisconnected = disconnect; 49 | client.onFailedConnectionAttempt = connectionFailed; 50 | const username = 'unused 4'; 51 | print(username); 52 | const password = 'password 4'; 53 | print(password); 54 | await client.connect(); 55 | expect( 56 | client.connectionStatus!.state == MqttConnectionState.connected, 57 | isTrue, 58 | ); 59 | await MqttUtilities.asyncSleep(2); 60 | expect(autoReconnectCallbackCalled, isTrue); 61 | expect(disconnectCallbackCalled, isFalse); 62 | expect(connectionFailedCallbackCalled, isFalse); 63 | expect( 64 | client.connectionStatus!.state == MqttConnectionState.connecting, 65 | isTrue, 66 | ); 67 | }, 68 | socketConnect: 69 | ( 70 | dynamic host, 71 | int port, { 72 | dynamic sourceAddress, 73 | int sourcePort = 0, 74 | Duration? timeout, 75 | }) => MqttMockSocketScenario1.connect( 76 | host, 77 | port, 78 | sourceAddress: sourceAddress, 79 | sourcePort: sourcePort, 80 | timeout: timeout, 81 | ), 82 | ); 83 | }); 84 | } 85 | -------------------------------------------------------------------------------- /test/issues/issue209/issue209-ws-mosquitto.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | import 'dart:async'; 9 | import 'dart:convert'; 10 | import 'package:mqtt_client/mqtt_client.dart'; 11 | import 'package:mqtt_client/mqtt_server_client.dart'; 12 | import 'package:test/test.dart'; 13 | 14 | Future main() async { 15 | test('Should maintain subscriptions after autoReconnect', () async { 16 | final client = MqttServerClient.withPort( 17 | 'ws://test.mosquitto.org', 18 | 'client-id-123456789', 19 | 8080, 20 | ); 21 | client.autoReconnect = true; 22 | client.logging(on: false); 23 | const topic = 'xd/+'; 24 | 25 | // Subscribe callback, we do the auto reconnect when we know we have subscribed 26 | // second time is from the resubscribe so re publish. 27 | var ignoreSubscribe = false; 28 | void subCB(subTopic) async { 29 | if (ignoreSubscribe) { 30 | print( 31 | 'ISSUE: Received re-subscribe callback for our topic - re publishing', 32 | ); 33 | client.publishMessage( 34 | 'xd/light', 35 | MqttQos.exactlyOnce, 36 | (MqttClientPayloadBuilder()..addUTF8String('xd')).payload, 37 | ); 38 | return; 39 | } 40 | if (topic == subTopic) { 41 | print( 42 | 'ISSUE: Received subscribe callback for our topic - auto reconnecting', 43 | ); 44 | client.doAutoReconnect(force: true); 45 | } else { 46 | print('ISSUE: Received subscribe callback for unknown topic $subTopic'); 47 | } 48 | ignoreSubscribe = true; 49 | print('ISSUE: Exiting subscribe callback'); 50 | } 51 | 52 | // Main test starts here 53 | print('ISSUE: Main test start'); 54 | client.onSubscribed = subCB; // Subscribe callback 55 | client.useWebSocket = true; 56 | print('ISSUE: Connecting'); 57 | await client.connect(); 58 | client.subscribe(topic, MqttQos.exactlyOnce); 59 | 60 | // Now publish the message 61 | print('ISSUE: Publishing'); 62 | client.publishMessage( 63 | 'xd/light', 64 | MqttQos.exactlyOnce, 65 | (MqttClientPayloadBuilder()..addUTF8String('xd')).payload, 66 | ); 67 | 68 | // Listen for our responses. 69 | print('ISSUE: Listening >>>>'); 70 | final stream = client.updates 71 | .expand((event) sync* { 72 | for (var e in event) { 73 | MqttPublishMessage message = e.payload; 74 | yield utf8.decode(message.payload.message); 75 | } 76 | }) 77 | .timeout(Duration(seconds: 10)); 78 | 79 | expect(await stream.first, equals('xd')); 80 | print('ISSUE: Test complete'); 81 | }); 82 | 83 | return 0; 84 | } 85 | -------------------------------------------------------------------------------- /test/manual/mqtt_client_mosquitto_ws_browser_test_manual.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 24/01/2020 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | @TestOn('browser') 9 | library; 10 | 11 | import 'package:mqtt_client/mqtt_client.dart'; 12 | import 'package:mqtt_client/mqtt_browser_client.dart'; 13 | import 'package:test/test.dart'; 14 | 15 | void main() { 16 | const mosquittoServer = 'ws://test.mosquitto.org'; 17 | const mosquittoPort = 8080; 18 | const testClientId = 'syncMqttTests'; 19 | 20 | group('Broker tests', () { 21 | test('Connect mosquitto test broker', () async { 22 | var pongCount = 0; 23 | void connectCallback() { 24 | print('Browser client connected callback'); 25 | } 26 | 27 | void pongCallback() { 28 | print('Browser client pong callback'); 29 | pongCount++; 30 | } 31 | 32 | final sleeper = MqttCancellableAsyncSleep(25000); 33 | final client = MqttBrowserClient(mosquittoServer, testClientId); 34 | client.port = mosquittoPort; 35 | client.logging(on: false); 36 | client.onConnected = connectCallback; 37 | client.pongCallback = pongCallback; 38 | client.keepAlivePeriod = 10; 39 | client.websocketProtocols = MqttClientConstants.protocolsSingleDefault; 40 | final connMess = MqttConnectMessage() 41 | .withClientIdentifier(testClientId) 42 | .withWillTopic('willtopic') 43 | .withWillMessage('My Will message') 44 | .startClean() // Non persistent session for testing 45 | .withWillQos(MqttQos.atLeastOnce); 46 | client.connectionMessage = connMess; 47 | var ok = true; 48 | expect(MqttClientEnvironment.isWebClient, isFalse); 49 | try { 50 | await client.connect(); 51 | var connectionOK = false; 52 | if (client.connectionStatus!.state == MqttConnectionState.connected) { 53 | print('Browser client connected locally'); 54 | connectionOK = true; 55 | expect(MqttClientEnvironment.isWebClient, isTrue); 56 | } else { 57 | print( 58 | 'Browser client connection failed - disconnecting, status is ${client.connectionStatus}', 59 | ); 60 | client.disconnect(); 61 | } 62 | await sleeper.sleep(); 63 | if (connectionOK) { 64 | if (pongCount == 2) { 65 | print('Browser client disconnecting normally'); 66 | client.disconnect(); 67 | } 68 | } 69 | } on NoConnectionException { 70 | print( 71 | '>>>>> TEST NOT OK - No connection exception thrown, cannot connect to Mosquitto', 72 | ); 73 | ok = false; 74 | } 75 | expect(ok, isTrue); 76 | }, timeout: Timeout.factor(2)); 77 | }); 78 | } 79 | -------------------------------------------------------------------------------- /test/manual/mqtt_client_mosquitto_wss_browser_test_manual.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 24/01/2020 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | @TestOn('browser') 9 | library; 10 | 11 | import 'package:mqtt_client/mqtt_client.dart'; 12 | import 'package:mqtt_client/mqtt_browser_client.dart'; 13 | import 'package:test/test.dart'; 14 | 15 | void main() { 16 | const mosquittoServer = 'wss://test.mosquitto.org'; 17 | const mosquittoPort = 8081; 18 | const testClientId = 'syncMqttTests'; 19 | 20 | group('Broker tests', () { 21 | test('Connect mosquitto test broker', () async { 22 | var pongCount = 0; 23 | void connectCallback() { 24 | print('Browser client connected callback'); 25 | } 26 | 27 | void pongCallback() { 28 | print('Browser client pong callback'); 29 | pongCount++; 30 | } 31 | 32 | final sleeper = MqttCancellableAsyncSleep(25000); 33 | final client = MqttBrowserClient(mosquittoServer, testClientId); 34 | client.port = mosquittoPort; 35 | client.logging(on: false); 36 | client.onConnected = connectCallback; 37 | client.pongCallback = pongCallback; 38 | client.keepAlivePeriod = 10; 39 | client.websocketProtocols = MqttClientConstants.protocolsSingleDefault; 40 | final connMess = MqttConnectMessage() 41 | .withClientIdentifier(testClientId) 42 | .withWillTopic('willtopic') 43 | .withWillMessage('My Will message') 44 | .startClean() // Non persistent session for testing 45 | .withWillQos(MqttQos.atLeastOnce); 46 | client.connectionMessage = connMess; 47 | var ok = true; 48 | try { 49 | await client.connect(); 50 | var connectionOK = false; 51 | expect(MqttClientEnvironment.isWebClient, isFalse); 52 | if (client.connectionStatus!.state == MqttConnectionState.connected) { 53 | print('Browser client connected locally'); 54 | connectionOK = true; 55 | expect(MqttClientEnvironment.isWebClient, isTrue); 56 | } else { 57 | print( 58 | 'Browser client connection failed - disconnecting, status is ${client.connectionStatus}', 59 | ); 60 | client.disconnect(); 61 | } 62 | await sleeper.sleep(); 63 | if (connectionOK) { 64 | if (pongCount == 2) { 65 | print('Browser client disconnecting normally'); 66 | client.disconnect(); 67 | } 68 | } 69 | } on NoConnectionException { 70 | print( 71 | '>>>>> TEST NOT OK - No connection exception thrown, cannot connect to Mosquitto', 72 | ); 73 | ok = false; 74 | } 75 | expect(ok, isTrue); 76 | }, timeout: Timeout.factor(2)); 77 | }); 78 | } 79 | -------------------------------------------------------------------------------- /lib/src/mqtt_client_topic.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../mqtt_client.dart'; 9 | 10 | typedef Validation = void Function(Topic topic); 11 | 12 | /// Provides the base implementation of an MQTT topic. 13 | abstract class Topic { 14 | /// Separator 15 | static const String topicSeparator = '/'; 16 | 17 | /// Multi wildcard 18 | static const String multiWildcard = '#'; 19 | 20 | /// Multi wildcard end 21 | static const String multiWildcardValidEnd = topicSeparator + multiWildcard; 22 | 23 | /// Wildcard 24 | static const String wildcard = '+'; 25 | 26 | /// Topic length 27 | static const int maxTopicLength = 65535; 28 | 29 | /// Raw topic 30 | String rawTopic; 31 | 32 | /// Topic fragments 33 | late List topicFragments; 34 | 35 | /// Returns true if there are any wildcards in the specified 36 | /// rawTopic, otherwise false. 37 | bool get hasWildcards => 38 | rawTopic.contains(multiWildcard) || rawTopic.contains(wildcard); 39 | 40 | /// Serves as a hash function for a topics. 41 | @override 42 | int get hashCode => rawTopic.hashCode; 43 | 44 | /// Creates a new instance of a rawTopic from a rawTopic string. 45 | /// rawTopic - The topic to represent. 46 | /// validations - The validations to run on the rawTopic. 47 | Topic(this.rawTopic, List validations) { 48 | topicFragments = rawTopic.split(topicSeparator[0]); 49 | // run all validations 50 | for (final validation in validations) { 51 | validation(this); 52 | } 53 | } 54 | 55 | /// Validates that the topic does not exceed the maximum length. 56 | /// topicInstance - The instance to check. 57 | static void validateMaxLength(Topic topicInstance) { 58 | if (topicInstance.rawTopic.length > maxTopicLength) { 59 | throw Exception( 60 | 'mqtt_client::Topic: The length of the supplied rawTopic ' 61 | '(${topicInstance.rawTopic.length}) is longer than the ' 62 | 'maximum allowable ($maxTopicLength)', 63 | ); 64 | } 65 | } 66 | 67 | /// Validates that the topic does not fall below the minimum length. 68 | /// topicInstance - The instance to check. 69 | static void validateMinLength(Topic topicInstance) { 70 | if (topicInstance.rawTopic.isEmpty) { 71 | throw Exception( 72 | 'mqtt_client::Topic: rawTopic must contain at least one character', 73 | ); 74 | } 75 | } 76 | 77 | /// Checks if one topic equals another topic exactly. 78 | @override 79 | bool operator ==(Object other) { 80 | if (identical(this, other)) { 81 | return true; 82 | } 83 | return other is Topic && rawTopic == other.rawTopic; 84 | } 85 | 86 | /// Returns a String representation of the topic. 87 | @override 88 | String toString() => rawTopic; 89 | } 90 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Steve Hamblett 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | 24 | <<< License for using the included Observable code >>> 25 | ------------------------------------------------------ 26 | 27 | Copyright 2016, the Dart project authors. All rights reserved. 28 | Redistribution and use in source and binary forms, with or without 29 | modification, are permitted provided that the following conditions are 30 | met: 31 | 32 | * Redistributions of source code must retain the above copyright 33 | notice, this list of conditions and the following disclaimer. 34 | * Redistributions in binary form must reproduce the above 35 | copyright notice, this list of conditions and the following 36 | disclaimer in the documentation and/or other materials provided 37 | with the distribution. 38 | * Neither the name of Google Inc. nor the names of its 39 | contributors may be used to endorse or promote products derived 40 | from this software without specific prior written permission. 41 | 42 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 43 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 44 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 45 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 46 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 47 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 48 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 49 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 50 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 51 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 52 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 53 | -------------------------------------------------------------------------------- /lib/src/messages/subscribe/mqtt_client_mqtt_subscribe_payload.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 19/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../../mqtt_client.dart'; 9 | 10 | /// Class that contains details related to an MQTT Subscribe messages payload 11 | class MqttSubscribePayload extends MqttPayload { 12 | /// Variable header 13 | MqttVariableHeader? variableHeader; 14 | 15 | /// Message header 16 | MqttHeader? header; 17 | 18 | /// The collection of subscriptions, Key is the topic, Value is the qos 19 | Map subscriptions = {}; 20 | 21 | /// Initializes a new instance of the MqttSubscribePayload class. 22 | MqttSubscribePayload(); 23 | 24 | /// Initializes a new instance of the MqttSubscribePayload class. 25 | MqttSubscribePayload.fromByteBuffer( 26 | this.header, 27 | this.variableHeader, 28 | MqttByteBuffer payloadStream, 29 | ) { 30 | readFrom(payloadStream); 31 | } 32 | 33 | /// Writes the payload to the supplied stream. 34 | @override 35 | void writeTo(MqttByteBuffer payloadStream) { 36 | subscriptions.forEach((String? key, MqttQos? value) { 37 | payloadStream.writeMqttStringM(key!); 38 | payloadStream.writeByte(value!.index); 39 | }); 40 | } 41 | 42 | /// Creates a payload from the specified header stream. 43 | @override 44 | void readFrom(MqttByteBuffer payloadStream) { 45 | var payloadBytesRead = 0; 46 | final payloadLength = header!.messageSize - variableHeader!.length; 47 | // Read all the topics and qos subscriptions from the message payload 48 | while (payloadBytesRead < payloadLength) { 49 | final topic = payloadStream.readMqttStringM(); 50 | final qos = MqttUtilities.getQosLevel(payloadStream.readByte()); 51 | payloadBytesRead += 52 | topic.length + 3; // +3 = Mqtt string length bytes + qos byte 53 | addSubscription(topic, qos); 54 | } 55 | } 56 | 57 | /// Gets the length of the payload in bytes when written to a stream. 58 | @override 59 | int getWriteLength() { 60 | var length = 0; 61 | final enc = MqttEncoding(); 62 | subscriptions.forEach((String? key, MqttQos? value) { 63 | length += enc.getByteCount(key!); 64 | length += 1; 65 | }); 66 | return length; 67 | } 68 | 69 | /// Adds a new subscription to the collection of subscriptions. 70 | void addSubscription(String topic, MqttQos qos) { 71 | subscriptions[topic] = qos; 72 | } 73 | 74 | /// Clears the subscriptions. 75 | void clearSubscriptions() { 76 | subscriptions.clear(); 77 | } 78 | 79 | @override 80 | String toString() { 81 | final sb = StringBuffer(); 82 | sb.writeln('Payload: Subscription [{${subscriptions.length}}]'); 83 | subscriptions.forEach((String? key, MqttQos? value) { 84 | sb.writeln('{{ Topic={$key}, Qos={$value} }}'); 85 | }); 86 | return sb.toString(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib/src/messages/mqtt_client_mqtt_message_factory.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 15/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../mqtt_client.dart'; 9 | 10 | /// Factory for generating instances of MQTT Messages 11 | class MqttMessageFactory { 12 | /// Gets an instance of an MqttMessage based on the message type requested. 13 | static MqttMessage getMessage( 14 | MqttHeader header, 15 | MqttByteBuffer messageStream, 16 | ) { 17 | MqttMessage message; 18 | switch (header.messageType) { 19 | case MqttMessageType.connect: 20 | message = MqttConnectMessage.fromByteBuffer(header, messageStream); 21 | break; 22 | case MqttMessageType.connectAck: 23 | message = MqttConnectAckMessage.fromByteBuffer(header, messageStream); 24 | break; 25 | case MqttMessageType.publish: 26 | message = MqttPublishMessage.fromByteBuffer(header, messageStream); 27 | break; 28 | case MqttMessageType.publishAck: 29 | message = MqttPublishAckMessage.fromByteBuffer(header, messageStream); 30 | break; 31 | case MqttMessageType.publishComplete: 32 | message = MqttPublishCompleteMessage.fromByteBuffer( 33 | header, 34 | messageStream, 35 | ); 36 | break; 37 | case MqttMessageType.publishReceived: 38 | message = MqttPublishReceivedMessage.fromByteBuffer( 39 | header, 40 | messageStream, 41 | ); 42 | break; 43 | case MqttMessageType.publishRelease: 44 | message = MqttPublishReleaseMessage.fromByteBuffer( 45 | header, 46 | messageStream, 47 | ); 48 | break; 49 | case MqttMessageType.subscribe: 50 | message = MqttSubscribeMessage.fromByteBuffer(header, messageStream); 51 | break; 52 | case MqttMessageType.subscribeAck: 53 | message = MqttSubscribeAckMessage.fromByteBuffer(header, messageStream); 54 | break; 55 | case MqttMessageType.unsubscribe: 56 | message = MqttUnsubscribeMessage.fromByteBuffer(header, messageStream); 57 | break; 58 | case MqttMessageType.unsubscribeAck: 59 | message = MqttUnsubscribeAckMessage.fromByteBuffer( 60 | header, 61 | messageStream, 62 | ); 63 | break; 64 | case MqttMessageType.pingRequest: 65 | message = MqttPingRequestMessage.fromHeader(header); 66 | break; 67 | case MqttMessageType.pingResponse: 68 | message = MqttPingResponseMessage.fromHeader(header); 69 | break; 70 | case MqttMessageType.disconnect: 71 | message = MqttDisconnectMessage.fromHeader(header); 72 | break; 73 | default: 74 | throw InvalidHeaderException( 75 | 'The Message Type specified ($header.messageType) is not a valid ' 76 | 'MQTT Message type or currently not supported.', 77 | ); 78 | } 79 | return message; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /test/issues/issue209/issue209-wss-mosquitto.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | import 'dart:async'; 9 | import 'dart:convert'; 10 | import 'package:mqtt_client/mqtt_client.dart'; 11 | import 'package:mqtt_client/mqtt_server_client.dart'; 12 | import 'package:test/test.dart'; 13 | 14 | Future main() async { 15 | test( 16 | 'Should maintain subscriptions after autoReconnect', 17 | () async { 18 | final client = MqttServerClient.withPort( 19 | 'wss://test.mosquitto.org', 20 | 'client-id-123456789', 21 | 8081, 22 | ); 23 | client.autoReconnect = true; 24 | client.logging(on: false); 25 | const topic = 'xd/+'; 26 | 27 | // Subscribe callback, we do the auto reconnect when we know we have subscribed 28 | // second time is from the resubscribe so re publish. 29 | var ignoreSubscribe = false; 30 | void subCB(subTopic) async { 31 | if (ignoreSubscribe) { 32 | print( 33 | 'ISSUE: Received re-subscribe callback for our topic - re publishing', 34 | ); 35 | client.publishMessage( 36 | 'xd/light', 37 | MqttQos.exactlyOnce, 38 | (MqttClientPayloadBuilder()..addUTF8String('xd')).payload, 39 | ); 40 | return; 41 | } 42 | if (topic == subTopic) { 43 | print( 44 | 'ISSUE: Received subscribe callback for our topic - auto reconnecting', 45 | ); 46 | client.doAutoReconnect(force: true); 47 | } else { 48 | print( 49 | 'ISSUE: Received subscribe callback for unknown topic $subTopic', 50 | ); 51 | } 52 | ignoreSubscribe = true; 53 | print('ISSUE: Exiting subscribe callback'); 54 | } 55 | 56 | // Main test starts here 57 | print('ISSUE: Main test start'); 58 | client.onSubscribed = subCB; // Subscribe callback 59 | client.useWebSocket = true; 60 | print('ISSUE: Connecting'); 61 | await client.connect(); 62 | client.subscribe(topic, MqttQos.exactlyOnce); 63 | 64 | // Now publish the message 65 | print('ISSUE: Publishing'); 66 | client.publishMessage( 67 | 'xd/light', 68 | MqttQos.exactlyOnce, 69 | (MqttClientPayloadBuilder()..addUTF8String('xd')).payload, 70 | ); 71 | 72 | // Listen for our responses. 73 | print('ISSUE: Listening >>>>'); 74 | final stream = client.updates 75 | .expand((event) sync* { 76 | for (var e in event) { 77 | MqttPublishMessage message = e.payload; 78 | yield utf8.decode(message.payload.message); 79 | } 80 | }) 81 | .timeout(Duration(seconds: 30)); 82 | 83 | expect(await stream.first, equals('xd')); 84 | print('ISSUE: Test complete'); 85 | }, 86 | timeout: Timeout.factor(2), 87 | ); 88 | 89 | return 0; 90 | } 91 | -------------------------------------------------------------------------------- /lib/src/connectionhandling/mqtt_client_mqtt_connection_base.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 29/03/2020 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../mqtt_client.dart'; 9 | 10 | /// The MQTT client connection base class 11 | abstract class MqttConnectionBase { 12 | /// The socket that maintains the connection to the MQTT broker. 13 | @protected 14 | T? client; 15 | 16 | /// The stream controller as returned when clients listen. 17 | @protected 18 | List listeners = []; 19 | 20 | /// The read wrapper 21 | @protected 22 | ReadWrapper? readWrapper; 23 | 24 | ///The read buffer 25 | @protected 26 | late MqttByteBuffer messageStream; 27 | 28 | /// Unsolicited disconnection callback 29 | @internal 30 | DisconnectCallback? onDisconnected; 31 | 32 | /// The event bus 33 | @protected 34 | events.EventBus? clientEventBus; 35 | 36 | /// Default constructor 37 | MqttConnectionBase(this.clientEventBus); 38 | 39 | /// Initializes a new instance of the MqttConnection class. 40 | MqttConnectionBase.fromConnect(String server, int port, this.clientEventBus) { 41 | connect(server, port); 42 | } 43 | 44 | /// Connect, must be overridden in connection classes 45 | Future connect(String server, int port); 46 | 47 | /// Connect for auto reconnect , must be overridden in connection classes 48 | Future connectAuto(String server, int port); 49 | 50 | /// Sends the message in the stream to the broker. 51 | void send(MqttByteBuffer message); 52 | 53 | /// Stops listening the socket immediately, must be overridden in connection classes 54 | void stopListening(); 55 | 56 | /// Closes the socket immediately, must be overridden in connection classes 57 | void closeClient(); 58 | 59 | /// Closes and dispose the socket immediately, must be overridden in connection classes 60 | @mustCallSuper 61 | void disposeClient(); 62 | 63 | /// OnError listener callback 64 | @protected 65 | void onError(dynamic error) { 66 | _disconnect(); 67 | if (onDisconnected != null) { 68 | MqttLogger.log( 69 | 'MqttConnectionBase::_onError - calling disconnected callback', 70 | ); 71 | onDisconnected!(); 72 | } 73 | } 74 | 75 | /// OnDone listener callback 76 | @protected 77 | void onDone() { 78 | _disconnect(); 79 | if (onDisconnected != null) { 80 | MqttLogger.log( 81 | 'MqttConnectionBase::_onDone - calling disconnected callback', 82 | ); 83 | onDisconnected!(); 84 | } 85 | } 86 | 87 | /// User requested or auto disconnect disconnection 88 | @protected 89 | void disconnect({bool auto = false}) { 90 | if (auto) { 91 | _disconnect(); 92 | } else { 93 | onDone(); 94 | } 95 | } 96 | 97 | /// Internal disconnect with stop listeners and dispose client 98 | void _disconnect() { 99 | stopListening(); 100 | disposeClient(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/src/connectionhandling/mqtt_client_imqtt_connection_handler.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 22/06/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../mqtt_client.dart'; 9 | 10 | /// Callback function definitions 11 | typedef MessageCallbackFunction = bool Function(MqttMessage? message); 12 | 13 | /// The connection handler interface class 14 | abstract class IMqttConnectionHandler { 15 | /// Successful connection callback 16 | ConnectCallback? onConnected; 17 | 18 | /// Unsolicited disconnection callback 19 | DisconnectCallback? onDisconnected; 20 | 21 | /// Auto reconnect callback 22 | AutoReconnectCallback? onAutoReconnect; 23 | 24 | /// Auto reconnected callback 25 | AutoReconnectCompleteCallback? onAutoReconnected; 26 | 27 | /// Failed connection attempt callback 28 | FailedConnectionAttemptCallback? onFailedConnectionAttempt; 29 | 30 | /// Auto reconnect in progress 31 | bool autoReconnectInProgress = false; 32 | 33 | // Server name, needed for auto reconnect. 34 | String? server; 35 | 36 | // Port number, needed for auto reconnect. 37 | int? port; 38 | 39 | // Connection message, needed for auto reconnect. 40 | MqttConnectMessage? connectionMessage; 41 | 42 | /// Callback function to handle bad certificate. if true, ignore the error. 43 | bool Function(Object certificate)? onBadCertificate; 44 | 45 | /// The connection status 46 | MqttClientConnectionStatus get connectionStatus; 47 | 48 | /// Runs the disconnection process to stop communicating 49 | /// with a message broker. 50 | MqttConnectionState disconnect(); 51 | 52 | /// Closes a connection. 53 | void close(); 54 | 55 | /// Kills all listeners from old connections. 56 | void stopListening(); 57 | 58 | /// Connects to a message broker 59 | /// The broker server to connect to 60 | /// The port to connect to 61 | /// The connect message to use to initiate the connection 62 | Future connect( 63 | String server, 64 | int port, 65 | MqttConnectMessage message, 66 | ); 67 | 68 | /// Register the specified callback to receive messages of a specific type. 69 | /// The type of message that the callback should be sent 70 | /// The callback function that will accept the message type 71 | void registerForMessage( 72 | MqttMessageType msgType, 73 | MessageCallbackFunction msgProcessorCallback, 74 | ); 75 | 76 | /// Sends a message to a message broker. 77 | void sendMessage(MqttMessage message); 78 | 79 | /// Unregisters the specified callbacks so it not longer receives 80 | /// messages of the specified type. 81 | /// The message type the callback currently receives 82 | void unRegisterForMessage(MqttMessageType msgType); 83 | 84 | /// Registers a callback to be executed whenever a message is 85 | /// sent by the connection handler. 86 | void registerForAllSentMessages(MessageCallbackFunction sentMsgCallback); 87 | 88 | /// UnRegisters a callback that is registerd to be executed whenever a 89 | /// message is sent by the connection handler. 90 | void unRegisterForAllSentMessages(MessageCallbackFunction sentMsgCallback); 91 | } 92 | -------------------------------------------------------------------------------- /test/issues/issue81.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | import 'dart:async'; 9 | import 'dart:io'; 10 | import 'package:mqtt_client/mqtt_client.dart'; 11 | import 'package:mqtt_client/mqtt_server_client.dart'; 12 | 13 | Future main() async { 14 | final client = MqttServerClient('mqtt.hsl.fi', '8883'); 15 | 16 | /// Version must be V3.1.1 17 | client.setProtocolV311(); 18 | 19 | /// Set logging on if needed, defaults to off 20 | client.logging(on: false); 21 | 22 | /// If you intend to use a keep alive value in your connect message that is not the default(60s) 23 | /// you must set it here 24 | client.keepAlivePeriod = 20; 25 | 26 | /// Add the unsolicited disconnection callback 27 | client.onDisconnected = onDisconnected; 28 | 29 | /// Add a subscribed callback, there is also an unsubscribed callback if you need it. 30 | /// You can add these before connection or change them dynamically after connection if 31 | /// you wish. 32 | client.onSubscribed = onSubscribed; 33 | 34 | /// Connect the client, any errors here are communicated by raising of the appropriate exception. Note 35 | /// in some circumstances the broker will just disconnect us, see the spec about this, we however eill 36 | /// never send malformed messages. 37 | var status = MqttClientConnectionStatus(); 38 | try { 39 | status = await client.connect(); 40 | } on Exception catch (e) { 41 | print('EXAMPLE::client exception - $e'); 42 | } 43 | 44 | /// Check we are connected 45 | if (status.state == MqttConnectionState.connected) { 46 | print('EXAMPLE::client connected'); 47 | } else { 48 | /// Use status here rather than state if you also want the broker return code. 49 | print('EXAMPLE::ERROR client connection failed - disconnecting, $status'); 50 | client.disconnect(); 51 | exit(-1); 52 | } 53 | 54 | /// Ok, lets try a subscription 55 | print( 56 | 'EXAMPLE::Subscribing to the /hfp/v1/journey/ongoing/+/+/+/2550/2/# topic', 57 | ); 58 | const topic = '/hfp/v1/journey/ongoing/+/+/+/2550/2/#'; 59 | client.subscribe(topic, MqttQos.atMostOnce); 60 | 61 | /// The client has a change notifier object(see the Observable class) which we then listen to to get 62 | /// notifications of published updates to each subscribed topic. 63 | client.updates.listen((List> c) { 64 | final MqttPublishMessage recMess = c[0].payload; 65 | final pt = MqttPublishPayload.bytesToStringAsString( 66 | recMess.payload.message, 67 | ); 68 | print( 69 | 'EXAMPLE::Change notification:: topic is <${c[0].topic}>, payload is <-- $pt -->', 70 | ); 71 | print(''); 72 | }); 73 | 74 | await MqttUtilities.asyncSleep(5); 75 | print('EXAMPLE::Disconnecting'); 76 | client.disconnect(); 77 | return 0; 78 | } 79 | 80 | /// The subscribed callback 81 | void onSubscribed(String topic) { 82 | print('EXAMPLE::Subscription confirmed for topic $topic'); 83 | } 84 | 85 | /// The unsolicited disconnect callback 86 | void onDisconnected() { 87 | print('EXAMPLE::OnDisconnected client callback - Client disconnection'); 88 | exit(-1); 89 | } 90 | -------------------------------------------------------------------------------- /test/issues/issue209/issue209-secure-mosquitto.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | import 'dart:async'; 9 | import 'dart:convert'; 10 | import 'dart:io'; 11 | import 'package:path/path.dart' as path; 12 | import 'package:mqtt_client/mqtt_client.dart'; 13 | import 'package:mqtt_client/mqtt_server_client.dart'; 14 | import 'package:test/test.dart'; 15 | 16 | Future main() async { 17 | test('Should maintain subscriptions after autoReconnect', () async { 18 | final client = MqttServerClient.withPort( 19 | 'test.mosquitto.org', 20 | 'client-id-123456789', 21 | 8883, 22 | ); 23 | client.autoReconnect = true; 24 | client.logging(on: false); 25 | const topic = 'xd/+'; 26 | 27 | // Subscribe callback, we do the auto reconnect when we know we have subscribed 28 | // second time is from the resubscribe so re publish. 29 | var ignoreSubscribe = false; 30 | void subCB(subTopic) async { 31 | if (ignoreSubscribe) { 32 | print( 33 | 'ISSUE: Received re-subscribe callback for our topic - re publishing', 34 | ); 35 | client.publishMessage( 36 | 'xd/light', 37 | MqttQos.exactlyOnce, 38 | (MqttClientPayloadBuilder()..addUTF8String('xd')).payload, 39 | ); 40 | return; 41 | } 42 | if (topic == subTopic) { 43 | print( 44 | 'ISSUE: Received subscribe callback for our topic - auto reconnecting', 45 | ); 46 | client.doAutoReconnect(force: true); 47 | } else { 48 | print('ISSUE: Received subscribe callback for unknown topic $subTopic'); 49 | } 50 | ignoreSubscribe = true; 51 | print('ISSUE: Exiting subscribe callback'); 52 | } 53 | 54 | // Main test starts here 55 | print('ISSUE: Main test start'); 56 | client.onSubscribed = subCB; // Subscribe callback 57 | print('ISSUE: Connecting'); 58 | client.secure = true; 59 | 60 | /// Security context 61 | final currDir = 62 | '${path.current}${path.separator}test${path.separator}issues${path.separator}issue209${path.separator}'; 63 | final context = SecurityContext.defaultContext; 64 | final certPath = currDir + path.join('pem', 'mosquitto.org.crt'); 65 | context.setTrustedCertificates(certPath); 66 | client.securityContext = context; 67 | await client.connect(); 68 | client.subscribe(topic, MqttQos.exactlyOnce); 69 | 70 | // Now publish the message 71 | print('ISSUE: Publishing'); 72 | client.publishMessage( 73 | 'xd/light', 74 | MqttQos.exactlyOnce, 75 | (MqttClientPayloadBuilder()..addUTF8String('xd')).payload, 76 | ); 77 | 78 | // Listen for our responses. 79 | print('ISSUE: Listening >>>>'); 80 | final stream = client.updates 81 | .expand((event) sync* { 82 | for (var e in event) { 83 | MqttPublishMessage message = e.payload; 84 | yield utf8.decode(message.payload.message); 85 | } 86 | }) 87 | .timeout(Duration(seconds: 7)); 88 | 89 | expect(await stream.first, equals('xd')); 90 | print('ISSUE: Test complete'); 91 | }); 92 | 93 | return 0; 94 | } 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mqtt_client 2 | [![Build Status](https://github.com/shamblett/mqtt_client/actions/workflows/ci.yml/badge.svg)](https://github.com/shamblett/mqtt_client/actions/workflows/ci.yml) 3 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fshamblett%2Fmqtt_client.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fshamblett%2Fmqtt_client?ref=badge_shield) 4 | 5 | A server and browser based MQTT client for Dart. 6 | 7 | The client is an MQTT v3(3.1 and 3.1.1) implementation(an equivalent MQTT v5 client can be found [here](https://pub.dev/packages/mqtt5_client)) supporting subscription/publishing at all QOS levels, 8 | keep alive and synchronous connection. The client is designed to take as much MQTT protocol work 9 | off the user as possible, connection protocol is handled automatically as are the message exchanges needed 10 | to support the different QOS levels and the keep alive mechanism. This allows the user to concentrate on 11 | publishing/subscribing and not the details of MQTT itself. 12 | 13 | Examples of usage can be found in the examples directory. An example is also provided 14 | showing how to use the client to connect to the mqtt-bridge of Google's IoT-Core suite. This demonstrates 15 | how to use secure connections and switch MQTT protocols. The test directory also contains standalone runnable scripts demonstrating subscription, publishing and topic filtering. 16 | 17 | The server client supports both normal and secure TCP connections and secure(wss) and non-secure(ws) websocket connections. 18 | The browser client supports only secure(wss) and non-secure(ws) websocket connections. 19 | 20 | The client has been used successfully with the MQTT brokers from several of the major cloud providers IOT/MQTT 21 | platforms, including :- 22 | * Google IOT Core 23 | * Amazon AWS 24 | * Microsoft Azure 25 | * IBM 26 | 27 | It has also been used with a range of both publicly available brokers such as Mosquitto and proprietary ones. 28 | An example using the adafruit MQTT broker for flutter can be found [here](https://github.com/BitKnitting/flutter_adafruit_mqtt). 29 | 30 | The code is originally a port from the C# [nMQTT](https://www.openhub.net/p/nMQTT) client library to Dart. 31 | 32 | Please read the changelog for details related to specific versions. 33 | 34 | ## Installation 35 | If you are using the client in a flutter environment on Android or iOS devices then the following device permission settings are necessary. 36 | 37 | ### iOS 38 | Add the following keys to your **Info.plist** file, located in **ios/Runner/Info.plist**: 39 | ``` 40 | NSLocalNetworkUsageDescription 41 | Looking for local tcp Bonjour service 42 | NSBonjourServices 43 | 44 | mqtt.tcp 45 | 46 | ``` 47 | ### Android 48 | Add the following Android permissions to the **AndroidManifest.xml** file, located in **android/app/src/main/AndroidManifest.xml**: 49 | ``` 50 | 51 | 52 | ``` 53 | ## License 54 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fshamblett%2Fmqtt_client.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fshamblett%2Fmqtt_client?ref=badge_large) 55 | -------------------------------------------------------------------------------- /lib/src/utility/mqtt_client_payload_builder.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 18/04/2018 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | part of '../../mqtt_client.dart'; 9 | 10 | /// Utility class to assist with the building of message topic payloads. 11 | /// Implements the builder pattern, i.e. returns itself to allow chaining. 12 | class MqttClientPayloadBuilder { 13 | typed.Uint8Buffer? _payload; 14 | 15 | /// Payload 16 | typed.Uint8Buffer? get payload => _payload; 17 | 18 | /// Length 19 | int get length => _payload!.length; 20 | 21 | /// Construction 22 | MqttClientPayloadBuilder() { 23 | _payload = typed.Uint8Buffer(); 24 | } 25 | 26 | /// Add a buffer 27 | MqttClientPayloadBuilder addBuffer(typed.Uint8Buffer buffer) { 28 | _payload!.addAll(buffer); 29 | return this; 30 | } 31 | 32 | /// Add byte, this will overflow on values > 2**8-1 33 | MqttClientPayloadBuilder addByte(int val) { 34 | _payload!.add(val); 35 | return this; 36 | } 37 | 38 | /// Add a bool, true is 1, false is 0 39 | MqttClientPayloadBuilder addBool({required bool val}) { 40 | val ? addByte(1) : addByte(0); 41 | return this; 42 | } 43 | 44 | /// Add a half word, 16 bits, this will overflow on values > 2**16-1 45 | MqttClientPayloadBuilder addHalf(int val) { 46 | final tmp = Uint16List.fromList([val]); 47 | _payload!.addAll(tmp.buffer.asInt8List()); 48 | return this; 49 | } 50 | 51 | /// Add a word, 32 bits, this will overflow on values > 2**32-1 52 | MqttClientPayloadBuilder addWord(int val) { 53 | final tmp = Uint32List.fromList([val]); 54 | _payload!.addAll(tmp.buffer.asInt8List()); 55 | return this; 56 | } 57 | 58 | /// Add a long word, 64 bits or a Dart int 59 | MqttClientPayloadBuilder addInt(int val) { 60 | final tmp = Uint64List.fromList([val]); 61 | _payload!.addAll(tmp.buffer.asInt8List()); 62 | return this; 63 | } 64 | 65 | /// Add a standard Dart string 66 | MqttClientPayloadBuilder addString(String val) { 67 | addUTF16String(val); 68 | return this; 69 | } 70 | 71 | /// Add a UTF16 string, note Dart natively encodes strings as UTF16 72 | MqttClientPayloadBuilder addUTF16String(String val) { 73 | for (final codeUnit in val.codeUnits) { 74 | if (codeUnit <= 255 && codeUnit >= 0) { 75 | _payload!.add(codeUnit); 76 | } else { 77 | addHalf(codeUnit); 78 | } 79 | } 80 | return this; 81 | } 82 | 83 | /// Add a UTF8 string 84 | MqttClientPayloadBuilder addUTF8String(String val) { 85 | const encoder = Utf8Encoder(); 86 | _payload!.addAll(encoder.convert(val)); 87 | return this; 88 | } 89 | 90 | /// Add a 32 bit double 91 | MqttClientPayloadBuilder addHalfDouble(double val) { 92 | final tmp = Float32List.fromList([val]); 93 | _payload!.addAll(tmp.buffer.asInt8List()); 94 | return this; 95 | } 96 | 97 | /// Add a 64 bit double 98 | MqttClientPayloadBuilder addDouble(double val) { 99 | final tmp = Float64List.fromList([val]); 100 | _payload!.addAll(tmp.buffer.asInt8List()); 101 | return this; 102 | } 103 | 104 | /// Clear the buffer 105 | MqttClientPayloadBuilder clear() { 106 | _payload!.clear(); 107 | return this; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /test/pem/self_signed.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDNTbksieMQukkT 3 | tztqeuLCoNWswyDnY8zpl70v/6JSNMJTpSLVehmwBXC0xrvYmQ6tacm/SCusGAPE 4 | 2+iyMsnUvpK0kCqOMQrcNHv88T4tQ9r9a1C0BfkjkrRMrWJpDNmG+XeM2tHhc+IT 5 | uew32KZE2zR/wfCZkExlOVVrwYXU/+O1+30fQQlgNka936K8+YLzMmu5xbJnlrnC 6 | F2Jkvl6v9YInqe5wtH7OcLkz1P3amYLNJSu2fWlNQFp3Sf/s0w0DPolBAUWdc6ah 7 | Q2rpWF2OxMaOgNB+0F9aLnmpnjj+lsr6+Y2I5dllvT/QbfPB94YcwTGDA2cZKzR3 8 | SST48n9zTgjvwWhoFRBl8i3VpJPZEwJuZnuh7UQK5JyullfLweuL+e33nnH5fhjc 9 | JaoBciwxZYASG7I3gq3c3CU85Yic5kmZ6aNQWlw3tpfaKiOUEsnMwatLWuUZTGie 10 | Pg9nluUh+W0wifyd+ilbD8jik223X6vTgZLinKu1kA3GO12g7+BVu9njdbDju+0J 11 | xIZCJyKW1T2WEGoYsih5aY3g+FD8hq4bLAdg5yplEFD5pcgrKo29rHSwV7/5UAtX 12 | wPCIaekRQpddg7dbp7VWS7GzWXOMEhED1U79ZcvNaisbIAuP0xabruQXoXJe2AZ8 13 | ASitSY8I/CFHrnS8TsrnHQTti2x0HQIDAQABAoICABpLnC4SMx0PXxDBZUDMIIMh 14 | Kn0IOQyqAbLhln+xVE8HJuSjwARrsSKUb80XrzOc1v5rEEYJrl5KbSEnsXQxgRxY 15 | lF3AIVM5ANUXe9H37CjMCh5vQlTsdu6RebpgcrxVwv7AsQk/daZUu14fcBvvCczD 16 | 7sN2SaTBkKdeYox8qvGPHNFQNsRsBhFmokTrgeKGarlPefC/W4xdB8nOSPHyWBpd 17 | baA66alR1PQICBCVdTYsd05jNmgXRAd6+JtSxQ7GMWJ6/gVvbkUubOc2xVjB0qnY 18 | lj3mTc4QTkY0sayB8XbaRYFDuVcRaXjYdnXxv0jWtTy1mevX4bqdiAjHO48PMDeP 19 | pkmwGZUL1CrYDgWydxl7rmfpBuao4n0pwt8h9oUhtMyoLdz/vbVbDQnLAQbnFXX4 20 | skwFnVylYnL9uz07V0CFxk2xvwgjz8YC6vhvioVjGKJNMmT9bA5HVgJ3U14YPd47 21 | TtqaOc9rldzHUlyHgQuSUv4KdWSSWC9ZZxGxgJcwXBia2eZwfch+v5IKaYGRu9K0 22 | MAzpxLu7gR46vExW2/ZYKtLpT2rBR7uZZqY2ZoqWp2VLXDxRfHHv9p5n6AWIJBkL 23 | ELut6V5UygTsgUrmQsIE7mOEtCDxjiL41UT1Igb1FGoXxdA+texRD0fgHEI0xT/+ 24 | s1pNKXTn2k4Il2QCG8NBAoIBAQD12qhkBcynKbCdiMPwNbi7S/+WmTkxCs2rGqdA 25 | praSdKdZ0QOvlW8kxauw8tUKSrIdy/1BXjYKEfv4eSHFBIUdJDRntyNZAdqEo8kQ 26 | XhP4kv59qT/h4n8oifXymVSTCBLWqYBF4S9kFKc1hJE7W36HoToRWMzPHLPUAFLz 27 | L681HmyVYZP/pcTjYISii7WsfuMqABPGCQBTIUe0xH44/QcER1DlLoxwplIsCUJq 28 | NaCH9edClFxzOwhuuSK73fX6nMpo9i/YSpuNfvUczgS5EBR+3zyCD9vD1TmeexsH 29 | 3ngZJ2QXQwSVOj2lx4nBAEDE75RzqyH4ijoStzSvdu11phApAoIBAQDVxqq3Ieqe 30 | uNGgQNxB2p7iqi9kwDMv9Xigh24WY3Zn8uMCe9OeBTD3ebaGEwlEzA4wVXRvqj7+ 31 | R1OeAgUW/GGPPFjhPLGlUA+l8A+PZ0taQloyDc+zx0MeEPwvOaUcwEEtd3JC3+kl 32 | Qp8T7ywEw4EksjSBwAA36ZhYnctXuYr7vW26APaEXuYae8h5CKOuQrR2JpoVoOmE 33 | 4XIfVJtQ+iLi7CfHidaTmJpEpWrVcTUksq1muATpUJAgzQqsMDuxjQSx7ZVncYAs 34 | gCHqFFBmwgCU1DJ2NULDWyTA4kR1aK7XK7B7BmNdCKS8DEqHw1gNk0st5wUvH1RD 35 | yVis8nE/cTLVAoIBAAnQSJh1gs3JZDtlkiyseEtBpVolk5rZi4wg7Dwwa2k9LtDu 36 | /u6rUFxvZ4MQrmBSyvlVgy5wzEdqoFwyuZ5bp9SZ9V2OeeK7qDVTNFtq5fEuLOpn 37 | Lx2dJOZBW4frhuy3jcjzyyL+8h1UlbgVKpbr+AkB7odyu/oocyUrPiRm6t9772mn 38 | E3lI7KsG10SFvyK7jeXHSwLEqTaoRWx5y3AK6ZTKI/iB+ykj+nO3iucibN4AetK1 39 | iVswKA4DZdeJ7J+sUogOC6rej4b+Ylpb/uREOA04a/nlGCihAkPJNjMCNlkKsXwc 40 | OLqcMInFhUIm+G7kAVD8q8Z3/glxNrEqViA0GMECggEBAJSN1/gnA4mE2XdJW0YQ 41 | DDVrGkAjJ+793/N96Ux5lcCqkoIZVncLGHoBBIurEtfSDVIdMCqg3eGJ41t7/9l3 42 | TLxirUjoCnLcbeNcLRP0LfLtBQyfeGRv5f4ww2512n1eRGy6ApbiTnvsDeumWqp8 43 | yXOcuswnhn9UPt/l13aAo0pI2sVVAfn+kFx7BFbHYCcqh4lLTewVw/DpUYrgGoZt 44 | LG0Qupv49znd4+YsJU9pugpkeAdXfWI878s7cV0yr2i0nDyg4x/dSbqi1+54a17d 45 | FdZWlzJ51Ik4/xNiqTwFoOo5nAwB5m6yIPfkeq5ZWwkkACxHy9xxIvdwJcUZPQVf 46 | PnkCggEBAKgGbVi60Od/9Td8zM9Vjt0/lTQIgriqY0rwH93Df+EGw8iU2hbaFVQb 47 | TrkyIkyx14HttCOjGRddOS1UsXot2rtIFG/nFDiaKVkmu88xTZvVTaFvUwJ4AmVr 48 | w6ak2rlb4DhhmyReRgQk0+HFpfGZyy9U/i8Bs2oMUCwB9LqPaTQ6pNcEXamGaD13 49 | KXVOvhrDJXNpS9D0kHxYCbpeaJ4WxRSc2A1mfzPhuVGTZGRFwEaN1FQL1kZZRp+8 50 | SVJyOD+bDl5e9GLoe5An/ZYpQ1N6k0KYINjbCP7RN4ytc+cRF428uY6BO3St5Lt4 51 | Lz28ZX+9MRjJrJLfhsuJuO7K2KBpXUc= 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /test/issues/issue54.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:mqtt_client/mqtt_client.dart'; 3 | import 'package:mqtt_client/mqtt_server_client.dart'; 4 | 5 | Future main() async { 6 | final client = MqttServerClient('test.mosquitto.org', ''); 7 | client.logging(on: false); 8 | client.keepAlivePeriod = 60; 9 | client.onDisconnected = onDisconnected; 10 | client.onSubscribed = onSubscribed; 11 | client.onUnsubscribed = onUnsubscribed; 12 | 13 | final connMess = MqttConnectMessage() 14 | .withClientIdentifier('Mqtt_MyClientUniqueId') 15 | .keepAliveFor(60) // Must agree with the keep alive set above or not set 16 | .withWillTopic('willtopic') // If you set this you must set a will message 17 | .withWillMessage('My Will message') 18 | .startClean() // Non persistent session for testing 19 | .withWillQos(MqttQos.atLeastOnce); 20 | print('EXAMPLE::Mosquitto client connecting....'); 21 | client.connectionMessage = connMess; 22 | 23 | try { 24 | await client.connect(); 25 | } on Exception catch (e) { 26 | print('EXAMPLE::client exception - $e'); 27 | client.disconnect(); 28 | } 29 | 30 | /// Check we are connected 31 | if (client.connectionStatus.state == MqttConnectionState.connected) { 32 | print('EXAMPLE::Mosquitto client connected'); 33 | } else { 34 | /// Use status here rather than state if you also want the broker return code. 35 | print( 36 | 'EXAMPLE::ERROR Mosquitto client connection failed - disconnecting, status is ${client.connectionStatus}', 37 | ); 38 | client.disconnect(); 39 | } 40 | 41 | const topic = 'com/spl/mqtt/test'; 42 | client.subscribe(topic, MqttQos.atLeastOnce); 43 | 44 | client.updates.listen((List> c) { 45 | final MqttPublishMessage recMess = c[0].payload; 46 | final pt = MqttPublishPayload.bytesToStringAsString( 47 | recMess.payload.message, 48 | ); 49 | print( 50 | 'EXAMPLE::Change notification:: topic is <${c[0].topic}>, payload is <-- $pt -->', 51 | ); 52 | print(''); 53 | }); 54 | final builder = MqttClientPayloadBuilder(); 55 | builder.addString('Hello from spl'); 56 | client.subscribe(topic, MqttQos.atLeastOnce); 57 | client.publishMessage(topic, MqttQos.exactlyOnce, builder.payload); 58 | print('EXAMPLE::Sleeping....'); 59 | await MqttUtilities.asyncSleep(20); 60 | 61 | print('EXAMPLE::Unsubscribing'); 62 | client.unsubscribe(topic); 63 | await MqttUtilities.asyncSleep(20); 64 | 65 | /// subscribe again 66 | print('EXAMPLE::subscribe again....'); 67 | client.subscribe(topic, MqttQos.atMostOnce); 68 | final builderM = MqttClientPayloadBuilder(); 69 | builderM.addString('Hello from spl again'); 70 | client.subscribe(topic, MqttQos.exactlyOnce); 71 | client.publishMessage(topic, MqttQos.exactlyOnce, builderM.payload); 72 | print('EXAMPLE::Sleeping....'); 73 | await MqttUtilities.asyncSleep(10); 74 | print('EXAMPLE::Disconnecting'); 75 | client.disconnect(); 76 | return 0; 77 | } 78 | 79 | /// The subscribed callback 80 | void onSubscribed(String topic) { 81 | print('EXAMPLE::Subscription confirmed for topic $topic'); 82 | } 83 | 84 | /// The unsubscribed callback 85 | void onUnsubscribed(String topic) { 86 | print('EXAMPLE::Unsubscribed from topic $topic'); 87 | } 88 | 89 | /// The unsolicited disconnect callback 90 | void onDisconnected() { 91 | print('EXAMPLE::OnDisconnected client callback - Client disconnection'); 92 | } 93 | -------------------------------------------------------------------------------- /test/issues/issue126.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | import 'dart:async'; 9 | import 'dart:io'; 10 | import 'package:mqtt_client/mqtt_client.dart'; 11 | import 'package:mqtt_client/mqtt_server_client.dart'; 12 | 13 | Future main() async { 14 | final client = MqttServerClient('localhost', '1883'); 15 | 16 | /// Version must be V3.1.1 17 | client.setProtocolV311(); 18 | 19 | /// Set logging on if needed, defaults to off 20 | client.logging(on: false); 21 | 22 | /// If you intend to use a keep alive value in your connect message that is not the default(60s) 23 | /// you must set it here 24 | client.keepAlivePeriod = 20; 25 | 26 | /// Add the unsolicited disconnection callback 27 | client.onDisconnected = onDisconnected; 28 | 29 | /// Add a subscribed callback, there is also an unsubscribed callback if you need it. 30 | /// You can add these before connection or change them dynamically after connection if 31 | /// you wish. 32 | client.onSubscribed = onSubscribed; 33 | 34 | /// Set the client id, user and password 35 | client.clientIdentifier = 'GID_HOME_APP@@@866554049565137'; 36 | 37 | /// Connect the client, any errors here are communicated by raising of the appropriate exception. Note 38 | /// in some circumstances the broker will just disconnect us, see the spec about this, we however eill 39 | /// never send malformed messages. 40 | var status = MqttClientConnectionStatus(); 41 | try { 42 | status = await client.connect( 43 | 'Signature|LTAI4FqgKhnxCTkovhEXoXLp|post-cn-0pp1c8ab60y', 44 | 'bMen4ngonwNS3is39Y89az1SQCg=', 45 | ); 46 | } on Exception catch (e) { 47 | print('EXAMPLE::client exception - $e'); 48 | } 49 | 50 | /// Check we are connected 51 | if (status.state == MqttConnectionState.connected) { 52 | print('EXAMPLE::client connected'); 53 | } else { 54 | /// Use status here rather than state if you also want the broker return code. 55 | print('EXAMPLE::ERROR client connection failed - disconnecting, $status'); 56 | client.disconnect(); 57 | exit(-1); 58 | } 59 | 60 | /// Ok, lets try a subscription 61 | print( 62 | 'EXAMPLE::Subscribing to the /hfp/v1/journey/ongoing/+/+/+/2550/2/# topic', 63 | ); 64 | const topic = '/hfp/v1/journey/ongoing/+/+/+/2550/2/#'; 65 | client.subscribe(topic, MqttQos.atMostOnce); 66 | 67 | /// The client has a change notifier object(see the Observable class) which we then listen to to get 68 | /// notifications of published updates to each subscribed topic. 69 | client.updates.listen((List> c) { 70 | final MqttPublishMessage recMess = c[0].payload; 71 | final pt = MqttPublishPayload.bytesToStringAsString( 72 | recMess.payload.message, 73 | ); 74 | print( 75 | 'EXAMPLE::Change notification:: topic is <${c[0].topic}>, payload is <-- $pt -->', 76 | ); 77 | print(''); 78 | }); 79 | 80 | await MqttUtilities.asyncSleep(5); 81 | print('EXAMPLE::Disconnecting'); 82 | client.disconnect(); 83 | return 0; 84 | } 85 | 86 | /// The subscribed callback 87 | void onSubscribed(String topic) { 88 | print('EXAMPLE::Subscription confirmed for topic $topic'); 89 | } 90 | 91 | /// The unsolicited disconnect callback 92 | void onDisconnected() { 93 | print('EXAMPLE::OnDisconnected client callback - Client disconnection'); 94 | exit(-1); 95 | } 96 | -------------------------------------------------------------------------------- /test/issues/issue209/issue209-normal-disconnectedbroker.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Package : mqtt_client 3 | * Author : S. Hamblett 4 | * Date : 31/05/2017 5 | * Copyright : S.Hamblett 6 | */ 7 | 8 | import 'dart:async'; 9 | import 'dart:convert'; 10 | import 'dart:io'; 11 | import 'package:mqtt_client/mqtt_client.dart'; 12 | import 'package:mqtt_client/mqtt_server_client.dart'; 13 | import 'package:test/test.dart'; 14 | 15 | Future main() async { 16 | test( 17 | 'Should maintain subscriptions after autoReconnect', 18 | () async { 19 | final client = MqttServerClient.withPort( 20 | 'localhost', 21 | 'client-id-123456789', 22 | 1883, 23 | ); 24 | client.autoReconnect = true; 25 | client.logging(on: false); 26 | client.keepAlivePeriod = 3; 27 | const topic = 'xd/+'; 28 | 29 | // Subscribe callback, we do the auto reconnect when we know we have subscribed 30 | // second time is from the resubscribe so re publish. 31 | var ignoreSubscribe = false; 32 | void subCB(subTopic) async { 33 | if (ignoreSubscribe) { 34 | print( 35 | 'ISSUE: Received re-subscribe callback for our topic - re publishing', 36 | ); 37 | client.publishMessage( 38 | 'xd/light', 39 | MqttQos.exactlyOnce, 40 | (MqttClientPayloadBuilder()..addUTF8String('xd')).payload, 41 | ); 42 | return; 43 | } 44 | if (topic == subTopic) { 45 | print( 46 | 'ISSUE: Received subscribe callback for our topic - disconnect the broker', 47 | ); 48 | print( 49 | '<<<<<<<<<<<<<<<<<<<<<< DISCONNECT THE BROKER >>>>>>>>>>>>>>>>>>>>>', 50 | ); 51 | sleep(Duration(seconds: 7)); 52 | client.publishMessage( 53 | 'xd/light', 54 | MqttQos.exactlyOnce, 55 | (MqttClientPayloadBuilder()..addUTF8String('xd')).payload, 56 | ); 57 | } else { 58 | print( 59 | 'ISSUE: Received subscribe callback for unknown topic $subTopic', 60 | ); 61 | } 62 | ignoreSubscribe = true; 63 | print('ISSUE: Exiting subscribe callback - reconnect the broker'); 64 | } 65 | 66 | // Main test starts here 67 | print('ISSUE: Main test start'); 68 | client.onSubscribed = subCB; // Subscribe callback 69 | print('ISSUE: Connecting'); 70 | await client.connect(); 71 | client.subscribe(topic, MqttQos.exactlyOnce); 72 | 73 | // Now publish the message 74 | print('ISSUE: Publishing'); 75 | client.publishMessage( 76 | 'xd/light', 77 | MqttQos.exactlyOnce, 78 | (MqttClientPayloadBuilder()..addUTF8String('xd')).payload, 79 | ); 80 | 81 | // Listen for our responses. 82 | print('ISSUE: Listening >>>>'); 83 | final stream = client.updates 84 | .expand((event) sync* { 85 | for (var e in event) { 86 | MqttPublishMessage message = e.payload; 87 | yield utf8.decode(message.payload.message); 88 | } 89 | }) 90 | .timeout(Duration(seconds: 35)); 91 | 92 | expect(await stream.first, equals('xd')); 93 | print('ISSUE: Test complete'); 94 | }, 95 | timeout: Timeout.factor(2), 96 | ); 97 | 98 | return 0; 99 | } 100 | --------------------------------------------------------------------------------