├── TMQTTTest.res ├── TMQTTTest_Icon.ico ├── Tests ├── TMQTTLibTests.res ├── TMQTTLibTests_Icon.ico ├── TMQTTLibTests.dpr ├── dunit.ini ├── TestMQTTReadThread.pas ├── TestMQTT.pas ├── TMQTTLibTests.dproj └── TestMQTTHeaders.pas ├── TMQTTTest.dpr ├── .gitattributes ├── LICENSE.md ├── TMQTT_v2.groupproj ├── .gitignore ├── README.md ├── uMain.dfm ├── uMain.pas ├── TMQTTTest.dproj └── TMQTT ├── MQTTReadThread.pas ├── MQTT.pas └── MQTTHeaders.pas /TMQTTTest.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamiei/Delphi-TMQTT2/HEAD/TMQTTTest.res -------------------------------------------------------------------------------- /TMQTTTest_Icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamiei/Delphi-TMQTT2/HEAD/TMQTTTest_Icon.ico -------------------------------------------------------------------------------- /Tests/TMQTTLibTests.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamiei/Delphi-TMQTT2/HEAD/Tests/TMQTTLibTests.res -------------------------------------------------------------------------------- /Tests/TMQTTLibTests_Icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamiei/Delphi-TMQTT2/HEAD/Tests/TMQTTLibTests_Icon.ico -------------------------------------------------------------------------------- /TMQTTTest.dpr: -------------------------------------------------------------------------------- 1 | program TMQTTTest; 2 | 3 | uses 4 | Forms, 5 | uMain in 'uMain.pas' {fMain}, 6 | MQTT in 'TMQTT\MQTT.pas', 7 | MQTTHeaders in 'TMQTT\MQTTHeaders.pas', 8 | MQTTReadThread in 'TMQTT\MQTTReadThread.pas'; 9 | 10 | {$R *.res} 11 | 12 | begin 13 | Application.Initialize; 14 | Application.MainFormOnTaskbar := True; 15 | Application.CreateForm(TfMain, fMain); 16 | Application.Run; 17 | end. 18 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /Tests/TMQTTLibTests.dpr: -------------------------------------------------------------------------------- 1 | program TMQTTLibTests; 2 | { 3 | 4 | Delphi DUnit Test Project 5 | ------------------------- 6 | This project contains the DUnit test framework and the GUI/Console test runners. 7 | Add "CONSOLE_TESTRUNNER" to the conditional defines entry in the project options 8 | to use the console test runner. Otherwise the GUI test runner will be used by 9 | default. 10 | 11 | } 12 | 13 | {$IFDEF CONSOLE_TESTRUNNER} 14 | {$APPTYPE CONSOLE} 15 | {$ENDIF} 16 | 17 | uses 18 | Forms, 19 | TestFramework, 20 | GUITestRunner, 21 | TextTestRunner, 22 | TestMQTTHeaders in 'TestMQTTHeaders.pas', 23 | MQTTHeaders in '..\TMQTT\MQTTHeaders.pas', 24 | TestMQTTReadThread in 'TestMQTTReadThread.pas', 25 | MQTTReadThread in '..\TMQTT\MQTTReadThread.pas', 26 | MQTT in '..\TMQTT\MQTT.pas', 27 | TestMQTT in 'TestMQTT.pas'; 28 | 29 | {$R *.RES} 30 | 31 | begin 32 | Application.Initialize; 33 | if IsConsole then 34 | TextTestRunner.RunRegisteredTests 35 | else 36 | GUITestRunner.RunRegisteredTests; 37 | end. 38 | 39 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Jamie Ingilby 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Tests/dunit.ini: -------------------------------------------------------------------------------- 1 | [GUITestRunner Config] 2 | AutoSave=1 3 | Left=709 4 | Top=29 5 | Width=500 6 | Height=718 7 | Maximized=0 8 | UseRegistry=0 9 | ResultsPanel.Height=174 10 | ErrorMessage.Height=68 11 | ErrorMessage.Visible=1 12 | FailureList.ColumnWidth[0]=120 13 | FailureList.ColumnWidth[1]=100 14 | FailureList.ColumnWidth[2]=200 15 | FailureList.ColumnWidth[3]=52 16 | HideTestNodesOnOpen=0 17 | BreakOnFailures=0 18 | FailOnNoChecksExecuted=0 19 | FailOnMemoryLeaked=0 20 | IgnoreSetUpTearDownLeaks=0 21 | ReportMemoryLeakTypes=0 22 | SelectTestedNode=1 23 | WarnOnFailTestOverride=0 24 | PopupX=350 25 | PopupY=30 26 | 27 | [Tests.TMQTTLibTests.exe] 28 | TestTMQTTVariableHeader=0 29 | 30 | [Tests.TMQTTLibTests.exe.TestTMQTTVariableHeader] 31 | TestToBytes=0 32 | 33 | [Tests.TMQTTLibTests.exe.TestTMQTTConnectVarHeader] 34 | 35 | [Tests.TMQTTLibTests.exe.TestTMQTTPublishVarHeader] 36 | 37 | [Tests.TMQTTLibTests.exe.TestTMQTTSubscribeVarHeader] 38 | 39 | [Tests.TMQTTLibTests.exe.TestTMQTTUnsubscribeVarHeader] 40 | 41 | [Tests.TMQTTLibTests.exe.TestTMQTTPayload] 42 | 43 | [Tests.TMQTTLibTests.exe.TestTMQTTMessage] 44 | 45 | [Tests.TMQTTLibTests.exe.TestTMQTTFixedHeader] 46 | 47 | [Tests.TMQTTLibTests.exe.TestTMQTT] 48 | 49 | [Tests.TMQTTLibTests.exe.TestUtilityMethods] 50 | 51 | [Tests.TMQTTLibTests.exe.TestTMQTTRecvUtilities] 52 | 53 | -------------------------------------------------------------------------------- /TMQTT_v2.groupproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | {8533DFFC-E042-4B77-A340-6EAB9A8BC91A} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Default.Personality.12 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Uncomment these types if you want even more clean repository. But be careful. 2 | # It can make harm to an existing project source. Read explanations below. 3 | # 4 | # Resource files are binaries containing manifest, project icon and version info. 5 | # They can not be viewed as text or compared by diff-tools. Consider replacing them with .rc files. 6 | #*.res 7 | # 8 | # Type library file (binary). In old Delphi versions it should be stored. 9 | # Since Delphi 2009 it is produced from .ridl file and can safely be ignored. 10 | #*.tlb 11 | # 12 | # Diagram Portfolio file. Used by the diagram editor up to Delphi 7. 13 | # Uncomment this if you are not using diagrams or use newer Delphi version. 14 | #*.ddp 15 | # 16 | # Visual LiveBindings file. Added in Delphi XE2. 17 | # Uncomment this if you are not using LiveBindings Designer. 18 | #*.vlb 19 | # 20 | # Deployment Manager configuration file for your project. Added in Delphi XE2. 21 | # Uncomment this if it is not mobile development and you do not use remote debug feature. 22 | #*.deployproj 23 | # 24 | 25 | # Delphi compiler-generated binaries (safe to delete) 26 | *.exe 27 | *.dll 28 | *.bpl 29 | *.bpi 30 | *.dcp 31 | *.so 32 | *.apk 33 | *.drc 34 | *.map 35 | *.dres 36 | *.rsm 37 | *.tds 38 | *.dcu 39 | *.lib 40 | 41 | # Delphi autogenerated files (duplicated info) 42 | *.cfg 43 | *Resource.rc 44 | 45 | # Delphi local files (user-specific info) 46 | *.local 47 | *.identcache 48 | *.projdata 49 | *.tvsconfig 50 | *.dsk 51 | 52 | # Delphi history and backups 53 | __history/ 54 | *.~* 55 | 56 | # ========================= 57 | # Operating System Files 58 | # ========================= 59 | 60 | # OSX 61 | # ========================= 62 | 63 | .DS_Store 64 | .AppleDouble 65 | .LSOverride 66 | 67 | # Thumbnails 68 | ._* 69 | 70 | # Files that might appear on external disk 71 | .Spotlight-V100 72 | .Trashes 73 | 74 | # Directories potentially created on remote AFP share 75 | .AppleDB 76 | .AppleDesktop 77 | Network Trash Folder 78 | Temporary Items 79 | .apdisk 80 | 81 | # Windows 82 | # ========================= 83 | 84 | # Windows image file caches 85 | Thumbs.db 86 | ehthumbs.db 87 | 88 | # Folder config file 89 | Desktop.ini 90 | 91 | # Recycle Bin used on file shares 92 | $RECYCLE.BIN/ 93 | 94 | # Windows Installer files 95 | *.cab 96 | *.msi 97 | *.msm 98 | *.msp 99 | 100 | # Windows shortcuts 101 | *.lnk 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TMQTT 2 (ALPHA) for Delphi by Jamie I 2 | 3 | ## Introduction 4 | 5 | **WARNING: This is still considered ALPHA quality, and is NOT considered ready for *any real* use yet. All contributions and bug fix pull requests are appreciated.** 6 | 7 | 8 | TMQTT is a non-visual Delphi Client Library for the IBM Websphere MQ Transport Telemetry protocol ( http://mqtt.org ). It allows you to connect to a Message Broker that uses MQTT such as the [Really Small Message Broker](http://alphaworks.ibm.com/tech/rsmb) which is freely available for evaluation purposes on IBM Alphaworks. Mosquitto is an open source MQTT 3.1 broker ( http://mosquitto.org/ ). 9 | 10 | TMQTT is a complete re-write of the original TMQTTClient that I wrote and it is sufficiently different enough to release in parallel. 11 | 12 | MQTT is an IoT protocol, further information can be found here: http://mqtt.org/ 13 | 14 | 15 | ## Points of Note 16 | * There may be a few bugs. 17 | * It is not currently FPC compatible [Planned] 18 | * There are some improvements related to socket error handling which I have yet to make inspired by ZiCog's contributions to the original TMQTTClient. 19 | Note: You should be aware that it uses part of the Synapse Internet Communications Library for its Socket support so you’ll need to ensure that this is available on your search path. 20 | 21 | 22 | ## Usage 23 | There is a sample VCL project included in the download but usage is relatively simple. This is a non-visual component so all you need to do is to put the TMQTT directory into your compiler paths and then put MQTT in your uses. 24 | 25 | ```delphi 26 | uses MQTT; 27 | var 28 | MQTTClient: TMQTT; 29 | begin 30 | MQTT := TMQTT.Create('localhost', 1883); 31 | try 32 | // Events 33 | MQTT.OnConnAck := GotConnAck; 34 | MQTT.OnPublish := GotPub; 35 | MQTT.OnPingResp := GotPingResp; 36 | MQTT.OnSubAck := GotSubAck; 37 | MQTT.OnUnSubAck := GotUnSubAck; 38 | MQTT.OnPubAck := GotPubAck; 39 | 40 | if MQTT.Connect then 41 | begin 42 | WriteLn('Connected!'); 43 | MQTT.Subscribe('/dev/test', 0); 44 | MQTT.Publish('/dev/test', 'This is a test message'); 45 | end 46 | else 47 | WriteLn('Failed to connect'); 48 | finally 49 | MQTT.Free; 50 | end; 51 | end; 52 | 53 | procedure GotPub(Sender: TObject; topic, payload: Ansistring); 54 | begin 55 | WriteLn('Message Recieved on ' + topic + ' payload: ' + payload); 56 | end; 57 | ``` 58 | 59 | Special thanks for this re-write got to [ZiCog](https://github.com/ZiCog) for his help and improvements to the TMQTTClient library, which helped enormously. 60 | 61 | If you are using my TMQTT then I would to love hear about how you’re using it, if you do appreciate it, please let me know! 62 | -------------------------------------------------------------------------------- /Tests/TestMQTTReadThread.pas: -------------------------------------------------------------------------------- 1 | unit TestMQTTReadThread; 2 | { 3 | 4 | Delphi DUnit Test Case 5 | ---------------------- 6 | This unit contains a skeleton test case class generated by the Test Case Wizard. 7 | Modify the generated code to correctly setup and call the methods from the unit 8 | being tested. 9 | 10 | } 11 | 12 | interface 13 | 14 | uses 15 | TestFramework, Windows, MQTTReadThread, Generics.Collections, SmartInspect, Classes, 16 | SysUtils, MQTTHeaders, blcksock, SyncObjs, SiAuto; 17 | 18 | type 19 | // Test methods for class TMQTTRecvUtilities 20 | 21 | TestTMQTTRecvUtilities = class(TTestCase) 22 | strict private 23 | FMQTTRecvUtilities: TMQTTRecvUtilities; 24 | public 25 | procedure SetUp; override; 26 | procedure TearDown; override; 27 | published 28 | procedure TestMSBLSBToInt; 29 | procedure TestRLBytesToInt; 30 | end; 31 | // Test methods for class TUnparsedMsg 32 | 33 | TestTUnparsedMsg = class(TTestCase) 34 | strict private 35 | FUnparsedMsg: TUnparsedMsg; 36 | public 37 | procedure SetUp; override; 38 | procedure TearDown; override; 39 | end; 40 | // Test methods for class TMQTTReadThread 41 | 42 | TestTMQTTReadThread = class(TTestCase) 43 | strict private 44 | FMQTTReadThread: TMQTTReadThread; 45 | public 46 | procedure SetUp; override; 47 | procedure TearDown; override; 48 | end; 49 | 50 | implementation 51 | 52 | procedure TestTMQTTRecvUtilities.SetUp; 53 | begin 54 | FMQTTRecvUtilities := TMQTTRecvUtilities.Create; 55 | end; 56 | 57 | procedure TestTMQTTRecvUtilities.TearDown; 58 | begin 59 | FMQTTRecvUtilities.Free; 60 | FMQTTRecvUtilities := nil; 61 | end; 62 | 63 | procedure TestTMQTTRecvUtilities.TestMSBLSBToInt; 64 | var 65 | ReturnValue: Integer; 66 | ALengthBytes: TBytes; 67 | begin 68 | ALengthBytes := TMQTTUtilities.IntToMSBLSB(6); 69 | ReturnValue := FMQTTRecvUtilities.MSBLSBToInt(ALengthBytes); 70 | Assert(ReturnValue = 6, 'MSBLSBToInt on 6'); 71 | ALengthBytes := TMQTTUtilities.IntToMSBLSB(0); 72 | ReturnValue := FMQTTRecvUtilities.MSBLSBToInt(ALengthBytes); 73 | Assert(ReturnValue = 0, 'MSBLSBToInt on 0'); 74 | ALengthBytes := TMQTTUtilities.IntToMSBLSB(101); 75 | ReturnValue := FMQTTRecvUtilities.MSBLSBToInt(ALengthBytes); 76 | Assert(ReturnValue = 101, 'MSBLSBToInt on 101'); 77 | end; 78 | 79 | procedure TestTMQTTRecvUtilities.TestRLBytesToInt; 80 | var 81 | ReturnValue: Integer; 82 | ARlBytes: TBytes; 83 | begin 84 | ARlBytes := TMQTTUtilities.RLIntToBytes(0); 85 | ReturnValue := FMQTTRecvUtilities.RLBytesToInt(ARlBytes); 86 | Assert(ReturnValue = 0, 'RLBytes on 0'); 87 | ARlBytes := TMQTTUtilities.RLIntToBytes(6); 88 | ReturnValue := FMQTTRecvUtilities.RLBytesToInt(ARlBytes); 89 | Assert(ReturnValue = 6, 'RLBytes on 6'); 90 | ARlBytes := TMQTTUtilities.RLIntToBytes(40); 91 | ReturnValue := FMQTTRecvUtilities.RLBytesToInt(ARlBytes); 92 | Assert(ReturnValue = 40, 'RLBytes on 40'); 93 | ARlBytes := TMQTTUtilities.RLIntToBytes(112); 94 | ReturnValue := FMQTTRecvUtilities.RLBytesToInt(ARlBytes); 95 | Assert(ReturnValue = 112, 'RLBytes on 112'); 96 | ARlBytes := TMQTTUtilities.RLIntToBytes(512); 97 | ReturnValue := FMQTTRecvUtilities.RLBytesToInt(ARlBytes); 98 | Assert(ReturnValue = 512, 'RLBytes on 512'); 99 | end; 100 | 101 | procedure TestTUnparsedMsg.SetUp; 102 | begin 103 | //FUnparsedMsg := TUnparsedMsg.Create; 104 | //Assert(FUnparsedMsg <> nil, 'Create failed'); 105 | end; 106 | 107 | procedure TestTUnparsedMsg.TearDown; 108 | begin 109 | //FUnparsedMsg.Free; 110 | //FUnparsedMsg := nil; 111 | end; 112 | 113 | procedure TestTMQTTReadThread.SetUp; 114 | begin 115 | //FMQTTReadThread := TMQTTReadThread.Create; 116 | Assert(FMQTTReadThread <> nil, 'Thread Creation Failed'); 117 | end; 118 | 119 | procedure TestTMQTTReadThread.TearDown; 120 | begin 121 | FMQTTReadThread.Free; 122 | FMQTTReadThread := nil; 123 | end; 124 | 125 | initialization 126 | // Register any test cases with the test runner 127 | RegisterTest(TestTMQTTRecvUtilities.Suite); 128 | RegisterTest(TestTUnparsedMsg.Suite); 129 | RegisterTest(TestTMQTTReadThread.Suite); 130 | end. 131 | 132 | -------------------------------------------------------------------------------- /uMain.dfm: -------------------------------------------------------------------------------- 1 | object fMain: TfMain 2 | Left = 0 3 | Top = 0 4 | Caption = 'TMQTT Version 2 Test' 5 | ClientHeight = 413 6 | ClientWidth = 421 7 | Color = clBtnFace 8 | Font.Charset = DEFAULT_CHARSET 9 | Font.Color = clWindowText 10 | Font.Height = -11 11 | Font.Name = 'Tahoma' 12 | Font.Style = [] 13 | OldCreateOrder = False 14 | PixelsPerInch = 96 15 | TextHeight = 13 16 | object lblHeader: TLabel 17 | Left = 8 18 | Top = 0 19 | Width = 410 20 | Height = 34 21 | Alignment = taCenter 22 | AutoSize = False 23 | Caption = 'Sample Client' 24 | Font.Charset = DEFAULT_CHARSET 25 | Font.Color = clNavy 26 | Font.Height = -21 27 | Font.Name = 'Tahoma' 28 | Font.Style = [fsBold, fsUnderline] 29 | ParentFont = False 30 | Layout = tlCenter 31 | end 32 | object lnlMQTTInfo: TLabel 33 | Left = 8 34 | Top = 337 35 | Width = 194 36 | Height = 13 37 | Caption = 'For more information about MQTT goto: ' 38 | end 39 | object lblMQTTUrl: TLabel 40 | Left = 208 41 | Top = 337 42 | Width = 102 43 | Height = 13 44 | Cursor = crHandPoint 45 | Caption = 'http://www.mqtt.org' 46 | Font.Charset = DEFAULT_CHARSET 47 | Font.Color = clBlue 48 | Font.Height = -11 49 | Font.Name = 'Tahoma' 50 | Font.Style = [fsUnderline] 51 | ParentFont = False 52 | end 53 | object lblPrimarilyTested: TLabel 54 | Left = 8 55 | Top = 356 56 | Width = 154 57 | Height = 13 58 | Caption = 'Server primarily tested against: ' 59 | end 60 | object lblRSMBUrl: TLabel 61 | Left = 168 62 | Top = 356 63 | Width = 181 64 | Height = 13 65 | Cursor = crHandPoint 66 | Caption = 'http://alphaworks.ibm.com/tech/rsmb' 67 | Font.Charset = DEFAULT_CHARSET 68 | Font.Color = clBlue 69 | Font.Height = -11 70 | Font.Name = 'Tahoma' 71 | Font.Style = [fsUnderline] 72 | ParentFont = False 73 | end 74 | object lblLimits: TLabel 75 | Left = 8 76 | Top = 375 77 | Width = 398 78 | Height = 13 79 | Caption = 80 | 'This Sample is not comprehensive of either the TMQTTClient nor t' + 81 | 'he MQTT Protocol' 82 | end 83 | object lblLimits2: TLabel 84 | Left = 8 85 | Top = 392 86 | Width = 288 87 | Height = 13 88 | Caption = 'but is a good place to start in learning how to use the client.' 89 | end 90 | object lblSynapse: TLabel 91 | Left = 8 92 | Top = 40 93 | Width = 402 94 | Height = 13 95 | Caption = 96 | 'You will need the Synapse Internet components to be in your proj' + 97 | 'ect search paths. ' 98 | end 99 | object btnConnect: TButton 100 | Left = 343 101 | Top = 56 102 | Width = 75 103 | Height = 25 104 | Caption = 'Connect' 105 | TabOrder = 0 106 | OnClick = btnConnectClick 107 | end 108 | object btnDisconnect: TButton 109 | Left = 343 110 | Top = 87 111 | Width = 75 112 | Height = 25 113 | Caption = 'Disconnect' 114 | TabOrder = 1 115 | OnClick = btnDisconnectClick 116 | end 117 | object btnPublish: TButton 118 | Left = 343 119 | Top = 150 120 | Width = 75 121 | Height = 25 122 | Caption = 'Publish' 123 | TabOrder = 2 124 | OnClick = btnPublishClick 125 | end 126 | object eTopic: TEdit 127 | Left = 8 128 | Top = 152 129 | Width = 121 130 | Height = 21 131 | TabOrder = 3 132 | Text = '/dev/test' 133 | end 134 | object eMessage: TEdit 135 | Left = 135 136 | Top = 152 137 | Width = 202 138 | Height = 21 139 | TabOrder = 4 140 | Text = 'This is a test message' 141 | end 142 | object eIP: TEdit 143 | Left = 8 144 | Top = 58 145 | Width = 202 146 | Height = 21 147 | TabOrder = 5 148 | Text = '127.0.0.1' 149 | end 150 | object ePort: TEdit 151 | Left = 216 152 | Top = 58 153 | Width = 121 154 | Height = 21 155 | TabOrder = 6 156 | Text = '1883' 157 | end 158 | object btnPing: TButton 159 | Left = 343 160 | Top = 118 161 | Width = 75 162 | Height = 25 163 | Caption = 'Ping' 164 | TabOrder = 7 165 | OnClick = btnPingClick 166 | end 167 | object btnSubscribe: TButton 168 | Left = 343 169 | Top = 179 170 | Width = 75 171 | Height = 25 172 | Caption = 'Subscribe' 173 | TabOrder = 8 174 | OnClick = btnSubscribeClick 175 | end 176 | object eSubTopic: TEdit 177 | Left = 135 178 | Top = 179 179 | Width = 202 180 | Height = 21 181 | TabOrder = 9 182 | Text = '/dev/test' 183 | end 184 | object mStatus: TMemo 185 | Left = 8 186 | Top = 210 187 | Width = 409 188 | Height = 121 189 | TabOrder = 10 190 | end 191 | end 192 | -------------------------------------------------------------------------------- /uMain.pas: -------------------------------------------------------------------------------- 1 | unit uMain; 2 | 3 | interface 4 | 5 | uses 6 | Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 7 | Dialogs, StdCtrls, ExtCtrls, 8 | 9 | MQTT; 10 | 11 | type 12 | TfMain = class(TForm) 13 | lblHeader: TLabel; 14 | lnlMQTTInfo: TLabel; 15 | lblMQTTUrl: TLabel; 16 | lblPrimarilyTested: TLabel; 17 | lblRSMBUrl: TLabel; 18 | lblLimits: TLabel; 19 | lblLimits2: TLabel; 20 | lblSynapse: TLabel; 21 | btnConnect: TButton; 22 | btnDisconnect: TButton; 23 | btnPublish: TButton; 24 | eTopic: TEdit; 25 | eMessage: TEdit; 26 | eIP: TEdit; 27 | ePort: TEdit; 28 | btnPing: TButton; 29 | btnSubscribe: TButton; 30 | eSubTopic: TEdit; 31 | mStatus: TMemo; 32 | procedure btnConnectClick(Sender: TObject); 33 | procedure btnDisconnectClick(Sender: TObject); 34 | procedure btnPingClick(Sender: TObject); 35 | procedure btnPublishClick(Sender: TObject); 36 | procedure btnSubscribeClick(Sender: TObject); 37 | procedure GotConnAck(Sender: TObject; ReturnCode: integer); 38 | procedure GotPingResp(Sender: TObject); 39 | procedure GotSubAck(Sender: TObject; MessageID: integer; GrantedQoS: Array of integer); 40 | procedure GotUnSubAck(Sender: TObject; MessageID: integer); 41 | procedure GotPub(Sender: TObject; topic, payload: Ansistring); 42 | procedure GotPubAck(Sender: TObject; MessageID: integer); 43 | procedure GotPubRec(Sender: TObject; MessageID: integer); 44 | procedure GotPubRel(Sender: TObject; MessageID: integer); 45 | procedure GotPubComp(Sender: TObject; MessageID: integer); 46 | private 47 | { Private declarations } 48 | public 49 | { Public declarations } 50 | end; 51 | 52 | var 53 | fMain: TfMain; 54 | MQTT: TMQTT; 55 | 56 | implementation 57 | 58 | {$R *.dfm} 59 | 60 | procedure TfMain.btnConnectClick(Sender: TObject); 61 | begin 62 | MQTT := TMQTT.Create(eIP.Text, StrToInt(ePort.Text)); 63 | MQTT.WillTopic := '/clients/will'; 64 | MQTT.WillMsg := 'Broker died!'; 65 | // Events 66 | MQTT.OnConnAck := GotConnAck; 67 | MQTT.OnPublish := GotPub; 68 | MQTT.OnPingResp := GotPingResp; 69 | MQTT.OnSubAck := GotSubAck; 70 | MQTT.OnUnSubAck := GotUnSubAck; 71 | MQTT.OnPubAck := GotPubAck; 72 | 73 | if MQTT.Connect then 74 | mStatus.Lines.Add('Connected to ' + eIP.Text + ' on ' + ePort.Text) 75 | else 76 | mStatus.Lines.Add('Failed to connect'); 77 | end; 78 | 79 | procedure TfMain.btnDisconnectClick(Sender: TObject); 80 | begin 81 | if (Assigned(MQTT)) then 82 | begin 83 | MQTT.Disconnect; 84 | mStatus.Lines.Add('Disconnected'); 85 | FreeAndNil(MQTT); 86 | end; 87 | end; 88 | 89 | procedure TfMain.btnPingClick(Sender: TObject); 90 | begin 91 | if (Assigned(MQTT)) then 92 | begin 93 | mStatus.Lines.Add('Ping'); 94 | MQTT.PingReq; 95 | end; 96 | end; 97 | 98 | procedure TfMain.btnPublishClick(Sender: TObject); 99 | begin 100 | if (Assigned(MQTT)) then 101 | begin 102 | MQTT.Publish(eTopic.Text, eMessage.Text); 103 | mStatus.Lines.Add('Published'); 104 | end; 105 | end; 106 | 107 | procedure TfMain.btnSubscribeClick(Sender: TObject); 108 | begin 109 | if (Assigned(MQTT)) then 110 | begin 111 | MQTT.Subscribe(eSubTopic.Text, 0); 112 | mStatus.Lines.Add('Subscribe'); 113 | end; 114 | end; 115 | 116 | procedure TfMain.GotConnAck(Sender: TObject; ReturnCode: integer); 117 | begin 118 | mStatus.Lines.Add('Connection Acknowledged: ' + IntToStr(ReturnCode)); 119 | end; 120 | 121 | procedure TfMain.GotPingResp(Sender: TObject); 122 | begin 123 | mStatus.Lines.Add('PONG!'); 124 | end; 125 | 126 | procedure TfMain.GotPub(Sender: TObject; topic, payload: Ansistring); 127 | begin 128 | mStatus.Lines.Add('Message Recieved on ' + topic + ' payload: ' + payload); 129 | end; 130 | 131 | procedure TfMain.GotPubAck(Sender: TObject; MessageID: integer); 132 | begin 133 | mStatus.Lines.Add('Got PubAck ' + IntToStr(MessageID)); 134 | end; 135 | 136 | procedure TfMain.GotPubComp(Sender: TObject; MessageID: integer); 137 | begin 138 | mStatus.Lines.Add('Got PubComp ' + IntToStr(MessageID)); 139 | end; 140 | 141 | procedure TfMain.GotPubRec(Sender: TObject; MessageID: integer); 142 | begin 143 | mStatus.Lines.Add('Got PubRec ' + IntToStr(MessageID)); 144 | end; 145 | 146 | procedure TfMain.GotPubRel(Sender: TObject; MessageID: integer); 147 | begin 148 | mStatus.Lines.Add('Got PubRel ' + IntToStr(MessageID)); 149 | end; 150 | 151 | procedure TfMain.GotSubAck(Sender: TObject; MessageID: integer; 152 | GrantedQoS: array of integer); 153 | begin 154 | mStatus.Lines.Add('Got SubAck ' + IntToStr(MessageID)); 155 | end; 156 | 157 | procedure TfMain.GotUnSubAck(Sender: TObject; MessageID: integer); 158 | begin 159 | mStatus.Lines.Add('Got UnSubAck ' + IntToStr(MessageID)); 160 | end; 161 | 162 | end. 163 | -------------------------------------------------------------------------------- /Tests/TestMQTT.pas: -------------------------------------------------------------------------------- 1 | unit TestMQTT; 2 | { 3 | 4 | Delphi DUnit Test Case 5 | ---------------------- 6 | This unit contains a skeleton test case class generated by the Test Case Wizard. 7 | Modify the generated code to correctly setup and call the methods from the unit 8 | being tested. 9 | 10 | } 11 | 12 | interface 13 | 14 | uses 15 | TestFramework, MQTT, Generics.Collections, Types, Classes, ExtCtrls, SysUtils, 16 | MQTTHeaders, MQTTReadThread, blcksock, SyncObjs, SmartInspect, SiAuto; 17 | 18 | type 19 | // Test methods for class TMQTT 20 | 21 | TestTMQTT = class(TTestCase) 22 | strict private 23 | FMQTT: TMQTT; 24 | public 25 | procedure SetUp; override; 26 | procedure TearDown; override; 27 | published 28 | procedure TestgetNextMessageId; 29 | procedure TestConnectMessage; 30 | procedure TestDisconnectMessage; 31 | procedure TestPublishMessage; 32 | procedure TestPingReqMessage; 33 | procedure TestSubscribeMessage; 34 | procedure TestUnsubscribeMessage; 35 | procedure TestConnect; 36 | procedure TestDisconnect; 37 | procedure TestPublish; 38 | procedure TestPublish1; 39 | procedure TestPublish2; 40 | procedure TestSubscribe; 41 | procedure TestSubscribe1; 42 | procedure TestUnsubscribe; 43 | procedure TestUnsubscribe1; 44 | procedure TestPingReq; 45 | end; 46 | 47 | implementation 48 | 49 | procedure TestTMQTT.SetUp; 50 | begin 51 | FMQTT := TMQTT.Create('localhost', 1883); 52 | end; 53 | 54 | procedure TestTMQTT.TearDown; 55 | begin 56 | FMQTT.Free; 57 | FMQTT := nil; 58 | end; 59 | 60 | procedure TestTMQTT.TestgetNextMessageId; 61 | var 62 | ReturnValue: Integer; 63 | begin 64 | //ReturnValue := FMQTT.getNextMessageId; 65 | // TODO: Validate method results 66 | end; 67 | 68 | procedure TestTMQTT.TestConnectMessage; 69 | var 70 | ReturnValue: TMQTTMessage; 71 | begin 72 | //ReturnValue := FMQTT.ConnectMessage; 73 | // TODO: Validate method results 74 | end; 75 | 76 | procedure TestTMQTT.TestDisconnectMessage; 77 | var 78 | ReturnValue: TMQTTMessage; 79 | begin 80 | //ReturnValue := FMQTT.DisconnectMessage; 81 | // TODO: Validate method results 82 | end; 83 | 84 | procedure TestTMQTT.TestPublishMessage; 85 | var 86 | ReturnValue: TMQTTMessage; 87 | begin 88 | //ReturnValue := FMQTT.PublishMessage; 89 | // TODO: Validate method results 90 | end; 91 | 92 | procedure TestTMQTT.TestPingReqMessage; 93 | var 94 | ReturnValue: TMQTTMessage; 95 | msgBytes: TBytes; 96 | begin 97 | //ReturnValue := FMQTT.PingReqMessage; 98 | // TODO: Validate method results 99 | ReturnValue := TMQTTMessage.Create; 100 | ReturnValue.FixedHeader.MessageType := Ord(TMQTTMessageType.PINGREQ); 101 | msgBytes := ReturnValue.ToBytes; 102 | 103 | end; 104 | 105 | procedure TestTMQTT.TestSubscribeMessage; 106 | var 107 | ReturnValue: TMQTTMessage; 108 | begin 109 | //ReturnValue := FMQTT.SubscribeMessage; 110 | // TODO: Validate method results 111 | end; 112 | 113 | procedure TestTMQTT.TestUnsubscribeMessage; 114 | var 115 | ReturnValue: TMQTTMessage; 116 | begin 117 | //ReturnValue := FMQTT.UnsubscribeMessage; 118 | // TODO: Validate method results 119 | end; 120 | 121 | procedure TestTMQTT.TestConnect; 122 | var 123 | ReturnValue: Boolean; 124 | begin 125 | //ReturnValue := FMQTT.Connect; 126 | // TODO: Validate method results 127 | end; 128 | 129 | procedure TestTMQTT.TestDisconnect; 130 | var 131 | ReturnValue: Boolean; 132 | begin 133 | //ReturnValue := FMQTT.Disconnect; 134 | // TODO: Validate method results 135 | end; 136 | 137 | procedure TestTMQTT.TestPublish; 138 | var 139 | ReturnValue: Boolean; 140 | sPayload: string; 141 | Topic: string; 142 | begin 143 | // TODO: Setup method call parameters 144 | //ReturnValue := FMQTT.Publish(Topic, sPayload); 145 | // TODO: Validate method results 146 | end; 147 | 148 | procedure TestTMQTT.TestPublish1; 149 | var 150 | ReturnValue: Boolean; 151 | Retain: Boolean; 152 | sPayload: string; 153 | Topic: string; 154 | begin 155 | // TODO: Setup method call parameters 156 | //ReturnValue := FMQTT.Publish(Topic, sPayload, Retain); 157 | // TODO: Validate method results 158 | end; 159 | 160 | procedure TestTMQTT.TestPublish2; 161 | var 162 | ReturnValue: Boolean; 163 | QoS: Integer; 164 | Retain: Boolean; 165 | sPayload: string; 166 | Topic: string; 167 | begin 168 | // TODO: Setup method call parameters 169 | //ReturnValue := FMQTT.Publish(Topic, sPayload, Retain, QoS); 170 | // TODO: Validate method results 171 | end; 172 | 173 | procedure TestTMQTT.TestSubscribe; 174 | var 175 | ReturnValue: Integer; 176 | RequestQoS: Integer; 177 | Topic: string; 178 | begin 179 | // TODO: Setup method call parameters 180 | //ReturnValue := FMQTT.Subscribe(Topic, RequestQoS); 181 | // TODO: Validate method results 182 | end; 183 | 184 | procedure TestTMQTT.TestSubscribe1; 185 | var 186 | ReturnValue: Integer; 187 | //Topics: TDictionary; 188 | begin 189 | // TODO: Setup method call parameters 190 | //ReturnValue := FMQTT.Subscribe(Topics); 191 | // TODO: Validate method results 192 | end; 193 | 194 | procedure TestTMQTT.TestUnsubscribe; 195 | var 196 | ReturnValue: Integer; 197 | Topic: string; 198 | begin 199 | // TODO: Setup method call parameters 200 | //ReturnValue := FMQTT.Unsubscribe(Topic); 201 | // TODO: Validate method results 202 | end; 203 | 204 | procedure TestTMQTT.TestUnsubscribe1; 205 | var 206 | ReturnValue: Integer; 207 | Topics: TStringList; 208 | begin 209 | // TODO: Setup method call parameters 210 | //ReturnValue := FMQTT.Unsubscribe(Topics); 211 | // TODO: Validate method results 212 | end; 213 | 214 | procedure TestTMQTT.TestPingReq; 215 | var 216 | ReturnValue: Boolean; 217 | begin 218 | //ReturnValue := FMQTT.PingReq; 219 | // TODO: Validate method results 220 | end; 221 | 222 | initialization 223 | // Register any test cases with the test runner 224 | RegisterTest(TestTMQTT.Suite); 225 | end. 226 | 227 | -------------------------------------------------------------------------------- /TMQTTTest.dproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | {325199D2-21BE-4089-BB6E-63FBB9BB17D6} 4 | 13.4 5 | TMQTTTest.dpr 6 | Debug 7 | DCC32 8 | VCL 9 | True 10 | Win32 11 | 1 12 | Application 13 | 14 | 15 | true 16 | 17 | 18 | true 19 | Base 20 | true 21 | 22 | 23 | true 24 | Base 25 | true 26 | 27 | 28 | true 29 | Base 30 | true 31 | 32 | 33 | true 34 | Base 35 | true 36 | 37 | 38 | true 39 | Cfg_2 40 | true 41 | true 42 | 43 | 44 | CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= 45 | Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;System;Xml;Data;Datasnap;Web;Soap;Winapi;$(DCC_Namespace) 46 | 2057 47 | ..\..\Development Stuff\Delphi\Synapse;$(DCC_UnitSearchPath) 48 | TMQTTTest.exe 49 | 00400000 50 | x86 51 | false 52 | false 53 | false 54 | false 55 | false 56 | 57 | 58 | $(BDS)\bin\default_app.manifest 59 | TMQTTTest_Icon.ico 60 | 61 | 62 | $(BDS)\bin\default_app.manifest 63 | 1033 64 | true 65 | TMQTTTest_Icon.ico 66 | CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= 67 | System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) 68 | 69 | 70 | false 71 | RELEASE;$(DCC_Define) 72 | 0 73 | false 74 | 75 | 76 | 3 77 | DEBUG;madExcept;$(DCC_Define) 78 | 79 | 80 | D:\Dev\Dev Tools\Delphi\synapse\trunk 81 | true 82 | 1033 83 | 84 | 85 | 86 | MainSource 87 | 88 | 89 |
fMain
90 |
91 | 92 | 93 | 94 | 95 | Cfg_2 96 | Base 97 | 98 | 99 | Base 100 | 101 | 102 | Cfg_1 103 | Base 104 | 105 |
106 | 107 | 108 | Delphi.Personality.12 109 | 110 | 111 | 112 | 113 | False 114 | True 115 | False 116 | 117 | 118 | False 119 | False 120 | 1 121 | 0 122 | 0 123 | 0 124 | False 125 | False 126 | False 127 | False 128 | False 129 | 2057 130 | 1252 131 | 132 | 133 | 134 | 135 | 1.0.0.0 136 | 137 | 138 | 139 | 140 | 141 | 1.0.0.0 142 | 143 | 144 | 145 | TMQTTTest.dpr 146 | 147 | 148 | Microsoft Office 2000 Sample Automation Server Wrapper Components 149 | Microsoft Office XP Sample Automation Server Wrapper Components 150 | Embarcadero C++Builder Office 2000 Servers Package 151 | Embarcadero C++Builder Office XP Servers Package 152 | 153 | 154 | False 155 | 156 | False 157 | True 158 | 159 | 160 | 12 161 | 162 | 163 |
164 | -------------------------------------------------------------------------------- /Tests/TMQTTLibTests.dproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | {0B0BF9D4-E894-423A-A386-0AA7F2D7C9A1} 4 | 13.4 5 | Debug 6 | DCC32 7 | TMQTTLibTests.dpr 8 | VCL 9 | True 10 | Win32 11 | 1 12 | Application 13 | 14 | 15 | true 16 | 17 | 18 | true 19 | Base 20 | true 21 | 22 | 23 | true 24 | Base 25 | true 26 | 27 | 28 | true 29 | Base 30 | true 31 | 32 | 33 | true 34 | Base 35 | true 36 | 37 | 38 | true 39 | Cfg_2 40 | true 41 | true 42 | 43 | 44 | CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= 45 | Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;System;Xml;Data;Datasnap;Web;Soap;Winapi;$(DCC_Namespace) 46 | 2057 47 | $(BDS)\Source\DUnit\src;$(DCC_UnitSearchPath) 48 | TMQTTLibTests.exe 49 | 00400000 50 | x86 51 | _CONSOLE_TESTRUNNER;$(DCC_Define) 52 | false 53 | false 54 | false 55 | . 56 | false 57 | false 58 | 59 | 60 | $(BDS)\bin\default_app.manifest 61 | TMQTTLibTests_Icon.ico 62 | 63 | 64 | $(BDS)\bin\default_app.manifest 65 | 1033 66 | true 67 | TMQTTLibTests_Icon.ico 68 | CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= 69 | System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) 70 | 71 | 72 | false 73 | RELEASE;$(DCC_Define) 74 | 0 75 | false 76 | 77 | 78 | DEBUG;$(DCC_Define) 79 | 80 | 81 | D:\Dev\Dev Tools\Delphi\synapse\trunk;$(DCC_UnitSearchPath) 82 | true 83 | 1033 84 | 85 | 86 | 87 | MainSource 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | Cfg_2 97 | Base 98 | 99 | 100 | Base 101 | 102 | 103 | Cfg_1 104 | Base 105 | 106 | 107 | 108 | 109 | Delphi.Personality.12 110 | 111 | 112 | 113 | 114 | False 115 | True 116 | False 117 | 118 | 119 | False 120 | False 121 | 1 122 | 0 123 | 0 124 | 0 125 | False 126 | False 127 | False 128 | False 129 | False 130 | 2057 131 | 1252 132 | 133 | 134 | 135 | 136 | 1.0.0.0 137 | 138 | 139 | 140 | 141 | 142 | 1.0.0.0 143 | 144 | 145 | 146 | TMQTTLibTests.dpr 147 | 148 | 149 | Microsoft Office 2000 Sample Automation Server Wrapper Components 150 | Microsoft Office XP Sample Automation Server Wrapper Components 151 | Embarcadero C++Builder Office 2000 Servers Package 152 | Embarcadero C++Builder Office XP Servers Package 153 | 154 | 155 | 156 | DUnit / Delphi Win32 157 | GUI 158 | 159 | 160 | False 161 | True 162 | 163 | 164 | 12 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /TMQTT/MQTTReadThread.pas: -------------------------------------------------------------------------------- 1 | unit MQTTReadThread; 2 | 3 | interface 4 | 5 | uses 6 | Classes, 7 | SysUtils, 8 | {$IFDEF MSWINDOWS} 9 | Windows, 10 | {$ENDIF} 11 | Generics.Collections, 12 | SyncObjs, 13 | 14 | MQTTHeaders, 15 | blcksock; 16 | 17 | type 18 | PTCPBlockSocket = ^TTCPBlockSocket; 19 | 20 | TMQTTRecvUtilities = class 21 | public 22 | class function MSBLSBToInt(ALengthBytes: TBytes): integer; 23 | class function RLBytesToInt(ARlBytes: TBytes): Integer; 24 | end; 25 | 26 | TUnparsedMsg = record 27 | public 28 | FixedHeader: Byte; 29 | RL: TBytes; 30 | Data: TBytes; 31 | end; 32 | 33 | TMQTTReadThread = class(TThread) 34 | private 35 | { Private declarations } 36 | FPSocket: TTCPBlockSocket; 37 | 38 | FCSock: TCriticalSection; 39 | FCurrentMsg: TUnparsedMsg; 40 | 41 | FCurrentRecvState: TMQTTRecvState; 42 | // Events 43 | FConnAckEvent: TConnAckEvent; 44 | FPublishEvent: TPublishEvent; 45 | FPingRespEvent: TPingRespEvent; 46 | FPingReqEvent: TPingReqEvent; 47 | FSubAckEvent: TSubAckEvent; 48 | FUnSubAckEvent: TUnSubAckEvent; 49 | FPubAckEvent: TPubAckEvent; 50 | FPubRelEvent: TPubRelEvent; 51 | FPubRecEvent: TPubRecEvent; 52 | FPubCompEvent: TPubCompEvent; 53 | 54 | 55 | procedure ProcessMessage; 56 | function readSingleString(const dataStream: TBytes; const indexStartAt: Integer; var stringRead: Ansistring): integer; 57 | function readMessageId(const dataStream: TBytes; const indexStartAt: Integer; var messageId: integer): integer; 58 | function readStringWithoutPrefix(const dataStream: TBytes; 59 | const indexStartAt: Integer; var stringRead: Ansistring): integer; 60 | protected 61 | procedure CleanStart; 62 | procedure Execute; override; 63 | public 64 | constructor Create(Socket: TTCPBlockSocket; CSSock: TCriticalSection); 65 | 66 | // Event properties. 67 | property OnConnAck : TConnAckEvent read FConnAckEvent write FConnAckEvent; 68 | property OnPublish : TPublishEvent read FPublishEvent write FPublishEvent; 69 | property OnPingResp : TPingRespEvent read FPingRespEvent write FPingRespEvent; 70 | property OnPingReq : TPingRespEvent read FPingRespEvent write FPingRespEvent; 71 | property OnSubAck : TSubAckEvent read FSubAckEvent write FSubAckEvent; 72 | property OnUnSubAck : TUnSubAckEvent read FUnSubAckEvent write FUnSubAckEvent; 73 | property OnPubAck : TUnSubAckEvent read FUnSubAckEvent write FUnSubAckEvent; 74 | property OnPubRec : TUnSubAckEvent read FUnSubAckEvent write FUnSubAckEvent; 75 | property OnPubRel : TUnSubAckEvent read FUnSubAckEvent write FUnSubAckEvent; 76 | property OnPubComp : TUnSubAckEvent read FUnSubAckEvent write FUnSubAckEvent; 77 | end; 78 | 79 | implementation 80 | 81 | uses 82 | MQTT; 83 | 84 | class function TMQTTRecvUtilities.MSBLSBToInt(ALengthBytes: TBytes): integer; 85 | begin 86 | Assert(ALengthBytes <> nil, 'Must not pass nil to this method'); 87 | Assert(Length(ALengthBytes) = 2, 'The MSB-LSB 2 bytes structure must be 2 Bytes in length'); 88 | 89 | Result := 0; 90 | Result := ALengthBytes[0] shl 8; 91 | Result := Result + ALengthBytes[1]; 92 | end; 93 | 94 | class function TMQTTRecvUtilities.RLBytesToInt(ARlBytes: TBytes): Integer; 95 | var 96 | multi: integer; 97 | i: integer; 98 | digit: Byte; 99 | begin 100 | Assert(ARlBytes <> nil, 'Must not pass nil to this method'); 101 | 102 | multi := 1; 103 | i := 0; 104 | Result := 0; 105 | 106 | if ((Length(ARlBytes) > 0) and (Length(ARlBytes) <= 4)) then 107 | begin 108 | digit := ARlBytes[i]; 109 | repeat 110 | digit := ARlBytes[i]; 111 | Result := Result + (digit and 127) * multi; 112 | multi := multi * 128; 113 | Inc(i); 114 | until ((digit and 128) = 0); 115 | end; 116 | end; 117 | 118 | procedure AppendBytes(var DestArray: TBytes; 119 | const NewBytes: TBytes); 120 | var 121 | DestLen: Integer; 122 | begin 123 | if Length(NewBytes) > 0 then 124 | begin 125 | DestLen := Length(DestArray); 126 | SetLength(DestArray, DestLen + Length(NewBytes)); 127 | Move(NewBytes[0], DestArray[DestLen], Length(NewBytes)); 128 | end; 129 | end; 130 | 131 | { TMQTTReadThread } 132 | 133 | procedure TMQTTReadThread.CleanStart; 134 | begin 135 | FCurrentRecvState := TMQTTRecvState.FixedHeaderByte; 136 | end; 137 | 138 | constructor TMQTTReadThread.Create(Socket: TTCPBlockSocket; CSSock: TCriticalSection); 139 | begin 140 | inherited Create(false); 141 | 142 | FPSocket := Socket; 143 | FCSock := CSSock; 144 | FreeOnTerminate := false; 145 | 146 | CleanStart; 147 | end; 148 | 149 | procedure TMQTTReadThread.Execute; 150 | var 151 | CurrentMessage: TUnparsedMsg; 152 | RLInt: Integer; 153 | Buffer: TBytes; 154 | i: integer; 155 | begin 156 | while not Terminated do 157 | begin 158 | FCSock.Acquire; 159 | try 160 | if FPSocket.WaitingDataEx > 0 then 161 | begin 162 | case FCurrentRecvState of 163 | TMQTTRecvState.FixedHeaderByte: 164 | begin 165 | CurrentMessage.FixedHeader := 0; 166 | CurrentMessage.FixedHeader := FPSocket.RecvByte(1000); 167 | if ((FPSocket.LastError = 0) and (CurrentMessage.FixedHeader <> 0)) then FCurrentRecvState := TMQTTRecvState.RemainingLength; 168 | end; 169 | TMQTTRecvState.RemainingLength: 170 | begin 171 | RLInt := 0; 172 | 173 | SetLength(CurrentMessage.RL, 1); 174 | SetLength(Buffer, 1); 175 | CurrentMessage.RL[0] := FPSocket.RecvByte(1000); 176 | for i := 1 to 4 do 177 | begin 178 | if (( CurrentMessage.RL[i - 1] and 128) <> 0) then 179 | begin 180 | Buffer[0] := FPSocket.PeekByte(1000); 181 | AppendBytes(CurrentMessage.RL, Buffer); 182 | end else Break; 183 | end; 184 | 185 | RLInt := TMQTTRecvUtilities.RLBytesToInt(CurrentMessage.RL); 186 | 187 | if (FPSocket.LastError = 0) then FCurrentRecvState := TMQTTRecvState.Data; 188 | end; 189 | TMQTTRecvState.Data: 190 | begin 191 | if (RLInt > 0) then 192 | begin 193 | SetLength(CurrentMessage.Data, RLInt); 194 | RLInt := RLInt - FPSocket.RecvBufferEx(Pointer(CurrentMessage.Data), RLInt, 1000); 195 | end; 196 | 197 | if ((FPSocket.LastError = 0) and (RLInt = 0)) then 198 | begin 199 | FCurrentMsg := CurrentMessage; 200 | Synchronize(ProcessMessage); 201 | CurrentMessage := Default(TUnparsedMsg); 202 | FCurrentRecvState := TMQTTRecvState.FixedHeaderByte; 203 | end; 204 | end; 205 | end; // end of Recv state case 206 | end; // end of waitingdata check 207 | finally 208 | FCSock.Release; 209 | end; 210 | end; 211 | end; 212 | 213 | procedure TMQTTReadThread.ProcessMessage; 214 | var 215 | NewMsg: TUnparsedMsg; 216 | FHCode: Byte; 217 | dataCaret: integer; 218 | grantedQoS: Array of Integer; 219 | I: Integer; 220 | strTopic, strPayload: AnsiString; 221 | begin 222 | dataCaret := 0; 223 | 224 | NewMsg := FCurrentMsg; 225 | FHCode := NewMsg.FixedHeader shr 4; 226 | case FHCode of 227 | Ord(TMQTTMessageType.CONNACK): 228 | begin 229 | if Length(NewMsg.Data) > 0 then 230 | begin 231 | if Assigned(FConnAckEvent) then OnConnAck(Self, NewMsg.Data[0]); 232 | end; 233 | end; 234 | Ord(TMQTTMessageType.PINGREQ): 235 | begin 236 | if Assigned(FPingReqEvent) then OnPingReq(Self); 237 | end; 238 | Ord(TMQTTMessageType.PINGRESP): 239 | begin 240 | if Assigned(FPingRespEvent) then OnPingResp(Self); 241 | end; 242 | Ord(TMQTTMessageType.PUBLISH): 243 | begin 244 | // Todo: This only applies for QoS level 0 messages. 245 | dataCaret := 0; 246 | dataCaret := readSingleString(NewMsg.Data, dataCaret, strTopic); 247 | dataCaret := readStringWithoutPrefix(NewMsg.Data, dataCaret, strPayload); 248 | if Assigned(FPublishEvent) then OnPublish(Self, strTopic, strPayload); 249 | end; 250 | Ord(TMQTTMessageType.SUBACK): 251 | begin 252 | if (Length(NewMsg.Data) > 2) then 253 | begin 254 | SetLength(grantedQoS, Length(NewMsg.Data) - 2); 255 | for I := 0 to Length(NewMsg.Data) - 1 do 256 | begin 257 | grantedQoS[i] := NewMsg.Data[i + 2]; 258 | end; 259 | if Assigned(FSubAckEvent) then OnSubAck(Self, TMQTTRecvUtilities.MSBLSBToInt(Copy(NewMsg.Data, 0, 2)), grantedQoS); 260 | end; 261 | end; 262 | Ord(TMQTTMessageType.UNSUBACK): 263 | begin 264 | if Length(NewMsg.Data) = 2 then 265 | begin 266 | if Assigned(FUnSubAckEvent) then OnUnSubAck(Self, TMQTTRecvUtilities.MSBLSBToInt(NewMsg.Data)) 267 | end; 268 | end; 269 | Ord(TMQTTMessageType.PUBREC): 270 | begin 271 | if Length(NewMsg.Data) = 2 then 272 | begin 273 | if Assigned(FPubRecEvent) then OnPubRec(Self, TMQTTRecvUtilities.MSBLSBToInt(NewMsg.Data)) 274 | end; 275 | end; 276 | Ord(TMQTTMessageType.PUBREL): 277 | begin 278 | if Length(NewMsg.Data) = 2 then 279 | begin 280 | if Assigned(FPubRelEvent) then OnPubRel(Self, TMQTTRecvUtilities.MSBLSBToInt(NewMsg.Data)) 281 | end; 282 | end; 283 | Ord(TMQTTMessageType.PUBACK): 284 | begin 285 | if Length(NewMsg.Data) = 2 then 286 | begin 287 | if Assigned(FPubAckEvent) then OnPubAck(Self, TMQTTRecvUtilities.MSBLSBToInt(NewMsg.Data)) 288 | end; 289 | end; 290 | Ord(TMQTTMessageType.PUBCOMP): 291 | begin 292 | if Length(NewMsg.Data) = 2 then 293 | begin 294 | if Assigned(FPubCompEvent) then OnPubComp(Self, TMQTTRecvUtilities.MSBLSBToInt(NewMsg.Data)) 295 | end; 296 | end; 297 | end; 298 | 299 | end; 300 | 301 | function TMQTTReadThread.readMessageId(const dataStream: TBytes; 302 | const indexStartAt: Integer; var messageId: integer): integer; 303 | begin 304 | messageId := TMQTTRecvUtilities.MSBLSBToInt(Copy(dataStream, indexStartAt, 2)); 305 | Result := indexStartAt + 2; 306 | end; 307 | 308 | function TMQTTReadThread.readStringWithoutPrefix(const dataStream: TBytes; 309 | const indexStartAt: Integer; var stringRead: Ansistring): integer; 310 | var 311 | strLength: integer; 312 | begin 313 | strLength := Length(dataStream) - (indexStartAt + 1); 314 | if strLength > 0 then 315 | begin 316 | SetString(stringRead, PAnsiChar(@dataStream[indexStartAt + 2]), (strLength -1) ); 317 | end; 318 | Result := indexStartAt + strLength; 319 | end; 320 | 321 | function TMQTTReadThread.readSingleString(const dataStream: TBytes; 322 | const indexStartAt: Integer; var stringRead: Ansistring): integer; 323 | var 324 | strLength: integer; 325 | begin 326 | strLength := TMQTTRecvUtilities.MSBLSBToInt(Copy(dataStream, indexStartAt, 2)); 327 | if strLength > 0 then 328 | begin 329 | SetString(stringRead, PAnsiChar(@dataStream[indexStartAt + 2]), strLength); 330 | end; 331 | Result := indexStartAt + strLength; 332 | end; 333 | 334 | end. 335 | -------------------------------------------------------------------------------- /Tests/TestMQTTHeaders.pas: -------------------------------------------------------------------------------- 1 | unit TestMQTTHeaders; 2 | { 3 | 4 | Delphi DUnit Test Case 5 | ---------------------- 6 | This unit contains a skeleton test case class generated by the Test Case Wizard. 7 | Modify the generated code to correctly setup and call the methods from the unit 8 | being tested. 9 | 10 | } 11 | 12 | interface 13 | 14 | uses 15 | TestFramework, Classes, Types, MQTTHeaders, SysUtils; 16 | 17 | type 18 | // Test methods for class TMQTTFixedHeader 19 | 20 | TestTMQTTFixedHeader = class(TTestCase) 21 | strict private 22 | FMQTTFixedHeader: TMQTTFixedHeader; 23 | public 24 | procedure SetUp; override; 25 | procedure TearDown; override; 26 | published 27 | procedure TestToByte; 28 | procedure TestToByteStr; 29 | procedure TestMessageTypes; 30 | end; 31 | 32 | // Test methods for RL Methods 33 | 34 | TestUtilityMethods = class(TTestCase) 35 | strict private 36 | 37 | public 38 | procedure SetUp; override; 39 | procedure TearDown; override; 40 | published 41 | procedure TestToRLToBytes; 42 | procedure TestUTF8EncodeToBytes; 43 | procedure TestIntToMSBLSB; 44 | end; 45 | 46 | // Test methods for class TMQTTConnectVarHeader 47 | 48 | TestTMQTTConnectVarHeader = class(TTestCase) 49 | strict private 50 | FMQTTConnectVarHeader: TMQTTConnectVarHeader; 51 | public 52 | procedure SetUp; override; 53 | procedure TearDown; override; 54 | published 55 | procedure TestToBytes; 56 | procedure TestVersionStrings; 57 | procedure TestKeepAliveOnCreation; 58 | end; 59 | // Test methods for class TMQTTPublishVarHeader 60 | 61 | TestTMQTTPublishVarHeader = class(TTestCase) 62 | strict private 63 | FMQTTPublishVarHeader: TMQTTPublishVarHeader; 64 | public 65 | procedure SetUp; override; 66 | procedure TearDown; override; 67 | published 68 | procedure TestToBytes; 69 | end; 70 | // Test methods for class TMQTTSubscribeVarHeader 71 | 72 | TestTMQTTSubscribeVarHeader = class(TTestCase) 73 | strict private 74 | FMQTTSubscribeVarHeader: TMQTTSubscribeVarHeader; 75 | public 76 | procedure SetUp; override; 77 | procedure TearDown; override; 78 | published 79 | procedure TestToBytes; 80 | end; 81 | // Test methods for class TMQTTUnsubscribeVarHeader 82 | 83 | TestTMQTTUnsubscribeVarHeader = class(TTestCase) 84 | strict private 85 | FMQTTUnsubscribeVarHeader: TMQTTUnsubscribeVarHeader; 86 | public 87 | procedure SetUp; override; 88 | procedure TearDown; override; 89 | published 90 | procedure TestToBytes; 91 | end; 92 | // Test methods for class TMQTTPayload 93 | 94 | TestTMQTTPayload = class(TTestCase) 95 | strict private 96 | FMQTTPayload: TMQTTPayload; 97 | public 98 | procedure SetUp; override; 99 | procedure TearDown; override; 100 | published 101 | procedure TestToBytes; 102 | end; 103 | // Test methods for class TMQTTMessage 104 | 105 | TestTMQTTMessage = class(TTestCase) 106 | strict private 107 | FMQTTMessage: TMQTTMessage; 108 | public 109 | procedure SetUp; override; 110 | procedure TearDown; override; 111 | published 112 | procedure TestToBytes; 113 | procedure TestToBytesPublish; 114 | end; 115 | 116 | implementation 117 | 118 | function IntToBin ( value: LongInt; digits: integer ): string; 119 | begin 120 | result := StringOfChar ( '0', digits ) ; 121 | while value > 0 do begin 122 | if ( value and 1 ) = 1 then 123 | result [ digits ] := '1'; 124 | dec ( digits ) ; 125 | value := value shr 1; 126 | end; 127 | end; 128 | 129 | procedure TestTMQTTConnectVarHeader.SetUp; 130 | begin 131 | FMQTTConnectVarHeader := TMQTTConnectVarHeader.Create; 132 | end; 133 | 134 | procedure TestTMQTTConnectVarHeader.TearDown; 135 | begin 136 | FMQTTConnectVarHeader.Free; 137 | FMQTTConnectVarHeader := nil; 138 | end; 139 | 140 | procedure TestTMQTTConnectVarHeader.TestKeepAliveOnCreation; 141 | var 142 | ConnectVar: TMQTTConnectVarHeader; 143 | begin 144 | ConnectVar := TMQTTConnectVarHeader.Create(10); 145 | Assert( ConnectVar.KeepAlive = 10, 'Keep Alive assigned' ); 146 | ConnectVar.Free; 147 | end; 148 | 149 | procedure TestTMQTTConnectVarHeader.TestToBytes; 150 | var 151 | ReturnValue: TBytes; 152 | i: integer; 153 | begin 154 | FMQTTConnectVarHeader.KeepAlive := 10; 155 | FMQTTConnectVarHeader.CleanStart := 1; 156 | FMQTTConnectVarHeader.WillFlag := 1; 157 | FMQTTConnectVarHeader.QoSLevel := 0; 158 | FMQTTConnectVarHeader.Retain := 0; 159 | //FMQTTConnectVarHeader. 160 | ReturnValue := FMQTTConnectVarHeader.ToBytes; 161 | Assert( Length(ReturnValue) = 12 , 'Length of Connect Variable Header' ); 162 | Assert( ReturnValue[0] = 0, 'Length of version string' ); 163 | Assert( ReturnValue[1] = Length(FMQTTConnectVarHeader.PROTOCOL_ID), 'Length of version string' ); 164 | for I := 1 to Length(FMQTTConnectVarHeader.PROTOCOL_ID) do 165 | begin 166 | Assert( Chr(ReturnValue[i + 1]) = FMQTTConnectVarHeader.PROTOCOL_ID[i], 'Version String, expected: ' + FMQTTConnectVarHeader.PROTOCOL_ID[i] + ' got:' + Chr(ReturnValue[1 + i]) ); 167 | end; 168 | // Todo: Finish the rest of this. 169 | end; 170 | 171 | procedure TestTMQTTConnectVarHeader.TestVersionStrings; 172 | begin 173 | Assert( FMQTTConnectVarHeader.PROTOCOL_ID = 'MQIsdp', 'Version String = MQIsdp' ); 174 | Assert( FMQTTConnectVarHeader.PROTOCOL_VER = 3, 'Version 3 of the Protocol' ); 175 | end; 176 | 177 | procedure TestTMQTTPublishVarHeader.SetUp; 178 | begin 179 | FMQTTPublishVarHeader := TMQTTPublishVarHeader.Create; 180 | end; 181 | 182 | procedure TestTMQTTPublishVarHeader.TearDown; 183 | begin 184 | FMQTTPublishVarHeader.Free; 185 | FMQTTPublishVarHeader := nil; 186 | end; 187 | 188 | procedure TestTMQTTPublishVarHeader.TestToBytes; 189 | var 190 | ReturnValue: TBytes; 191 | begin 192 | ReturnValue := FMQTTPublishVarHeader.ToBytes; 193 | // TODO: Validate method results 194 | end; 195 | 196 | procedure TestTMQTTSubscribeVarHeader.SetUp; 197 | begin 198 | FMQTTSubscribeVarHeader := TMQTTSubscribeVarHeader.Create; 199 | end; 200 | 201 | procedure TestTMQTTSubscribeVarHeader.TearDown; 202 | begin 203 | FMQTTSubscribeVarHeader.Free; 204 | FMQTTSubscribeVarHeader := nil; 205 | end; 206 | 207 | procedure TestTMQTTSubscribeVarHeader.TestToBytes; 208 | var 209 | ReturnValue: TBytes; 210 | begin 211 | ReturnValue := FMQTTSubscribeVarHeader.ToBytes; 212 | // TODO: Validate method results 213 | end; 214 | 215 | procedure TestTMQTTUnsubscribeVarHeader.SetUp; 216 | begin 217 | FMQTTUnsubscribeVarHeader := TMQTTUnsubscribeVarHeader.Create; 218 | end; 219 | 220 | procedure TestTMQTTUnsubscribeVarHeader.TearDown; 221 | begin 222 | FMQTTUnsubscribeVarHeader.Free; 223 | FMQTTUnsubscribeVarHeader := nil; 224 | end; 225 | 226 | procedure TestTMQTTUnsubscribeVarHeader.TestToBytes; 227 | var 228 | ReturnValue: TBytes; 229 | begin 230 | ReturnValue := FMQTTUnsubscribeVarHeader.ToBytes; 231 | // TODO: Validate method results 232 | end; 233 | 234 | procedure TestTMQTTPayload.SetUp; 235 | begin 236 | FMQTTPayload := TMQTTPayload.Create; 237 | end; 238 | 239 | procedure TestTMQTTPayload.TearDown; 240 | begin 241 | FMQTTPayload.Free; 242 | FMQTTPayload := nil; 243 | end; 244 | 245 | procedure TestTMQTTPayload.TestToBytes; 246 | var 247 | ReturnValue: TBytes; 248 | begin 249 | FMQTTPayload.Contents.Add('test'); 250 | ReturnValue := FMQTTPayload.ToBytes; 251 | Assert( ReturnValue[0] = 0, 'Length Bytes' ); 252 | Assert( ReturnValue[1] = 4, 'Length Bytes' ); 253 | Assert( Chr(ReturnValue[2]) = 't', 'test String' ); 254 | Assert( Chr(ReturnValue[3]) = 'e', 'test String' ); 255 | Assert( Chr(ReturnValue[4]) = 's', 'test String' ); 256 | Assert( Chr(ReturnValue[5]) = 't', 'test String' ); 257 | FMQTTPayload.Contents.Add('again'); 258 | ReturnValue := FMQTTPayload.ToBytes; 259 | Assert( ReturnValue[6] = 0, 'Length Bytes' ); 260 | Assert( ReturnValue[7] = 5, 'Length Bytes, Actual Value' + IntToStr(ReturnValue[7]) ); 261 | Assert( Chr(ReturnValue[8]) = 'a', 'again String' ); 262 | Assert( Chr(ReturnValue[9]) = 'g', 'again String' ); 263 | Assert( Chr(ReturnValue[10]) = 'a', 'again String' ); 264 | Assert( Chr(ReturnValue[11]) = 'i', 'again String' ); 265 | Assert( Chr(ReturnValue[12]) = 'n', 'again String' ); 266 | end; 267 | 268 | procedure TestTMQTTMessage.SetUp; 269 | begin 270 | FMQTTMessage := TMQTTMessage.Create; 271 | end; 272 | 273 | procedure TestTMQTTMessage.TearDown; 274 | begin 275 | FMQTTMessage.Free; 276 | FMQTTMessage := nil; 277 | end; 278 | 279 | procedure TestTMQTTMessage.TestToBytes; 280 | var 281 | ReturnValue: TBytes; 282 | FH: TMQTTFixedHeader; 283 | VH: TMQTTConnectVarHeader; 284 | PL: TMQTTPayload; 285 | begin 286 | FH.MessageType := Ord(TMQTTMessageType.CONNECT); 287 | FMQTTMessage.FixedHeader := FH; 288 | VH := TMQTTConnectVarHeader.Create(10); 289 | VH.CleanStart := 1; 290 | VH.WillFlag := 1; 291 | VH.QoSLevel := 1; 292 | FMQTTMessage.VariableHeader := VH; 293 | PL := TMQTTPayload.Create; 294 | PL.Contents.Add('/test'); 295 | PL.Contents.Add('broker died!'); 296 | FMQTTMessage.Payload := PL; 297 | 298 | ReturnValue := FMQTTMessage.ToBytes; 299 | // TODO: Validate the whole message end to end. 300 | Assert( ReturnValue[0] = 16, 'Connect Flag' ); 301 | Assert( ReturnValue[1] = 33, 'Remaining Length Bytes' + IntToStr(ReturnValue[1]) ); //12 + 20 302 | Assert( ReturnValue[2] = 0, 'VString L' ); 303 | Assert( ReturnValue[3] = Length(TMQTTConnectVarHeader.PROTOCOL_ID), 'Variable Length' ); 304 | //Assert( Chr(ReturnValue[2]) = 't', 'test String' ); 305 | //Assert( Chr(ReturnValue[3]) = 'e', 'test String' ); 306 | //Assert( Chr(ReturnValue[4]) = 's', 'test String' ); 307 | //Assert( Chr(ReturnValue[5]) = 't', 'test String' ); 308 | end; 309 | 310 | procedure TestTMQTTMessage.TestToBytesPublish; 311 | var 312 | ReturnValue: TBytes; 313 | FH: TMQTTFixedHeader; 314 | VH: TMQTTPublishVarHeader; 315 | PL: TMQTTPayload; 316 | begin 317 | FH.MessageType := Ord(TMQTTMessageType.PUBLISH); 318 | FMQTTMessage.FixedHeader := FH; 319 | VH := TMQTTPublishVarHeader.Create(0); 320 | VH.Topic := '/dev/test'; 321 | FMQTTMessage.VariableHeader := VH; 322 | PL := TMQTTPayload.Create; 323 | PL.Contents.Add('this is a test!'); 324 | FMQTTMessage.Payload := PL; 325 | 326 | ReturnValue := FMQTTMessage.ToBytes; 327 | // TODO: Validate the whole message end to end. 328 | Assert( ReturnValue[0] = 48, 'Publish Flag' ); 329 | Assert( ReturnValue[1] = 28, 'Remaining Length Bytes: ' + IntToStr(ReturnValue[1]) ); //12 + 20 330 | Assert( ReturnValue[2] = 0, 'String Low:' ); 331 | Assert( ReturnValue[3] = 9, 'String High:' + IntToStr(ReturnValue[3])); 332 | Assert( ReturnValue[13] = 0, 'String Low:' ); 333 | Assert( ReturnValue[14] = 15, 'String High:' + IntToStr(ReturnValue[14])); 334 | Assert( Chr(ReturnValue[15]) = 't', 'test String' ); 335 | Assert( Chr(ReturnValue[16]) = 'h', 'test String' ); 336 | Assert( Chr(ReturnValue[17]) = 'i', 'test String' ); 337 | Assert( Chr(ReturnValue[18]) = 's', 'test String' ); 338 | end; 339 | 340 | { TestTMQTTFixedHeader } 341 | 342 | procedure TestTMQTTFixedHeader.SetUp; 343 | begin 344 | inherited; 345 | FMQTTFixedHeader.MessageType := Ord(TMQTTMessageType.CONNECT); 346 | FMQTTFixedHeader.Retain := 0; 347 | FMQTTFixedHeader.QoSLevel := 0; 348 | FMQTTFixedHeader.Duplicate := 0; 349 | end; 350 | 351 | procedure TestTMQTTFixedHeader.TearDown; 352 | begin 353 | inherited; 354 | 355 | end; 356 | 357 | procedure TestTMQTTFixedHeader.TestMessageTypes; 358 | begin 359 | FMQTTFixedHeader.MessageType := Ord(TMQTTMessageType.CONNECT); 360 | Assert( FMQTTFixedHeader.Flags = 16, 'Fixed Header - Connect' ); 361 | FMQTTFixedHeader.MessageType := Ord(TMQTTMessageType.CONNACK); 362 | Assert( FMQTTFixedHeader.Flags = 32, 'Fixed Header - ConAck' ); 363 | FMQTTFixedHeader.MessageType := Ord(TMQTTMessageType.PUBLISH); 364 | Assert( FMQTTFixedHeader.Flags = 48, 'Fixed Header - Publish' ); 365 | FMQTTFixedHeader.MessageType := Ord(TMQTTMessageType.PUBACK); 366 | Assert( FMQTTFixedHeader.Flags = 64, 'Fixed Header - PubAck' ); 367 | FMQTTFixedHeader.MessageType := Ord(TMQTTMessageType.PUBREC); 368 | Assert( FMQTTFixedHeader.Flags = 80, 'Fixed Header - PubRec' ); 369 | FMQTTFixedHeader.MessageType := Ord(TMQTTMessageType.PUBREL); 370 | Assert( FMQTTFixedHeader.Flags = 96, 'Fixed Header - PubRel' ); 371 | FMQTTFixedHeader.MessageType := Ord(TMQTTMessageType.PUBCOMP); 372 | Assert( FMQTTFixedHeader.Flags = 112, 'Fixed Header - PubComp' ); 373 | FMQTTFixedHeader.MessageType := Ord(TMQTTMessageType.SUBSCRIBE); 374 | Assert( FMQTTFixedHeader.Flags = 128, 'Fixed Header - Subscribe' ); 375 | FMQTTFixedHeader.MessageType := Ord(TMQTTMessageType.SUBACK); 376 | Assert( FMQTTFixedHeader.Flags = 144, 'Fixed Header - Sub Ack' ); 377 | FMQTTFixedHeader.MessageType := Ord(TMQTTMessageType.UNSUBSCRIBE); 378 | Assert( FMQTTFixedHeader.Flags = 160, 'Fixed Header - Unsubscribe' ); 379 | FMQTTFixedHeader.MessageType := Ord(TMQTTMessageType.UNSUBACK); 380 | Assert( FMQTTFixedHeader.Flags = 176, 'Fixed Header - Unsubscribe Ack' ); 381 | FMQTTFixedHeader.MessageType := Ord(TMQTTMessageType.PINGREQ); 382 | Assert( FMQTTFixedHeader.Flags = 192, 'Fixed Header - Ping Req' ); 383 | FMQTTFixedHeader.MessageType := Ord(TMQTTMessageType.PINGRESP); 384 | Assert( FMQTTFixedHeader.Flags = 208, 'Fixed Header - Ping Resp' ); 385 | FMQTTFixedHeader.MessageType := Ord(TMQTTMessageType.DISCONNECT); 386 | Assert( FMQTTFixedHeader.Flags = 224, 'Fixed Header - Disconnect' ); 387 | end; 388 | 389 | procedure TestTMQTTFixedHeader.TestToByte; 390 | var 391 | byteValue: Word; 392 | begin 393 | byteValue := FMQTTFixedHeader.Flags; 394 | Assert( byteValue = 16, 'Fixed Header Flags' ); 395 | end; 396 | 397 | procedure TestTMQTTFixedHeader.TestToByteStr; 398 | var 399 | byteValue: Word; 400 | str: string; 401 | begin 402 | byteValue := FMQTTFixedHeader.Flags; 403 | str := IntToBin(byteValue, 8); 404 | Assert( str = '00010000', 'Fixed Header Flags' ); 405 | end; 406 | 407 | { TestRLMethods } 408 | 409 | procedure TestUtilityMethods.SetUp; 410 | begin 411 | inherited; 412 | 413 | end; 414 | 415 | 416 | procedure TestUtilityMethods.TearDown; 417 | begin 418 | inherited; 419 | 420 | end; 421 | 422 | procedure TestUtilityMethods.TestIntToMSBLSB; 423 | var 424 | LengthBytes: TBytes; 425 | begin 426 | LengthBytes := TMQTTUtilities.IntToMSBLSB(4); 427 | Assert( LengthBytes[0] = 0, 'Zero' ); 428 | Assert( LengthBytes[1] = 4, 'Size' ); 429 | LengthBytes := TMQTTUtilities.IntToMSBLSB(8); 430 | Assert( LengthBytes[0] = 0, 'Zero' ); 431 | Assert( LengthBytes[1] = 8, 'Size' ); 432 | LengthBytes := TMQTTUtilities.IntToMSBLSB(20); 433 | Assert( LengthBytes[0] = 0, 'Zero' ); 434 | Assert( LengthBytes[1] = 20, 'Size' ); 435 | end; 436 | 437 | procedure TestUtilityMethods.TestToRLToBytes; 438 | var 439 | RLBytes: TBytes; 440 | begin 441 | RLBytes := TMQTTUtilities.RLIntToBytes(0); 442 | Assert( Length(RLBytes) = 1, 'Correct number of Bytes' ); 443 | Assert( RLBytes[0] = 0, 'Correct Value in byte 0' ); 444 | RLBytes := TMQTTUtilities.RLIntToBytes(126); 445 | Assert( Length(RLBytes) = 1, 'Correct number of Bytes' ); 446 | Assert( RLBytes[0] = 126, 'Correct Value in byte 0' ); 447 | RLBytes := TMQTTUtilities.RLIntToBytes(128); 448 | Assert( Length(RLBytes) = 2, 'Correct number of Bytes' ); 449 | Assert( RLBytes[0] = 128, 'Correct Value in byte 0' ); 450 | Assert( RLBytes[1] = 1, 'Correct Value in byte 1' ); 451 | RLBytes := TMQTTUtilities.RLIntToBytes(16383); 452 | Assert( Length(RLBytes) = 2, 'Correct number of Bytes' ); 453 | Assert( RLBytes[0] = 255, 'Correct Value in byte 0' ); 454 | Assert( RLBytes[1] = 127, 'Correct Value in byte 1' ); 455 | RLBytes := TMQTTUtilities.RLIntToBytes(16384); 456 | Assert( Length(RLBytes) = 3, 'Correct number of Bytes' ); 457 | Assert( RLBytes[0] = 128, 'Correct Value in byte 0' ); 458 | Assert( RLBytes[1] = 128, 'Correct Value in byte 1' ); 459 | Assert( RLBytes[2] = 1, 'Correct Value in byte 2' ); 460 | RLBytes := TMQTTUtilities.RLIntToBytes(268435455); 461 | Assert( Length(RLBytes) = 4, 'Correct number of Bytes' ); 462 | Assert( RLBytes[0] = 255, 'Correct Value in byte 0' ); 463 | Assert( RLBytes[1] = 255, 'Correct Value in byte 1' ); 464 | Assert( RLBytes[2] = 255, 'Correct Value in byte 2' ); 465 | Assert( RLBytes[3] = 127, 'Correct Value in byte 3' ); 466 | end; 467 | 468 | procedure TestUtilityMethods.TestUTF8EncodeToBytes; 469 | var 470 | str: TBytes; 471 | testStr: string; 472 | testStringSets: Array of string; 473 | i: integer; 474 | begin 475 | SetLength(testStringSets, 3); 476 | testStringSets[0] := 'test'; 477 | testStringSets[1] := 'test'; 478 | testStringSets[2] := 'test'; 479 | 480 | for testStr in testStringSets do 481 | begin 482 | str := TMQTTUtilities.UTF8EncodeToBytes(testStr); 483 | Assert( Length(str) = Length(testStr) + 2, 'Length of UTF8 Bytes' ); 484 | Assert( str[0] = 0, 'Correct Length Value in byte 0' ); 485 | Assert( str[1] = Length(testStr), 'Correct Length Value in byte 1' ); 486 | for I := 2 to Length(str) do 487 | begin 488 | Assert( testStr[i - 1] = Chr(str[i]), 'Correct String, Expected: ' + testStr[i - 1] + ' Got: ' + Chr(str[i])); 489 | end; 490 | end; 491 | end; 492 | 493 | initialization 494 | // Register any test cases with the test runner 495 | RegisterTest(TestTMQTTFixedHeader.Suite); 496 | RegisterTest(TestUtilityMethods.Suite); 497 | RegisterTest(TestTMQTTConnectVarHeader.Suite); 498 | RegisterTest(TestTMQTTPublishVarHeader.Suite); 499 | RegisterTest(TestTMQTTSubscribeVarHeader.Suite); 500 | RegisterTest(TestTMQTTUnsubscribeVarHeader.Suite); 501 | RegisterTest(TestTMQTTPayload.Suite); 502 | RegisterTest(TestTMQTTMessage.Suite); 503 | end. 504 | 505 | -------------------------------------------------------------------------------- /TMQTT/MQTT.pas: -------------------------------------------------------------------------------- 1 | unit MQTT; 2 | 3 | interface 4 | 5 | uses 6 | SysUtils, 7 | Types, 8 | Classes, 9 | ExtCtrls, 10 | Generics.Collections, 11 | SyncObjs, 12 | 13 | blcksock, 14 | 15 | MQTTHeaders, 16 | MQTTReadThread; 17 | 18 | type 19 | {$IF not declared(TBytes)} 20 | TBytes = array of Byte; 21 | {$IFEND} 22 | 23 | TMQTT = class 24 | private 25 | { Private Declarations } 26 | FClientID: AnsiString; 27 | FHostname: string; 28 | FPort: Integer; 29 | FMessageID: Integer; 30 | FisConnected: boolean; 31 | FRecvThread: TMQTTReadThread; 32 | FCSSock: TCriticalSection; 33 | 34 | FWillMsg: AnsiString; 35 | FWillTopic: AnsiString; 36 | FUsername: AnsiString; 37 | FPassword: AnsiString; 38 | 39 | FSocket: TTCPBlockSocket; 40 | FKeepAliveTimer: TTimer; 41 | 42 | // Event Fields 43 | FConnAckEvent: TConnAckEvent; 44 | FPublishEvent: TPublishEvent; 45 | FPingRespEvent: TPingRespEvent; 46 | FPingReqEvent: TPingReqEvent; 47 | FSubAckEvent: TSubAckEvent; 48 | FUnSubAckEvent: TUnSubAckEvent; 49 | FPubAckEvent: TPubAckEvent; 50 | FPubRelEvent: TPubRelEvent; 51 | FPubRecEvent: TPubRecEvent; 52 | FPubCompEvent: TPubCompEvent; 53 | 54 | function WriteData(AData: TBytes): boolean; 55 | function hasWill: boolean; 56 | function getNextMessageId: integer; 57 | function createAndResumeRecvThread(Socket: TTCPBlockSocket): boolean; 58 | 59 | // TMQTTMessage Factory Methods. 60 | function ConnectMessage: TMQTTMessage; 61 | function DisconnectMessage: TMQTTMessage; 62 | function PublishMessage: TMQTTMessage; 63 | function PingReqMessage: TMQTTMessage; 64 | function SubscribeMessage: TMQTTMessage; 65 | function UnsubscribeMessage: TMQTTMessage; 66 | 67 | // Our Keep Alive Ping Timer Event 68 | procedure KeepAliveTimer_Event(sender: TObject); 69 | 70 | // Recv Thread Event Handling Procedures. 71 | procedure GotConnAck(Sender: TObject; ReturnCode: integer); 72 | procedure GotPingResp(Sender: TObject); 73 | procedure GotSubAck(Sender: TObject; MessageID: integer; GrantedQoS: Array of integer); 74 | procedure GotUnSubAck(Sender: TObject; MessageID: integer); 75 | procedure GotPub(Sender: TObject; topic, payload: Ansistring); 76 | procedure GotPubAck(Sender: TObject; MessageID: integer); 77 | procedure GotPubRec(Sender: TObject; MessageID: integer); 78 | procedure GotPubRel(Sender: TObject; MessageID: integer); 79 | procedure GotPubComp(Sender: TObject; MessageID: integer); 80 | 81 | public 82 | { Public Declarations } 83 | 84 | function Connect: boolean; 85 | function Disconnect: boolean; 86 | function Publish(Topic: Ansistring; sPayload: Ansistring): boolean; overload; 87 | function Publish(Topic: Ansistring; sPayload: Ansistring; Retain: boolean): boolean; overload; 88 | function Publish(Topic: Ansistring; sPayload: Ansistring; Retain: boolean; QoS: integer): boolean; overload; 89 | function Subscribe(Topic: Ansistring; RequestQoS: integer): integer; overload; 90 | function Subscribe(Topics: TDictionary): integer; overload; 91 | function Unsubscribe(Topic: Ansistring): integer; overload ; 92 | function Unsubscribe(Topics: TStringList): integer; overload; 93 | function PingReq: boolean; 94 | constructor Create(hostName: string; port: integer); 95 | destructor Destroy; override; 96 | 97 | property WillTopic: AnsiString read FWillTopic write FWillTopic; 98 | property WillMsg: AnsiString read FWillMsg write FWillMsg; 99 | 100 | property Username: AnsiString read FUsername write FUsername; 101 | property Password: AnsiString read FPassword write FPassword; 102 | // Client ID is our Client Identifier. 103 | property ClientID : AnsiString read FClientID write FClientID; 104 | property isConnected: boolean read FisConnected; 105 | 106 | // Event Handlers 107 | property OnConnAck : TConnAckEvent read FConnAckEvent write FConnAckEvent; 108 | property OnPublish : TPublishEvent read FPublishEvent write FPublishEvent; 109 | property OnPingResp : TPingRespEvent read FPingRespEvent write FPingRespEvent; 110 | property OnPingReq : TPingRespEvent read FPingRespEvent write FPingRespEvent; 111 | property OnSubAck : TSubAckEvent read FSubAckEvent write FSubAckEvent; 112 | property OnUnSubAck : TUnSubAckEvent read FUnSubAckEvent write FUnSubAckEvent; 113 | property OnPubAck : TUnSubAckEvent read FUnSubAckEvent write FUnSubAckEvent; 114 | property OnPubRec : TUnSubAckEvent read FUnSubAckEvent write FUnSubAckEvent; 115 | property OnPubRel : TUnSubAckEvent read FUnSubAckEvent write FUnSubAckEvent; 116 | property OnPubComp : TUnSubAckEvent read FUnSubAckEvent write FUnSubAckEvent; 117 | end; 118 | 119 | implementation 120 | 121 | 122 | { TMQTTClient } 123 | 124 | procedure TMQTT.GotConnAck(Sender: TObject; ReturnCode: integer); 125 | begin 126 | if Assigned(FConnAckEvent) then OnConnAck(Self, ReturnCode); 127 | end; 128 | 129 | function TMQTT.Connect: boolean; 130 | var 131 | Msg: TMQTTMessage; 132 | begin 133 | // Create socket and connect. 134 | FSocket := TTCPBlockSocket.Create; 135 | try 136 | FSocket.Connect(Self.FHostname, IntToStr(Self.FPort)); 137 | FisConnected := true; 138 | except 139 | // If we encounter an exception upon connection then reraise it, free the socket 140 | // and reset our isConnected flag. 141 | on E: Exception do 142 | begin 143 | raise; 144 | FisConnected := false; 145 | FSocket.Free; 146 | end; 147 | end; 148 | 149 | if FisConnected then 150 | begin 151 | Msg := ConnectMessage; 152 | try 153 | Msg.Payload.Contents.Add(Self.FClientID); 154 | (Msg.VariableHeader as TMQTTConnectVarHeader).WillFlag := ord(hasWill); 155 | if hasWill then 156 | begin 157 | Msg.Payload.Contents.Add(Self.FWillTopic); 158 | Msg.Payload.Contents.Add(Self.FWillMsg); 159 | end; 160 | 161 | if ((Length(FUsername) > 1) and (Length(FPassword) > 1)) then 162 | begin 163 | Msg.Payload.Contents.Add(FUsername); 164 | Msg.Payload.Contents.Add(FPassword); 165 | end; 166 | 167 | 168 | if WriteData(Msg.ToBytes) then Result := true else Result := false; 169 | // Start our Receive thread. 170 | if (Result and createAndResumeRecvThread(FSocket)) then 171 | begin 172 | // Use the KeepAlive that we just sent to determine our ping timer. 173 | FKeepAliveTimer.Interval := (Round((Msg.VariableHeader as TMQTTConnectVarHeader).KeepAlive * 0.80)) * 1000; 174 | FKeepAliveTimer.Enabled := true; 175 | end; 176 | 177 | finally 178 | Msg.Free; 179 | end; 180 | end; 181 | end; 182 | 183 | function TMQTT.ConnectMessage: TMQTTMessage; 184 | begin 185 | Result := TMQTTMessage.Create; 186 | Result.VariableHeader := TMQTTConnectVarHeader.Create; 187 | Result.Payload := TMQTTPayload.Create; 188 | Result.FixedHeader.MessageType := Ord(TMQTTMessageType.CONNECT); 189 | Result.FixedHeader.Retain := 0; 190 | Result.FixedHeader.QoSLevel := 0; 191 | Result.FixedHeader.Duplicate := 0; 192 | end; 193 | 194 | constructor TMQTT.Create(hostName: string; port: integer); 195 | begin 196 | inherited Create; 197 | 198 | Self.FisConnected := false; 199 | Self.FHostname := Hostname; 200 | Self.FPort := Port; 201 | Self.FMessageID := 1; 202 | // Randomise and create a random client id. 203 | Randomize; 204 | Self.FClientID := 'TMQTT' + IntToStr(Random(1000) + 1); 205 | FCSSock := TCriticalSection.Create; 206 | 207 | // Create the timer responsible for pinging. 208 | FKeepAliveTimer := TTimer.Create(nil); 209 | FKeepAliveTimer.Enabled := false; 210 | FKeepAliveTimer.OnTimer := KeepAliveTimer_Event; 211 | end; 212 | 213 | function TMQTT.createAndResumeRecvThread(Socket: TTCPBlockSocket): boolean; 214 | begin 215 | Result := false; 216 | try 217 | FRecvThread := TMQTTReadThread.Create(Socket, FCSSock); 218 | 219 | { Todo: Assign Event Handlers here. } 220 | FRecvThread.OnConnAck := Self.GotConnAck; 221 | FRecvThread.OnPublish := Self.GotPub; 222 | FRecvThread.OnPingResp := Self.GotPingResp; 223 | FRecvThread.OnSubAck := Self.GotSubAck; 224 | FRecvThread.OnPubAck := Self.GotPubAck; 225 | Result := true; 226 | except 227 | Result := false; 228 | end; 229 | end; 230 | 231 | destructor TMQTT.Destroy; 232 | begin 233 | if Assigned(FSocket) then 234 | begin 235 | Disconnect; 236 | end; 237 | if Assigned(FKeepAliveTimer) then 238 | begin 239 | FreeAndNil(FKeepAliveTimer); 240 | end; 241 | if Assigned(FRecvThread) then 242 | begin 243 | FreeAndNil(FRecvThread); 244 | end; 245 | if Assigned(FCSSock) then 246 | begin 247 | FreeAndNil(FCSSock); 248 | end; 249 | inherited; 250 | end; 251 | 252 | function TMQTT.Disconnect: boolean; 253 | var 254 | Msg: TMQTTMessage; 255 | begin 256 | Result := false; 257 | if isConnected then 258 | begin 259 | FKeepAliveTimer.Enabled := false; 260 | Msg := DisconnectMessage; 261 | if WriteData(Msg.ToBytes) then Result := true else Result := false; 262 | Msg.Free; 263 | // Terminate our socket receive thread. 264 | FRecvThread.Terminate; 265 | FRecvThread.WaitFor; 266 | 267 | // Close our socket. 268 | FSocket.CloseSocket; 269 | FisConnected := False; 270 | 271 | // Free everything. 272 | if Assigned(FRecvThread) then FreeAndNil(FRecvThread); 273 | if Assigned(FSocket) then FreeAndNil(FSocket); 274 | end; 275 | end; 276 | 277 | function TMQTT.DisconnectMessage: TMQTTMessage; 278 | begin 279 | Result := TMQTTMessage.Create; 280 | Result.FixedHeader.MessageType := Ord(TMQTTMessageType.DISCONNECT); 281 | end; 282 | 283 | function TMQTT.getNextMessageId: integer; 284 | begin 285 | // If we've reached the upper bounds of our 16 bit unsigned message Id then 286 | // start again. The spec says it typically does but is not required to Inc(MsgId,1). 287 | if (FMessageID = 65535) then 288 | begin 289 | FMessageID := 1; 290 | end; 291 | 292 | // Return our current message Id 293 | Result := FMessageID; 294 | // Increment message Id 295 | Inc(FMessageID); 296 | end; 297 | 298 | function TMQTT.hasWill: boolean; 299 | begin 300 | if ((Length(FWillTopic) < 1) and (Length(FWillMsg) < 1)) then 301 | begin 302 | Result := false; 303 | end else Result := true; 304 | end; 305 | 306 | procedure TMQTT.KeepAliveTimer_Event(sender: TObject); 307 | begin 308 | if Self.isConnected then 309 | begin 310 | PingReq; 311 | end; 312 | end; 313 | 314 | function TMQTT.PingReq: boolean; 315 | var 316 | Msg: TMQTTMessage; 317 | begin 318 | Result := false; 319 | if isConnected then 320 | begin 321 | Msg := PingReqMessage; 322 | if WriteData(Msg.ToBytes) then Result := true else Result := false; 323 | Msg.Free; 324 | end; 325 | end; 326 | 327 | function TMQTT.PingReqMessage: TMQTTMessage; 328 | begin 329 | Result := TMQTTMessage.Create; 330 | Result.FixedHeader.MessageType := Ord(TMQTTMessageType.PINGREQ); 331 | end; 332 | 333 | procedure TMQTT.GotPingResp(Sender: TObject); 334 | begin 335 | if Assigned(FPingRespEvent) then OnPingResp(Self); 336 | end; 337 | 338 | function TMQTT.Publish(Topic, sPayload: Ansistring; Retain: boolean): boolean; 339 | begin 340 | Result := Publish(Topic, sPayload, Retain, 0); 341 | end; 342 | 343 | function TMQTT.Publish(Topic, sPayload: Ansistring): boolean; 344 | begin 345 | Result := Publish(Topic, sPayload, false, 0); 346 | end; 347 | 348 | function TMQTT.Publish(Topic, sPayload: Ansistring; Retain: boolean; 349 | QoS: integer): boolean; 350 | var 351 | Msg: TMQTTMessage; 352 | begin 353 | if ((QoS > -1) and (QoS <= 3)) then 354 | begin 355 | if isConnected then 356 | begin 357 | Msg := PublishMessage; 358 | Msg.FixedHeader.QoSLevel := QoS; 359 | (Msg.VariableHeader as TMQTTPublishVarHeader).QoSLevel := QoS; 360 | (Msg.VariableHeader as TMQTTPublishVarHeader).Topic := Topic; 361 | if (QoS > 0) then 362 | begin 363 | (Msg.VariableHeader as TMQTTPublishVarHeader).MessageID := getNextMessageId; 364 | end; 365 | Msg.Payload.Contents.Add(sPayload); 366 | Msg.Payload.PublishMessage := true; 367 | if WriteData(Msg.ToBytes) then Result := true else Result := false; 368 | Msg.Free; 369 | end; 370 | end 371 | else 372 | raise EInvalidOp.Create('QoS level can only be equal to or between 0 and 3.'); 373 | end; 374 | 375 | function TMQTT.PublishMessage: TMQTTMessage; 376 | begin 377 | Result := TMQTTMessage.Create; 378 | Result.FixedHeader.MessageType := Ord(TMQTTMessageType.PUBLISH); 379 | Result.VariableHeader := TMQTTPublishVarHeader.Create(0); 380 | Result.Payload := TMQTTPayload.Create; 381 | end; 382 | 383 | procedure TMQTT.GotPubRec(Sender: TObject; MessageID: integer); 384 | begin 385 | if Assigned(FPubRecEvent) then OnPubRec(Self, MessageID); 386 | end; 387 | 388 | procedure TMQTT.GotPubRel(Sender: TObject; MessageID: integer); 389 | begin 390 | if Assigned(FPubRelEvent) then OnPubRel(Self, MessageID); 391 | end; 392 | 393 | function TMQTT.Subscribe(Topic: Ansistring; RequestQoS: integer): integer; 394 | var 395 | dTopics: TDictionary; 396 | begin 397 | dTopics := TDictionary.Create; 398 | dTopics.Add(Topic, RequestQoS); 399 | Result := Subscribe(dTopics); 400 | dTopics.Free; 401 | end; 402 | 403 | procedure TMQTT.GotSubAck(Sender: TObject; MessageID: integer; 404 | GrantedQoS: array of integer); 405 | begin 406 | if Assigned(FSubAckEvent) then OnSubAck(Self, MessageID, GrantedQoS); 407 | end; 408 | 409 | function TMQTT.Subscribe(Topics: TDictionary): integer; 410 | var 411 | Msg: TMQTTMessage; 412 | MsgId: Integer; 413 | sTopic: AnsiString; 414 | data: TBytes; 415 | begin 416 | Result := -1; 417 | if isConnected then 418 | begin 419 | Msg := SubscribeMessage; 420 | MsgId := getNextMessageId; 421 | (Msg.VariableHeader as TMQTTSubscribeVarHeader).MessageID := MsgId; 422 | 423 | for sTopic in Topics.Keys do 424 | begin 425 | Msg.Payload.Contents.Add(sTopic); 426 | Msg.Payload.Contents.Add(IntToStr(Topics.Items[sTopic])) 427 | end; 428 | // the subscribe message contains integer literals not encoded as strings. 429 | Msg.Payload.ContainsIntLiterals := true; 430 | 431 | data := Msg.ToBytes; 432 | if WriteData(data) then Result := MsgId; 433 | 434 | Msg.Free; 435 | end; 436 | end; 437 | 438 | function TMQTT.SubscribeMessage: TMQTTMessage; 439 | begin 440 | Result := TMQTTMessage.Create; 441 | Result.FixedHeader.MessageType := Ord(TMQTTMessageType.SUBSCRIBE); 442 | Result.FixedHeader.QoSLevel := 0; 443 | Result.VariableHeader := TMQTTSubscribeVarHeader.Create; 444 | Result.Payload := TMQTTPayload.Create; 445 | end; 446 | 447 | function TMQTT.Unsubscribe(Topic: Ansistring): integer; 448 | var 449 | slTopics: TStringList; 450 | begin 451 | slTopics := TStringList.Create; 452 | slTopics.Add(Topic); 453 | Result := Unsubscribe(slTopics); 454 | slTopics.Free; 455 | end; 456 | 457 | procedure TMQTT.GotUnSubAck(Sender: TObject; MessageID: integer); 458 | begin 459 | if Assigned(FUnSubAckEvent) then OnUnSubAck(Self, MessageID); 460 | end; 461 | 462 | function TMQTT.Unsubscribe(Topics: TStringList): integer; 463 | var 464 | Msg: TMQTTMessage; 465 | MsgId: integer; 466 | sTopic: AnsiString; 467 | begin 468 | Result := -1; 469 | if isConnected then 470 | begin 471 | Msg := UnsubscribeMessage; 472 | MsgId := getNextMessageId; 473 | (Msg.VariableHeader as TMQTTSubscribeVarHeader).MessageID := MsgId; 474 | 475 | Msg.Payload.Contents.AddStrings(Topics); 476 | 477 | if WriteData(Msg.ToBytes) then Result := MsgId; 478 | 479 | Msg.Free; 480 | end; 481 | end; 482 | 483 | function TMQTT.UnsubscribeMessage: TMQTTMessage; 484 | var 485 | Msg: TMQTTMessage; 486 | begin 487 | Result := TMQTTMessage.Create; 488 | Result.FixedHeader.MessageType := Ord(TMQTTMessageType.UNSUBSCRIBE); 489 | Result.FixedHeader.QoSLevel := 1; 490 | Result.VariableHeader := TMQTTUnsubscribeVarHeader.Create; 491 | Result.Payload := TMQTTPayload.Create; 492 | end; 493 | 494 | function TMQTT.WriteData(AData: TBytes): boolean; 495 | var 496 | sentData: integer; 497 | attemptsToWrite: integer; 498 | begin 499 | Result := False; 500 | sentData := 0; 501 | attemptsToWrite := 1; 502 | if isConnected then 503 | begin 504 | repeat 505 | FCSSock.Acquire; 506 | try 507 | if FSocket.CanWrite(500 * attemptsToWrite) then 508 | begin 509 | sentData := sentData + FSocket.SendBuffer(Pointer(Copy(AData, sentData - 1, Length(AData) + 1)), Length(AData) - sentData); 510 | Inc(attemptsToWrite); 511 | end; 512 | finally 513 | FCSSock.Release; 514 | end; 515 | until ((attemptsToWrite = 3) or (sentData = Length(AData))); 516 | if sentData = Length(AData) then 517 | begin 518 | Result := True; 519 | FisConnected := true; 520 | end 521 | else 522 | begin 523 | Result := False; 524 | FisConnected := false; 525 | raise Exception.Create('Error Writing to Socket, it appears to be disconnected'); 526 | end; 527 | end; 528 | end; 529 | 530 | 531 | procedure TMQTT.GotPub(Sender: TObject; topic, payload: Ansistring); 532 | begin 533 | if Assigned(FPublishEvent) then OnPublish(Self, topic, payload); 534 | end; 535 | 536 | procedure TMQTT.GotPubAck(Sender: TObject; MessageID: integer); 537 | begin 538 | if Assigned(FPubAckEvent) then OnPubAck(Self, MessageID); 539 | end; 540 | 541 | procedure TMQTT.GotPubComp(Sender: TObject; MessageID: integer); 542 | begin 543 | if Assigned(FPubCompEvent) then OnPubComp(Self, MessageID); 544 | end; 545 | 546 | end. 547 | -------------------------------------------------------------------------------- /TMQTT/MQTTHeaders.pas: -------------------------------------------------------------------------------- 1 | unit MQTTHeaders; 2 | 3 | interface 4 | 5 | uses 6 | SysUtils, 7 | Types, 8 | Classes; 9 | 10 | type 11 | 12 | TMQTTMessageType = ( 13 | Reserved0, //0 Reserved 14 | CONNECT, // 1 Client request to connect to Broker 15 | CONNACK, // 2 Connect Acknowledgment 16 | PUBLISH, // 3 Publish message 17 | PUBACK, // 4 Publish Acknowledgment 18 | PUBREC, // 5 Publish Received (assured delivery part 1) 19 | PUBREL, // 6 Publish Release (assured delivery part 2) 20 | PUBCOMP, // 7 Publish Complete (assured delivery part 3) 21 | SUBSCRIBE, // 8 Client Subscribe request 22 | SUBACK, // 9 Subscribe Acknowledgment 23 | UNSUBSCRIBE, // 10 Client Unsubscribe request 24 | UNSUBACK, // 11 Unsubscribe Acknowledgment 25 | PINGREQ, // 12 PING Request 26 | PINGRESP, // 13 PING Response 27 | DISCONNECT, // 14 Client is Disconnecting 28 | Reserved15 // 15 29 | ); 30 | 31 | TMQTTRecvState = ( 32 | FixedHeaderByte, 33 | RemainingLength, 34 | Data 35 | ); 36 | 37 | { 38 | bit 7 6 5 4 3 2 1 0 39 | byte 1 Message Type DUP flag QoS level RETAIN 40 | byte 2 Remaining Length 41 | } 42 | TMQTTFixedHeader = packed record 43 | private 44 | function GetBits(const aIndex: Integer): Integer; 45 | procedure SetBits(const aIndex: Integer; const aValue: Integer); 46 | public 47 | Flags: Byte; 48 | property Retain: Integer index $0001 read GetBits write SetBits; // 1 bits at offset 0 49 | property QoSLevel: Integer index $0102 read GetBits write SetBits; // 2 bits at offset 1 50 | property Duplicate: Integer index $0301 read GetBits write SetBits; // 1 bits at offset 3 51 | property MessageType: Integer index $0404 read GetBits write SetBits; // 4 bits at offset 4 52 | end; 53 | 54 | { 55 | Description 7 6 5 4 3 2 1 0 56 | Connect Flags 57 | byte 10 1 1 0 0 1 1 1 x 58 | Will RETAIN (0) 59 | Will QoS (01) 60 | Will flag (1) 61 | Clean Start (1) 62 | Username Flag (1) 63 | Password Flag (1) 64 | } 65 | TMQTTConnectFlags = packed record 66 | private 67 | function GetBits(const aIndex: Integer): Integer; 68 | procedure SetBits(const aIndex: Integer; const aValue: Integer); 69 | public 70 | Flags: Byte; 71 | property CleanStart: Integer index $0101 read GetBits write SetBits; // 1 bit at offset 1 72 | property WillFlag: Integer index $0201 read GetBits write SetBits; // 1 bit at offset 2 73 | property WillQoS: Integer index $0302 read GetBits write SetBits; // 2 bits at offset 3 74 | property WillRetain: Integer index $0501 read GetBits write SetBits; // 1 bit at offset 5 75 | property Password: Integer index $0601 read GetBits write SetBits; // 1 bit at offset 6 76 | property UserName: Integer index $0701 read GetBits write SetBits; // 1 bit at offset 7 77 | end; 78 | 79 | TConnAckEvent = procedure (Sender: TObject; ReturnCode: integer) of object; 80 | TPublishEvent = procedure (Sender: TObject; topic, payload: Ansistring) of object; 81 | TPingRespEvent = procedure (Sender: TObject) of object; 82 | TPingReqEvent = procedure (Sender: TObject) of object; 83 | TSubAckEvent = procedure (Sender: TObject; MessageID: integer; GrantedQoS: Array of integer) of object; 84 | TUnSubAckEvent = procedure (Sender: TObject; MessageID: integer) of object; 85 | TPubAckEvent = procedure (Sender: TObject; MessageID: integer) of object; 86 | TPubRelEvent = procedure (Sender: TObject; MessageID: integer) of object; 87 | TPubRecEvent = procedure (Sender: TObject; MessageID: integer) of object; 88 | TPubCompEvent = procedure (Sender: TObject; MessageID: integer) of object; 89 | 90 | TMQTTVariableHeader = class 91 | private 92 | FBytes: TBytes; 93 | protected 94 | procedure AddField(AByte: Byte); overload; 95 | procedure AddField(ABytes: TBytes); overload; 96 | procedure ClearField; 97 | public 98 | constructor Create; 99 | function ToBytes: TBytes; virtual; 100 | end; 101 | 102 | TMQTTConnectVarHeader = class(TMQTTVariableHeader) 103 | const 104 | PROTOCOL_ID = 'MQIsdp'; 105 | PROTOCOL_VER = 3; 106 | private 107 | FConnectFlags: TMQTTConnectFlags; 108 | FKeepAlive: integer; 109 | function rebuildHeader: boolean; 110 | procedure setupDefaultValues; 111 | function get_CleanStart: integer; 112 | function get_QoSLevel: integer; 113 | function get_Retain: integer; 114 | procedure set_CleanStart(const Value: integer); 115 | procedure set_QoSLevel(const Value: integer); 116 | procedure set_Retain(const Value: integer); 117 | function get_WillFlag: integer; 118 | procedure set_WillFlag(const Value: integer); 119 | function get_Username: integer; 120 | procedure set_Username(const Value: integer); 121 | function get_Password: integer; 122 | procedure set_Password(const Value: integer); 123 | public 124 | constructor Create(AKeepAlive: integer); overload; 125 | constructor Create; overload; 126 | constructor Create(ACleanStart: boolean); overload; 127 | property KeepAlive: integer read FKeepAlive write FKeepAlive; 128 | property CleanStart: integer read get_CleanStart write set_CleanStart; 129 | property QoSLevel: integer read get_QoSLevel write set_QoSLevel; 130 | property Retain: integer read get_Retain write set_Retain; 131 | property Username: integer read get_Username write set_Username; 132 | property Password: integer read get_Password write set_Password; 133 | property WillFlag: integer read get_WillFlag write set_WillFlag; 134 | function ToBytes: TBytes; override; 135 | end; 136 | 137 | TMQTTPublishVarHeader = class(TMQTTVariableHeader) 138 | private 139 | FTopic: AnsiString; 140 | FQoSLevel: integer; 141 | FMessageID: integer; 142 | function get_MessageID: integer; 143 | function get_QoSLevel: integer; 144 | procedure set_MessageID(const Value: integer); 145 | procedure set_QoSLevel(const Value: integer); 146 | function get_Topic: AnsiString; 147 | procedure set_Topic(const Value: AnsiString); 148 | procedure rebuildHeader; 149 | public 150 | constructor Create(QoSLevel: integer); overload; 151 | property MessageID: integer read get_MessageID write set_MessageID; 152 | property QoSLevel: integer read get_QoSLevel write set_QoSLevel; 153 | property Topic: AnsiString read get_Topic write set_Topic; 154 | function ToBytes: TBytes; override; 155 | end; 156 | 157 | TMQTTSubscribeVarHeader = class(TMQTTVariableHeader) 158 | private 159 | FMessageID: integer; 160 | function get_MessageID: integer; 161 | procedure set_MessageID(const Value: integer); 162 | public 163 | constructor Create(MessageId: integer); overload; 164 | property MessageID: integer read get_MessageID write set_MessageID; 165 | function ToBytes: TBytes; override; 166 | end; 167 | 168 | TMQTTUnsubscribeVarHeader = class(TMQTTVariableHeader) 169 | private 170 | FMessageID: integer; 171 | function get_MessageID: integer; 172 | procedure set_MessageID(const Value: integer); 173 | public 174 | constructor Create(MessageId: integer); overload; 175 | property MessageID: integer read get_MessageID write set_MessageID; 176 | function ToBytes: TBytes; override; 177 | end; 178 | 179 | TMQTTPayload = class 180 | private 181 | FContents: TStringList; 182 | FContainsIntLiterals: boolean; 183 | FPublishMessage: boolean; 184 | public 185 | constructor Create; 186 | destructor Destroy; override; 187 | function ToBytes: TBytes; overload; 188 | function ToBytes(WithIntegerLiterals: boolean): TBytes; overload; 189 | property Contents: TStringList read FContents; 190 | property ContainsIntLiterals: boolean read FContainsIntLiterals write FContainsIntLiterals; 191 | property PublishMessage: boolean read FPublishMessage write FPublishMessage; 192 | end; 193 | 194 | TMQTTMessage = class 195 | private 196 | FRemainingLength: Integer; 197 | public 198 | FixedHeader: TMQTTFixedHeader; 199 | VariableHeader: TMQTTVariableHeader; 200 | Payload: TMQTTPayload; 201 | constructor Create; 202 | destructor Destroy; override; 203 | function ToBytes: TBytes; 204 | property RemainingLength: integer read FRemainingLength; 205 | end; 206 | 207 | TMQTTUtilities = class 208 | public 209 | class function UTF8EncodeToBytes(AStrToEncode: AnsiString): TBytes; 210 | class function UTF8EncodeToBytesNoLength(AStrToEncode: AnsiString): TBytes; 211 | class function RLIntToBytes(ARlInt: integer): TBytes; 212 | class function IntToMSBLSB(ANumber: Word): TBytes; 213 | end; 214 | 215 | implementation 216 | 217 | function GetDWordBits(const Bits: Byte; const aIndex: Integer): Integer; 218 | begin 219 | Result := (Bits shr (aIndex shr 8)) // offset 220 | and ((1 shl Byte(aIndex)) - 1); // mask 221 | end; 222 | 223 | procedure SetDWordBits(var Bits: Byte; const aIndex: Integer; const aValue: Integer); 224 | var 225 | Offset: Byte; 226 | Mask: Integer; 227 | begin 228 | Mask := ((1 shl Byte(aIndex)) - 1); 229 | Assert(aValue <= Mask); 230 | 231 | Offset := aIndex shr 8; 232 | Bits := (Bits and (not (Mask shl Offset))) 233 | or DWORD(aValue shl Offset); 234 | end; 235 | 236 | class function TMQTTUtilities.IntToMSBLSB(ANumber: Word): TBytes; 237 | begin 238 | SetLength(Result, 2); 239 | Result[0] := ANumber div 256; 240 | Result[1] := ANumber mod 256; 241 | end; 242 | 243 | { MSBLSBToInt is in the MQTTRecvThread unit } 244 | 245 | class function TMQTTUtilities.UTF8EncodeToBytes(AStrToEncode: AnsiString): TBytes; 246 | var 247 | i: integer; 248 | begin 249 | { This is a UTF-8 hack to give 2 Bytes of Length MSB-LSB followed by a Single-byte 250 | per character String. } 251 | SetLength(Result, Length(AStrToEncode) + 2); 252 | 253 | Result[0] := Length(AStrToEncode) div 256; 254 | Result[1] := Length(AStrToEncode) mod 256; 255 | 256 | for I := 0 to Length(AStrToEncode) - 1 do 257 | begin 258 | Result[i + 2] := Ord(AStrToEncode[i + 1]); 259 | end; 260 | end; 261 | 262 | class function TMQTTUtilities.UTF8EncodeToBytesNoLength(AStrToEncode: AnsiString): TBytes; 263 | var 264 | i: integer; 265 | begin 266 | SetLength(Result, Length(AStrToEncode)); 267 | for i := 0 to Length(AStrToEncode) - 1 do 268 | begin 269 | Result[i] := Ord(AStrToEncode[i + 1]); 270 | end; 271 | end; 272 | 273 | procedure AppendToByteArray(ASourceBytes: TBytes; var ATargetBytes: TBytes); overload; 274 | var 275 | iUpperBnd: integer; 276 | begin 277 | if Length(ASourceBytes) > 0 then 278 | begin 279 | iUpperBnd := Length(ATargetBytes); 280 | SetLength(ATargetBytes, iUpperBnd + Length(ASourceBytes)); 281 | Move(ASourceBytes[0], ATargetBytes[iUpperBnd], Length(ASourceBytes)); 282 | end; 283 | end; 284 | 285 | procedure AppendToByteArray(ASourceByte: Byte; var ATargetBytes: TBytes); overload; 286 | var 287 | iUpperBnd: integer; 288 | begin 289 | iUpperBnd := Length(ATargetBytes); 290 | SetLength(ATargetBytes, iUpperBnd + 1); 291 | Move(ASourceByte, ATargetBytes[iUpperBnd], 1); 292 | end; 293 | 294 | class function TMQTTUtilities.RLIntToBytes(ARlInt: integer): TBytes; 295 | var 296 | byteindex: integer; 297 | digit: integer; 298 | begin 299 | SetLength(Result, 1); 300 | byteindex := 0; 301 | while (ARlInt > 0) do 302 | begin 303 | digit := ARlInt mod 128; 304 | ARlInt := ARlInt div 128; 305 | if ARlInt > 0 then 306 | begin 307 | digit := digit or $80; 308 | end; 309 | Result[byteindex] := digit; 310 | if ARlInt > 0 then 311 | begin 312 | inc(byteindex); 313 | SetLength(Result, Length(Result) + 1); 314 | end; 315 | end; 316 | end; 317 | 318 | { TMQTTFixedHeader } 319 | 320 | function TMQTTFixedHeader.GetBits(const aIndex: Integer): Integer; 321 | begin 322 | Result := GetDWordBits(Flags, aIndex); 323 | end; 324 | 325 | procedure TMQTTFixedHeader.SetBits(const aIndex, aValue: Integer); 326 | begin 327 | SetDWordBits(Flags, aIndex, aValue); 328 | end; 329 | 330 | { TMQTTMessage } 331 | 332 | 333 | { TMQTTVariableHeader } 334 | 335 | procedure TMQTTVariableHeader.AddField(AByte: Byte); 336 | var 337 | DestUpperBnd: Integer; 338 | begin 339 | DestUpperBnd := Length(FBytes); 340 | SetLength(FBytes, DestUpperBnd + SizeOf(AByte)); 341 | Move(AByte, FBytes[DestUpperBnd], SizeOf(AByte)); 342 | end; 343 | 344 | procedure TMQTTVariableHeader.AddField(ABytes: TBytes); 345 | var 346 | DestUpperBnd: Integer; 347 | begin 348 | DestUpperBnd := Length(FBytes); 349 | SetLength(FBytes, DestUpperBnd + Length(ABytes)); 350 | Move(ABytes[0], FBytes[DestUpperBnd], Length(ABytes)); 351 | end; 352 | 353 | procedure TMQTTVariableHeader.ClearField; 354 | begin 355 | SetLength(FBytes, 0); 356 | end; 357 | 358 | constructor TMQTTVariableHeader.Create; 359 | begin 360 | end; 361 | 362 | function TMQTTVariableHeader.ToBytes: TBytes; 363 | begin 364 | Result := FBytes; 365 | end; 366 | 367 | { TMQTTConnectVarHeader } 368 | 369 | constructor TMQTTConnectVarHeader.Create(ACleanStart: boolean); 370 | begin 371 | inherited Create; 372 | setupDefaultValues; 373 | Self.FConnectFlags.CleanStart := Ord(ACleanStart); 374 | end; 375 | 376 | function TMQTTConnectVarHeader.get_CleanStart: integer; 377 | begin 378 | Result := Self.FConnectFlags.CleanStart; 379 | end; 380 | 381 | function TMQTTConnectVarHeader.get_Password: integer; 382 | begin 383 | Result := Self.FConnectFlags.Password; 384 | end; 385 | 386 | function TMQTTConnectVarHeader.get_QoSLevel: integer; 387 | begin 388 | Result := Self.FConnectFlags.WillQoS; 389 | end; 390 | 391 | function TMQTTConnectVarHeader.get_Retain: integer; 392 | begin 393 | Result := Self.FConnectFlags.WillRetain; 394 | end; 395 | 396 | function TMQTTConnectVarHeader.get_Username: integer; 397 | begin 398 | Result := Self.FConnectFlags.UserName; 399 | end; 400 | 401 | function TMQTTConnectVarHeader.get_WillFlag: integer; 402 | begin 403 | Result := Self.FConnectFlags.WillFlag; 404 | end; 405 | 406 | constructor TMQTTConnectVarHeader.Create(AKeepAlive: integer); 407 | begin 408 | inherited Create; 409 | setupDefaultValues; 410 | Self.FKeepAlive := AKeepAlive; 411 | end; 412 | 413 | constructor TMQTTConnectVarHeader.Create; 414 | begin 415 | inherited Create; 416 | setupDefaultValues; 417 | end; 418 | 419 | function TMQTTConnectVarHeader.rebuildHeader: boolean; 420 | begin 421 | try 422 | ClearField; 423 | AddField(TMQTTUtilities.UTF8EncodeToBytes(Self.PROTOCOL_ID)); 424 | AddField(Byte(Self.PROTOCOL_VER)); 425 | AddField(FConnectFlags.Flags); 426 | AddField(TMQTTUtilities.IntToMSBLSB(FKeepAlive)); 427 | except 428 | Result := false; 429 | end; 430 | Result := true; 431 | end; 432 | 433 | procedure TMQTTConnectVarHeader.setupDefaultValues; 434 | begin 435 | Self.FConnectFlags.Flags := 0; 436 | Self.FConnectFlags.CleanStart := 1; 437 | Self.FConnectFlags.WillQoS := 1; 438 | Self.FConnectFlags.WillRetain := 0; 439 | Self.FConnectFlags.WillFlag := 1; 440 | Self.FConnectFlags.UserName := 0; 441 | Self.FConnectFlags.Password := 0; 442 | Self.FKeepAlive := 10; 443 | end; 444 | 445 | procedure TMQTTConnectVarHeader.set_CleanStart(const Value: integer); 446 | begin 447 | Self.FConnectFlags.CleanStart := Value; 448 | end; 449 | 450 | procedure TMQTTConnectVarHeader.set_Password(const Value: integer); 451 | begin 452 | Self.FConnectFlags.UserName := Value; 453 | end; 454 | 455 | procedure TMQTTConnectVarHeader.set_QoSLevel(const Value: integer); 456 | begin 457 | Self.FConnectFlags.WillQoS := Value; 458 | end; 459 | 460 | procedure TMQTTConnectVarHeader.set_Retain(const Value: integer); 461 | begin 462 | Self.FConnectFlags.WillRetain := Value; 463 | end; 464 | 465 | procedure TMQTTConnectVarHeader.set_Username(const Value: integer); 466 | begin 467 | Self.FConnectFlags.Password := Value; 468 | end; 469 | 470 | procedure TMQTTConnectVarHeader.set_WillFlag(const Value: integer); 471 | begin 472 | Self.FConnectFlags.WillFlag := Value; 473 | end; 474 | 475 | function TMQTTConnectVarHeader.ToBytes: TBytes; 476 | begin 477 | Self.rebuildHeader; 478 | Result := FBytes; 479 | end; 480 | 481 | { TMQTTConnectFlags } 482 | 483 | function TMQTTConnectFlags.GetBits(const aIndex: Integer): Integer; 484 | begin 485 | Result := GetDWordBits(Flags, aIndex); 486 | end; 487 | 488 | procedure TMQTTConnectFlags.SetBits(const aIndex, aValue: Integer); 489 | begin 490 | SetDWordBits(Flags, aIndex, aValue); 491 | end; 492 | 493 | { TMQTTPayload } 494 | 495 | constructor TMQTTPayload.Create; 496 | begin 497 | FContents := TStringList.Create(); 498 | FContainsIntLiterals := false; 499 | FPublishMessage := false; 500 | end; 501 | 502 | destructor TMQTTPayload.Destroy; 503 | begin 504 | FContents.Free; 505 | inherited; 506 | end; 507 | 508 | function TMQTTPayload.ToBytes(WithIntegerLiterals: boolean): TBytes; 509 | var 510 | line: string; 511 | lineAsBytes: TBytes; 512 | lineAsInt: integer; 513 | begin 514 | SetLength(Result, 0); 515 | for line in FContents do 516 | begin 517 | // This is really nasty and needs refactoring into subclasses 518 | if PublishMessage then 519 | begin 520 | lineAsBytes := TMQTTUtilities.UTF8EncodeToBytesNoLength(line); 521 | AppendToByteArray(lineAsBytes, Result); 522 | end 523 | else 524 | begin 525 | if (WithIntegerLiterals and TryStrToInt(line, lineAsInt))then 526 | begin 527 | AppendToByteArray(Lo(lineAsInt), Result); 528 | end 529 | else 530 | begin 531 | lineAsBytes := TMQTTUtilities.UTF8EncodeToBytes(line); 532 | AppendToByteArray(lineAsBytes, Result); 533 | end; 534 | end; 535 | end; 536 | end; 537 | 538 | function TMQTTPayload.ToBytes: TBytes; 539 | begin 540 | Result := ToBytes(FContainsIntLiterals); 541 | end; 542 | 543 | { TMQTTMessage } 544 | 545 | constructor TMQTTMessage.Create; 546 | begin 547 | inherited; 548 | // Fill our Fixed Header with Zeros to wipe any unintended noise. 549 | //FillChar(FixedHeader, SizeOf(FixedHeader), #0); 550 | end; 551 | 552 | destructor TMQTTMessage.Destroy; 553 | begin 554 | if Assigned(VariableHeader) then VariableHeader.Free; 555 | if Assigned(Payload) then Payload.Free; 556 | inherited; 557 | end; 558 | 559 | function TMQTTMessage.ToBytes: TBytes; 560 | var 561 | iRemainingLength: integer; 562 | bytesRemainingLength: TBytes; 563 | i: integer; 564 | begin 565 | 566 | try 567 | iRemainingLength := 0; 568 | if Assigned(VariableHeader) then iRemainingLength := iRemainingLength + Length(VariableHeader.ToBytes); 569 | if Assigned(Payload) then iRemainingLength := iRemainingLength + Length(Payload.ToBytes); 570 | 571 | FRemainingLength := iRemainingLength; 572 | bytesRemainingLength := TMQTTUtilities.RLIntToBytes(FRemainingLength); 573 | 574 | AppendToByteArray(FixedHeader.Flags, Result); 575 | AppendToByteArray(bytesRemainingLength, Result); 576 | if Assigned(VariableHeader) then AppendToByteArray(VariableHeader.ToBytes, Result); 577 | if Assigned(Payload) then AppendToByteArray(Payload.ToBytes, Result); 578 | 579 | except 580 | //on E:Exception do 581 | 582 | end; 583 | end; 584 | 585 | { TMQTTPublishVarHeader } 586 | 587 | constructor TMQTTPublishVarHeader.Create(QoSLevel: integer); 588 | begin 589 | inherited Create; 590 | FQosLevel := QoSLevel; 591 | end; 592 | 593 | function TMQTTPublishVarHeader.get_MessageID: integer; 594 | begin 595 | Result := FMessageID; 596 | end; 597 | 598 | function TMQTTPublishVarHeader.get_QoSLevel: integer; 599 | begin 600 | Result := FQoSLevel; 601 | end; 602 | 603 | function TMQTTPublishVarHeader.get_Topic: AnsiString; 604 | begin 605 | Result := FTopic; 606 | end; 607 | 608 | procedure TMQTTPublishVarHeader.rebuildHeader; 609 | begin 610 | ClearField; 611 | AddField(TMQTTUtilities.UTF8EncodeToBytes(FTopic)); 612 | if (FQoSLevel > 0) then 613 | begin 614 | AddField(TMQTTUtilities.IntToMSBLSB(FMessageID)); 615 | end; 616 | end; 617 | 618 | procedure TMQTTPublishVarHeader.set_MessageID(const Value: integer); 619 | begin 620 | FMessageID := Value; 621 | end; 622 | 623 | procedure TMQTTPublishVarHeader.set_QoSLevel(const Value: integer); 624 | begin 625 | FQoSLevel := Value; 626 | end; 627 | 628 | procedure TMQTTPublishVarHeader.set_Topic(const Value: AnsiString); 629 | begin 630 | FTopic := Value; 631 | end; 632 | 633 | function TMQTTPublishVarHeader.ToBytes: TBytes; 634 | begin 635 | Self.rebuildHeader; 636 | Result := Self.FBytes; 637 | end; 638 | 639 | { TMQTTSubscribeVarHeader } 640 | 641 | constructor TMQTTSubscribeVarHeader.Create(MessageId: integer); 642 | begin 643 | inherited Create; 644 | FMessageID := MessageId; 645 | end; 646 | 647 | function TMQTTSubscribeVarHeader.get_MessageID: integer; 648 | begin 649 | Result := FMessageID; 650 | end; 651 | 652 | procedure TMQTTSubscribeVarHeader.set_MessageID(const Value: integer); 653 | begin 654 | FMessageID := Value; 655 | end; 656 | 657 | function TMQTTSubscribeVarHeader.ToBytes: TBytes; 658 | begin 659 | ClearField; 660 | AddField(TMQTTUtilities.IntToMSBLSB(FMessageID)); 661 | Result := FBytes; 662 | end; 663 | 664 | { TMQTTUnsubscribeVarHeader } 665 | 666 | constructor TMQTTUnsubscribeVarHeader.Create(MessageId: integer); 667 | begin 668 | inherited Create; 669 | FMessageID := MessageId; 670 | end; 671 | 672 | function TMQTTUnsubscribeVarHeader.get_MessageID: integer; 673 | begin 674 | Result := FMessageID; 675 | end; 676 | 677 | procedure TMQTTUnsubscribeVarHeader.set_MessageID(const Value: integer); 678 | begin 679 | FMessageID := Value; 680 | end; 681 | 682 | function TMQTTUnsubscribeVarHeader.ToBytes: TBytes; 683 | begin 684 | ClearField; 685 | AddField(TMQTTUtilities.IntToMSBLSB(FMessageID)); 686 | Result := FBytes; 687 | end; 688 | 689 | end. 690 | --------------------------------------------------------------------------------