├── .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 |
--------------------------------------------------------------------------------