commit ede25d06ae16a1c48742bffd8790d9f3d4853fb0 Author: BlubbFish Date: Sat Mar 31 20:42:28 2018 +0000 [DW] Flex4Grid deleted [NF] BosMon Mqtt Plugin created diff --git a/M2Mqtt.sln b/M2Mqtt.sln new file mode 100644 index 0000000..cd45efc --- /dev/null +++ b/M2Mqtt.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27004.2010 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "M2Mqtt", "M2Mqtt\M2Mqtt.csproj", "{A11AEF5A-B246-4FE8-8330-06DB73CC8074}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A11AEF5A-B246-4FE8-8330-06DB73CC8074}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A11AEF5A-B246-4FE8-8330-06DB73CC8074}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A11AEF5A-B246-4FE8-8330-06DB73CC8074}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A11AEF5A-B246-4FE8-8330-06DB73CC8074}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E4CE8710-7B51-439F-A427-9AEFFE71E682} + EndGlobalSection +EndGlobal diff --git a/M2Mqtt/Exceptions/MqttClientException.cs b/M2Mqtt/Exceptions/MqttClientException.cs new file mode 100644 index 0000000..bd58745 --- /dev/null +++ b/M2Mqtt/Exceptions/MqttClientException.cs @@ -0,0 +1,105 @@ +/* +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; + +namespace uPLibrary.Networking.M2Mqtt.Exceptions +{ + /// + /// MQTT client exception + /// + public class MqttClientException : ApplicationException + { + /// + /// Constructor + /// + /// Error code + public MqttClientException(MqttClientErrorCode errorCode) + { + this.errorCode = errorCode; + } + + // error code + private MqttClientErrorCode errorCode; + + /// + /// Error code + /// + public MqttClientErrorCode ErrorCode + { + get { return this.errorCode; } + set { this.errorCode = value; } + } + } + + /// + /// MQTT client erroro code + /// + public enum MqttClientErrorCode + { + /// + /// Will topic length error + /// + WillTopicWrong = 1, + + /// + /// Keep alive period too large + /// + KeepAliveWrong, + + /// + /// Topic contains wildcards + /// + TopicWildcard, + + /// + /// Topic length wrong + /// + TopicLength, + + /// + /// QoS level not allowed + /// + QosNotAllowed, + + /// + /// Topics list empty for subscribe + /// + TopicsEmpty, + + /// + /// Qos levels list empty for subscribe + /// + QosLevelsEmpty, + + /// + /// Topics / Qos Levels not match in subscribe + /// + TopicsQosLevelsNotMatch, + + /// + /// Wrong message from broker + /// + WrongBrokerMessage, + + /// + /// Wrong Message Id + /// + WrongMessageId + } +} diff --git a/M2Mqtt/Exceptions/MqttCommunicationException.cs b/M2Mqtt/Exceptions/MqttCommunicationException.cs new file mode 100644 index 0000000..9d2591c --- /dev/null +++ b/M2Mqtt/Exceptions/MqttCommunicationException.cs @@ -0,0 +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; + +namespace uPLibrary.Networking.M2Mqtt.Exceptions +{ + /// + /// Exception due to error communication with broker on socket + /// + public class MqttCommunicationException : ApplicationException + { + /// + /// Default constructor + /// + public MqttCommunicationException() + { + } + + /// + /// Constructor + /// + /// Inner Exception + public MqttCommunicationException(Exception e) + : base(String.Empty, e) + { + } + } +} diff --git a/M2Mqtt/Exceptions/MqttConnectionException.cs b/M2Mqtt/Exceptions/MqttConnectionException.cs new file mode 100644 index 0000000..9d178c2 --- /dev/null +++ b/M2Mqtt/Exceptions/MqttConnectionException.cs @@ -0,0 +1,33 @@ +/* +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; + +namespace uPLibrary.Networking.M2Mqtt.Exceptions +{ + /// + /// Connection to the broker exception + /// + public class MqttConnectionException : ApplicationException + { + public MqttConnectionException(string message, Exception innerException) + : base(message, innerException) + { + } + } +} diff --git a/M2Mqtt/Exceptions/MqttTimeoutException.cs b/M2Mqtt/Exceptions/MqttTimeoutException.cs new file mode 100644 index 0000000..a5b825e --- /dev/null +++ b/M2Mqtt/Exceptions/MqttTimeoutException.cs @@ -0,0 +1,29 @@ +/* +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; + +namespace uPLibrary.Networking.M2Mqtt.Exceptions +{ + /// + /// Timeout on receiving from broker exception + /// + public class MqttTimeoutException : ApplicationException + { + } +} diff --git a/M2Mqtt/IMqttNetworkChannel.cs b/M2Mqtt/IMqttNetworkChannel.cs new file mode 100644 index 0000000..bf01880 --- /dev/null +++ b/M2Mqtt/IMqttNetworkChannel.cs @@ -0,0 +1,58 @@ +/* +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; +using System.Text; + +namespace uPLibrary.Networking.M2Mqtt +{ + /// + /// Interface for channel under MQTT library + /// + public interface IMqttNetworkChannel + { + /// + /// Data available on channel + /// + bool DataAvailable { get; } + + /// + /// Receive data from the network channel + /// + /// Data buffer for receiving data + /// Number of bytes received + int Receive(byte[] buffer); + + /// + /// Send data on the network channel to the broker + /// + /// Data buffer to send + /// Number of byte sent + int Send(byte[] buffer); + + /// + /// Close the network channel + /// + void Close(); + + /// + /// Connect to remote server + /// + void Connect(); + } +} diff --git a/M2Mqtt/M2Mqtt.csproj b/M2Mqtt/M2Mqtt.csproj new file mode 100644 index 0000000..5e1ab26 --- /dev/null +++ b/M2Mqtt/M2Mqtt.csproj @@ -0,0 +1,82 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {A11AEF5A-B246-4FE8-8330-06DB73CC8074} + Library + Properties + uPLibrary.Networking.M2Mqtt + M2Mqtt + v2.0 + 512 + + + + true + full + false + bin\Debug\ + TRACE;DEBUG;SSL + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE;SSL + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/M2Mqtt/M2MqttMono.csproj b/M2Mqtt/M2MqttMono.csproj new file mode 100644 index 0000000..da48297 --- /dev/null +++ b/M2Mqtt/M2MqttMono.csproj @@ -0,0 +1,71 @@ + + + + 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 new file mode 100644 index 0000000..bf64adf --- /dev/null +++ b/M2Mqtt/M2MqttNetCf35.csproj @@ -0,0 +1,106 @@ + + + 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 new file mode 100644 index 0000000..0ca164d --- /dev/null +++ b/M2Mqtt/M2MqttNetCf39.csproj @@ -0,0 +1,87 @@ + + + + + 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 new file mode 100644 index 0000000..f8459e0 --- /dev/null +++ b/M2Mqtt/Messages/MqttMsgBase.cs @@ -0,0 +1,176 @@ +/* +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 && !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; + +namespace uPLibrary.Networking.M2Mqtt.Messages +{ + /// + /// Base class for all MQTT messages + /// + public abstract class MqttMsgBase + { + #region Constants... + + // mask, offset and size for fixed header fields + internal const byte MSG_TYPE_MASK = 0xF0; + internal const byte MSG_TYPE_OFFSET = 0x04; + internal const byte MSG_TYPE_SIZE = 0x04; + internal const byte DUP_FLAG_MASK = 0x08; + internal const byte DUP_FLAG_OFFSET = 0x03; + internal const byte DUP_FLAG_SIZE = 0x01; + internal const byte QOS_LEVEL_MASK = 0x06; + internal const byte QOS_LEVEL_OFFSET = 0x01; + internal const byte QOS_LEVEL_SIZE = 0x02; + internal const byte RETAIN_FLAG_MASK = 0x01; + internal const byte RETAIN_FLAG_OFFSET = 0x00; + internal const byte RETAIN_FLAG_SIZE = 0x01; + + // MQTT message types + internal const byte MQTT_MSG_CONNECT_TYPE = 0x01; + internal const byte MQTT_MSG_CONNACK_TYPE = 0x02; + internal const byte MQTT_MSG_PUBLISH_TYPE = 0x03; + internal const byte MQTT_MSG_PUBACK_TYPE = 0x04; + internal const byte MQTT_MSG_PUBREC_TYPE = 0x05; + internal const byte MQTT_MSG_PUBREL_TYPE = 0x06; + internal const byte MQTT_MSG_PUBCOMP_TYPE = 0x07; + internal const byte MQTT_MSG_SUBSCRIBE_TYPE = 0x08; + internal const byte MQTT_MSG_SUBACK_TYPE = 0x09; + internal const byte MQTT_MSG_UNSUBSCRIBE_TYPE = 0x0A; + internal const byte MQTT_MSG_UNSUBACK_TYPE = 0x0B; + internal const byte MQTT_MSG_PINGREQ_TYPE = 0x0C; + internal const byte MQTT_MSG_PINGRESP_TYPE = 0x0D; + internal const byte MQTT_MSG_DISCONNECT_TYPE = 0x0E; + + // 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; + + internal const ushort MAX_TOPIC_LENGTH = 65535; + internal const ushort MIN_TOPIC_LENGTH = 1; + internal const byte MESSAGE_ID_SIZE = 2; + + #endregion + + #region Properties... + + /// + /// Message type + /// + public byte Type + { + get { return this.type; } + set { this.type = value; } + } + + /// + /// Duplicate message flag + /// + public bool DupFlag + { + get { return this.dupFlag; } + set { this.dupFlag = value; } + } + + /// + /// Quality of Service level + /// + public byte QosLevel + { + get { return this.qosLevel; } + set { this.qosLevel = value; } + } + + /// + /// Retain message flag + /// + public bool Retain + { + get { return this.retain; } + set { this.retain = value; } + } + + #endregion + + // message type + protected byte type; + // duplicate delivery + protected bool dupFlag; + // quality of service level + protected byte qosLevel; + // retain flag + protected bool retain; + + /// + /// Returns message bytes rapresentation + /// + /// Bytes rapresentation + public abstract byte[] GetBytes(); + + /// + /// Encode remaining length and insert it into message buffer + /// + /// Remaining length value to encode + /// Message buffer for inserting encoded value + /// Index from which insert encoded value into buffer + /// Index updated + protected int encodeRemainingLength(int remainingLength, byte[] buffer, int index) + { + int digit = 0; + do + { + digit = remainingLength % 128; + remainingLength /= 128; + if (remainingLength > 0) + digit = digit | 0x80; + buffer[index++] = (byte)digit; + } while (remainingLength > 0); + return index; + } + + /// + /// Decode remaining length reading bytes from socket + /// + /// Channel from reading bytes + /// Decoded remaining length + protected static int decodeRemainingLength(IMqttNetworkChannel channel) + { + int multiplier = 1; + int value = 0; + int digit = 0; + byte[] nextByte = new byte[1]; + do + { + // next digit from stream + channel.Receive(nextByte); + digit = nextByte[0]; + value += ((digit & 127) * multiplier); + multiplier *= 128; + } while ((digit & 128) != 0); + return value; + } + } +} diff --git a/M2Mqtt/Messages/MqttMsgConnack.cs b/M2Mqtt/Messages/MqttMsgConnack.cs new file mode 100644 index 0000000..d6ca107 --- /dev/null +++ b/M2Mqtt/Messages/MqttMsgConnack.cs @@ -0,0 +1,137 @@ +/* +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; + +namespace uPLibrary.Networking.M2Mqtt.Messages +{ + /// + /// Class for CONNACK message from broker to client + /// + public class MqttMsgConnack : MqttMsgBase + { + #region Constants... + + // return codes for CONNACK message + public const byte CONN_ACCEPTED = 0x00; + public const byte CONN_REFUSED_PROT_VERS = 0x01; + public const byte CONN_REFUSED_IDENT_REJECTED = 0x02; + public const byte CONN_REFUSED_SERVER_UNAVAILABLE = 0x03; + public const byte CONN_REFUSED_USERNAME_PASSWORD = 0x04; + public const byte CONN_REFUSED_NOT_AUTHORIZED = 0x05; + + private const byte TOPIC_NAME_COMP_RESP_BYTE_OFFSET = 0; + private const byte TOPIC_NAME_COMP_RESP_BYTE_SIZE = 1; + private const byte CONN_RETURN_CODE_BYTE_OFFSET = 1; + private const byte CONN_RETURN_CODE_BYTE_SIZE = 1; + + #endregion + + #region Properties... + + /// + /// Return Code + /// + public byte ReturnCode + { + get { return this.returnCode; } + set { this.returnCode = value; } + } + + #endregion + + // return code for CONNACK message + private byte returnCode; + + /// + /// Constructor + /// + public MqttMsgConnack() + { + this.type = MQTT_MSG_CONNACK_TYPE; + } + + /// + /// Parse bytes for a CONNACK message + /// + /// First fixed header byte + /// Channel connected to the broker + /// CONNACK message instance + public static MqttMsgConnack Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + { + byte[] buffer; + MqttMsgConnack msg = new MqttMsgConnack(); + + // get remaining length and allocate buffer + int remainingLength = MqttMsgBase.decodeRemainingLength(channel); + buffer = new byte[remainingLength]; + + // read bytes from socket... + channel.Receive(buffer); + // ...and set return code from broker + msg.returnCode = buffer[CONN_RETURN_CODE_BYTE_OFFSET]; + + return msg; + } + + public override byte[] GetBytes() + { + int fixedHeaderSize = 0; + int varHeaderSize = 0; + int payloadSize = 0; + int remainingLength = 0; + 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); + + remainingLength += (varHeaderSize + payloadSize); + + // first byte of fixed header + fixedHeaderSize = 1; + + int temp = remainingLength; + // increase fixed header size based on remaining length + // (each remaining length byte can encode until 128) + do + { + fixedHeaderSize++; + temp = temp / 128; + } while (temp > 0); + + // allocate buffer for message + buffer = new byte[fixedHeaderSize + varHeaderSize + payloadSize]; + + // first fixed header byte + buffer[index] = (byte)(MQTT_MSG_CONNACK_TYPE << MSG_TYPE_OFFSET); + index++; + + // encode remaining length + index = this.encodeRemainingLength(remainingLength, buffer, index); + + // topic name compression response (reserved values. not used); + buffer[index++] = 0x00; + + // connect return code + buffer[index++] = this.returnCode; + + return buffer; + } + } +} diff --git a/M2Mqtt/Messages/MqttMsgConnect.cs b/M2Mqtt/Messages/MqttMsgConnect.cs new file mode 100644 index 0000000..67e0835 --- /dev/null +++ b/M2Mqtt/Messages/MqttMsgConnect.cs @@ -0,0 +1,509 @@ +/* +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; +using System.Text; +using uPLibrary.Networking.M2Mqtt.Exceptions; + +namespace uPLibrary.Networking.M2Mqtt.Messages +{ + /// + /// Class for CONNECT message from client to broker + /// + public class MqttMsgConnect : MqttMsgBase + { + #region Constants... + + // protocol name supported + internal const string PROTOCOL_NAME = "MQIsdp"; + + // max length for client id + 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 CONNECT_FLAGS_SIZE = 1; + internal const byte KEEP_ALIVE_TIME_SIZE = 2; + + internal const byte PROTOCOL_VERSION = 0x03; + internal const ushort KEEP_ALIVE_PERIOD_DEFAULT = 60; // seconds + internal const ushort MAX_KEEP_ALIVE = 65535; // 16 bit + + // connect flags + internal const byte USERNAME_FLAG_MASK = 0x80; + internal const byte USERNAME_FLAG_OFFSET = 0x07; + internal const byte USERNAME_FLAG_SIZE = 0x01; + internal const byte PASSWORD_FLAG_MASK = 0x40; + internal const byte PASSWORD_FLAG_OFFSET = 0x06; + internal const byte PASSWORD_FLAG_SIZE = 0x01; + internal const byte WILL_RETAIN_FLAG_MASK = 0x20; + internal const byte WILL_RETAIN_FLAG_OFFSET = 0x05; + internal const byte WILL_RETAIN_FLAG_SIZE = 0x01; + internal const byte WILL_QOS_FLAG_MASK = 0x18; + internal const byte WILL_QOS_FLAG_OFFSET = 0x03; + internal const byte WILL_QOS_FLAG_SIZE = 0x02; + internal const byte WILL_FLAG_MASK = 0x04; + internal const byte WILL_FLAG_OFFSET = 0x02; + internal const byte WILL_FLAG_SIZE = 0x01; + internal const byte CLEAN_SESSION_FLAG_MASK = 0x02; + internal const byte CLEAN_SESSION_FLAG_OFFSET = 0x01; + internal const byte CLEAN_SESSION_FLAG_SIZE = 0x01; + + #endregion + + #region Properties... + + /// + /// Protocol name + /// + public string ProtocolName + { + get { return this.protocolName; } + set { this.protocolName = value; } + } + + /// + /// Protocol version + /// + public byte ProtocolVersion + { + get { return this.protocolVersion; } + set { this.protocolVersion = value; } + } + + /// + /// Client identifier + /// + public string ClientId + { + get { return this.clientId; } + set { this.clientId = value; } + } + + /// + /// Will retain flag + /// + public bool WillRetain + { + get { return this.willRetain; } + set { this.willRetain = value; } + } + + /// + /// Will QOS level + /// + public byte WillQosLevel + { + get { return this.willQosLevel; } + set { this.willQosLevel = value; } + } + + /// + /// Will flag + /// + public bool WillFlag + { + get { return this.willFlag; } + set { this.willFlag = value; } + } + + /// + /// Will topic + /// + public string WillTopic + { + get { return this.willTopic; } + set { this.willTopic = value; } + } + + /// + /// Will message + /// + public string WillMessage + { + get { return this.willMessage; } + set { this.willMessage = value; } + } + + /// + /// Username + /// + public string Username + { + get { return this.username; } + set { this.username = value; } + } + + /// + /// Password + /// + public string Password + { + get { return this.password; } + set { this.password = value; } + } + + /// + /// Clean session flag + /// + public bool CleanSession + { + get { return this.cleanSession; } + set { this.cleanSession = value; } + } + + /// + /// Keep alive period + /// + public ushort KeepAlivePeriod + { + get { return this.keepAlivePeriod; } + set { this.keepAlivePeriod = value; } + } + + #endregion + + // protocol name + private string protocolName; + // protocol version + private byte protocolVersion; + // client identifier + private string clientId; + // will retain flag + protected bool willRetain; + // will quality of service level + protected byte willQosLevel; + // will flag + private bool willFlag; + // will topic + private string willTopic; + // will message + private string willMessage; + // username + private string username; + // password + private string password; + // clean session flag + private bool cleanSession; + // keep alive period (in sec) + private ushort keepAlivePeriod; + + /// + /// Constructor + /// + public MqttMsgConnect() + { + this.type = MQTT_MSG_CONNECT_TYPE; + } + + /// + /// Constructor + /// + /// Client identifier + public MqttMsgConnect(string clientId) : + this(clientId, null, null, false, QOS_LEVEL_AT_LEAST_ONCE, false, null, null, true, KEEP_ALIVE_PERIOD_DEFAULT) + { + } + + /// + /// Constructor + /// + /// Client identifier + /// Username + /// Password + /// Will retain flag + /// Will QOS level + /// Will flag + /// Will topic + /// Will message + /// Clean sessione flag + /// Keep alive period + public MqttMsgConnect(string clientId, + string username, + string password, + bool willRetain, + byte willQosLevel, + bool willFlag, + string willTopic, + string willMessage, + bool cleanSession, + ushort keepAlivePeriod + ) + { + this.type = MQTT_MSG_CONNECT_TYPE; + + this.clientId = clientId; + this.username = username; + this.password = password; + this.willRetain = willRetain; + this.willQosLevel = willQosLevel; + this.willFlag = willFlag; + this.willTopic = willTopic; + this.willMessage = willMessage; + this.cleanSession = cleanSession; + this.keepAlivePeriod = keepAlivePeriod; + } + + /// + /// Parse bytes for a CONNECT message + /// + /// First fixed header byte + /// Channel connected to the broker + /// CONNECT message instance + public static MqttMsgConnect Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + { + byte[] buffer; + int index = 0; + int protNameUtf8Length; + byte[] protNameUtf8; + bool isUsernameFlag; + bool isPasswordFlag; + int clientIdUtf8Length; + byte[] clientIdUtf8; + int willTopicUtf8Length; + byte[] willTopicUtf8; + int willMessageUtf8Length; + byte[] willMessageUtf8; + int usernameUtf8Length; + byte[] usernameUtf8; + int passwordUtf8Length; + byte[] passwordUtf8; + MqttMsgConnect msg = new MqttMsgConnect(); + + // get remaining length and allocate buffer + int remainingLength = MqttMsgBase.decodeRemainingLength(channel); + buffer = new byte[remainingLength]; + + // read bytes from socket... + channel.Receive(buffer); + + // protocol name + protNameUtf8Length = ((buffer[index++] << 8) & 0xFF00); + protNameUtf8Length |= buffer[index++]; + protNameUtf8 = new byte[protNameUtf8Length]; + Array.Copy(buffer, index, protNameUtf8, 0, protNameUtf8Length); + index += protNameUtf8Length; + msg.protocolName = new String(Encoding.UTF8.GetChars(protNameUtf8)); + + // protocol version + msg.protocolVersion = buffer[index]; + index += PROTOCOL_VERSION_NUMBER_SIZE; + + // connect flags + isUsernameFlag = (buffer[index] & USERNAME_FLAG_MASK) != 0x00; + isPasswordFlag = (buffer[index] & PASSWORD_FLAG_MASK) != 0x00; + msg.willRetain = (buffer[index] & WILL_RETAIN_FLAG_MASK) != 0x00; + msg.willQosLevel = (byte)((buffer[index] & WILL_QOS_FLAG_MASK) >> WILL_QOS_FLAG_OFFSET); + msg.willFlag = (buffer[index] & WILL_FLAG_MASK) != 0x00; + msg.cleanSession = (buffer[index] & CLEAN_SESSION_FLAG_MASK) != 0x00; + index += CONNECT_FLAGS_SIZE; + + // keep alive timer + msg.keepAlivePeriod = (ushort)((buffer[index++] << 8) & 0xFF00); + msg.keepAlivePeriod |= buffer[index++]; + + // client identifier + 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)); + + // will topic and will message + if (msg.willFlag) + { + willTopicUtf8Length = ((buffer[index++] << 8) & 0xFF00); + willTopicUtf8Length |= buffer[index++]; + willTopicUtf8 = new byte[willTopicUtf8Length]; + Array.Copy(buffer, index, willTopicUtf8, 0, willTopicUtf8Length); + index += willTopicUtf8Length; + msg.willTopic = new String(Encoding.UTF8.GetChars(willTopicUtf8)); + + willMessageUtf8Length = ((buffer[index++] << 8) & 0xFF00); + willMessageUtf8Length |= buffer[index++]; + willMessageUtf8 = new byte[willMessageUtf8Length]; + Array.Copy(buffer, index, willMessageUtf8, 0, willMessageUtf8Length); + index += willMessageUtf8Length; + msg.willMessage = new String(Encoding.UTF8.GetChars(willMessageUtf8)); + } + + // username + if (isUsernameFlag) + { + usernameUtf8Length = ((buffer[index++] << 8) & 0xFF00); + usernameUtf8Length |= buffer[index++]; + usernameUtf8 = new byte[usernameUtf8Length]; + Array.Copy(buffer, index, usernameUtf8, 0, usernameUtf8Length); + index += usernameUtf8Length; + msg.username = new String(Encoding.UTF8.GetChars(usernameUtf8)); + } + + // password + if (isPasswordFlag) + { + passwordUtf8Length = ((buffer[index++] << 8) & 0xFF00); + passwordUtf8Length |= buffer[index++]; + passwordUtf8 = new byte[passwordUtf8Length]; + Array.Copy(buffer, index, passwordUtf8, 0, passwordUtf8Length); + index += passwordUtf8Length; + msg.password = new String(Encoding.UTF8.GetChars(passwordUtf8)); + } + + return msg; + } + + public override byte[] GetBytes() + { + int fixedHeaderSize = 0; + int varHeaderSize = 0; + int payloadSize = 0; + int remainingLength = 0; + byte[] buffer; + 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; + + // 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); + + // protocol name field size + varHeaderSize += (PROTOCOL_NAME_LEN_SIZE + PROTOCOL_NAME_SIZE); + // protocol version number field size + varHeaderSize += PROTOCOL_VERSION_NUMBER_SIZE; + // connect flags field size + varHeaderSize += CONNECT_FLAGS_SIZE; + // keep alive timer field size + varHeaderSize += KEEP_ALIVE_TIME_SIZE; + + // client identifier field size + payloadSize += clientIdUtf8.Length + 2; + // will topic field size + payloadSize += (willTopicUtf8 != null) ? (willTopicUtf8.Length + 2) : 0; + // will message field size + payloadSize += (willMessageUtf8 != null) ? (willMessageUtf8.Length + 2) : 0; + // username field size + payloadSize += (usernameUtf8 != null) ? (usernameUtf8.Length + 2) : 0; + // password field size + payloadSize += (passwordUtf8 != null) ? (passwordUtf8.Length + 2) : 0; + + remainingLength += (varHeaderSize + payloadSize); + + // first byte of fixed header + fixedHeaderSize = 1; + + int temp = remainingLength; + // increase fixed header size based on remaining length + // (each remaining length byte can encode until 128) + do + { + fixedHeaderSize++; + temp = temp / 128; + } while (temp > 0); + + // allocate buffer for message + buffer = new byte[fixedHeaderSize + varHeaderSize + payloadSize]; + + // first fixed header byte + buffer[index++] = (MQTT_MSG_CONNECT_TYPE << MSG_TYPE_OFFSET); + + // 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; + + // 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 |= (this.willRetain) ? (byte)(1 << WILL_RETAIN_FLAG_OFFSET) : (byte)0x00; + 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; + + // keep alive period + buffer[index++] = (byte)((this.keepAlivePeriod >> 8) & 0x00FF); // MSB + buffer[index++] = (byte)(this.keepAlivePeriod & 0x00FF); // LSB + + // client identifier + buffer[index++] = (byte)((clientIdUtf8.Length >> 8) & 0x00FF); // MSB + buffer[index++] = (byte)(clientIdUtf8.Length & 0x00FF); // LSB + Array.Copy(clientIdUtf8, 0, buffer, index, clientIdUtf8.Length); + index += clientIdUtf8.Length; + + // will topic + if (this.willFlag && (this.willTopic != null)) + { + buffer[index++] = (byte)((willTopicUtf8.Length >> 8) & 0x00FF); // MSB + buffer[index++] = (byte)(willTopicUtf8.Length & 0x00FF); // LSB + Array.Copy(willTopicUtf8, 0, buffer, index, willTopicUtf8.Length); + index += willTopicUtf8.Length; + } + + // will message + if (this.willFlag && (this.willMessage != null)) + { + buffer[index++] = (byte)((willMessageUtf8.Length >> 8) & 0x00FF); // MSB + buffer[index++] = (byte)(willMessageUtf8.Length & 0x00FF); // LSB + Array.Copy(willMessageUtf8, 0, buffer, index, willMessageUtf8.Length); + index += willMessageUtf8.Length; + } + + // username + if (this.username != null) + { + buffer[index++] = (byte)((usernameUtf8.Length >> 8) & 0x00FF); // MSB + buffer[index++] = (byte)(usernameUtf8.Length & 0x00FF); // LSB + Array.Copy(usernameUtf8, 0, buffer, index, usernameUtf8.Length); + index += usernameUtf8.Length; + } + + // password + if (this.password != null) + { + buffer[index++] = (byte)((passwordUtf8.Length >> 8) & 0x00FF); // MSB + buffer[index++] = (byte)(passwordUtf8.Length & 0x00FF); // LSB + Array.Copy(passwordUtf8, 0, buffer, index, passwordUtf8.Length); + index += passwordUtf8.Length; + } + + return buffer; + } + } +} diff --git a/M2Mqtt/Messages/MqttMsgConnectEventArgs.cs b/M2Mqtt/Messages/MqttMsgConnectEventArgs.cs new file mode 100644 index 0000000..6c901e1 --- /dev/null +++ b/M2Mqtt/Messages/MqttMsgConnectEventArgs.cs @@ -0,0 +1,46 @@ +/* +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 (!MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3) +using System; +#else +using Microsoft.SPOT; +#endif + +namespace uPLibrary.Networking.M2Mqtt.Messages +{ + /// + /// Event Args class for CONNECT message received from client + /// + public class MqttMsgConnectEventArgs : EventArgs + { + /// + /// Message received from client + /// + public MqttMsgConnect Message { get; private set; } + + /// + /// Constructor + /// + /// CONNECT message received from client + public MqttMsgConnectEventArgs(MqttMsgConnect connect) + { + this.Message = connect; + } + } +} diff --git a/M2Mqtt/Messages/MqttMsgContext.cs b/M2Mqtt/Messages/MqttMsgContext.cs new file mode 100644 index 0000000..204703f --- /dev/null +++ b/M2Mqtt/Messages/MqttMsgContext.cs @@ -0,0 +1,141 @@ +/* +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; +using System.Text; + +namespace uPLibrary.Networking.M2Mqtt.Messages +{ + /// + /// Context for MQTT message + /// + public class MqttMsgContext + { + /// + /// MQTT message + /// + public MqttMsgBase Message { get; set; } + + /// + /// MQTT message state + /// + public MqttMsgState State { get; set; } + + /// + /// Flow of the message + /// + public MqttMsgFlow Flow { get; set; } + + /// + /// Timestamp in ticks (for retry) + /// + public int Timestamp { get; set; } + + /// + /// Attempt (for retry) + /// + public int Attempt { get; set; } + } + + /// + /// Flow of the message + /// + public enum MqttMsgFlow + { + /// + /// To publish to subscribers + /// + ToPublish, + + /// + /// To acknowledge to publisher + /// + ToAcknowledge + } + + /// + /// MQTT message state + /// + public enum MqttMsgState + { + /// + /// QOS = 0, Message queued + /// + QueuedQos0, + + /// + /// QOS = 1, Message queued + /// + QueuedQos1, + + /// + /// QOS = 2, Message queued + /// + QueuedQos2, + + /// + /// QOS = 1, PUBLISH sent, wait for PUBACK + /// + WaitForPuback, + + /// + /// QOS = 2, PUBLISH sent, wait for PUBREC + /// + WaitForPubrec, + + /// + /// QOS = 2, PUBREC sent, wait for PUBREL + /// + WaitForPubrel, + + /// + /// QOS = 2, PUBREL sent, wait for PUBCOMP + /// + WaitForPubcomp, + + /// + /// QOS = 2, start first phase handshake send PUBREC + /// + SendPubrec, + + /// + /// QOS = 2, start second phase handshake send PUBREL + /// + SendPubrel, + + /// + /// QOS = 2, end second phase handshake send PUBCOMP + /// + SendPubcomp, + + /// + /// QOS = 1, PUBLISH received, send PUBACK + /// + SendPuback, + + /// + /// (QOS = 1), SUBSCRIBE sent, wait for SUBACK + /// + WaitForSuback, + + /// + /// (QOS = 1), UNSUBSCRIBE sent, wait for UNSUBACK + /// + WaitForUnsuback + } +} diff --git a/M2Mqtt/Messages/MqttMsgDisconnect.cs b/M2Mqtt/Messages/MqttMsgDisconnect.cs new file mode 100644 index 0000000..8c4eb7e --- /dev/null +++ b/M2Mqtt/Messages/MqttMsgDisconnect.cs @@ -0,0 +1,64 @@ +/* +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. +*/ + + +namespace uPLibrary.Networking.M2Mqtt.Messages +{ + /// + /// Class for DISCONNECT message from client to broker + /// + public class MqttMsgDisconnect : MqttMsgBase + { + /// + /// Constructor + /// + public MqttMsgDisconnect() + { + this.type = MQTT_MSG_DISCONNECT_TYPE; + } + + /// + /// Parse bytes for a DISCONNECT message + /// + /// First fixed header byte + /// Channel connected to the broker + /// DISCONNECT message instance + public static MqttMsgDisconnect Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + { + MqttMsgDisconnect msg = new MqttMsgDisconnect(); + + // get remaining length and allocate buffer + int remainingLength = MqttMsgBase.decodeRemainingLength(channel); + // NOTE : remainingLength must be 0 + + return msg; + } + + public override byte[] GetBytes() + { + byte[] buffer = new byte[2]; + int index = 0; + + // first fixed header byte + buffer[index++] = (MQTT_MSG_DISCONNECT_TYPE << MSG_TYPE_OFFSET); + buffer[index++] = 0x00; + + return buffer; + } + } +} diff --git a/M2Mqtt/Messages/MqttMsgPingReq.cs b/M2Mqtt/Messages/MqttMsgPingReq.cs new file mode 100644 index 0000000..e8f4efb --- /dev/null +++ b/M2Mqtt/Messages/MqttMsgPingReq.cs @@ -0,0 +1,64 @@ +/* +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. +*/ + + +namespace uPLibrary.Networking.M2Mqtt.Messages +{ + /// + /// Class for PINGREQ message from client to broker + /// + public class MqttMsgPingReq : MqttMsgBase + { + /// + /// Constructor + /// + public MqttMsgPingReq() + { + this.type = MQTT_MSG_PINGREQ_TYPE; + } + + public override byte[] GetBytes() + { + byte[] buffer = new byte[2]; + int index = 0; + + // first fixed header byte + buffer[index++] = (MQTT_MSG_PINGREQ_TYPE << MSG_TYPE_OFFSET); + buffer[index++] = 0x00; + + return buffer; + } + + /// + /// Parse bytes for a PINGREQ message + /// + /// First fixed header byte + /// Channel connected to the broker + /// PINGREQ message instance + public static MqttMsgPingReq Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + { + MqttMsgPingReq msg = new MqttMsgPingReq(); + + // 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; + } + } +} diff --git a/M2Mqtt/Messages/MqttMsgPingResp.cs b/M2Mqtt/Messages/MqttMsgPingResp.cs new file mode 100644 index 0000000..988edcf --- /dev/null +++ b/M2Mqtt/Messages/MqttMsgPingResp.cs @@ -0,0 +1,65 @@ +/* +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; + +namespace uPLibrary.Networking.M2Mqtt.Messages +{ + /// + /// Class for PINGRESP message from client to broker + /// + public class MqttMsgPingResp : MqttMsgBase + { + /// + /// Constructor + /// + public MqttMsgPingResp() + { + this.type = MQTT_MSG_PINGRESP_TYPE; + } + + /// + /// Parse bytes for a PINGRESP message + /// + /// First fixed header byte + /// Channel connected to the broker + /// PINGRESP message instance + public static MqttMsgPingResp Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + { + MqttMsgPingResp msg = new MqttMsgPingResp(); + + // 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 byte[] GetBytes() + { + byte[] buffer = new byte[2]; + int index = 0; + + // first fixed header byte + buffer[index++] = (MQTT_MSG_PINGRESP_TYPE << MSG_TYPE_OFFSET); + buffer[index++] = 0x00; + + return buffer; + } + } +} diff --git a/M2Mqtt/Messages/MqttMsgPuback.cs b/M2Mqtt/Messages/MqttMsgPuback.cs new file mode 100644 index 0000000..646746c --- /dev/null +++ b/M2Mqtt/Messages/MqttMsgPuback.cs @@ -0,0 +1,119 @@ +/* +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. +*/ + +namespace uPLibrary.Networking.M2Mqtt.Messages +{ + /// + /// Class for PUBACK message from broker to client + /// + 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 + /// + public MqttMsgPuback() + { + this.type = MQTT_MSG_PUBACK_TYPE; + } + + public override byte[] GetBytes() + { + int fixedHeaderSize = 0; + int varHeaderSize = 0; + int payloadSize = 0; + int remainingLength = 0; + byte[] buffer; + int index = 0; + + // message identifier + varHeaderSize += MESSAGE_ID_SIZE; + + remainingLength += (varHeaderSize + payloadSize); + + // first byte of fixed header + fixedHeaderSize = 1; + + int temp = remainingLength; + // increase fixed header size based on remaining length + // (each remaining length byte can encode until 128) + do + { + fixedHeaderSize++; + temp = temp / 128; + } while (temp > 0); + + // allocate buffer for message + buffer = new byte[fixedHeaderSize + varHeaderSize + payloadSize]; + + // first fixed header byte + buffer[index++] = (MQTT_MSG_PUBACK_TYPE << MSG_TYPE_OFFSET); + + // encode remaining length + index = this.encodeRemainingLength(remainingLength, buffer, index); + + // get message identifier + buffer[index++] = (byte)((this.messageId >> 8) & 0x00FF); // MSB + buffer[index++] = (byte)(this.messageId & 0x00FF); // LSB + + return buffer; + } + + /// + /// Parse bytes for a PUBACK message + /// + /// First fixed header byte + /// Channel connected to the broker + /// PUBACK message instance + public static MqttMsgPuback Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + { + byte[] buffer; + int index = 0; + MqttMsgPuback msg = new MqttMsgPuback(); + + // get remaining length and allocate buffer + int remainingLength = MqttMsgBase.decodeRemainingLength(channel); + buffer = new byte[remainingLength]; + + // read bytes from socket... + channel.Receive(buffer); + + // message id + msg.messageId = (ushort)((buffer[index++] << 8) & 0xFF00); + msg.messageId |= (buffer[index++]); + + return msg; + } + } +} diff --git a/M2Mqtt/Messages/MqttMsgPubcomp.cs b/M2Mqtt/Messages/MqttMsgPubcomp.cs new file mode 100644 index 0000000..ca53667 --- /dev/null +++ b/M2Mqtt/Messages/MqttMsgPubcomp.cs @@ -0,0 +1,118 @@ +/* +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. +*/ + +namespace uPLibrary.Networking.M2Mqtt.Messages +{ + /// + /// Class for PUBCOMP message from broker to client + /// + 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 + /// + public MqttMsgPubcomp() + { + this.type = MQTT_MSG_PUBCOMP_TYPE; + } + + public override byte[] GetBytes() + { + int fixedHeaderSize = 0; + int varHeaderSize = 0; + int payloadSize = 0; + int remainingLength = 0; + byte[] buffer; + int index = 0; + + // message identifier + varHeaderSize += MESSAGE_ID_SIZE; + + remainingLength += (varHeaderSize + payloadSize); + + // first byte of fixed header + fixedHeaderSize = 1; + + int temp = remainingLength; + // increase fixed header size based on remaining length + // (each remaining length byte can encode until 128) + do + { + fixedHeaderSize++; + temp = temp / 128; + } while (temp > 0); + + // allocate buffer for message + buffer = new byte[fixedHeaderSize + varHeaderSize + payloadSize]; + + // first fixed header byte + buffer[index++] = (MQTT_MSG_PUBCOMP_TYPE << MSG_TYPE_OFFSET); + + // encode remaining length + index = this.encodeRemainingLength(remainingLength, buffer, index); + + // get message identifier + buffer[index++] = (byte)((this.messageId >> 8) & 0x00FF); // MSB + buffer[index++] = (byte)(this.messageId & 0x00FF); // LSB + + return buffer; + } + + /// + /// Parse bytes for a PUBCOMP message + /// + /// First fixed header byte + /// Channel connected to the broker + /// PUBCOMP message instance + public static MqttMsgPubcomp Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + { + byte[] buffer; + int index = 0; + MqttMsgPubcomp msg = new MqttMsgPubcomp(); + + // get remaining length and allocate buffer + int remainingLength = MqttMsgBase.decodeRemainingLength(channel); + buffer = new byte[remainingLength]; + + // read bytes from socket... + channel.Receive(buffer); + + // message id + msg.messageId = (ushort)((buffer[index++] << 8) & 0xFF00); + msg.messageId |= (buffer[index++]); + + return msg; + } + } +} diff --git a/M2Mqtt/Messages/MqttMsgPublish.cs b/M2Mqtt/Messages/MqttMsgPublish.cs new file mode 100644 index 0000000..8ee80b5 --- /dev/null +++ b/M2Mqtt/Messages/MqttMsgPublish.cs @@ -0,0 +1,270 @@ +/* +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; +using System.Text; +using uPLibrary.Networking.M2Mqtt.Exceptions; + +namespace uPLibrary.Networking.M2Mqtt.Messages +{ + /// + /// Class for PUBLISH message from client to broker + /// + public class MqttMsgPublish : MqttMsgBase + { + #region Properties... + + /// + /// Message topic + /// + public string Topic + { + get { return this.topic; } + set { this.topic = value; } + } + + /// + /// Message data + /// + public byte[] Message + { + get { return this.message; } + 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 + /// + public MqttMsgPublish() + { + this.type = MQTT_MSG_PUBLISH_TYPE; + } + + /// + /// Constructor + /// + /// Message topic + /// Message data + public MqttMsgPublish(string topic, byte[] message) : + this(topic, message, false, QOS_LEVEL_AT_MOST_ONCE, false) + { + } + + /// + /// Constructor + /// + /// Message topic + /// Message data + /// Duplicate flag + /// Quality of Service level + /// Retain flag + public MqttMsgPublish(string topic, + byte[] message, + bool dupFlag, + byte qosLevel, + bool retain) : base() + { + this.type = MQTT_MSG_PUBLISH_TYPE; + + this.topic = topic; + this.message = message; + this.dupFlag = dupFlag; + this.qosLevel = qosLevel; + this.retain = retain; + this.messageId = 0; + } + + public override byte[] GetBytes() + { + int fixedHeaderSize = 0; + int varHeaderSize = 0; + int payloadSize = 0; + int remainingLength = 0; + byte[] buffer; + int index = 0; + + // topic can't contain wildcards + if ((this.topic.IndexOf('#') != -1) || (this.topic.IndexOf('+') != -1)) + throw new MqttClientException(MqttClientErrorCode.TopicWildcard); + + // check topic length + if ((this.topic.Length < MIN_TOPIC_LENGTH) || (this.topic.Length > MAX_TOPIC_LENGTH)) + throw new MqttClientException(MqttClientErrorCode.TopicLength); + + byte[] topicUtf8 = Encoding.UTF8.GetBytes(this.topic); + + // topic name + varHeaderSize += topicUtf8.Length + 2; + + // message id is valid only with QOS level 1 or QOS level 2 + if ((this.qosLevel == QOS_LEVEL_AT_LEAST_ONCE) || + (this.qosLevel == QOS_LEVEL_EXACTLY_ONCE)) + { + varHeaderSize += MESSAGE_ID_SIZE; + } + + // check on message with zero length + if (this.message != null) + // message data + payloadSize += this.message.Length; + + remainingLength += (varHeaderSize + payloadSize); + + // first byte of fixed header + fixedHeaderSize = 1; + + int temp = remainingLength; + // increase fixed header size based on remaining length + // (each remaining length byte can encode until 128) + do + { + fixedHeaderSize++; + temp = temp / 128; + } while (temp > 0); + + // allocate buffer for message + buffer = new byte[fixedHeaderSize + varHeaderSize + payloadSize]; + + // first fixed header byte + buffer[index] = (byte)((MQTT_MSG_PUBLISH_TYPE << MSG_TYPE_OFFSET) | + (this.qosLevel << QOS_LEVEL_OFFSET)); + buffer[index] |= this.dupFlag ? (byte)(1 << DUP_FLAG_OFFSET) : (byte)0x00; + buffer[index] |= this.retain ? (byte)(1 << RETAIN_FLAG_OFFSET) : (byte)0x00; + index++; + + // encode remaining length + index = this.encodeRemainingLength(remainingLength, buffer, index); + + // topic name + buffer[index++] = (byte)((topicUtf8.Length >> 8) & 0x00FF); // MSB + buffer[index++] = (byte)(topicUtf8.Length & 0x00FF); // LSB + Array.Copy(topicUtf8, 0, buffer, index, topicUtf8.Length); + index += topicUtf8.Length; + + // message id is valid only with QOS level 1 or QOS level 2 + if ((this.qosLevel == QOS_LEVEL_AT_LEAST_ONCE) || + (this.qosLevel == QOS_LEVEL_EXACTLY_ONCE)) + { + // check message identifier assigned + if (this.messageId == 0) + throw new MqttClientException(MqttClientErrorCode.WrongMessageId); + buffer[index++] = (byte)((this.messageId >> 8) & 0x00FF); // MSB + buffer[index++] = (byte)(this.messageId & 0x00FF); // LSB + } + + // check on message with zero length + if (this.message != null) + { + // message data + Array.Copy(this.message, 0, buffer, index, this.message.Length); + index += this.message.Length; + } + + return buffer; + } + + /// + /// Parse bytes for a PUBLISH message + /// + /// First fixed header byte + /// Channel connected to the broker + /// PUBLISH message instance + public static MqttMsgPublish Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + { + byte[] buffer; + int index = 0; + byte[] topicUtf8; + int topicUtf8Length; + MqttMsgPublish msg = new MqttMsgPublish(); + + // get remaining length and allocate buffer + int remainingLength = MqttMsgBase.decodeRemainingLength(channel); + buffer = new byte[remainingLength]; + + // read bytes from socket... + int received = channel.Receive(buffer); + + // topic name + topicUtf8Length = ((buffer[index++] << 8) & 0xFF00); + topicUtf8Length |= buffer[index++]; + topicUtf8 = new byte[topicUtf8Length]; + Array.Copy(buffer, index, topicUtf8, 0, topicUtf8Length); + index += topicUtf8Length; + msg.topic = new String(Encoding.UTF8.GetChars(topicUtf8)); + + // 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); + // read retain flag from fixed header + msg.retain = (((fixedHeaderFirstByte & RETAIN_FLAG_MASK) >> RETAIN_FLAG_OFFSET) == 0x01); + + // message id is valid only with QOS level 1 or QOS level 2 + if ((msg.qosLevel == QOS_LEVEL_AT_LEAST_ONCE) || + (msg.qosLevel == QOS_LEVEL_EXACTLY_ONCE)) + { + // message id + msg.messageId = (ushort)((buffer[index++] << 8) & 0xFF00); + msg.messageId |= (buffer[index++]); + } + + // get payload with message data + int messageSize = remainingLength - index; + int remaining = messageSize; + int messageOffset = 0; + msg.message = new byte[messageSize]; + + // BUG FIX 26/07/2013 : receiving large payload + + // copy first part of payload data received + Array.Copy(buffer, index, msg.message, messageOffset, received - index); + remaining -= (received - index); + messageOffset += (received - index); + + // if payload isn't finished + while (remaining > 0) + { + // receive other payload data + received = channel.Receive(buffer); + Array.Copy(buffer, 0, msg.message, messageOffset, received); + remaining -= received; + messageOffset += received; + } + + return msg; + } + } +} diff --git a/M2Mqtt/Messages/MqttMsgPublishEventArgs.cs b/M2Mqtt/Messages/MqttMsgPublishEventArgs.cs new file mode 100644 index 0000000..5954d91 --- /dev/null +++ b/M2Mqtt/Messages/MqttMsgPublishEventArgs.cs @@ -0,0 +1,113 @@ +/* +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 (!MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3) +using System; +#else +using Microsoft.SPOT; +#endif + +namespace uPLibrary.Networking.M2Mqtt.Messages +{ + /// + /// Event Args class for PUBLISH message received from broker + /// + public class MqttMsgPublishEventArgs : EventArgs + { + #region Properties... + + /// + /// Message topic + /// + public string Topic + { + get { return this.topic; } + internal set { this.topic = value; } + } + + /// + /// Message data + /// + public byte[] Message + { + get { return this.message; } + internal set { this.message = value; } + } + + /// + /// Duplicate message flag + /// + public bool DupFlag + { + get { return this.dupFlag; } + set { this.dupFlag = value; } + } + + /// + /// Quality of Service level + /// + public byte QosLevel + { + get { return this.qosLevel; } + internal set { this.qosLevel = value; } + } + + /// + /// Retain message flag + /// + public bool Retain + { + get { return this.retain; } + internal set { this.retain = value; } + } + + #endregion + + // message topic + private string topic; + // message data + private byte[] message; + // duplicate delivery + private bool dupFlag; + // quality of service level + private byte qosLevel; + // retain flag + private bool retain; + + /// + /// Constructor + /// + /// Message topic + /// Message data + /// Duplicate delivery flag + /// Quality of Service level + /// Retain flag + public MqttMsgPublishEventArgs(string topic, + byte[] message, + bool dupFlag, + byte qosLevel, + bool retain) + { + this.topic = topic; + this.message = message; + this.dupFlag = dupFlag; + this.qosLevel = qosLevel; + this.retain = retain; + } + } +} diff --git a/M2Mqtt/Messages/MqttMsgPublishedEventArgs.cs b/M2Mqtt/Messages/MqttMsgPublishedEventArgs.cs new file mode 100644 index 0000000..811d946 --- /dev/null +++ b/M2Mqtt/Messages/MqttMsgPublishedEventArgs.cs @@ -0,0 +1,57 @@ +/* +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 (!MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3) +using System; +#else +using Microsoft.SPOT; +#endif + +namespace uPLibrary.Networking.M2Mqtt.Messages +{ + /// + /// Event Args class for published message + /// + public class MqttMsgPublishedEventArgs : EventArgs + { + #region Properties... + + /// + /// Message identifier + /// + public ushort MessageId + { + get { return this.messageId; } + internal set { this.messageId = value; } + } + + #endregion + + // message identifier + ushort messageId; + + /// + /// Constructor + /// + /// Message identifier published + public MqttMsgPublishedEventArgs(ushort messageId) + { + this.messageId = messageId; + } + } +} diff --git a/M2Mqtt/Messages/MqttMsgPubrec.cs b/M2Mqtt/Messages/MqttMsgPubrec.cs new file mode 100644 index 0000000..05ce84a --- /dev/null +++ b/M2Mqtt/Messages/MqttMsgPubrec.cs @@ -0,0 +1,118 @@ +/* +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. +*/ + +namespace uPLibrary.Networking.M2Mqtt.Messages +{ + /// + /// Class for PUBREC message from broker to client + /// + 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 + /// + public MqttMsgPubrec() + { + this.type = MQTT_MSG_PUBREC_TYPE; + } + + public override byte[] GetBytes() + { + int fixedHeaderSize = 0; + int varHeaderSize = 0; + int payloadSize = 0; + int remainingLength = 0; + byte[] buffer; + int index = 0; + + // message identifier + varHeaderSize += MESSAGE_ID_SIZE; + + remainingLength += (varHeaderSize + payloadSize); + + // first byte of fixed header + fixedHeaderSize = 1; + + int temp = remainingLength; + // increase fixed header size based on remaining length + // (each remaining length byte can encode until 128) + do + { + fixedHeaderSize++; + temp = temp / 128; + } while (temp > 0); + + // allocate buffer for message + buffer = new byte[fixedHeaderSize + varHeaderSize + payloadSize]; + + // first fixed header byte + buffer[index++] = (MQTT_MSG_PUBREC_TYPE << MSG_TYPE_OFFSET); + + // encode remaining length + index = this.encodeRemainingLength(remainingLength, buffer, index); + + // get message identifier + buffer[index++] = (byte)((this.messageId >> 8) & 0x00FF); // MSB + buffer[index++] = (byte)(this.messageId & 0x00FF); // LSB + + return buffer; + } + + /// + /// Parse bytes for a PUBREC message + /// + /// First fixed header byte + /// Channel connected to the broker + /// PUBREC message instance + public static MqttMsgPubrec Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + { + byte[] buffer; + int index = 0; + MqttMsgPubrec msg = new MqttMsgPubrec(); + + // get remaining length and allocate buffer + int remainingLength = MqttMsgBase.decodeRemainingLength(channel); + buffer = new byte[remainingLength]; + + // read bytes from socket... + channel.Receive(buffer); + + // message id + msg.messageId = (ushort)((buffer[index++] << 8) & 0xFF00); + msg.messageId |= (buffer[index++]); + + return msg; + } + } +} diff --git a/M2Mqtt/Messages/MqttMsgPubrel.cs b/M2Mqtt/Messages/MqttMsgPubrel.cs new file mode 100644 index 0000000..25c10a2 --- /dev/null +++ b/M2Mqtt/Messages/MqttMsgPubrel.cs @@ -0,0 +1,128 @@ +/* +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. +*/ + +namespace uPLibrary.Networking.M2Mqtt.Messages +{ + /// + /// Class for PUBREL message from client top broker + /// + 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 + this.qosLevel = QOS_LEVEL_AT_LEAST_ONCE; + } + + public override byte[] GetBytes() + { + int fixedHeaderSize = 0; + int varHeaderSize = 0; + int payloadSize = 0; + int remainingLength = 0; + byte[] buffer; + int index = 0; + + // message identifier + varHeaderSize += MESSAGE_ID_SIZE; + + remainingLength += (varHeaderSize + payloadSize); + + // first byte of fixed header + fixedHeaderSize = 1; + + int temp = remainingLength; + // increase fixed header size based on remaining length + // (each remaining length byte can encode until 128) + do + { + fixedHeaderSize++; + temp = temp / 128; + } while (temp > 0); + + // allocate buffer for message + buffer = new byte[fixedHeaderSize + varHeaderSize + payloadSize]; + + // first fixed header byte + 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++; + + // encode remaining length + index = this.encodeRemainingLength(remainingLength, buffer, index); + + // get next message identifier + buffer[index++] = (byte)((this.messageId >> 8) & 0x00FF); // MSB + buffer[index++] = (byte)(this.messageId & 0x00FF); // LSB + + return buffer; + } + + /// + /// Parse bytes for a PUBREL message + /// + /// First fixed header byte + /// Channel connected to the broker + /// PUBREL message instance + public static MqttMsgPubrel Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + { + byte[] buffer; + int index = 0; + MqttMsgPubrel msg = new MqttMsgPubrel(); + + // get remaining length and allocate buffer + int remainingLength = MqttMsgBase.decodeRemainingLength(channel); + buffer = new byte[remainingLength]; + + // 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); + + // message id + msg.messageId = (ushort)((buffer[index++] << 8) & 0xFF00); + msg.messageId |= (buffer[index++]); + + return msg; + } + } +} diff --git a/M2Mqtt/Messages/MqttMsgSuback.cs b/M2Mqtt/Messages/MqttMsgSuback.cs new file mode 100644 index 0000000..5b847ff --- /dev/null +++ b/M2Mqtt/Messages/MqttMsgSuback.cs @@ -0,0 +1,153 @@ +/* +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; + +namespace uPLibrary.Networking.M2Mqtt.Messages +{ + /// + /// Class for SUBACK message from broker to client + /// + public class MqttMsgSuback : MqttMsgBase + { + #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 + /// + public byte[] GrantedQoSLevels + { + get { return this.grantedQosLevels; } + set { this.grantedQosLevels = value; } + } + + #endregion + + // message identifier + private ushort messageId; + // granted QOS levels + byte[] grantedQosLevels; + + /// + /// Constructor + /// + public MqttMsgSuback() + { + this.type = MQTT_MSG_SUBACK_TYPE; + } + + /// + /// Parse bytes for a SUBACK message + /// + /// First fixed header byte + /// Channel connected to the broker + /// SUBACK message instance + public static MqttMsgSuback Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + { + byte[] buffer; + int index = 0; + MqttMsgSuback msg = new MqttMsgSuback(); + + // get remaining length and allocate buffer + int remainingLength = MqttMsgBase.decodeRemainingLength(channel); + buffer = new byte[remainingLength]; + + // read bytes from socket... + channel.Receive(buffer); + + // message id + msg.messageId = (ushort)((buffer[index++] << 8) & 0xFF00); + msg.messageId |= (buffer[index++]); + + // payload contains QoS levels granted + msg.grantedQosLevels = new byte[remainingLength - MESSAGE_ID_SIZE]; + int qosIdx = 0; + do + { + msg.grantedQosLevels[qosIdx++] = buffer[index++]; + } while (index < remainingLength); + + return msg; + } + + public override byte[] GetBytes() + { + int fixedHeaderSize = 0; + int varHeaderSize = 0; + int payloadSize = 0; + int remainingLength = 0; + byte[] buffer; + int index = 0; + + // message identifier + varHeaderSize += MESSAGE_ID_SIZE; + + int grantedQosIdx = 0; + for (grantedQosIdx = 0; grantedQosIdx < this.grantedQosLevels.Length; grantedQosIdx++) + { + payloadSize++; + } + + remainingLength += (varHeaderSize + payloadSize); + + // first byte of fixed header + fixedHeaderSize = 1; + + int temp = remainingLength; + // increase fixed header size based on remaining length + // (each remaining length byte can encode until 128) + do + { + fixedHeaderSize++; + temp = temp / 128; + } while (temp > 0); + + // allocate buffer for message + buffer = new byte[fixedHeaderSize + varHeaderSize + payloadSize]; + + // first fixed header byte + buffer[index] = (byte)(MQTT_MSG_SUBACK_TYPE << MSG_TYPE_OFFSET); + index++; + + // encode remaining length + index = this.encodeRemainingLength(remainingLength, buffer, index); + + // message id + buffer[index++] = (byte)((this.messageId >> 8) & 0x00FF); // MSB + buffer[index++] = (byte)(this.messageId & 0x00FF); // LSB + + // payload contains QoS levels granted + for (grantedQosIdx = 0; grantedQosIdx < this.grantedQosLevels.Length; grantedQosIdx++) + { + buffer[index++] = this.grantedQosLevels[grantedQosIdx]; + } + + return buffer; + } + } +} diff --git a/M2Mqtt/Messages/MqttMsgSubscribe.cs b/M2Mqtt/Messages/MqttMsgSubscribe.cs new file mode 100644 index 0000000..478ee2e --- /dev/null +++ b/M2Mqtt/Messages/MqttMsgSubscribe.cs @@ -0,0 +1,255 @@ +/* +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; +// if NOT .Net Micro Framework +#if (!MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3) +using System.Collections.Generic; +#endif +using System.Collections; +using System.Text; +using uPLibrary.Networking.M2Mqtt.Exceptions; + +namespace uPLibrary.Networking.M2Mqtt.Messages +{ + /// + /// Class for SUBSCRIBE message from client to broker + /// + public class MqttMsgSubscribe : MqttMsgBase + { + #region Properties... + + /// + /// List of topics to subscribe + /// + public string[] Topics + { + get { return this.topics; } + set { this.topics = value; } + } + + /// + /// List of QOS Levels related to topics + /// + public byte[] QoSLevels + { + get { return this.qosLevels; } + 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 + /// + public MqttMsgSubscribe() + { + this.type = MQTT_MSG_SUBSCRIBE_TYPE; + } + + /// + /// Constructor + /// + /// List of topics to subscribe + /// List of QOS Levels related to topics + public MqttMsgSubscribe(string[] topics, byte[] qosLevels) + { + this.type = MQTT_MSG_SUBSCRIBE_TYPE; + + this.topics = topics; + this.qosLevels = qosLevels; + + // SUBSCRIBE message uses QoS Level 1 + this.qosLevel = QOS_LEVEL_AT_LEAST_ONCE; + } + + /// + /// Parse bytes for a SUBSCRIBE message + /// + /// First fixed header byte + /// Channel connected to the broker + /// SUBSCRIBE message instance + public static MqttMsgSubscribe Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + { + byte[] buffer; + int index = 0; + byte[] topicUtf8; + int topicUtf8Length; + MqttMsgSubscribe msg = new MqttMsgSubscribe(); + + // get remaining length and allocate buffer + int remainingLength = MqttMsgBase.decodeRemainingLength(channel); + buffer = new byte[remainingLength]; + + // 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; + + // message id + msg.messageId = (ushort)((buffer[index++] << 8) & 0xFF00); + msg.messageId |= (buffer[index++]); + + // payload contains topics and QoS levels + // NOTE : before, I don't know how many topics will be in the payload (so use List) + +// if .Net Micro Framework +#if (MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3) + IList tmpTopics = new ArrayList(); + IList tmpQosLevels = new ArrayList(); +// else other frameworks (.Net, .Net Compact, Mono, Windows Phone) +#else + IList tmpTopics = new List(); + IList tmpQosLevels = new List(); +#endif + do + { + // topic name + topicUtf8Length = ((buffer[index++] << 8) & 0xFF00); + topicUtf8Length |= buffer[index++]; + topicUtf8 = new byte[topicUtf8Length]; + Array.Copy(buffer, index, topicUtf8, 0, topicUtf8Length); + index += topicUtf8Length; + tmpTopics.Add(new String(Encoding.UTF8.GetChars(topicUtf8))); + + // QoS level + tmpQosLevels.Add(buffer[index++]); + + } while (index < remainingLength); + + // copy from list to array + msg.topics = new string[tmpTopics.Count]; + msg.qosLevels = new byte[tmpQosLevels.Count]; + for (int i = 0; i < tmpTopics.Count; i++) + { + msg.topics[i] = (string)tmpTopics[i]; + msg.qosLevels[i] = (byte)tmpQosLevels[i]; + } + + return msg; + } + + public override byte[] GetBytes() + { + int fixedHeaderSize = 0; + int varHeaderSize = 0; + int payloadSize = 0; + int remainingLength = 0; + byte[] buffer; + int index = 0; + + // topics list empty + if ((this.topics == null) || (this.topics.Length == 0)) + throw new MqttClientException(MqttClientErrorCode.TopicsEmpty); + + // qos levels list empty + if ((this.qosLevels == null) || (this.qosLevels.Length == 0)) + throw new MqttClientException(MqttClientErrorCode.QosLevelsEmpty); + + // topics and qos levels lists length don't match + if (this.topics.Length != this.qosLevels.Length) + throw new MqttClientException(MqttClientErrorCode.TopicsQosLevelsNotMatch); + + // message identifier + varHeaderSize += MESSAGE_ID_SIZE; + + int topicIdx = 0; + byte[][] topicsUtf8 = new byte[this.topics.Length][]; + + for (topicIdx = 0; topicIdx < this.topics.Length; topicIdx++) + { + // check topic length + if ((this.topics[topicIdx].Length < MIN_TOPIC_LENGTH) || (this.topics[topicIdx].Length > MAX_TOPIC_LENGTH)) + throw new MqttClientException(MqttClientErrorCode.TopicLength); + + topicsUtf8[topicIdx] = Encoding.UTF8.GetBytes(this.topics[topicIdx]); + payloadSize += 2; // topic size (MSB, LSB) + payloadSize += topicsUtf8[topicIdx].Length; + payloadSize++; // byte for QoS + } + + remainingLength += (varHeaderSize + payloadSize); + + // first byte of fixed header + fixedHeaderSize = 1; + + int temp = remainingLength; + // increase fixed header size based on remaining length + // (each remaining length byte can encode until 128) + do + { + fixedHeaderSize++; + temp = temp / 128; + } while (temp > 0); + + // allocate buffer for message + buffer = new byte[fixedHeaderSize + varHeaderSize + payloadSize]; + + // first fixed header byte + 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++; + + // encode remaining length + index = this.encodeRemainingLength(remainingLength, buffer, index); + + // check message identifier assigned (SUBSCRIBE uses QoS Level 1, so message id is mandatory) + if (this.messageId == 0) + throw new MqttClientException(MqttClientErrorCode.WrongMessageId); + buffer[index++] = (byte)((messageId >> 8) & 0x00FF); // MSB + buffer[index++] = (byte)(messageId & 0x00FF); // LSB + + topicIdx = 0; + for (topicIdx = 0; topicIdx < this.topics.Length; topicIdx++) + { + // topic name + buffer[index++] = (byte)((topicsUtf8[topicIdx].Length >> 8) & 0x00FF); // MSB + buffer[index++] = (byte)(topicsUtf8[topicIdx].Length & 0x00FF); // LSB + Array.Copy(topicsUtf8[topicIdx], 0, buffer, index, topicsUtf8[topicIdx].Length); + index += topicsUtf8[topicIdx].Length; + + // requested QoS + buffer[index++] = this.qosLevels[topicIdx]; + } + + return buffer; + } + } +} diff --git a/M2Mqtt/Messages/MqttMsgSubscribeEventArgs.cs b/M2Mqtt/Messages/MqttMsgSubscribeEventArgs.cs new file mode 100644 index 0000000..92a5824 --- /dev/null +++ b/M2Mqtt/Messages/MqttMsgSubscribeEventArgs.cs @@ -0,0 +1,83 @@ +/* +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 (!MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3) +using System; +#else +using Microsoft.SPOT; +#endif + +namespace uPLibrary.Networking.M2Mqtt.Messages +{ + /// + /// Event Args class for subscribe request on topics + /// + public class MqttMsgSubscribeEventArgs : EventArgs + { + #region Properties... + + /// + /// Message identifier + /// + public ushort MessageId + { + get { return this.messageId; } + internal set { this.messageId = value; } + } + + /// + /// Topics requested to subscribe + /// + public string[] Topics + { + get { return this.topics; } + internal set { this.topics = value; } + } + + /// + /// List of QOS Levels requested + /// + public byte[] QoSLevels + { + get { return this.qosLevels; } + internal set { this.qosLevels = value; } + } + + #endregion + + // message identifier + ushort messageId; + // topics requested to subscribe + string[] topics; + // QoS levels requested + byte[] qosLevels; + + /// + /// Constructor + /// + /// Message identifier for subscribe topics request + /// Topics requested to subscribe + /// List of QOS Levels requested + public MqttMsgSubscribeEventArgs(ushort messageId, string[] topics, byte[] qosLevels) + { + this.messageId = messageId; + this.topics = topics; + this.qosLevels = qosLevels; + } + } +} diff --git a/M2Mqtt/Messages/MqttMsgSubscribedEventArgs.cs b/M2Mqtt/Messages/MqttMsgSubscribedEventArgs.cs new file mode 100644 index 0000000..3b6daab --- /dev/null +++ b/M2Mqtt/Messages/MqttMsgSubscribedEventArgs.cs @@ -0,0 +1,70 @@ +/* +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 (!MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3) +using System; +#else +using Microsoft.SPOT; +#endif + +namespace uPLibrary.Networking.M2Mqtt.Messages +{ + /// + /// Event Args class for subscribed topics + /// + public class MqttMsgSubscribedEventArgs : EventArgs + { + #region Properties... + + /// + /// Message identifier + /// + public ushort MessageId + { + get { return this.messageId; } + internal set { this.messageId = value; } + } + + /// + /// List of granted QOS Levels + /// + public byte[] GrantedQoSLevels + { + get { return this.grantedQosLevels; } + internal set { this.grantedQosLevels = value; } + } + + #endregion + + // message identifier + ushort messageId; + // granted QOS levels + byte[] grantedQosLevels; + + /// + /// Constructor + /// + /// Message identifier for subscribed topics + /// List of granted QOS Levels + public MqttMsgSubscribedEventArgs(ushort messageId, byte[] grantedQosLevels) + { + this.messageId = messageId; + this.grantedQosLevels = grantedQosLevels; + } + } +} diff --git a/M2Mqtt/Messages/MqttMsgUnsuback.cs b/M2Mqtt/Messages/MqttMsgUnsuback.cs new file mode 100644 index 0000000..7c1a810 --- /dev/null +++ b/M2Mqtt/Messages/MqttMsgUnsuback.cs @@ -0,0 +1,122 @@ +/* +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; + +namespace uPLibrary.Networking.M2Mqtt.Messages +{ + /// + /// Class for UNSUBACK message from broker to client + /// + 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 + /// + public MqttMsgUnsuback() + { + this.type = MQTT_MSG_UNSUBACK_TYPE; + } + + /// + /// Parse bytes for a UNSUBACK message + /// + /// First fixed header byte + /// Channel connected to the broker + /// UNSUBACK message instance + public static MqttMsgUnsuback Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + { + byte[] buffer; + int index = 0; + MqttMsgUnsuback msg = new MqttMsgUnsuback(); + + // get remaining length and allocate buffer + int remainingLength = MqttMsgBase.decodeRemainingLength(channel); + buffer = new byte[remainingLength]; + + // read bytes from socket... + channel.Receive(buffer); + + // message id + msg.messageId = (ushort)((buffer[index++] << 8) & 0xFF00); + msg.messageId |= (buffer[index++]); + + return msg; + } + + public override byte[] GetBytes() + { + int fixedHeaderSize = 0; + int varHeaderSize = 0; + int payloadSize = 0; + int remainingLength = 0; + byte[] buffer; + int index = 0; + + // message identifier + varHeaderSize += MESSAGE_ID_SIZE; + + remainingLength += (varHeaderSize + payloadSize); + + // first byte of fixed header + fixedHeaderSize = 1; + + int temp = remainingLength; + // increase fixed header size based on remaining length + // (each remaining length byte can encode until 128) + do + { + fixedHeaderSize++; + temp = temp / 128; + } while (temp > 0); + + // allocate buffer for message + buffer = new byte[fixedHeaderSize + varHeaderSize + payloadSize]; + + // first fixed header byte + buffer[index] = (byte)(MQTT_MSG_UNSUBACK_TYPE << MSG_TYPE_OFFSET); + index++; + + // encode remaining length + index = this.encodeRemainingLength(remainingLength, buffer, index); + + // message id + buffer[index++] = (byte)((this.messageId >> 8) & 0x00FF); // MSB + buffer[index++] = (byte)(this.messageId & 0x00FF); // LSB + + return buffer; + } + } +} diff --git a/M2Mqtt/Messages/MqttMsgUnsubscribe.cs b/M2Mqtt/Messages/MqttMsgUnsubscribe.cs new file mode 100644 index 0000000..b2f53c2 --- /dev/null +++ b/M2Mqtt/Messages/MqttMsgUnsubscribe.cs @@ -0,0 +1,222 @@ +/* +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; +// if NOT .Net Micro Framework +#if (!MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3) +using System.Collections.Generic; +#endif +using System.Collections; +using System.Text; +using uPLibrary.Networking.M2Mqtt.Exceptions; + +namespace uPLibrary.Networking.M2Mqtt.Messages +{ + /// + /// Class for UNSUBSCRIBE message from client to broker + /// + public class MqttMsgUnsubscribe : MqttMsgBase + { + #region Properties... + + /// + /// List of topics to unsubscribe + /// + public string[] Topics + { + get { return this.topics; } + 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 + /// + public MqttMsgUnsubscribe() + { + this.type = MQTT_MSG_UNSUBSCRIBE_TYPE; + } + + /// + /// Constructor + /// + /// List of topics to unsubscribe + public MqttMsgUnsubscribe(string[] topics) + { + this.type = MQTT_MSG_UNSUBSCRIBE_TYPE; + + this.topics = topics; + + // UNSUBSCRIBE message uses QoS Level 1 + this.qosLevel = QOS_LEVEL_AT_LEAST_ONCE; + } + + /// + /// Parse bytes for a UNSUBSCRIBE message + /// + /// First fixed header byte + /// Channel connected to the broker + /// UNSUBSCRIBE message instance + public static MqttMsgUnsubscribe Parse(byte fixedHeaderFirstByte, IMqttNetworkChannel channel) + { + byte[] buffer; + int index = 0; + byte[] topicUtf8; + int topicUtf8Length; + MqttMsgUnsubscribe msg = new MqttMsgUnsubscribe(); + + // get remaining length and allocate buffer + int remainingLength = MqttMsgBase.decodeRemainingLength(channel); + buffer = new byte[remainingLength]; + + // 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; + + // message id + msg.messageId = (ushort)((buffer[index++] << 8) & 0xFF00); + msg.messageId |= (buffer[index++]); + + // payload contains topics + // NOTE : before, I don't know how many topics will be in the payload (so use List) + +// if .Net Micro Framework +#if (MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3) + IList tmpTopics = new ArrayList(); +// else other frameworks (.Net, .Net Compact, Mono, Windows Phone) +#else + IList tmpTopics = new List(); +#endif + do + { + // topic name + topicUtf8Length = ((buffer[index++] << 8) & 0xFF00); + topicUtf8Length |= buffer[index++]; + topicUtf8 = new byte[topicUtf8Length]; + Array.Copy(buffer, index, topicUtf8, 0, topicUtf8Length); + index += topicUtf8Length; + tmpTopics.Add(new String(Encoding.UTF8.GetChars(topicUtf8))); + } while (index < remainingLength); + + // copy from list to array + msg.topics = new string[tmpTopics.Count]; + for (int i = 0; i < tmpTopics.Count; i++) + { + msg.topics[i] = (string)tmpTopics[i]; + } + + return msg; + } + + public override byte[] GetBytes() + { + int fixedHeaderSize = 0; + int varHeaderSize = 0; + int payloadSize = 0; + int remainingLength = 0; + byte[] buffer; + int index = 0; + + // topics list empty + if ((this.topics == null) || (this.topics.Length == 0)) + throw new MqttClientException(MqttClientErrorCode.TopicsEmpty); + + // message identifier + varHeaderSize += MESSAGE_ID_SIZE; + + int topicIdx = 0; + byte[][] topicsUtf8 = new byte[this.topics.Length][]; + + for (topicIdx = 0; topicIdx < this.topics.Length; topicIdx++) + { + // check topic length + if ((this.topics[topicIdx].Length < MIN_TOPIC_LENGTH) || (this.topics[topicIdx].Length > MAX_TOPIC_LENGTH)) + throw new MqttClientException(MqttClientErrorCode.TopicLength); + + topicsUtf8[topicIdx] = Encoding.UTF8.GetBytes(this.topics[topicIdx]); + payloadSize += 2; // topic size (MSB, LSB) + payloadSize += topicsUtf8[topicIdx].Length; + } + + remainingLength += (varHeaderSize + payloadSize); + + // first byte of fixed header + fixedHeaderSize = 1; + + int temp = remainingLength; + // increase fixed header size based on remaining length + // (each remaining length byte can encode until 128) + do + { + fixedHeaderSize++; + temp = temp / 128; + } while (temp > 0); + + // allocate buffer for message + buffer = new byte[fixedHeaderSize + varHeaderSize + payloadSize]; + + // first fixed header byte + 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++; + + // encode remaining length + index = this.encodeRemainingLength(remainingLength, buffer, index); + + // check message identifier assigned + if (this.messageId == 0) + throw new MqttClientException(MqttClientErrorCode.WrongMessageId); + buffer[index++] = (byte)((messageId >> 8) & 0x00FF); // MSB + buffer[index++] = (byte)(messageId & 0x00FF); // LSB + + topicIdx = 0; + for (topicIdx = 0; topicIdx < this.topics.Length; topicIdx++) + { + // topic name + buffer[index++] = (byte)((topicsUtf8[topicIdx].Length >> 8) & 0x00FF); // MSB + buffer[index++] = (byte)(topicsUtf8[topicIdx].Length & 0x00FF); // LSB + Array.Copy(topicsUtf8[topicIdx], 0, buffer, index, topicsUtf8[topicIdx].Length); + index += topicsUtf8[topicIdx].Length; + } + + return buffer; + } + } +} diff --git a/M2Mqtt/Messages/MqttMsgUnsubscribeEventArgs.cs b/M2Mqtt/Messages/MqttMsgUnsubscribeEventArgs.cs new file mode 100644 index 0000000..5f1dd74 --- /dev/null +++ b/M2Mqtt/Messages/MqttMsgUnsubscribeEventArgs.cs @@ -0,0 +1,70 @@ +/* +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 (!MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3) +using System; +#else +using Microsoft.SPOT; +#endif + +namespace uPLibrary.Networking.M2Mqtt.Messages +{ + /// + /// Event Args class for unsubscribe request on topics + /// + public class MqttMsgUnsubscribeEventArgs : EventArgs + { + #region Properties... + + /// + /// Message identifier + /// + public ushort MessageId + { + get { return this.messageId; } + internal set { this.messageId = value; } + } + + /// + /// Topics requested to subscribe + /// + public string[] Topics + { + get { return this.topics; } + internal set { this.topics = value; } + } + + #endregion + + // message identifier + ushort messageId; + // topics requested to unsubscribe + string[] topics; + + /// + /// Constructor + /// + /// Message identifier for subscribed topics + /// Topics requested to subscribe + public MqttMsgUnsubscribeEventArgs(ushort messageId, string[] topics) + { + this.messageId = messageId; + this.topics = topics; + } + } +} diff --git a/M2Mqtt/Messages/MqttMsgUnsubscribedEventArgs.cs b/M2Mqtt/Messages/MqttMsgUnsubscribedEventArgs.cs new file mode 100644 index 0000000..99d8791 --- /dev/null +++ b/M2Mqtt/Messages/MqttMsgUnsubscribedEventArgs.cs @@ -0,0 +1,57 @@ +/* +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 (!MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3) +using System; +#else +using Microsoft.SPOT; +#endif + +namespace uPLibrary.Networking.M2Mqtt.Messages +{ + /// + /// Event Args class for unsubscribed topic + /// + public class MqttMsgUnsubscribedEventArgs : EventArgs + { + #region Properties... + + /// + /// Message identifier + /// + public ushort MessageId + { + get { return this.messageId; } + internal set { this.messageId = value; } + } + + #endregion + + // message identifier + ushort messageId; + + /// + /// Constructor + /// + /// Message identifier for unsubscribed topic + public MqttMsgUnsubscribedEventArgs(ushort messageId) + { + this.messageId = messageId; + } + } +} diff --git a/M2Mqtt/MqttClient.cs b/M2Mqtt/MqttClient.cs new file mode 100644 index 0000000..29a6696 --- /dev/null +++ b/M2Mqtt/MqttClient.cs @@ -0,0 +1,1999 @@ +/* +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; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using uPLibrary.Networking.M2Mqtt.Exceptions; +using uPLibrary.Networking.M2Mqtt.Messages; +using System.Collections; +// if .Net Micro Framework +#if (MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3) +using Microsoft.SPOT; +#if SSL +using Microsoft.SPOT.Net.Security; +#endif +// else other frameworks (.Net, .Net Compact, Mono, Windows Phone) +#else + +#if (SSL && !WINDOWS_PHONE) +using System.Security.Authentication; +using System.Net.Security; +#endif +#endif + +using System.Security.Cryptography.X509Certificates; + +namespace uPLibrary.Networking.M2Mqtt +{ + /// + /// MQTT Client + /// + public class MqttClient + { +#if BROKER + #region Constants ... + + // thread names + private const string RECEIVE_THREAD_NAME = "ReceiveThread"; + private const string RECEIVE_EVENT_THREAD_NAME = "ReceiveEventThread"; + private const string PROCESS_INFLIGHT_THREAD_NAME = "ProcessInflightThread"; + private const string KEEP_ALIVE_THREAD = "KeepAliveThread"; + + #endregion +#endif + + /// + /// Delagate that defines event handler for PUBLISH message received + /// + public delegate void MqttMsgPublishEventHandler(object sender, MqttMsgPublishEventArgs e); + + /// + /// Delegate that defines event handler for published message + /// + public delegate void MqttMsgPublishedEventHandler(object sender, MqttMsgPublishedEventArgs e); + + /// + /// Delagate that defines event handler for subscribed topic + /// + public delegate void MqttMsgSubscribedEventHandler(object sender, MqttMsgSubscribedEventArgs e); + + /// + /// Delagate that defines event handler for unsubscribed topic + /// + public delegate void MqttMsgUnsubscribedEventHandler(object sender, MqttMsgUnsubscribedEventArgs e); + +#if BROKER + /// + /// Delagate that defines event handler for SUBSCRIBE message received + /// + public delegate void MqttMsgSubscribeEventHandler(object sender, MqttMsgSubscribeEventArgs e); + + /// + /// Delagate that defines event handler for UNSUBSCRIBE message received + /// + public delegate void MqttMsgUnsubscribeEventHandler(object sender, MqttMsgUnsubscribeEventArgs e); + + /// + /// 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); + + // CA certificate + private X509Certificate caCert; + + // broker hostname, 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; + 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; + + // event for signaling synchronous receive + AutoResetEvent syncEndReceiving; + // message received + MqttMsgBase msgReceived; + + // exeption thrown during receiving + Exception exReceiving; + + // keep alive period (in ms) + private int keepAlivePeriod; + // thread for sending keep alive message + private Thread keepAliveThread; + private AutoResetEvent keepAliveEvent; + // keep alive timeout expired + private bool isKeepAliveTimeout; + // last communication time in ticks + private long lastCommTime; + + // event for PUBLISH message received + public event MqttMsgPublishEventHandler MqttMsgPublishReceived; + // event for published message + public event MqttMsgPublishedEventHandler MqttMsgPublished; + // event for subscribed topic + public event MqttMsgSubscribedEventHandler MqttMsgSubscribed; + // event for unsubscribed topic + public event MqttMsgUnsubscribedEventHandler MqttMsgUnsubscribed; +#if BROKER + // event for SUBSCRIBE message received + public event MqttMsgSubscribeEventHandler MqttMsgSubscribeReceived; + // event for USUBSCRIBE message received + public event MqttMsgUnsubscribeEventHandler MqttMsgUnsubscribeReceived; + // event for CONNECT message received + public event MqttMsgConnectEventHandler MqttMsgConnected; +#endif + // event for client disconnection (DISCONNECT message or not) + public event MqttMsgDisconnectEventHandler MqttMsgDisconnected; + + // channel to communicate over the network + private IMqttNetworkChannel channel; + + // inflight messages queue + private Queue inflightQueue; + // internal queue for received messages about inflight messages + private Queue internalQueue; + // receive queue for received messages + private Queue receiveQueue; + + // reference to avoid access to singleton via property + private MqttSettings settings; + + // current message identifier generated + private ushort messageIdCounter = 0; + + /// + /// Connection status between client and broker + /// + public bool IsConnected { get; private set; } + + /// + /// Client identifier + /// + public string ClientId { get; private set; } + + /// + /// Clean session flag + /// + public bool CleanSession { get; private set; } + + /// + /// Will flag + /// + public bool WillFlag { get; private set; } + + /// + /// Will QOS level + /// + public byte WillQosLevel { get; private set; } + + /// + /// Will topic + /// + public string WillTopic { get; private set; } + + /// + /// Will message + /// + public string WillMessage { get; private set; } + + /// + /// Constructor + /// + /// Broker IP address + public MqttClient(IPAddress brokerIpAddress) : + this(brokerIpAddress, MqttSettings.MQTT_BROKER_DEFAULT_PORT, false, null) + { + } + + /// + /// Constructor + /// + /// Broker IP address + /// Broker port + /// Using secure connection + /// CA certificate for secure connection + public MqttClient(IPAddress brokerIpAddress, int brokerPort, bool secure, X509Certificate caCert) + { + this.Init(null, brokerIpAddress, brokerPort, secure, caCert); + } + + /// + /// Constructor + /// + /// Broker Host Name + public MqttClient(string brokerHostName) : + this(brokerHostName, MqttSettings.MQTT_BROKER_DEFAULT_PORT, false, null) + { + } + + /// + /// Constructor + /// + /// Broker Host Name + /// 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) + { + 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"); + } + +#if BROKER + /// + /// Constructor + /// + /// Raw socket for communication + public MqttClient(Socket socket) + { + this.channel = new MqttNetworkChannel(socket); + + // reference to MQTT settings + this.settings = MqttSettings.Instance; + + // client not connected yet (CONNACK not send from client), some default values + this.IsConnected = false; + this.ClientId = null; + this.CleanSession = true; + + this.keepAliveEvent = new AutoResetEvent(false); + + // queue for handling inflight messages (publishing and acknowledge) + this.inflightWaitHandle = new AutoResetEvent(false); + this.inflightQueue = new Queue(); + + // queue for received message + this.receiveEventWaitHandle = new AutoResetEvent(false); + this.receiveQueue = new Queue(); + this.internalQueue = new Queue(); + } +#endif + + /// + /// MqttClient initialization + /// + /// Broker host name + /// Broker 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 !"); +#else + 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; + + this.syncEndReceiving = new AutoResetEvent(false); + this.keepAliveEvent = new AutoResetEvent(false); + + // queue for handling inflight messages (publishing and acknowledge) + this.inflightWaitHandle = new AutoResetEvent(false); + this.inflightQueue = new Queue(); + + // queue for received message + this.receiveEventWaitHandle = new AutoResetEvent(false); + this.receiveQueue = new Queue(); + this.internalQueue = new Queue(); + } + + /// + /// Connect to broker + /// + /// Client identifier + /// Return code of CONNACK message from broker + public byte Connect(string clientId) + { + return this.Connect(clientId, null, null, false, MqttMsgConnect.QOS_LEVEL_AT_MOST_ONCE, false, null, null, true, MqttMsgConnect.KEEP_ALIVE_PERIOD_DEFAULT); + } + + /// + /// Connect to broker + /// + /// Client identifier + /// Username + /// Password + /// Return code of CONNACK message from broker + public byte Connect(string clientId, + string username, + string password) + { + return this.Connect(clientId, username, password, false, MqttMsgConnect.QOS_LEVEL_AT_MOST_ONCE, false, null, null, true, MqttMsgConnect.KEEP_ALIVE_PERIOD_DEFAULT); + } + + /// + /// Connect to broker + /// + /// Client identifier + /// Username + /// Password + /// Clean sessione flag + /// Keep alive period + /// Return code of CONNACK message from broker + public byte Connect(string clientId, + string username, + string password, + bool cleanSession, + ushort keepAlivePeriod) + { + return this.Connect(clientId, username, password, false, MqttMsgConnect.QOS_LEVEL_AT_MOST_ONCE, false, null, null, cleanSession, keepAlivePeriod); + } + + /// + /// Connect to broker + /// + /// Client identifier + /// Username + /// Password + /// Will retain flag + /// Will QOS level + /// Will flag + /// Will topic + /// Will message + /// Clean sessione flag + /// Keep alive period + /// Return code of CONNACK message from broker + public byte Connect(string clientId, + string username, + string password, + bool willRetain, + byte willQosLevel, + bool willFlag, + string willTopic, + string willMessage, + bool cleanSession, + ushort keepAlivePeriod) + { + // create CONNECT message + MqttMsgConnect connect = new MqttMsgConnect(clientId, + username, + password, + willRetain, + willQosLevel, + willFlag, + willTopic, + willMessage, + cleanSession, + keepAlivePeriod); + + 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 + this.channel.Connect(); + } + catch (Exception ex) + { + throw new MqttConnectionException("Exception connecting to the broker", ex); + } + + this.lastCommTime = 0; + this.isRunning = true; + // start thread for receiving messages from broker + this.receiveThread = new Thread(this.ReceiveThread); + this.receiveThread.Start(); + + MqttMsgConnack connack = (MqttMsgConnack)this.SendReceive(connect.GetBytes()); + // if connection accepted, start keep alive timer and + if (connack.ReturnCode == MqttMsgConnack.CONN_ACCEPTED) + { + // set all client properties + this.ClientId = clientId; + this.CleanSession = cleanSession; + this.WillFlag = willFlag; + this.WillTopic = willTopic; + this.WillMessage = willMessage; + this.WillQosLevel = willQosLevel; + + 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(); + + // start thread for raising received message event from broker + this.receiveEventThread = new Thread(this.ReceiveEventThread); + this.receiveEventThread.Start(); + + // start thread for handling inflight messages queue to broker asynchronously (publish and acknowledge) + this.processInflightThread = new Thread(this.ProcessInflightThread); + this.processInflightThread.Start(); + + this.IsConnected = true; + } + return connack.ReturnCode; + } + + /// + /// Disconnect from broker + /// + public void Disconnect() + { + MqttMsgDisconnect disconnect = new MqttMsgDisconnect(); + this.Send(disconnect.GetBytes()); + + // close client + this.Close(); + } + +#if BROKER + /// + /// Open client communication + /// + public void Open() + { + 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(); + + // 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(); + + // 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(); + } +#endif + + /// + /// Close client + /// +#if BROKER + public void Close() +#else + private void Close() +#endif + { + // 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) + { + 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) + { + 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(); +#else + // unlock keep alive thread and wait + this.keepAliveEvent.Set(); + + if (this.keepAliveThread != null) + this.keepAliveThread.Join(); +#endif + } + + // close network channel + this.channel.Close(); + + // keep alive thread will set it gracefully + if (!this.isKeepAliveTimeout) + this.IsConnected = false; + } + + /// + /// Execute ping to broker for keep alive + /// + /// PINGRESP message from broker + private MqttMsgPingResp Ping() + { + MqttMsgPingReq pingreq = new MqttMsgPingReq(); + try + { + // broker must send PINGRESP within timeout equal to keep alive period + return (MqttMsgPingResp)this.SendReceive(pingreq.GetBytes(), this.keepAlivePeriod); + } + catch (Exception) + { + this.isKeepAliveTimeout = true; + // client must close connection + this.Close(); + return null; + } + } + +#if BROKER + /// + /// 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) + { + this.lastCommTime = 0; + + // create CONNACK message and ... + MqttMsgConnack connack = new MqttMsgConnack(); + connack.ReturnCode = returnCode; + // ... send it to the client + this.Send(connack.GetBytes()); + + // connection accepted, start keep alive thread checking + if (connack.ReturnCode == MqttMsgConnack.CONN_ACCEPTED) + { + this.ClientId = connect.ClientId; + this.CleanSession = connect.CleanSession; + this.WillFlag = connect.WillFlag; + this.WillTopic = connect.WillTopic; + this.WillMessage = connect.WillMessage; + this.WillQosLevel = connect.WillQosLevel; + + this.keepAlivePeriod = connect.KeepAlivePeriod * 1000; // convert in ms + // broker has a tolerance of 1.5 specified keep alive period + 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(); + + this.IsConnected = true; + } + // connection refused, close TCP/IP channel + else + { + this.Close(); + } + } + + /// + /// Send SUBACK message to the client + /// + /// Message Id for the SUBSCRIBE message that is being acknowledged + /// Granted QoS Levels + public void Suback(ushort messageId, byte[] grantedQosLevels) + { + MqttMsgSuback suback = new MqttMsgSuback(); + suback.MessageId = messageId; + suback.GrantedQoSLevels = grantedQosLevels; + + this.Send(suback.GetBytes()); + } + + /// + /// Send UNSUBACK message to the client + /// + /// Message Id for the UNSUBSCRIBE message that is being acknowledged + public void Unsuback(ushort messageId) + { + MqttMsgUnsuback unsuback = new MqttMsgUnsuback(); + unsuback.MessageId = messageId; + + this.Send(unsuback.GetBytes()); + } +#endif + + /// + /// Subscribe for message topics + /// + /// List of topics to subscribe + /// QOS levels related to topics + /// Message Id related to SUBSCRIBE message + public ushort Subscribe(string[] topics, byte[] qosLevels) + { + MqttMsgSubscribe subscribe = + new MqttMsgSubscribe(topics, qosLevels); + subscribe.MessageId = this.GetMessageId(); + + // enqueue subscribe request into the inflight queue + this.EnqueueInflight(subscribe, MqttMsgFlow.ToPublish); + + return subscribe.MessageId; + } + + public ushort Subscribe(string topic, byte qos) + { + return Subscribe(new[] { topic }, new[] { qos }); + } + + /// + /// Unsubscribe for message topics + /// + /// List of topics to unsubscribe + /// Message Id in UNSUBACK message from broker + public ushort Unsubscribe(string[] topics) + { + MqttMsgUnsubscribe unsubscribe = + new MqttMsgUnsubscribe(topics); + unsubscribe.MessageId = this.GetMessageId(); + + // enqueue unsubscribe request into the inflight queue + this.EnqueueInflight(unsubscribe, MqttMsgFlow.ToPublish); + + return unsubscribe.MessageId; + } + + /// + /// Publish a message asynchronously (QoS Level 0 and not retained) + /// + /// Message topic + /// Message data (payload) + /// Message Id related to PUBLISH message + public ushort Publish(string topic, byte[] message) + { + return this.Publish(topic, message, MqttMsgBase.QOS_LEVEL_AT_MOST_ONCE, false); + } + + /// + /// Publish a message asynchronously + /// + /// Message topic + /// Message data (payload) + /// QoS Level + /// Retain flag + /// Message Id related to PUBLISH message + public ushort Publish(string topic, byte[] message, byte qosLevel, bool retain) + { + MqttMsgPublish publish = + new MqttMsgPublish(topic, message, false, qosLevel, retain); + publish.MessageId = this.GetMessageId(); + + // enqueue message to publish into the inflight queue + this.EnqueueInflight(publish, MqttMsgFlow.ToPublish); + + return publish.MessageId; + } + + /// + /// Wrapper method for raising message received event + /// + /// Message received + private void OnMqttMsgReceived(MqttMsgBase msg) + { + lock (this.receiveQueue) + { + this.receiveQueue.Enqueue(msg); + } + + this.receiveEventWaitHandle.Set(); + } + + /// + /// Wrapper method for raising PUBLISH message received event + /// + /// PUBLISH message received + private void OnMqttMsgPublishReceived(MqttMsgPublish publish) + { + if (this.MqttMsgPublishReceived != null) + { + this.MqttMsgPublishReceived(this, + new MqttMsgPublishEventArgs(publish.Topic, publish.Message, publish.DupFlag, publish.QosLevel, publish.Retain)); + } + } + + /// + /// Wrapper method for raising published message event + /// + /// Message identifier for published message + private void OnMqttMsgPublished(ushort messageId) + { + if (this.MqttMsgPublished != null) + { + this.MqttMsgPublished(this, + new MqttMsgPublishedEventArgs(messageId)); + } + } + + /// + /// Wrapper method for raising subscribed topic event + /// + /// SUBACK message received + private void OnMqttMsgSubscribed(MqttMsgSuback suback) + { + if (this.MqttMsgSubscribed != null) + { + this.MqttMsgSubscribed(this, + new MqttMsgSubscribedEventArgs(suback.MessageId, suback.GrantedQoSLevels)); + } + } + + /// + /// Wrapper method for raising unsubscribed topic event + /// + /// Message identifier for unsubscribed topic + private void OnMqttMsgUnsubscribed(ushort messageId) + { + if (this.MqttMsgUnsubscribed != null) + { + this.MqttMsgUnsubscribed(this, + new MqttMsgUnsubscribedEventArgs(messageId)); + } + } + +#if BROKER + /// + /// Wrapper method for raising SUBSCRIBE message event + /// + /// Message identifier for subscribe topics request + /// Topics requested to subscribe + /// List of QOS Levels requested + private void OnMqttMsgSubscribeReceived(ushort messageId, string[] topics, byte[] qosLevels) + { + if (this.MqttMsgSubscribeReceived != null) + { + this.MqttMsgSubscribeReceived(this, + new MqttMsgSubscribeEventArgs(messageId, topics, qosLevels)); + } + } + + /// + /// Wrapper method for raising UNSUBSCRIBE message event + /// + /// Message identifier for unsubscribe topics request + /// Topics requested to unsubscribe + private void OnMqttMsgUnsubscribeReceived(ushort messageId, string[] topics) + { + if (this.MqttMsgUnsubscribeReceived != null) + { + this.MqttMsgUnsubscribeReceived(this, + new MqttMsgUnsubscribeEventArgs(messageId, topics)); + } + } + + /// + /// Wrapper method for client connection event + /// + private void OnMqttMsgConnected(MqttMsgConnect connect) + { + if (this.MqttMsgConnected != null) + { + this.MqttMsgConnected(this, new MqttMsgConnectEventArgs(connect)); + } + } +#endif + + /// + /// Wrapper method for client disconnection event + /// + private void OnMqttMsgDisconnected() + { + if (this.MqttMsgDisconnected != null) + { + this.MqttMsgDisconnected(this, EventArgs.Empty); + } + } + + /// + /// Send a message + /// + /// Message bytes + private void Send(byte[] msgBytes) + { + try + { + // send message + this.channel.Send(msgBytes); + +#if !BROKER + // update last message sent ticks + this.lastCommTime = Environment.TickCount; +#endif + } + catch (Exception e) + { + throw new MqttCommunicationException(e); + } + } + + /// + /// Send a message to the broker and wait answer + /// + /// Message bytes + /// MQTT message response + private MqttMsgBase SendReceive(byte[] msgBytes) + { + return this.SendReceive(msgBytes, MqttSettings.MQTT_DEFAULT_TIMEOUT); + } + + /// + /// Send a message to the broker and wait answer + /// + /// Message bytes + /// Timeout for receiving answer + /// MQTT message response + private MqttMsgBase SendReceive(byte[] msgBytes, int timeout) + { + // reset handle before sending + this.syncEndReceiving.Reset(); + try + { + // send message + this.channel.Send(msgBytes); + + // update last message sent ticks + this.lastCommTime = Environment.TickCount; + } + catch (SocketException 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; +#endif + + throw new MqttCommunicationException(e); + } + +#if (MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK) + // wait for answer from broker + if (this.syncEndReceiving.WaitOne(timeout, false)) +#else + // wait for answer from broker + if (this.syncEndReceiving.WaitOne(timeout)) +#endif + { + // message received without exception + if (this.exReceiving == null) + return this.msgReceived; + // receiving thread catched exception + else + throw this.exReceiving; + } + else + { + // throw timeout exception + //throw new MqttTimeoutException(); + throw new MqttCommunicationException(); + } + } + + /// + /// Enqueue a message into the inflight queue + /// + /// Message to enqueue + /// Message flow (publish, acknowledge) + private void EnqueueInflight(MqttMsgBase msg, MqttMsgFlow flow) + { + // enqueue is needed (or not) + bool enqueue = true; + + // if it is a PUBLISH message with QoS Level 2 + if ((msg.Type == MqttMsgBase.MQTT_MSG_PUBLISH_TYPE) && + (msg.QosLevel == MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE)) + { + lock (this.inflightQueue) + { + // if it is a PUBLISH message already received (it is in the inflight queue), the publisher + // re-sent it because it didn't received the PUBREC. In this case, we have to re-send 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(((MqttMsgPublish)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 + if (msgCtx != null) + { + msgCtx.State = MqttMsgState.QueuedQos2; + msgCtx.Flow = MqttMsgFlow.ToAcknowledge; + enqueue = false; + } + } + } + + if (enqueue) + { + // set a default state + MqttMsgState state = MqttMsgState.QueuedQos0; + + // based on QoS level, the messages flow between broker and client changes + switch (msg.QosLevel) + { + // QoS Level 0 + case MqttMsgBase.QOS_LEVEL_AT_MOST_ONCE: + + state = MqttMsgState.QueuedQos0; + break; + + // QoS Level 1 + case MqttMsgBase.QOS_LEVEL_AT_LEAST_ONCE: + + state = MqttMsgState.QueuedQos1; + break; + + // QoS Level 2 + case MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE: + + state = MqttMsgState.QueuedQos2; + break; + } + + // queue message context + MqttMsgContext msgContext = new MqttMsgContext() + { + Message = msg, + State = state, + Flow = flow, + Attempt = 0 + }; + + lock (this.inflightQueue) + { + // enqueue message and unlock send thread + this.inflightQueue.Enqueue(msgContext); + } + } + + this.inflightWaitHandle.Set(); + } + + /// + /// Enqueue a message into the internal queue + /// + /// Message to enqueue + private void EnqueueInternal(MqttMsgBase msg) + { + // enqueue is needed (or not) + bool enqueue = true; + + // if it is a PUBREL message (for QoS Level 2) + if (msg.Type == MqttMsgBase.MQTT_MSG_PUBREL_TYPE) + { + lock (this.inflightQueue) + { + // if it is a PUBREL but the corresponding PUBLISH isn't in the inflight queue, + // it means that we processed PUBLISH message and received PUBREL and we sent PUBCOMP + // but publisher didn't receive PUBCOMP so it re-sent PUBREL. We need only to re-send 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(((MqttMsgPubrel)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; + + this.Send(pubcomp.GetBytes()); + + enqueue = false; + } + } + } + + if (enqueue) + { + lock (this.internalQueue) + { + this.internalQueue.Enqueue(msg); + this.inflightWaitHandle.Set(); + } + } + } + + /// + /// Thread for receiving messages + /// + private void ReceiveThread() + { + int readBytes = 0; + 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); + } + + if (readBytes > 0) + { +#if BROKER + // update last message received ticks + this.lastCommTime = Environment.TickCount; +#endif + + // extract message type from received byte + msgType = (byte)((fixedHeaderFirstByte[0] & MqttMsgBase.MSG_TYPE_MASK) >> MqttMsgBase.MSG_TYPE_OFFSET); + + switch (msgType) + { + // CONNECT message received + case MqttMsgBase.MQTT_MSG_CONNECT_TYPE: + +#if BROKER + MqttMsgConnect connect = MqttMsgConnect.Parse(fixedHeaderFirstByte[0], this.channel); + + // raise message received event + this.OnMqttMsgReceived(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.syncEndReceiving.Set(); + break; +#endif + + // PINGREQ message received + case MqttMsgBase.MQTT_MSG_PINGREQ_TYPE: + +#if BROKER + this.msgReceived = MqttMsgPingReq.Parse(fixedHeaderFirstByte[0], this.channel); + + MqttMsgPingResp pingresp = new MqttMsgPingResp(); + this.Send(pingresp.GetBytes()); + + // raise message received event + //this.OnMqttMsgReceived(this.msgReceived); + break; +#else + throw new MqttClientException(MqttClientErrorCode.WrongBrokerMessage); +#endif + + // PINGRESP message received + case MqttMsgBase.MQTT_MSG_PINGRESP_TYPE: + +#if BROKER + throw new MqttClientException(MqttClientErrorCode.WrongBrokerMessage); +#else + this.msgReceived = MqttMsgPingResp.Parse(fixedHeaderFirstByte[0], this.channel); + this.syncEndReceiving.Set(); + break; +#endif + + // SUBSCRIBE message received + case MqttMsgBase.MQTT_MSG_SUBSCRIBE_TYPE: + +#if BROKER + MqttMsgSubscribe subscribe = MqttMsgSubscribe.Parse(fixedHeaderFirstByte[0], this.channel); + + // raise message received event + this.OnMqttMsgReceived(subscribe); + + break; +#else + throw new MqttClientException(MqttClientErrorCode.WrongBrokerMessage); +#endif + + // SUBACK message received + case MqttMsgBase.MQTT_MSG_SUBACK_TYPE: + +#if BROKER + 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); + + // enqueue SUBACK message into the internal queue + this.EnqueueInternal(suback); + + break; +#endif + + // PUBLISH message received + case MqttMsgBase.MQTT_MSG_PUBLISH_TYPE: + + MqttMsgPublish publish = MqttMsgPublish.Parse(fixedHeaderFirstByte[0], this.channel); + + // enqueue PUBLISH message to acknowledge into the inflight queue + this.EnqueueInflight(publish, MqttMsgFlow.ToAcknowledge); + + break; + + // PUBACK message received + 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); + + // enqueue PUBACK message into the internal queue + this.EnqueueInternal(puback); + + break; + + // PUBREC message received + 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); + + // enqueue PUBREC message into the internal queue + this.EnqueueInternal(pubrec); + + break; + + // PUBREL message received + 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); + + // 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); + + // enqueue PUBCOMP message into the internal queue + this.EnqueueInternal(pubcomp); + + break; + + // UNSUBSCRIBE message received + case MqttMsgBase.MQTT_MSG_UNSUBSCRIBE_TYPE: + +#if BROKER + MqttMsgUnsubscribe unsubscribe = MqttMsgUnsubscribe.Parse(fixedHeaderFirstByte[0], this.channel); + + // raise message received event + this.OnMqttMsgReceived(unsubscribe); + + break; +#else + throw new MqttClientException(MqttClientErrorCode.WrongBrokerMessage); +#endif + + // UNSUBACK message received + case MqttMsgBase.MQTT_MSG_UNSUBACK_TYPE: + +#if BROKER + 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); + + // enqueue UNSUBACK message into the internal queue + this.EnqueueInternal(unsuback); + + break; +#endif + + // DISCONNECT message received + case MqttMsgDisconnect.MQTT_MSG_DISCONNECT_TYPE: + +#if BROKER + MqttMsgDisconnect disconnect = MqttMsgDisconnect.Parse(fixedHeaderFirstByte[0], this.channel); + + // raise message received event + this.OnMqttMsgReceived(disconnect); + + break; +#else + throw new MqttClientException(MqttClientErrorCode.WrongBrokerMessage); +#endif + + default: + + throw new MqttClientException(MqttClientErrorCode.WrongBrokerMessage); + } + + this.exReceiving = null; + } + + } + catch (Exception e) + { + this.exReceiving = new MqttCommunicationException(e); + } + } + } + + /// + /// Thread for handling keep alive message + /// + private void KeepAliveThread() + { + long now = 0; + int wait = this.keepAlivePeriod; + this.isKeepAliveTimeout = false; + + while (this.isRunning) + { +#if (MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK) + // waiting... + this.keepAliveEvent.WaitOne(wait, false); +#else + // waiting... + this.keepAliveEvent.WaitOne(wait); +#endif + + if (this.isRunning) + { + now = Environment.TickCount; + + // if timeout exceeded ... + if ((now - this.lastCommTime) >= this.keepAlivePeriod) + { +#if BROKER + this.isKeepAliveTimeout = true; + // client must close connection + this.Close(); +#else + // ... send keep alive + this.Ping(); + wait = this.keepAlivePeriod; +#endif + } + else + { + // update waiting time + wait = (int)(this.keepAlivePeriod - (now - this.lastCommTime)); + } + } + } + + if (this.isKeepAliveTimeout) + { + this.IsConnected = false; + // raise disconnection client event + this.OnMqttMsgDisconnected(); + } + } + + /// + /// Thread for raising received message event + /// + private void ReceiveEventThread() + { + while (this.isRunning) + { + if (this.receiveQueue.Count == 0) + // wait on receiving message from client + this.receiveEventWaitHandle.WaitOne(); + + // check if it is running or we are closing client + if (this.isRunning) + { + // get message from queue + MqttMsgBase msg = null; + lock (this.receiveQueue) + { + if (this.receiveQueue.Count > 0) + msg = (MqttMsgBase)this.receiveQueue.Dequeue(); + } + + if (msg != null) + { + 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; +#else + throw new MqttClientException(MqttClientErrorCode.WrongBrokerMessage); +#endif + + // 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); +#else + throw new MqttClientException(MqttClientErrorCode.WrongBrokerMessage); +#endif + + // SUBACK message received + case MqttMsgBase.MQTT_MSG_SUBACK_TYPE: + + // raise subscribed topic event (SUBACK message received) + this.OnMqttMsgSubscribed((MqttMsgSuback)msg); + break; + + // PUBLISH message received + case MqttMsgBase.MQTT_MSG_PUBLISH_TYPE: + + // raise PUBLISH message received event + this.OnMqttMsgPublishReceived((MqttMsgPublish)msg); + break; + + // 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; + + // PUBREL message received + case MqttMsgBase.MQTT_MSG_PUBREL_TYPE: + + // raise message received event + // (PUBREL received for QoS Level 2) + this.OnMqttMsgPublishReceived((MqttMsgPublish)msg); + break; + + // 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; + + // 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; +#else + throw new MqttClientException(MqttClientErrorCode.WrongBrokerMessage); +#endif + + // UNSUBACK message received + case MqttMsgBase.MQTT_MSG_UNSUBACK_TYPE: + + // raise unsubscribed topic event + this.OnMqttMsgUnsubscribed(((MqttMsgUnsuback)msg).MessageId); + break; + + // DISCONNECT message received from client + case MqttMsgDisconnect.MQTT_MSG_DISCONNECT_TYPE: + +#if BROKER + // raise disconnected client event (DISCONNECT message received) + this.OnMqttMsgDisconnected(); + break; +#else + throw new MqttClientException(MqttClientErrorCode.WrongBrokerMessage); +#endif + } + } + } + } + } + + /// + /// Process inflight messages queue + /// + private void ProcessInflightThread() + { + MqttMsgContext msgContext = null; + MqttMsgBase msgInflight = null; + MqttMsgBase msgReceived = null; + bool acknowledge = false; + int timeout = Timeout.Infinite; + + try + { + while (this.isRunning) + { +#if (MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK) + // wait on message queueud to inflight + this.inflightWaitHandle.WaitOne(timeout, false); +#else + // wait on message queueud to inflight + this.inflightWaitHandle.WaitOne(timeout); +#endif + + // it could be unblocked because Close() method is joining + if (this.isRunning) + { + lock (this.inflightQueue) + { + // set timeout tu MaxValue instead of Infinte (-1) to perform + // compare with calcultad current msgTimeout + timeout = Int32.MaxValue; + + // a message inflight could be re-enqueued but we have to + // analyze it only just one time for cycle + int count = this.inflightQueue.Count; + // process all inflight queued messages + while (count > 0) + { + count--; + acknowledge = false; + msgReceived = null; + + // dequeue message context from queue + msgContext = (MqttMsgContext)this.inflightQueue.Dequeue(); + + // get inflight message + msgInflight = (MqttMsgBase)msgContext.Message; + + switch (msgContext.State) + { + case MqttMsgState.QueuedQos0: + + // QoS 0, PUBLISH message to send to broker, no state change, no acknowledge + if (msgContext.Flow == MqttMsgFlow.ToPublish) + { + this.Send(msgInflight.GetBytes()); + } + // QoS 0, no need acknowledge + else if (msgContext.Flow == MqttMsgFlow.ToAcknowledge) + { + // notify published message from broker (no need acknowledged) + this.OnMqttMsgReceived(msgInflight); + } + break; + + case MqttMsgState.QueuedQos1: + + // QoS 1, PUBLISH or SUBSCRIBE/UNSUBSCRIBE message to send to broker, state change to wait PUBACK or SUBACK/UNSUBACK + if (msgContext.Flow == MqttMsgFlow.ToPublish) + { + if (msgInflight.Type == MqttMsgBase.MQTT_MSG_PUBLISH_TYPE) + // PUBLISH message to send, wait for PUBACK + msgContext.State = MqttMsgState.WaitForPuback; + else if (msgInflight.Type == MqttMsgBase.MQTT_MSG_SUBSCRIBE_TYPE) + // SUBSCRIBE message to send, wait for SUBACK + msgContext.State = MqttMsgState.WaitForSuback; + else if (msgInflight.Type == MqttMsgBase.MQTT_MSG_UNSUBSCRIBE_TYPE) + // 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.GetBytes()); + + // update timeout + int msgTimeout = (this.settings.DelayOnRetry - (Environment.TickCount - msgContext.Timestamp)); + timeout = (msgTimeout < timeout) ? msgTimeout : timeout; + + // re-enqueue message (I have to re-analyze for receiving PUBACK, SUBACK or UNSUBACK) + this.inflightQueue.Enqueue(msgContext); + } + // QoS 1, PUBLISH message received from broker to acknowledge, send PUBACK + else if (msgContext.Flow == MqttMsgFlow.ToAcknowledge) + { + MqttMsgPuback puback = new MqttMsgPuback(); + puback.MessageId = ((MqttMsgPublish)msgInflight).MessageId; + + this.Send(puback.GetBytes()); + + // notify published message from broker and acknowledged + this.OnMqttMsgReceived(msgInflight); + } + break; + + case MqttMsgState.QueuedQos2: + + // 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++; + // retry ? set dup flag + if (msgContext.Attempt > 1) + msgInflight.DupFlag = true; + + this.Send(msgInflight.GetBytes()); + + // update timeout + int msgTimeout = (this.settings.DelayOnRetry - (Environment.TickCount - msgContext.Timestamp)); + timeout = (msgTimeout < timeout) ? msgTimeout : timeout; + + // re-enqueue message (I have to re-analyze for receiving PUBREC) + this.inflightQueue.Enqueue(msgContext); + } + // QoS 2, PUBLISH message received from broker to acknowledge, send PUBREC, state change to wait PUBREL + else if (msgContext.Flow == MqttMsgFlow.ToAcknowledge) + { + MqttMsgPubrec pubrec = new MqttMsgPubrec(); + pubrec.MessageId = ((MqttMsgPublish)msgInflight).MessageId; + + msgContext.State = MqttMsgState.WaitForPubrel; + + this.Send(pubrec.GetBytes()); + + // re-enqueue message (I have to re-analyze for receiving PUBREL) + this.inflightQueue.Enqueue(msgContext); + } + break; + + case MqttMsgState.WaitForPuback: + case MqttMsgState.WaitForSuback: + case MqttMsgState.WaitForUnsuback: + + // QoS 1, waiting for PUBACK of a PUBLISH message sent or + // waiting for SUBACK of a SUBSCRIBE message sent or + // waiting for UNSUBACK of a UNSUBSCRIBE message sent or + if (msgContext.Flow == MqttMsgFlow.ToPublish) + { + acknowledge = false; + lock (this.internalQueue) + { + if (this.internalQueue.Count > 0) + msgReceived = (MqttMsgBase)this.internalQueue.Peek(); + } + + // 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))) + { + // 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))) + { + lock (this.internalQueue) + { + // received message processed + this.internalQueue.Dequeue(); + acknowledge = true; + } + + // notify received acknowledge from broker of a published message or subscribe/unsubscribe message + this.OnMqttMsgReceived(msgReceived); + } + } + + // current message not acknowledged, no PUBACK or SUBACK/UNSUBACK or not equal messageid + if (!acknowledge) + { + // 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) + { + // max retry not reached, resend + if (msgContext.Attempt <= this.settings.AttemptsOnRetry) + { + msgContext.State = MqttMsgState.QueuedQos1; + + // re-enqueue message + this.inflightQueue.Enqueue(msgContext); + + // update timeout (0 -> reanalyze queue immediately) + timeout = 0; + } + } + else + { + // re-enqueue message (I have to re-analyze for receiving PUBACK, SUBACK or UNSUBACK) + this.inflightQueue.Enqueue(msgContext); + + // update timeout + int msgTimeout = (this.settings.DelayOnRetry - (Environment.TickCount - msgContext.Timestamp)); + timeout = (msgTimeout < timeout) ? msgTimeout : timeout; + } + } + } + break; + + case MqttMsgState.WaitForPubrec: + + // QoS 2, waiting for PUBREC of a PUBLISH message sent + if (msgContext.Flow == MqttMsgFlow.ToPublish) + { + acknowledge = false; + lock (this.internalQueue) + { + if (this.internalQueue.Count > 0) + msgReceived = (MqttMsgBase)this.internalQueue.Peek(); + } + + // it is a PUBREC message + 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) + { + lock (this.internalQueue) + { + // received message processed + this.internalQueue.Dequeue(); + acknowledge = true; + } + + MqttMsgPubrel pubrel = new MqttMsgPubrel(); + pubrel.MessageId = ((MqttMsgPublish)msgInflight).MessageId; + + msgContext.State = MqttMsgState.WaitForPubcomp; + msgContext.Timestamp = Environment.TickCount; + msgContext.Attempt = 1; + + this.Send(pubrel.GetBytes()); + + // update timeout + int msgTimeout = (this.settings.DelayOnRetry - (Environment.TickCount - msgContext.Timestamp)); + timeout = (msgTimeout < timeout) ? msgTimeout : timeout; + + // re-enqueue message + this.inflightQueue.Enqueue(msgContext); + } + } + + // current message not acknowledged + if (!acknowledge) + { + // check timeout for receiving PUBREC since PUBLISH was sent + if ((Environment.TickCount - msgContext.Timestamp) >= this.settings.DelayOnRetry) + { + // max retry not reached, resend + if (msgContext.Attempt <= this.settings.AttemptsOnRetry) + { + msgContext.State = MqttMsgState.QueuedQos2; + + // re-enqueue message + this.inflightQueue.Enqueue(msgContext); + + // update timeout (0 -> reanalyze queue immediately) + timeout = 0; + } + } + else + { + // re-enqueue message + this.inflightQueue.Enqueue(msgContext); + + // update timeout + int msgTimeout = (this.settings.DelayOnRetry - (Environment.TickCount - msgContext.Timestamp)); + timeout = (msgTimeout < timeout) ? msgTimeout : timeout; + } + } + } + break; + + case MqttMsgState.WaitForPubrel: + + // QoS 2, waiting for PUBREL of a PUBREC message sent + if (msgContext.Flow == MqttMsgFlow.ToAcknowledge) + { + lock (this.internalQueue) + { + if (this.internalQueue.Count > 0) + msgReceived = (MqttMsgBase)this.internalQueue.Peek(); + } + + // it is a PUBREL message + 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) + { + lock (this.internalQueue) + { + // received message processed + this.internalQueue.Dequeue(); + } + + MqttMsgPubcomp pubcomp = new MqttMsgPubcomp(); + pubcomp.MessageId = ((MqttMsgPublish)msgInflight).MessageId; + + this.Send(pubcomp.GetBytes()); + + // notify published message from broker and acknowledged + this.OnMqttMsgReceived(msgInflight); + } + else + { + // re-enqueue message + this.inflightQueue.Enqueue(msgContext); + } + } + else + { + // re-enqueue message + this.inflightQueue.Enqueue(msgContext); + } + } + break; + + case MqttMsgState.WaitForPubcomp: + + // QoS 2, waiting for PUBCOMP of a PUBREL message sent + if (msgContext.Flow == MqttMsgFlow.ToPublish) + { + acknowledge = false; + lock (this.internalQueue) + { + if (this.internalQueue.Count > 0) + msgReceived = (MqttMsgBase)this.internalQueue.Peek(); + } + + // it is a PUBCOMP message + if ((msgReceived != null) && (msgReceived.Type == MqttMsgBase.MQTT_MSG_PUBCOMP_TYPE)) + { + // PUBCOMP message for the current message + if (((MqttMsgPubcomp)msgReceived).MessageId == ((MqttMsgPublish)msgInflight).MessageId) + { + lock (this.internalQueue) + { + // received message processed + this.internalQueue.Dequeue(); + acknowledge = true; + } + + // notify received acknowledge from broker of a published message + this.OnMqttMsgReceived(msgReceived); + } + } + + // current message not acknowledged + if (!acknowledge) + { + // check timeout for receiving PUBCOMP since PUBREL was sent + if ((Environment.TickCount - msgContext.Timestamp) >= this.settings.DelayOnRetry) + { + // max retry not reached, resend + if (msgContext.Attempt < this.settings.AttemptsOnRetry) + { + msgContext.State = MqttMsgState.SendPubrel; + + // re-enqueue message + this.inflightQueue.Enqueue(msgContext); + + // update timeout (0 -> reanalyze queue immediately) + timeout = 0; + } + } + else + { + // re-enqueue message + this.inflightQueue.Enqueue(msgContext); + + // update timeout + int msgTimeout = (this.settings.DelayOnRetry - (Environment.TickCount - msgContext.Timestamp)); + timeout = (msgTimeout < timeout) ? msgTimeout : timeout; + } + } + } + break; + + case MqttMsgState.SendPubrec: + + // TODO : impossible ? --> QueuedQos2 ToAcknowledge + break; + + case MqttMsgState.SendPubrel: + + // QoS 2, PUBREL message to send to broker, state change to wait PUBCOMP + if (msgContext.Flow == MqttMsgFlow.ToPublish) + { + MqttMsgPubrel pubrel = new MqttMsgPubrel(); + pubrel.MessageId = ((MqttMsgPublish)msgInflight).MessageId; + + msgContext.State = MqttMsgState.WaitForPubcomp; + msgContext.Timestamp = Environment.TickCount; + msgContext.Attempt++; + // retry ? set dup flag + if (msgContext.Attempt > 1) + pubrel.DupFlag = true; + + this.Send(pubrel.GetBytes()); + + // update timeout + int msgTimeout = (this.settings.DelayOnRetry - (Environment.TickCount - msgContext.Timestamp)); + timeout = (msgTimeout < timeout) ? msgTimeout : timeout; + + // re-enqueue message + this.inflightQueue.Enqueue(msgContext); + } + break; + + case MqttMsgState.SendPubcomp: + // TODO : impossible ? + break; + case MqttMsgState.SendPuback: + // TODO : impossible ? --> QueuedQos1 ToAcknowledge + break; + default: + break; + } + } + + // if calculated timeout is MaxValue, it means that must be Infinite (-1) + if (timeout == Int32.MaxValue) + timeout = Timeout.Infinite; + } + } + } + } + catch (MqttCommunicationException) + { + this.Close(); + + // raise disconnection client event + this.OnMqttMsgDisconnected(); + } + } + + /// + /// 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; + return this.messageIdCounter; + } + + /// + /// Finder class for PUBLISH message inside a queue + /// + internal class MqttMsgContextFinder + { + // PUBLISH message id + internal ushort MessageId { get; set; } + // message flow into inflight queue + internal MqttMsgFlow Flow { get; set; } + + /// + /// Constructor + /// + /// Message Id + /// Message flow inside inflight queue + internal MqttMsgContextFinder(ushort messageId, MqttMsgFlow flow) + { + this.MessageId = messageId; + this.Flow = flow; + } + + internal bool Find(object item) + { + MqttMsgContext msgCtx = (MqttMsgContext)item; + return ((msgCtx.Message.Type == MqttMsgBase.MQTT_MSG_PUBLISH_TYPE) && + (((MqttMsgPublish)msgCtx.Message).MessageId == this.MessageId) && + msgCtx.Flow == this.Flow); + + } + } + } +} diff --git a/M2Mqtt/MqttNetworkChannel.cs b/M2Mqtt/MqttNetworkChannel.cs new file mode 100644 index 0000000..4455496 --- /dev/null +++ b/M2Mqtt/MqttNetworkChannel.cs @@ -0,0 +1,272 @@ +/* +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/MqttSettings.cs b/M2Mqtt/MqttSettings.cs new file mode 100644 index 0000000..9db3351 --- /dev/null +++ b/M2Mqtt/MqttSettings.cs @@ -0,0 +1,98 @@ +/* +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. +*/ + +namespace uPLibrary.Networking.M2Mqtt +{ + /// + /// Settings class for the MQTT broker + /// + public class MqttSettings + { + // default port for MQTT protocol + 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; + // 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; + + /// + /// Listening connection port + /// + public int Port { get; internal set; } + + /// + /// Listening connection SSL port + /// + public int SslPort { get; internal set; } + + /// + /// Timeout on client connection (before receiving CONNECT message) + /// + public int TimeoutOnConnection { get; internal set; } + + /// + /// Timeout on receiving + /// + public int TimeoutOnReceiving { get; internal set; } + + /// + /// Attempts on retry + /// + public int AttemptsOnRetry { get; internal set; } + + /// + /// Delay on retry + /// + public int DelayOnRetry { get; internal set; } + + /// + /// Singleton instance of settings + /// + public static MqttSettings Instance + { + get + { + if (instance == null) + instance = new MqttSettings(); + return instance; + } + } + + // singleton instance + private static MqttSettings instance; + + /// + /// Constructor + /// + private MqttSettings() + { + this.Port = MQTT_BROKER_DEFAULT_PORT; + this.SslPort = MQTT_BROKER_DEFAULT_SSL_PORT; + this.TimeoutOnReceiving = MQTT_DEFAULT_TIMEOUT; + this.AttemptsOnRetry = MQTT_ATTEMPTS_RETRY; + this.DelayOnRetry = MQTT_DELAY_RETRY; + this.TimeoutOnConnection = MQTT_CONNECT_TIMEOUT; + } + } +} diff --git a/M2Mqtt/Properties/AssemblyInfo.cs b/M2Mqtt/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f99a6c1 --- /dev/null +++ b/M2Mqtt/Properties/AssemblyInfo.cs @@ -0,0 +1,46 @@ +/* +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")] +#endif \ No newline at end of file diff --git a/M2Mqtt/Utility/QueueExtension.cs b/M2Mqtt/Utility/QueueExtension.cs new file mode 100644 index 0000000..988198f --- /dev/null +++ b/M2Mqtt/Utility/QueueExtension.cs @@ -0,0 +1,52 @@ +/* +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; +using System.Collections; + +namespace uPLibrary.Networking.M2Mqtt +{ + /// + /// Extension class for a Queue + /// + internal static class QueueExtension + { + /// + /// Predicate for searching inside a queue + /// + /// Item of the queue + /// Result of predicate + internal delegate bool QueuePredicate(object item); + + /// + /// Get (without removing) an item from queue based on predicate + /// + /// Queue in which to search + /// Predicate to verify to get item + /// Item matches the predicate + internal static object Get(Queue queue, QueuePredicate predicate) + { + foreach (var item in queue) + { + if (predicate(item)) + return item; + } + return null; + } + } +} diff --git a/M2Mqtt/bin/Release/M2Mqtt.dll b/M2Mqtt/bin/Release/M2Mqtt.dll new file mode 100644 index 0000000..b8d32e3 Binary files /dev/null and b/M2Mqtt/bin/Release/M2Mqtt.dll differ diff --git a/M2Mqtt/uM2MqttNetMf42.csproj b/M2Mqtt/uM2MqttNetMf42.csproj new file mode 100644 index 0000000..62c26ae --- /dev/null +++ b/M2Mqtt/uM2MqttNetMf42.csproj @@ -0,0 +1,75 @@ + + + + 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 new file mode 100644 index 0000000..7acbd06 --- /dev/null +++ b/M2Mqtt/uM2MqttNetMf43.csproj @@ -0,0 +1,75 @@ + + + + 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