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 915dcd8..f3f4a49 100644 Binary files a/M2Mqtt/bin/Release/M2Mqtt.dll and b/M2Mqtt/bin/Release/M2Mqtt.dll differ diff --git a/M2Mqtt/uM2MqttNetMf42.csproj b/M2Mqtt/uM2MqttNetMf42.csproj deleted file mode 100644 index 62c26ae..0000000 --- a/M2Mqtt/uM2MqttNetMf42.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 - {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