├── .gitignore ├── LICENSE ├── README.md ├── ctacke.MQTT.Unit.Test ├── App.config ├── ClientTest.cs ├── ConnectTest.cs ├── FixedHeaderTest.cs ├── Properties │ └── AssemblyInfo.cs ├── ctacke.MQTT.Unit.Test.csproj └── packages.config ├── ctacke.MQTT.sln ├── ctacke.MQTT ├── Constants │ ├── ConnectResult.cs │ ├── ConnectionState.cs │ ├── MessageType.cs │ └── QoS.cs ├── MQTTClient.cs ├── MQTTString.cs ├── Messages │ ├── Connect.cs │ ├── ConnectAck.cs │ ├── Disconnect.cs │ ├── FixedHeader.cs │ ├── Message.cs │ ├── PingRequest.cs │ ├── PingResponse.cs │ ├── Publish.cs │ ├── Subscribe.cs │ ├── Unsubscribe.cs │ └── VariableHeaders │ │ ├── ConnectHeaderData.cs │ │ ├── HeaderData.cs │ │ ├── MessageIDHeaderData.cs │ │ ├── PublishHeaderData.cs │ │ └── VariableHeader.cs ├── OpenNETCF.MQTT.Mono.csproj ├── OpenNETCF.MQTT.csproj ├── Subscription.cs ├── SubscriptionCollection.cs └── ctacke.MQTT.csproj └── opennetcf-mqtt.nuspec /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | packages/ 4 | .vs/ 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2018 Chris Tacke 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mqtt 2 | Chris Tacke's MQTT Library. Provides an simple .NET Standard 2.0 MQTT Client API. 3 | -------------------------------------------------------------------------------- /ctacke.MQTT.Unit.Test/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ctacke.MQTT.Unit.Test/ClientTest.cs: -------------------------------------------------------------------------------- 1 | using ctacke.MQTT; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System; 4 | using System.Threading; 5 | using System.Diagnostics; 6 | 7 | namespace ctacke.MQTT.Unit.Test 8 | { 9 | // Could not load file or assembly 'System.Net.Sockets, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' 10 | [TestClass()] 11 | public class ClientTest 12 | { 13 | public TestContext TestContext { get; set; } 14 | 15 | [TestMethod()] 16 | public void ClientReceiveTest() 17 | { 18 | var receivedMessage = false; 19 | 20 | var client = new MQTTClient("ec2-18-217-218-110.us-east-2.compute.amazonaws.com", 1883); 21 | client.MessageReceived += (topic, qos, payload) => 22 | { 23 | Debug.WriteLine("RX: " + topic); 24 | receivedMessage = true; 25 | }; 26 | 27 | var i = 0; 28 | client.Connect("solution-family", "solution-family", "s36158"); 29 | while (!client.IsConnected) 30 | { 31 | Thread.Sleep(1000); 32 | 33 | if (i++ > 10) Assert.Fail(); 34 | } 35 | 36 | Assert.IsTrue(client.IsConnected); 37 | client.Subscriptions.Add(new Subscription("solution-family/#")); 38 | 39 | i = 0; 40 | while (true) 41 | { 42 | if (receivedMessage) break; 43 | 44 | Thread.Sleep(1000); 45 | client.Publish("solution-family/Test", "Hello", QoS.FireAndForget, false); 46 | 47 | if (i++ > 10) break; 48 | } 49 | 50 | Assert.IsTrue(receivedMessage); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ctacke.MQTT.Unit.Test/ConnectTest.cs: -------------------------------------------------------------------------------- 1 | using ctacke.MQTT; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System; 4 | 5 | namespace ctacke.MQTT.Unit.Test 6 | { 7 | [TestClass()] 8 | public class ConnectTest 9 | { 10 | public TestContext TestContext { get; set; } 11 | 12 | [TestMethod()] 13 | public void ConnectConstructorTest() 14 | { 15 | var connect = new Connect("OpenNETCF", "cloudCT1!", "ctacke211"); 16 | 17 | var bytes = connect.Serialize(); 18 | } 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ctacke.MQTT.Unit.Test/FixedHeaderTest.cs: -------------------------------------------------------------------------------- 1 | using ctacke.MQTT; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System; 4 | 5 | namespace ctacke.MQTT.Unit.Test 6 | { 7 | [TestClass()] 8 | public class FixedHeaderTest 9 | { 10 | public TestContext TestContext { get; set; } 11 | 12 | [TestMethod()] 13 | public void DeserializationTest_1() 14 | { 15 | var bytes = new byte[] { 0x20, 0x02 }; 16 | var header = FixedHeader.Deserialize(bytes); 17 | 18 | Assert.AreEqual(MessageType.ConnectAck, header.MessageType); 19 | Assert.AreEqual(2, header.RemainingLength); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ctacke.MQTT.Unit.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ctacke.MQTT.Unit.Test")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("ctacke.MQTT.Unit.Test")] 13 | [assembly: AssemblyCopyright("Copyright © 2012")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("b423acd4-87b3-4bfb-8509-3d7c0c009a05")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /ctacke.MQTT.Unit.Test/ctacke.MQTT.Unit.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 7 | 8 | 2.0 9 | {E0B21AC7-AFA9-43CC-BBE6-18DB0F62D8E3} 10 | Library 11 | Properties 12 | ctacke.MQTT.Unit.Test 13 | ctacke.MQTT.Unit.Test 14 | v4.6.1 15 | 512 16 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 17 | SAK 18 | SAK 19 | SAK 20 | SAK 21 | 22 | 23 | 24 | true 25 | full 26 | false 27 | bin\Debug\ 28 | DEBUG;TRACE 29 | prompt 30 | 4 31 | false 32 | 33 | 34 | pdbonly 35 | true 36 | bin\Release\ 37 | TRACE 38 | prompt 39 | 4 40 | false 41 | 42 | 43 | 44 | 45 | ..\packages\opennetcf-extensions-standard.1.0.17160.0\lib\netstandard1.1\OpenNETCF.Extensions.dll 46 | 47 | 48 | 49 | 3.5 50 | 51 | 52 | 53 | 54 | ..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll 55 | 56 | 57 | 58 | 59 | 60 | 61 | False 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | {a2db4897-8167-49bc-adec-0020d1a3e0c3} 77 | ctacke.MQTT 78 | 79 | 80 | 81 | 88 | -------------------------------------------------------------------------------- /ctacke.MQTT.Unit.Test/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /ctacke.MQTT.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26730.8 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ctacke.MQTT", "ctacke.MQTT\ctacke.MQTT.csproj", "{A2DB4897-8167-49BC-ADEC-0020D1A3E0C3}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ctacke.MQTT.Unit.Test", "ctacke.MQTT.Unit.Test\ctacke.MQTT.Unit.Test.csproj", "{E0B21AC7-AFA9-43CC-BBE6-18DB0F62D8E3}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {A2DB4897-8167-49BC-ADEC-0020D1A3E0C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {A2DB4897-8167-49BC-ADEC-0020D1A3E0C3}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {A2DB4897-8167-49BC-ADEC-0020D1A3E0C3}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {A2DB4897-8167-49BC-ADEC-0020D1A3E0C3}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {E0B21AC7-AFA9-43CC-BBE6-18DB0F62D8E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {E0B21AC7-AFA9-43CC-BBE6-18DB0F62D8E3}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {E0B21AC7-AFA9-43CC-BBE6-18DB0F62D8E3}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {E0B21AC7-AFA9-43CC-BBE6-18DB0F62D8E3}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {173E43E1-C1C6-4BBF-9B15-F7087BB766D8} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /ctacke.MQTT/Constants/ConnectResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace ctacke.MQTT 7 | { 8 | public enum ConnectResult 9 | { 10 | Accepted = 0, 11 | BadProtocolVersion = 1, 12 | IdentifierRejected = 2, 13 | ServerUnavailable = 3, 14 | FailedAuthentication = 4, 15 | NotAuthorized = 5 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ctacke.MQTT/Constants/ConnectionState.cs: -------------------------------------------------------------------------------- 1 | #if MONO 2 | using Output = System.Console; 3 | #else 4 | using Output = System.Diagnostics.Debug; 5 | #endif 6 | 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Net.Sockets; 12 | using System.IO; 13 | using System.Security.Cryptography.X509Certificates; 14 | using System.Threading; 15 | using System.Diagnostics; 16 | 17 | #if !WindowsCE 18 | using System.Net.Security; 19 | #endif 20 | 21 | namespace ctacke.MQTT 22 | { 23 | public enum ConnectionState 24 | { 25 | Disconnected, 26 | Connecting, 27 | Connected 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ctacke.MQTT/Constants/MessageType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace ctacke.MQTT 7 | { 8 | public enum MessageType 9 | { 10 | Reserved_0 = 0, 11 | Connect = 1, 12 | ConnectAck = 2, 13 | Publish = 3, 14 | PublishAck = 4, 15 | PublishReceive = 5, 16 | PublishRelease = 6, 17 | PublishComplete = 7, 18 | Subscribe = 8, 19 | SubscribeAck = 9, 20 | Unsubscribe = 10, 21 | UnsubscribeAck = 11, 22 | PingRequest = 12, 23 | PingResponse = 13, 24 | Disconnect = 14, 25 | Reserved_15 = 15 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ctacke.MQTT/Constants/QoS.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace ctacke.MQTT 7 | { 8 | public enum QoS 9 | { 10 | FireAndForget = 0, 11 | AcknowledgeDelivery = 1, 12 | AssureDelivery = 2, 13 | Reserved = 3 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ctacke.MQTT/MQTTClient.cs: -------------------------------------------------------------------------------- 1 | #if MONO 2 | using Output = System.Console; 3 | #else 4 | using Output = System.Diagnostics.Debug; 5 | #endif 6 | 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Text; 10 | using System.Net.Sockets; 11 | using System.IO; 12 | using System.Security.Cryptography.X509Certificates; 13 | using System.Threading; 14 | using System.Diagnostics; 15 | 16 | #if !WindowsCE 17 | using System.Net.Security; 18 | using System.Threading.Tasks; 19 | using System.Runtime.CompilerServices; 20 | using OpenNETCF; 21 | #endif 22 | 23 | [assembly: InternalsVisibleTo("ctacke.MQTT.Unit.Test")] 24 | 25 | namespace ctacke.MQTT 26 | { 27 | public delegate void PublicationReceivedHandler(string topic, QoS qos, byte[] payload); 28 | 29 | public class MQTTClient : DisposableBase 30 | { 31 | public event PublicationReceivedHandler MessageReceived; 32 | public event EventHandler Connected; 33 | public event EventHandler Disconnected; 34 | 35 | public const int DefaultPort = 1883; 36 | 37 | private int DefaultTimeout = 120000; 38 | private int DefaultPingPeriod = 60000; 39 | private int DefaultReconnectPeriod = 5000; 40 | 41 | private TcpClient m_client; 42 | private Stream m_stream; 43 | private Timer m_pingTimer; 44 | private Timer m_reconnectTimer; 45 | private bool m_shouldReconnect; 46 | private ConnectionState m_state; 47 | private ushort m_currentMessageID; 48 | private object m_syncRoot = new object(); 49 | private string m_lastUserName; 50 | private string m_lastPassword; 51 | private string m_lastClientIdentifier; 52 | private bool m_reconnecting; 53 | private Task m_rxTask; 54 | private CancellationTokenSource m_rxCancelToken; 55 | 56 | private CircularBuffer m_messageQueue = new CircularBuffer(100); 57 | 58 | public bool UseSSL { get; private set; } 59 | public string SSLTargetHost { get; private set; } 60 | public string BrokerHostName { get; private set; } 61 | public int BrokerPort { get; private set; } 62 | public int ReconnectPeriod { get; set; } 63 | public bool TracingEnabled { get; set; } 64 | public SubscriptionCollection Subscriptions { get; private set; } 65 | 66 | public MQTTClient(string brokerHostName) 67 | : this(brokerHostName, DefaultPort) 68 | { 69 | } 70 | 71 | public MQTTClient(string brokerHostName, int brokerPort) 72 | : this(brokerHostName, brokerPort, false, null) 73 | { 74 | } 75 | 76 | public MQTTClient(string brokerHostName, int brokerPort, bool useSSL, string sslTargetHost) 77 | { 78 | TracingEnabled = false; 79 | 80 | UseSSL = useSSL; 81 | SSLTargetHost = sslTargetHost; 82 | ReconnectPeriod = DefaultReconnectPeriod; 83 | 84 | BrokerHostName = brokerHostName; 85 | BrokerPort = brokerPort; 86 | 87 | ConnectionState = ConnectionState.Disconnected; 88 | 89 | Subscriptions = new SubscriptionCollection(); 90 | Subscriptions.SubscriptionAdded += Subscriptions_SubscriptionAdded; 91 | Subscriptions.SubscriptionRemoved += Subscriptions_SubscriptionRemoved; 92 | } 93 | 94 | protected override void ReleaseManagedResources() 95 | { 96 | m_messageQueue.Clear(); 97 | Disconnect(); 98 | } 99 | 100 | private void Subscriptions_SubscriptionRemoved(object sender, GenericEventArgs e) 101 | { 102 | var unsub = new Unsubscribe(new string[] { e.Value }, GetNextMessageID()); 103 | Send(unsub); 104 | } 105 | 106 | private void Subscriptions_SubscriptionAdded(object sender, GenericEventArgs e) 107 | { 108 | var subs = new Subscribe( 109 | new Subscription[] 110 | { 111 | e.Value 112 | }, 113 | GetNextMessageID() 114 | ); 115 | 116 | Send(subs); 117 | } 118 | 119 | private ushort GetNextMessageID() 120 | { 121 | lock (m_syncRoot) 122 | { 123 | if (m_currentMessageID == ushort.MaxValue) 124 | { 125 | m_currentMessageID = 0; 126 | } 127 | 128 | m_currentMessageID++; 129 | 130 | return m_currentMessageID; 131 | } 132 | } 133 | 134 | public ConnectionState ConnectionState 135 | { 136 | get { return m_state; } 137 | private set 138 | { 139 | if (TracingEnabled) 140 | { 141 | Output.WriteLine("MQTT State: " + value.ToString()); 142 | } 143 | 144 | if (value == m_state) return; 145 | 146 | m_state = value; 147 | 148 | if (m_reconnectTimer == null) 149 | { 150 | m_reconnectTimer = new Timer(ReconnectProc, null, Timeout.Infinite, Timeout.Infinite); 151 | } 152 | else 153 | { 154 | switch (ConnectionState) 155 | { 156 | case MQTT.ConnectionState.Disconnected: 157 | m_reconnectTimer.Change(ReconnectPeriod, ReconnectPeriod); 158 | break; 159 | default: 160 | m_reconnectTimer.Change(Timeout.Infinite, Timeout.Infinite); 161 | break; 162 | } 163 | } 164 | } 165 | } 166 | 167 | public string ClientIdentifier 168 | { 169 | get { return m_lastClientIdentifier; } 170 | } 171 | 172 | public async Task ConnectAsync(string clientID, string userName = null, string password = null) 173 | { 174 | if (await ConnectAsync(BrokerHostName, BrokerPort)) 175 | { 176 | var connectMessage = new Connect(userName, password, clientID); 177 | await SendAsync(connectMessage); 178 | } 179 | } 180 | 181 | public void Connect(string clientID, string userName = null, string password = null) 182 | { 183 | AsyncHelper.RunSync(async () => await ConnectAsync(clientID, userName, password)); 184 | } 185 | 186 | private bool Connect(string hostName, int port) 187 | { 188 | return AsyncHelper.RunSync(async () => await ConnectAsync(hostName, port)); 189 | } 190 | 191 | private async Task ConnectAsync(string hostName, int port) 192 | { 193 | if (IsConnected) throw new Exception("Already connected"); 194 | 195 | ConnectionState = MQTT.ConnectionState.Connecting; 196 | 197 | m_shouldReconnect = true; 198 | 199 | // create the client and connect 200 | m_client = new TcpClient(); 201 | m_client.SendTimeout = DefaultTimeout; 202 | m_client.ReceiveTimeout = DefaultTimeout; 203 | 204 | try 205 | { 206 | await m_client.ConnectAsync(BrokerHostName, BrokerPort); 207 | } 208 | catch (Exception ex) 209 | { 210 | if (TracingEnabled) 211 | { 212 | Output.WriteLine("Exception attempting to connect to MQTT Broker: " + ex.Message); 213 | } 214 | ConnectionState = MQTT.ConnectionState.Disconnected; 215 | return false; 216 | } 217 | 218 | var stream = m_client.GetStream(); 219 | 220 | if (UseSSL) 221 | { 222 | throw new NotSupportedException(); 223 | /* 224 | var ssl = new SslStream(stream, false, new RemoteCertificateValidationCallback(CertValidationProc)); 225 | 226 | // TODO: allow local certificates 227 | ssl.AuthenticateAsClient(SSLTargetHost); 228 | 229 | stream = ssl; 230 | */ 231 | } 232 | 233 | m_stream = stream; 234 | 235 | m_rxCancelToken = new CancellationTokenSource(); 236 | 237 | if (m_rxTask != null) 238 | { 239 | m_rxCancelToken.Cancel(); 240 | try 241 | { 242 | m_rxTask.Wait(); 243 | } 244 | catch { /* NOP from our cancellation */ } 245 | m_rxCancelToken.Dispose(); 246 | } 247 | 248 | m_rxTask = Task.Run(() => RxThreadProc(), m_rxCancelToken.Token); 249 | 250 | return true; 251 | } 252 | 253 | public void Disconnect() 254 | { 255 | if (!IsConnected) return; 256 | 257 | m_shouldReconnect = false; 258 | IsConnected = false; 259 | Send(new Disconnect()); 260 | 261 | if (m_rxTask != null) 262 | { 263 | m_rxCancelToken.Cancel(); 264 | try 265 | { 266 | m_rxTask.Wait(); 267 | } 268 | catch { /* NOP from our cancellation */ } 269 | m_rxCancelToken.Dispose(); 270 | } 271 | 272 | m_client.Dispose(); 273 | } 274 | 275 | public bool IsConnected 276 | { 277 | get { return ConnectionState == MQTT.ConnectionState.Connected; } 278 | private set 279 | { 280 | if (value) 281 | { 282 | ConnectionState = MQTT.ConnectionState.Connected; 283 | 284 | Connected.Fire(this, EventArgs.Empty); 285 | 286 | // SEND ANY QUEUED ITEMS 287 | while (m_messageQueue.Count > 0) 288 | { 289 | Send(m_messageQueue.Dequeue()); 290 | if (!IsConnected) break; 291 | } 292 | } 293 | else 294 | { 295 | ConnectionState = MQTT.ConnectionState.Disconnected; 296 | 297 | Disconnected.Fire(this, EventArgs.Empty); 298 | } 299 | } 300 | } 301 | 302 | private int PingPeriod 303 | { 304 | get 305 | { 306 | if (DefaultTimeout < DefaultPingPeriod) 307 | { 308 | return DefaultTimeout - 5000; 309 | } 310 | else 311 | { 312 | return DefaultPingPeriod; 313 | } 314 | } 315 | } 316 | 317 | private void PingTimerProc(object state) 318 | { 319 | if (!IsConnected) return; 320 | 321 | var ping = new PingRequest(); 322 | Send(ping); 323 | } 324 | 325 | private async void RxThreadProc() 326 | { 327 | try 328 | { 329 | if (IsConnected) 330 | { 331 | #if !WindowsCE 332 | m_stream.ReadTimeout = DefaultTimeout; 333 | #endif 334 | } 335 | } 336 | catch (ObjectDisposedException) 337 | { 338 | IsConnected = false; 339 | } 340 | 341 | m_rxCancelToken.Token.ThrowIfCancellationRequested(); 342 | 343 | 344 | // start a ping timer on a period less than the above timeout (else we'll timeout and exit) 345 | m_pingTimer = new Timer(PingTimerProc, null, PingPeriod, PingPeriod); 346 | 347 | List header = new List(4); 348 | 349 | while((ConnectionState == ConnectionState.Connecting) || (ConnectionState == ConnectionState.Connected)) 350 | { 351 | if (m_rxCancelToken.Token.IsCancellationRequested) 352 | { 353 | m_rxCancelToken.Token.ThrowIfCancellationRequested(); 354 | } 355 | 356 | header.Clear(); 357 | 358 | // the first byte gives us the type 359 | try 360 | { 361 | var byte0 = m_stream.ReadByte(); 362 | if (byte0 == -1) 363 | { 364 | await Task.Delay(500); 365 | continue; 366 | } 367 | 368 | //var messageType = (MessageType)(byte0 >> 4); 369 | header.Add((byte)byte0); 370 | 371 | byte lengthByte; 372 | // now pull the "remaining length" 373 | do 374 | { 375 | lengthByte = (byte)m_stream.ReadByte(); 376 | header.Add(lengthByte); 377 | } while ((lengthByte & 0x80) != 0); 378 | 379 | var length = FixedHeader.DecodeRemainingLength(header, 1); 380 | 381 | byte[] buffer = null; 382 | int read = 0; 383 | 384 | if (length > 0) 385 | { 386 | // and pull the payload 387 | buffer = new byte[length]; 388 | do 389 | { 390 | read += m_stream.Read(buffer, read, length - read); 391 | } while (read < length); 392 | } 393 | 394 | // deserialize and dispatch 395 | DeserializeAndDispatchMessage(header.ToArray(), buffer); 396 | } 397 | catch (Exception ex) 398 | { 399 | // happens during hang up, maybe on error 400 | // needs testing 401 | if (TracingEnabled) 402 | { 403 | Output.WriteLine("!!! Exception in MQTTBroker.RxProc\r\n" + ex.Message); 404 | } 405 | //if (Debugger.IsAttached) Debugger.Break(); 406 | IsConnected = false; 407 | } 408 | } 409 | 410 | m_pingTimer.Dispose(); 411 | 412 | } 413 | 414 | private void ReconnectProc(object state) 415 | { 416 | DoReconnect(); 417 | } 418 | 419 | private void DoReconnect() 420 | { 421 | switch (ConnectionState) 422 | { 423 | case MQTT.ConnectionState.Connected: 424 | case MQTT.ConnectionState.Connecting: 425 | return; 426 | } 427 | 428 | if (m_shouldReconnect) 429 | { 430 | // make sure we don't ask more than once 431 | if (m_reconnecting) return; 432 | m_reconnecting = true; 433 | 434 | if (TracingEnabled) 435 | { 436 | Output.WriteLine("MQTTBrokerProxy: Queueing up an auto-reconnect..."); 437 | } 438 | try 439 | { 440 | if (TracingEnabled) 441 | { 442 | Output.WriteLine("MQTTBrokerProxy: Reconnecting..."); 443 | } 444 | 445 | Connect(BrokerHostName, BrokerPort); 446 | 447 | if (ConnectionState == MQTT.ConnectionState.Connecting) 448 | { 449 | Send(new Connect(m_lastUserName, m_lastPassword, m_lastClientIdentifier)); 450 | } 451 | 452 | m_reconnecting = false; 453 | } 454 | catch 455 | { 456 | m_reconnecting = false; 457 | } 458 | } 459 | } 460 | 461 | private Message DeserializeAndDispatchMessage(byte[] headerdata, byte[] payload) 462 | { 463 | var header = FixedHeader.Deserialize(headerdata); 464 | 465 | if (TracingEnabled) 466 | { 467 | Output.WriteLine("Received: " + header.MessageType.ToString()); 468 | } 469 | 470 | switch (header.MessageType) 471 | { 472 | case MessageType.ConnectAck: 473 | var connect = new ConnectAck(header, payload); 474 | IsConnected = true; 475 | return connect; 476 | case MessageType.PingResponse: 477 | return new PingResponse(header, payload); 478 | case MessageType.SubscribeAck: 479 | break; 480 | case MessageType.Publish: 481 | var pub = new Publish(header, payload); 482 | var handler = MessageReceived; 483 | if (handler != null) 484 | { 485 | handler(pub.Topic, pub.QoS, pub.Payload); 486 | } 487 | break; 488 | case MessageType.PublishAck: 489 | // TODO: handle this 490 | break; 491 | case MessageType.Connect: 492 | // server connecting to us 493 | throw new NotSupportedException(); 494 | default: 495 | throw new NotSupportedException(); 496 | } 497 | 498 | return null; 499 | } 500 | 501 | #if !WindowsCE 502 | public bool CertValidationProc(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) 503 | { 504 | // TODO: check for errors 505 | 506 | // allow all (even unauthenticated) servers 507 | return true; 508 | } 509 | #endif 510 | 511 | internal void Send(Message message) 512 | { 513 | AsyncHelper.RunSync(async () => await SendAsync(message)); 514 | } 515 | 516 | internal async Task SendAsync(Message message) 517 | { 518 | if ((!IsConnected) && !(message is Connect)) 519 | { 520 | // queue these up for delivery after (re)connect 521 | m_messageQueue.Enqueue(message); 522 | return; 523 | } 524 | 525 | if (message is Connect) 526 | { 527 | m_lastUserName = (message as Connect).UserName; 528 | m_lastPassword = (message as Connect).Password; 529 | m_lastClientIdentifier = (message as Connect).ClientIdentifier; 530 | } 531 | 532 | byte[] data = null; 533 | 534 | try 535 | { 536 | data = message.Serialize(); 537 | } 538 | catch(Exception ex) 539 | { 540 | if (TracingEnabled) 541 | { 542 | Debug.WriteLine("MQTTBrokerProxy.Send: Serialization exception " + ex.Message); 543 | } 544 | if (Debugger.IsAttached) Debugger.Break(); 545 | 546 | return; 547 | } 548 | 549 | try 550 | { 551 | await m_stream.WriteAsync(data, 0, data.Length); 552 | m_stream.Flush(); 553 | } 554 | catch(Exception ex) 555 | { 556 | if (TracingEnabled) 557 | { 558 | Debug.WriteLine("MQTTBrokerProxy.Send: stream write exception " + ex.Message); 559 | } 560 | IsConnected = false; 561 | //if (Debugger.IsAttached) Debugger.Break(); 562 | 563 | // TODO: should we reconnect on all exceptions, or just some? 564 | //DoReconnect(); 565 | } 566 | 567 | // TODO: queue this and look for response 568 | } 569 | 570 | public void Publish(string topic, string data, QoS qos, bool retain) 571 | { 572 | AsyncHelper.RunSync(async () => await PublishAsync(topic, data, qos, retain)); 573 | } 574 | 575 | public void Publish(string topic, byte[] data, QoS qos, bool retain) 576 | { 577 | AsyncHelper.RunSync(async () => await PublishAsync(topic, data, qos, retain)); 578 | } 579 | 580 | public async Task PublishAsync(string topic, string data, QoS qos, bool retain) 581 | { 582 | var bytes = Encoding.UTF8.GetBytes(data); 583 | await PublishAsync(topic, bytes, qos, retain); 584 | } 585 | 586 | public async Task PublishAsync(string topic, byte[] data, QoS qos, bool retain) 587 | { 588 | Publish publish; 589 | 590 | if (qos == QoS.FireAndForget) 591 | { 592 | publish = new Publish(topic, data); 593 | } 594 | else 595 | { 596 | var messageID = GetNextMessageID(); 597 | publish = new Publish(topic, data, messageID, qos, retain); 598 | } 599 | 600 | await SendAsync(publish); 601 | } 602 | } 603 | } 604 | -------------------------------------------------------------------------------- /ctacke.MQTT/MQTTString.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using System.Text; 5 | 6 | namespace ctacke.MQTT 7 | { 8 | public class MQTTString 9 | { 10 | public string Value { get; set; } 11 | 12 | public static implicit operator string(MQTTString s) 13 | { 14 | if (s == null) return null; 15 | 16 | return s.Value; 17 | } 18 | 19 | public static implicit operator MQTTString (string s) 20 | { 21 | return new MQTTString() { Value = s }; 22 | } 23 | 24 | public override string ToString() 25 | { 26 | return Value; 27 | } 28 | 29 | public byte[] Serialize() 30 | { 31 | var data = new List(Value.Length + 2); 32 | 33 | // to big-endian 34 | data.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)Value.Length))); 35 | data.AddRange(Encoding.UTF8.GetBytes(Value)); 36 | 37 | return data.ToArray(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ctacke.MQTT/Messages/Connect.cs: -------------------------------------------------------------------------------- 1 | using OpenNETCF; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace ctacke.MQTT 6 | { 7 | internal class Connect : Message 8 | { 9 | private VariableHeader m_header; 10 | 11 | public MQTTString UserName { get; private set; } 12 | public MQTTString Password { get; private set; } 13 | public MQTTString ClientIdentifier { get; private set; } 14 | 15 | // TODO: 16 | private MQTTString WillTopic { get; set; } 17 | private MQTTString WillMessage { get; set; } 18 | 19 | public Connect(string userName, string password, string clientIdentifier) 20 | : base(MessageType.Connect) 21 | { 22 | m_header = new VariableHeader(); 23 | VariableHeader = m_header; 24 | 25 | if(!userName.IsNullOrEmpty()) 26 | { 27 | UserName = userName; 28 | m_header.HeaderData.HasUserName = true; 29 | } 30 | 31 | if(!password.IsNullOrEmpty()) 32 | { 33 | Password = password; 34 | m_header.HeaderData.HasPassword = true; 35 | } 36 | 37 | Validate 38 | .Begin() 39 | .ParameterIsNotNull(clientIdentifier, "clientIdentifier") 40 | .IsGreaterThanOrEqualTo(clientIdentifier.Length, 1) 41 | .IsLessThanOrEqualTo(clientIdentifier.Length, 23) 42 | .Check(); 43 | 44 | ClientIdentifier = clientIdentifier; 45 | } 46 | 47 | public override byte[] Payload 48 | { 49 | get 50 | { 51 | var data = new List(); 52 | 53 | data.AddRange(ClientIdentifier.Serialize()); 54 | 55 | if (WillTopic != null) 56 | { 57 | data.AddRange(WillTopic.Serialize()); 58 | } 59 | if (WillMessage != null) 60 | { 61 | data.AddRange(WillMessage.Serialize()); 62 | } 63 | if (UserName != null) 64 | { 65 | data.AddRange(UserName.Serialize()); 66 | } 67 | if (Password != null) 68 | { 69 | data.AddRange(Password.Serialize()); 70 | } 71 | 72 | return data.ToArray(); 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /ctacke.MQTT/Messages/ConnectAck.cs: -------------------------------------------------------------------------------- 1 | namespace ctacke.MQTT 2 | { 3 | internal class ConnectAck : Message 4 | { 5 | private byte[] m_payload; 6 | 7 | public ConnectResult Result 8 | { 9 | get 10 | { 11 | return (ConnectResult)m_payload[1]; 12 | } 13 | } 14 | 15 | internal ConnectAck(FixedHeader header, byte[] payload) 16 | : base(header) 17 | { 18 | m_payload = payload; 19 | } 20 | 21 | public override byte[] Payload 22 | { 23 | get { return m_payload; } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ctacke.MQTT/Messages/Disconnect.cs: -------------------------------------------------------------------------------- 1 | namespace ctacke.MQTT 2 | { 3 | internal class Disconnect : Message 4 | { 5 | public Disconnect() 6 | : base(MessageType.Disconnect) 7 | { 8 | // NOTE: Disconnect has no variable header and no payload 9 | 10 | VariableHeader = null; 11 | } 12 | 13 | public override byte[] Payload 14 | { 15 | get { return null; } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ctacke.MQTT/Messages/FixedHeader.cs: -------------------------------------------------------------------------------- 1 | using OpenNETCF; 2 | using System.Collections.Generic; 3 | 4 | namespace ctacke.MQTT 5 | { 6 | internal class FixedHeader 7 | { 8 | public FixedHeader() 9 | { 10 | } 11 | 12 | public bool Retain { get; set; } 13 | public QoS QoS { get; set; } 14 | public bool DuplicateDelivery { get; set; } 15 | public MessageType MessageType { get; set; } 16 | public int RemainingLength { get; set; } 17 | 18 | private const byte RetainBit = (1 << 0); 19 | private const byte DuplicateDeliveryBit = (byte)(1 << 3); 20 | 21 | public byte[] Serialize() 22 | { 23 | var data = new List(5); 24 | 25 | data.Add(0); 26 | 27 | // ---- message type ---- 28 | data[0] |= (byte)(((byte)MessageType) << 4); 29 | 30 | // ---- duplicate ---- 31 | if (DuplicateDelivery) 32 | { 33 | data[0] |= DuplicateDeliveryBit; 34 | } 35 | 36 | // ---- qos ---- 37 | //var qos = (byte)(IPAddress.HostToNetworkOrder((int)QoS) << 1); 38 | var qos = (byte)(((int)QoS) << 1); 39 | data[0] |= qos; 40 | 41 | // ---- retain ---- 42 | if (Retain) 43 | { 44 | data[0] |= RetainBit; 45 | } 46 | 47 | // ---- remaining length ---- 48 | data.AddRange(EncodeRemainingLength(RemainingLength)); 49 | 50 | return data.ToArray(); 51 | } 52 | 53 | public static FixedHeader Deserialize(byte[] data) 54 | { 55 | Validate 56 | .Begin() 57 | .ParameterIsNotNull(data, "data") 58 | .IsGreaterThanOrEqualTo(data.Length, 2) 59 | .Check(); 60 | 61 | var header = new FixedHeader(); 62 | 63 | // ---- message type ---- 64 | header.MessageType = (MessageType)(data[0] >> 4); 65 | // ---- duplicate ---- 66 | header.DuplicateDelivery = (data[0] & DuplicateDeliveryBit) == DuplicateDeliveryBit; 67 | // ---- qos ---- 68 | var qos = (QoS)(( data[0] >> 1) & 3); 69 | header.QoS = qos; 70 | 71 | // ---- retain ---- 72 | header.Retain = (data[0] & RetainBit) == RetainBit; 73 | 74 | // ---- remaining length ---- 75 | header.RemainingLength = DecodeRemainingLength(data, 1); 76 | return header; 77 | } 78 | 79 | private byte[] EncodeRemainingLength(int length) 80 | { 81 | var data = new List(4); 82 | int digit; 83 | 84 | do 85 | { 86 | digit = length % 128; 87 | length = length / 128; 88 | 89 | if (length > 0) 90 | { 91 | digit = digit | 0x80; 92 | } 93 | data.Add((byte)digit); 94 | } while (length > 0); 95 | 96 | return data.ToArray(); 97 | } 98 | 99 | internal static int DecodeRemainingLength(IEnumerable data) 100 | { 101 | return DecodeRemainingLength(data, 0); 102 | } 103 | 104 | internal static int DecodeRemainingLength(IEnumerable data, int startOffset) 105 | { 106 | var value = 0; 107 | var factor = 1; 108 | var pos = 0; 109 | 110 | foreach (var b in data) 111 | { 112 | if (pos++ < startOffset) continue; 113 | 114 | value += (b & ~0x80) * factor; 115 | 116 | factor <<= 7; 117 | } 118 | 119 | return value; 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ctacke.MQTT/Messages/Message.cs: -------------------------------------------------------------------------------- 1 | using OpenNETCF; 2 | using System.Collections.Generic; 3 | 4 | namespace ctacke.MQTT 5 | { 6 | internal abstract class Message 7 | { 8 | internal FixedHeader FixedHeader { get; private set; } 9 | protected IVariableHeader VariableHeader { get; set; } 10 | 11 | internal Message(FixedHeader header) 12 | { 13 | Validate 14 | .Begin() 15 | .ParameterIsNotNull(header, "header") 16 | .Check(); 17 | 18 | FixedHeader = header; 19 | } 20 | 21 | public Message(MessageType type) 22 | { 23 | FixedHeader = new FixedHeader(); 24 | 25 | FixedHeader.MessageType = type; 26 | } 27 | 28 | public Message(MessageType type, QoS qos, bool retain, bool duplicateDelivery) 29 | { 30 | FixedHeader = new FixedHeader(); 31 | 32 | FixedHeader.MessageType = type; 33 | FixedHeader.QoS = qos; 34 | FixedHeader.Retain = retain; 35 | FixedHeader.DuplicateDelivery = duplicateDelivery; 36 | } 37 | 38 | public QoS QoS 39 | { 40 | get { return FixedHeader.QoS; } 41 | } 42 | 43 | public byte[] Serialize() 44 | { 45 | var data = new List(); 46 | 47 | var payload = this.Payload; 48 | var length = payload == null ? 0 : payload.Length; 49 | 50 | byte[] variableHeader = null; 51 | if (VariableHeader != null) 52 | { 53 | variableHeader = VariableHeader.Serialize(); 54 | length += variableHeader.Length; 55 | } 56 | 57 | FixedHeader.RemainingLength = length; 58 | 59 | data.AddRange(FixedHeader.Serialize()); 60 | if(variableHeader != null) 61 | { 62 | data.AddRange(variableHeader); 63 | } 64 | 65 | if (payload != null) 66 | { 67 | data.AddRange(payload); 68 | } 69 | 70 | return data.ToArray(); 71 | } 72 | 73 | public abstract byte[] Payload { get; } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /ctacke.MQTT/Messages/PingRequest.cs: -------------------------------------------------------------------------------- 1 | namespace ctacke.MQTT 2 | { 3 | internal class PingRequest : Message 4 | { 5 | public PingRequest() 6 | : base(MessageType.PingRequest) 7 | { 8 | // NOTE: PingRequest has no variable header and no payload 9 | 10 | VariableHeader = null; 11 | } 12 | 13 | public override byte[] Payload 14 | { 15 | get { return null; } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ctacke.MQTT/Messages/PingResponse.cs: -------------------------------------------------------------------------------- 1 | namespace ctacke.MQTT 2 | { 3 | internal class PingResponse : Message 4 | { 5 | internal PingResponse(FixedHeader header, byte[] payload) 6 | : base(header.MessageType) 7 | { 8 | // NOTE: PingResponse has no variable header and no payload 9 | 10 | VariableHeader = null; 11 | } 12 | 13 | public override byte[] Payload 14 | { 15 | get { return null; } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ctacke.MQTT/Messages/Publish.cs: -------------------------------------------------------------------------------- 1 | using OpenNETCF; 2 | using System; 3 | using System.Text; 4 | 5 | namespace ctacke.MQTT 6 | { 7 | internal class Publish : Message 8 | { 9 | private VariableHeader m_header; 10 | private byte[] m_data; 11 | 12 | internal Publish(FixedHeader header, byte[] payload) 13 | : base(header) 14 | { 15 | var payloadLength = payload.Length; 16 | m_header = new VariableHeader(); 17 | VariableHeader = m_header; 18 | 19 | // get topic length 20 | var topicLength = System.Net.IPAddress.NetworkToHostOrder(BitConverter.ToInt16(payload, 0)); 21 | payloadLength -= 2; 22 | 23 | // get topic name 24 | m_header.HeaderData.TopicName = Encoding.UTF8.GetString(payload, 2, topicLength); 25 | payloadLength -= topicLength; 26 | 27 | if (header.QoS > 0) 28 | { 29 | // pull message ID 30 | m_header.HeaderData.MessageID = (ushort)System.Net.IPAddress.NetworkToHostOrder(BitConverter.ToInt16(payload, payload.Length - payloadLength)); 31 | payloadLength -= 2; 32 | } 33 | 34 | m_data = new byte[payloadLength]; 35 | Buffer.BlockCopy(payload, payload.Length - payloadLength, m_data, 0, m_data.Length); 36 | } 37 | 38 | public Publish(string topicName, byte[] messageData) 39 | : this(topicName, messageData, 0, QoS.FireAndForget, false) 40 | { 41 | } 42 | 43 | public Publish(string topicName, byte[] messageData, ushort messageID, QoS qos, bool retain) 44 | : base(MessageType.Publish, qos, retain, false) 45 | { 46 | m_header = new VariableHeader(); 47 | VariableHeader = m_header; 48 | 49 | Validate 50 | .Begin() 51 | .IsNotNullOrEmpty(topicName) 52 | .ParameterIsNotNull(messageData, "messageData") 53 | .Check(); 54 | 55 | m_header.HeaderData.TopicName = topicName; 56 | if (messageID != 0) 57 | { 58 | m_header.HeaderData.MessageID = messageID; 59 | } 60 | 61 | m_data = messageData; 62 | } 63 | 64 | public string Topic 65 | { 66 | get 67 | { 68 | return m_header.HeaderData.TopicName; 69 | } 70 | } 71 | 72 | public override byte[] Payload 73 | { 74 | get { return m_data; } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /ctacke.MQTT/Messages/Subscribe.cs: -------------------------------------------------------------------------------- 1 | using OpenNETCF; 2 | using System.Collections.Generic; 3 | 4 | namespace ctacke.MQTT 5 | { 6 | internal class Subscribe : Message 7 | { 8 | private VariableHeader m_header; 9 | private Subscription[] m_subscriptions; 10 | 11 | public Subscribe(Subscription[] subscriptions, ushort messageID) 12 | : base(MessageType.Subscribe, QoS.AcknowledgeDelivery, false, false) 13 | { 14 | Validate 15 | .Begin() 16 | .ParameterIsNotNull(subscriptions, "subscriptions") 17 | .IsGreaterThanOrEqualTo(1, subscriptions.Length) 18 | .Check(); 19 | 20 | m_header = new VariableHeader(); 21 | m_header.HeaderData.MessageID = messageID; 22 | VariableHeader = m_header; 23 | 24 | m_subscriptions = subscriptions; 25 | } 26 | 27 | public override byte[] Payload 28 | { 29 | get 30 | { 31 | var data = new List(m_subscriptions.Length * 3); 32 | 33 | foreach (var s in m_subscriptions) 34 | { 35 | data.AddRange(s.Serialize()); 36 | } 37 | 38 | return data.ToArray(); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /ctacke.MQTT/Messages/Unsubscribe.cs: -------------------------------------------------------------------------------- 1 | using OpenNETCF; 2 | using System.Collections.Generic; 3 | 4 | namespace ctacke.MQTT 5 | { 6 | internal class Unsubscribe : Message 7 | { 8 | private VariableHeader m_header; 9 | private string[] m_topics; 10 | 11 | public Unsubscribe(string[] topics, ushort messageID) 12 | : base(MessageType.Unsubscribe, QoS.AcknowledgeDelivery, false, false) 13 | { 14 | Validate 15 | .Begin() 16 | .ParameterIsNotNull(topics, "topics") 17 | .IsGreaterThanOrEqualTo(1, topics.Length) 18 | .Check(); 19 | 20 | m_header = new VariableHeader(); 21 | m_header.HeaderData.MessageID = messageID; 22 | VariableHeader = m_header; 23 | 24 | m_topics = topics; 25 | } 26 | 27 | public override byte[] Payload 28 | { 29 | get 30 | { 31 | var data = new List(512); 32 | 33 | foreach (var s in m_topics) 34 | { 35 | data.AddRange(((MQTTString)s).Serialize()); 36 | } 37 | 38 | return data.ToArray(); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /ctacke.MQTT/Messages/VariableHeaders/ConnectHeaderData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | 5 | namespace ctacke.MQTT 6 | { 7 | public class ConnectHeaderData : HeaderData 8 | { 9 | public const short DefaultKeepAlive = 0x3c; 10 | 11 | public MQTTString ProtocolName { get; set; } 12 | public byte ProtocolVersion { get; set; } 13 | 14 | public bool CleanSession { get; set; } 15 | public bool Will { get; set; } 16 | public QoS WillQoS { get; set; } 17 | public bool WillRetain { get; set; } 18 | public bool HasUserName { get; set; } 19 | public bool HasPassword { get; set; } 20 | public short KeepAliveSeconds { get; set; } 21 | 22 | public ConnectHeaderData() 23 | { 24 | ProtocolName = "MQIsdp"; 25 | ProtocolVersion = 0x03; 26 | 27 | CleanSession = true; 28 | KeepAliveSeconds = DefaultKeepAlive; 29 | } 30 | 31 | public override byte[] Serialize() 32 | { 33 | var data = new List(); 34 | 35 | data.AddRange(ProtocolName.Serialize()); 36 | data.Add(ProtocolVersion); 37 | 38 | byte flags = 0; 39 | 40 | if (CleanSession) 41 | { 42 | flags |= (byte)(1 << 1); 43 | } 44 | 45 | if (Will) 46 | { 47 | flags |= (byte)(1 << 2); // will 48 | flags |= (byte)(((int)WillQoS) << 3); // will qos 49 | if (WillRetain) 50 | { 51 | flags |= (byte)(1 << 5); // will retain 52 | } 53 | } 54 | 55 | if(HasPassword) 56 | { 57 | flags |= (byte)(1 << 6); 58 | } 59 | 60 | if (HasUserName) 61 | { 62 | flags |= (byte)(1 << 7); 63 | } 64 | 65 | data.Add(flags); 66 | 67 | data.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(KeepAliveSeconds))); 68 | 69 | return data.ToArray(); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /ctacke.MQTT/Messages/VariableHeaders/HeaderData.cs: -------------------------------------------------------------------------------- 1 | namespace ctacke.MQTT 2 | { 3 | public abstract class HeaderData 4 | { 5 | public abstract byte[] Serialize(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ctacke.MQTT/Messages/VariableHeaders/MessageIDHeaderData.cs: -------------------------------------------------------------------------------- 1 | using OpenNETCF; 2 | using System; 3 | using System.Net; 4 | 5 | namespace ctacke.MQTT 6 | { 7 | public class MessageIDHeaderData : HeaderData 8 | { 9 | private ushort m_id; 10 | 11 | public ushort MessageID 12 | { 13 | get { return m_id; } 14 | set 15 | { 16 | Validate 17 | .Begin() 18 | .AreNotEqual(value, 0) 19 | .Check(); 20 | 21 | m_id = value; 22 | } 23 | } 24 | 25 | public override byte[] Serialize() 26 | { 27 | return BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)MessageID)); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ctacke.MQTT/Messages/VariableHeaders/PublishHeaderData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | 5 | namespace ctacke.MQTT 6 | { 7 | public class PublishHeaderData : HeaderData 8 | { 9 | private MQTTString m_topic; 10 | 11 | public MQTTString TopicName 12 | { 13 | get { return m_topic; } 14 | set 15 | { 16 | m_topic = Uri.EscapeUriString(value); 17 | } 18 | } 19 | public ushort? MessageID { get; set; } 20 | 21 | public override byte[] Serialize() 22 | { 23 | var data = new List(TopicName.Value.Length); 24 | data.AddRange(TopicName.Serialize()); 25 | 26 | if (MessageID.HasValue) 27 | { 28 | data.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(MessageID.Value))); 29 | } 30 | 31 | return data.ToArray(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ctacke.MQTT/Messages/VariableHeaders/VariableHeader.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace ctacke.MQTT 4 | { 5 | public interface IVariableHeader 6 | { 7 | byte[] Serialize(); 8 | } 9 | 10 | public class VariableHeader : IVariableHeader 11 | where T : HeaderData, new() 12 | { 13 | public T HeaderData { get; set; } 14 | 15 | 16 | public VariableHeader() 17 | { 18 | HeaderData = new T(); 19 | } 20 | 21 | public byte[] Serialize() 22 | { 23 | var data = new List(); 24 | 25 | data.AddRange(HeaderData.Serialize()); 26 | 27 | return data.ToArray(); ; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ctacke.MQTT/OpenNETCF.MQTT.Mono.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {2209799F-5B06-449F-86FF-0807DECF9289} 8 | Library 9 | Properties 10 | OpenNETCF.MQTT 11 | OpenNETCF.MQTT 12 | v4.5 13 | 512 14 | SAK 15 | SAK 16 | SAK 17 | SAK 18 | 19 | 20 | true 21 | full 22 | false 23 | bin\Debug\Mono\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | true 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\Mono\ 33 | TRACE 34 | prompt 35 | 4 36 | true 37 | 38 | 39 | 40 | ..\packages\com.opennetcf.extensions.pcl.1.0.17139.0\lib\portable45-net45+win8+wpa81\OpenNETCF.Extensions.dll 41 | True 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 87 | -------------------------------------------------------------------------------- /ctacke.MQTT/OpenNETCF.MQTT.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /ctacke.MQTT/Subscription.cs: -------------------------------------------------------------------------------- 1 | using OpenNETCF; 2 | using System.Collections.Generic; 3 | 4 | namespace ctacke.MQTT 5 | { 6 | public class Subscription 7 | { 8 | public string TopicName { get; private set; } 9 | public QoS QoS { get; private set; } 10 | 11 | public Subscription(string topic) 12 | : this(topic, QoS.FireAndForget) 13 | { 14 | } 15 | 16 | internal Subscription() 17 | { 18 | } 19 | 20 | public Subscription(string topic, QoS qos) 21 | { 22 | Validate 23 | .Begin() 24 | .IsNotNullOrEmpty(topic) 25 | .Check(); 26 | 27 | TopicName = topic; 28 | QoS = qos; 29 | } 30 | 31 | internal byte[] Serialize() 32 | { 33 | //var name = TopicName.Replace("+", "%2B").Replace("#", "%23"); 34 | var name = TopicName; 35 | var data = new List(name.Length + 3); 36 | 37 | data.AddRange(((MQTTString)name).Serialize()); 38 | data.Add((byte)QoS); 39 | 40 | return data.ToArray(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ctacke.MQTT/SubscriptionCollection.cs: -------------------------------------------------------------------------------- 1 | using OpenNETCF; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace ctacke.MQTT 7 | { 8 | public class SubscriptionCollection : IEnumerable 9 | { 10 | private Dictionary m_subscriptions = new Dictionary(); 11 | 12 | public event EventHandler> SubscriptionAdded; 13 | public event EventHandler> SubscriptionRemoved; 14 | 15 | public int Count 16 | { 17 | get { return m_subscriptions.Count; } 18 | } 19 | 20 | public Subscription this[int index] 21 | { 22 | get { return m_subscriptions.Values.ToList()[index]; } 23 | } 24 | 25 | public Subscription this[string topic] 26 | { 27 | get { return m_subscriptions[topic]; } 28 | } 29 | 30 | public Subscription Add(string topic, QoS qos) 31 | { 32 | var s = new Subscription(topic, qos); 33 | Add(s); 34 | return s; 35 | } 36 | 37 | public void Add(Subscription subscription) 38 | { 39 | // TODO: validate uniqueness of topic? 40 | if (!m_subscriptions.ContainsKey(subscription.TopicName)) 41 | { 42 | m_subscriptions.Add(subscription.TopicName, subscription); 43 | } 44 | if(SubscriptionAdded != null) SubscriptionAdded(this, new GenericEventArgs(subscription)); 45 | } 46 | 47 | public void Remove(Subscription subscription) 48 | { 49 | Remove(subscription.TopicName); 50 | } 51 | 52 | public void Remove(string topic) 53 | { 54 | m_subscriptions.Remove(topic); 55 | if(SubscriptionRemoved != null) SubscriptionRemoved(this, new GenericEventArgs(topic)); 56 | } 57 | 58 | public IEnumerator GetEnumerator() 59 | { 60 | return m_subscriptions.Values.GetEnumerator(); 61 | } 62 | 63 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 64 | { 65 | return GetEnumerator(); 66 | } 67 | } 68 | 69 | //public class Subscription 70 | //{ 71 | // public Subscription(string topic) 72 | // : this(topic, QoS.FireAndForget) 73 | // { 74 | // } 75 | 76 | // public Subscription(string topic, QoS qos) 77 | // { 78 | // Validate 79 | // .Begin() 80 | // .IsNotNullOrEmpty(topic) 81 | // .Check(); 82 | 83 | // Topic = topic; 84 | // QoS = qos; 85 | // } 86 | 87 | // public readonly string Topic { get; set; } 88 | // public readonly QoS QoS { get; set; } 89 | //} 90 | } 91 | -------------------------------------------------------------------------------- /ctacke.MQTT/ctacke.MQTT.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | netstandard2.0 6 | portable-net45+win8+wpa81+wp8 7 | full 8 | ctacke.MQTT 9 | 1.0.17253 10 | true 11 | A lightweight, .NET Standard MQTT Client Library 12 | ©2015-2017 Chris Tacke 13 | 14 | https://github.com/ctacke/mqtt 15 | git 16 | MQTT 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /opennetcf-mqtt.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | opennetcf-mqtt 5 | 1.0.0.0 6 | OpenNETCF MQTT Library 7 | Chris Tacke 8 | false 9 | OpenNETCF's implementation of the MQ Telemetry Transport (MQTT) protocol in a friendly .NET library form. Huzzah! 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | --------------------------------------------------------------------------------