From 9bb84237bb49053c68bc54afa199183ea76eac99 Mon Sep 17 00:00:00 2001 From: BlubbFish Date: Sun, 1 Apr 2018 17:31:18 +0000 Subject: [PATCH] [NF] Mqtt library changed --- M2Mqtt.sln | 2 +- M2Mqtt/Exceptions/MqttClientException.cs | 59 +- .../Exceptions/MqttCommunicationException.cs | 24 +- M2Mqtt/Exceptions/MqttConnectionException.cs | 24 +- M2Mqtt/Exceptions/MqttTimeoutException.cs | 24 +- M2Mqtt/IMqttNetworkChannel.cs | 35 +- M2Mqtt/Internal/InternalEvent.cs | 25 + M2Mqtt/Internal/MsgInternalEvent.cs | 51 + M2Mqtt/Internal/MsgPublishedInternalEvent.cs | 53 + M2Mqtt/M2Mqtt.csproj | 17 +- M2Mqtt/M2MqttMono.csproj | 71 - M2Mqtt/M2MqttNetCf35.csproj | 106 -- M2Mqtt/M2MqttNetCf39.csproj | 87 - M2Mqtt/Messages/MqttMsgBase.cs | 139 +- M2Mqtt/Messages/MqttMsgConnack.cs | 96 +- M2Mqtt/Messages/MqttMsgConnect.cs | 179 +- M2Mqtt/Messages/MqttMsgConnectEventArgs.cs | 22 +- M2Mqtt/Messages/MqttMsgContext.cs | 42 +- M2Mqtt/Messages/MqttMsgDisconnect.cs | 52 +- M2Mqtt/Messages/MqttMsgPingReq.cs | 52 +- M2Mqtt/Messages/MqttMsgPingResp.cs | 52 +- M2Mqtt/Messages/MqttMsgPuback.cs | 70 +- M2Mqtt/Messages/MqttMsgPubcomp.cs | 69 +- M2Mqtt/Messages/MqttMsgPublish.cs | 59 +- M2Mqtt/Messages/MqttMsgPublishEventArgs.cs | 22 +- M2Mqtt/Messages/MqttMsgPublishedEventArgs.cs | 49 +- M2Mqtt/Messages/MqttMsgPubrec.cs | 69 +- M2Mqtt/Messages/MqttMsgPubrel.cs | 90 +- M2Mqtt/Messages/MqttMsgSuback.cs | 67 +- M2Mqtt/Messages/MqttMsgSubscribe.cs | 91 +- M2Mqtt/Messages/MqttMsgSubscribeEventArgs.cs | 22 +- M2Mqtt/Messages/MqttMsgSubscribedEventArgs.cs | 22 +- M2Mqtt/Messages/MqttMsgUnsuback.cs | 72 +- M2Mqtt/Messages/MqttMsgUnsubscribe.cs | 91 +- .../Messages/MqttMsgUnsubscribeEventArgs.cs | 22 +- .../Messages/MqttMsgUnsubscribedEventArgs.cs | 22 +- M2Mqtt/MqttClient.cs | 1445 ++++++++++++----- M2Mqtt/MqttNetworkChannel.cs | 272 ---- M2Mqtt/MqttSecurity.cs | 30 + M2Mqtt/MqttSettings.cs | 34 +- M2Mqtt/Net/Fx.cs | 36 + M2Mqtt/Net/MqttNetworkChannel.cs | 472 ++++++ M2Mqtt/Properties/AssemblyInfo.cs | 88 +- M2Mqtt/Session/MqttBrokerSession.cs | 65 + M2Mqtt/Session/MqttClientSession.cs | 33 + M2Mqtt/Session/MqttSession.cs | 63 + M2Mqtt/Utility/QueueExtension.cs | 24 +- M2Mqtt/Utility/Trace.cs | 86 + M2Mqtt/WinRT/Fx.cs | 36 + M2Mqtt/WinRT/Hashtable.cs | 27 + M2Mqtt/WinRT/MqttNetworkChannel.cs | 179 ++ M2Mqtt/WinRT/Queue.cs | 27 + M2Mqtt/bin/Release/M2Mqtt.dll | Bin 38912 -> 49664 bytes M2Mqtt/uM2MqttNetMf42.csproj | 75 - M2Mqtt/uM2MqttNetMf43.csproj | 75 - 55 files changed, 3349 insertions(+), 1767 deletions(-) create mode 100644 M2Mqtt/Internal/InternalEvent.cs create mode 100644 M2Mqtt/Internal/MsgInternalEvent.cs create mode 100644 M2Mqtt/Internal/MsgPublishedInternalEvent.cs delete mode 100644 M2Mqtt/M2MqttMono.csproj delete mode 100644 M2Mqtt/M2MqttNetCf35.csproj delete mode 100644 M2Mqtt/M2MqttNetCf39.csproj delete mode 100644 M2Mqtt/MqttNetworkChannel.cs create mode 100644 M2Mqtt/MqttSecurity.cs create mode 100644 M2Mqtt/Net/Fx.cs create mode 100644 M2Mqtt/Net/MqttNetworkChannel.cs create mode 100644 M2Mqtt/Session/MqttBrokerSession.cs create mode 100644 M2Mqtt/Session/MqttClientSession.cs create mode 100644 M2Mqtt/Session/MqttSession.cs create mode 100644 M2Mqtt/Utility/Trace.cs create mode 100644 M2Mqtt/WinRT/Fx.cs create mode 100644 M2Mqtt/WinRT/Hashtable.cs create mode 100644 M2Mqtt/WinRT/MqttNetworkChannel.cs create mode 100644 M2Mqtt/WinRT/Queue.cs delete mode 100644 M2Mqtt/uM2MqttNetMf42.csproj delete mode 100644 M2Mqtt/uM2MqttNetMf43.csproj diff --git a/M2Mqtt.sln b/M2Mqtt.sln index cd45efc..f3d48da 100644 --- a/M2Mqtt.sln +++ b/M2Mqtt.sln @@ -20,6 +20,6 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {E4CE8710-7B51-439F-A427-9AEFFE71E682} + SolutionGuid = {135814CF-A469-4436-9C51-3CFE4B93D2FE} EndGlobalSection EndGlobal diff --git a/M2Mqtt/Exceptions/MqttClientException.cs b/M2Mqtt/Exceptions/MqttClientException.cs index bd58745..cd8303f 100644 --- a/M2Mqtt/Exceptions/MqttClientException.cs +++ b/M2Mqtt/Exceptions/MqttClientException.cs @@ -1,19 +1,17 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ using System; @@ -23,7 +21,7 @@ namespace uPLibrary.Networking.M2Mqtt.Exceptions /// /// MQTT client exception /// - public class MqttClientException : ApplicationException + public class MqttClientException : Exception { /// /// Constructor @@ -53,9 +51,9 @@ namespace uPLibrary.Networking.M2Mqtt.Exceptions public enum MqttClientErrorCode { /// - /// Will topic length error + /// Will error (topic, message or QoS level) /// - WillTopicWrong = 1, + WillWrong = 1, /// /// Keep alive period too large @@ -100,6 +98,35 @@ namespace uPLibrary.Networking.M2Mqtt.Exceptions /// /// Wrong Message Id /// - WrongMessageId + WrongMessageId, + + /// + /// Inflight queue is full + /// + InflightQueueFull, + + // [v3.1.1] + /// + /// Invalid flag bits received + /// + InvalidFlagBits, + + // [v3.1.1] + /// + /// Invalid connect flags received + /// + InvalidConnectFlags, + + // [v3.1.1] + /// + /// Invalid client id + /// + InvalidClientId, + + // [v3.1.1] + /// + /// Invalid protocol name + /// + InvalidProtocolName } } diff --git a/M2Mqtt/Exceptions/MqttCommunicationException.cs b/M2Mqtt/Exceptions/MqttCommunicationException.cs index 9d2591c..6b916cc 100644 --- a/M2Mqtt/Exceptions/MqttCommunicationException.cs +++ b/M2Mqtt/Exceptions/MqttCommunicationException.cs @@ -1,19 +1,17 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ using System; @@ -23,7 +21,7 @@ namespace uPLibrary.Networking.M2Mqtt.Exceptions /// /// Exception due to error communication with broker on socket /// - public class MqttCommunicationException : ApplicationException + public class MqttCommunicationException : Exception { /// /// Default constructor diff --git a/M2Mqtt/Exceptions/MqttConnectionException.cs b/M2Mqtt/Exceptions/MqttConnectionException.cs index 9d178c2..3203c7e 100644 --- a/M2Mqtt/Exceptions/MqttConnectionException.cs +++ b/M2Mqtt/Exceptions/MqttConnectionException.cs @@ -1,19 +1,17 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ using System; @@ -23,7 +21,7 @@ namespace uPLibrary.Networking.M2Mqtt.Exceptions /// /// Connection to the broker exception /// - public class MqttConnectionException : ApplicationException + public class MqttConnectionException : Exception { public MqttConnectionException(string message, Exception innerException) : base(message, innerException) diff --git a/M2Mqtt/Exceptions/MqttTimeoutException.cs b/M2Mqtt/Exceptions/MqttTimeoutException.cs index a5b825e..b15e69e 100644 --- a/M2Mqtt/Exceptions/MqttTimeoutException.cs +++ b/M2Mqtt/Exceptions/MqttTimeoutException.cs @@ -1,19 +1,17 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ using System; @@ -23,7 +21,7 @@ namespace uPLibrary.Networking.M2Mqtt.Exceptions /// /// Timeout on receiving from broker exception /// - public class MqttTimeoutException : ApplicationException + public class MqttTimeoutException : Exception { } } diff --git a/M2Mqtt/IMqttNetworkChannel.cs b/M2Mqtt/IMqttNetworkChannel.cs index bf01880..f4ff90f 100644 --- a/M2Mqtt/IMqttNetworkChannel.cs +++ b/M2Mqtt/IMqttNetworkChannel.cs @@ -1,19 +1,17 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ using System; @@ -38,6 +36,14 @@ namespace uPLibrary.Networking.M2Mqtt /// Number of bytes received int Receive(byte[] buffer); + /// + /// Receive data from the network channel with a specified timeout + /// + /// Data buffer for receiving data + /// Timeout on receiving (in milliseconds) + /// Number of bytes received + int Receive(byte[] buffer, int timeout); + /// /// Send data on the network channel to the broker /// @@ -54,5 +60,10 @@ namespace uPLibrary.Networking.M2Mqtt /// Connect to remote server /// void Connect(); + + /// + /// Accept client connection + /// + void Accept(); } } diff --git a/M2Mqtt/Internal/InternalEvent.cs b/M2Mqtt/Internal/InternalEvent.cs new file mode 100644 index 0000000..7f298c7 --- /dev/null +++ b/M2Mqtt/Internal/InternalEvent.cs @@ -0,0 +1,25 @@ +/* +Copyright (c) 2013, 2014 Paolo Patierno + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation +*/ + +namespace uPLibrary.Networking.M2Mqtt.Internal +{ + /// + /// Generic internal event for dispatching + /// + public abstract class InternalEvent + { + } +} diff --git a/M2Mqtt/Internal/MsgInternalEvent.cs b/M2Mqtt/Internal/MsgInternalEvent.cs new file mode 100644 index 0000000..8cf46a4 --- /dev/null +++ b/M2Mqtt/Internal/MsgInternalEvent.cs @@ -0,0 +1,51 @@ +/* +Copyright (c) 2013, 2014 Paolo Patierno + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation +*/ + +using uPLibrary.Networking.M2Mqtt.Messages; + +namespace uPLibrary.Networking.M2Mqtt.Internal +{ + /// + /// Internal event with a message + /// + public class MsgInternalEvent : InternalEvent + { + #region Properties ... + + /// + /// Related message + /// + public MqttMsgBase Message + { + get { return this.msg; } + set { this.msg = value; } + } + + #endregion + + // related message + protected MqttMsgBase msg; + + /// + /// Constructor + /// + /// Related message + public MsgInternalEvent(MqttMsgBase msg) + { + this.msg = msg; + } + } +} diff --git a/M2Mqtt/Internal/MsgPublishedInternalEvent.cs b/M2Mqtt/Internal/MsgPublishedInternalEvent.cs new file mode 100644 index 0000000..5a7f182 --- /dev/null +++ b/M2Mqtt/Internal/MsgPublishedInternalEvent.cs @@ -0,0 +1,53 @@ +/* +Copyright (c) 2013, 2014 Paolo Patierno + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation +*/ + +using uPLibrary.Networking.M2Mqtt.Messages; + +namespace uPLibrary.Networking.M2Mqtt.Internal +{ + /// + /// Internal event for a published message + /// + public class MsgPublishedInternalEvent : MsgInternalEvent + { + #region Properties... + + /// + /// Message published (or failed due to retries) + /// + public bool IsPublished + { + get { return this.isPublished; } + internal set { this.isPublished = value; } + } + + #endregion + + // published flag + bool isPublished; + + /// + /// Constructor + /// + /// Message published + /// Publish flag + public MsgPublishedInternalEvent(MqttMsgBase msg, bool isPublished) + : base(msg) + { + this.isPublished = isPublished; + } + } +} diff --git a/M2Mqtt/M2Mqtt.csproj b/M2Mqtt/M2Mqtt.csproj index 5e1ab26..24464d3 100644 --- a/M2Mqtt/M2Mqtt.csproj +++ b/M2Mqtt/M2Mqtt.csproj @@ -19,17 +19,19 @@ full false bin\Debug\ - TRACE;DEBUG;SSL + TRACE;DEBUG prompt 4 + false pdbonly true bin\Release\ - TRACE;SSL + TRACE prompt 4 + false @@ -42,6 +44,9 @@ + + + @@ -66,9 +71,15 @@ - + + + + + + + diff --git a/M2Mqtt/M2MqttMono.csproj b/M2Mqtt/M2MqttMono.csproj deleted file mode 100644 index da48297..0000000 --- a/M2Mqtt/M2MqttMono.csproj +++ /dev/null @@ -1,71 +0,0 @@ - - - - Debug - AnyCPU - 10.0.0 - 2.0 - {9B706DEC-4CE7-4764-BDBE-8A5F855E5857} - Library - uPLibrary.Networking.M2Mqtt - M2Mqtt - - - true - full - false - bin\Debug\Mono - DEBUG;SSL - prompt - 4 - false - - - none - false - bin\Release\Mono - prompt - 4 - false - SSL - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/M2Mqtt/M2MqttNetCf35.csproj b/M2Mqtt/M2MqttNetCf35.csproj deleted file mode 100644 index bf64adf..0000000 --- a/M2Mqtt/M2MqttNetCf35.csproj +++ /dev/null @@ -1,106 +0,0 @@ - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {194FEA1B-E67F-4FC0-AC47-CD71F7F060CC} - Library - Properties - uPLibrary.Networking.M2Mqtt - M2Mqtt - {4D628B5B-2FBC-4AA6-8C16-197242AEB884};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - WindowsCE - E2BECB1F-8C8C-41ba-B736-9BE7D946A398 - 5.0 - M2MqttNetCf35 - v3.5 - Windows CE - - - - - true - full - false - bin\Debug\NetCf35\ - TRACE;DEBUG;WindowsCE,COMPACT_FRAMEWORK - true - true - prompt - 512 - 4 - Off - - - pdbonly - true - bin\Release\NetCf35\ - TRACE;WindowsCE,COMPACT_FRAMEWORK - true - true - prompt - 512 - 4 - Off - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/M2Mqtt/M2MqttNetCf39.csproj b/M2Mqtt/M2MqttNetCf39.csproj deleted file mode 100644 index 0ca164d..0000000 --- a/M2Mqtt/M2MqttNetCf39.csproj +++ /dev/null @@ -1,87 +0,0 @@ - - - - - Debug - AnyCPU - {BB9B7FF4-6502-41AF-8851-5060B67645E8} - {6AFDAB0D-95EF-424D-8A49-099ECD40B0FF};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - uPLibrary.Networking.M2Mqtt - M2Mqtt - WindowsEmbeddedCompact - v3.9 - v8.0 - 512 - - - true - full - false - bin\Debug\NetCf39\ - TRACE;DEBUG;COMPACT_FRAMEWORK - prompt - 4 - ManagedMinimumRules.ruleset - - - pdbonly - true - bin\Release\NetCf39\ - TRACE;COMPACT_FRAMEWORK - prompt - 4 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/M2Mqtt/Messages/MqttMsgBase.cs b/M2Mqtt/Messages/MqttMsgBase.cs index f8459e0..870fe5f 100644 --- a/M2Mqtt/Messages/MqttMsgBase.cs +++ b/M2Mqtt/Messages/MqttMsgBase.cs @@ -1,29 +1,21 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ -#if SSL && !WINDOWS_PHONE -#if (MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3) -using Microsoft.SPOT.Net.Security; -#else -using System.Net.Security; -#endif -#endif using System; +using System.Text; namespace uPLibrary.Networking.M2Mqtt.Messages { @@ -38,6 +30,9 @@ namespace uPLibrary.Networking.M2Mqtt.Messages internal const byte MSG_TYPE_MASK = 0xF0; internal const byte MSG_TYPE_OFFSET = 0x04; internal const byte MSG_TYPE_SIZE = 0x04; + internal const byte MSG_FLAG_BITS_MASK = 0x0F; // [v3.1.1] + internal const byte MSG_FLAG_BITS_OFFSET = 0x00; // [v3.1.1] + internal const byte MSG_FLAG_BITS_SIZE = 0x04; // [v3.1.1] internal const byte DUP_FLAG_MASK = 0x08; internal const byte DUP_FLAG_OFFSET = 0x03; internal const byte DUP_FLAG_SIZE = 0x01; @@ -64,11 +59,30 @@ namespace uPLibrary.Networking.M2Mqtt.Messages internal const byte MQTT_MSG_PINGRESP_TYPE = 0x0D; internal const byte MQTT_MSG_DISCONNECT_TYPE = 0x0E; + // [v3.1.1] MQTT flag bits + internal const byte MQTT_MSG_CONNECT_FLAG_BITS = 0x00; + internal const byte MQTT_MSG_CONNACK_FLAG_BITS = 0x00; + internal const byte MQTT_MSG_PUBLISH_FLAG_BITS = 0x00; // just defined as 0x00 but depends on publish props (dup, qos, retain) + internal const byte MQTT_MSG_PUBACK_FLAG_BITS = 0x00; + internal const byte MQTT_MSG_PUBREC_FLAG_BITS = 0x00; + internal const byte MQTT_MSG_PUBREL_FLAG_BITS = 0x02; + internal const byte MQTT_MSG_PUBCOMP_FLAG_BITS = 0x00; + internal const byte MQTT_MSG_SUBSCRIBE_FLAG_BITS = 0x02; + internal const byte MQTT_MSG_SUBACK_FLAG_BITS = 0x00; + internal const byte MQTT_MSG_UNSUBSCRIBE_FLAG_BITS = 0x02; + internal const byte MQTT_MSG_UNSUBACK_FLAG_BITS = 0x00; + internal const byte MQTT_MSG_PINGREQ_FLAG_BITS = 0x00; + internal const byte MQTT_MSG_PINGRESP_FLAG_BITS = 0x00; + internal const byte MQTT_MSG_DISCONNECT_FLAG_BITS = 0x00; + // QOS levels public const byte QOS_LEVEL_AT_MOST_ONCE = 0x00; public const byte QOS_LEVEL_AT_LEAST_ONCE = 0x01; public const byte QOS_LEVEL_EXACTLY_ONCE = 0x02; + // SUBSCRIBE QoS level granted failure [v3.1.1] + public const byte QOS_LEVEL_GRANTED_FAILURE = 0x80; + internal const ushort MAX_TOPIC_LENGTH = 65535; internal const ushort MIN_TOPIC_LENGTH = 1; internal const byte MESSAGE_ID_SIZE = 2; @@ -113,6 +127,15 @@ namespace uPLibrary.Networking.M2Mqtt.Messages set { this.retain = value; } } + /// + /// Message identifier for the message + /// + public ushort MessageId + { + get { return this.messageId; } + set { this.messageId = value; } + } + #endregion // message type @@ -123,12 +146,15 @@ namespace uPLibrary.Networking.M2Mqtt.Messages protected byte qosLevel; // retain flag protected bool retain; + // message identifier + protected ushort messageId; /// /// Returns message bytes rapresentation /// + /// Protocol version /// Bytes rapresentation - public abstract byte[] GetBytes(); + public abstract byte[] GetBytes(byte protocolVersion); /// /// Encode remaining length and insert it into message buffer @@ -172,5 +198,78 @@ namespace uPLibrary.Networking.M2Mqtt.Messages } while ((digit & 128) != 0); return value; } + +#if TRACE + /// + /// Returns a string representation of the message for tracing + /// + /// Message name + /// Message fields name + /// Message fields value + /// String representation of the message + protected string GetTraceString(string name, object[] fieldNames, object[] fieldValues) + { + StringBuilder sb = new StringBuilder(); + sb.Append(name); + + if ((fieldNames != null) && (fieldValues != null)) + { + sb.Append("("); + bool addComma = false; + for (int i = 0; i < fieldValues.Length; i++) + { + if (fieldValues[i] != null) + { + if (addComma) + { + sb.Append(","); + } + + sb.Append(fieldNames[i]); + sb.Append(":"); + sb.Append(GetStringObject(fieldValues[i])); + addComma = true; + } + } + sb.Append(")"); + } + + return sb.ToString(); + } + + object GetStringObject(object value) + { + byte[] binary = value as byte[]; + if (binary != null) + { + string hexChars = "0123456789ABCDEF"; + StringBuilder sb = new StringBuilder(binary.Length * 2); + for (int i = 0; i < binary.Length; ++i) + { + sb.Append(hexChars[binary[i] >> 4]); + sb.Append(hexChars[binary[i] & 0x0F]); + } + + return sb.ToString(); + } + + object[] list = value as object[]; + if (list != null) + { + StringBuilder sb = new StringBuilder(); + sb.Append('['); + for (int i = 0; i < list.Length; ++i) + { + if (i > 0) sb.Append(','); + sb.Append(list[i]); + } + sb.Append(']'); + + return sb.ToString(); + } + + return value; + } +#endif } } diff --git a/M2Mqtt/Messages/MqttMsgConnack.cs b/M2Mqtt/Messages/MqttMsgConnack.cs index d6ca107..ae5a342 100644 --- a/M2Mqtt/Messages/MqttMsgConnack.cs +++ b/M2Mqtt/Messages/MqttMsgConnack.cs @@ -1,22 +1,21 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ using System; +using uPLibrary.Networking.M2Mqtt.Exceptions; namespace uPLibrary.Networking.M2Mqtt.Messages { @@ -37,6 +36,13 @@ namespace uPLibrary.Networking.M2Mqtt.Messages private const byte TOPIC_NAME_COMP_RESP_BYTE_OFFSET = 0; private const byte TOPIC_NAME_COMP_RESP_BYTE_SIZE = 1; + // [v3.1.1] connect acknowledge flags replace "old" topic name compression respone (not used in 3.1) + private const byte CONN_ACK_FLAGS_BYTE_OFFSET = 0; + private const byte CONN_ACK_FLAGS_BYTE_SIZE = 1; + // [v3.1.1] session present flag + private const byte SESSION_PRESENT_FLAG_MASK = 0x01; + private const byte SESSION_PRESENT_FLAG_OFFSET = 0x00; + private const byte SESSION_PRESENT_FLAG_SIZE = 0x01; private const byte CONN_RETURN_CODE_BYTE_OFFSET = 1; private const byte CONN_RETURN_CODE_BYTE_SIZE = 1; @@ -44,6 +50,16 @@ namespace uPLibrary.Networking.M2Mqtt.Messages #region Properties... + // [v3.1.1] session present flag + /// + /// Session present flag + /// + public bool SessionPresent + { + get { return this.sessionPresent; } + set { this.sessionPresent = value; } + } + /// /// Return Code /// @@ -55,6 +71,9 @@ namespace uPLibrary.Networking.M2Mqtt.Messages #endregion + // [v3.1.1] session present flag + private bool sessionPresent; + // return code for CONNACK message private byte returnCode; @@ -70,26 +89,39 @@ namespace uPLibrary.Networking.M2Mqtt.Messages /// Parse bytes for a CONNACK message /// /// First fixed header byte + /// Protocol Version /// Channel connected to the broker /// CONNACK message instance - public static MqttMsgConnack Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + public static MqttMsgConnack Parse(byte fixedHeaderFirstByte, byte protocolVersion, IMqttNetworkChannel channel) { byte[] buffer; MqttMsgConnack msg = new MqttMsgConnack(); + if (protocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1_1) + { + // [v3.1.1] check flag bits + if ((fixedHeaderFirstByte & MSG_FLAG_BITS_MASK) != MQTT_MSG_CONNACK_FLAG_BITS) + throw new MqttClientException(MqttClientErrorCode.InvalidFlagBits); + } + // get remaining length and allocate buffer int remainingLength = MqttMsgBase.decodeRemainingLength(channel); buffer = new byte[remainingLength]; // read bytes from socket... channel.Receive(buffer); + if (protocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1_1) + { + // [v3.1.1] ... set session present flag ... + msg.sessionPresent = (buffer[CONN_ACK_FLAGS_BYTE_OFFSET] & SESSION_PRESENT_FLAG_MASK) != 0x00; + } // ...and set return code from broker msg.returnCode = buffer[CONN_RETURN_CODE_BYTE_OFFSET]; return msg; } - public override byte[] GetBytes() + public override byte[] GetBytes(byte ProtocolVersion) { int fixedHeaderSize = 0; int varHeaderSize = 0; @@ -98,8 +130,12 @@ namespace uPLibrary.Networking.M2Mqtt.Messages byte[] buffer; int index = 0; - // topic name compression response and connect return code - varHeaderSize += (TOPIC_NAME_COMP_RESP_BYTE_SIZE + CONN_RETURN_CODE_BYTE_SIZE); + if (ProtocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1_1) + // flags byte and connect return code + varHeaderSize += (CONN_ACK_FLAGS_BYTE_SIZE + CONN_RETURN_CODE_BYTE_SIZE); + else + // topic name compression response and connect return code + varHeaderSize += (TOPIC_NAME_COMP_RESP_BYTE_SIZE + CONN_RETURN_CODE_BYTE_SIZE); remainingLength += (varHeaderSize + payloadSize); @@ -119,19 +155,37 @@ namespace uPLibrary.Networking.M2Mqtt.Messages buffer = new byte[fixedHeaderSize + varHeaderSize + payloadSize]; // first fixed header byte - buffer[index] = (byte)(MQTT_MSG_CONNACK_TYPE << MSG_TYPE_OFFSET); - index++; - + if (ProtocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1_1) + buffer[index++] = (MQTT_MSG_CONNACK_TYPE << MSG_TYPE_OFFSET) | MQTT_MSG_CONNACK_FLAG_BITS; // [v.3.1.1] + else + buffer[index++] = (byte)(MQTT_MSG_CONNACK_TYPE << MSG_TYPE_OFFSET); + // encode remaining length index = this.encodeRemainingLength(remainingLength, buffer, index); - // topic name compression response (reserved values. not used); - buffer[index++] = 0x00; + if (ProtocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1_1) + // [v3.1.1] session present flag + buffer[index++] = this.sessionPresent ? (byte)(1 << SESSION_PRESENT_FLAG_OFFSET) : (byte)0x00; + else + // topic name compression response (reserved values. not used); + buffer[index++] = 0x00; // connect return code buffer[index++] = this.returnCode; return buffer; } + + public override string ToString() + { +#if TRACE + return this.GetTraceString( + "CONNACK", + new object[] { "returnCode" }, + new object[] { this.returnCode }); +#else + return base.ToString(); +#endif + } } } diff --git a/M2Mqtt/Messages/MqttMsgConnect.cs b/M2Mqtt/Messages/MqttMsgConnect.cs index 67e0835..382cf83 100644 --- a/M2Mqtt/Messages/MqttMsgConnect.cs +++ b/M2Mqtt/Messages/MqttMsgConnect.cs @@ -1,19 +1,17 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ using System; @@ -30,19 +28,22 @@ namespace uPLibrary.Networking.M2Mqtt.Messages #region Constants... // protocol name supported - internal const string PROTOCOL_NAME = "MQIsdp"; - - // max length for client id + internal const string PROTOCOL_NAME_V3_1 = "MQIsdp"; + internal const string PROTOCOL_NAME_V3_1_1 = "MQTT"; // [v.3.1.1] + + // max length for client id (removed in 3.1.1) internal const int CLIENT_ID_MAX_LENGTH = 23; // variable header fields internal const byte PROTOCOL_NAME_LEN_SIZE = 2; - internal const byte PROTOCOL_NAME_SIZE = 6; - internal const byte PROTOCOL_VERSION_NUMBER_SIZE = 1; + internal const byte PROTOCOL_NAME_V3_1_SIZE = 6; + internal const byte PROTOCOL_NAME_V3_1_1_SIZE = 4; // [v.3.1.1] + internal const byte PROTOCOL_VERSION_SIZE = 1; internal const byte CONNECT_FLAGS_SIZE = 1; internal const byte KEEP_ALIVE_TIME_SIZE = 2; - internal const byte PROTOCOL_VERSION = 0x03; + internal const byte PROTOCOL_VERSION_V3_1 = 0x03; + internal const byte PROTOCOL_VERSION_V3_1_1 = 0x04; // [v.3.1.1] internal const ushort KEEP_ALIVE_PERIOD_DEFAULT = 60; // seconds internal const ushort MAX_KEEP_ALIVE = 65535; // 16 bit @@ -65,6 +66,10 @@ namespace uPLibrary.Networking.M2Mqtt.Messages internal const byte CLEAN_SESSION_FLAG_MASK = 0x02; internal const byte CLEAN_SESSION_FLAG_OFFSET = 0x01; internal const byte CLEAN_SESSION_FLAG_SIZE = 0x01; + // [v.3.1.1] lsb (reserved) must be now 0 + internal const byte RESERVED_FLAG_MASK = 0x01; + internal const byte RESERVED_FLAG_OFFSET = 0x00; + internal const byte RESERVED_FLAG_SIZE = 0x01; #endregion @@ -218,7 +223,7 @@ namespace uPLibrary.Networking.M2Mqtt.Messages /// /// Client identifier public MqttMsgConnect(string clientId) : - this(clientId, null, null, false, QOS_LEVEL_AT_LEAST_ONCE, false, null, null, true, KEEP_ALIVE_PERIOD_DEFAULT) + this(clientId, null, null, false, QOS_LEVEL_AT_LEAST_ONCE, false, null, null, true, KEEP_ALIVE_PERIOD_DEFAULT, PROTOCOL_VERSION_V3_1_1) { } @@ -235,6 +240,7 @@ namespace uPLibrary.Networking.M2Mqtt.Messages /// Will message /// Clean sessione flag /// Keep alive period + /// Protocol version public MqttMsgConnect(string clientId, string username, string password, @@ -244,7 +250,8 @@ namespace uPLibrary.Networking.M2Mqtt.Messages string willTopic, string willMessage, bool cleanSession, - ushort keepAlivePeriod + ushort keepAlivePeriod, + byte protocolVersion ) { this.type = MQTT_MSG_CONNECT_TYPE; @@ -259,15 +266,19 @@ namespace uPLibrary.Networking.M2Mqtt.Messages this.willMessage = willMessage; this.cleanSession = cleanSession; this.keepAlivePeriod = keepAlivePeriod; + // [v.3.1.1] added new protocol name and version + this.protocolVersion = protocolVersion; + this.protocolName = (this.protocolVersion == PROTOCOL_VERSION_V3_1_1) ? PROTOCOL_NAME_V3_1_1 : PROTOCOL_NAME_V3_1; } /// /// Parse bytes for a CONNECT message /// /// First fixed header byte + /// Protocol Version /// Channel connected to the broker /// CONNECT message instance - public static MqttMsgConnect Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + public static MqttMsgConnect Parse(byte fixedHeaderFirstByte, byte protocolVersion, IMqttNetworkChannel channel) { byte[] buffer; int index = 0; @@ -302,11 +313,20 @@ namespace uPLibrary.Networking.M2Mqtt.Messages index += protNameUtf8Length; msg.protocolName = new String(Encoding.UTF8.GetChars(protNameUtf8)); + // [v3.1.1] wrong protocol name + if (!msg.protocolName.Equals(PROTOCOL_NAME_V3_1) && !msg.protocolName.Equals(PROTOCOL_NAME_V3_1_1)) + throw new MqttClientException(MqttClientErrorCode.InvalidProtocolName); + // protocol version msg.protocolVersion = buffer[index]; - index += PROTOCOL_VERSION_NUMBER_SIZE; + index += PROTOCOL_VERSION_SIZE; // connect flags + // [v3.1.1] check lsb (reserved) must be 0 + if ((msg.protocolVersion == PROTOCOL_VERSION_V3_1_1) && + ((buffer[index] & RESERVED_FLAG_MASK) != 0x00)) + throw new MqttClientException(MqttClientErrorCode.InvalidConnectFlags); + isUsernameFlag = (buffer[index] & USERNAME_FLAG_MASK) != 0x00; isPasswordFlag = (buffer[index] & PASSWORD_FLAG_MASK) != 0x00; msg.willRetain = (buffer[index] & WILL_RETAIN_FLAG_MASK) != 0x00; @@ -319,13 +339,16 @@ namespace uPLibrary.Networking.M2Mqtt.Messages msg.keepAlivePeriod = (ushort)((buffer[index++] << 8) & 0xFF00); msg.keepAlivePeriod |= buffer[index++]; - // client identifier + // client identifier [v3.1.1] it may be zero bytes long (empty string) clientIdUtf8Length = ((buffer[index++] << 8) & 0xFF00); clientIdUtf8Length |= buffer[index++]; clientIdUtf8 = new byte[clientIdUtf8Length]; Array.Copy(buffer, index, clientIdUtf8, 0, clientIdUtf8Length); index += clientIdUtf8Length; msg.clientId = new String(Encoding.UTF8.GetChars(clientIdUtf8)); + // [v3.1.1] if client identifier is zero bytes long, clean session must be true + if ((msg.protocolVersion == PROTOCOL_VERSION_V3_1_1) && (clientIdUtf8Length == 0) && (!msg.cleanSession)) + throw new MqttClientException(MqttClientErrorCode.InvalidClientId); // will topic and will message if (msg.willFlag) @@ -370,7 +393,7 @@ namespace uPLibrary.Networking.M2Mqtt.Messages return msg; } - public override byte[] GetBytes() + public override byte[] GetBytes(byte protocolVersion) { int fixedHeaderSize = 0; int varHeaderSize = 0; @@ -380,21 +403,49 @@ namespace uPLibrary.Networking.M2Mqtt.Messages int index = 0; byte[] clientIdUtf8 = Encoding.UTF8.GetBytes(this.clientId); - byte[] willTopicUtf8 = (this.willTopic != null) ? Encoding.UTF8.GetBytes(this.willTopic) : null; - byte[] willMessageUtf8 = (this.willMessage != null) ? Encoding.UTF8.GetBytes(this.willMessage) : null; - byte[] usernameUtf8 = (this.username != null) ? Encoding.UTF8.GetBytes(this.username) : null; - byte[] passwordUtf8 = (this.password != null) ? Encoding.UTF8.GetBytes(this.password) : null; + byte[] willTopicUtf8 = (this.willFlag && (this.willTopic != null)) ? Encoding.UTF8.GetBytes(this.willTopic) : null; + byte[] willMessageUtf8 = (this.willFlag && (this.willMessage != null)) ? Encoding.UTF8.GetBytes(this.willMessage) : null; + byte[] usernameUtf8 = ((this.username != null) && (this.username.Length > 0)) ? Encoding.UTF8.GetBytes(this.username) : null; + byte[] passwordUtf8 = ((this.password != null) && (this.password.Length > 0)) ? Encoding.UTF8.GetBytes(this.password) : null; + + // [v3.1.1] + if (this.protocolVersion == PROTOCOL_VERSION_V3_1_1) + { + // will flag set, will topic and will message MUST be present + if (this.willFlag && ((this.willQosLevel >= 0x03) || + (willTopicUtf8 == null) || (willMessageUtf8 == null) || + ((willTopicUtf8 != null) && (willTopicUtf8.Length == 0)) || + ((willMessageUtf8 != null) && (willMessageUtf8.Length == 0)))) + throw new MqttClientException(MqttClientErrorCode.WillWrong); + // willflag not set, retain must be 0 and will topic and message MUST NOT be present + else if (!this.willFlag && ((this.willRetain) || + (willTopicUtf8 != null) || (willMessageUtf8 != null) || + ((willTopicUtf8 != null) && (willTopicUtf8.Length != 0)) || + ((willMessageUtf8 != null) && (willMessageUtf8.Length != 0)))) + throw new MqttClientException(MqttClientErrorCode.WillWrong); + } - // will flag set but will topic wrong - if (this.willFlag && (willTopicUtf8.Length == 0)) - throw new MqttClientException(MqttClientErrorCode.WillTopicWrong); if (this.keepAlivePeriod > MAX_KEEP_ALIVE) throw new MqttClientException(MqttClientErrorCode.KeepAliveWrong); + // check on will QoS Level + if ((this.willQosLevel < MqttMsgBase.QOS_LEVEL_AT_MOST_ONCE) || + (this.willQosLevel > MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE)) + throw new MqttClientException(MqttClientErrorCode.WillWrong); + // protocol name field size - varHeaderSize += (PROTOCOL_NAME_LEN_SIZE + PROTOCOL_NAME_SIZE); - // protocol version number field size - varHeaderSize += PROTOCOL_VERSION_NUMBER_SIZE; + // MQTT version 3.1 + if (this.protocolVersion == PROTOCOL_VERSION_V3_1) + { + varHeaderSize += (PROTOCOL_NAME_LEN_SIZE + PROTOCOL_NAME_V3_1_SIZE); + } + // MQTT version 3.1.1 + else + { + varHeaderSize += (PROTOCOL_NAME_LEN_SIZE + PROTOCOL_NAME_V3_1_1_SIZE); + } + // protocol level field size + varHeaderSize += PROTOCOL_VERSION_SIZE; // connect flags field size varHeaderSize += CONNECT_FLAGS_SIZE; // keep alive timer field size @@ -429,30 +480,40 @@ namespace uPLibrary.Networking.M2Mqtt.Messages buffer = new byte[fixedHeaderSize + varHeaderSize + payloadSize]; // first fixed header byte - buffer[index++] = (MQTT_MSG_CONNECT_TYPE << MSG_TYPE_OFFSET); + buffer[index++] = (MQTT_MSG_CONNECT_TYPE << MSG_TYPE_OFFSET) | MQTT_MSG_CONNECT_FLAG_BITS; // [v.3.1.1] // encode remaining length index = this.encodeRemainingLength(remainingLength, buffer, index); // protocol name buffer[index++] = 0; // MSB protocol name size - buffer[index++] = PROTOCOL_NAME_SIZE; // LSB protocol name size - buffer[index++] = (byte)'M'; - buffer[index++] = (byte)'Q'; - buffer[index++] = (byte)'I'; - buffer[index++] = (byte)'s'; - buffer[index++] = (byte)'d'; - buffer[index++] = (byte)'p'; - - // protocol version - buffer[index++] = PROTOCOL_VERSION; - + // MQTT version 3.1 + if (this.protocolVersion == PROTOCOL_VERSION_V3_1) + { + buffer[index++] = PROTOCOL_NAME_V3_1_SIZE; // LSB protocol name size + Array.Copy(Encoding.UTF8.GetBytes(PROTOCOL_NAME_V3_1), 0, buffer, index, PROTOCOL_NAME_V3_1_SIZE); + index += PROTOCOL_NAME_V3_1_SIZE; + // protocol version + buffer[index++] = PROTOCOL_VERSION_V3_1; + } + // MQTT version 3.1.1 + else + { + buffer[index++] = PROTOCOL_NAME_V3_1_1_SIZE; // LSB protocol name size + Array.Copy(Encoding.UTF8.GetBytes(PROTOCOL_NAME_V3_1_1), 0, buffer, index, PROTOCOL_NAME_V3_1_1_SIZE); + index += PROTOCOL_NAME_V3_1_1_SIZE; + // protocol version + buffer[index++] = PROTOCOL_VERSION_V3_1_1; + } + // connect flags byte connectFlags = 0x00; - connectFlags |= (this.username != null) ? (byte)(1 << USERNAME_FLAG_OFFSET) : (byte)0x00; - connectFlags |= (this.password != null) ? (byte)(1 << PASSWORD_FLAG_OFFSET) : (byte)0x00; + connectFlags |= (usernameUtf8 != null) ? (byte)(1 << USERNAME_FLAG_OFFSET) : (byte)0x00; + connectFlags |= (passwordUtf8 != null) ? (byte)(1 << PASSWORD_FLAG_OFFSET) : (byte)0x00; connectFlags |= (this.willRetain) ? (byte)(1 << WILL_RETAIN_FLAG_OFFSET) : (byte)0x00; - connectFlags |= (byte)(this.willQosLevel << WILL_QOS_FLAG_OFFSET); + // only if will flag is set, we have to use will QoS level (otherwise is MUST be 0) + if (this.willFlag) + connectFlags |= (byte)(this.willQosLevel << WILL_QOS_FLAG_OFFSET); connectFlags |= (this.willFlag) ? (byte)(1 << WILL_FLAG_OFFSET) : (byte)0x00; connectFlags |= (this.cleanSession) ? (byte)(1 << CLEAN_SESSION_FLAG_OFFSET) : (byte)0x00; buffer[index++] = connectFlags; @@ -468,7 +529,7 @@ namespace uPLibrary.Networking.M2Mqtt.Messages index += clientIdUtf8.Length; // will topic - if (this.willFlag && (this.willTopic != null)) + if (this.willFlag && (willTopicUtf8 != null)) { buffer[index++] = (byte)((willTopicUtf8.Length >> 8) & 0x00FF); // MSB buffer[index++] = (byte)(willTopicUtf8.Length & 0x00FF); // LSB @@ -477,7 +538,7 @@ namespace uPLibrary.Networking.M2Mqtt.Messages } // will message - if (this.willFlag && (this.willMessage != null)) + if (this.willFlag && (willMessageUtf8 != null)) { buffer[index++] = (byte)((willMessageUtf8.Length >> 8) & 0x00FF); // MSB buffer[index++] = (byte)(willMessageUtf8.Length & 0x00FF); // LSB @@ -486,7 +547,7 @@ namespace uPLibrary.Networking.M2Mqtt.Messages } // username - if (this.username != null) + if (usernameUtf8 != null) { buffer[index++] = (byte)((usernameUtf8.Length >> 8) & 0x00FF); // MSB buffer[index++] = (byte)(usernameUtf8.Length & 0x00FF); // LSB @@ -495,7 +556,7 @@ namespace uPLibrary.Networking.M2Mqtt.Messages } // password - if (this.password != null) + if (passwordUtf8 != null) { buffer[index++] = (byte)((passwordUtf8.Length >> 8) & 0x00FF); // MSB buffer[index++] = (byte)(passwordUtf8.Length & 0x00FF); // LSB @@ -505,5 +566,17 @@ namespace uPLibrary.Networking.M2Mqtt.Messages return buffer; } + + public override string ToString() + { +#if TRACE + return this.GetTraceString( + "CONNECT", + new object[] { "protocolName", "protocolVersion", "clientId", "willFlag", "willRetain", "willQosLevel", "willTopic", "willMessage", "username", "password", "cleanSession", "keepAlivePeriod" }, + new object[] { this.protocolName, this.protocolVersion, this.clientId, this.willFlag, this.willRetain, this.willQosLevel, this.willTopic, this.willMessage, this.username, this.password, this.cleanSession, this.keepAlivePeriod }); +#else + return base.ToString(); +#endif + } } } diff --git a/M2Mqtt/Messages/MqttMsgConnectEventArgs.cs b/M2Mqtt/Messages/MqttMsgConnectEventArgs.cs index 6c901e1..ce0b3fe 100644 --- a/M2Mqtt/Messages/MqttMsgConnectEventArgs.cs +++ b/M2Mqtt/Messages/MqttMsgConnectEventArgs.cs @@ -1,19 +1,17 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ #if (!MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3) diff --git a/M2Mqtt/Messages/MqttMsgContext.cs b/M2Mqtt/Messages/MqttMsgContext.cs index 204703f..a2850d7 100644 --- a/M2Mqtt/Messages/MqttMsgContext.cs +++ b/M2Mqtt/Messages/MqttMsgContext.cs @@ -1,19 +1,17 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ using System; @@ -50,6 +48,14 @@ namespace uPLibrary.Networking.M2Mqtt.Messages /// Attempt (for retry) /// public int Attempt { get; set; } + + /// + /// Unique key + /// + public string Key + { + get { return this.Flow + "_" + this.Message.MessageId; } + } } /// @@ -128,6 +134,18 @@ namespace uPLibrary.Networking.M2Mqtt.Messages /// SendPuback, + // [v3.1.1] SUBSCRIBE isn't "officially" QOS = 1 + /// + /// Send SUBSCRIBE message + /// + SendSubscribe, + + // [v3.1.1] UNSUBSCRIBE isn't "officially" QOS = 1 + /// + /// Send UNSUBSCRIBE message + /// + SendUnsubscribe, + /// /// (QOS = 1), SUBSCRIBE sent, wait for SUBACK /// diff --git a/M2Mqtt/Messages/MqttMsgDisconnect.cs b/M2Mqtt/Messages/MqttMsgDisconnect.cs index 8c4eb7e..d918473 100644 --- a/M2Mqtt/Messages/MqttMsgDisconnect.cs +++ b/M2Mqtt/Messages/MqttMsgDisconnect.cs @@ -1,21 +1,20 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ +using uPLibrary.Networking.M2Mqtt.Exceptions; namespace uPLibrary.Networking.M2Mqtt.Messages { @@ -36,12 +35,20 @@ namespace uPLibrary.Networking.M2Mqtt.Messages /// Parse bytes for a DISCONNECT message /// /// First fixed header byte + /// Protocol Version /// Channel connected to the broker /// DISCONNECT message instance - public static MqttMsgDisconnect Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + public static MqttMsgDisconnect Parse(byte fixedHeaderFirstByte, byte protocolVersion, IMqttNetworkChannel channel) { MqttMsgDisconnect msg = new MqttMsgDisconnect(); + if (protocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1_1) + { + // [v3.1.1] check flag bits + if ((fixedHeaderFirstByte & MSG_FLAG_BITS_MASK) != MQTT_MSG_DISCONNECT_FLAG_BITS) + throw new MqttClientException(MqttClientErrorCode.InvalidFlagBits); + } + // get remaining length and allocate buffer int remainingLength = MqttMsgBase.decodeRemainingLength(channel); // NOTE : remainingLength must be 0 @@ -49,16 +56,31 @@ namespace uPLibrary.Networking.M2Mqtt.Messages return msg; } - public override byte[] GetBytes() + public override byte[] GetBytes(byte protocolVersion) { byte[] buffer = new byte[2]; int index = 0; // first fixed header byte - buffer[index++] = (MQTT_MSG_DISCONNECT_TYPE << MSG_TYPE_OFFSET); + if (protocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1_1) + buffer[index++] = (MQTT_MSG_DISCONNECT_TYPE << MSG_TYPE_OFFSET) | MQTT_MSG_DISCONNECT_FLAG_BITS; // [v.3.1.1] + else + buffer[index++] = (MQTT_MSG_DISCONNECT_TYPE << MSG_TYPE_OFFSET); buffer[index++] = 0x00; return buffer; } + + public override string ToString() + { +#if TRACE + return this.GetTraceString( + "DISCONNECT", + null, + null); +#else + return base.ToString(); +#endif + } } } diff --git a/M2Mqtt/Messages/MqttMsgPingReq.cs b/M2Mqtt/Messages/MqttMsgPingReq.cs index e8f4efb..b46bdb6 100644 --- a/M2Mqtt/Messages/MqttMsgPingReq.cs +++ b/M2Mqtt/Messages/MqttMsgPingReq.cs @@ -1,21 +1,20 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ +using uPLibrary.Networking.M2Mqtt.Exceptions; namespace uPLibrary.Networking.M2Mqtt.Messages { @@ -32,13 +31,16 @@ namespace uPLibrary.Networking.M2Mqtt.Messages this.type = MQTT_MSG_PINGREQ_TYPE; } - public override byte[] GetBytes() + public override byte[] GetBytes(byte protocolVersion) { byte[] buffer = new byte[2]; int index = 0; // first fixed header byte - buffer[index++] = (MQTT_MSG_PINGREQ_TYPE << MSG_TYPE_OFFSET); + if (protocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1_1) + buffer[index++] = (MQTT_MSG_PINGREQ_TYPE << MSG_TYPE_OFFSET) | MQTT_MSG_PINGREQ_FLAG_BITS; // [v.3.1.1] + else + buffer[index++] = (MQTT_MSG_PINGREQ_TYPE << MSG_TYPE_OFFSET); buffer[index++] = 0x00; return buffer; @@ -48,17 +50,37 @@ namespace uPLibrary.Networking.M2Mqtt.Messages /// Parse bytes for a PINGREQ message /// /// First fixed header byte + /// Protocol Version /// Channel connected to the broker /// PINGREQ message instance - public static MqttMsgPingReq Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + public static MqttMsgPingReq Parse(byte fixedHeaderFirstByte, byte protocolVersion, IMqttNetworkChannel channel) { MqttMsgPingReq msg = new MqttMsgPingReq(); + if (protocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1_1) + { + // [v3.1.1] check flag bits + if ((fixedHeaderFirstByte & MSG_FLAG_BITS_MASK) != MQTT_MSG_PINGREQ_FLAG_BITS) + throw new MqttClientException(MqttClientErrorCode.InvalidFlagBits); + } + // already know remaininglength is zero (MQTT specification), // so it isn't necessary to read other data from socket int remainingLength = MqttMsgBase.decodeRemainingLength(channel); return msg; } + + public override string ToString() + { +#if TRACE + return this.GetTraceString( + "PINGREQ", + null, + null); +#else + return base.ToString(); +#endif + } } } diff --git a/M2Mqtt/Messages/MqttMsgPingResp.cs b/M2Mqtt/Messages/MqttMsgPingResp.cs index 988edcf..2b028b9 100644 --- a/M2Mqtt/Messages/MqttMsgPingResp.cs +++ b/M2Mqtt/Messages/MqttMsgPingResp.cs @@ -1,22 +1,21 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ using System; +using uPLibrary.Networking.M2Mqtt.Exceptions; namespace uPLibrary.Networking.M2Mqtt.Messages { @@ -37,12 +36,20 @@ namespace uPLibrary.Networking.M2Mqtt.Messages /// Parse bytes for a PINGRESP message /// /// First fixed header byte + /// Protocol Version /// Channel connected to the broker /// PINGRESP message instance - public static MqttMsgPingResp Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + public static MqttMsgPingResp Parse(byte fixedHeaderFirstByte, byte protocolVersion, IMqttNetworkChannel channel) { MqttMsgPingResp msg = new MqttMsgPingResp(); + if (protocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1_1) + { + // [v3.1.1] check flag bits + if ((fixedHeaderFirstByte & MSG_FLAG_BITS_MASK) != MQTT_MSG_PINGRESP_FLAG_BITS) + throw new MqttClientException(MqttClientErrorCode.InvalidFlagBits); + } + // already know remaininglength is zero (MQTT specification), // so it isn't necessary to read other data from socket int remainingLength = MqttMsgBase.decodeRemainingLength(channel); @@ -50,16 +57,31 @@ namespace uPLibrary.Networking.M2Mqtt.Messages return msg; } - public override byte[] GetBytes() + public override byte[] GetBytes(byte protocolVersion) { byte[] buffer = new byte[2]; int index = 0; // first fixed header byte - buffer[index++] = (MQTT_MSG_PINGRESP_TYPE << MSG_TYPE_OFFSET); + if (protocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1_1) + buffer[index++] = (MQTT_MSG_PINGRESP_TYPE << MSG_TYPE_OFFSET) | MQTT_MSG_PINGRESP_FLAG_BITS; // [v.3.1.1] + else + buffer[index++] = (MQTT_MSG_PINGRESP_TYPE << MSG_TYPE_OFFSET); buffer[index++] = 0x00; return buffer; } + + public override string ToString() + { +#if TRACE + return this.GetTraceString( + "PINGRESP", + null, + null); +#else + return base.ToString(); +#endif + } } } diff --git a/M2Mqtt/Messages/MqttMsgPuback.cs b/M2Mqtt/Messages/MqttMsgPuback.cs index 646746c..e00e37f 100644 --- a/M2Mqtt/Messages/MqttMsgPuback.cs +++ b/M2Mqtt/Messages/MqttMsgPuback.cs @@ -1,21 +1,21 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ +using uPLibrary.Networking.M2Mqtt.Exceptions; + namespace uPLibrary.Networking.M2Mqtt.Messages { /// @@ -23,23 +23,6 @@ namespace uPLibrary.Networking.M2Mqtt.Messages /// public class MqttMsgPuback : MqttMsgBase { - #region Properties... - - /// - /// Message identifier for the publish message - /// that is acknowledged - /// - public ushort MessageId - { - get { return this.messageId; } - set { this.messageId = value; } - } - - #endregion - - // message identifier - private ushort messageId; - /// /// Constructor /// @@ -48,7 +31,7 @@ namespace uPLibrary.Networking.M2Mqtt.Messages this.type = MQTT_MSG_PUBACK_TYPE; } - public override byte[] GetBytes() + public override byte[] GetBytes(byte protocolVersion) { int fixedHeaderSize = 0; int varHeaderSize = 0; @@ -78,7 +61,10 @@ namespace uPLibrary.Networking.M2Mqtt.Messages buffer = new byte[fixedHeaderSize + varHeaderSize + payloadSize]; // first fixed header byte - buffer[index++] = (MQTT_MSG_PUBACK_TYPE << MSG_TYPE_OFFSET); + if (protocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1_1) + buffer[index++] = (MQTT_MSG_PUBACK_TYPE << MSG_TYPE_OFFSET) | MQTT_MSG_PUBACK_FLAG_BITS; // [v.3.1.1] + else + buffer[index++] = (MQTT_MSG_PUBACK_TYPE << MSG_TYPE_OFFSET); // encode remaining length index = this.encodeRemainingLength(remainingLength, buffer, index); @@ -94,14 +80,22 @@ namespace uPLibrary.Networking.M2Mqtt.Messages /// Parse bytes for a PUBACK message /// /// First fixed header byte + /// Protocol Version /// Channel connected to the broker /// PUBACK message instance - public static MqttMsgPuback Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + public static MqttMsgPuback Parse(byte fixedHeaderFirstByte, byte protocolVersion, IMqttNetworkChannel channel) { byte[] buffer; int index = 0; MqttMsgPuback msg = new MqttMsgPuback(); + if (protocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1_1) + { + // [v3.1.1] check flag bits + if ((fixedHeaderFirstByte & MSG_FLAG_BITS_MASK) != MQTT_MSG_PUBACK_FLAG_BITS) + throw new MqttClientException(MqttClientErrorCode.InvalidFlagBits); + } + // get remaining length and allocate buffer int remainingLength = MqttMsgBase.decodeRemainingLength(channel); buffer = new byte[remainingLength]; @@ -115,5 +109,17 @@ namespace uPLibrary.Networking.M2Mqtt.Messages return msg; } + + public override string ToString() + { +#if TRACE + return this.GetTraceString( + "PUBACK", + new object[] { "messageId" }, + new object[] { this.messageId }); +#else + return base.ToString(); +#endif + } } } diff --git a/M2Mqtt/Messages/MqttMsgPubcomp.cs b/M2Mqtt/Messages/MqttMsgPubcomp.cs index ca53667..eeabd3b 100644 --- a/M2Mqtt/Messages/MqttMsgPubcomp.cs +++ b/M2Mqtt/Messages/MqttMsgPubcomp.cs @@ -1,21 +1,21 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ +using uPLibrary.Networking.M2Mqtt.Exceptions; + namespace uPLibrary.Networking.M2Mqtt.Messages { /// @@ -23,22 +23,6 @@ namespace uPLibrary.Networking.M2Mqtt.Messages /// public class MqttMsgPubcomp : MqttMsgBase { - #region Properties... - - /// - /// Message identifier for the acknowledged publish message - /// - public ushort MessageId - { - get { return this.messageId; } - set { this.messageId = value; } - } - - #endregion - - // message identifier - private ushort messageId; - /// /// Constructor /// @@ -47,7 +31,7 @@ namespace uPLibrary.Networking.M2Mqtt.Messages this.type = MQTT_MSG_PUBCOMP_TYPE; } - public override byte[] GetBytes() + public override byte[] GetBytes(byte protocolVersion) { int fixedHeaderSize = 0; int varHeaderSize = 0; @@ -77,7 +61,10 @@ namespace uPLibrary.Networking.M2Mqtt.Messages buffer = new byte[fixedHeaderSize + varHeaderSize + payloadSize]; // first fixed header byte - buffer[index++] = (MQTT_MSG_PUBCOMP_TYPE << MSG_TYPE_OFFSET); + if (protocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1_1) + buffer[index++] = (MQTT_MSG_PUBCOMP_TYPE << MSG_TYPE_OFFSET) | MQTT_MSG_PUBCOMP_FLAG_BITS; // [v.3.1.1] + else + buffer[index++] = (MQTT_MSG_PUBCOMP_TYPE << MSG_TYPE_OFFSET); // encode remaining length index = this.encodeRemainingLength(remainingLength, buffer, index); @@ -93,14 +80,22 @@ namespace uPLibrary.Networking.M2Mqtt.Messages /// Parse bytes for a PUBCOMP message /// /// First fixed header byte + /// Protocol Version /// Channel connected to the broker /// PUBCOMP message instance - public static MqttMsgPubcomp Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + public static MqttMsgPubcomp Parse(byte fixedHeaderFirstByte, byte protocolVersion, IMqttNetworkChannel channel) { byte[] buffer; int index = 0; MqttMsgPubcomp msg = new MqttMsgPubcomp(); + if (protocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1_1) + { + // [v3.1.1] check flag bits + if ((fixedHeaderFirstByte & MSG_FLAG_BITS_MASK) != MQTT_MSG_PUBCOMP_FLAG_BITS) + throw new MqttClientException(MqttClientErrorCode.InvalidFlagBits); + } + // get remaining length and allocate buffer int remainingLength = MqttMsgBase.decodeRemainingLength(channel); buffer = new byte[remainingLength]; @@ -114,5 +109,17 @@ namespace uPLibrary.Networking.M2Mqtt.Messages return msg; } + + public override string ToString() + { +#if TRACE + return this.GetTraceString( + "PUBCOMP", + new object[] { "messageId" }, + new object[] { this.messageId }); +#else + return base.ToString(); +#endif + } } } diff --git a/M2Mqtt/Messages/MqttMsgPublish.cs b/M2Mqtt/Messages/MqttMsgPublish.cs index 8ee80b5..6bbf207 100644 --- a/M2Mqtt/Messages/MqttMsgPublish.cs +++ b/M2Mqtt/Messages/MqttMsgPublish.cs @@ -1,19 +1,17 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ using System; @@ -47,24 +45,13 @@ namespace uPLibrary.Networking.M2Mqtt.Messages set { this.message = value; } } - /// - /// Message identifier - /// - public ushort MessageId - { - get { return this.messageId; } - set { this.messageId = value; } - } - #endregion // message topic private string topic; // message data private byte[] message; - // message identifier - ushort messageId; - + /// /// Constructor /// @@ -107,7 +94,7 @@ namespace uPLibrary.Networking.M2Mqtt.Messages this.messageId = 0; } - public override byte[] GetBytes() + public override byte[] GetBytes(byte protocolVersion) { int fixedHeaderSize = 0; int varHeaderSize = 0; @@ -124,6 +111,10 @@ namespace uPLibrary.Networking.M2Mqtt.Messages if ((this.topic.Length < MIN_TOPIC_LENGTH) || (this.topic.Length > MAX_TOPIC_LENGTH)) throw new MqttClientException(MqttClientErrorCode.TopicLength); + // check wrong QoS level (both bits can't be set 1) + if (this.qosLevel > QOS_LEVEL_EXACTLY_ONCE) + throw new MqttClientException(MqttClientErrorCode.QosNotAllowed); + byte[] topicUtf8 = Encoding.UTF8.GetBytes(this.topic); // topic name @@ -200,9 +191,10 @@ namespace uPLibrary.Networking.M2Mqtt.Messages /// Parse bytes for a PUBLISH message /// /// First fixed header byte + /// Protocol Version /// Channel connected to the broker /// PUBLISH message instance - public static MqttMsgPublish Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + public static MqttMsgPublish Parse(byte fixedHeaderFirstByte, byte protocolVersion, IMqttNetworkChannel channel) { byte[] buffer; int index = 0; @@ -227,6 +219,9 @@ namespace uPLibrary.Networking.M2Mqtt.Messages // read QoS level from fixed header msg.qosLevel = (byte)((fixedHeaderFirstByte & QOS_LEVEL_MASK) >> QOS_LEVEL_OFFSET); + // check wrong QoS level (both bits can't be set 1) + if (msg.qosLevel > QOS_LEVEL_EXACTLY_ONCE) + throw new MqttClientException(MqttClientErrorCode.QosNotAllowed); // read DUP flag from fixed header msg.dupFlag = (((fixedHeaderFirstByte & DUP_FLAG_MASK) >> DUP_FLAG_OFFSET) == 0x01); // read retain flag from fixed header @@ -266,5 +261,17 @@ namespace uPLibrary.Networking.M2Mqtt.Messages return msg; } + + public override string ToString() + { +#if TRACE + return this.GetTraceString( + "PUBLISH", + new object[] { "messageId", "topic", "message" }, + new object[] { this.messageId, this.topic, this.message }); +#else + return base.ToString(); +#endif + } } } diff --git a/M2Mqtt/Messages/MqttMsgPublishEventArgs.cs b/M2Mqtt/Messages/MqttMsgPublishEventArgs.cs index 5954d91..efb2333 100644 --- a/M2Mqtt/Messages/MqttMsgPublishEventArgs.cs +++ b/M2Mqtt/Messages/MqttMsgPublishEventArgs.cs @@ -1,19 +1,17 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ #if (!MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3) diff --git a/M2Mqtt/Messages/MqttMsgPublishedEventArgs.cs b/M2Mqtt/Messages/MqttMsgPublishedEventArgs.cs index 811d946..e507c38 100644 --- a/M2Mqtt/Messages/MqttMsgPublishedEventArgs.cs +++ b/M2Mqtt/Messages/MqttMsgPublishedEventArgs.cs @@ -1,19 +1,17 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ #if (!MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3) @@ -40,18 +38,41 @@ namespace uPLibrary.Networking.M2Mqtt.Messages internal set { this.messageId = value; } } + /// + /// Message published (or failed due to retries) + /// + public bool IsPublished + { + get { return this.isPublished; } + internal set { this.isPublished = value; } + } + #endregion // message identifier ushort messageId; + // published flag + bool isPublished; + + /// + /// Constructor (published message) + /// + /// Message identifier published + public MqttMsgPublishedEventArgs(ushort messageId) + : this(messageId, true) + { + } + /// /// Constructor /// - /// Message identifier published - public MqttMsgPublishedEventArgs(ushort messageId) + /// Message identifier + /// Publish flag + public MqttMsgPublishedEventArgs(ushort messageId, bool isPublished) { this.messageId = messageId; + this.isPublished = isPublished; } } } diff --git a/M2Mqtt/Messages/MqttMsgPubrec.cs b/M2Mqtt/Messages/MqttMsgPubrec.cs index 05ce84a..1855bf9 100644 --- a/M2Mqtt/Messages/MqttMsgPubrec.cs +++ b/M2Mqtt/Messages/MqttMsgPubrec.cs @@ -1,21 +1,21 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ +using uPLibrary.Networking.M2Mqtt.Exceptions; + namespace uPLibrary.Networking.M2Mqtt.Messages { /// @@ -23,22 +23,6 @@ namespace uPLibrary.Networking.M2Mqtt.Messages /// public class MqttMsgPubrec : MqttMsgBase { - #region Properties... - - /// - /// Message identifier for the acknowledged publish message - /// - public ushort MessageId - { - get { return this.messageId; } - set { this.messageId = value; } - } - - #endregion - - // message identifier - private ushort messageId; - /// /// Constructor /// @@ -47,7 +31,7 @@ namespace uPLibrary.Networking.M2Mqtt.Messages this.type = MQTT_MSG_PUBREC_TYPE; } - public override byte[] GetBytes() + public override byte[] GetBytes(byte protocolVersion) { int fixedHeaderSize = 0; int varHeaderSize = 0; @@ -77,7 +61,10 @@ namespace uPLibrary.Networking.M2Mqtt.Messages buffer = new byte[fixedHeaderSize + varHeaderSize + payloadSize]; // first fixed header byte - buffer[index++] = (MQTT_MSG_PUBREC_TYPE << MSG_TYPE_OFFSET); + if (protocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1_1) + buffer[index++] = (MQTT_MSG_PUBREC_TYPE << MSG_TYPE_OFFSET) | MQTT_MSG_PUBREC_FLAG_BITS; // [v.3.1.1] + else + buffer[index++] = (MQTT_MSG_PUBREC_TYPE << MSG_TYPE_OFFSET); // encode remaining length index = this.encodeRemainingLength(remainingLength, buffer, index); @@ -93,14 +80,22 @@ namespace uPLibrary.Networking.M2Mqtt.Messages /// Parse bytes for a PUBREC message /// /// First fixed header byte + /// Protocol Version /// Channel connected to the broker /// PUBREC message instance - public static MqttMsgPubrec Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + public static MqttMsgPubrec Parse(byte fixedHeaderFirstByte, byte protocolVersion, IMqttNetworkChannel channel) { byte[] buffer; int index = 0; MqttMsgPubrec msg = new MqttMsgPubrec(); + if (protocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1_1) + { + // [v3.1.1] check flag bits + if ((fixedHeaderFirstByte & MSG_FLAG_BITS_MASK) != MQTT_MSG_PUBREC_FLAG_BITS) + throw new MqttClientException(MqttClientErrorCode.InvalidFlagBits); + } + // get remaining length and allocate buffer int remainingLength = MqttMsgBase.decodeRemainingLength(channel); buffer = new byte[remainingLength]; @@ -114,5 +109,17 @@ namespace uPLibrary.Networking.M2Mqtt.Messages return msg; } + + public override string ToString() + { +#if TRACE + return this.GetTraceString( + "PUBREC", + new object[] { "messageId" }, + new object[] { this.messageId }); +#else + return base.ToString(); +#endif + } } } diff --git a/M2Mqtt/Messages/MqttMsgPubrel.cs b/M2Mqtt/Messages/MqttMsgPubrel.cs index 25c10a2..e43ca91 100644 --- a/M2Mqtt/Messages/MqttMsgPubrel.cs +++ b/M2Mqtt/Messages/MqttMsgPubrel.cs @@ -1,21 +1,21 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ +using uPLibrary.Networking.M2Mqtt.Exceptions; + namespace uPLibrary.Networking.M2Mqtt.Messages { /// @@ -23,33 +23,17 @@ namespace uPLibrary.Networking.M2Mqtt.Messages /// public class MqttMsgPubrel : MqttMsgBase { - #region Properties... - - /// - /// Message identifier for the acknowledged publish message - /// - public ushort MessageId - { - get { return this.messageId; } - set { this.messageId = value; } - } - - #endregion - - // message identifier - private ushort messageId; - /// /// Constructor /// public MqttMsgPubrel() { this.type = MQTT_MSG_PUBREL_TYPE; - // PUBREL message use QoS Level 1 + // PUBREL message use QoS Level 1 (not "officially" in 3.1.1) this.qosLevel = QOS_LEVEL_AT_LEAST_ONCE; } - public override byte[] GetBytes() + public override byte[] GetBytes(byte protocolVersion) { int fixedHeaderSize = 0; int varHeaderSize = 0; @@ -79,10 +63,15 @@ namespace uPLibrary.Networking.M2Mqtt.Messages buffer = new byte[fixedHeaderSize + varHeaderSize + payloadSize]; // first fixed header byte - buffer[index] = (byte)((MQTT_MSG_PUBREL_TYPE << MSG_TYPE_OFFSET) | + if (protocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1_1) + buffer[index++] = (MQTT_MSG_PUBREL_TYPE << MSG_TYPE_OFFSET) | MQTT_MSG_PUBREL_FLAG_BITS; // [v.3.1.1] + else + { + buffer[index] = (byte)((MQTT_MSG_PUBREL_TYPE << MSG_TYPE_OFFSET) | (this.qosLevel << QOS_LEVEL_OFFSET)); - buffer[index] |= this.dupFlag ? (byte)(1 << DUP_FLAG_OFFSET) : (byte)0x00; - index++; + buffer[index] |= this.dupFlag ? (byte)(1 << DUP_FLAG_OFFSET) : (byte)0x00; + index++; + } // encode remaining length index = this.encodeRemainingLength(remainingLength, buffer, index); @@ -98,14 +87,22 @@ namespace uPLibrary.Networking.M2Mqtt.Messages /// Parse bytes for a PUBREL message /// /// First fixed header byte + /// Protocol Version /// Channel connected to the broker /// PUBREL message instance - public static MqttMsgPubrel Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + public static MqttMsgPubrel Parse(byte fixedHeaderFirstByte, byte protocolVersion, IMqttNetworkChannel channel) { byte[] buffer; int index = 0; MqttMsgPubrel msg = new MqttMsgPubrel(); + if (protocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1_1) + { + // [v3.1.1] check flag bits + if ((fixedHeaderFirstByte & MSG_FLAG_BITS_MASK) != MQTT_MSG_PUBREL_FLAG_BITS) + throw new MqttClientException(MqttClientErrorCode.InvalidFlagBits); + } + // get remaining length and allocate buffer int remainingLength = MqttMsgBase.decodeRemainingLength(channel); buffer = new byte[remainingLength]; @@ -113,10 +110,15 @@ namespace uPLibrary.Networking.M2Mqtt.Messages // read bytes from socket... channel.Receive(buffer); - // read QoS level from fixed header (would be QoS Level 1) - msg.qosLevel = (byte)((fixedHeaderFirstByte & QOS_LEVEL_MASK) >> QOS_LEVEL_OFFSET); - // read DUP flag from fixed header - msg.dupFlag = (((fixedHeaderFirstByte & DUP_FLAG_MASK) >> DUP_FLAG_OFFSET) == 0x01); + if (protocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1) + { + // only 3.1.0 + + // read QoS level from fixed header (would be QoS Level 1) + msg.qosLevel = (byte)((fixedHeaderFirstByte & QOS_LEVEL_MASK) >> QOS_LEVEL_OFFSET); + // read DUP flag from fixed header + msg.dupFlag = (((fixedHeaderFirstByte & DUP_FLAG_MASK) >> DUP_FLAG_OFFSET) == 0x01); + } // message id msg.messageId = (ushort)((buffer[index++] << 8) & 0xFF00); @@ -124,5 +126,17 @@ namespace uPLibrary.Networking.M2Mqtt.Messages return msg; } + + public override string ToString() + { +#if TRACE + return this.GetTraceString( + "PUBREL", + new object[] { "messageId" }, + new object[] { this.messageId }); +#else + return base.ToString(); +#endif + } } } diff --git a/M2Mqtt/Messages/MqttMsgSuback.cs b/M2Mqtt/Messages/MqttMsgSuback.cs index 5b847ff..6ecfd0d 100644 --- a/M2Mqtt/Messages/MqttMsgSuback.cs +++ b/M2Mqtt/Messages/MqttMsgSuback.cs @@ -1,22 +1,21 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ using System; +using uPLibrary.Networking.M2Mqtt.Exceptions; namespace uPLibrary.Networking.M2Mqtt.Messages { @@ -27,16 +26,6 @@ namespace uPLibrary.Networking.M2Mqtt.Messages { #region Properties... - /// - /// Message identifier for the subscribe message - /// that is acknowledged - /// - public ushort MessageId - { - get { return this.messageId; } - set { this.messageId = value; } - } - /// /// List of granted QOS Levels /// @@ -48,8 +37,6 @@ namespace uPLibrary.Networking.M2Mqtt.Messages #endregion - // message identifier - private ushort messageId; // granted QOS levels byte[] grantedQosLevels; @@ -65,14 +52,22 @@ namespace uPLibrary.Networking.M2Mqtt.Messages /// Parse bytes for a SUBACK message /// /// First fixed header byte + /// Protocol Version /// Channel connected to the broker /// SUBACK message instance - public static MqttMsgSuback Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + public static MqttMsgSuback Parse(byte fixedHeaderFirstByte, byte protocolVersion, IMqttNetworkChannel channel) { byte[] buffer; int index = 0; MqttMsgSuback msg = new MqttMsgSuback(); + if (protocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1_1) + { + // [v3.1.1] check flag bits + if ((fixedHeaderFirstByte & MSG_FLAG_BITS_MASK) != MQTT_MSG_SUBACK_FLAG_BITS) + throw new MqttClientException(MqttClientErrorCode.InvalidFlagBits); + } + // get remaining length and allocate buffer int remainingLength = MqttMsgBase.decodeRemainingLength(channel); buffer = new byte[remainingLength]; @@ -95,7 +90,7 @@ namespace uPLibrary.Networking.M2Mqtt.Messages return msg; } - public override byte[] GetBytes() + public override byte[] GetBytes(byte protocolVersion) { int fixedHeaderSize = 0; int varHeaderSize = 0; @@ -131,9 +126,11 @@ namespace uPLibrary.Networking.M2Mqtt.Messages buffer = new byte[fixedHeaderSize + varHeaderSize + payloadSize]; // first fixed header byte - buffer[index] = (byte)(MQTT_MSG_SUBACK_TYPE << MSG_TYPE_OFFSET); - index++; - + if (protocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1_1) + buffer[index++] = (MQTT_MSG_SUBACK_TYPE << MSG_TYPE_OFFSET) | MQTT_MSG_SUBACK_FLAG_BITS; // [v.3.1.1] + else + buffer[index++] = (byte)(MQTT_MSG_SUBACK_TYPE << MSG_TYPE_OFFSET); + // encode remaining length index = this.encodeRemainingLength(remainingLength, buffer, index); @@ -149,5 +146,17 @@ namespace uPLibrary.Networking.M2Mqtt.Messages return buffer; } + + public override string ToString() + { +#if TRACE + return this.GetTraceString( + "SUBACK", + new object[] { "messageId", "grantedQosLevels" }, + new object[] { this.messageId, this.grantedQosLevels }); +#else + return base.ToString(); +#endif + } } } diff --git a/M2Mqtt/Messages/MqttMsgSubscribe.cs b/M2Mqtt/Messages/MqttMsgSubscribe.cs index 478ee2e..0e3927e 100644 --- a/M2Mqtt/Messages/MqttMsgSubscribe.cs +++ b/M2Mqtt/Messages/MqttMsgSubscribe.cs @@ -1,19 +1,17 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ using System; @@ -52,24 +50,13 @@ namespace uPLibrary.Networking.M2Mqtt.Messages set { this.qosLevels = value; } } - /// - /// Message identifier - /// - public ushort MessageId - { - get { return this.messageId; } - set { this.messageId = value; } - } - #endregion // topics to subscribe string[] topics; // QOS levels related to topics byte[] qosLevels; - // message identifier - ushort messageId; - + /// /// Constructor /// @@ -90,7 +77,7 @@ namespace uPLibrary.Networking.M2Mqtt.Messages this.topics = topics; this.qosLevels = qosLevels; - // SUBSCRIBE message uses QoS Level 1 + // SUBSCRIBE message uses QoS Level 1 (not "officially" in 3.1.1) this.qosLevel = QOS_LEVEL_AT_LEAST_ONCE; } @@ -98,9 +85,10 @@ namespace uPLibrary.Networking.M2Mqtt.Messages /// Parse bytes for a SUBSCRIBE message /// /// First fixed header byte + /// Protocol Version /// Channel connected to the broker /// SUBSCRIBE message instance - public static MqttMsgSubscribe Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + public static MqttMsgSubscribe Parse(byte fixedHeaderFirstByte, byte protocolVersion, IMqttNetworkChannel channel) { byte[] buffer; int index = 0; @@ -108,6 +96,13 @@ namespace uPLibrary.Networking.M2Mqtt.Messages int topicUtf8Length; MqttMsgSubscribe msg = new MqttMsgSubscribe(); + if (protocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1_1) + { + // [v3.1.1] check flag bits + if ((fixedHeaderFirstByte & MSG_FLAG_BITS_MASK) != MQTT_MSG_SUBSCRIBE_FLAG_BITS) + throw new MqttClientException(MqttClientErrorCode.InvalidFlagBits); + } + // get remaining length and allocate buffer int remainingLength = MqttMsgBase.decodeRemainingLength(channel); buffer = new byte[remainingLength]; @@ -115,12 +110,17 @@ namespace uPLibrary.Networking.M2Mqtt.Messages // read bytes from socket... int received = channel.Receive(buffer); - // read QoS level from fixed header - msg.qosLevel = (byte)((fixedHeaderFirstByte & QOS_LEVEL_MASK) >> QOS_LEVEL_OFFSET); - // read DUP flag from fixed header - msg.dupFlag = (((fixedHeaderFirstByte & DUP_FLAG_MASK) >> DUP_FLAG_OFFSET) == 0x01); - // retain flag not used - msg.retain = false; + if (protocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1) + { + // only 3.1.0 + + // read QoS level from fixed header + msg.qosLevel = (byte)((fixedHeaderFirstByte & QOS_LEVEL_MASK) >> QOS_LEVEL_OFFSET); + // read DUP flag from fixed header + msg.dupFlag = (((fixedHeaderFirstByte & DUP_FLAG_MASK) >> DUP_FLAG_OFFSET) == 0x01); + // retain flag not used + msg.retain = false; + } // message id msg.messageId = (ushort)((buffer[index++] << 8) & 0xFF00); @@ -165,7 +165,7 @@ namespace uPLibrary.Networking.M2Mqtt.Messages return msg; } - public override byte[] GetBytes() + public override byte[] GetBytes(byte protocolVersion) { int fixedHeaderSize = 0; int varHeaderSize = 0; @@ -222,11 +222,16 @@ namespace uPLibrary.Networking.M2Mqtt.Messages buffer = new byte[fixedHeaderSize + varHeaderSize + payloadSize]; // first fixed header byte - buffer[index] = (byte)((MQTT_MSG_SUBSCRIBE_TYPE << MSG_TYPE_OFFSET) | + if (protocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1_1) + buffer[index++] = (MQTT_MSG_SUBSCRIBE_TYPE << MSG_TYPE_OFFSET) | MQTT_MSG_SUBSCRIBE_FLAG_BITS; // [v.3.1.1] + else + { + buffer[index] = (byte)((MQTT_MSG_SUBSCRIBE_TYPE << MSG_TYPE_OFFSET) | (this.qosLevel << QOS_LEVEL_OFFSET)); - buffer[index] |= this.dupFlag ? (byte)(1 << DUP_FLAG_OFFSET) : (byte)0x00; - index++; - + buffer[index] |= this.dupFlag ? (byte)(1 << DUP_FLAG_OFFSET) : (byte)0x00; + index++; + } + // encode remaining length index = this.encodeRemainingLength(remainingLength, buffer, index); @@ -251,5 +256,17 @@ namespace uPLibrary.Networking.M2Mqtt.Messages return buffer; } + + public override string ToString() + { +#if TRACE + return this.GetTraceString( + "SUBSCRIBE", + new object[] { "messageId", "topics", "qosLevels" }, + new object[] { this.messageId, this.topics, this.qosLevels }); +#else + return base.ToString(); +#endif + } } } diff --git a/M2Mqtt/Messages/MqttMsgSubscribeEventArgs.cs b/M2Mqtt/Messages/MqttMsgSubscribeEventArgs.cs index 92a5824..91c85f9 100644 --- a/M2Mqtt/Messages/MqttMsgSubscribeEventArgs.cs +++ b/M2Mqtt/Messages/MqttMsgSubscribeEventArgs.cs @@ -1,19 +1,17 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ #if (!MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3) diff --git a/M2Mqtt/Messages/MqttMsgSubscribedEventArgs.cs b/M2Mqtt/Messages/MqttMsgSubscribedEventArgs.cs index 3b6daab..58bbb22 100644 --- a/M2Mqtt/Messages/MqttMsgSubscribedEventArgs.cs +++ b/M2Mqtt/Messages/MqttMsgSubscribedEventArgs.cs @@ -1,19 +1,17 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ #if (!MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3) diff --git a/M2Mqtt/Messages/MqttMsgUnsuback.cs b/M2Mqtt/Messages/MqttMsgUnsuback.cs index 7c1a810..8e49a06 100644 --- a/M2Mqtt/Messages/MqttMsgUnsuback.cs +++ b/M2Mqtt/Messages/MqttMsgUnsuback.cs @@ -1,22 +1,21 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ using System; +using uPLibrary.Networking.M2Mqtt.Exceptions; namespace uPLibrary.Networking.M2Mqtt.Messages { @@ -25,23 +24,6 @@ namespace uPLibrary.Networking.M2Mqtt.Messages /// public class MqttMsgUnsuback : MqttMsgBase { - #region Properties... - - /// - /// Message identifier for the unsubscribe message - /// that is acknowledged - /// - public ushort MessageId - { - get { return this.messageId; } - set { this.messageId = value; } - } - - #endregion - - // message identifier - private ushort messageId; - /// /// Constructor /// @@ -54,14 +36,22 @@ namespace uPLibrary.Networking.M2Mqtt.Messages /// Parse bytes for a UNSUBACK message /// /// First fixed header byte + /// Protocol Version /// Channel connected to the broker /// UNSUBACK message instance - public static MqttMsgUnsuback Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + public static MqttMsgUnsuback Parse(byte fixedHeaderFirstByte, byte protocolVersion, IMqttNetworkChannel channel) { byte[] buffer; int index = 0; MqttMsgUnsuback msg = new MqttMsgUnsuback(); + if (protocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1_1) + { + // [v3.1.1] check flag bits + if ((fixedHeaderFirstByte & MSG_FLAG_BITS_MASK) != MQTT_MSG_UNSUBACK_FLAG_BITS) + throw new MqttClientException(MqttClientErrorCode.InvalidFlagBits); + } + // get remaining length and allocate buffer int remainingLength = MqttMsgBase.decodeRemainingLength(channel); buffer = new byte[remainingLength]; @@ -76,7 +66,7 @@ namespace uPLibrary.Networking.M2Mqtt.Messages return msg; } - public override byte[] GetBytes() + public override byte[] GetBytes(byte protocolVersion) { int fixedHeaderSize = 0; int varHeaderSize = 0; @@ -106,9 +96,11 @@ namespace uPLibrary.Networking.M2Mqtt.Messages buffer = new byte[fixedHeaderSize + varHeaderSize + payloadSize]; // first fixed header byte - buffer[index] = (byte)(MQTT_MSG_UNSUBACK_TYPE << MSG_TYPE_OFFSET); - index++; - + if (protocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1_1) + buffer[index++] = (MQTT_MSG_UNSUBACK_TYPE << MSG_TYPE_OFFSET) | MQTT_MSG_UNSUBACK_FLAG_BITS; // [v.3.1.1] + else + buffer[index++] = (byte)(MQTT_MSG_UNSUBACK_TYPE << MSG_TYPE_OFFSET); + // encode remaining length index = this.encodeRemainingLength(remainingLength, buffer, index); @@ -118,5 +110,17 @@ namespace uPLibrary.Networking.M2Mqtt.Messages return buffer; } + + public override string ToString() + { +#if TRACE + return this.GetTraceString( + "UNSUBACK", + new object[] { "messageId" }, + new object[] { this.messageId }); +#else + return base.ToString(); +#endif + } } } diff --git a/M2Mqtt/Messages/MqttMsgUnsubscribe.cs b/M2Mqtt/Messages/MqttMsgUnsubscribe.cs index b2f53c2..e485133 100644 --- a/M2Mqtt/Messages/MqttMsgUnsubscribe.cs +++ b/M2Mqtt/Messages/MqttMsgUnsubscribe.cs @@ -1,19 +1,17 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ using System; @@ -43,22 +41,11 @@ namespace uPLibrary.Networking.M2Mqtt.Messages set { this.topics = value; } } - /// - /// Message identifier - /// - public ushort MessageId - { - get { return this.messageId; } - set { this.messageId = value; } - } - #endregion // topics to unsubscribe string[] topics; - // message identifier - ushort messageId; - + /// /// Constructor /// @@ -77,7 +64,7 @@ namespace uPLibrary.Networking.M2Mqtt.Messages this.topics = topics; - // UNSUBSCRIBE message uses QoS Level 1 + // UNSUBSCRIBE message uses QoS Level 1 (not "officially" in 3.1.1) this.qosLevel = QOS_LEVEL_AT_LEAST_ONCE; } @@ -85,9 +72,10 @@ namespace uPLibrary.Networking.M2Mqtt.Messages /// Parse bytes for a UNSUBSCRIBE message /// /// First fixed header byte + /// Protocol Version /// Channel connected to the broker /// UNSUBSCRIBE message instance - public static MqttMsgUnsubscribe Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + public static MqttMsgUnsubscribe Parse(byte fixedHeaderFirstByte, byte protocolVersion, IMqttNetworkChannel channel) { byte[] buffer; int index = 0; @@ -95,6 +83,13 @@ namespace uPLibrary.Networking.M2Mqtt.Messages int topicUtf8Length; MqttMsgUnsubscribe msg = new MqttMsgUnsubscribe(); + if (protocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1_1) + { + // [v3.1.1] check flag bits + if ((fixedHeaderFirstByte & MSG_FLAG_BITS_MASK) != MQTT_MSG_UNSUBSCRIBE_FLAG_BITS) + throw new MqttClientException(MqttClientErrorCode.InvalidFlagBits); + } + // get remaining length and allocate buffer int remainingLength = MqttMsgBase.decodeRemainingLength(channel); buffer = new byte[remainingLength]; @@ -102,12 +97,17 @@ namespace uPLibrary.Networking.M2Mqtt.Messages // read bytes from socket... int received = channel.Receive(buffer); - // read QoS level from fixed header - msg.qosLevel = (byte)((fixedHeaderFirstByte & QOS_LEVEL_MASK) >> QOS_LEVEL_OFFSET); - // read DUP flag from fixed header - msg.dupFlag = (((fixedHeaderFirstByte & DUP_FLAG_MASK) >> DUP_FLAG_OFFSET) == 0x01); - // retain flag not used - msg.retain = false; + if (protocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1) + { + // only 3.1.0 + + // read QoS level from fixed header + msg.qosLevel = (byte)((fixedHeaderFirstByte & QOS_LEVEL_MASK) >> QOS_LEVEL_OFFSET); + // read DUP flag from fixed header + msg.dupFlag = (((fixedHeaderFirstByte & DUP_FLAG_MASK) >> DUP_FLAG_OFFSET) == 0x01); + // retain flag not used + msg.retain = false; + } // message id msg.messageId = (ushort)((buffer[index++] << 8) & 0xFF00); @@ -144,7 +144,7 @@ namespace uPLibrary.Networking.M2Mqtt.Messages return msg; } - public override byte[] GetBytes() + public override byte[] GetBytes(byte protocolVersion) { int fixedHeaderSize = 0; int varHeaderSize = 0; @@ -192,11 +192,16 @@ namespace uPLibrary.Networking.M2Mqtt.Messages buffer = new byte[fixedHeaderSize + varHeaderSize + payloadSize]; // first fixed header byte - buffer[index] = (byte)((MQTT_MSG_UNSUBSCRIBE_TYPE << MSG_TYPE_OFFSET) | + if (protocolVersion == MqttMsgConnect.PROTOCOL_VERSION_V3_1_1) + buffer[index++] = (MQTT_MSG_UNSUBSCRIBE_TYPE << MSG_TYPE_OFFSET) | MQTT_MSG_UNSUBSCRIBE_FLAG_BITS; // [v.3.1.1] + else + { + buffer[index] = (byte)((MQTT_MSG_UNSUBSCRIBE_TYPE << MSG_TYPE_OFFSET) | (this.qosLevel << QOS_LEVEL_OFFSET)); - buffer[index] |= this.dupFlag ? (byte)(1 << DUP_FLAG_OFFSET) : (byte)0x00; - index++; - + buffer[index] |= this.dupFlag ? (byte)(1 << DUP_FLAG_OFFSET) : (byte)0x00; + index++; + } + // encode remaining length index = this.encodeRemainingLength(remainingLength, buffer, index); @@ -218,5 +223,17 @@ namespace uPLibrary.Networking.M2Mqtt.Messages return buffer; } + + public override string ToString() + { +#if TRACE + return this.GetTraceString( + "UNSUBSCRIBE", + new object[] { "messageId", "topics" }, + new object[] { this.messageId, this.topics }); +#else + return base.ToString(); +#endif + } } } diff --git a/M2Mqtt/Messages/MqttMsgUnsubscribeEventArgs.cs b/M2Mqtt/Messages/MqttMsgUnsubscribeEventArgs.cs index 5f1dd74..e158783 100644 --- a/M2Mqtt/Messages/MqttMsgUnsubscribeEventArgs.cs +++ b/M2Mqtt/Messages/MqttMsgUnsubscribeEventArgs.cs @@ -1,19 +1,17 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ #if (!MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3) diff --git a/M2Mqtt/Messages/MqttMsgUnsubscribedEventArgs.cs b/M2Mqtt/Messages/MqttMsgUnsubscribedEventArgs.cs index 99d8791..e65c383 100644 --- a/M2Mqtt/Messages/MqttMsgUnsubscribedEventArgs.cs +++ b/M2Mqtt/Messages/MqttMsgUnsubscribedEventArgs.cs @@ -1,19 +1,17 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ #if (!MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3) diff --git a/M2Mqtt/MqttClient.cs b/M2Mqtt/MqttClient.cs index 29a6696..e5a2415 100644 --- a/M2Mqtt/MqttClient.cs +++ b/M2Mqtt/MqttClient.cs @@ -1,28 +1,31 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ using System; using System.Net; +#if !(WINDOWS_APP || WINDOWS_PHONE_APP) using System.Net.Sockets; +using System.Security.Cryptography.X509Certificates; +#endif using System.Threading; using uPLibrary.Networking.M2Mqtt.Exceptions; using uPLibrary.Networking.M2Mqtt.Messages; -using System.Collections; +using uPLibrary.Networking.M2Mqtt.Session; +using uPLibrary.Networking.M2Mqtt.Utility; +using uPLibrary.Networking.M2Mqtt.Internal; // if .Net Micro Framework #if (MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3) using Microsoft.SPOT; @@ -31,14 +34,24 @@ using Microsoft.SPOT.Net.Security; #endif // else other frameworks (.Net, .Net Compact, Mono, Windows Phone) #else - -#if (SSL && !WINDOWS_PHONE) +using System.Collections.Generic; +#if (SSL && !(WINDOWS_APP || WINDOWS_PHONE_APP)) using System.Security.Authentication; using System.Net.Security; #endif #endif -using System.Security.Cryptography.X509Certificates; +#if (WINDOWS_APP || WINDOWS_PHONE_APP) +using Windows.Networking.Sockets; +#endif + +using System.Collections; + +// alias needed due to Microsoft.SPOT.Trace in .Net Micro Framework +// (it's ambiguos with uPLibrary.Networking.M2Mqtt.Utility.Trace) +using MqttUtility = uPLibrary.Networking.M2Mqtt.Utility; +using System.IO; +using System.Net.Security; namespace uPLibrary.Networking.M2Mqtt { @@ -52,7 +65,7 @@ namespace uPLibrary.Networking.M2Mqtt // thread names private const string RECEIVE_THREAD_NAME = "ReceiveThread"; - private const string RECEIVE_EVENT_THREAD_NAME = "ReceiveEventThread"; + private const string RECEIVE_EVENT_THREAD_NAME = "DispatchEventThread"; private const string PROCESS_INFLIGHT_THREAD_NAME = "ProcessInflightThread"; private const string KEEP_ALIVE_THREAD = "KeepAliveThread"; @@ -94,33 +107,27 @@ namespace uPLibrary.Networking.M2Mqtt /// Delagate that defines event handler for CONNECT message received /// public delegate void MqttMsgConnectEventHandler(object sender, MqttMsgConnectEventArgs e); -#endif /// /// Delegate that defines event handler for client disconnection (DISCONNECT message or not) /// public delegate void MqttMsgDisconnectEventHandler(object sender, EventArgs e); +#endif - // CA certificate - private X509Certificate caCert; + /// + /// Delegate that defines event handler for cliet/peer disconnection + /// + public delegate void ConnectionClosedEventHandler(object sender, EventArgs e); - // broker hostname, ip address and port + // broker hostname (or ip address) and port private string brokerHostName; - private IPAddress brokerIpAddress; private int brokerPort; - // using SSL - private bool secure; - // thread for receiving incoming message - private Thread receiveThread; - // thread for raising received message event - private Thread receiveEventThread; + // running status of threads private bool isRunning; // event for raising received message event private AutoResetEvent receiveEventWaitHandle; - // thread for handling inflight messages queue asynchronously - private Thread processInflightThread; // event for starting process inflight queue asynchronously private AutoResetEvent inflightWaitHandle; @@ -134,13 +141,11 @@ namespace uPLibrary.Networking.M2Mqtt // keep alive period (in ms) private int keepAlivePeriod; - // thread for sending keep alive message - private Thread keepAliveThread; + // events for signaling on keep alive thread private AutoResetEvent keepAliveEvent; - // keep alive timeout expired - private bool isKeepAliveTimeout; + private AutoResetEvent keepAliveEventEnd; // last communication time in ticks - private long lastCommTime; + private int lastCommTime; // event for PUBLISH message received public event MqttMsgPublishEventHandler MqttMsgPublishReceived; @@ -157,10 +162,13 @@ namespace uPLibrary.Networking.M2Mqtt public event MqttMsgUnsubscribeEventHandler MqttMsgUnsubscribeReceived; // event for CONNECT message received public event MqttMsgConnectEventHandler MqttMsgConnected; -#endif - // event for client disconnection (DISCONNECT message or not) + // event for DISCONNECT message received public event MqttMsgDisconnectEventHandler MqttMsgDisconnected; +#endif + // event for peer/client disconnection + public event ConnectionClosedEventHandler ConnectionClosed; + // channel to communicate over the network private IMqttNetworkChannel channel; @@ -168,8 +176,10 @@ namespace uPLibrary.Networking.M2Mqtt private Queue inflightQueue; // internal queue for received messages about inflight messages private Queue internalQueue; - // receive queue for received messages - private Queue receiveQueue; + // internal queue for dispatching events + private Queue eventQueue; + // session + private MqttClientSession session; // reference to avoid access to singleton via property private MqttSettings settings; @@ -177,6 +187,9 @@ namespace uPLibrary.Networking.M2Mqtt // current message identifier generated private ushort messageIdCounter = 0; + // connection is closing due to peer + private bool isConnectionClosing; + /// /// Connection status between client and broker /// @@ -212,12 +225,38 @@ namespace uPLibrary.Networking.M2Mqtt /// public string WillMessage { get; private set; } + /// + /// MQTT protocol version + /// + public MqttProtocolVersion ProtocolVersion { get; set; } + +#if BROKER + /// + /// MQTT Client Session + /// + public MqttClientSession Session + { + get { return this.session; } + set { this.session = value; } + } +#endif + + /// + /// MQTT client settings + /// + public MqttSettings Settings + { + get { return this.settings; } + } + +#if !(WINDOWS_APP || WINDOWS_PHONE_APP) /// /// Constructor /// /// Broker IP address + [Obsolete("Use this ctor MqttClient(string brokerHostName) insted")] public MqttClient(IPAddress brokerIpAddress) : - this(brokerIpAddress, MqttSettings.MQTT_BROKER_DEFAULT_PORT, false, null) + this(brokerIpAddress, MqttSettings.MQTT_BROKER_DEFAULT_PORT, false, null, null, MqttSslProtocols.None) { } @@ -228,57 +267,121 @@ namespace uPLibrary.Networking.M2Mqtt /// Broker port /// Using secure connection /// CA certificate for secure connection - public MqttClient(IPAddress brokerIpAddress, int brokerPort, bool secure, X509Certificate caCert) + /// Client certificate + /// SSL/TLS protocol version + [Obsolete("Use this ctor MqttClient(string brokerHostName, int brokerPort, bool secure, X509Certificate caCert) insted")] + public MqttClient(IPAddress brokerIpAddress, int brokerPort, bool secure, X509Certificate caCert, X509Certificate clientCert, MqttSslProtocols sslProtocol) { - this.Init(null, brokerIpAddress, brokerPort, secure, caCert); +#if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK) + this.Init(brokerIpAddress.ToString(), brokerPort, secure, caCert, clientCert, sslProtocol, null, null); +#else + this.Init(brokerIpAddress.ToString(), brokerPort, secure, caCert, clientCert, sslProtocol); +#endif } +#endif /// /// Constructor /// - /// Broker Host Name + /// Broker Host Name or IP Address public MqttClient(string brokerHostName) : - this(brokerHostName, MqttSettings.MQTT_BROKER_DEFAULT_PORT, false, null) +#if !(WINDOWS_APP || WINDOWS_PHONE_APP) + this(brokerHostName, MqttSettings.MQTT_BROKER_DEFAULT_PORT, false, null, null, MqttSslProtocols.None) +#else + this(brokerHostName, MqttSettings.MQTT_BROKER_DEFAULT_PORT, false, MqttSslProtocols.None) +#endif { } /// /// Constructor /// - /// Broker Host Name + /// Broker Host Name or IP Address + /// Broker port + /// Using secure connection + /// SSL/TLS protocol version +#if !(WINDOWS_APP || WINDOWS_PHONE_APP) + /// CA certificate for secure connection + /// Client certificate + public MqttClient(string brokerHostName, int brokerPort, bool secure, X509Certificate caCert, X509Certificate clientCert, MqttSslProtocols sslProtocol) +#else + public MqttClient(string brokerHostName, int brokerPort, bool secure, MqttSslProtocols sslProtocol) +#endif + { +#if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK || WINDOWS_APP || WINDOWS_PHONE_APP) + this.Init(brokerHostName, brokerPort, secure, caCert, clientCert, sslProtocol, null, null); +#elif (WINDOWS_APP || WINDOWS_PHONE_APP) + this.Init(brokerHostName, brokerPort, secure, sslProtocol); +#else + this.Init(brokerHostName, brokerPort, secure, caCert, clientCert, sslProtocol); +#endif + } + + +#if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK || WINDOWS_APP || WINDOWS_PHONE_APP) + + /// + /// Constructor + /// + /// Broker Host Name or IP Address /// Broker port /// Using secure connection /// CA certificate for secure connection - public MqttClient(string brokerHostName, int brokerPort = MqttSettings.MQTT_BROKER_DEFAULT_PORT, bool secure = false, X509Certificate caCert = null, bool skipIdAdressResolution = false) + /// Client certificate + /// SSL/TLS protocol version + /// A RemoteCertificateValidationCallback delegate responsible for validating the certificate supplied by the remote party + public MqttClient(string brokerHostName, int brokerPort, bool secure, X509Certificate caCert, X509Certificate clientCert, MqttSslProtocols sslProtocol, + RemoteCertificateValidationCallback userCertificateValidationCallback) + : this(brokerHostName, brokerPort, secure, caCert, clientCert, sslProtocol, userCertificateValidationCallback, null) { - if (skipIdAdressResolution) - { - this.Init(brokerHostName, null, brokerPort, secure, caCert); - return; - } - // throw exceptions to the caller - IPHostEntry hostEntry = Dns.GetHostEntry(brokerHostName); - - if ((hostEntry != null) && (hostEntry.AddressList.Length > 0)) - { - // check for the first address not null - // it seems that with .Net Micro Framework, the IPV6 addresses aren't supported and return "null" - int i = 0; - while (hostEntry.AddressList[i] == null) i++; - this.Init(brokerHostName, hostEntry.AddressList[i], brokerPort, secure, caCert); - } - else - throw new ApplicationException("No address found for the broker"); } + /// + /// Constructor + /// + /// Broker Host Name or IP Address + /// Broker port + /// Using secure connection + /// SSL/TLS protocol version + /// A RemoteCertificateValidationCallback delegate responsible for validating the certificate supplied by the remote party + /// A LocalCertificateSelectionCallback delegate responsible for selecting the certificate used for authentication + public MqttClient(string brokerHostName, int brokerPort, bool secure, MqttSslProtocols sslProtocol, + RemoteCertificateValidationCallback userCertificateValidationCallback, + LocalCertificateSelectionCallback userCertificateSelectionCallback) + : this(brokerHostName, brokerPort, secure, null, null, sslProtocol, userCertificateValidationCallback, userCertificateSelectionCallback) + { + } + + /// + /// Constructor + /// + /// Broker Host Name or IP Address + /// Broker port + /// Using secure connection + /// CA certificate for secure connection + /// Client certificate + /// SSL/TLS protocol version + /// A RemoteCertificateValidationCallback delegate responsible for validating the certificate supplied by the remote party + /// A LocalCertificateSelectionCallback delegate responsible for selecting the certificate used for authentication + public MqttClient(string brokerHostName, int brokerPort, bool secure, X509Certificate caCert, X509Certificate clientCert, MqttSslProtocols sslProtocol, + RemoteCertificateValidationCallback userCertificateValidationCallback, + LocalCertificateSelectionCallback userCertificateSelectionCallback) + { + this.Init(brokerHostName, brokerPort, secure, caCert, clientCert, sslProtocol, userCertificateValidationCallback, userCertificateSelectionCallback); + } +#endif + #if BROKER /// /// Constructor /// - /// Raw socket for communication - public MqttClient(Socket socket) + /// Network channel for communication + public MqttClient(IMqttNetworkChannel channel) { - this.channel = new MqttNetworkChannel(socket); + // set default MQTT protocol version (default is 3.1.1) + this.ProtocolVersion = MqttProtocolVersion.Version_3_1_1; + + this.channel = channel; // reference to MQTT settings this.settings = MqttSettings.Instance; @@ -296,49 +399,53 @@ namespace uPLibrary.Networking.M2Mqtt // queue for received message this.receiveEventWaitHandle = new AutoResetEvent(false); - this.receiveQueue = new Queue(); + this.eventQueue = new Queue(); this.internalQueue = new Queue(); + + // session + this.session = null; } #endif /// /// MqttClient initialization /// - /// Broker host name - /// Broker IP address + /// Broker Host Name or IP Address /// Broker port /// >Using secure connection /// CA certificate for secure connection - private void Init(string brokerHostName, IPAddress brokerIpAddress, int brokerPort, bool secure, X509Certificate caCert) - { -#if SSL - // check security parameters - if ((secure) && (caCert == null)) - throw new ArgumentException("Secure requested but CA certificate is null !"); + /// Client certificate + /// SSL/TLS protocol version +#if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK || WINDOWS_APP || WINDOWS_PHONE_APP) + /// A RemoteCertificateValidationCallback delegate responsible for validating the certificate supplied by the remote party + /// A LocalCertificateSelectionCallback delegate responsible for selecting the certificate used for authentication + private void Init(string brokerHostName, int brokerPort, bool secure, X509Certificate caCert, X509Certificate clientCert, MqttSslProtocols sslProtocol, + RemoteCertificateValidationCallback userCertificateValidationCallback, + LocalCertificateSelectionCallback userCertificateSelectionCallback) +#elif (WINDOWS_APP || WINDOWS_PHONE_APP) + private void Init(string brokerHostName, int brokerPort, bool secure, MqttSslProtocols sslProtocol) #else + private void Init(string brokerHostName, int brokerPort, bool secure, X509Certificate caCert, X509Certificate clientCert, MqttSslProtocols sslProtocol) +#endif + { + // set default MQTT protocol version (default is 3.1.1) + this.ProtocolVersion = MqttProtocolVersion.Version_3_1_1; +#if !SSL + // check security parameters if (secure) throw new ArgumentException("Library compiled without SSL support"); #endif this.brokerHostName = brokerHostName; - // if broker hostname is null, set ip address - if (this.brokerHostName == null) - this.brokerHostName = brokerIpAddress.ToString(); - - this.brokerIpAddress = brokerIpAddress; this.brokerPort = brokerPort; - this.secure = secure; - -#if SSL - // if secure, load CA certificate - if (this.secure) - { - this.caCert = caCert; - } -#endif // reference to MQTT settings this.settings = MqttSettings.Instance; + // set settings port based on secure connection or not + if (!secure) + this.settings.Port = this.brokerPort; + else + this.settings.SslPort = this.brokerPort; this.syncEndReceiving = new AutoResetEvent(false); this.keepAliveEvent = new AutoResetEvent(false); @@ -349,8 +456,20 @@ namespace uPLibrary.Networking.M2Mqtt // queue for received message this.receiveEventWaitHandle = new AutoResetEvent(false); - this.receiveQueue = new Queue(); + this.eventQueue = new Queue(); this.internalQueue = new Queue(); + + // session + this.session = null; + + // create network channel +#if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK || WINDOWS_APP || WINDOWS_PHONE_APP) + this.channel = new MqttNetworkChannel(this.brokerHostName, this.brokerPort, secure, caCert, clientCert, sslProtocol, userCertificateValidationCallback, userCertificateSelectionCallback); +#elif (WINDOWS_APP || WINDOWS_PHONE_APP) + this.channel = new MqttNetworkChannel(this.brokerHostName, this.brokerPort, secure, sslProtocol); +#else + this.channel = new MqttNetworkChannel(this.brokerHostName, this.brokerPort, secure, caCert, clientCert, sslProtocol); +#endif } /// @@ -430,16 +549,12 @@ namespace uPLibrary.Networking.M2Mqtt willTopic, willMessage, cleanSession, - keepAlivePeriod); + keepAlivePeriod, + (byte)this.ProtocolVersion); try { - // create network channel and connect to broker -#if WINDOWS_PHONE - this.channel = new WPMqttNetworkChannel(this.brokerHostName, this.brokerIpAddress, this.brokerPort, this.secure, this.caCert); -#else - this.channel = new MqttNetworkChannel(this.brokerHostName, this.brokerIpAddress, this.brokerPort, this.secure, this.caCert); -#endif + // connect to the broker this.channel.Connect(); } catch (Exception ex) @@ -449,11 +564,11 @@ namespace uPLibrary.Networking.M2Mqtt this.lastCommTime = 0; this.isRunning = true; + this.isConnectionClosing = false; // start thread for receiving messages from broker - this.receiveThread = new Thread(this.ReceiveThread); - this.receiveThread.Start(); - - MqttMsgConnack connack = (MqttMsgConnack)this.SendReceive(connect.GetBytes()); + Fx.StartThread(this.ReceiveThread); + + MqttMsgConnack connack = (MqttMsgConnack)this.SendReceive(connect); // if connection accepted, start keep alive timer and if (connack.ReturnCode == MqttMsgConnack.CONN_ACCEPTED) { @@ -467,17 +582,21 @@ namespace uPLibrary.Networking.M2Mqtt this.keepAlivePeriod = keepAlivePeriod * 1000; // convert in ms - // start thread for sending keep alive message to the broker - this.keepAliveThread = new Thread(this.KeepAliveThread); - this.keepAliveThread.Start(); + // restore previous session + this.RestoreSession(); + + // keep alive period equals zero means turning off keep alive mechanism + if (this.keepAlivePeriod != 0) + { + // start thread for sending keep alive message to the broker + Fx.StartThread(this.KeepAliveThread); + } // start thread for raising received message event from broker - this.receiveEventThread = new Thread(this.ReceiveEventThread); - this.receiveEventThread.Start(); - + Fx.StartThread(this.DispatchEventThread); + // start thread for handling inflight messages queue to broker asynchronously (publish and acknowledge) - this.processInflightThread = new Thread(this.ProcessInflightThread); - this.processInflightThread.Start(); + Fx.StartThread(this.ProcessInflightThread); this.IsConnected = true; } @@ -490,10 +609,10 @@ namespace uPLibrary.Networking.M2Mqtt public void Disconnect() { MqttMsgDisconnect disconnect = new MqttMsgDisconnect(); - this.Send(disconnect.GetBytes()); + this.Send(disconnect); // close client - this.Close(); + this.OnConnectionClosing(); } #if BROKER @@ -505,19 +624,13 @@ namespace uPLibrary.Networking.M2Mqtt this.isRunning = true; // start thread for receiving messages from client - this.receiveThread = new Thread(this.ReceiveThread); - this.receiveThread.Name = RECEIVE_THREAD_NAME; - this.receiveThread.Start(); + Fx.StartThread(this.ReceiveThread); // start thread for raising received message event from client - this.receiveEventThread = new Thread(this.ReceiveEventThread); - this.receiveEventThread.Name = RECEIVE_EVENT_THREAD_NAME; - this.receiveEventThread.Start(); + Fx.StartThread(this.DispatchEventThread); // start thread for handling inflight messages queue to client asynchronously (publish and acknowledge) - this.processInflightThread = new Thread(this.ProcessInflightThread); - this.processInflightThread.Name = PROCESS_INFLIGHT_THREAD_NAME; - this.processInflightThread.Start(); + Fx.StartThread(this.ProcessInflightThread); } #endif @@ -533,50 +646,34 @@ namespace uPLibrary.Networking.M2Mqtt // stop receiving thread this.isRunning = false; - // wait end receive thread - //if (this.receiveThread != null) - // this.receiveThread.Join(); - // wait end receive event thread - if (this.receiveEventThread != null) - { + if (this.receiveEventWaitHandle != null) this.receiveEventWaitHandle.Set(); - // NOTE : no join because Close() could be called inside ReceiveEventThread - // so we have to avoid deadlock - //this.receiveEventThread.Join(); - } - // waint end process inflight thread - if (this.processInflightThread != null) - { + // wait end process inflight thread + if (this.inflightWaitHandle != null) this.inflightWaitHandle.Set(); - // NOTE : no join because Close() could be called inside ProcessInflightThread - // so we have to avoid deadlock - //this.processInflightThread.Join(); - } - // avoid deadlock if keep alive timeout expired - if (!this.isKeepAliveTimeout) - { #if BROKER - // unlock keep alive thread and wait - if (this.keepAliveThread != null) - this.keepAliveEvent.Set(); + // unlock keep alive thread + this.keepAliveEvent.Set(); #else - // unlock keep alive thread and wait - this.keepAliveEvent.Set(); + // unlock keep alive thread and wait + this.keepAliveEvent.Set(); - if (this.keepAliveThread != null) - this.keepAliveThread.Join(); + if (this.keepAliveEventEnd != null) + this.keepAliveEventEnd.WaitOne(); #endif - } + + // clear all queues + this.inflightQueue.Clear(); + this.internalQueue.Clear(); + this.eventQueue.Clear(); // close network channel this.channel.Close(); - // keep alive thread will set it gracefully - if (!this.isKeepAliveTimeout) - this.IsConnected = false; + this.IsConnected = false; } /// @@ -589,13 +686,16 @@ namespace uPLibrary.Networking.M2Mqtt try { // broker must send PINGRESP within timeout equal to keep alive period - return (MqttMsgPingResp)this.SendReceive(pingreq.GetBytes(), this.keepAlivePeriod); + return (MqttMsgPingResp)this.SendReceive(pingreq, this.keepAlivePeriod); } - catch (Exception) + catch (Exception e) { - this.isKeepAliveTimeout = true; +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Error, "Exception occurred: {0}", e.ToString()); +#endif + // client must close connection - this.Close(); + this.OnConnectionClosing(); return null; } } @@ -604,22 +704,29 @@ namespace uPLibrary.Networking.M2Mqtt /// /// Send CONNACK message to the client (connection accepted or not) /// - /// Return code for CONNACK message /// CONNECT message with all client information - public void Connack(byte returnCode, MqttMsgConnect connect) + /// Return code for CONNACK message + /// If not null, client id assigned by broker + /// Session present on the broker + public void Connack(MqttMsgConnect connect, byte returnCode, string clientId, bool sessionPresent) { this.lastCommTime = 0; // create CONNACK message and ... MqttMsgConnack connack = new MqttMsgConnack(); connack.ReturnCode = returnCode; + // [v3.1.1] session present flag + if (this.ProtocolVersion == MqttProtocolVersion.Version_3_1_1) + connack.SessionPresent = sessionPresent; // ... send it to the client - this.Send(connack.GetBytes()); + this.Send(connack); // connection accepted, start keep alive thread checking if (connack.ReturnCode == MqttMsgConnack.CONN_ACCEPTED) { - this.ClientId = connect.ClientId; + // [v3.1.1] if client id isn't null, the CONNECT message has a cliend id with zero bytes length + // and broker assigned a unique identifier to the client + this.ClientId = (clientId == null) ? connect.ClientId : clientId; this.CleanSession = connect.CleanSession; this.WillFlag = connect.WillFlag; this.WillTopic = connect.WillTopic; @@ -631,10 +738,9 @@ namespace uPLibrary.Networking.M2Mqtt this.keepAlivePeriod += (this.keepAlivePeriod / 2); // start thread for checking keep alive period timeout - this.keepAliveThread = new Thread(this.KeepAliveThread); - this.keepAliveThread.Name = KEEP_ALIVE_THREAD; - this.keepAliveThread.Start(); + Fx.StartThread(this.KeepAliveThread); + this.isConnectionClosing = false; this.IsConnected = true; } // connection refused, close TCP/IP channel @@ -655,7 +761,7 @@ namespace uPLibrary.Networking.M2Mqtt suback.MessageId = messageId; suback.GrantedQoSLevels = grantedQosLevels; - this.Send(suback.GetBytes()); + this.Send(suback); } /// @@ -667,7 +773,7 @@ namespace uPLibrary.Networking.M2Mqtt MqttMsgUnsuback unsuback = new MqttMsgUnsuback(); unsuback.MessageId = messageId; - this.Send(unsuback.GetBytes()); + this.Send(unsuback); } #endif @@ -689,11 +795,6 @@ namespace uPLibrary.Networking.M2Mqtt return subscribe.MessageId; } - public ushort Subscribe(string topic, byte qos) - { - return Subscribe(new[] { topic }, new[] { qos }); - } - /// /// Unsubscribe for message topics /// @@ -737,25 +838,42 @@ namespace uPLibrary.Networking.M2Mqtt publish.MessageId = this.GetMessageId(); // enqueue message to publish into the inflight queue - this.EnqueueInflight(publish, MqttMsgFlow.ToPublish); + bool enqueue = this.EnqueueInflight(publish, MqttMsgFlow.ToPublish); - return publish.MessageId; + // message enqueued + if (enqueue) + return publish.MessageId; + // infligh queue full, message not enqueued + else + throw new MqttClientException(MqttClientErrorCode.InflightQueueFull); } /// - /// Wrapper method for raising message received event + /// Wrapper method for raising events /// - /// Message received - private void OnMqttMsgReceived(MqttMsgBase msg) + /// Internal event + private void OnInternalEvent(InternalEvent internalEvent) { - lock (this.receiveQueue) + lock (this.eventQueue) { - this.receiveQueue.Enqueue(msg); + this.eventQueue.Enqueue(internalEvent); } this.receiveEventWaitHandle.Set(); } + /// + /// Wrapper method for raising closing connection event + /// + private void OnConnectionClosing() + { + if (!this.isConnectionClosing) + { + this.isConnectionClosing = true; + this.receiveEventWaitHandle.Set(); + } + } + /// /// Wrapper method for raising PUBLISH message received event /// @@ -773,12 +891,13 @@ namespace uPLibrary.Networking.M2Mqtt /// Wrapper method for raising published message event /// /// Message identifier for published message - private void OnMqttMsgPublished(ushort messageId) + /// Publish flag + private void OnMqttMsgPublished(ushort messageId, bool isPublished) { if (this.MqttMsgPublished != null) { this.MqttMsgPublished(this, - new MqttMsgPublishedEventArgs(messageId)); + new MqttMsgPublishedEventArgs(messageId, isPublished)); } } @@ -839,19 +958,19 @@ namespace uPLibrary.Networking.M2Mqtt } /// - /// Wrapper method for client connection event + /// Wrapper method for raising CONNECT message event /// private void OnMqttMsgConnected(MqttMsgConnect connect) { if (this.MqttMsgConnected != null) { + this.ProtocolVersion = (MqttProtocolVersion)connect.ProtocolVersion; this.MqttMsgConnected(this, new MqttMsgConnectEventArgs(connect)); } } -#endif /// - /// Wrapper method for client disconnection event + /// Wrapper method for raising DISCONNECT message event /// private void OnMqttMsgDisconnected() { @@ -860,6 +979,18 @@ namespace uPLibrary.Networking.M2Mqtt this.MqttMsgDisconnected(this, EventArgs.Empty); } } +#endif + + /// + /// Wrapper method for peer/client disconnection + /// + private void OnConnectionClosed() + { + if (this.ConnectionClosed != null) + { + this.ConnectionClosed(this, EventArgs.Empty); + } + } /// /// Send a message @@ -879,10 +1010,26 @@ namespace uPLibrary.Networking.M2Mqtt } catch (Exception e) { +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Error, "Exception occurred: {0}", e.ToString()); +#endif + throw new MqttCommunicationException(e); } } + /// + /// Send a message + /// + /// Message + private void Send(MqttMsgBase msg) + { +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Frame, "SEND {0}", msg); +#endif + this.Send(msg.GetBytes((byte)this.ProtocolVersion)); + } + /// /// Send a message to the broker and wait answer /// @@ -911,12 +1058,18 @@ namespace uPLibrary.Networking.M2Mqtt // update last message sent ticks this.lastCommTime = Environment.TickCount; } - catch (SocketException e) + catch (Exception e) { -#if (!MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3 && !COMPACT_FRAMEWORK) - // connection reset by broker - if (e.SocketErrorCode == SocketError.ConnectionReset) - this.IsConnected = false; +#if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK || WINDOWS_APP || WINDOWS_PHONE_APP) + if (typeof(SocketException) == e.GetType()) + { + // connection reset by broker + if (((SocketException)e).SocketErrorCode == SocketError.ConnectionReset) + this.IsConnected = false; + } +#endif +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Error, "Exception occurred: {0}", e.ToString()); #endif throw new MqttCommunicationException(e); @@ -940,17 +1093,41 @@ namespace uPLibrary.Networking.M2Mqtt else { // throw timeout exception - //throw new MqttTimeoutException(); throw new MqttCommunicationException(); } } + /// + /// Send a message to the broker and wait answer + /// + /// Message + /// MQTT message response + private MqttMsgBase SendReceive(MqttMsgBase msg) + { + return this.SendReceive(msg, MqttSettings.MQTT_DEFAULT_TIMEOUT); + } + + /// + /// Send a message to the broker and wait answer + /// + /// Message + /// Timeout for receiving answer + /// MQTT message response + private MqttMsgBase SendReceive(MqttMsgBase msg, int timeout) + { +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Frame, "SEND {0}", msg); +#endif + return this.SendReceive(msg.GetBytes((byte)this.ProtocolVersion), timeout); + } + /// /// Enqueue a message into the inflight queue /// /// Message to enqueue /// Message flow (publish, acknowledge) - private void EnqueueInflight(MqttMsgBase msg, MqttMsgFlow flow) + /// Message enqueued or not + private bool EnqueueInflight(MqttMsgBase msg, MqttMsgFlow flow) { // enqueue is needed (or not) bool enqueue = true; @@ -966,8 +1143,8 @@ namespace uPLibrary.Networking.M2Mqtt // NOTE : I need to find on message id and flow because the broker could be publish/received // to/from client and message id could be the same (one tracked by broker and the other by client) - MqttMsgContextFinder msgCtxFinder = new MqttMsgContextFinder(((MqttMsgPublish)msg).MessageId, MqttMsgFlow.ToAcknowledge); - MqttMsgContext msgCtx = (MqttMsgContext)QueueExtension.Get(this.inflightQueue, msgCtxFinder.Find); + MqttMsgContextFinder msgCtxFinder = new MqttMsgContextFinder(msg.MessageId, MqttMsgFlow.ToAcknowledge); + MqttMsgContext msgCtx = (MqttMsgContext) QueueExtension.Get(this.inflightQueue, msgCtxFinder.Find); // the PUBLISH message is alredy in the inflight queue, we don't need to re-enqueue but we need // to change state to re-send PUBREC @@ -1007,6 +1184,13 @@ namespace uPLibrary.Networking.M2Mqtt break; } + // [v3.1.1] SUBSCRIBE and UNSUBSCRIBE aren't "officially" QOS = 1 + // so QueuedQos1 state isn't valid for them + if (msg.Type == MqttMsgBase.MQTT_MSG_SUBSCRIBE_TYPE) + state = MqttMsgState.SendSubscribe; + else if (msg.Type == MqttMsgBase.MQTT_MSG_UNSUBSCRIBE_TYPE) + state = MqttMsgState.SendUnsubscribe; + // queue message context MqttMsgContext msgContext = new MqttMsgContext() { @@ -1018,12 +1202,44 @@ namespace uPLibrary.Networking.M2Mqtt lock (this.inflightQueue) { - // enqueue message and unlock send thread - this.inflightQueue.Enqueue(msgContext); + // check number of messages inside inflight queue + enqueue = (this.inflightQueue.Count < this.settings.InflightQueueSize); + + if (enqueue) + { + // enqueue message and unlock send thread + this.inflightQueue.Enqueue(msgContext); + +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Queuing, "enqueued {0}", msg); +#endif + + // PUBLISH message + if (msg.Type == MqttMsgBase.MQTT_MSG_PUBLISH_TYPE) + { + // to publish and QoS level 1 or 2 + if ((msgContext.Flow == MqttMsgFlow.ToPublish) && + ((msg.QosLevel == MqttMsgBase.QOS_LEVEL_AT_LEAST_ONCE) || + (msg.QosLevel == MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE))) + { + if (this.session != null) + this.session.InflightMessages.Add(msgContext.Key, msgContext); + } + // to acknowledge and QoS level 2 + else if ((msgContext.Flow == MqttMsgFlow.ToAcknowledge) && + (msg.QosLevel == MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE)) + { + if (this.session != null) + this.session.InflightMessages.Add(msgContext.Key, msgContext); + } + } + } } } this.inflightWaitHandle.Set(); + + return enqueue; } /// @@ -1046,28 +1262,73 @@ namespace uPLibrary.Networking.M2Mqtt // NOTE : I need to find on message id and flow because the broker could be publish/received // to/from client and message id could be the same (one tracked by broker and the other by client) - MqttMsgContextFinder msgCtxFinder = new MqttMsgContextFinder(((MqttMsgPubrel)msg).MessageId, MqttMsgFlow.ToAcknowledge); - MqttMsgContext msgCtx = (MqttMsgContext)QueueExtension.Get(this.inflightQueue, msgCtxFinder.Find); + MqttMsgContextFinder msgCtxFinder = new MqttMsgContextFinder(msg.MessageId, MqttMsgFlow.ToAcknowledge); + MqttMsgContext msgCtx = (MqttMsgContext) QueueExtension.Get(this.inflightQueue,msgCtxFinder.Find); // the PUBLISH message isn't in the inflight queue, it was already processed so // we need to re-send PUBCOMP only if (msgCtx == null) { MqttMsgPubcomp pubcomp = new MqttMsgPubcomp(); - pubcomp.MessageId = ((MqttMsgPubrel)msg).MessageId; + pubcomp.MessageId = msg.MessageId; - this.Send(pubcomp.GetBytes()); + this.Send(pubcomp); enqueue = false; } } } + // if it is a PUBCOMP message (for QoS Level 2) + else if (msg.Type == MqttMsgBase.MQTT_MSG_PUBCOMP_TYPE) + { + lock (this.inflightQueue) + { + // if it is a PUBCOMP but the corresponding PUBLISH isn't in the inflight queue, + // it means that we sent PUBLISH message, sent PUBREL (after receiving PUBREC) and already received PUBCOMP + // but publisher didn't receive PUBREL so it re-sent PUBCOMP. We need only to ignore this PUBCOMP. + + // NOTE : I need to find on message id and flow because the broker could be publish/received + // to/from client and message id could be the same (one tracked by broker and the other by client) + MqttMsgContextFinder msgCtxFinder = new MqttMsgContextFinder(msg.MessageId, MqttMsgFlow.ToPublish); + MqttMsgContext msgCtx = (MqttMsgContext) QueueExtension.Get(this.inflightQueue, msgCtxFinder.Find); + + // the PUBLISH message isn't in the inflight queue, it was already sent so we need to ignore this PUBCOMP + if (msgCtx == null) + { + enqueue = false; + } + } + } + // if it is a PUBREC message (for QoS Level 2) + else if (msg.Type == MqttMsgBase.MQTT_MSG_PUBREC_TYPE) + { + lock (this.inflightQueue) + { + // if it is a PUBREC but the corresponding PUBLISH isn't in the inflight queue, + // it means that we sent PUBLISH message more times (retries) but broker didn't send PUBREC in time + // the publish is failed and we need only to ignore this PUBREC. + + // NOTE : I need to find on message id and flow because the broker could be publish/received + // to/from client and message id could be the same (one tracked by broker and the other by client) + MqttMsgContextFinder msgCtxFinder = new MqttMsgContextFinder(msg.MessageId, MqttMsgFlow.ToPublish); + MqttMsgContext msgCtx = (MqttMsgContext) QueueExtension.Get(this.inflightQueue, msgCtxFinder.Find); + + // the PUBLISH message isn't in the inflight queue, it was already sent so we need to ignore this PUBREC + if (msgCtx == null) + { + enqueue = false; + } + } + } if (enqueue) { lock (this.internalQueue) { this.internalQueue.Enqueue(msg); +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Queuing, "enqueued {0}", msg); +#endif this.inflightWaitHandle.Set(); } } @@ -1082,44 +1343,12 @@ namespace uPLibrary.Networking.M2Mqtt byte[] fixedHeaderFirstByte = new byte[1]; byte msgType; -#if BROKER - long now = 0; - - // receive thread started, broker need to receive the first message - // (CONNECT) within a reasonable amount of time after TCP/IP connection - long connectTime = Environment.TickCount; -#endif - while (this.isRunning) { try { - if (this.channel.DataAvailable) - // read first byte (fixed header) - readBytes = this.channel.Receive(fixedHeaderFirstByte); - else - { -#if BROKER - // client not connected (client didn't send CONNECT yet) - if (!this.IsConnected) - { - now = Environment.TickCount; - - // if connect timeout exceeded ... - if ((now - connectTime) >= this.settings.TimeoutOnConnection) - { - // client must close connection - this.Close(); - - // client raw disconnection - this.OnMqttMsgDisconnected(); - } - } -#endif - // no bytes available, sleep before retry - readBytes = 0; - Thread.Sleep(10); - } + // read first byte (fixed header) + readBytes = this.channel.Receive(fixedHeaderFirstByte); if (readBytes > 0) { @@ -1137,22 +1366,28 @@ namespace uPLibrary.Networking.M2Mqtt case MqttMsgBase.MQTT_MSG_CONNECT_TYPE: #if BROKER - MqttMsgConnect connect = MqttMsgConnect.Parse(fixedHeaderFirstByte[0], this.channel); - + MqttMsgConnect connect = MqttMsgConnect.Parse(fixedHeaderFirstByte[0], (byte)this.ProtocolVersion, this.channel); +#if TRACE + Trace.WriteLine(TraceLevel.Frame, "RECV {0}", connect); +#endif + // raise message received event - this.OnMqttMsgReceived(connect); + this.OnInternalEvent(new MsgInternalEvent(connect)); break; #else throw new MqttClientException(MqttClientErrorCode.WrongBrokerMessage); #endif - + // CONNACK message received case MqttMsgBase.MQTT_MSG_CONNACK_TYPE: #if BROKER throw new MqttClientException(MqttClientErrorCode.WrongBrokerMessage); #else - this.msgReceived = MqttMsgConnack.Parse(fixedHeaderFirstByte[0], this.channel); + this.msgReceived = MqttMsgConnack.Parse(fixedHeaderFirstByte[0], (byte)this.ProtocolVersion, this.channel); +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Frame, "RECV {0}", this.msgReceived); +#endif this.syncEndReceiving.Set(); break; #endif @@ -1161,13 +1396,14 @@ namespace uPLibrary.Networking.M2Mqtt case MqttMsgBase.MQTT_MSG_PINGREQ_TYPE: #if BROKER - this.msgReceived = MqttMsgPingReq.Parse(fixedHeaderFirstByte[0], this.channel); + this.msgReceived = MqttMsgPingReq.Parse(fixedHeaderFirstByte[0], (byte)this.ProtocolVersion, this.channel); +#if TRACE + Trace.WriteLine(TraceLevel.Frame, "RECV {0}", this.msgReceived); +#endif MqttMsgPingResp pingresp = new MqttMsgPingResp(); - this.Send(pingresp.GetBytes()); + this.Send(pingresp); - // raise message received event - //this.OnMqttMsgReceived(this.msgReceived); break; #else throw new MqttClientException(MqttClientErrorCode.WrongBrokerMessage); @@ -1179,7 +1415,10 @@ namespace uPLibrary.Networking.M2Mqtt #if BROKER throw new MqttClientException(MqttClientErrorCode.WrongBrokerMessage); #else - this.msgReceived = MqttMsgPingResp.Parse(fixedHeaderFirstByte[0], this.channel); + this.msgReceived = MqttMsgPingResp.Parse(fixedHeaderFirstByte[0], (byte)this.ProtocolVersion, this.channel); +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Frame, "RECV {0}", this.msgReceived); +#endif this.syncEndReceiving.Set(); break; #endif @@ -1188,10 +1427,13 @@ namespace uPLibrary.Networking.M2Mqtt case MqttMsgBase.MQTT_MSG_SUBSCRIBE_TYPE: #if BROKER - MqttMsgSubscribe subscribe = MqttMsgSubscribe.Parse(fixedHeaderFirstByte[0], this.channel); + MqttMsgSubscribe subscribe = MqttMsgSubscribe.Parse(fixedHeaderFirstByte[0], (byte)this.ProtocolVersion, this.channel); +#if TRACE + Trace.WriteLine(TraceLevel.Frame, "RECV {0}", subscribe); +#endif // raise message received event - this.OnMqttMsgReceived(subscribe); + this.OnInternalEvent(new MsgInternalEvent(subscribe)); break; #else @@ -1205,7 +1447,10 @@ namespace uPLibrary.Networking.M2Mqtt throw new MqttClientException(MqttClientErrorCode.WrongBrokerMessage); #else // enqueue SUBACK message received (for QoS Level 1) into the internal queue - MqttMsgSuback suback = MqttMsgSuback.Parse(fixedHeaderFirstByte[0], this.channel); + MqttMsgSuback suback = MqttMsgSuback.Parse(fixedHeaderFirstByte[0], (byte)this.ProtocolVersion, this.channel); +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Frame, "RECV {0}", suback); +#endif // enqueue SUBACK message into the internal queue this.EnqueueInternal(suback); @@ -1216,7 +1461,10 @@ namespace uPLibrary.Networking.M2Mqtt // PUBLISH message received case MqttMsgBase.MQTT_MSG_PUBLISH_TYPE: - MqttMsgPublish publish = MqttMsgPublish.Parse(fixedHeaderFirstByte[0], this.channel); + MqttMsgPublish publish = MqttMsgPublish.Parse(fixedHeaderFirstByte[0], (byte)this.ProtocolVersion, this.channel); +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Frame, "RECV {0}", publish); +#endif // enqueue PUBLISH message to acknowledge into the inflight queue this.EnqueueInflight(publish, MqttMsgFlow.ToAcknowledge); @@ -1227,7 +1475,10 @@ namespace uPLibrary.Networking.M2Mqtt case MqttMsgBase.MQTT_MSG_PUBACK_TYPE: // enqueue PUBACK message received (for QoS Level 1) into the internal queue - MqttMsgPuback puback = MqttMsgPuback.Parse(fixedHeaderFirstByte[0], this.channel); + MqttMsgPuback puback = MqttMsgPuback.Parse(fixedHeaderFirstByte[0], (byte)this.ProtocolVersion, this.channel); +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Frame, "RECV {0}", puback); +#endif // enqueue PUBACK message into the internal queue this.EnqueueInternal(puback); @@ -1238,7 +1489,10 @@ namespace uPLibrary.Networking.M2Mqtt case MqttMsgBase.MQTT_MSG_PUBREC_TYPE: // enqueue PUBREC message received (for QoS Level 2) into the internal queue - MqttMsgPubrec pubrec = MqttMsgPubrec.Parse(fixedHeaderFirstByte[0], this.channel); + MqttMsgPubrec pubrec = MqttMsgPubrec.Parse(fixedHeaderFirstByte[0], (byte)this.ProtocolVersion, this.channel); +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Frame, "RECV {0}", pubrec); +#endif // enqueue PUBREC message into the internal queue this.EnqueueInternal(pubrec); @@ -1249,18 +1503,24 @@ namespace uPLibrary.Networking.M2Mqtt case MqttMsgBase.MQTT_MSG_PUBREL_TYPE: // enqueue PUBREL message received (for QoS Level 2) into the internal queue - MqttMsgPubrel pubrel = MqttMsgPubrel.Parse(fixedHeaderFirstByte[0], this.channel); + MqttMsgPubrel pubrel = MqttMsgPubrel.Parse(fixedHeaderFirstByte[0], (byte)this.ProtocolVersion, this.channel); +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Frame, "RECV {0}", pubrel); +#endif // enqueue PUBREL message into the internal queue this.EnqueueInternal(pubrel); break; - + // PUBCOMP message received case MqttMsgBase.MQTT_MSG_PUBCOMP_TYPE: // enqueue PUBCOMP message received (for QoS Level 2) into the internal queue - MqttMsgPubcomp pubcomp = MqttMsgPubcomp.Parse(fixedHeaderFirstByte[0], this.channel); + MqttMsgPubcomp pubcomp = MqttMsgPubcomp.Parse(fixedHeaderFirstByte[0], (byte)this.ProtocolVersion, this.channel); +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Frame, "RECV {0}", pubcomp); +#endif // enqueue PUBCOMP message into the internal queue this.EnqueueInternal(pubcomp); @@ -1271,10 +1531,13 @@ namespace uPLibrary.Networking.M2Mqtt case MqttMsgBase.MQTT_MSG_UNSUBSCRIBE_TYPE: #if BROKER - MqttMsgUnsubscribe unsubscribe = MqttMsgUnsubscribe.Parse(fixedHeaderFirstByte[0], this.channel); + MqttMsgUnsubscribe unsubscribe = MqttMsgUnsubscribe.Parse(fixedHeaderFirstByte[0], (byte)this.ProtocolVersion, this.channel); +#if TRACE + Trace.WriteLine(TraceLevel.Frame, "RECV {0}", unsubscribe); +#endif // raise message received event - this.OnMqttMsgReceived(unsubscribe); + this.OnInternalEvent(new MsgInternalEvent(unsubscribe)); break; #else @@ -1288,7 +1551,10 @@ namespace uPLibrary.Networking.M2Mqtt throw new MqttClientException(MqttClientErrorCode.WrongBrokerMessage); #else // enqueue UNSUBACK message received (for QoS Level 1) into the internal queue - MqttMsgUnsuback unsuback = MqttMsgUnsuback.Parse(fixedHeaderFirstByte[0], this.channel); + MqttMsgUnsuback unsuback = MqttMsgUnsuback.Parse(fixedHeaderFirstByte[0], (byte)this.ProtocolVersion, this.channel); +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Frame, "RECV {0}", unsuback); +#endif // enqueue UNSUBACK message into the internal queue this.EnqueueInternal(unsuback); @@ -1300,10 +1566,13 @@ namespace uPLibrary.Networking.M2Mqtt case MqttMsgDisconnect.MQTT_MSG_DISCONNECT_TYPE: #if BROKER - MqttMsgDisconnect disconnect = MqttMsgDisconnect.Parse(fixedHeaderFirstByte[0], this.channel); + MqttMsgDisconnect disconnect = MqttMsgDisconnect.Parse(fixedHeaderFirstByte[0], (byte)this.ProtocolVersion, this.channel); +#if TRACE + Trace.WriteLine(TraceLevel.Frame, "RECV {0}", disconnect); +#endif // raise message received event - this.OnMqttMsgReceived(disconnect); + this.OnInternalEvent(new MsgInternalEvent(disconnect)); break; #else @@ -1317,11 +1586,42 @@ namespace uPLibrary.Networking.M2Mqtt this.exReceiving = null; } - + // zero bytes read, peer gracefully closed socket + else + { + // wake up thread that will notify connection is closing + this.OnConnectionClosing(); + } } catch (Exception e) { +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Error, "Exception occurred: {0}", e.ToString()); +#endif this.exReceiving = new MqttCommunicationException(e); + + bool close = false; + if (e.GetType() == typeof(MqttClientException)) + { + // [v3.1.1] scenarios the receiver MUST close the network connection + MqttClientException ex = e as MqttClientException; + close = ((ex.ErrorCode == MqttClientErrorCode.InvalidFlagBits) || + (ex.ErrorCode == MqttClientErrorCode.InvalidProtocolName) || + (ex.ErrorCode == MqttClientErrorCode.InvalidConnectFlags)); + } +#if !(WINDOWS_APP || WINDOWS_PHONE_APP) + else if ((e.GetType() == typeof(IOException)) || (e.GetType() == typeof(SocketException)) || + ((e.InnerException != null) && (e.InnerException.GetType() == typeof(SocketException)))) // added for SSL/TLS incoming connection that use SslStream that wraps SocketException + { + close = true; + } +#endif + + if (close) + { + // wake up thread that will notify connection is closing + this.OnConnectionClosing(); + } } } } @@ -1331,9 +1631,11 @@ namespace uPLibrary.Networking.M2Mqtt /// private void KeepAliveThread() { - long now = 0; + int delta = 0; int wait = this.keepAlivePeriod; - this.isKeepAliveTimeout = false; + + // create event to signal that current thread is end + this.keepAliveEventEnd = new AutoResetEvent(false); while (this.isRunning) { @@ -1347,154 +1649,195 @@ namespace uPLibrary.Networking.M2Mqtt if (this.isRunning) { - now = Environment.TickCount; + delta = Environment.TickCount - this.lastCommTime; // if timeout exceeded ... - if ((now - this.lastCommTime) >= this.keepAlivePeriod) + if (delta >= this.keepAlivePeriod) { #if BROKER - this.isKeepAliveTimeout = true; // client must close connection - this.Close(); + this.OnConnectionClosing(); #else // ... send keep alive - this.Ping(); - wait = this.keepAlivePeriod; + this.Ping(); + wait = this.keepAlivePeriod; #endif } else { // update waiting time - wait = (int)(this.keepAlivePeriod - (now - this.lastCommTime)); + wait = this.keepAlivePeriod - delta; } } } - if (this.isKeepAliveTimeout) - { - this.IsConnected = false; - // raise disconnection client event - this.OnMqttMsgDisconnected(); - } + // signal thread end + this.keepAliveEventEnd.Set(); } /// - /// Thread for raising received message event + /// Thread for raising event /// - private void ReceiveEventThread() + private void DispatchEventThread() { while (this.isRunning) { - if (this.receiveQueue.Count == 0) +#if BROKER + if ((this.eventQueue.Count == 0) && !this.isConnectionClosing) + { + // broker need to receive the first message (CONNECT) + // within a reasonable amount of time after TCP/IP connection + if (!this.IsConnected) + { + // wait on receiving message from client with a connection timeout + if (!this.receiveEventWaitHandle.WaitOne(this.settings.TimeoutOnConnection)) + { + // client must close connection + this.Close(); + + // client raw disconnection + this.OnConnectionClosed(); + } + } + else + { + // wait on receiving message from client + this.receiveEventWaitHandle.WaitOne(); + } + } +#else + if ((this.eventQueue.Count == 0) && !this.isConnectionClosing) // wait on receiving message from client this.receiveEventWaitHandle.WaitOne(); +#endif // check if it is running or we are closing client if (this.isRunning) { - // get message from queue - MqttMsgBase msg = null; - lock (this.receiveQueue) + // get event from queue + InternalEvent internalEvent = null; + lock (this.eventQueue) { - if (this.receiveQueue.Count > 0) - msg = (MqttMsgBase)this.receiveQueue.Dequeue(); + if (this.eventQueue.Count > 0) + internalEvent = (InternalEvent)this.eventQueue.Dequeue(); } - if (msg != null) + // it's an event with a message inside + if (internalEvent != null) { - switch (msg.Type) + MqttMsgBase msg = ((MsgInternalEvent)internalEvent).Message; + + if (msg != null) { - // CONNECT message received - case MqttMsgBase.MQTT_MSG_CONNECT_TYPE: + switch (msg.Type) + { + // CONNECT message received + case MqttMsgBase.MQTT_MSG_CONNECT_TYPE: #if BROKER - // raise connected client event (CONNECT message received) - this.OnMqttMsgConnected((MqttMsgConnect)msg); - break; + // raise connected client event (CONNECT message received) + this.OnMqttMsgConnected((MqttMsgConnect)msg); + break; #else - throw new MqttClientException(MqttClientErrorCode.WrongBrokerMessage); + throw new MqttClientException(MqttClientErrorCode.WrongBrokerMessage); #endif - // SUBSCRIBE message received - case MqttMsgBase.MQTT_MSG_SUBSCRIBE_TYPE: + // SUBSCRIBE message received + case MqttMsgBase.MQTT_MSG_SUBSCRIBE_TYPE: #if BROKER - MqttMsgSubscribe subscribe = (MqttMsgSubscribe)msg; - // raise subscribe topic event (SUBSCRIBE message received) - this.OnMqttMsgSubscribeReceived(subscribe.MessageId, subscribe.Topics, subscribe.QoSLevels); + MqttMsgSubscribe subscribe = (MqttMsgSubscribe)msg; + // raise subscribe topic event (SUBSCRIBE message received) + this.OnMqttMsgSubscribeReceived(subscribe.MessageId, subscribe.Topics, subscribe.QoSLevels); + break; #else - throw new MqttClientException(MqttClientErrorCode.WrongBrokerMessage); + throw new MqttClientException(MqttClientErrorCode.WrongBrokerMessage); #endif - // SUBACK message received - case MqttMsgBase.MQTT_MSG_SUBACK_TYPE: + // SUBACK message received + case MqttMsgBase.MQTT_MSG_SUBACK_TYPE: - // raise subscribed topic event (SUBACK message received) - this.OnMqttMsgSubscribed((MqttMsgSuback)msg); - break; + // raise subscribed topic event (SUBACK message received) + this.OnMqttMsgSubscribed((MqttMsgSuback)msg); + break; - // PUBLISH message received - case MqttMsgBase.MQTT_MSG_PUBLISH_TYPE: + // PUBLISH message received + case MqttMsgBase.MQTT_MSG_PUBLISH_TYPE: - // raise PUBLISH message received event - this.OnMqttMsgPublishReceived((MqttMsgPublish)msg); - break; + // PUBLISH message received in a published internal event, no publish succeeded + if (internalEvent.GetType() == typeof(MsgPublishedInternalEvent)) + this.OnMqttMsgPublished(msg.MessageId, false); + else + // raise PUBLISH message received event + this.OnMqttMsgPublishReceived((MqttMsgPublish)msg); + break; - // PUBACK message received - case MqttMsgBase.MQTT_MSG_PUBACK_TYPE: + // PUBACK message received + case MqttMsgBase.MQTT_MSG_PUBACK_TYPE: - // raise published message event - // (PUBACK received for QoS Level 1) - this.OnMqttMsgPublished(((MqttMsgPuback)msg).MessageId); - break; + // raise published message event + // (PUBACK received for QoS Level 1) + this.OnMqttMsgPublished(msg.MessageId, true); + break; - // PUBREL message received - case MqttMsgBase.MQTT_MSG_PUBREL_TYPE: + // PUBREL message received + case MqttMsgBase.MQTT_MSG_PUBREL_TYPE: - // raise message received event - // (PUBREL received for QoS Level 2) - this.OnMqttMsgPublishReceived((MqttMsgPublish)msg); - break; + // raise message received event + // (PUBREL received for QoS Level 2) + this.OnMqttMsgPublishReceived((MqttMsgPublish)msg); + break; - // PUBCOMP message received - case MqttMsgBase.MQTT_MSG_PUBCOMP_TYPE: + // PUBCOMP message received + case MqttMsgBase.MQTT_MSG_PUBCOMP_TYPE: - // raise published message event - // (PUBCOMP received for QoS Level 2) - this.OnMqttMsgPublished(((MqttMsgPubcomp)msg).MessageId); - break; + // raise published message event + // (PUBCOMP received for QoS Level 2) + this.OnMqttMsgPublished(msg.MessageId, true); + break; - // UNSUBSCRIBE message received from client - case MqttMsgBase.MQTT_MSG_UNSUBSCRIBE_TYPE: + // UNSUBSCRIBE message received from client + case MqttMsgBase.MQTT_MSG_UNSUBSCRIBE_TYPE: #if BROKER - MqttMsgUnsubscribe unsubscribe = (MqttMsgUnsubscribe)msg; - // raise unsubscribe topic event (UNSUBSCRIBE message received) - this.OnMqttMsgUnsubscribeReceived(unsubscribe.MessageId, unsubscribe.Topics); - break; + MqttMsgUnsubscribe unsubscribe = (MqttMsgUnsubscribe)msg; + // raise unsubscribe topic event (UNSUBSCRIBE message received) + this.OnMqttMsgUnsubscribeReceived(unsubscribe.MessageId, unsubscribe.Topics); + break; #else - throw new MqttClientException(MqttClientErrorCode.WrongBrokerMessage); + throw new MqttClientException(MqttClientErrorCode.WrongBrokerMessage); #endif - // UNSUBACK message received - case MqttMsgBase.MQTT_MSG_UNSUBACK_TYPE: + // UNSUBACK message received + case MqttMsgBase.MQTT_MSG_UNSUBACK_TYPE: - // raise unsubscribed topic event - this.OnMqttMsgUnsubscribed(((MqttMsgUnsuback)msg).MessageId); - break; + // raise unsubscribed topic event + this.OnMqttMsgUnsubscribed(msg.MessageId); + break; - // DISCONNECT message received from client - case MqttMsgDisconnect.MQTT_MSG_DISCONNECT_TYPE: + // DISCONNECT message received from client + case MqttMsgDisconnect.MQTT_MSG_DISCONNECT_TYPE: #if BROKER - // raise disconnected client event (DISCONNECT message received) - this.OnMqttMsgDisconnected(); - break; + // raise disconnected client event (DISCONNECT message received) + this.OnMqttMsgDisconnected(); + break; #else - throw new MqttClientException(MqttClientErrorCode.WrongBrokerMessage); + throw new MqttClientException(MqttClientErrorCode.WrongBrokerMessage); #endif + } } } + + // all events for received messages dispatched, check if there is closing connection + if ((this.eventQueue.Count == 0) && this.isConnectionClosing) + { + // client must close connection + this.Close(); + + // client raw disconnection + this.OnConnectionClosed(); + } } } } @@ -1507,8 +1850,11 @@ namespace uPLibrary.Networking.M2Mqtt MqttMsgContext msgContext = null; MqttMsgBase msgInflight = null; MqttMsgBase msgReceived = null; + InternalEvent internalEvent = null; bool acknowledge = false; int timeout = Timeout.Infinite; + int delta; + bool msgReceivedProcessed = false; try { @@ -1527,6 +1873,14 @@ namespace uPLibrary.Networking.M2Mqtt { lock (this.inflightQueue) { + // message received and peeked from internal queue is processed + // NOTE : it has the corresponding message in inflight queue based on messageId + // (ex. a PUBREC for a PUBLISH, a SUBACK for a SUBSCRIBE, ...) + // if it's orphan we need to remove from internal queue + msgReceivedProcessed = false; + acknowledge = false; + msgReceived = null; + // set timeout tu MaxValue instead of Infinte (-1) to perform // compare with calcultad current msgTimeout timeout = Int32.MaxValue; @@ -1541,6 +1895,10 @@ namespace uPLibrary.Networking.M2Mqtt acknowledge = false; msgReceived = null; + // check to be sure that client isn't closing and all queues are now empty ! + if (!this.isRunning) + break; + // dequeue message context from queue msgContext = (MqttMsgContext)this.inflightQueue.Dequeue(); @@ -1554,24 +1912,40 @@ namespace uPLibrary.Networking.M2Mqtt // QoS 0, PUBLISH message to send to broker, no state change, no acknowledge if (msgContext.Flow == MqttMsgFlow.ToPublish) { - this.Send(msgInflight.GetBytes()); + this.Send(msgInflight); } // QoS 0, no need acknowledge else if (msgContext.Flow == MqttMsgFlow.ToAcknowledge) { + internalEvent = new MsgInternalEvent(msgInflight); // notify published message from broker (no need acknowledged) - this.OnMqttMsgReceived(msgInflight); + this.OnInternalEvent(internalEvent); } + +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Queuing, "processed {0}", msgInflight); +#endif break; case MqttMsgState.QueuedQos1: + // [v3.1.1] SUBSCRIBE and UNSIBSCRIBE aren't "officially" QOS = 1 + case MqttMsgState.SendSubscribe: + case MqttMsgState.SendUnsubscribe: // QoS 1, PUBLISH or SUBSCRIBE/UNSUBSCRIBE message to send to broker, state change to wait PUBACK or SUBACK/UNSUBACK if (msgContext.Flow == MqttMsgFlow.ToPublish) { + msgContext.Timestamp = Environment.TickCount; + msgContext.Attempt++; + if (msgInflight.Type == MqttMsgBase.MQTT_MSG_PUBLISH_TYPE) + { // PUBLISH message to send, wait for PUBACK msgContext.State = MqttMsgState.WaitForPuback; + // retry ? set dup flag [v3.1.1] only for PUBLISH message + if (msgContext.Attempt > 1) + msgInflight.DupFlag = true; + } else if (msgInflight.Type == MqttMsgBase.MQTT_MSG_SUBSCRIBE_TYPE) // SUBSCRIBE message to send, wait for SUBACK msgContext.State = MqttMsgState.WaitForSuback; @@ -1579,17 +1953,10 @@ namespace uPLibrary.Networking.M2Mqtt // UNSUBSCRIBE message to send, wait for UNSUBACK msgContext.State = MqttMsgState.WaitForUnsuback; - msgContext.Timestamp = Environment.TickCount; - msgContext.Attempt++; - // retry ? set dup flag - if (msgContext.Attempt > 1) - msgInflight.DupFlag = true; + this.Send(msgInflight); - this.Send(msgInflight.GetBytes()); - - // update timeout - int msgTimeout = (this.settings.DelayOnRetry - (Environment.TickCount - msgContext.Timestamp)); - timeout = (msgTimeout < timeout) ? msgTimeout : timeout; + // update timeout : minimum between delay (based on current message sent) or current timeout + timeout = (this.settings.DelayOnRetry < timeout) ? this.settings.DelayOnRetry : timeout; // re-enqueue message (I have to re-analyze for receiving PUBACK, SUBACK or UNSUBACK) this.inflightQueue.Enqueue(msgContext); @@ -1598,12 +1965,17 @@ namespace uPLibrary.Networking.M2Mqtt else if (msgContext.Flow == MqttMsgFlow.ToAcknowledge) { MqttMsgPuback puback = new MqttMsgPuback(); - puback.MessageId = ((MqttMsgPublish)msgInflight).MessageId; + puback.MessageId = msgInflight.MessageId; - this.Send(puback.GetBytes()); + this.Send(puback); + internalEvent = new MsgInternalEvent(msgInflight); // notify published message from broker and acknowledged - this.OnMqttMsgReceived(msgInflight); + this.OnInternalEvent(internalEvent); + +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Queuing, "processed {0}", msgInflight); +#endif } break; @@ -1612,18 +1984,17 @@ namespace uPLibrary.Networking.M2Mqtt // QoS 2, PUBLISH message to send to broker, state change to wait PUBREC if (msgContext.Flow == MqttMsgFlow.ToPublish) { - msgContext.State = MqttMsgState.WaitForPubrec; msgContext.Timestamp = Environment.TickCount; msgContext.Attempt++; + msgContext.State = MqttMsgState.WaitForPubrec; // retry ? set dup flag if (msgContext.Attempt > 1) msgInflight.DupFlag = true; - this.Send(msgInflight.GetBytes()); + this.Send(msgInflight); - // update timeout - int msgTimeout = (this.settings.DelayOnRetry - (Environment.TickCount - msgContext.Timestamp)); - timeout = (msgTimeout < timeout) ? msgTimeout : timeout; + // update timeout : minimum between delay (based on current message sent) or current timeout + timeout = (this.settings.DelayOnRetry < timeout) ? this.settings.DelayOnRetry : timeout; // re-enqueue message (I have to re-analyze for receiving PUBREC) this.inflightQueue.Enqueue(msgContext); @@ -1632,11 +2003,11 @@ namespace uPLibrary.Networking.M2Mqtt else if (msgContext.Flow == MqttMsgFlow.ToAcknowledge) { MqttMsgPubrec pubrec = new MqttMsgPubrec(); - pubrec.MessageId = ((MqttMsgPublish)msgInflight).MessageId; + pubrec.MessageId = msgInflight.MessageId; msgContext.State = MqttMsgState.WaitForPubrel; - this.Send(pubrec.GetBytes()); + this.Send(pubrec); // re-enqueue message (I have to re-analyze for receiving PUBREL) this.inflightQueue.Enqueue(msgContext); @@ -1660,37 +2031,62 @@ namespace uPLibrary.Networking.M2Mqtt } // it is a PUBACK message or a SUBACK/UNSUBACK message - if ((msgReceived != null) && ((msgReceived.Type == MqttMsgBase.MQTT_MSG_PUBACK_TYPE) || - (msgReceived.Type == MqttMsgBase.MQTT_MSG_SUBACK_TYPE) || - (msgReceived.Type == MqttMsgBase.MQTT_MSG_UNSUBACK_TYPE))) + if (msgReceived != null) { - // PUBACK message or SUBACK message for the current message - if (((msgInflight.Type == MqttMsgBase.MQTT_MSG_PUBLISH_TYPE) && (((MqttMsgPuback)msgReceived).MessageId == ((MqttMsgPublish)msgInflight).MessageId)) || - ((msgInflight.Type == MqttMsgBase.MQTT_MSG_SUBSCRIBE_TYPE) && (((MqttMsgSuback)msgReceived).MessageId == ((MqttMsgSubscribe)msgInflight).MessageId)) || - ((msgInflight.Type == MqttMsgBase.MQTT_MSG_UNSUBSCRIBE_TYPE) && (((MqttMsgUnsuback)msgReceived).MessageId == ((MqttMsgUnsubscribe)msgInflight).MessageId))) + // PUBACK message or SUBACK/UNSUBACK message for the current message + if (((msgReceived.Type == MqttMsgBase.MQTT_MSG_PUBACK_TYPE) && (msgInflight.Type == MqttMsgBase.MQTT_MSG_PUBLISH_TYPE) && (msgReceived.MessageId == msgInflight.MessageId)) || + ((msgReceived.Type == MqttMsgBase.MQTT_MSG_SUBACK_TYPE) && (msgInflight.Type == MqttMsgBase.MQTT_MSG_SUBSCRIBE_TYPE) && (msgReceived.MessageId == msgInflight.MessageId)) || + ((msgReceived.Type == MqttMsgBase.MQTT_MSG_UNSUBACK_TYPE) && (msgInflight.Type == MqttMsgBase.MQTT_MSG_UNSUBSCRIBE_TYPE) && (msgReceived.MessageId == msgInflight.MessageId))) { lock (this.internalQueue) { // received message processed this.internalQueue.Dequeue(); acknowledge = true; + msgReceivedProcessed = true; +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Queuing, "dequeued {0}", msgReceived); +#endif } + // if PUBACK received, confirm published with flag + if (msgReceived.Type == MqttMsgBase.MQTT_MSG_PUBACK_TYPE) + internalEvent = new MsgPublishedInternalEvent(msgReceived, true); + else + internalEvent = new MsgInternalEvent(msgReceived); + // notify received acknowledge from broker of a published message or subscribe/unsubscribe message - this.OnMqttMsgReceived(msgReceived); + this.OnInternalEvent(internalEvent); + + // PUBACK received for PUBLISH message with QoS Level 1, remove from session state + if ((msgInflight.Type == MqttMsgBase.MQTT_MSG_PUBLISH_TYPE) && + (this.session != null) && +#if (MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK) + (this.session.InflightMessages.Contains(msgContext.Key))) +#else + (this.session.InflightMessages.ContainsKey(msgContext.Key))) +#endif + { + this.session.InflightMessages.Remove(msgContext.Key); + } + +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Queuing, "processed {0}", msgInflight); +#endif } } // current message not acknowledged, no PUBACK or SUBACK/UNSUBACK or not equal messageid if (!acknowledge) { + delta = Environment.TickCount - msgContext.Timestamp; // check timeout for receiving PUBACK since PUBLISH was sent or // for receiving SUBACK since SUBSCRIBE was sent or // for receiving UNSUBACK since UNSUBSCRIBE was sent - if ((Environment.TickCount - msgContext.Timestamp) >= this.settings.DelayOnRetry) + if (delta >= this.settings.DelayOnRetry) { // max retry not reached, resend - if (msgContext.Attempt <= this.settings.AttemptsOnRetry) + if (msgContext.Attempt < this.settings.AttemptsOnRetry) { msgContext.State = MqttMsgState.QueuedQos1; @@ -1700,6 +2096,30 @@ namespace uPLibrary.Networking.M2Mqtt // update timeout (0 -> reanalyze queue immediately) timeout = 0; } + else + { + // if PUBACK for a PUBLISH message not received after retries, raise event for not published + if (msgInflight.Type == MqttMsgBase.MQTT_MSG_PUBLISH_TYPE) + { + // PUBACK not received in time, PUBLISH retries failed, need to remove from session inflight messages too + if ((this.session != null) && +#if (MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK) + (this.session.InflightMessages.Contains(msgContext.Key))) +#else + (this.session.InflightMessages.ContainsKey(msgContext.Key))) +#endif + { + this.session.InflightMessages.Remove(msgContext.Key); + } + + internalEvent = new MsgPublishedInternalEvent(msgInflight, false); + + // notify not received acknowledge from broker and message not published + this.OnInternalEvent(internalEvent); + } + // NOTE : not raise events for SUBACK or UNSUBACK not received + // for the user no event raised means subscribe/unsubscribe failed + } } else { @@ -1707,7 +2127,7 @@ namespace uPLibrary.Networking.M2Mqtt this.inflightQueue.Enqueue(msgContext); // update timeout - int msgTimeout = (this.settings.DelayOnRetry - (Environment.TickCount - msgContext.Timestamp)); + int msgTimeout = (this.settings.DelayOnRetry - delta); timeout = (msgTimeout < timeout) ? msgTimeout : timeout; } } @@ -1730,27 +2150,30 @@ namespace uPLibrary.Networking.M2Mqtt if ((msgReceived != null) && (msgReceived.Type == MqttMsgBase.MQTT_MSG_PUBREC_TYPE)) { // PUBREC message for the current PUBLISH message, send PUBREL, wait for PUBCOMP - if (((MqttMsgPubrec)msgReceived).MessageId == ((MqttMsgPublish)msgInflight).MessageId) + if (msgReceived.MessageId == msgInflight.MessageId) { lock (this.internalQueue) { // received message processed this.internalQueue.Dequeue(); acknowledge = true; + msgReceivedProcessed = true; +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Queuing, "dequeued {0}", msgReceived); +#endif } MqttMsgPubrel pubrel = new MqttMsgPubrel(); - pubrel.MessageId = ((MqttMsgPublish)msgInflight).MessageId; + pubrel.MessageId = msgInflight.MessageId; msgContext.State = MqttMsgState.WaitForPubcomp; msgContext.Timestamp = Environment.TickCount; msgContext.Attempt = 1; - this.Send(pubrel.GetBytes()); + this.Send(pubrel); - // update timeout - int msgTimeout = (this.settings.DelayOnRetry - (Environment.TickCount - msgContext.Timestamp)); - timeout = (msgTimeout < timeout) ? msgTimeout : timeout; + // update timeout : minimum between delay (based on current message sent) or current timeout + timeout = (this.settings.DelayOnRetry < timeout) ? this.settings.DelayOnRetry : timeout; // re-enqueue message this.inflightQueue.Enqueue(msgContext); @@ -1760,11 +2183,12 @@ namespace uPLibrary.Networking.M2Mqtt // current message not acknowledged if (!acknowledge) { + delta = Environment.TickCount - msgContext.Timestamp; // check timeout for receiving PUBREC since PUBLISH was sent - if ((Environment.TickCount - msgContext.Timestamp) >= this.settings.DelayOnRetry) + if (delta >= this.settings.DelayOnRetry) { // max retry not reached, resend - if (msgContext.Attempt <= this.settings.AttemptsOnRetry) + if (msgContext.Attempt < this.settings.AttemptsOnRetry) { msgContext.State = MqttMsgState.QueuedQos2; @@ -1774,6 +2198,24 @@ namespace uPLibrary.Networking.M2Mqtt // update timeout (0 -> reanalyze queue immediately) timeout = 0; } + else + { + // PUBREC not received in time, PUBLISH retries failed, need to remove from session inflight messages too + if ((this.session != null) && +#if (MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK) + (this.session.InflightMessages.Contains(msgContext.Key))) +#else + (this.session.InflightMessages.ContainsKey(msgContext.Key))) +#endif + { + this.session.InflightMessages.Remove(msgContext.Key); + } + + // if PUBREC for a PUBLISH message not received after retries, raise event for not published + internalEvent = new MsgPublishedInternalEvent(msgInflight, false); + // notify not received acknowledge from broker and message not published + this.OnInternalEvent(internalEvent); + } } else { @@ -1781,7 +2223,7 @@ namespace uPLibrary.Networking.M2Mqtt this.inflightQueue.Enqueue(msgContext); // update timeout - int msgTimeout = (this.settings.DelayOnRetry - (Environment.TickCount - msgContext.Timestamp)); + int msgTimeout = (this.settings.DelayOnRetry - delta); timeout = (msgTimeout < timeout) ? msgTimeout : timeout; } } @@ -1803,21 +2245,42 @@ namespace uPLibrary.Networking.M2Mqtt if ((msgReceived != null) && (msgReceived.Type == MqttMsgBase.MQTT_MSG_PUBREL_TYPE)) { // PUBREL message for the current message, send PUBCOMP - if (((MqttMsgPubrel)msgReceived).MessageId == ((MqttMsgPublish)msgInflight).MessageId) + if (msgReceived.MessageId == msgInflight.MessageId) { lock (this.internalQueue) { // received message processed this.internalQueue.Dequeue(); + msgReceivedProcessed = true; +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Queuing, "dequeued {0}", msgReceived); +#endif } MqttMsgPubcomp pubcomp = new MqttMsgPubcomp(); - pubcomp.MessageId = ((MqttMsgPublish)msgInflight).MessageId; + pubcomp.MessageId = msgInflight.MessageId; - this.Send(pubcomp.GetBytes()); + this.Send(pubcomp); + internalEvent = new MsgInternalEvent(msgInflight); // notify published message from broker and acknowledged - this.OnMqttMsgReceived(msgInflight); + this.OnInternalEvent(internalEvent); + + // PUBREL received (and PUBCOMP sent) for PUBLISH message with QoS Level 2, remove from session state + if ((msgInflight.Type == MqttMsgBase.MQTT_MSG_PUBLISH_TYPE) && + (this.session != null) && +#if (MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK) + (this.session.InflightMessages.Contains(msgContext.Key))) +#else + (this.session.InflightMessages.ContainsKey(msgContext.Key))) +#endif + { + this.session.InflightMessages.Remove(msgContext.Key); + } + +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Queuing, "processed {0}", msgInflight); +#endif } else { @@ -1849,25 +2312,69 @@ namespace uPLibrary.Networking.M2Mqtt if ((msgReceived != null) && (msgReceived.Type == MqttMsgBase.MQTT_MSG_PUBCOMP_TYPE)) { // PUBCOMP message for the current message - if (((MqttMsgPubcomp)msgReceived).MessageId == ((MqttMsgPublish)msgInflight).MessageId) + if (msgReceived.MessageId == msgInflight.MessageId) { lock (this.internalQueue) { // received message processed this.internalQueue.Dequeue(); acknowledge = true; + msgReceivedProcessed = true; +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Queuing, "dequeued {0}", msgReceived); +#endif } + internalEvent = new MsgPublishedInternalEvent(msgReceived, true); // notify received acknowledge from broker of a published message - this.OnMqttMsgReceived(msgReceived); + this.OnInternalEvent(internalEvent); + + // PUBCOMP received for PUBLISH message with QoS Level 2, remove from session state + if ((msgInflight.Type == MqttMsgBase.MQTT_MSG_PUBLISH_TYPE) && + (this.session != null) && +#if (MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK) + (this.session.InflightMessages.Contains(msgContext.Key))) +#else + (this.session.InflightMessages.ContainsKey(msgContext.Key))) +#endif + { + this.session.InflightMessages.Remove(msgContext.Key); + } + +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Queuing, "processed {0}", msgInflight); +#endif + } + } + // it is a PUBREC message + else if ((msgReceived != null) && (msgReceived.Type == MqttMsgBase.MQTT_MSG_PUBREC_TYPE)) + { + // another PUBREC message for the current message due to a retransmitted PUBLISH + // I'm in waiting for PUBCOMP, so I can discard this PUBREC + if (msgReceived.MessageId == msgInflight.MessageId) + { + lock (this.internalQueue) + { + // received message processed + this.internalQueue.Dequeue(); + acknowledge = true; + msgReceivedProcessed = true; +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Queuing, "dequeued {0}", msgReceived); +#endif + + // re-enqueue message + this.inflightQueue.Enqueue(msgContext); + } } } // current message not acknowledged if (!acknowledge) { + delta = Environment.TickCount - msgContext.Timestamp; // check timeout for receiving PUBCOMP since PUBREL was sent - if ((Environment.TickCount - msgContext.Timestamp) >= this.settings.DelayOnRetry) + if (delta >= this.settings.DelayOnRetry) { // max retry not reached, resend if (msgContext.Attempt < this.settings.AttemptsOnRetry) @@ -1880,6 +2387,24 @@ namespace uPLibrary.Networking.M2Mqtt // update timeout (0 -> reanalyze queue immediately) timeout = 0; } + else + { + // PUBCOMP not received, PUBREL retries failed, need to remove from session inflight messages too + if ((this.session != null) && +#if (MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK) + (this.session.InflightMessages.Contains(msgContext.Key))) +#else + (this.session.InflightMessages.ContainsKey(msgContext.Key))) +#endif + { + this.session.InflightMessages.Remove(msgContext.Key); + } + + // if PUBCOMP for a PUBLISH message not received after retries, raise event for not published + internalEvent = new MsgPublishedInternalEvent(msgInflight, false); + // notify not received acknowledge from broker and message not published + this.OnInternalEvent(internalEvent); + } } else { @@ -1887,7 +2412,7 @@ namespace uPLibrary.Networking.M2Mqtt this.inflightQueue.Enqueue(msgContext); // update timeout - int msgTimeout = (this.settings.DelayOnRetry - (Environment.TickCount - msgContext.Timestamp)); + int msgTimeout = (this.settings.DelayOnRetry - delta); timeout = (msgTimeout < timeout) ? msgTimeout : timeout; } } @@ -1905,20 +2430,22 @@ namespace uPLibrary.Networking.M2Mqtt if (msgContext.Flow == MqttMsgFlow.ToPublish) { MqttMsgPubrel pubrel = new MqttMsgPubrel(); - pubrel.MessageId = ((MqttMsgPublish)msgInflight).MessageId; + pubrel.MessageId = msgInflight.MessageId; msgContext.State = MqttMsgState.WaitForPubcomp; msgContext.Timestamp = Environment.TickCount; msgContext.Attempt++; - // retry ? set dup flag - if (msgContext.Attempt > 1) - pubrel.DupFlag = true; + // retry ? set dup flag [v3.1.1] no needed + if (this.ProtocolVersion == MqttProtocolVersion.Version_3_1) + { + if (msgContext.Attempt > 1) + pubrel.DupFlag = true; + } + + this.Send(pubrel); - this.Send(pubrel.GetBytes()); - - // update timeout - int msgTimeout = (this.settings.DelayOnRetry - (Environment.TickCount - msgContext.Timestamp)); - timeout = (msgTimeout < timeout) ? msgTimeout : timeout; + // update timeout : minimum between delay (based on current message sent) or current timeout + timeout = (this.settings.DelayOnRetry < timeout) ? this.settings.DelayOnRetry : timeout; // re-enqueue message this.inflightQueue.Enqueue(msgContext); @@ -1939,29 +2466,126 @@ namespace uPLibrary.Networking.M2Mqtt // if calculated timeout is MaxValue, it means that must be Infinite (-1) if (timeout == Int32.MaxValue) timeout = Timeout.Infinite; + + // if message received is orphan, no corresponding message in inflight queue + // based on messageId, we need to remove from the queue + if ((msgReceived != null) && !msgReceivedProcessed) + { + this.internalQueue.Dequeue(); +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Queuing, "dequeued {0} orphan", msgReceived); +#endif + } } } } } - catch (MqttCommunicationException) + catch (MqttCommunicationException e) { - this.Close(); + // possible exception on Send, I need to re-enqueue not sent message + if (msgContext != null) + // re-enqueue message + this.inflightQueue.Enqueue(msgContext); + +#if TRACE + MqttUtility.Trace.WriteLine(TraceLevel.Error, "Exception occurred: {0}", e.ToString()); +#endif // raise disconnection client event - this.OnMqttMsgDisconnected(); + this.OnConnectionClosing(); } } + /// + /// Restore session + /// + private void RestoreSession() + { + // if not clean session + if (!this.CleanSession) + { + // there is a previous session + if (this.session != null) + { + lock (this.inflightQueue) + { + foreach (MqttMsgContext msgContext in this.session.InflightMessages.Values) + { + this.inflightQueue.Enqueue(msgContext); + + // if it is a PUBLISH message to publish + if ((msgContext.Message.Type == MqttMsgBase.MQTT_MSG_PUBLISH_TYPE) && + (msgContext.Flow == MqttMsgFlow.ToPublish)) + { + // it's QoS 1 and we haven't received PUBACK + if ((msgContext.Message.QosLevel == MqttMsgBase.QOS_LEVEL_AT_LEAST_ONCE) && + (msgContext.State == MqttMsgState.WaitForPuback)) + { + // we haven't received PUBACK, we need to resend PUBLISH message + msgContext.State = MqttMsgState.QueuedQos1; + } + // it's QoS 2 + else if (msgContext.Message.QosLevel == MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE) + { + // we haven't received PUBREC, we need to resend PUBLISH message + if (msgContext.State == MqttMsgState.WaitForPubrec) + { + msgContext.State = MqttMsgState.QueuedQos2; + } + // we haven't received PUBCOMP, we need to resend PUBREL for it + else if (msgContext.State == MqttMsgState.WaitForPubcomp) + { + msgContext.State = MqttMsgState.SendPubrel; + } + } + } + } + } + + // unlock process inflight queue + this.inflightWaitHandle.Set(); + } + else + { + // create new session + this.session = new MqttClientSession(this.ClientId); + } + } + // clean any previous session + else + { + if (this.session != null) + this.session.Clear(); + } + } + +#if BROKER + + /// + /// Load a given session + /// + /// MQTT Client session to load + public void LoadSession(MqttClientSession session) + { + // if not clean session + if (!this.CleanSession) + { + // set the session ... + this.session = session; + // ... and restore it + this.RestoreSession(); + } + } +#endif + /// /// Generate the next message identifier /// /// Message identifier private ushort GetMessageId() { - if (this.messageIdCounter == 0) - this.messageIdCounter++; - else - this.messageIdCounter = ((this.messageIdCounter % UInt16.MaxValue) != 0) ? (ushort)(this.messageIdCounter + 1) : (ushort)0; + // if 0 or max UInt16, it becomes 1 (first valid messageId) + this.messageIdCounter = ((this.messageIdCounter % UInt16.MaxValue) != 0) ? (ushort)(this.messageIdCounter + 1) : (ushort)1; return this.messageIdCounter; } @@ -1990,10 +2614,19 @@ namespace uPLibrary.Networking.M2Mqtt { MqttMsgContext msgCtx = (MqttMsgContext)item; return ((msgCtx.Message.Type == MqttMsgBase.MQTT_MSG_PUBLISH_TYPE) && - (((MqttMsgPublish)msgCtx.Message).MessageId == this.MessageId) && + (msgCtx.Message.MessageId == this.MessageId) && msgCtx.Flow == this.Flow); } } } + + /// + /// MQTT protocol version + /// + public enum MqttProtocolVersion + { + Version_3_1 = MqttMsgConnect.PROTOCOL_VERSION_V3_1, + Version_3_1_1 = MqttMsgConnect.PROTOCOL_VERSION_V3_1_1 + } } diff --git a/M2Mqtt/MqttNetworkChannel.cs b/M2Mqtt/MqttNetworkChannel.cs deleted file mode 100644 index 4455496..0000000 --- a/M2Mqtt/MqttNetworkChannel.cs +++ /dev/null @@ -1,272 +0,0 @@ -/* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library. -*/ - -#if SSL -#if (MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3) -using Microsoft.SPOT.Net.Security; -#else -using System.Net.Security; -using System.Security.Authentication; -#endif -#endif -using System.Net.Sockets; -using System.Net; -using System.Security.Cryptography.X509Certificates; - -namespace uPLibrary.Networking.M2Mqtt -{ - /// - /// Channel to communicate over the network - /// - public class MqttNetworkChannel : IMqttNetworkChannel - { - // remote host information - private string remoteHostName; - private IPAddress remoteIpAddress; - private int remotePort; - - // socket for communication - private Socket socket; - // using SSL - private bool secure; - - // CA certificate - private X509Certificate caCert; - - /// - /// Remote host name - /// - public string RemoteHostName { get { return this.remoteHostName; } } - - /// - /// Remote IP address - /// - public IPAddress RemoteIpAddress { get { return this.remoteIpAddress; } } - - /// - /// Remote port - /// - public int RemotePort { get { return this.remotePort; } } - -#if SSL - // SSL stream - private SslStream sslStream; -#if (!MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3) - private NetworkStream netStream; -#endif -#endif - - /// - /// Data available on the channel - /// - public bool DataAvailable - { - get - { -#if SSL -#if (MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3) - if (secure) - return this.sslStream.DataAvailable; - else - return (this.socket.Available > 0); -#else - if (secure) - return this.netStream.DataAvailable; - else - return (this.socket.Available > 0); -#endif -#else - return (this.socket.Available > 0); -#endif - } - } - - /// - /// Constructor - /// - /// Socket opened with the client - public MqttNetworkChannel(Socket socket) - { - this.socket = socket; - } - - /// - /// Constructor - /// - /// Remote Host name - /// Remote IP address - /// Remote port - public MqttNetworkChannel(string remoteHostName, IPAddress remoteIpAddress, int remotePort) : - this(remoteHostName, remoteIpAddress, remotePort, false, null) - { - } - - /// - /// Constructor - /// - /// Remote Host name - /// Remote IP address - /// Remote port - /// Using SSL - /// CA certificate - public MqttNetworkChannel(string remoteHostName, IPAddress remoteIpAddress, int remotePort, bool secure, X509Certificate caCert) - { - this.remoteHostName = remoteHostName; - this.remoteIpAddress = remoteIpAddress; - this.remotePort = remotePort; - this.secure = secure; - this.caCert = caCert; - } - - /// - /// Connect to remote server - /// - public void Connect() - { - this.socket = new Socket(IPAddressUtility.GetAddressFamily(this.remoteIpAddress), SocketType.Stream, ProtocolType.Tcp); - // try connection to the broker - this.socket.Connect(new IPEndPoint(this.remoteIpAddress, this.remotePort)); - -#if SSL - // secure channel requested - if (secure) - { - // create SSL stream -#if (MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3) - this.sslStream = new SslStream(this.socket); -#else - this.netStream = new NetworkStream(this.socket); - this.sslStream = new SslStream(this.netStream); -#endif - - // server authentication (SSL/TLS handshake) -#if (MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3) - this.sslStream.AuthenticateAsClient(this.remoteHostName, - null, - new X509Certificate[] { this.caCert }, - SslVerification.CertificateRequired, - SslProtocols.TLSv1); -#else - this.sslStream.AuthenticateAsClient(this.remoteHostName, - new X509CertificateCollection(new X509Certificate[] { this.caCert }), - SslProtocols.Tls, false); -#endif - } -#endif - } - - /// - /// Send data on the network channel - /// - /// Data buffer to send - /// Number of byte sent - public int Send(byte[] buffer) - { -#if SSL - if (this.secure) - { - this.sslStream.Write(buffer, 0, buffer.Length); - return buffer.Length; - } - else - return this.socket.Send(buffer, 0, buffer.Length, SocketFlags.None); -#else - return this.socket.Send(buffer, 0, buffer.Length, SocketFlags.None); -#endif - } - - /// - /// Receive data from the network - /// - /// Data buffer for receiving data - /// Number of bytes received - public int Receive(byte[] buffer) - { -#if SSL - if (this.secure) - { - // read all data needed (until fill buffer) - int idx = 0; - while (idx < buffer.Length) - { - idx += this.sslStream.Read(buffer, idx, buffer.Length - idx); - } - return buffer.Length; - } - else - { - // read all data needed (until fill buffer) - int idx = 0; - while (idx < buffer.Length) - { - idx += this.socket.Receive(buffer, idx, buffer.Length - idx, SocketFlags.None); - } - return buffer.Length; - } -#else - // read all data needed (until fill buffer) - int idx = 0; - while (idx < buffer.Length) - { - idx += this.socket.Receive(buffer, idx, buffer.Length - idx, SocketFlags.None); - } - return buffer.Length; -#endif - } - - /// - /// Close the network channel - /// - public void Close() - { -#if SSL - if (this.secure) - { -#if (!MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3) - this.netStream.Close(); -#endif - this.sslStream.Close(); - } - this.socket.Close(); -#else - this.socket.Close(); -#endif - } - } - - /// - /// IPAddress Utility class - /// - public static class IPAddressUtility - { - /// - /// Return AddressFamily for the IP address - /// - /// IP address to check - /// Address family - public static AddressFamily GetAddressFamily(IPAddress ipAddress) - { -#if (!MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3) - return ipAddress.AddressFamily; -#else - return (ipAddress.ToString().IndexOf(':') != -1) ? - AddressFamily.InterNetworkV6 : AddressFamily.InterNetwork; -#endif - } - } -} diff --git a/M2Mqtt/MqttSecurity.cs b/M2Mqtt/MqttSecurity.cs new file mode 100644 index 0000000..b19f108 --- /dev/null +++ b/M2Mqtt/MqttSecurity.cs @@ -0,0 +1,30 @@ +/* +Copyright (c) 2013, 2014 Paolo Patierno + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation +*/ + +namespace uPLibrary.Networking.M2Mqtt +{ + /// + /// Supported SSL/TLS protocol versions + /// + public enum MqttSslProtocols + { + None, + SSLv3, + TLSv1_0, + TLSv1_1, + TLSv1_2 + } +} diff --git a/M2Mqtt/MqttSettings.cs b/M2Mqtt/MqttSettings.cs index 9db3351..994f622 100644 --- a/M2Mqtt/MqttSettings.cs +++ b/M2Mqtt/MqttSettings.cs @@ -1,19 +1,17 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ namespace uPLibrary.Networking.M2Mqtt @@ -27,14 +25,16 @@ namespace uPLibrary.Networking.M2Mqtt public const int MQTT_BROKER_DEFAULT_PORT = 1883; public const int MQTT_BROKER_DEFAULT_SSL_PORT = 8883; // default timeout on receiving from client - public const int MQTT_DEFAULT_TIMEOUT = 25000; + public const int MQTT_DEFAULT_TIMEOUT = 30000; // max publish, subscribe and unsubscribe retry for QoS Level 1 or 2 public const int MQTT_ATTEMPTS_RETRY = 3; // delay for retry publish, subscribe and unsubscribe for QoS Level 1 or 2 public const int MQTT_DELAY_RETRY = 10000; // broker need to receive the first message (CONNECT) // within a reasonable amount of time after TCP/IP connection - public const int MQTT_CONNECT_TIMEOUT = 25000; + public const int MQTT_CONNECT_TIMEOUT = 30000; + // default inflight queue size + public const int MQTT_MAX_INFLIGHT_QUEUE_SIZE = int.MaxValue; /// /// Listening connection port @@ -66,6 +66,11 @@ namespace uPLibrary.Networking.M2Mqtt /// public int DelayOnRetry { get; internal set; } + /// + /// Inflight queue size + /// + public int InflightQueueSize { get; set; } + /// /// Singleton instance of settings /// @@ -93,6 +98,7 @@ namespace uPLibrary.Networking.M2Mqtt this.AttemptsOnRetry = MQTT_ATTEMPTS_RETRY; this.DelayOnRetry = MQTT_DELAY_RETRY; this.TimeoutOnConnection = MQTT_CONNECT_TIMEOUT; + this.InflightQueueSize = MQTT_MAX_INFLIGHT_QUEUE_SIZE; } } } diff --git a/M2Mqtt/Net/Fx.cs b/M2Mqtt/Net/Fx.cs new file mode 100644 index 0000000..3fcc505 --- /dev/null +++ b/M2Mqtt/Net/Fx.cs @@ -0,0 +1,36 @@ +/* +Copyright (c) 2013, 2014 Paolo Patierno + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation +*/ + +using System.Threading; + +namespace uPLibrary.Networking.M2Mqtt +{ + /// + /// Support methods fos specific framework + /// + public class Fx + { + public static void StartThread(ThreadStart threadStart) + { + new Thread(threadStart).Start(); + } + + public static void SleepThread(int millisecondsTimeout) + { + Thread.Sleep(millisecondsTimeout); + } + } +} diff --git a/M2Mqtt/Net/MqttNetworkChannel.cs b/M2Mqtt/Net/MqttNetworkChannel.cs new file mode 100644 index 0000000..445bcc6 --- /dev/null +++ b/M2Mqtt/Net/MqttNetworkChannel.cs @@ -0,0 +1,472 @@ +/* +Copyright (c) 2013, 2014 Paolo Patierno + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation +*/ + +#if SSL +#if (MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3) +using Microsoft.SPOT.Net.Security; +#else +using System.Net.Security; +using System.Security.Authentication; +#endif +#endif +using System.Net.Sockets; +using System.Net; +using System.Security.Cryptography.X509Certificates; +using System; +using System.Security.Authentication; +using System.Net.Security; + +namespace uPLibrary.Networking.M2Mqtt +{ + /// + /// Channel to communicate over the network + /// + public class MqttNetworkChannel : IMqttNetworkChannel + { +#if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK) + private readonly RemoteCertificateValidationCallback userCertificateValidationCallback; + private readonly LocalCertificateSelectionCallback userCertificateSelectionCallback; +#endif + // remote host information + private string remoteHostName; + private IPAddress remoteIpAddress; + private int remotePort; + + // socket for communication + private Socket socket; + // using SSL + private bool secure; + + // CA certificate (on client) + private X509Certificate caCert; + // Server certificate (on broker) + private X509Certificate serverCert; + // client certificate (on client) + private X509Certificate clientCert; + + // SSL/TLS protocol version + private MqttSslProtocols sslProtocol; + + /// + /// Remote host name + /// + public string RemoteHostName { get { return this.remoteHostName; } } + + /// + /// Remote IP address + /// + public IPAddress RemoteIpAddress { get { return this.remoteIpAddress; } } + + /// + /// Remote port + /// + public int RemotePort { get { return this.remotePort; } } + +#if SSL + // SSL stream + private SslStream sslStream; +#if (!MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3) + private NetworkStream netStream; +#endif +#endif + + /// + /// Data available on the channel + /// + public bool DataAvailable + { + get + { +#if SSL +#if (MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3) + if (secure) + return this.sslStream.DataAvailable; + else + return (this.socket.Available > 0); +#else + if (secure) + return this.netStream.DataAvailable; + else + return (this.socket.Available > 0); +#endif +#else + return (this.socket.Available > 0); +#endif + } + } + + /// + /// Constructor + /// + /// Socket opened with the client + public MqttNetworkChannel(Socket socket) +#if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK) + : this(socket, false, null, MqttSslProtocols.None, null, null) +#else + : this(socket, false, null, MqttSslProtocols.None) +#endif + { + + } + + /// + /// Constructor + /// + /// Socket opened with the client + /// Secure connection (SSL/TLS) + /// Server X509 certificate for secure connection + /// SSL/TLS protocol version +#if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK) + /// A RemoteCertificateValidationCallback delegate responsible for validating the certificate supplied by the remote party + /// A LocalCertificateSelectionCallback delegate responsible for selecting the certificate used for authentication + public MqttNetworkChannel(Socket socket, bool secure, X509Certificate serverCert, MqttSslProtocols sslProtocol, + RemoteCertificateValidationCallback userCertificateValidationCallback, + LocalCertificateSelectionCallback userCertificateSelectionCallback) +#else + public MqttNetworkChannel(Socket socket, bool secure, X509Certificate serverCert, MqttSslProtocols sslProtocol) +#endif + { + this.socket = socket; + this.secure = secure; + this.serverCert = serverCert; + this.sslProtocol = sslProtocol; +#if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK) + this.userCertificateValidationCallback = userCertificateValidationCallback; + this.userCertificateSelectionCallback = userCertificateSelectionCallback; +#endif + } + + /// + /// Constructor + /// + /// Remote Host name + /// Remote port + public MqttNetworkChannel(string remoteHostName, int remotePort) +#if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK) + : this(remoteHostName, remotePort, false, null, null, MqttSslProtocols.None, null, null) +#else + : this(remoteHostName, remotePort, false, null, null, MqttSslProtocols.None) +#endif + { + } + + /// + /// Constructor + /// + /// Remote Host name + /// Remote port + /// Using SSL + /// CA certificate + /// Client certificate + /// SSL/TLS protocol version +#if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK) + /// A RemoteCertificateValidationCallback delegate responsible for validating the certificate supplied by the remote party + /// A LocalCertificateSelectionCallback delegate responsible for selecting the certificate used for authentication + public MqttNetworkChannel(string remoteHostName, int remotePort, bool secure, X509Certificate caCert, X509Certificate clientCert, MqttSslProtocols sslProtocol, + RemoteCertificateValidationCallback userCertificateValidationCallback, + LocalCertificateSelectionCallback userCertificateSelectionCallback) +#else + public MqttNetworkChannel(string remoteHostName, int remotePort, bool secure, X509Certificate caCert, X509Certificate clientCert, MqttSslProtocols sslProtocol) +#endif + { + IPAddress remoteIpAddress = null; + try + { + // check if remoteHostName is a valid IP address and get it + remoteIpAddress = IPAddress.Parse(remoteHostName); + } + catch + { + } + + // in this case the parameter remoteHostName isn't a valid IP address + if (remoteIpAddress == null) + { + IPHostEntry hostEntry = Dns.GetHostEntry(remoteHostName); + if ((hostEntry != null) && (hostEntry.AddressList.Length > 0)) + { + // check for the first address not null + // it seems that with .Net Micro Framework, the IPV6 addresses aren't supported and return "null" + int i = 0; + while (hostEntry.AddressList[i] == null) i++; + remoteIpAddress = hostEntry.AddressList[i]; + } + else + { + throw new Exception("No address found for the remote host name"); + } + } + + this.remoteHostName = remoteHostName; + this.remoteIpAddress = remoteIpAddress; + this.remotePort = remotePort; + this.secure = secure; + this.caCert = caCert; + this.clientCert = clientCert; + this.sslProtocol = sslProtocol; +#if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK) + this.userCertificateValidationCallback = userCertificateValidationCallback; + this.userCertificateSelectionCallback = userCertificateSelectionCallback; +#endif + } + + /// + /// Connect to remote server + /// + public void Connect() + { + this.socket = new Socket(IPAddressUtility.GetAddressFamily(this.remoteIpAddress), SocketType.Stream, ProtocolType.Tcp); + // try connection to the broker + this.socket.Connect(new IPEndPoint(this.remoteIpAddress, this.remotePort)); + +#if SSL + // secure channel requested + if (secure) + { + // create SSL stream +#if (MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3) + this.sslStream = new SslStream(this.socket); +#else + this.netStream = new NetworkStream(this.socket); + this.sslStream = new SslStream(this.netStream, false, this.userCertificateValidationCallback, this.userCertificateSelectionCallback); +#endif + + // server authentication (SSL/TLS handshake) +#if (MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3) + this.sslStream.AuthenticateAsClient(this.remoteHostName, + this.clientCert, + new X509Certificate[] { this.caCert }, + SslVerification.CertificateRequired, + MqttSslUtility.ToSslPlatformEnum(this.sslProtocol)); +#else + X509CertificateCollection clientCertificates = null; + // check if there is a client certificate to add to the collection, otherwise it's null (as empty) + if (this.clientCert != null) + clientCertificates = new X509CertificateCollection(new X509Certificate[] { this.clientCert }); + + this.sslStream.AuthenticateAsClient(this.remoteHostName, + clientCertificates, + MqttSslUtility.ToSslPlatformEnum(this.sslProtocol), + false); + +#endif + } +#endif + } + + /// + /// Send data on the network channel + /// + /// Data buffer to send + /// Number of byte sent + public int Send(byte[] buffer) + { +#if SSL + if (this.secure) + { + this.sslStream.Write(buffer, 0, buffer.Length); + this.sslStream.Flush(); + return buffer.Length; + } + else + return this.socket.Send(buffer, 0, buffer.Length, SocketFlags.None); +#else + return this.socket.Send(buffer, 0, buffer.Length, SocketFlags.None); +#endif + } + + /// + /// Receive data from the network + /// + /// Data buffer for receiving data + /// Number of bytes received + public int Receive(byte[] buffer) + { +#if SSL + if (this.secure) + { + // read all data needed (until fill buffer) + int idx = 0, read = 0; + while (idx < buffer.Length) + { + // fixed scenario with socket closed gracefully by peer/broker and + // Read return 0. Avoid infinite loop. + read = this.sslStream.Read(buffer, idx, buffer.Length - idx); + if (read == 0) + return 0; + idx += read; + } + return buffer.Length; + } + else + { + // read all data needed (until fill buffer) + int idx = 0, read = 0; + while (idx < buffer.Length) + { + // fixed scenario with socket closed gracefully by peer/broker and + // Read return 0. Avoid infinite loop. + read = this.socket.Receive(buffer, idx, buffer.Length - idx, SocketFlags.None); + if (read == 0) + return 0; + idx += read; + } + return buffer.Length; + } +#else + // read all data needed (until fill buffer) + int idx = 0, read = 0; + while (idx < buffer.Length) + { + // fixed scenario with socket closed gracefully by peer/broker and + // Read return 0. Avoid infinite loop. + read = this.socket.Receive(buffer, idx, buffer.Length - idx, SocketFlags.None); + if (read == 0) + return 0; + idx += read; + } + return buffer.Length; +#endif + } + + /// + /// Receive data from the network channel with a specified timeout + /// + /// Data buffer for receiving data + /// Timeout on receiving (in milliseconds) + /// Number of bytes received + public int Receive(byte[] buffer, int timeout) + { + // check data availability (timeout is in microseconds) + if (this.socket.Poll(timeout * 1000, SelectMode.SelectRead)) + { + return this.Receive(buffer); + } + else + { + return 0; + } + } + + /// + /// Close the network channel + /// + public void Close() + { +#if SSL + if (this.secure) + { +#if (!MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3) + this.netStream.Close(); +#endif + this.sslStream.Close(); + } + this.socket.Close(); +#else + this.socket.Close(); +#endif + } + + /// + /// Accept connection from a remote client + /// + public void Accept() + { +#if SSL + // secure channel requested + if (secure) + { +#if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3) + + this.netStream = new NetworkStream(this.socket); + this.sslStream = new SslStream(this.netStream, false, this.userCertificateValidationCallback, this.userCertificateSelectionCallback); + + this.sslStream.AuthenticateAsServer(this.serverCert, false, MqttSslUtility.ToSslPlatformEnum(this.sslProtocol), false); +#endif + } + + return; +#else + return; +#endif + } + } + + /// + /// IPAddress Utility class + /// + public static class IPAddressUtility + { + /// + /// Return AddressFamily for the IP address + /// + /// IP address to check + /// Address family + public static AddressFamily GetAddressFamily(IPAddress ipAddress) + { +#if (!MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3) + return ipAddress.AddressFamily; +#else + return (ipAddress.ToString().IndexOf(':') != -1) ? + AddressFamily.InterNetworkV6 : AddressFamily.InterNetwork; +#endif + } + } + + /// + /// MQTT SSL utility class + /// + public static class MqttSslUtility + { +#if (!MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3 && !COMPACT_FRAMEWORK) + public static SslProtocols ToSslPlatformEnum(MqttSslProtocols mqttSslProtocol) + { + switch (mqttSslProtocol) + { + case MqttSslProtocols.None: + return SslProtocols.None; + case MqttSslProtocols.SSLv3: + return SslProtocols.Ssl3; + case MqttSslProtocols.TLSv1_0: + return SslProtocols.Tls; + /*case MqttSslProtocols.TLSv1_1: + return SslProtocols.Tls11; + case MqttSslProtocols.TLSv1_2: + return SslProtocols.Tls12;*/ + default: + throw new ArgumentException("SSL/TLS protocol version not supported"); + } + } +#elif (MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3) + public static SslProtocols ToSslPlatformEnum(MqttSslProtocols mqttSslProtocol) + { + switch (mqttSslProtocol) + { + case MqttSslProtocols.None: + return SslProtocols.None; + case MqttSslProtocols.SSLv3: + return SslProtocols.SSLv3; + case MqttSslProtocols.TLSv1_0: + return SslProtocols.TLSv1; + case MqttSslProtocols.TLSv1_1: + case MqttSslProtocols.TLSv1_2: + default: + throw new ArgumentException("SSL/TLS protocol version not supported"); + } + } +#endif + } +} diff --git a/M2Mqtt/Properties/AssemblyInfo.cs b/M2Mqtt/Properties/AssemblyInfo.cs index f99a6c1..b218618 100644 --- a/M2Mqtt/Properties/AssemblyInfo.cs +++ b/M2Mqtt/Properties/AssemblyInfo.cs @@ -1,46 +1,44 @@ -/* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library. -*/ - -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("M2Mqtt")] -[assembly: AssemblyDescription("MQTT Client Library for M2M communication")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Paolo Patierno")] -[assembly: AssemblyProduct("M2Mqtt")] -[assembly: AssemblyCopyright("Copyright © Paolo Patierno 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -[assembly: AssemblyVersion("3.3.0.0")] -// to avoid compilation error (AssemblyFileVersionAttribute doesn't exist) under .Net CF 3.5 -#if !WindowsCE -[assembly: AssemblyFileVersion("3.3.0.0")] +/* +Copyright (c) 2013, 2014 Paolo Patierno + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation +*/ + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("M2Mqtt")] +[assembly: AssemblyDescription("MQTT Client Library for M2M communication")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Paolo Patierno")] +[assembly: AssemblyProduct("M2Mqtt")] +[assembly: AssemblyCopyright("Copyright © Paolo Patierno 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("4.3.0.0")] +// to avoid compilation error (AssemblyFileVersionAttribute doesn't exist) under .Net CF 3.5 +#if !WindowsCE +[assembly: AssemblyFileVersion("4.3.0.0")] #endif \ No newline at end of file diff --git a/M2Mqtt/Session/MqttBrokerSession.cs b/M2Mqtt/Session/MqttBrokerSession.cs new file mode 100644 index 0000000..859213c --- /dev/null +++ b/M2Mqtt/Session/MqttBrokerSession.cs @@ -0,0 +1,65 @@ +/* +Copyright (c) 2013, 2014 Paolo Patierno + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation +*/ + +#if BROKER +using System.Collections; +using System.Collections.Generic; +using uPLibrary.Networking.M2Mqtt.Managers; +using uPLibrary.Networking.M2Mqtt.Messages; + +namespace uPLibrary.Networking.M2Mqtt.Session +{ + /// + /// MQTT Broker Session + /// + public class MqttBrokerSession : MqttSession + { + /// + /// Client related to the subscription + /// + public MqttClient Client { get; set; } + + /// + /// Subscriptions for the client session + /// + public List Subscriptions; + + /// + /// Outgoing messages to publish + /// + public Queue OutgoingMessages; + + /// + /// Constructor + /// + public MqttBrokerSession() + : base() + { + this.Client = null; + this.Subscriptions = new List(); + this.OutgoingMessages = new Queue(); + } + + public override void Clear() + { + base.Clear(); + this.Client = null; + this.Subscriptions.Clear(); + this.OutgoingMessages.Clear(); + } + } +} +#endif \ No newline at end of file diff --git a/M2Mqtt/Session/MqttClientSession.cs b/M2Mqtt/Session/MqttClientSession.cs new file mode 100644 index 0000000..7613880 --- /dev/null +++ b/M2Mqtt/Session/MqttClientSession.cs @@ -0,0 +1,33 @@ +/* +Copyright (c) 2013, 2014 Paolo Patierno + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation +*/ + +namespace uPLibrary.Networking.M2Mqtt.Session +{ + /// + /// MQTT Client Session + /// + public class MqttClientSession : MqttSession + { + /// + /// Constructor + /// + /// Client Id to create session + public MqttClientSession(string clientId) + : base(clientId) + { + } + } +} diff --git a/M2Mqtt/Session/MqttSession.cs b/M2Mqtt/Session/MqttSession.cs new file mode 100644 index 0000000..712a854 --- /dev/null +++ b/M2Mqtt/Session/MqttSession.cs @@ -0,0 +1,63 @@ +/* +Copyright (c) 2013, 2014 Paolo Patierno + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation +*/ + +using System.Collections; + +namespace uPLibrary.Networking.M2Mqtt.Session +{ + /// + /// MQTT Session base class + /// + public abstract class MqttSession + { + /// + /// Client Id + /// + public string ClientId { get; set; } + + /// + /// Messages inflight during session + /// + public Hashtable InflightMessages { get; set; } + + /// + /// Constructor + /// + public MqttSession() + : this(null) + { + } + + /// + /// Constructor + /// + /// Client Id to create session + public MqttSession(string clientId) + { + this.ClientId = clientId; + this.InflightMessages = new Hashtable(); + } + + /// + /// Clean session + /// + public virtual void Clear() + { + this.ClientId = null; + this.InflightMessages.Clear(); + } + } +} diff --git a/M2Mqtt/Utility/QueueExtension.cs b/M2Mqtt/Utility/QueueExtension.cs index 988198f..70175c3 100644 --- a/M2Mqtt/Utility/QueueExtension.cs +++ b/M2Mqtt/Utility/QueueExtension.cs @@ -1,25 +1,23 @@ /* -M2Mqtt - MQTT Client Library for .Net -Copyright (c) 2014, Paolo Patierno, All rights reserved. +Copyright (c) 2013, 2014 Paolo Patierno -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. -You should have received a copy of the GNU Lesser General Public -License along with this library. +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation */ using System; using System.Collections; -namespace uPLibrary.Networking.M2Mqtt +namespace uPLibrary.Networking.M2Mqtt.Utility { /// /// Extension class for a Queue diff --git a/M2Mqtt/Utility/Trace.cs b/M2Mqtt/Utility/Trace.cs new file mode 100644 index 0000000..c299ebe --- /dev/null +++ b/M2Mqtt/Utility/Trace.cs @@ -0,0 +1,86 @@ +/* +Copyright (c) 2013, 2014 Paolo Patierno + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation +*/ + +using System.Diagnostics; + +namespace uPLibrary.Networking.M2Mqtt.Utility +{ + /// + /// Tracing levels + /// + public enum TraceLevel + { + Error = 0x01, + Warning = 0x02, + Information = 0x04, + Verbose = 0x0F, + Frame = 0x10, + Queuing = 0x20 + } + + // delegate for writing trace + public delegate void WriteTrace(string format, params object[] args); + + /// + /// Tracing class + /// + public static class Trace + { + public static TraceLevel TraceLevel; + public static WriteTrace TraceListener; + + [Conditional("DEBUG")] + public static void Debug(string format, params object[] args) + { + if (TraceListener != null) + { + TraceListener(format, args); + } + } + + public static void WriteLine(TraceLevel level, string format) + { + if (TraceListener != null && (level & TraceLevel) > 0) + { + TraceListener(format); + } + } + + public static void WriteLine(TraceLevel level, string format, object arg1) + { + if (TraceListener != null && (level & TraceLevel) > 0) + { + TraceListener(format, arg1); + } + } + + public static void WriteLine(TraceLevel level, string format, object arg1, object arg2) + { + if (TraceListener != null && (level & TraceLevel) > 0) + { + TraceListener(format, arg1, arg2); + } + } + + public static void WriteLine(TraceLevel level, string format, object arg1, object arg2, object arg3) + { + if (TraceListener != null && (level & TraceLevel) > 0) + { + TraceListener(format, arg1, arg2, arg3); + } + } + } +} \ No newline at end of file diff --git a/M2Mqtt/WinRT/Fx.cs b/M2Mqtt/WinRT/Fx.cs new file mode 100644 index 0000000..6d62d28 --- /dev/null +++ b/M2Mqtt/WinRT/Fx.cs @@ -0,0 +1,36 @@ +/* +Copyright (c) 2013, 2014 Paolo Patierno + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation +*/ + +using System.Threading; +using System.Threading.Tasks; + +namespace uPLibrary.Networking.M2Mqtt +{ + /// + /// Support methods fos specific framework + /// + public class Fx + { + + public delegate void ThreadStart(); + public static void StartThread(ThreadStart threadStart) + { + Task.Factory.StartNew(o => ((ThreadStart)o)(), threadStart); + } + + public static void SleepThread(int millisecondsTimeout) { Task.Delay(millisecondsTimeout).RunSynchronously(); } + } +} diff --git a/M2Mqtt/WinRT/Hashtable.cs b/M2Mqtt/WinRT/Hashtable.cs new file mode 100644 index 0000000..a42532b --- /dev/null +++ b/M2Mqtt/WinRT/Hashtable.cs @@ -0,0 +1,27 @@ +/* +Copyright (c) 2013, 2014 Paolo Patierno + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation +*/ + +using System.Collections.Generic; + +namespace uPLibrary.Networking.M2Mqtt +{ + /// + /// Wrapper Hashtable class for generic Dictionary (the only available in WinRT) + /// + public class Hashtable : Dictionary + { + } +} diff --git a/M2Mqtt/WinRT/MqttNetworkChannel.cs b/M2Mqtt/WinRT/MqttNetworkChannel.cs new file mode 100644 index 0000000..305992c --- /dev/null +++ b/M2Mqtt/WinRT/MqttNetworkChannel.cs @@ -0,0 +1,179 @@ +/* +Copyright (c) 2013, 2014 Paolo Patierno + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation +*/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Windows.Networking; +using Windows.Networking.Sockets; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.Storage.Streams; +using System.Threading; + +namespace uPLibrary.Networking.M2Mqtt +{ + public class MqttNetworkChannel : IMqttNetworkChannel + { + // stream socket for communication + private StreamSocket socket; + + // remote host information + private HostName remoteHostName; + private int remotePort; + + // using SSL + private bool secure; + + // SSL/TLS protocol version + private MqttSslProtocols sslProtocol; + + /// + /// Constructor + /// + /// Socket opened with the client + public MqttNetworkChannel(StreamSocket socket) + { + this.socket = socket; + this.sslProtocol = MqttSslProtocols.None; + } + + /// + /// Constructor + /// + /// Remote Host name + /// Remote port + /// Using SSL + /// SSL/TLS protocol version + public MqttNetworkChannel(string remoteHostName, int remotePort, bool secure, MqttSslProtocols sslProtocol) + { + this.remoteHostName = new HostName(remoteHostName); + this.remotePort = remotePort; + this.secure = secure; + this.sslProtocol = sslProtocol; + + if (secure && (sslProtocol == MqttSslProtocols.None)) + throw new ArgumentException("For secure connection, an SSL/TLS protocol version is needed"); + } + + public bool DataAvailable + { + get { return true; } + } + + public int Receive(byte[] buffer) + { + IBuffer result; + + // read all data needed (until fill buffer) + int idx = 0; + while (idx < buffer.Length) + { + // fixed scenario with socket closed gracefully by peer/broker and + // Read return 0. Avoid infinite loop. + + // read is executed synchronously + result = this.socket.InputStream.ReadAsync(buffer.AsBuffer(), (uint)buffer.Length, InputStreamOptions.None).AsTask().Result; + if (result.Length == 0) + return 0; + idx += (int)result.Length; + } + return buffer.Length; + } + + public int Receive(byte[] buffer, int timeout) + { + CancellationTokenSource cts = new CancellationTokenSource(timeout); + + try + { + IBuffer result; + + // read all data needed (until fill buffer) + int idx = 0; + while (idx < buffer.Length) + { + // fixed scenario with socket closed gracefully by peer/broker and + // Read return 0. Avoid infinite loop. + + // read is executed synchronously + result = this.socket.InputStream.ReadAsync(buffer.AsBuffer(), (uint)buffer.Length, InputStreamOptions.None).AsTask(cts.Token).Result; + if (result.Length == 0) + return 0; + idx += (int)result.Length; + } + return buffer.Length; + } + catch (TaskCanceledException) + { + return 0; + } + } + + public int Send(byte[] buffer) + { + // send is executed synchronously + return (int)this.socket.OutputStream.WriteAsync(buffer.AsBuffer()).AsTask().Result; + } + + public void Close() + { + this.socket.Dispose(); + } + + public void Connect() + { + this.socket = new StreamSocket(); + + // connection is executed synchronously + this.socket.ConnectAsync(this.remoteHostName, + this.remotePort.ToString(), + MqttSslUtility.ToSslPlatformEnum(this.sslProtocol)).AsTask().Wait(); + } + + public void Accept() + { + // TODO : SSL support with StreamSocket / StreamSocketListener seems to be NOT supported + return; + } + } + + /// + /// MQTT SSL utility class + /// + public static class MqttSslUtility + { + public static SocketProtectionLevel ToSslPlatformEnum(MqttSslProtocols mqttSslProtocol) + { + switch (mqttSslProtocol) + { + case MqttSslProtocols.None: + return SocketProtectionLevel.PlainSocket; + case MqttSslProtocols.SSLv3: + return SocketProtectionLevel.SslAllowNullEncryption; + case MqttSslProtocols.TLSv1_0: + return SocketProtectionLevel.Tls10; + case MqttSslProtocols.TLSv1_1: + return SocketProtectionLevel.Tls11; + case MqttSslProtocols.TLSv1_2: + return SocketProtectionLevel.Tls12; + default: + throw new ArgumentException("SSL/TLS protocol version not supported"); + } + } + } +} diff --git a/M2Mqtt/WinRT/Queue.cs b/M2Mqtt/WinRT/Queue.cs new file mode 100644 index 0000000..bc6728f --- /dev/null +++ b/M2Mqtt/WinRT/Queue.cs @@ -0,0 +1,27 @@ +/* +Copyright (c) 2013, 2014 Paolo Patierno + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Paolo Patierno - initial API and implementation and/or initial documentation +*/ + +using System.Collections.Generic; + +namespace uPLibrary.Networking.M2Mqtt +{ + /// + /// Wrapper Queue class for generic Queue (the only available in WinRT) + /// + public class Queue : Queue + { + } +} diff --git a/M2Mqtt/bin/Release/M2Mqtt.dll b/M2Mqtt/bin/Release/M2Mqtt.dll index 915dcd8c2bc349c80c646b36301078b5e2058080..f3f4a492a64fb3b7c99452385238ec7587824633 100644 GIT binary patch literal 49664 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 literal 38912 zcmeHw3w&Hv)$clI&YYP{l4)j=&LnBtPU(ZtrlGV2N-5AZN!vi$q)GaukfzDB4NWp- zW&$lW5JUJ6MMc32_rKQO=gi5Yh@aQr z@BZ%Zrk!=xYp=cc+H0@9&ps!UW$Uga9}!vjeE1>Jy}0tvLV+I*1|belygy8L=RGy) zUZe4;No`xYGl^~K)aGENY)mH7{e6kM9Yoa&Rm8>o-3{BCdo9l@h z4IdpSdT^an+pBa~BHx%rbR{^p;+}Imt_ge&;zLvn8?RicXFvX7z@L_v={2zsf9uFfX6XF(5M0RWlw zRks_I{3M7rRHrlPPEgXegFv{W|ACL^XCa2GI-TrEL6O@^uj0eDuEEFivyfOFw-|62p9>6AqW_g9EKoZ9Of_t0b{bm5Cn`V4nq(yraBBkPBeXq zQv*b?l1kKSPhW0UB144C5})M{Ta_?tPhaHf#41s-tzy(BOb<>;5mhAYuPQWsCAnh0 z$_Oll?5ZQ}R210dRnubmGps;-O}=6T_Ei1R^f`F0U>dVwTc#LA&cR$T=}y$cKVhqC zOxQm&KV8Fe%$(3Ua2BH0GIuhZSzuZz1b9gCtN{d=W%>s&?v@$01~4AbWLOn6!~OwO zW0{q!(TRcajK(sE*3babhKr*IE}qYL1_2ftDEF}Qa%?0;M`IjN<)?f?=j8|9DT&2m zB_;8R3o0@H*^$MwymHGwnjD_c<+VI)h5g#VxLF}ujgDXlBQj)Rf7lKO!ohG}IFz{% zm4r;?9doC+W2bmSykPER4`k}u0cG6uoeWcOko`(we)>P*C zkT2#3_Izk%5dEPH3WWv`2Pn@l1?PlJJGBuCmB#>N;URNsY7@};x1uAauM)OwGn`k6 z!NS$9gi~=1RKjbx1}iZfxcVz$8rM+b*J%2>0n8t%`gm|XRU=+eHA7W38n#~&5YAA%cu}zX>d%UWxS?EGS@)Q?e5Va%u1v%Jx5GB zQw<%NG37aFMOA^hlfx#3{)VQRJGoxwafCTMRAkMZXzt_)ba>pys(js71Y1at7>3j~ zcS?zX8Iz_@N4?{){>;S{RYfw_d)jI4WLH)m3sx5P&HPuQ(=fDbK()Rk{}l>MSN*oB zJV8;_ZyzD85Soiv%e=%E(cKeqO}zkM+Of)cxZ0_o08Z~SC17(F4880f9yR_ZN`}YrG$Z;iwU}2O zsjj9kbqnh7rEU|rUf}5hHvncH1W4JKM(~KN%2iQyHGP?Huq?!~J!%E58n3P}`lOin z0h@S@)xz2YCX2-;_zDw$Wm%MEXP7%NA+0KOkgw*s3G^iuW0CZq{mnlrYJ4e7DtnsA zDLtg7_bYbBg!VZ+Y7+B^p>bsM*dK#6V1Ebph#WMKb8Kx@=3tLgW(wvxm@zZp&XKmu zr=}1*p~PYg-5e)ORej2UmBJDgs*0IA!!R*3VD7|VS<@AHPR8Jt>2?*+xWlO&+1lWC zAVdg7BJq$fwGPcIo73S-@c@+0S!3=*IPub9mxv`+r&?llYAkg%Tk#KRt0E$P9hkaWZ;qI5AaYHbM0H6O zmnJ<$V_sG``(;0xfr;+=rDD$3AyxRY2op2)C@TLL?wC0>^Cf5&=6Q;xz6g$)r*b_f zzabu~Hg_VyInGyvnrPl`xGvTGDW%mZr$4)>d|$3Vzcu83B@zp%bdo>sX>fEnr-0g3 zV^sVFQYNU-jJ#NAVyJQ|!pe@FxEmUQ*zN>}OM7|eGX=r;+IqwdcKVVR=##ka!{=N0 zAcGMS5w+v98J~On32ykaT8UzkH5<3H#oL9Acth5kX zGn&-e8IY`kyu?PU=BsRnwPq*pXV$7RXUr}#D~~{fp(ZwFAiXkjhNHK(8_Oq`yVhN>eH7iwKeO+)NqU#gaSWIoQ2Z0EQFD3|EfN2e!Gk=Rjs zl}c5jOco_fPwqMHhv>B}xo$`hUko;KEFFap<8}D7;DcBsY_%wfPcJ?!v0Bh#g;BY{v>q^m?G2&TBx(PJIT=#0|@3 zxD*E>fqHf(XAEp@iSOIr!Dk1ue;CX^<8wP!fU)@Sl|L-!&%9ef*r_W3W=70FE@#f$ z?F!-xF9dD|DvyNLyd5roJ~kK>(2HB1T_(%NjH!wl2 zek9**e*UoL+inBm3*WEeaC z2Ae?%-@N(vS(qV{Al8xN38>jesmRl;R@gt`(@?kL%B>zxVjDFt1(BMz%DWy*-U`+h zha+KkY{Mvc4BM1&oIeBTzcLCBx}z{|;XRCJu>m(z51|5&qZjUhX!M~T;i$~xC|f)x zGR9Lr^%!_|#fZ?4%XBp(W4z&Zi1}F*DRP}qG&I2T=1)PY^7_o<%Fzbp&%BXRn_%w% z@SwZ|k(cMOBQMJL1L_J>D$@yiZbZ_tHeSbLPelT?G-466$h`p*NHP^?_ z!Q8|OsMc0bGynOyiMyYFA`BM%H=Juv7kkrKU-MM^wlcBqwXY-Ft4BWn1t&axI$fin12zbU=gm@;_Bs} zf(*s_J-B-LcYtR7gTweg$GXD$d1U_+)gK&vO<6=O1$?77YuV9EHT!JUzgS841mRLy1nC>5I+C z8>*qG={TgG27SIBuCJARz1-Z1lLt8|n~7@*LVciwvO{*HqPh6Y@E-9VN0Lg+VpAFB|_6#yYgfhidST+P@UAE zs#ijDCtJ#(ZJ|^(fNt(Gl;y@`u&E0TaLd8qOJ)``UF?$QScn-kq8Q39i~3!i%L4jv@33=Vi7$*TW_rcNN^rs>!j? zjIp@6;c9(0O4@5vxPK3=2{AGImuw4%^)Vd6aMOJOFam31DQtQ}Yw!#UII#Ksl-Q}`f#mxFsRT}ys;of4E931H)Iu{~exP(rf>U2sFFxEQ^LBKf8VF&`o=?+7X;#roQ zd&bbVSjd+cL_1H@*HeoOU+k!$EzAue%Re=~rpRYMv)31!^}Ju5(PBU-jA?GX8@?yIS0>O#rwcG3POh?SD3kBucKJ%pjhFTsb@sM{rtr7&YG|7G}9gu#Z z4sIv%2$iC)%ofOem0f^xY#hQfBIHeb+EhEfdq50`{+ZCnUKai0w#9JZ<`0yv$N{1l`7`Tg58iF`O5m!So^m!DXSH<=7ste&41Ut_kjG6rC zK~yF2B#t|ZtjaAxR^|FRt8#BSt8ypttY?7xhG#tk+$B8g8Q`AaS%XxhBGZ*lvj2SH^>;T3ofA@rq3Bszd9!~~#t00bj-~)f+!ge4C7gm~>U_yGO=+gGG&S#`| zLYIq}J1JzTklZ&m%&Z~JeOkI388cNz#c8_Y4pZf03`c=+6~ne2Kv*MgrGpW_+rxhMUH@hHDzQG7V2gf-xNaDbs7d6n594riIA3th0axbmdP5`> z!+D@GmzOga@|e2}=Bk>88^kTUYY@0HSO9|su}Ho)n4dFP;4#S27#YPR?!#S^z?I1` zOon5)^Hyyv%$W>(OmbvL#&CZ0(_eka|3h<-1elutsYM7doUO`4p6M%@QKW_JH6_Ff z%YF6=nO4G}n&4g`_eTk_dE%CMg-k0UR$-38s)9)1@|l6SKBJZ>lkL}0fl|c_Ou%}M z(3B6dXE0)4-d(z8O+hdc;4PXB1DXyhI_S`Oiq3QBkfKA1&ZXgeNyGUvrA{rz_>Gc= zCD~3a+WQZ)a78X5Ph94~S9tIS51#ShT^@W}4lmmq<0Br&*RdIXY3hb&6jwPpHp85_ z&|z^-S8b-Op;T#~GQsFoYOr#*!5S5!JbS99=sQsDYf)Q*U zs!_C=?dl>qpeklbP%D&|I~543~hHxFt7}%~cq%Ce4SdKBE zFs8NwIDS*>sUTQSYcc8Zk^_Ro=B>h2j$9Bhu67uLfbm&}AqW`PI1E9+xYl6^a-v>& zCx+FceT=+?pjO9eDlg$^h;E=M% z$QuPtovx)qr$u|wdq;|boGTAu_eVqQqO-R+ofX8{pp<*sXc4v}|N7ofC7p$bNII$^ zqr`?1xY42S6$7*cZK_cFl^fmOi~l`4*x~Cx_i^e}>9eyQJv|^DhbL}zJRykViSgPK zyl4JD=!b2G^aIyEsvlVJk$(8n$Myr8f*<&j^8-^ng>K_3zZoY87+-N1f`IW=hargL zsWRm$J_W#wRh4)0jQowBJW8f*V%y4=ux;i0D&Ds8?G$fYPv>iFK+Nhme#0P+W$%55 zYU0GnsXEO4v2%(vJwwsefih6_97U6V%+k9Z`f=Kji*&>X7MO>}=4TyOBb7A6%$*!Pv6~V|h|f zfOo)_47GPZ&4V7X%!>9n?kc$NASU1zBmRLQu?aHX&IG$ZV(D-QX{>b4%!o68oJKq? z!5gur$|T86E_O-)&pjh}t{KBaJu9Maw`C?^BWy`5;aQ(QuvU2{!01{$1r^?!;^2%U z^sPC14o|uTTy?DsRSvsn)PngnR4lVu5O={G5E6444pwqOz_`O<2m%HsveFO)jJq6$ zAYgpmVF-egKeZ?iut`jIHiXH}#d)%GDW2@2jM8_5mKF-)WRr3Kp1j+9$h^z7j~dS` zaN-$0mAo`${dxFf=Uq01yvvtdXfVZj_a44--W6n(&YTtE2fQ%I;fcz5ZAYg5*Vp5T z8$MnZ`iNYk+B~Y$xDP{|#`7A7e9oHv_@R8}nT%71%*CnNW4W2eb-mAQrpGp}^5 zB&`9i6wmR3(hQ^rBygwEdjom1e-%`5Wzo}ULt^N9$BRx3uS^u%GeW`mngYB*v4%5= z>Jv_ffAi0+KZz?*$44H-Dff*!u-q+YV7WzQo+W`h8L@Zo9nrv_s$6t0vYORLG= zOP|53K^i)XwXZHxv-kltNoKJin6w8Rh9FqD^zzSgOi$rL?OTqTAYgpkVF&`ocN~Tw zY1a@JfFV}HK0<`hKImu*;wJQbl`MF@@!TW+_E781h{?^?u&VGSH@BqbEYW%XmiC^S1PHF4@Eh)|2CTYj)QTgk;s9*AzAh|mt zAu)NdomX-}!1$iS5G0r1SdO$LT$ovovB6w=gtbvhuDk)&43SSi#yqzPdcMkL!0FR) zR-!Nzc*dzl5HOx~7=obQBB`OW9M8B-tFi(Yj!lzJ#0XA7Z2II^DfDZ!`UTd*)l0Rx z7edX-G8CLMM!gMWR*nPpfiXRX4d|lQlNTBKH*gDJ^Q_s`v#O7tb?oe8L6Dz%0B^2B zUN{%;u*@WSxB_@~!C1X~^$Iz$Z%s+L^T_rX>F7Q@?^NW~c3!Er0 zE&R&_zESo_u}!}S-y09nXUfXTZTe-PGGf!68f&u%cBe*D}AImM3v>wLUT{?RTUw+w3zXN3f3=(*8$#8$eK?TGc2bU@%C(3 zYWcfZI}oglglM6(O8sFRTRSfD)p0hS%5i%N0NKkm3@{1?-H7X3 z(cW>2G7u5xmG_Q28WJ0yBKjRT)$YT`^Is(tLQUiARt}> zbs?hW8llR@F!ed6P|jD*R&LSMS$OgGyJgmM3`+pM-x$BVBD?Pq)ZLJhsy3N`x@|;ad!cJA$$*DPvBm_7X>bg zFy~u^2LSI7&aHy4j4-D*?;zmSk%s~Qmd_SGEjl-qv-Fa2Ty}N%G3MueAMm!|j{qkH zp9kC$dK4_;tV=i+&AwmB3vBBc*SF^ZVG_fFI_uwO{6Oo4*%gyj$RxL+=2; zUHC_bSQ?Xl-6H)uQu=jd`Cp);dZ+q+bMbrNe69Ec->SjC`R@XJS>WL|;|l~f3ycC9 zv_N{-BJdX>=3F4~eBs;#7@@5NZ~0mWKk&T+__W3FizdUA;FtOtZx-p5!nsp8X9{Pr zaN31)xNw$A*#`|Sb%ww}fs+OPMBrNjTLgYZ;5>n!6<8(~Oo2xNt{Qxl7~U@M6~NZP z7kqC6J}R&mIu_j!W;hPxZqfE)#v>*7pwuQQbwApFaP|u4bm7bvPJ?iYg|k{XY2nNR$DqFyaul_dFy0(^ z&$P&f2Q1nOZy5Am5y#%+HtS3l&E*nrM-?z<7c_Y;aKu)GOp77Uk(Q7Rd~1FPFeouI z8PK*nB*xy1ge-WqFl0F#WO;B76F5`gxl!hP#%4IFn4uM8m=;b@IB$++PEs`Y2)q~2 zpsE13N5N;M<%kmwv)$ef};46S-1k9*BNmqnExDZI4s0J5~E z{ErungO)=b%pYg*&WC><@qZ(X`#42_dLJvjkD3$A*cEKbn`HUuVojX^%A$js`b4nO z!rL~Q+6~I4S(8u<<=ze)X$5GDrtIL6R*?2-YEJMNE012#)G44sRCJgs_v_#ZRz5A& z6y=>@70`gD9t$48ugzU3)cL^|f;WJ=T+5CL?7{oh*JxR7pa|6GwXCb;0N#oIvX=Fi z+yLqhEqk@-09KECwd{9AH-LIj%NmO7tOz}>W$TKUf_g^FzH0Bm?-jnJWe?azpnjoc zzQR5Dow(m>S#e9qJVtAwn{(pyli)>tZasUn=$SGm+_RykcQ)cMp{zS){c zZM>bOUG#og+L}UHmpa>;N>{toxmG3J?NS$6Gl{>Lg3E=lPdk>Hg*u;j+>fOnX=-xh zv(~Zvo~vlq6zl=j1CnLr=#+|Z7m{x z6CZnPx;XD)tCl)6bxZIGtB&|ZZDowvc+^zQ{(cm2d9@cbuj;VYY9~x zp~`))=%?0Fs&c9SvKr`aO)V(@jdcn=IbCVK9R0SnjP~N#lj}P@fZZok=Zx4>9V`-FNUk7wg@ zdRWOY?w^G0S(kd>YNA(NYO~csMf177Hw>PQtLaOc;(5E8UeFZJ=`}QmZ-b!8qq&w& z7wQ_ytMK{P(k7vH1;c2|I_eYZf?yo?a_i`9Et?P7sdS!}Er#q=x>(CzDOq5wrz^GW zFC_-8r|Y$BQ)Gd08r`U60}%r|u3NQiV#NaEbh=B+=HM3ebb3I`mO$1{-_^1;khRm3 zLR}L)y&&Y@K(7mRKD=bp2Aa2k`*#(MEgu8fl|tO&y6j-9`6nY60eS7kyV#&lDfPuj77SQ$H)dL4G-xYk$Q5`*^8;6Fn!g zPf=xbJgA}*SavnOh-0e1n^p+*DQWc?v{opzv!Kp8gE};ILBUc`XDCWEx6&tsx`rMO z&+u=huL$)BaecjX1mChFn?8q((o08a>X*nUy)<7_RV8&+A1%_btsj0b`NBwl4rfR}PpdQjxykHNVMNepINHI%h+vzP${WAXvP=8jGczy?2Cy94K710iw;!<<`=g_4>@u<}JKSi?^ zv8Ec8T{L&Glneg0puxY376^5I@ZEytpz4(jHkbP^qBd8v&3`ei*VMZO-?lEMq^A61 z*7`4@UQLY|b2_NAH1*iHF8`7ug{FQw*1+BBb(;D``2yoo z+NY`C<0R-(9iO|T@0ZanN~Wp%gyP<&{Fl*VLaE+fL4WXQUO`B`t|zXbnL=^7v;9}l z@j|H@cGD@Y>^%Q&TJI@$6Nz1OL{ho4H(}Ja3JJ)xS z|7yBIs9j>^8oE|fY~>o-rzo*e01S*Wg&W!hbDYE7Y#w^@!c;>GPWU0%G@i z+OMhK7L@w;&>fn3x8Qke58bCJ?!)KkK}~V(pQ9%fCAIIR7hURF|6Y2-rEc)wKvVcY zN=McgsI<{(*?#{QD2ZooJo~wZFH)~i7X-P6FVa~`h8mu*zC;&lP42^&=`yX!efTn6 zqh;KO{d9wtaUb^6m$i&D<}Gx)rZ{8XLicKlGv=*yP?zJ3c`H4xWt=f@qo=iuGv;md zl9HiiH~7Co0UUQaeYlgxXo~xACzUEnymXiRijU)oyXb((c%<*}-$i3^80uKLo8p>c zD|b^uQDWsDnkSSR>3isHq1ej3{(I;&J{Z*g{3aze#a6yaeVY33(o+Aube5)kWzSpp z(s_!K8t$Vjg<_ir{r6GH3bv{0dw~8R)UM#%_+$PD=x>@jDgG2FvqjbZ3~D$)Ax*tn z`n+|3iZ#U}{Vf`&DQ?-fXo{kw_HWZ1mwML!ZCWD~x8%nG=EI{wTwOZ6S_moxYa+Q`;-h; zeD(|ULzfEKFVQQy+<)RFkDt;Tn!2qZ1nT#$rcE!?yDk;CUnc*l(rVcJU*^w-X#Si9 zxbNfaeJeTCBgWZ<=7efIK}>jOKmAU(*BjERJmWfva<`1wtwSN>B0r}TQ0@r z-X2o!53cO&!X@@QE|o4^ZvWY(xZK}{lzY#Won5%r{<}-13pdywxD=PeaRq*y{^3?x zDDK}@n@w5W)sMMW$vD)Q4EV;vn+EcnENB^LbJLUVw17@D*k z)_inS!Dt){Cr2LX?OKp*EE#~Jm@4+(xoLup5Nlz~1@7v?DAzk+5IM;QA^y^vZ zm~^h-%r{7J7NL|E_u-6)dmVutgT5VM_>RCTY5#*!<|zCPv6F))?G;~fDU;rCaf6O5 z=Tb5B+N4|11|J(g6VVHd0%FWZzm=BY@B%GV z`e%v-FXvEpK0bf+(dTnW9_x=Ynj>3PC|>>OHh4=N3O`h@z4{|-IaK_N!|3xlGxtkH z(MUTgen#5#a&q>4IJ+Kh^Pg?=$ToQE^3Ie)X>yxQDm_FTDkujbJ-EJ+bS8|RkO@Z9$Zz#@Fr8N=fQ)(C76xI*As zfgJ*`2efID(G#9TM;L1gX99n^XcC^@Gyf=~zv6P-iJUg>YCP8yn*zJ3F|r45Sm6!> zPmWo$2K-xbjyElS2cUr`%?-ws@&mw2@oc`scr*4>XnwQkRp2Yie~Pzf4^;e;x{c?{ z--PBd(7coGEBGDlFuKC;pwy#dryDoY_i-1^oC7F(d(lF0eiB`1yh(hP_adEJxWIT3 zHEuC>qxLND_se!d%4d780bXp}0{#`i&nUmfxY3w4<|gPI%)b@*+Q9w5A1`8@&+T56 zv)CQ($Ke#;H~^gojq-{@^KoMyl>>gVEMcyI{lkI(K3;1+ zjT&uQVZ2bZ9{l%lHdi2=5`h);R^V=Piogo#*A#(ObZ_xv=3Ig7VYYCf;ysh`|78Yo z$`tkS_vIevi{eD45BLt?K|EEC0v@0^;6bVYe3&KyK1S03p9IHGZ_y0Ew`nHep9KDu zjs*U9Itq}CIe?aNG+j8g!+jOCDSk-9!1I)kF~ zDdAi!wO=Z=Un#X;EjrhU&R&800K>H3SOGtL#W)?-?vS!~OFQosodW_73jbl)4A5gn zC*YH4gP(q6Yyx~vdh~*5zHDp(=T))rx^RANoB_^T(uTK%^CzPRoWDv7|1OrvWG`7J zdo^gXw+jR>61)U3NL^+h;1)9jxJ~e^;M>jZ;C#a5Fq06%4Z67XX4BEU<{O98Jm zcLQE+UITcY`8mM7=03oE<`)4IKDIf<$95`xY^Tb{c4qn5&Riecndf6W3w&&6p^xp< z`99~X#&_AgWLIO8@e9Cl_~yN8ykVFRID_JVb7(Q(@ze-dEBsSv1MpVipGvy|2XWq; z@x3aL48{`z7Yf`Ua8Terfd>V?Dv(Uk7r0R127!YD&lk8);Ee(g3VcxDs{&sWNIte> z2uuiEC~$+oL4o@Oz9!JHSbvhh8i5@G&lh;3!fn>MQQ(6DUlV8qq^!UVd5jMV+$Zp$ zz*hxQh@}aE3k7ZvI4E$Rz=HxQBANm>2pkl+PvAj;uL`87XbM~?utwkpfgJ(|1)eYP zMu86sd`)0YIa}xun3&A?LV+6u4hkGg3-Ps?T{!!_n7)8_VGiO=lP8hKe@uTx{wOw% zH0Bz0#y!TP#*dAcjQ=uzW&F{YX*QTmW{0`WJjdK+K5zcYw0u6S?u0iq_#Lp@f_nic z1@{4N3Em7iK5su(I)i+G7S=Z#PZ)z#0<0AG4rPeUsd#&78lLsdKzvp~G85l0GYc=I z9ffaXIR;wCLJJuh-$y%+PQY)moP^&>TtpM77QZo4PYGH~lkm4JW)L3&URih@?mb=( zGF%ea3%H|%@jnzXJiT}y;CF21#|s$-!VIr3yBToGxLW{~g*AeoEbzS&=AR#7cx(m3 z^#V5*d=>chVYXQuVf!huACej`Dq#MbQcGP4kcv^}U)uDR|Cp8}}thHX@`5cbKM*mmuQA0w)iUC4NLv1TcuWu|elc zi6}}Kh@1fU1^5(6e3=qq1|lgByf8`>;R$sJc48=JAhPm-mqOP-bQJHv^iq0+ES#HvmoK z(#gQv08Ltnm^X0LHx>A5Ivj8v_Ido2@Ce}R5zGAKZwBzwF&p@4-b~;d@C`=>bpV=p z-|i^jn*mL{Z8rya51@(N1;63f2WaBC)Um*~0m^;@cp5uM6VH#12c88qu>&~)_*sA^ zp7Wdpd^;fTNos(f18CAt%rt{O0cg?yW}HFi0-AIlo*)}E2xww|*8u#JfF`oZDZnoQ zH1Ygo8SqO1O}Y%eG3atYldgbo4EzC{6~K4HH+aVf(8M#zmB6nCH1Ss7YT)|-O}x{$ z7WfwdaZe9F8FJj(4?=zPk1K_(8Sw(9l-AaG?Cdlf!_;=^Abt|zaP+~ z2jD*gJFd;ZzlHrVP9OkH`i{K6co5LUJAu96e;3e1c1!_(4A4ZLJQMg+fF}I_9yREP zfF|Ay><9iFph?feqXx3+*}#7Sj~es>po#YdcLIL}(8Q_W0Pt4c+8{mi1qP^ z*75TF=JCnuLRq&v*X>&5$zOrrcJX)|U z)z#mVJdtXfmM?E_sb9RZwZ5*sp{{;;8>o}(YuoDUl&q$KuAG*wXlrX<*1Dv+R<wO^e!^nj30m)F43z zr8_yX6GrWe8*7#*V{VA$SgRWv8$B9Aw$!)PG%WXUYY}|Q+gs70h9)n+Ze_DaOUiQ0 z=B(!;coY{j*2Cry&mc6VhUF`lEkcxv&~Y&vZB?vgXq@UiOe|?zO3UE+A&lC_2A+~| zJ&PPI$Fa+5T2G;&?h)2d_XsQJo*e0Qk8nqEkMLdhV~GF`G)n{S%=(MXKy7s#I#WgD%+fdkbf(mmZ9sWsWx)!e@^ zo$RD#XJ)g@GMhEEx}!V0IF;5EFEDMXZQZz~axvzu?(XTy@pIJKE7nL?L6)G5j2wwj)tbn7s>Ii2c6g$;e1db&4n$!Z(;1CXO|bv^ZGp+!S0 z^RjBXx~QY8t6jUjwSQx#Gu^#0$t~+mos}F#(9}0l*<)g5U*=FohKO7X55NmJR%L5+Eia3HZY($XuPziuDWi7qN6vo=R-tpjiYn5 z&$|%Wn_b*fjc%ta8d}qEsax4_0d{7~Q`wrHp48ds)&e)+Pu$wxzNn*fD}FI;ad)x@ zA~#S+ik$EqDe?Gdq)=NNDd1KdQrMd9$RzCw>i>Tr#4 zMPu@;WY0)xZBMeJuNAWeTi20N*S-#qLn>vm9lhH|maoZT$=Q}2DQHfmN8z?+dPWy4 z#u$z?qOE7R``Pr4kqULmo{k-dl5#es#oYkC$?TStnh0)cX-2*Sk0--$o||s8j^tE$ zxvR4yt*kV6WHM)`KyY!F*yeDDCk7{*d#1RWm3?q)UuTlK9fap9`r4oFtHoce74sf4 zos+O)eL9^=yLvf_Wvx8-vpF6&NrifjmvqZ>2_3Xjr-L)!6VM{_(r4#_on(1flum6; zrk&!wYE_`NR88mBzSP-0$*#>ws!jE7>qsZ-w|8#o=mTBf*VWK>7Ru40XrU20l>Cur&K%VOAN24uT8cK38=Jnyfhyp^GbzOLl@ z=4v>2m*zENL2~Xp)aHsA^?jYGE=IaD=x`rnZ7KDO;*{CZ*NF-5E@vv@N33$v9(Qrd zX(zYmaBjOw&Ac%sH;YRm;;jd2#U?r9tc|=`Sk{s4+~Uj^y`O-ZgKblLEooF^vTt*C zi?X5dt|U5^gd01$xmTK-82rwk{!K-ZG^@$ zF4mXKY93eDfNcpOF+o@EiqYZzUTGqhQ;dr=P@_)sJQ8l4Cb?-EO{=0Or%+zzT>bWJ zvX4`dSaveCPES(BaNah(x;Q#qw|9pog@!OXJ^MFjs{^nW^jRehOYpf{K54N#!E(DlX~1awnr+*9bUj$PlafUx9(XP5?>mG@?j_K)T#;_D z(m7J^Q-)fU8^xSXO?q>GFS?oIa9rp{=9rnS-P;YG<8s>>`A#<~Eh!m3_HRQB~J`-kc^yZOX|ob@dIV z>`>IE#VzUCCw7e{*Xb(xQNq#6Q&wGcc-D1yZ0^H$3aQzRShX%zBbHu0hQ=8Kr+qUee9yTE|yiV{s zo8fLLMD?53yo?NGYciWfJsIwiLUcwQHi!sQRRHHUBSz3G96EeniEB~D$1Cv|93j*8 zM)38l&hdG4R-{_xPEoq!p;)#g-GPzE6A!P-2qrwKwA3wUhMa^A*W><9XE=;qpF0-0 zyz|jx=uT!a%0q(1D4n5(=9(^<*Aig7kI5z*wmCd3wL@sNC(p^dT}>T<2%c&+${A|f zcm|F|D5WlXWVPpX;QaF>-)~?#vX@rpg5qmKb>aWy-}4RCYVV{^@?n0#zw0dioVynV;2%^gO?xH6MM~oT@A=N0j5`C*L>JF|3 z##Pbd#}Cs!YGz{_>F(`x(xx@Yo5d)BCo0+&cl37m?BFriP1OwE@_T*f98h?D=Bn_WJ{7bY;*_Ms#J^ zpP=#46VRqGx5l&~s*E=H6Q)s7YJ(FUywM66(FM>D;lgMg(_0v9XVA=Gg++!@!4@if zcC;Ih!hVfy644cY)X$!)m>h^jXMvM2gV7bi$h=@Mx-^J_D}vDtz#I9JpYR!x z9g)EYBZJ?w(Ua&2`1@Aem8aP6(OyM7&6n}fsOV{ef1LSRJ_V> z@gH2X_)o1_{0@c1e`3ve+pvtFf$wiJZ2tBoe9Kb6$TLDlzENNl8e#Lp55X;1hIeEd zFn_x+Q!TvlPv!zL291SA!YH*s;)P!ZCIhA|IGXUSApoct-;T^6j{&}O41YJA0bcAU z1o3hBoM75$x(TcXpL%=}_%xU{zDox4k-=UD`xxBJU_azRd{*F7$`i)+1M9`-OnmzB zsr1>AdHBrr+2bJ_AGYks;Kgt$oTdWvI(6BrF8kEwW_8(*IpF7cCQ%YSArHZe@HV5z zEC5z8 zVe!RpawJ3(0-L!70YSSMO3R~(@qXCh3+4-ET>^bfkW_F2Pb_W|zFsY|BYFbHB3c?G zvx3vY9cTg<^~s#^lM##t1IUC^{LvuVYH*`fTbWx4F1NW-s;^`;0T7wTbYvc*a|NBt z+6$n~*M<68r>{%(wGmgFBXhi89Eh3fH@QHwR#>mE8}zjcSJW|HCdT+Mca$B+MYcf1 zwgT)lAG=WTv%2VZR}SBztvu?;kv#TBw1G=jMDgcYtY`v@D-A?*bsgmD0*-mUpYp`gCYxrDA)Y4kl zdhe;d*X%a#UUu8pKD}n z(VFe=>P~Tu$F9eX2k+!F>vhL6>w9rZuwHe4J@)tOalV_x5xHhnZ|mB~&H2Cjr}_3= z^Uh#Ak)yrXdXFCdo>grsU5Ceze9$E?7bKH%ipC{AoC+KGO{)K!e)yXOu~|_967}R%hyFVAMdHaO(#N?`=m~`jP0&Lci6&x!94z2e3CCWou!t7yptdjhiv>d3A2X zKi+}&z^alY@IGU=?B%(=J0RtD@;-kV&BjOVwAm8h2RJ)*r?n@_o5-rJZPK3|@Lq@1 z%YA7Eo?B}`9xxGH?skWSn@>wR)&mj8flv^b> zGbqLP1pG4x-w(>4S@Pj}_)Tga1-{GR`a967TuZ(7tFeFKdk_BM`yPIC8V|Un1ow0s zyu>ZsjQb#N%}5@9BdF@{q57VHZ)f3+XpTZ2EpHtvGP+P_FX~L=4vR-m{hTQMXwvm_ zWN<$mKQP53KGa{{{#1* - - - M2Mqtt - Library - uPLibrary.Networking.M2Mqtt - {b69e3092-b931-443c-abe7-7e7b65f2a37f};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 9.0.21022 - 2.0 - {F733523A-F14E-4F5A-9E7C-085CA80F52B1} - v4.2 - $(MSBuildExtensionsPath32)\Microsoft\.NET Micro Framework\ - - - true - full - false - bin\Debug\NetMf42\ - DEBUG;TRACE,MF_FRAMEWORK_VERSION_V4_2,SSL - prompt - 4 - - - pdbonly - true - bin\Release\NetMf42\ - TRACE,MF_FRAMEWORK_VERSION_V4_2,SSL - prompt - 4 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/M2Mqtt/uM2MqttNetMf43.csproj b/M2Mqtt/uM2MqttNetMf43.csproj deleted file mode 100644 index 7acbd06..0000000 --- a/M2Mqtt/uM2MqttNetMf43.csproj +++ /dev/null @@ -1,75 +0,0 @@ - - - - M2Mqtt - Library - uPLibrary.Networking.M2Mqtt - {b69e3092-b931-443c-abe7-7e7b65f2a37f};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 9.0.21022 - 2.0 - {6A6D540B-8554-4FFD-8884-8BEFCCD9AD41} - v4.3 - $(MSBuildExtensionsPath32)\Microsoft\.NET Micro Framework\ - - - true - full - false - bin\Debug\NetMf43\ - DEBUG;TRACE,MF_FRAMEWORK_VERSION_V4_3,SSL - prompt - 4 - - - pdbonly - true - bin\Release\NetMf43\ - TRACE,MF_FRAMEWORK_VERSION_V4_3,SSL - prompt - 4 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file