├── .gitignore ├── AUTHORS ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── connack.go ├── connack_test.go ├── connackcode.go ├── connect.go ├── connect_test.go ├── disconnect.go ├── disconnect_test.go ├── doc.go ├── header.go ├── header_test.go ├── message.go ├── message_test.go ├── ping_test.go ├── pingreq.go ├── pingresp.go ├── puback.go ├── puback_test.go ├── pubcomp.go ├── pubcomp_test.go ├── publish.go ├── publish_test.go ├── pubrec.go ├── pubrec_test.go ├── pubrel.go ├── pubrel_test.go ├── suback.go ├── suback_test.go ├── subscribe.go ├── subscribe_test.go ├── unsuback.go ├── unsuback_test.go ├── unsubscribe.go └── unsubscribe_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | 24 | .idea 25 | *.iml 26 | 27 | *.swp 28 | *.un~ 29 | 30 | .DS_Store 31 | 32 | *.pprof 33 | *.prof 34 | *.test 35 | *.out 36 | 37 | *.sublime-project 38 | *.sublime-workspace 39 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of SurgeMQ authors for copyright purposes. 2 | 3 | # If you are submitting a patch, please add your name or the name of the 4 | # organization which holds the copyright to this list in alphabetical order. 5 | 6 | # Names should be added to this file as 7 | # Name 8 | # The email address is not required for organizations. 9 | # Please keep the list sorted. 10 | 11 | 12 | # Individual Persons 13 | 14 | Jian Zhen 15 | 16 | # Organizations 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | ## Reporting Issues 4 | 5 | Before creating a new Issue, please check first if a similar Issue [already exists](https://github.com/surgemq/message/issues?state=open) or was [recently closed](https://github.com/surgemq/message/issues?direction=desc&page=1&sort=updated&state=closed). 6 | 7 | Please provide the following minimum information: 8 | * Your SurgeMQ version (or git SHA) 9 | * Your Go version (run `go version` in your console) 10 | * A detailed issue description 11 | * Error Log if present 12 | * If possible, a short example 13 | 14 | 15 | ## Contributing Code 16 | 17 | By contributing to this project, you share your code under the Apache License, Version 2.0, as specified in the LICENSE file. 18 | Don't forget to add yourself to the AUTHORS file. 19 | 20 | ### Pull Requests Checklist 21 | 22 | Please check the following points before submitting your pull request: 23 | - [x] Code compiles correctly 24 | - [x] Created tests, if possible 25 | - [x] All tests pass 26 | - [x] Extended the README / documentation, if necessary 27 | - [x] Added yourself to the AUTHORS file 28 | 29 | ### Code Review 30 | 31 | Everyone is invited to review and comment on pull requests. 32 | If it looks fine to you, comment with "LGTM" (Looks good to me). 33 | 34 | If changes are required, notice the reviewers with "PTAL" (Please take another look) after committing the fixes. 35 | 36 | Before merging the Pull Request, at least one [team member](https://github.com/orgs/surgemq/people) must have commented with "LGTM". 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Package message is an encoder/decoder library for MQTT 3.1 and 3.1.1 messages. You can 2 | find the MQTT specs at the following locations: 3 | 4 | > 3.1.1 - http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/ 5 | > 3.1 - http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html 6 | 7 | From the spec: 8 | 9 | > MQTT is a Client Server publish/subscribe messaging transport protocol. It is 10 | > light weight, open, simple, and designed so as to be easy to implement. These 11 | > characteristics make it ideal for use in many situations, including constrained 12 | > environments such as for communication in Machine to Machine (M2M) and Internet 13 | > of Things (IoT) contexts where a small code footprint is required and/or network 14 | > bandwidth is at a premium. 15 | > 16 | > The MQTT protocol works by exchanging a series of MQTT messages in a defined way. 17 | > The protocol runs over TCP/IP, or over other network protocols that provide 18 | > ordered, lossless, bi-directional connections. 19 | 20 | 21 | There are two main items to take note in this package. The first is 22 | 23 | ``` 24 | type MessageType byte 25 | ``` 26 | 27 | MessageType is the type representing the MQTT packet types. In the MQTT spec, MQTT 28 | control packet type is represented as a 4-bit unsigned value. MessageType receives 29 | several methods that returns string representations of the names and descriptions. 30 | 31 | Also, one of the methods is New(). It returns a new Message object based on the mtype 32 | parameter. For example: 33 | 34 | ``` 35 | m, err := CONNECT.New() 36 | msg := m.(*ConnectMessage) 37 | ``` 38 | 39 | This would return a PublishMessage struct, but mapped to the Message interface. You can 40 | then type assert it back to a *PublishMessage. Another way to create a new 41 | PublishMessage is to call 42 | 43 | ``` 44 | msg := NewConnectMessage() 45 | ``` 46 | 47 | Every message type has a New function that returns a new message. The list of available 48 | message types are defined as constants below. 49 | 50 | As you may have noticed, the second important item is the Message interface. It defines 51 | several methods that are common to all messages, including Name(), Desc(), and Type(). 52 | Most importantly, it also defines the Encode() and Decode() methods. 53 | 54 | ``` 55 | Encode() (io.Reader, int, error) 56 | Decode(io.Reader) (int, error) 57 | ``` 58 | 59 | Encode returns an io.Reader in which the encoded bytes can be read. The second return 60 | value is the number of bytes encoded, so the caller knows how many bytes there will be. 61 | If Encode returns an error, then the first two return values should be considered invalid. 62 | Any changes to the message after Encode() is called will invalidate the io.Reader. 63 | 64 | Decode reads from the io.Reader parameter until a full message is decoded, or when io.Reader 65 | returns EOF or error. The first return value is the number of bytes read from io.Reader. 66 | The second is error if Decode encounters any problems. 67 | 68 | With these in mind, we can now do: 69 | 70 | ``` 71 | // Create a new CONNECT message 72 | msg := NewConnectMessage() 73 | 74 | // Set the appropriate parameters 75 | msg.SetWillQos(1) 76 | msg.SetVersion(4) 77 | msg.SetCleanSession(true) 78 | msg.SetClientId([]byte("surgemq")) 79 | msg.SetKeepAlive(10) 80 | msg.SetWillTopic([]byte("will")) 81 | msg.SetWillMessage([]byte("send me home")) 82 | msg.SetUsername([]byte("surgemq")) 83 | msg.SetPassword([]byte("verysecret")) 84 | 85 | // Encode the message and get the io.Reader 86 | r, n, err := msg.Encode() 87 | if err == nil { 88 | return err 89 | } 90 | 91 | // Write n bytes into the connection 92 | m, err := io.CopyN(conn, r, int64(n)) 93 | if err != nil { 94 | return err 95 | } 96 | 97 | fmt.Printf("Sent %d bytes of %s message", m, msg.Name()) 98 | ``` 99 | 100 | To receive a CONNECT message from a connection, we can do: 101 | 102 | ``` 103 | // Create a new CONNECT message 104 | msg := NewConnectMessage() 105 | 106 | // Decode the message by reading from conn 107 | n, err := msg.Decode(conn) 108 | ``` 109 | 110 | If you don't know what type of message is coming down the pipe, you can do something like this: 111 | 112 | ``` 113 | // Create a buffered IO reader for the connection 114 | br := bufio.NewReader(conn) 115 | 116 | // Peek at the first byte, which contains the message type 117 | b, err := br.Peek(1) 118 | if err != nil { 119 | return err 120 | } 121 | 122 | // Extract the type from the first byte 123 | t := MessageType(b[0] >> 4) 124 | 125 | // Create a new message 126 | msg, err := t.New() 127 | if err != nil { 128 | return err 129 | } 130 | 131 | // Decode it from the bufio.Reader 132 | n, err := msg.Decode(br) 133 | if err != nil { 134 | return err 135 | } 136 | ``` 137 | -------------------------------------------------------------------------------- /connack.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | import "fmt" 18 | 19 | // The CONNACK Packet is the packet sent by the Server in response to a CONNECT Packet 20 | // received from a Client. The first packet sent from the Server to the Client MUST 21 | // be a CONNACK Packet [MQTT-3.2.0-1]. 22 | // 23 | // If the Client does not receive a CONNACK Packet from the Server within a reasonable 24 | // amount of time, the Client SHOULD close the Network Connection. A "reasonable" amount 25 | // of time depends on the type of application and the communications infrastructure. 26 | type ConnackMessage struct { 27 | header 28 | 29 | sessionPresent bool 30 | returnCode ConnackCode 31 | } 32 | 33 | var _ Message = (*ConnackMessage)(nil) 34 | 35 | // NewConnackMessage creates a new CONNACK message 36 | func NewConnackMessage() *ConnackMessage { 37 | msg := &ConnackMessage{} 38 | msg.SetType(CONNACK) 39 | 40 | return msg 41 | } 42 | 43 | // String returns a string representation of the CONNACK message 44 | func (this ConnackMessage) String() string { 45 | return fmt.Sprintf("%s, Session Present=%t, Return code=%q\n", this.header, this.sessionPresent, this.returnCode) 46 | } 47 | 48 | // SessionPresent returns the session present flag value 49 | func (this *ConnackMessage) SessionPresent() bool { 50 | return this.sessionPresent 51 | } 52 | 53 | // SetSessionPresent sets the value of the session present flag 54 | func (this *ConnackMessage) SetSessionPresent(v bool) { 55 | if v { 56 | this.sessionPresent = true 57 | } else { 58 | this.sessionPresent = false 59 | } 60 | 61 | this.dirty = true 62 | } 63 | 64 | // ReturnCode returns the return code received for the CONNECT message. The return 65 | // type is an error 66 | func (this *ConnackMessage) ReturnCode() ConnackCode { 67 | return this.returnCode 68 | } 69 | 70 | func (this *ConnackMessage) SetReturnCode(ret ConnackCode) { 71 | this.returnCode = ret 72 | this.dirty = true 73 | } 74 | 75 | func (this *ConnackMessage) Len() int { 76 | if !this.dirty { 77 | return len(this.dbuf) 78 | } 79 | 80 | ml := this.msglen() 81 | 82 | if err := this.SetRemainingLength(int32(ml)); err != nil { 83 | return 0 84 | } 85 | 86 | return this.header.msglen() + ml 87 | } 88 | 89 | func (this *ConnackMessage) Decode(src []byte) (int, error) { 90 | total := 0 91 | 92 | n, err := this.header.decode(src) 93 | total += n 94 | if err != nil { 95 | return total, err 96 | } 97 | 98 | b := src[total] 99 | 100 | if b&254 != 0 { 101 | return 0, fmt.Errorf("connack/Decode: Bits 7-1 in Connack Acknowledge Flags byte (1) are not 0") 102 | } 103 | 104 | this.sessionPresent = b&0x1 == 1 105 | total++ 106 | 107 | b = src[total] 108 | 109 | // Read return code 110 | if b > 5 { 111 | return 0, fmt.Errorf("connack/Decode: Invalid CONNACK return code (%d)", b) 112 | } 113 | 114 | this.returnCode = ConnackCode(b) 115 | total++ 116 | 117 | this.dirty = false 118 | 119 | return total, nil 120 | } 121 | 122 | func (this *ConnackMessage) Encode(dst []byte) (int, error) { 123 | if !this.dirty { 124 | if len(dst) < len(this.dbuf) { 125 | return 0, fmt.Errorf("connack/Encode: Insufficient buffer size. Expecting %d, got %d.", len(this.dbuf), len(dst)) 126 | } 127 | 128 | return copy(dst, this.dbuf), nil 129 | } 130 | 131 | // CONNACK remaining length fixed at 2 bytes 132 | hl := this.header.msglen() 133 | ml := this.msglen() 134 | 135 | if len(dst) < hl+ml { 136 | return 0, fmt.Errorf("connack/Encode: Insufficient buffer size. Expecting %d, got %d.", hl+ml, len(dst)) 137 | } 138 | 139 | if err := this.SetRemainingLength(int32(ml)); err != nil { 140 | return 0, err 141 | } 142 | 143 | total := 0 144 | 145 | n, err := this.header.encode(dst[total:]) 146 | total += n 147 | if err != nil { 148 | return 0, err 149 | } 150 | 151 | if this.sessionPresent { 152 | dst[total] = 1 153 | } 154 | total++ 155 | 156 | if this.returnCode > 5 { 157 | return total, fmt.Errorf("connack/Encode: Invalid CONNACK return code (%d)", this.returnCode) 158 | } 159 | 160 | dst[total] = this.returnCode.Value() 161 | total++ 162 | 163 | return total, nil 164 | } 165 | 166 | func (this *ConnackMessage) msglen() int { 167 | return 2 168 | } 169 | -------------------------------------------------------------------------------- /connack_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | ) 22 | 23 | func TestConnackMessageFields(t *testing.T) { 24 | msg := NewConnackMessage() 25 | 26 | msg.SetSessionPresent(true) 27 | require.True(t, msg.SessionPresent(), "Error setting session present flag.") 28 | 29 | msg.SetSessionPresent(false) 30 | require.False(t, msg.SessionPresent(), "Error setting session present flag.") 31 | 32 | msg.SetReturnCode(ConnectionAccepted) 33 | require.Equal(t, ConnectionAccepted, msg.ReturnCode(), "Error setting return code.") 34 | } 35 | 36 | func TestConnackMessageDecode(t *testing.T) { 37 | msgBytes := []byte{ 38 | byte(CONNACK << 4), 39 | 2, 40 | 0, // session not present 41 | 0, // connection accepted 42 | } 43 | 44 | msg := NewConnackMessage() 45 | 46 | n, err := msg.Decode(msgBytes) 47 | 48 | require.NoError(t, err, "Error decoding message.") 49 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 50 | require.False(t, msg.SessionPresent(), "Error decoding session present flag.") 51 | require.Equal(t, ConnectionAccepted, msg.ReturnCode(), "Error decoding return code.") 52 | } 53 | 54 | // testing wrong message length 55 | func TestConnackMessageDecode2(t *testing.T) { 56 | msgBytes := []byte{ 57 | byte(CONNACK << 4), 58 | 3, 59 | 0, // session not present 60 | 0, // connection accepted 61 | } 62 | 63 | msg := NewConnackMessage() 64 | 65 | _, err := msg.Decode(msgBytes) 66 | require.Error(t, err, "Error decoding message.") 67 | } 68 | 69 | // testing wrong message size 70 | func TestConnackMessageDecode3(t *testing.T) { 71 | msgBytes := []byte{ 72 | byte(CONNACK << 4), 73 | 2, 74 | 0, // session not present 75 | } 76 | 77 | msg := NewConnackMessage() 78 | 79 | _, err := msg.Decode(msgBytes) 80 | require.Error(t, err, "Error decoding message.") 81 | } 82 | 83 | // testing wrong reserve bits 84 | func TestConnackMessageDecode4(t *testing.T) { 85 | msgBytes := []byte{ 86 | byte(CONNACK << 4), 87 | 2, 88 | 64, // <- wrong size 89 | 0, // connection accepted 90 | } 91 | 92 | msg := NewConnackMessage() 93 | 94 | _, err := msg.Decode(msgBytes) 95 | require.Error(t, err, "Error decoding message.") 96 | } 97 | 98 | // testing invalid return code 99 | func TestConnackMessageDecode5(t *testing.T) { 100 | msgBytes := []byte{ 101 | byte(CONNACK << 4), 102 | 2, 103 | 0, 104 | 6, // <- wrong code 105 | } 106 | 107 | msg := NewConnackMessage() 108 | 109 | _, err := msg.Decode(msgBytes) 110 | require.Error(t, err, "Error decoding message.") 111 | } 112 | 113 | func TestConnackMessageEncode(t *testing.T) { 114 | msgBytes := []byte{ 115 | byte(CONNACK << 4), 116 | 2, 117 | 1, // session present 118 | 0, // connection accepted 119 | } 120 | 121 | msg := NewConnackMessage() 122 | msg.SetReturnCode(ConnectionAccepted) 123 | msg.SetSessionPresent(true) 124 | 125 | dst := make([]byte, 10) 126 | n, err := msg.Encode(dst) 127 | 128 | require.NoError(t, err, "Error decoding message.") 129 | require.Equal(t, len(msgBytes), n, "Error encoding message.") 130 | require.Equal(t, msgBytes, dst[:n], "Error encoding connack message.") 131 | } 132 | 133 | // test to ensure encoding and decoding are the same 134 | // decode, encode, and decode again 135 | func TestConnackDecodeEncodeEquiv(t *testing.T) { 136 | msgBytes := []byte{ 137 | byte(CONNACK << 4), 138 | 2, 139 | 0, // session not present 140 | 0, // connection accepted 141 | } 142 | 143 | msg := NewConnackMessage() 144 | n, err := msg.Decode(msgBytes) 145 | 146 | require.NoError(t, err, "Error decoding message.") 147 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 148 | 149 | dst := make([]byte, 100) 150 | n2, err := msg.Encode(dst) 151 | 152 | require.NoError(t, err, "Error decoding message.") 153 | require.Equal(t, len(msgBytes), n2, "Error decoding message.") 154 | require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") 155 | 156 | n3, err := msg.Decode(dst) 157 | 158 | require.NoError(t, err, "Error decoding message.") 159 | require.Equal(t, len(msgBytes), n3, "Error decoding message.") 160 | } 161 | -------------------------------------------------------------------------------- /connackcode.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | // ConnackCode is the type representing the return code in the CONNACK message, 18 | // returned after the initial CONNECT message 19 | type ConnackCode byte 20 | 21 | const ( 22 | // Connection accepted 23 | ConnectionAccepted ConnackCode = iota 24 | 25 | // The Server does not support the level of the MQTT protocol requested by the Client 26 | ErrInvalidProtocolVersion 27 | 28 | // The Client identifier is correct UTF-8 but not allowed by the server 29 | ErrIdentifierRejected 30 | 31 | // The Network Connection has been made but the MQTT service is unavailable 32 | ErrServerUnavailable 33 | 34 | // The data in the user name or password is malformed 35 | ErrBadUsernameOrPassword 36 | 37 | // The Client is not authorized to connect 38 | ErrNotAuthorized 39 | ) 40 | 41 | // Value returns the value of the ConnackCode, which is just the byte representation 42 | func (this ConnackCode) Value() byte { 43 | return byte(this) 44 | } 45 | 46 | // Desc returns the description of the ConnackCode 47 | func (this ConnackCode) Desc() string { 48 | switch this { 49 | case 0: 50 | return "Connection accepted" 51 | case 1: 52 | return "The Server does not support the level of the MQTT protocol requested by the Client" 53 | case 2: 54 | return "The Client identifier is correct UTF-8 but not allowed by the server" 55 | case 3: 56 | return "The Network Connection has been made but the MQTT service is unavailable" 57 | case 4: 58 | return "The data in the user name or password is malformed" 59 | case 5: 60 | return "The Client is not authorized to connect" 61 | } 62 | 63 | return "" 64 | } 65 | 66 | // Valid checks to see if the ConnackCode is valid. Currently valid codes are <= 5 67 | func (this ConnackCode) Valid() bool { 68 | return this <= 5 69 | } 70 | 71 | // Error returns the corresonding error string for the ConnackCode 72 | func (this ConnackCode) Error() string { 73 | switch this { 74 | case 0: 75 | return "Connection accepted" 76 | case 1: 77 | return "Connection Refused, unacceptable protocol version" 78 | case 2: 79 | return "Connection Refused, identifier rejected" 80 | case 3: 81 | return "Connection Refused, Server unavailable" 82 | case 4: 83 | return "Connection Refused, bad user name or password" 84 | case 5: 85 | return "Connection Refused, not authorized" 86 | } 87 | 88 | return "Unknown error" 89 | } 90 | -------------------------------------------------------------------------------- /connect.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | import ( 18 | "encoding/binary" 19 | "fmt" 20 | "regexp" 21 | ) 22 | 23 | var clientIdRegexp *regexp.Regexp 24 | 25 | func init() { 26 | // Added space for Paho compliance test 27 | // Added underscore (_) for MQTT C client test 28 | clientIdRegexp = regexp.MustCompile("^[0-9a-zA-Z _]*$") 29 | } 30 | 31 | // After a Network Connection is established by a Client to a Server, the first Packet 32 | // sent from the Client to the Server MUST be a CONNECT Packet [MQTT-3.1.0-1]. 33 | // 34 | // A Client can only send the CONNECT Packet once over a Network Connection. The Server 35 | // MUST process a second CONNECT Packet sent from a Client as a protocol violation and 36 | // disconnect the Client [MQTT-3.1.0-2]. See section 4.8 for information about 37 | // handling errors. 38 | type ConnectMessage struct { 39 | header 40 | 41 | // 7: username flag 42 | // 6: password flag 43 | // 5: will retain 44 | // 4-3: will QoS 45 | // 2: will flag 46 | // 1: clean session 47 | // 0: reserved 48 | connectFlags byte 49 | 50 | version byte 51 | 52 | keepAlive uint16 53 | 54 | protoName, 55 | clientId, 56 | willTopic, 57 | willMessage, 58 | username, 59 | password []byte 60 | } 61 | 62 | var _ Message = (*ConnectMessage)(nil) 63 | 64 | // NewConnectMessage creates a new CONNECT message. 65 | func NewConnectMessage() *ConnectMessage { 66 | msg := &ConnectMessage{} 67 | msg.SetType(CONNECT) 68 | 69 | return msg 70 | } 71 | 72 | // String returns a string representation of the CONNECT message 73 | func (this ConnectMessage) String() string { 74 | return fmt.Sprintf("%s, Connect Flags=%08b, Version=%d, KeepAlive=%d, Client ID=%q, Will Topic=%q, Will Message=%q, Username=%q, Password=%q", 75 | this.header, 76 | this.connectFlags, 77 | this.Version(), 78 | this.KeepAlive(), 79 | this.ClientId(), 80 | this.WillTopic(), 81 | this.WillMessage(), 82 | this.Username(), 83 | this.Password(), 84 | ) 85 | } 86 | 87 | // Version returns the the 8 bit unsigned value that represents the revision level 88 | // of the protocol used by the Client. The value of the Protocol Level field for 89 | // the version 3.1.1 of the protocol is 4 (0x04). 90 | func (this *ConnectMessage) Version() byte { 91 | return this.version 92 | } 93 | 94 | // SetVersion sets the version value of the CONNECT message 95 | func (this *ConnectMessage) SetVersion(v byte) error { 96 | if _, ok := SupportedVersions[v]; !ok { 97 | return fmt.Errorf("connect/SetVersion: Invalid version number %d", v) 98 | } 99 | 100 | this.version = v 101 | this.dirty = true 102 | 103 | return nil 104 | } 105 | 106 | // CleanSession returns the bit that specifies the handling of the Session state. 107 | // The Client and Server can store Session state to enable reliable messaging to 108 | // continue across a sequence of Network Connections. This bit is used to control 109 | // the lifetime of the Session state. 110 | func (this *ConnectMessage) CleanSession() bool { 111 | return ((this.connectFlags >> 1) & 0x1) == 1 112 | } 113 | 114 | // SetCleanSession sets the bit that specifies the handling of the Session state. 115 | func (this *ConnectMessage) SetCleanSession(v bool) { 116 | if v { 117 | this.connectFlags |= 0x2 // 00000010 118 | } else { 119 | this.connectFlags &= 253 // 11111101 120 | } 121 | 122 | this.dirty = true 123 | } 124 | 125 | // WillFlag returns the bit that specifies whether a Will Message should be stored 126 | // on the server. If the Will Flag is set to 1 this indicates that, if the Connect 127 | // request is accepted, a Will Message MUST be stored on the Server and associated 128 | // with the Network Connection. 129 | func (this *ConnectMessage) WillFlag() bool { 130 | return ((this.connectFlags >> 2) & 0x1) == 1 131 | } 132 | 133 | // SetWillFlag sets the bit that specifies whether a Will Message should be stored 134 | // on the server. 135 | func (this *ConnectMessage) SetWillFlag(v bool) { 136 | if v { 137 | this.connectFlags |= 0x4 // 00000100 138 | } else { 139 | this.connectFlags &= 251 // 11111011 140 | } 141 | 142 | this.dirty = true 143 | } 144 | 145 | // WillQos returns the two bits that specify the QoS level to be used when publishing 146 | // the Will Message. 147 | func (this *ConnectMessage) WillQos() byte { 148 | return (this.connectFlags >> 3) & 0x3 149 | } 150 | 151 | // SetWillQos sets the two bits that specify the QoS level to be used when publishing 152 | // the Will Message. 153 | func (this *ConnectMessage) SetWillQos(qos byte) error { 154 | if qos != QosAtMostOnce && qos != QosAtLeastOnce && qos != QosExactlyOnce { 155 | return fmt.Errorf("connect/SetWillQos: Invalid QoS level %d", qos) 156 | } 157 | 158 | this.connectFlags = (this.connectFlags & 231) | (qos << 3) // 231 = 11100111 159 | this.dirty = true 160 | 161 | return nil 162 | } 163 | 164 | // WillRetain returns the bit specifies if the Will Message is to be Retained when it 165 | // is published. 166 | func (this *ConnectMessage) WillRetain() bool { 167 | return ((this.connectFlags >> 5) & 0x1) == 1 168 | } 169 | 170 | // SetWillRetain sets the bit specifies if the Will Message is to be Retained when it 171 | // is published. 172 | func (this *ConnectMessage) SetWillRetain(v bool) { 173 | if v { 174 | this.connectFlags |= 32 // 00100000 175 | } else { 176 | this.connectFlags &= 223 // 11011111 177 | } 178 | 179 | this.dirty = true 180 | } 181 | 182 | // UsernameFlag returns the bit that specifies whether a user name is present in the 183 | // payload. 184 | func (this *ConnectMessage) UsernameFlag() bool { 185 | return ((this.connectFlags >> 7) & 0x1) == 1 186 | } 187 | 188 | // SetUsernameFlag sets the bit that specifies whether a user name is present in the 189 | // payload. 190 | func (this *ConnectMessage) SetUsernameFlag(v bool) { 191 | if v { 192 | this.connectFlags |= 128 // 10000000 193 | } else { 194 | this.connectFlags &= 127 // 01111111 195 | } 196 | 197 | this.dirty = true 198 | } 199 | 200 | // PasswordFlag returns the bit that specifies whether a password is present in the 201 | // payload. 202 | func (this *ConnectMessage) PasswordFlag() bool { 203 | return ((this.connectFlags >> 6) & 0x1) == 1 204 | } 205 | 206 | // SetPasswordFlag sets the bit that specifies whether a password is present in the 207 | // payload. 208 | func (this *ConnectMessage) SetPasswordFlag(v bool) { 209 | if v { 210 | this.connectFlags |= 64 // 01000000 211 | } else { 212 | this.connectFlags &= 191 // 10111111 213 | } 214 | 215 | this.dirty = true 216 | } 217 | 218 | // KeepAlive returns a time interval measured in seconds. Expressed as a 16-bit word, 219 | // it is the maximum time interval that is permitted to elapse between the point at 220 | // which the Client finishes transmitting one Control Packet and the point it starts 221 | // sending the next. 222 | func (this *ConnectMessage) KeepAlive() uint16 { 223 | return this.keepAlive 224 | } 225 | 226 | // SetKeepAlive sets the time interval in which the server should keep the connection 227 | // alive. 228 | func (this *ConnectMessage) SetKeepAlive(v uint16) { 229 | this.keepAlive = v 230 | 231 | this.dirty = true 232 | } 233 | 234 | // ClientId returns an ID that identifies the Client to the Server. Each Client 235 | // connecting to the Server has a unique ClientId. The ClientId MUST be used by 236 | // Clients and by Servers to identify state that they hold relating to this MQTT 237 | // Session between the Client and the Server 238 | func (this *ConnectMessage) ClientId() []byte { 239 | return this.clientId 240 | } 241 | 242 | // SetClientId sets an ID that identifies the Client to the Server. 243 | func (this *ConnectMessage) SetClientId(v []byte) error { 244 | if len(v) > 0 && !this.validClientId(v) { 245 | return ErrIdentifierRejected 246 | } 247 | 248 | this.clientId = v 249 | this.dirty = true 250 | 251 | return nil 252 | } 253 | 254 | // WillTopic returns the topic in which the Will Message should be published to. 255 | // If the Will Flag is set to 1, the Will Topic must be in the payload. 256 | func (this *ConnectMessage) WillTopic() []byte { 257 | return this.willTopic 258 | } 259 | 260 | // SetWillTopic sets the topic in which the Will Message should be published to. 261 | func (this *ConnectMessage) SetWillTopic(v []byte) { 262 | this.willTopic = v 263 | 264 | if len(v) > 0 { 265 | this.SetWillFlag(true) 266 | } else if len(this.willMessage) == 0 { 267 | this.SetWillFlag(false) 268 | } 269 | 270 | this.dirty = true 271 | } 272 | 273 | // WillMessage returns the Will Message that is to be published to the Will Topic. 274 | func (this *ConnectMessage) WillMessage() []byte { 275 | return this.willMessage 276 | } 277 | 278 | // SetWillMessage sets the Will Message that is to be published to the Will Topic. 279 | func (this *ConnectMessage) SetWillMessage(v []byte) { 280 | this.willMessage = v 281 | 282 | if len(v) > 0 { 283 | this.SetWillFlag(true) 284 | } else if len(this.willTopic) == 0 { 285 | this.SetWillFlag(false) 286 | } 287 | 288 | this.dirty = true 289 | } 290 | 291 | // Username returns the username from the payload. If the User Name Flag is set to 1, 292 | // this must be in the payload. It can be used by the Server for authentication and 293 | // authorization. 294 | func (this *ConnectMessage) Username() []byte { 295 | return this.username 296 | } 297 | 298 | // SetUsername sets the username for authentication. 299 | func (this *ConnectMessage) SetUsername(v []byte) { 300 | this.username = v 301 | 302 | if len(v) > 0 { 303 | this.SetUsernameFlag(true) 304 | } else { 305 | this.SetUsernameFlag(false) 306 | } 307 | 308 | this.dirty = true 309 | } 310 | 311 | // Password returns the password from the payload. If the Password Flag is set to 1, 312 | // this must be in the payload. It can be used by the Server for authentication and 313 | // authorization. 314 | func (this *ConnectMessage) Password() []byte { 315 | return this.password 316 | } 317 | 318 | // SetPassword sets the username for authentication. 319 | func (this *ConnectMessage) SetPassword(v []byte) { 320 | this.password = v 321 | 322 | if len(v) > 0 { 323 | this.SetPasswordFlag(true) 324 | } else { 325 | this.SetPasswordFlag(false) 326 | } 327 | 328 | this.dirty = true 329 | } 330 | 331 | func (this *ConnectMessage) Len() int { 332 | if !this.dirty { 333 | return len(this.dbuf) 334 | } 335 | 336 | ml := this.msglen() 337 | 338 | if err := this.SetRemainingLength(int32(ml)); err != nil { 339 | return 0 340 | } 341 | 342 | return this.header.msglen() + ml 343 | } 344 | 345 | // For the CONNECT message, the error returned could be a ConnackReturnCode, so 346 | // be sure to check that. Otherwise it's a generic error. If a generic error is 347 | // returned, this Message should be considered invalid. 348 | // 349 | // Caller should call ValidConnackError(err) to see if the returned error is 350 | // a Connack error. If so, caller should send the Client back the corresponding 351 | // CONNACK message. 352 | func (this *ConnectMessage) Decode(src []byte) (int, error) { 353 | total := 0 354 | 355 | n, err := this.header.decode(src[total:]) 356 | if err != nil { 357 | return total + n, err 358 | } 359 | total += n 360 | 361 | if n, err = this.decodeMessage(src[total:]); err != nil { 362 | return total + n, err 363 | } 364 | total += n 365 | 366 | this.dirty = false 367 | 368 | return total, nil 369 | } 370 | 371 | func (this *ConnectMessage) Encode(dst []byte) (int, error) { 372 | if !this.dirty { 373 | if len(dst) < len(this.dbuf) { 374 | return 0, fmt.Errorf("connect/Encode: Insufficient buffer size. Expecting %d, got %d.", len(this.dbuf), len(dst)) 375 | } 376 | 377 | return copy(dst, this.dbuf), nil 378 | } 379 | 380 | if this.Type() != CONNECT { 381 | return 0, fmt.Errorf("connect/Encode: Invalid message type. Expecting %d, got %d", CONNECT, this.Type()) 382 | } 383 | 384 | _, ok := SupportedVersions[this.version] 385 | if !ok { 386 | return 0, ErrInvalidProtocolVersion 387 | } 388 | 389 | hl := this.header.msglen() 390 | ml := this.msglen() 391 | 392 | if len(dst) < hl+ml { 393 | return 0, fmt.Errorf("connect/Encode: Insufficient buffer size. Expecting %d, got %d.", hl+ml, len(dst)) 394 | } 395 | 396 | if err := this.SetRemainingLength(int32(ml)); err != nil { 397 | return 0, err 398 | } 399 | 400 | total := 0 401 | 402 | n, err := this.header.encode(dst[total:]) 403 | total += n 404 | if err != nil { 405 | return total, err 406 | } 407 | 408 | n, err = this.encodeMessage(dst[total:]) 409 | total += n 410 | if err != nil { 411 | return total, err 412 | } 413 | 414 | return total, nil 415 | } 416 | 417 | func (this *ConnectMessage) encodeMessage(dst []byte) (int, error) { 418 | total := 0 419 | 420 | n, err := writeLPBytes(dst[total:], []byte(SupportedVersions[this.version])) 421 | total += n 422 | if err != nil { 423 | return total, err 424 | } 425 | 426 | dst[total] = this.version 427 | total += 1 428 | 429 | dst[total] = this.connectFlags 430 | total += 1 431 | 432 | binary.BigEndian.PutUint16(dst[total:], this.keepAlive) 433 | total += 2 434 | 435 | n, err = writeLPBytes(dst[total:], this.clientId) 436 | total += n 437 | if err != nil { 438 | return total, err 439 | } 440 | 441 | if this.WillFlag() { 442 | n, err = writeLPBytes(dst[total:], this.willTopic) 443 | total += n 444 | if err != nil { 445 | return total, err 446 | } 447 | 448 | n, err = writeLPBytes(dst[total:], this.willMessage) 449 | total += n 450 | if err != nil { 451 | return total, err 452 | } 453 | } 454 | 455 | // According to the 3.1 spec, it's possible that the usernameFlag is set, 456 | // but the username string is missing. 457 | if this.UsernameFlag() && len(this.username) > 0 { 458 | n, err = writeLPBytes(dst[total:], this.username) 459 | total += n 460 | if err != nil { 461 | return total, err 462 | } 463 | } 464 | 465 | // According to the 3.1 spec, it's possible that the passwordFlag is set, 466 | // but the password string is missing. 467 | if this.PasswordFlag() && len(this.password) > 0 { 468 | n, err = writeLPBytes(dst[total:], this.password) 469 | total += n 470 | if err != nil { 471 | return total, err 472 | } 473 | } 474 | 475 | return total, nil 476 | } 477 | 478 | func (this *ConnectMessage) decodeMessage(src []byte) (int, error) { 479 | var err error 480 | n, total := 0, 0 481 | 482 | this.protoName, n, err = readLPBytes(src[total:]) 483 | total += n 484 | if err != nil { 485 | return total, err 486 | } 487 | 488 | this.version = src[total] 489 | total++ 490 | 491 | if verstr, ok := SupportedVersions[this.version]; !ok { 492 | return total, ErrInvalidProtocolVersion 493 | } else if verstr != string(this.protoName) { 494 | return total, ErrInvalidProtocolVersion 495 | } 496 | 497 | this.connectFlags = src[total] 498 | total++ 499 | 500 | if this.connectFlags&0x1 != 0 { 501 | return total, fmt.Errorf("connect/decodeMessage: Connect Flags reserved bit 0 is not 0") 502 | } 503 | 504 | if this.WillQos() > QosExactlyOnce { 505 | return total, fmt.Errorf("connect/decodeMessage: Invalid QoS level (%d) for %s message", this.WillQos(), this.Name()) 506 | } 507 | 508 | if !this.WillFlag() && (this.WillRetain() || this.WillQos() != QosAtMostOnce) { 509 | return total, fmt.Errorf("connect/decodeMessage: Protocol violation: If the Will Flag (%t) is set to 0 the Will QoS (%d) and Will Retain (%t) fields MUST be set to zero", this.WillFlag(), this.WillQos(), this.WillRetain()) 510 | } 511 | 512 | if this.UsernameFlag() && !this.PasswordFlag() { 513 | return total, fmt.Errorf("connect/decodeMessage: Username flag is set but Password flag is not set") 514 | } 515 | 516 | if len(src[total:]) < 2 { 517 | return 0, fmt.Errorf("connect/decodeMessage: Insufficient buffer size. Expecting %d, got %d.", 2, len(src[total:])) 518 | } 519 | 520 | this.keepAlive = binary.BigEndian.Uint16(src[total:]) 521 | total += 2 522 | 523 | this.clientId, n, err = readLPBytes(src[total:]) 524 | total += n 525 | if err != nil { 526 | return total, err 527 | } 528 | 529 | // If the Client supplies a zero-byte ClientId, the Client MUST also set CleanSession to 1 530 | if len(this.clientId) == 0 && !this.CleanSession() { 531 | return total, ErrIdentifierRejected 532 | } 533 | 534 | // The ClientId must contain only characters 0-9, a-z, and A-Z 535 | // We also support ClientId longer than 23 encoded bytes 536 | // We do not support ClientId outside of the above characters 537 | if len(this.clientId) > 0 && !this.validClientId(this.clientId) { 538 | return total, ErrIdentifierRejected 539 | } 540 | 541 | if this.WillFlag() { 542 | this.willTopic, n, err = readLPBytes(src[total:]) 543 | total += n 544 | if err != nil { 545 | return total, err 546 | } 547 | 548 | this.willMessage, n, err = readLPBytes(src[total:]) 549 | total += n 550 | if err != nil { 551 | return total, err 552 | } 553 | } 554 | 555 | // According to the 3.1 spec, it's possible that the passwordFlag is set, 556 | // but the password string is missing. 557 | if this.UsernameFlag() && len(src[total:]) > 0 { 558 | this.username, n, err = readLPBytes(src[total:]) 559 | total += n 560 | if err != nil { 561 | return total, err 562 | } 563 | } 564 | 565 | // According to the 3.1 spec, it's possible that the passwordFlag is set, 566 | // but the password string is missing. 567 | if this.PasswordFlag() && len(src[total:]) > 0 { 568 | this.password, n, err = readLPBytes(src[total:]) 569 | total += n 570 | if err != nil { 571 | return total, err 572 | } 573 | } 574 | 575 | return total, nil 576 | } 577 | 578 | func (this *ConnectMessage) msglen() int { 579 | total := 0 580 | 581 | verstr, ok := SupportedVersions[this.version] 582 | if !ok { 583 | return total 584 | } 585 | 586 | // 2 bytes protocol name length 587 | // n bytes protocol name 588 | // 1 byte protocol version 589 | // 1 byte connect flags 590 | // 2 bytes keep alive timer 591 | total += 2 + len(verstr) + 1 + 1 + 2 592 | 593 | // Add the clientID length, 2 is the length prefix 594 | total += 2 + len(this.clientId) 595 | 596 | // Add the will topic and will message length, and the length prefixes 597 | if this.WillFlag() { 598 | total += 2 + len(this.willTopic) + 2 + len(this.willMessage) 599 | } 600 | 601 | // Add the username length 602 | // According to the 3.1 spec, it's possible that the usernameFlag is set, 603 | // but the user name string is missing. 604 | if this.UsernameFlag() && len(this.username) > 0 { 605 | total += 2 + len(this.username) 606 | } 607 | 608 | // Add the password length 609 | // According to the 3.1 spec, it's possible that the passwordFlag is set, 610 | // but the password string is missing. 611 | if this.PasswordFlag() && len(this.password) > 0 { 612 | total += 2 + len(this.password) 613 | } 614 | 615 | return total 616 | } 617 | 618 | // validClientId checks the client ID, which is a slice of bytes, to see if it's valid. 619 | // Client ID is valid if it meets the requirement from the MQTT spec: 620 | // The Server MUST allow ClientIds which are between 1 and 23 UTF-8 encoded bytes in length, 621 | // and that contain only the characters 622 | // 623 | // "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 624 | func (this *ConnectMessage) validClientId(cid []byte) bool { 625 | // Fixed https://github.com/surgemq/surgemq/issues/4 626 | //if len(cid) > 23 { 627 | // return false 628 | //} 629 | 630 | if this.Version() == 0x3 { 631 | return true 632 | } 633 | 634 | return clientIdRegexp.Match(cid) 635 | } 636 | -------------------------------------------------------------------------------- /connect_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | ) 22 | 23 | func TestConnectMessageFields(t *testing.T) { 24 | msg := NewConnectMessage() 25 | 26 | err := msg.SetVersion(0x3) 27 | require.NoError(t, err, "Error setting message version.") 28 | 29 | require.Equal(t, 0x3, int(msg.Version()), "Incorrect version number") 30 | 31 | err = msg.SetVersion(0x5) 32 | require.Error(t, err) 33 | 34 | msg.SetCleanSession(true) 35 | require.True(t, msg.CleanSession(), "Error setting clean session flag.") 36 | 37 | msg.SetCleanSession(false) 38 | require.False(t, msg.CleanSession(), "Error setting clean session flag.") 39 | 40 | msg.SetWillFlag(true) 41 | require.True(t, msg.WillFlag(), "Error setting will flag.") 42 | 43 | msg.SetWillFlag(false) 44 | require.False(t, msg.WillFlag(), "Error setting will flag.") 45 | 46 | msg.SetWillRetain(true) 47 | require.True(t, msg.WillRetain(), "Error setting will retain.") 48 | 49 | msg.SetWillRetain(false) 50 | require.False(t, msg.WillRetain(), "Error setting will retain.") 51 | 52 | msg.SetPasswordFlag(true) 53 | require.True(t, msg.PasswordFlag(), "Error setting password flag.") 54 | 55 | msg.SetPasswordFlag(false) 56 | require.False(t, msg.PasswordFlag(), "Error setting password flag.") 57 | 58 | msg.SetUsernameFlag(true) 59 | require.True(t, msg.UsernameFlag(), "Error setting username flag.") 60 | 61 | msg.SetUsernameFlag(false) 62 | require.False(t, msg.UsernameFlag(), "Error setting username flag.") 63 | 64 | msg.SetWillQos(1) 65 | require.Equal(t, 1, int(msg.WillQos()), "Error setting will QoS.") 66 | 67 | err = msg.SetWillQos(4) 68 | require.Error(t, err) 69 | 70 | err = msg.SetClientId([]byte("j0j0jfajf02j0asdjf")) 71 | require.NoError(t, err, "Error setting client ID") 72 | 73 | require.Equal(t, "j0j0jfajf02j0asdjf", string(msg.ClientId()), "Error setting client ID.") 74 | 75 | err = msg.SetClientId([]byte("this is good for v3")) 76 | require.NoError(t, err) 77 | 78 | msg.SetVersion(0x4) 79 | 80 | err = msg.SetClientId([]byte("this is no good for v4!")) 81 | require.Error(t, err) 82 | 83 | msg.SetVersion(0x3) 84 | 85 | msg.SetWillTopic([]byte("willtopic")) 86 | require.Equal(t, "willtopic", string(msg.WillTopic()), "Error setting will topic.") 87 | 88 | require.True(t, msg.WillFlag(), "Error setting will flag.") 89 | 90 | msg.SetWillTopic([]byte("")) 91 | require.Equal(t, "", string(msg.WillTopic()), "Error setting will topic.") 92 | 93 | require.False(t, msg.WillFlag(), "Error setting will flag.") 94 | 95 | msg.SetWillMessage([]byte("this is a will message")) 96 | require.Equal(t, "this is a will message", string(msg.WillMessage()), "Error setting will message.") 97 | 98 | require.True(t, msg.WillFlag(), "Error setting will flag.") 99 | 100 | msg.SetWillMessage([]byte("")) 101 | require.Equal(t, "", string(msg.WillMessage()), "Error setting will topic.") 102 | 103 | require.False(t, msg.WillFlag(), "Error setting will flag.") 104 | 105 | msg.SetWillTopic([]byte("willtopic")) 106 | msg.SetWillMessage([]byte("this is a will message")) 107 | msg.SetWillTopic([]byte("")) 108 | require.True(t, msg.WillFlag(), "Error setting will topic.") 109 | 110 | msg.SetUsername([]byte("myname")) 111 | require.Equal(t, "myname", string(msg.Username()), "Error setting will message.") 112 | 113 | require.True(t, msg.UsernameFlag(), "Error setting will flag.") 114 | 115 | msg.SetUsername([]byte("")) 116 | require.Equal(t, "", string(msg.Username()), "Error setting will message.") 117 | 118 | require.False(t, msg.UsernameFlag(), "Error setting will flag.") 119 | 120 | msg.SetPassword([]byte("myname")) 121 | require.Equal(t, "myname", string(msg.Password()), "Error setting will message.") 122 | 123 | require.True(t, msg.PasswordFlag(), "Error setting will flag.") 124 | 125 | msg.SetPassword([]byte("")) 126 | require.Equal(t, "", string(msg.Password()), "Error setting will message.") 127 | 128 | require.False(t, msg.PasswordFlag(), "Error setting will flag.") 129 | } 130 | 131 | func TestConnectMessageDecode(t *testing.T) { 132 | msgBytes := []byte{ 133 | byte(CONNECT << 4), 134 | 60, 135 | 0, // Length MSB (0) 136 | 4, // Length LSB (4) 137 | 'M', 'Q', 'T', 'T', 138 | 4, // Protocol level 4 139 | 206, // connect flags 11001110, will QoS = 01 140 | 0, // Keep Alive MSB (0) 141 | 10, // Keep Alive LSB (10) 142 | 0, // Client ID MSB (0) 143 | 7, // Client ID LSB (7) 144 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 145 | 0, // Will Topic MSB (0) 146 | 4, // Will Topic LSB (4) 147 | 'w', 'i', 'l', 'l', 148 | 0, // Will Message MSB (0) 149 | 12, // Will Message LSB (12) 150 | 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', 151 | 0, // Username ID MSB (0) 152 | 7, // Username ID LSB (7) 153 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 154 | 0, // Password ID MSB (0) 155 | 10, // Password ID LSB (10) 156 | 'v', 'e', 'r', 'y', 's', 'e', 'c', 'r', 'e', 't', 157 | } 158 | 159 | msg := NewConnectMessage() 160 | n, err := msg.Decode(msgBytes) 161 | 162 | require.NoError(t, err, "Error decoding message.") 163 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 164 | require.Equal(t, 206, int(msg.connectFlags), "Incorrect flag value.") 165 | require.Equal(t, 10, int(msg.KeepAlive()), "Incorrect KeepAlive value.") 166 | require.Equal(t, "surgemq", string(msg.ClientId()), "Incorrect client ID value.") 167 | require.Equal(t, "will", string(msg.WillTopic()), "Incorrect will topic value.") 168 | require.Equal(t, "send me home", string(msg.WillMessage()), "Incorrect will message value.") 169 | require.Equal(t, "surgemq", string(msg.Username()), "Incorrect username value.") 170 | require.Equal(t, "verysecret", string(msg.Password()), "Incorrect password value.") 171 | } 172 | 173 | func TestConnectMessageDecode2(t *testing.T) { 174 | // missing last byte 't' 175 | msgBytes := []byte{ 176 | byte(CONNECT << 4), 177 | 60, 178 | 0, // Length MSB (0) 179 | 4, // Length LSB (4) 180 | 'M', 'Q', 'T', 'T', 181 | 4, // Protocol level 4 182 | 206, // connect flags 11001110, will QoS = 01 183 | 0, // Keep Alive MSB (0) 184 | 10, // Keep Alive LSB (10) 185 | 0, // Client ID MSB (0) 186 | 7, // Client ID LSB (7) 187 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 188 | 0, // Will Topic MSB (0) 189 | 4, // Will Topic LSB (4) 190 | 'w', 'i', 'l', 'l', 191 | 0, // Will Message MSB (0) 192 | 12, // Will Message LSB (12) 193 | 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', 194 | 0, // Username ID MSB (0) 195 | 7, // Username ID LSB (7) 196 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 197 | 0, // Password ID MSB (0) 198 | 10, // Password ID LSB (10) 199 | 'v', 'e', 'r', 'y', 's', 'e', 'c', 'r', 'e', 200 | } 201 | 202 | msg := NewConnectMessage() 203 | _, err := msg.Decode(msgBytes) 204 | 205 | require.Error(t, err) 206 | } 207 | 208 | func TestConnectMessageDecode3(t *testing.T) { 209 | // extra bytes 210 | msgBytes := []byte{ 211 | byte(CONNECT << 4), 212 | 60, 213 | 0, // Length MSB (0) 214 | 4, // Length LSB (4) 215 | 'M', 'Q', 'T', 'T', 216 | 4, // Protocol level 4 217 | 206, // connect flags 11001110, will QoS = 01 218 | 0, // Keep Alive MSB (0) 219 | 10, // Keep Alive LSB (10) 220 | 0, // Client ID MSB (0) 221 | 7, // Client ID LSB (7) 222 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 223 | 0, // Will Topic MSB (0) 224 | 4, // Will Topic LSB (4) 225 | 'w', 'i', 'l', 'l', 226 | 0, // Will Message MSB (0) 227 | 12, // Will Message LSB (12) 228 | 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', 229 | 0, // Username ID MSB (0) 230 | 7, // Username ID LSB (7) 231 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 232 | 0, // Password ID MSB (0) 233 | 10, // Password ID LSB (10) 234 | 'v', 'e', 'r', 'y', 's', 'e', 'c', 'r', 'e', 't', 235 | 'e', 'x', 't', 'r', 'a', 236 | } 237 | 238 | msg := NewConnectMessage() 239 | n, err := msg.Decode(msgBytes) 240 | 241 | require.NoError(t, err) 242 | require.Equal(t, 62, n) 243 | } 244 | 245 | func TestConnectMessageDecode4(t *testing.T) { 246 | // missing client Id, clean session == 0 247 | msgBytes := []byte{ 248 | byte(CONNECT << 4), 249 | 53, 250 | 0, // Length MSB (0) 251 | 4, // Length LSB (4) 252 | 'M', 'Q', 'T', 'T', 253 | 4, // Protocol level 4 254 | 204, // connect flags 11001110, will QoS = 01 255 | 0, // Keep Alive MSB (0) 256 | 10, // Keep Alive LSB (10) 257 | 0, // Client ID MSB (0) 258 | 0, // Client ID LSB (0) 259 | 0, // Will Topic MSB (0) 260 | 4, // Will Topic LSB (4) 261 | 'w', 'i', 'l', 'l', 262 | 0, // Will Message MSB (0) 263 | 12, // Will Message LSB (12) 264 | 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', 265 | 0, // Username ID MSB (0) 266 | 7, // Username ID LSB (7) 267 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 268 | 0, // Password ID MSB (0) 269 | 10, // Password ID LSB (10) 270 | 'v', 'e', 'r', 'y', 's', 'e', 'c', 'r', 'e', 't', 271 | } 272 | 273 | msg := NewConnectMessage() 274 | _, err := msg.Decode(msgBytes) 275 | 276 | require.Error(t, err) 277 | } 278 | 279 | func TestConnectMessageEncode(t *testing.T) { 280 | msgBytes := []byte{ 281 | byte(CONNECT << 4), 282 | 60, 283 | 0, // Length MSB (0) 284 | 4, // Length LSB (4) 285 | 'M', 'Q', 'T', 'T', 286 | 4, // Protocol level 4 287 | 206, // connect flags 11001110, will QoS = 01 288 | 0, // Keep Alive MSB (0) 289 | 10, // Keep Alive LSB (10) 290 | 0, // Client ID MSB (0) 291 | 7, // Client ID LSB (7) 292 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 293 | 0, // Will Topic MSB (0) 294 | 4, // Will Topic LSB (4) 295 | 'w', 'i', 'l', 'l', 296 | 0, // Will Message MSB (0) 297 | 12, // Will Message LSB (12) 298 | 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', 299 | 0, // Username ID MSB (0) 300 | 7, // Username ID LSB (7) 301 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 302 | 0, // Password ID MSB (0) 303 | 10, // Password ID LSB (10) 304 | 'v', 'e', 'r', 'y', 's', 'e', 'c', 'r', 'e', 't', 305 | } 306 | 307 | msg := NewConnectMessage() 308 | msg.SetWillQos(1) 309 | msg.SetVersion(4) 310 | msg.SetCleanSession(true) 311 | msg.SetClientId([]byte("surgemq")) 312 | msg.SetKeepAlive(10) 313 | msg.SetWillTopic([]byte("will")) 314 | msg.SetWillMessage([]byte("send me home")) 315 | msg.SetUsername([]byte("surgemq")) 316 | msg.SetPassword([]byte("verysecret")) 317 | 318 | dst := make([]byte, 100) 319 | n, err := msg.Encode(dst) 320 | 321 | require.NoError(t, err, "Error decoding message.") 322 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 323 | require.Equal(t, msgBytes, dst[:n], "Error decoding message.") 324 | } 325 | 326 | // test to ensure encoding and decoding are the same 327 | // decode, encode, and decode again 328 | func TestConnectDecodeEncodeEquiv(t *testing.T) { 329 | msgBytes := []byte{ 330 | byte(CONNECT << 4), 331 | 60, 332 | 0, // Length MSB (0) 333 | 4, // Length LSB (4) 334 | 'M', 'Q', 'T', 'T', 335 | 4, // Protocol level 4 336 | 206, // connect flags 11001110, will QoS = 01 337 | 0, // Keep Alive MSB (0) 338 | 10, // Keep Alive LSB (10) 339 | 0, // Client ID MSB (0) 340 | 7, // Client ID LSB (7) 341 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 342 | 0, // Will Topic MSB (0) 343 | 4, // Will Topic LSB (4) 344 | 'w', 'i', 'l', 'l', 345 | 0, // Will Message MSB (0) 346 | 12, // Will Message LSB (12) 347 | 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', 348 | 0, // Username ID MSB (0) 349 | 7, // Username ID LSB (7) 350 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 351 | 0, // Password ID MSB (0) 352 | 10, // Password ID LSB (10) 353 | 'v', 'e', 'r', 'y', 's', 'e', 'c', 'r', 'e', 't', 354 | } 355 | 356 | msg := NewConnectMessage() 357 | n, err := msg.Decode(msgBytes) 358 | 359 | require.NoError(t, err, "Error decoding message.") 360 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 361 | 362 | dst := make([]byte, 100) 363 | n2, err := msg.Encode(dst) 364 | 365 | require.NoError(t, err, "Error decoding message.") 366 | require.Equal(t, len(msgBytes), n2, "Error decoding message.") 367 | require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") 368 | 369 | n3, err := msg.Decode(dst) 370 | 371 | require.NoError(t, err, "Error decoding message.") 372 | require.Equal(t, len(msgBytes), n3, "Error decoding message.") 373 | } 374 | -------------------------------------------------------------------------------- /disconnect.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | import "fmt" 18 | 19 | // The DISCONNECT Packet is the final Control Packet sent from the Client to the Server. 20 | // It indicates that the Client is disconnecting cleanly. 21 | type DisconnectMessage struct { 22 | header 23 | } 24 | 25 | var _ Message = (*DisconnectMessage)(nil) 26 | 27 | // NewDisconnectMessage creates a new DISCONNECT message. 28 | func NewDisconnectMessage() *DisconnectMessage { 29 | msg := &DisconnectMessage{} 30 | msg.SetType(DISCONNECT) 31 | 32 | return msg 33 | } 34 | 35 | func (this *DisconnectMessage) Decode(src []byte) (int, error) { 36 | return this.header.decode(src) 37 | } 38 | 39 | func (this *DisconnectMessage) Encode(dst []byte) (int, error) { 40 | if !this.dirty { 41 | if len(dst) < len(this.dbuf) { 42 | return 0, fmt.Errorf("disconnect/Encode: Insufficient buffer size. Expecting %d, got %d.", len(this.dbuf), len(dst)) 43 | } 44 | 45 | return copy(dst, this.dbuf), nil 46 | } 47 | 48 | return this.header.encode(dst) 49 | } 50 | -------------------------------------------------------------------------------- /disconnect_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | ) 22 | 23 | func TestDisconnectMessageDecode(t *testing.T) { 24 | msgBytes := []byte{ 25 | byte(DISCONNECT << 4), 26 | 0, 27 | } 28 | 29 | msg := NewDisconnectMessage() 30 | n, err := msg.Decode(msgBytes) 31 | 32 | require.NoError(t, err, "Error decoding message.") 33 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 34 | require.Equal(t, DISCONNECT, msg.Type(), "Error decoding message.") 35 | } 36 | 37 | func TestDisconnectMessageEncode(t *testing.T) { 38 | msgBytes := []byte{ 39 | byte(DISCONNECT << 4), 40 | 0, 41 | } 42 | 43 | msg := NewDisconnectMessage() 44 | 45 | dst := make([]byte, 10) 46 | n, err := msg.Encode(dst) 47 | 48 | require.NoError(t, err, "Error decoding message.") 49 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 50 | require.Equal(t, msgBytes, dst[:n], "Error decoding message.") 51 | } 52 | 53 | // test to ensure encoding and decoding are the same 54 | // decode, encode, and decode again 55 | func TestDisconnectDecodeEncodeEquiv(t *testing.T) { 56 | msgBytes := []byte{ 57 | byte(DISCONNECT << 4), 58 | 0, 59 | } 60 | 61 | msg := NewDisconnectMessage() 62 | n, err := msg.Decode(msgBytes) 63 | 64 | require.NoError(t, err, "Error decoding message.") 65 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 66 | 67 | dst := make([]byte, 100) 68 | n2, err := msg.Encode(dst) 69 | 70 | require.NoError(t, err, "Error decoding message.") 71 | require.Equal(t, len(msgBytes), n2, "Error decoding message.") 72 | require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") 73 | 74 | n3, err := msg.Decode(dst) 75 | 76 | require.NoError(t, err, "Error decoding message.") 77 | require.Equal(t, len(msgBytes), n3, "Error decoding message.") 78 | } 79 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | Package message is an encoder/decoder library for MQTT 3.1 and 3.1.1 messages. You can 17 | find the MQTT specs at the following locations: 18 | 19 | 3.1.1 - http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/ 20 | 3.1 - http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html 21 | 22 | From the spec: 23 | 24 | MQTT is a Client Server publish/subscribe messaging transport protocol. It is 25 | light weight, open, simple, and designed so as to be easy to implement. These 26 | characteristics make it ideal for use in many situations, including constrained 27 | environments such as for communication in Machine to Machine (M2M) and Internet 28 | of Things (IoT) contexts where a small code footprint is required and/or network 29 | bandwidth is at a premium. 30 | 31 | The MQTT protocol works by exchanging a series of MQTT messages in a defined way. 32 | The protocol runs over TCP/IP, or over other network protocols that provide 33 | ordered, lossless, bi-directional connections. 34 | 35 | 36 | There are two main items to take note in this package. The first is 37 | 38 | type MessageType byte 39 | 40 | MessageType is the type representing the MQTT packet types. In the MQTT spec, MQTT 41 | control packet type is represented as a 4-bit unsigned value. MessageType receives 42 | several methods that returns string representations of the names and descriptions. 43 | 44 | Also, one of the methods is New(). It returns a new Message object based on the mtype 45 | parameter. For example: 46 | 47 | m, err := CONNECT.New() 48 | msg := m.(*ConnectMessage) 49 | 50 | This would return a PublishMessage struct, but mapped to the Message interface. You can 51 | then type assert it back to a *PublishMessage. Another way to create a new 52 | PublishMessage is to call 53 | 54 | msg := NewConnectMessage() 55 | 56 | Every message type has a New function that returns a new message. The list of available 57 | message types are defined as constants below. 58 | 59 | As you may have noticed, the second important item is the Message interface. It defines 60 | several methods that are common to all messages, including Name(), Desc(), and Type(). 61 | Most importantly, it also defines the Encode() and Decode() methods. 62 | 63 | Encode() (io.Reader, int, error) 64 | Decode(io.Reader) (int, error) 65 | 66 | Encode returns an io.Reader in which the encoded bytes can be read. The second return 67 | value is the number of bytes encoded, so the caller knows how many bytes there will be. 68 | If Encode returns an error, then the first two return values should be considered invalid. 69 | Any changes to the message after Encode() is called will invalidate the io.Reader. 70 | 71 | Decode reads from the io.Reader parameter until a full message is decoded, or when io.Reader 72 | returns EOF or error. The first return value is the number of bytes read from io.Reader. 73 | The second is error if Decode encounters any problems. 74 | 75 | With these in mind, we can now do: 76 | 77 | // Create a new CONNECT message 78 | msg := NewConnectMessage() 79 | 80 | // Set the appropriate parameters 81 | msg.SetWillQos(1) 82 | msg.SetVersion(4) 83 | msg.SetCleanSession(true) 84 | msg.SetClientId([]byte("surgemq")) 85 | msg.SetKeepAlive(10) 86 | msg.SetWillTopic([]byte("will")) 87 | msg.SetWillMessage([]byte("send me home")) 88 | msg.SetUsername([]byte("surgemq")) 89 | msg.SetPassword([]byte("verysecret")) 90 | 91 | // Encode the message and get the io.Reader 92 | r, n, err := msg.Encode() 93 | if err == nil { 94 | return err 95 | } 96 | 97 | // Write n bytes into the connection 98 | m, err := io.CopyN(conn, r, int64(n)) 99 | if err != nil { 100 | return err 101 | } 102 | 103 | fmt.Printf("Sent %d bytes of %s message", m, msg.Name()) 104 | 105 | To receive a CONNECT message from a connection, we can do: 106 | 107 | // Create a new CONNECT message 108 | msg := NewConnectMessage() 109 | 110 | // Decode the message by reading from conn 111 | n, err := msg.Decode(conn) 112 | 113 | If you don't know what type of message is coming down the pipe, you can do something like this: 114 | 115 | // Create a buffered IO reader for the connection 116 | br := bufio.NewReader(conn) 117 | 118 | // Peek at the first byte, which contains the message type 119 | b, err := br.Peek(1) 120 | if err != nil { 121 | return err 122 | } 123 | 124 | // Extract the type from the first byte 125 | t := MessageType(b[0] >> 4) 126 | 127 | // Create a new message 128 | msg, err := t.New() 129 | if err != nil { 130 | return err 131 | } 132 | 133 | // Decode it from the bufio.Reader 134 | n, err := msg.Decode(br) 135 | if err != nil { 136 | return err 137 | } 138 | 139 | 140 | */ 141 | package message 142 | -------------------------------------------------------------------------------- /header.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | import ( 18 | "encoding/binary" 19 | "fmt" 20 | ) 21 | 22 | var ( 23 | gPacketId uint64 = 0 24 | ) 25 | 26 | // Fixed header 27 | // - 1 byte for control packet type (bits 7-4) and flags (bits 3-0) 28 | // - up to 4 byte for remaining length 29 | type header struct { 30 | // Header fields 31 | //mtype MessageType 32 | //flags byte 33 | remlen int32 34 | 35 | // mtypeflags is the first byte of the buffer, 4 bits for mtype, 4 bits for flags 36 | mtypeflags []byte 37 | 38 | // Some messages need packet ID, 2 byte uint16 39 | packetId []byte 40 | 41 | // Points to the decoding buffer 42 | dbuf []byte 43 | 44 | // Whether the message has changed since last decode 45 | dirty bool 46 | } 47 | 48 | // String returns a string representation of the message. 49 | func (this header) String() string { 50 | return fmt.Sprintf("Type=%q, Flags=%08b, Remaining Length=%d", this.Type().Name(), this.Flags(), this.remlen) 51 | } 52 | 53 | // Name returns a string representation of the message type. Examples include 54 | // "PUBLISH", "SUBSCRIBE", and others. This is statically defined for each of 55 | // the message types and cannot be changed. 56 | func (this *header) Name() string { 57 | return this.Type().Name() 58 | } 59 | 60 | // Desc returns a string description of the message type. For example, a 61 | // CONNECT message would return "Client request to connect to Server." These 62 | // descriptions are statically defined (copied from the MQTT spec) and cannot 63 | // be changed. 64 | func (this *header) Desc() string { 65 | return this.Type().Desc() 66 | } 67 | 68 | // Type returns the MessageType of the Message. The retured value should be one 69 | // of the constants defined for MessageType. 70 | func (this *header) Type() MessageType { 71 | //return this.mtype 72 | if len(this.mtypeflags) != 1 { 73 | this.mtypeflags = make([]byte, 1) 74 | this.dirty = true 75 | } 76 | 77 | return MessageType(this.mtypeflags[0] >> 4) 78 | } 79 | 80 | // SetType sets the message type of this message. It also correctly sets the 81 | // default flags for the message type. It returns an error if the type is invalid. 82 | func (this *header) SetType(mtype MessageType) error { 83 | if !mtype.Valid() { 84 | return fmt.Errorf("header/SetType: Invalid control packet type %d", mtype) 85 | } 86 | 87 | // Notice we don't set the message to be dirty when we are not allocating a new 88 | // buffer. In this case, it means the buffer is probably a sub-slice of another 89 | // slice. If that's the case, then during encoding we would have copied the whole 90 | // backing buffer anyway. 91 | if len(this.mtypeflags) != 1 { 92 | this.mtypeflags = make([]byte, 1) 93 | this.dirty = true 94 | } 95 | 96 | this.mtypeflags[0] = byte(mtype)<<4 | (mtype.DefaultFlags() & 0xf) 97 | 98 | return nil 99 | } 100 | 101 | // Flags returns the fixed header flags for this message. 102 | func (this *header) Flags() byte { 103 | //return this.flags 104 | return this.mtypeflags[0] & 0x0f 105 | } 106 | 107 | // RemainingLength returns the length of the non-fixed-header part of the message. 108 | func (this *header) RemainingLength() int32 { 109 | return this.remlen 110 | } 111 | 112 | // SetRemainingLength sets the length of the non-fixed-header part of the message. 113 | // It returns error if the length is greater than 268435455, which is the max 114 | // message length as defined by the MQTT spec. 115 | func (this *header) SetRemainingLength(remlen int32) error { 116 | if remlen > maxRemainingLength || remlen < 0 { 117 | return fmt.Errorf("header/SetLength: Remaining length (%d) out of bound (max %d, min 0)", remlen, maxRemainingLength) 118 | } 119 | 120 | this.remlen = remlen 121 | this.dirty = true 122 | 123 | return nil 124 | } 125 | 126 | func (this *header) Len() int { 127 | return this.msglen() 128 | } 129 | 130 | // PacketId returns the ID of the packet. 131 | func (this *header) PacketId() uint16 { 132 | if len(this.packetId) == 2 { 133 | return binary.BigEndian.Uint16(this.packetId) 134 | } 135 | 136 | return 0 137 | } 138 | 139 | // SetPacketId sets the ID of the packet. 140 | func (this *header) SetPacketId(v uint16) { 141 | // If setting to 0, nothing to do, move on 142 | if v == 0 { 143 | return 144 | } 145 | 146 | // If packetId buffer is not 2 bytes (uint16), then we allocate a new one and 147 | // make dirty. Then we encode the packet ID into the buffer. 148 | if len(this.packetId) != 2 { 149 | this.packetId = make([]byte, 2) 150 | this.dirty = true 151 | } 152 | 153 | // Notice we don't set the message to be dirty when we are not allocating a new 154 | // buffer. In this case, it means the buffer is probably a sub-slice of another 155 | // slice. If that's the case, then during encoding we would have copied the whole 156 | // backing buffer anyway. 157 | binary.BigEndian.PutUint16(this.packetId, v) 158 | } 159 | 160 | func (this *header) encode(dst []byte) (int, error) { 161 | ml := this.msglen() 162 | 163 | if len(dst) < ml { 164 | return 0, fmt.Errorf("header/Encode: Insufficient buffer size. Expecting %d, got %d.", ml, len(dst)) 165 | } 166 | 167 | total := 0 168 | 169 | if this.remlen > maxRemainingLength || this.remlen < 0 { 170 | return total, fmt.Errorf("header/Encode: Remaining length (%d) out of bound (max %d, min 0)", this.remlen, maxRemainingLength) 171 | } 172 | 173 | if !this.Type().Valid() { 174 | return total, fmt.Errorf("header/Encode: Invalid message type %d", this.Type()) 175 | } 176 | 177 | dst[total] = this.mtypeflags[0] 178 | total += 1 179 | 180 | n := binary.PutUvarint(dst[total:], uint64(this.remlen)) 181 | total += n 182 | 183 | return total, nil 184 | } 185 | 186 | // Decode reads from the io.Reader parameter until a full message is decoded, or 187 | // when io.Reader returns EOF or error. The first return value is the number of 188 | // bytes read from io.Reader. The second is error if Decode encounters any problems. 189 | func (this *header) decode(src []byte) (int, error) { 190 | total := 0 191 | 192 | this.dbuf = src 193 | 194 | mtype := this.Type() 195 | //mtype := MessageType(0) 196 | 197 | this.mtypeflags = src[total : total+1] 198 | //mtype := MessageType(src[total] >> 4) 199 | if !this.Type().Valid() { 200 | return total, fmt.Errorf("header/Decode: Invalid message type %d.", mtype) 201 | } 202 | 203 | if mtype != this.Type() { 204 | return total, fmt.Errorf("header/Decode: Invalid message type %d. Expecting %d.", this.Type(), mtype) 205 | } 206 | 207 | //this.flags = src[total] & 0x0f 208 | if this.Type() != PUBLISH && this.Flags() != this.Type().DefaultFlags() { 209 | return total, fmt.Errorf("header/Decode: Invalid message (%d) flags. Expecting %d, got %d", this.Type(), this.Type().DefaultFlags(), this.Flags()) 210 | } 211 | 212 | if this.Type() == PUBLISH && !ValidQos((this.Flags()>>1)&0x3) { 213 | return total, fmt.Errorf("header/Decode: Invalid QoS (%d) for PUBLISH message.", (this.Flags()>>1)&0x3) 214 | } 215 | 216 | total++ 217 | 218 | remlen, m := binary.Uvarint(src[total:]) 219 | total += m 220 | this.remlen = int32(remlen) 221 | 222 | if this.remlen > maxRemainingLength || remlen < 0 { 223 | return total, fmt.Errorf("header/Decode: Remaining length (%d) out of bound (max %d, min 0)", this.remlen, maxRemainingLength) 224 | } 225 | 226 | if int(this.remlen) > len(src[total:]) { 227 | return total, fmt.Errorf("header/Decode: Remaining length (%d) is greater than remaining buffer (%d)", this.remlen, len(src[total:])) 228 | } 229 | 230 | return total, nil 231 | } 232 | 233 | func (this *header) msglen() int { 234 | // message type and flag byte 235 | total := 1 236 | 237 | if this.remlen <= 127 { 238 | total += 1 239 | } else if this.remlen <= 16383 { 240 | total += 2 241 | } else if this.remlen <= 2097151 { 242 | total += 3 243 | } else { 244 | total += 4 245 | } 246 | 247 | return total 248 | } 249 | -------------------------------------------------------------------------------- /header_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | ) 22 | 23 | func TestMessageHeaderFields(t *testing.T) { 24 | header := &header{} 25 | 26 | header.SetRemainingLength(33) 27 | 28 | require.Equal(t, int32(33), header.RemainingLength()) 29 | 30 | err := header.SetRemainingLength(268435456) 31 | 32 | require.Error(t, err) 33 | 34 | err = header.SetRemainingLength(-1) 35 | 36 | require.Error(t, err) 37 | 38 | err = header.SetType(RESERVED) 39 | 40 | require.Error(t, err) 41 | 42 | err = header.SetType(PUBREL) 43 | 44 | require.NoError(t, err) 45 | require.Equal(t, PUBREL, header.Type()) 46 | require.Equal(t, "PUBREL", header.Name()) 47 | require.Equal(t, 2, int(header.Flags())) 48 | } 49 | 50 | // Not enough bytes 51 | func TestMessageHeaderDecode(t *testing.T) { 52 | buf := []byte{0x6f, 193, 2} 53 | header := &header{} 54 | 55 | _, err := header.decode(buf) 56 | require.Error(t, err) 57 | } 58 | 59 | // Remaining length too big 60 | func TestMessageHeaderDecode2(t *testing.T) { 61 | buf := []byte{0x62, 0xff, 0xff, 0xff, 0xff} 62 | header := &header{} 63 | 64 | _, err := header.decode(buf) 65 | require.Error(t, err) 66 | } 67 | 68 | func TestMessageHeaderDecode3(t *testing.T) { 69 | buf := []byte{0x62, 0xff} 70 | header := &header{} 71 | 72 | _, err := header.decode(buf) 73 | require.Error(t, err) 74 | } 75 | 76 | func TestMessageHeaderDecode4(t *testing.T) { 77 | buf := []byte{0x62, 0xff, 0xff, 0xff, 0x7f} 78 | header := &header{ 79 | mtypeflags: []byte{6<<4 | 2}, 80 | //mtype: 6, 81 | //flags: 2, 82 | } 83 | 84 | n, err := header.decode(buf) 85 | 86 | require.Error(t, err) 87 | require.Equal(t, 5, n) 88 | require.Equal(t, maxRemainingLength, header.RemainingLength()) 89 | } 90 | 91 | func TestMessageHeaderDecode5(t *testing.T) { 92 | buf := []byte{0x62, 0xff, 0x7f} 93 | header := &header{ 94 | mtypeflags: []byte{6<<4 | 2}, 95 | //mtype: 6, 96 | //flags: 2, 97 | } 98 | 99 | n, err := header.decode(buf) 100 | require.Error(t, err) 101 | require.Equal(t, 3, n) 102 | } 103 | 104 | func TestMessageHeaderEncode1(t *testing.T) { 105 | header := &header{} 106 | headerBytes := []byte{0x62, 193, 2} 107 | 108 | err := header.SetType(PUBREL) 109 | 110 | require.NoError(t, err) 111 | 112 | err = header.SetRemainingLength(321) 113 | 114 | require.NoError(t, err) 115 | 116 | buf := make([]byte, 3) 117 | n, err := header.encode(buf) 118 | 119 | require.NoError(t, err) 120 | require.Equal(t, 3, n) 121 | require.Equal(t, headerBytes, buf) 122 | } 123 | 124 | func TestMessageHeaderEncode2(t *testing.T) { 125 | header := &header{} 126 | 127 | err := header.SetType(PUBREL) 128 | require.NoError(t, err) 129 | 130 | header.remlen = 268435456 131 | 132 | buf := make([]byte, 5) 133 | _, err = header.encode(buf) 134 | 135 | require.Error(t, err) 136 | } 137 | 138 | func TestMessageHeaderEncode3(t *testing.T) { 139 | header := &header{} 140 | headerBytes := []byte{0x62, 0xff, 0xff, 0xff, 0x7f} 141 | 142 | err := header.SetType(PUBREL) 143 | 144 | require.NoError(t, err) 145 | 146 | err = header.SetRemainingLength(maxRemainingLength) 147 | 148 | require.NoError(t, err) 149 | 150 | buf := make([]byte, 5) 151 | n, err := header.encode(buf) 152 | 153 | require.NoError(t, err) 154 | require.Equal(t, 5, n) 155 | require.Equal(t, headerBytes, buf) 156 | } 157 | 158 | func TestMessageHeaderEncode4(t *testing.T) { 159 | header := &header{ 160 | mtypeflags: []byte{byte(RESERVED2) << 4}, 161 | //mtype: 6, 162 | //flags: 2, 163 | } 164 | 165 | buf := make([]byte, 5) 166 | _, err := header.encode(buf) 167 | require.Error(t, err) 168 | } 169 | 170 | /* 171 | // This test is to ensure that an empty message is at least 2 bytes long 172 | func TestMessageHeaderEncode5(t *testing.T) { 173 | msg := NewPingreqMessage() 174 | 175 | dst, n, err := msg.encode() 176 | if err != nil { 177 | t.Errorf("Error encoding PINGREQ message: %v", err) 178 | } else if n != 2 { 179 | t.Errorf("Incorrect result. Expecting length of 2 bytes, got %d.", dst.(*bytes.Buffer).Len()) 180 | } 181 | } 182 | */ 183 | -------------------------------------------------------------------------------- /message.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | import ( 18 | "bytes" 19 | "encoding/binary" 20 | "fmt" 21 | ) 22 | 23 | const ( 24 | maxLPString uint16 = 65535 25 | maxFixedHeaderLength int = 5 26 | maxRemainingLength int32 = 268435455 // bytes, or 256 MB 27 | ) 28 | 29 | const ( 30 | // QoS 0: At most once delivery 31 | // The message is delivered according to the capabilities of the underlying network. 32 | // No response is sent by the receiver and no retry is performed by the sender. The 33 | // message arrives at the receiver either once or not at all. 34 | QosAtMostOnce byte = iota 35 | 36 | // QoS 1: At least once delivery 37 | // This quality of service ensures that the message arrives at the receiver at least once. 38 | // A QoS 1 PUBLISH Packet has a Packet Identifier in its variable header and is acknowledged 39 | // by a PUBACK Packet. Section 2.3.1 provides more information about Packet Identifiers. 40 | QosAtLeastOnce 41 | 42 | // QoS 2: Exactly once delivery 43 | // This is the highest quality of service, for use when neither loss nor duplication of 44 | // messages are acceptable. There is an increased overhead associated with this quality of 45 | // service. 46 | QosExactlyOnce 47 | 48 | // QosFailure is a return value for a subscription if there's a problem while subscribing 49 | // to a specific topic. 50 | QosFailure = 0x80 51 | ) 52 | 53 | // SupportedVersions is a map of the version number (0x3 or 0x4) to the version string, 54 | // "MQIsdp" for 0x3, and "MQTT" for 0x4. 55 | var SupportedVersions map[byte]string = map[byte]string{ 56 | 0x3: "MQIsdp", 57 | 0x4: "MQTT", 58 | } 59 | 60 | // MessageType is the type representing the MQTT packet types. In the MQTT spec, 61 | // MQTT control packet type is represented as a 4-bit unsigned value. 62 | type MessageType byte 63 | 64 | // Message is an interface defined for all MQTT message types. 65 | type Message interface { 66 | // Name returns a string representation of the message type. Examples include 67 | // "PUBLISH", "SUBSCRIBE", and others. This is statically defined for each of 68 | // the message types and cannot be changed. 69 | Name() string 70 | 71 | // Desc returns a string description of the message type. For example, a 72 | // CONNECT message would return "Client request to connect to Server." These 73 | // descriptions are statically defined (copied from the MQTT spec) and cannot 74 | // be changed. 75 | Desc() string 76 | 77 | // Type returns the MessageType of the Message. The retured value should be one 78 | // of the constants defined for MessageType. 79 | Type() MessageType 80 | 81 | // PacketId returns the packet ID of the Message. The retured value is 0 if 82 | // there's no packet ID for this message type. Otherwise non-0. 83 | PacketId() uint16 84 | 85 | // Encode writes the message bytes into the byte array from the argument. It 86 | // returns the number of bytes encoded and whether there's any errors along 87 | // the way. If there's any errors, then the byte slice and count should be 88 | // considered invalid. 89 | Encode([]byte) (int, error) 90 | 91 | // Decode reads the bytes in the byte slice from the argument. It returns the 92 | // total number of bytes decoded, and whether there's any errors during the 93 | // process. The byte slice MUST NOT be modified during the duration of this 94 | // message being available since the byte slice is internally stored for 95 | // references. 96 | Decode([]byte) (int, error) 97 | 98 | Len() int 99 | } 100 | 101 | const ( 102 | // RESERVED is a reserved value and should be considered an invalid message type 103 | RESERVED MessageType = iota 104 | 105 | // CONNECT: Client to Server. Client request to connect to Server. 106 | CONNECT 107 | 108 | // CONNACK: Server to Client. Connect acknowledgement. 109 | CONNACK 110 | 111 | // PUBLISH: Client to Server, or Server to Client. Publish message. 112 | PUBLISH 113 | 114 | // PUBACK: Client to Server, or Server to Client. Publish acknowledgment for 115 | // QoS 1 messages. 116 | PUBACK 117 | 118 | // PUBACK: Client to Server, or Server to Client. Publish received for QoS 2 messages. 119 | // Assured delivery part 1. 120 | PUBREC 121 | 122 | // PUBREL: Client to Server, or Server to Client. Publish release for QoS 2 messages. 123 | // Assured delivery part 1. 124 | PUBREL 125 | 126 | // PUBCOMP: Client to Server, or Server to Client. Publish complete for QoS 2 messages. 127 | // Assured delivery part 3. 128 | PUBCOMP 129 | 130 | // SUBSCRIBE: Client to Server. Client subscribe request. 131 | SUBSCRIBE 132 | 133 | // SUBACK: Server to Client. Subscribe acknowledgement. 134 | SUBACK 135 | 136 | // UNSUBSCRIBE: Client to Server. Unsubscribe request. 137 | UNSUBSCRIBE 138 | 139 | // UNSUBACK: Server to Client. Unsubscribe acknowlegment. 140 | UNSUBACK 141 | 142 | // PINGREQ: Client to Server. PING request. 143 | PINGREQ 144 | 145 | // PINGRESP: Server to Client. PING response. 146 | PINGRESP 147 | 148 | // DISCONNECT: Client to Server. Client is disconnecting. 149 | DISCONNECT 150 | 151 | // RESERVED2 is a reserved value and should be considered an invalid message type. 152 | RESERVED2 153 | ) 154 | 155 | func (this MessageType) String() string { 156 | return this.Name() 157 | } 158 | 159 | // Name returns the name of the message type. It should correspond to one of the 160 | // constant values defined for MessageType. It is statically defined and cannot 161 | // be changed. 162 | func (this MessageType) Name() string { 163 | switch this { 164 | case RESERVED: 165 | return "RESERVED" 166 | case CONNECT: 167 | return "CONNECT" 168 | case CONNACK: 169 | return "CONNACK" 170 | case PUBLISH: 171 | return "PUBLISH" 172 | case PUBACK: 173 | return "PUBACK" 174 | case PUBREC: 175 | return "PUBREC" 176 | case PUBREL: 177 | return "PUBREL" 178 | case PUBCOMP: 179 | return "PUBCOMP" 180 | case SUBSCRIBE: 181 | return "SUBSCRIBE" 182 | case SUBACK: 183 | return "SUBACK" 184 | case UNSUBSCRIBE: 185 | return "UNSUBSCRIBE" 186 | case UNSUBACK: 187 | return "UNSUBACK" 188 | case PINGREQ: 189 | return "PINGREQ" 190 | case PINGRESP: 191 | return "PINGRESP" 192 | case DISCONNECT: 193 | return "DISCONNECT" 194 | case RESERVED2: 195 | return "RESERVED2" 196 | } 197 | 198 | return "UNKNOWN" 199 | } 200 | 201 | // Desc returns the description of the message type. It is statically defined (copied 202 | // from MQTT spec) and cannot be changed. 203 | func (this MessageType) Desc() string { 204 | switch this { 205 | case RESERVED: 206 | return "Reserved" 207 | case CONNECT: 208 | return "Client request to connect to Server" 209 | case CONNACK: 210 | return "Connect acknowledgement" 211 | case PUBLISH: 212 | return "Publish message" 213 | case PUBACK: 214 | return "Publish acknowledgement" 215 | case PUBREC: 216 | return "Publish received (assured delivery part 1)" 217 | case PUBREL: 218 | return "Publish release (assured delivery part 2)" 219 | case PUBCOMP: 220 | return "Publish complete (assured delivery part 3)" 221 | case SUBSCRIBE: 222 | return "Client subscribe request" 223 | case SUBACK: 224 | return "Subscribe acknowledgement" 225 | case UNSUBSCRIBE: 226 | return "Unsubscribe request" 227 | case UNSUBACK: 228 | return "Unsubscribe acknowledgement" 229 | case PINGREQ: 230 | return "PING request" 231 | case PINGRESP: 232 | return "PING response" 233 | case DISCONNECT: 234 | return "Client is disconnecting" 235 | case RESERVED2: 236 | return "Reserved" 237 | } 238 | 239 | return "UNKNOWN" 240 | } 241 | 242 | // DefaultFlags returns the default flag values for the message type, as defined by 243 | // the MQTT spec. 244 | func (this MessageType) DefaultFlags() byte { 245 | switch this { 246 | case RESERVED: 247 | return 0 248 | case CONNECT: 249 | return 0 250 | case CONNACK: 251 | return 0 252 | case PUBLISH: 253 | return 0 254 | case PUBACK: 255 | return 0 256 | case PUBREC: 257 | return 0 258 | case PUBREL: 259 | return 2 260 | case PUBCOMP: 261 | return 0 262 | case SUBSCRIBE: 263 | return 2 264 | case SUBACK: 265 | return 0 266 | case UNSUBSCRIBE: 267 | return 2 268 | case UNSUBACK: 269 | return 0 270 | case PINGREQ: 271 | return 0 272 | case PINGRESP: 273 | return 0 274 | case DISCONNECT: 275 | return 0 276 | case RESERVED2: 277 | return 0 278 | } 279 | 280 | return 0 281 | } 282 | 283 | // New creates a new message based on the message type. It is a shortcut to call 284 | // one of the New*Message functions. If an error is returned then the message type 285 | // is invalid. 286 | func (this MessageType) New() (Message, error) { 287 | switch this { 288 | case CONNECT: 289 | return NewConnectMessage(), nil 290 | case CONNACK: 291 | return NewConnackMessage(), nil 292 | case PUBLISH: 293 | return NewPublishMessage(), nil 294 | case PUBACK: 295 | return NewPubackMessage(), nil 296 | case PUBREC: 297 | return NewPubrecMessage(), nil 298 | case PUBREL: 299 | return NewPubrelMessage(), nil 300 | case PUBCOMP: 301 | return NewPubcompMessage(), nil 302 | case SUBSCRIBE: 303 | return NewSubscribeMessage(), nil 304 | case SUBACK: 305 | return NewSubackMessage(), nil 306 | case UNSUBSCRIBE: 307 | return NewUnsubscribeMessage(), nil 308 | case UNSUBACK: 309 | return NewUnsubackMessage(), nil 310 | case PINGREQ: 311 | return NewPingreqMessage(), nil 312 | case PINGRESP: 313 | return NewPingrespMessage(), nil 314 | case DISCONNECT: 315 | return NewDisconnectMessage(), nil 316 | } 317 | 318 | return nil, fmt.Errorf("msgtype/NewMessage: Invalid message type %d", this) 319 | } 320 | 321 | // Valid returns a boolean indicating whether the message type is valid or not. 322 | func (this MessageType) Valid() bool { 323 | return this > RESERVED && this < RESERVED2 324 | } 325 | 326 | // ValidTopic checks the topic, which is a slice of bytes, to see if it's valid. Topic is 327 | // considered valid if it's longer than 0 bytes, and doesn't contain any wildcard characters 328 | // such as + and #. 329 | func ValidTopic(topic []byte) bool { 330 | return len(topic) > 0 && bytes.IndexByte(topic, '#') == -1 && bytes.IndexByte(topic, '+') == -1 331 | } 332 | 333 | // ValidQos checks the QoS value to see if it's valid. Valid QoS are QosAtMostOnce, 334 | // QosAtLeastonce, and QosExactlyOnce. 335 | func ValidQos(qos byte) bool { 336 | return qos == QosAtMostOnce || qos == QosAtLeastOnce || qos == QosExactlyOnce 337 | } 338 | 339 | // ValidVersion checks to see if the version is valid. Current supported versions include 0x3 and 0x4. 340 | func ValidVersion(v byte) bool { 341 | _, ok := SupportedVersions[v] 342 | return ok 343 | } 344 | 345 | // ValidConnackError checks to see if the error is a Connack Error or not 346 | func ValidConnackError(err error) bool { 347 | return err == ErrInvalidProtocolVersion || err == ErrIdentifierRejected || 348 | err == ErrServerUnavailable || err == ErrBadUsernameOrPassword || err == ErrNotAuthorized 349 | } 350 | 351 | // Read length prefixed bytes 352 | func readLPBytes(buf []byte) ([]byte, int, error) { 353 | if len(buf) < 2 { 354 | return nil, 0, fmt.Errorf("utils/readLPBytes: Insufficient buffer size. Expecting %d, got %d.", 2, len(buf)) 355 | } 356 | 357 | n, total := 0, 0 358 | 359 | n = int(binary.BigEndian.Uint16(buf)) 360 | total += 2 361 | 362 | if len(buf) < n { 363 | return nil, total, fmt.Errorf("utils/readLPBytes: Insufficient buffer size. Expecting %d, got %d.", n, len(buf)) 364 | } 365 | 366 | total += n 367 | 368 | return buf[2:total], total, nil 369 | } 370 | 371 | // Write length prefixed bytes 372 | func writeLPBytes(buf []byte, b []byte) (int, error) { 373 | total, n := 0, len(b) 374 | 375 | if n > int(maxLPString) { 376 | return 0, fmt.Errorf("utils/writeLPBytes: Length (%d) greater than %d bytes.", n, maxLPString) 377 | } 378 | 379 | if len(buf) < 2+n { 380 | return 0, fmt.Errorf("utils/writeLPBytes: Insufficient buffer size. Expecting %d, got %d.", 2+n, len(buf)) 381 | } 382 | 383 | binary.BigEndian.PutUint16(buf, uint16(n)) 384 | total += 2 385 | 386 | copy(buf[total:], b) 387 | total += n 388 | 389 | return total, nil 390 | } 391 | -------------------------------------------------------------------------------- /message_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | ) 22 | 23 | var ( 24 | lpstrings []string = []string{ 25 | "this is a test", 26 | "hope it succeeds", 27 | "but just in case", 28 | "send me your millions", 29 | "", 30 | } 31 | 32 | lpstringBytes []byte = []byte{ 33 | 0x0, 0xe, 't', 'h', 'i', 's', ' ', 'i', 's', ' ', 'a', ' ', 't', 'e', 's', 't', 34 | 0x0, 0x10, 'h', 'o', 'p', 'e', ' ', 'i', 't', ' ', 's', 'u', 'c', 'c', 'e', 'e', 'd', 's', 35 | 0x0, 0x10, 'b', 'u', 't', ' ', 'j', 'u', 's', 't', ' ', 'i', 'n', ' ', 'c', 'a', 's', 'e', 36 | 0x0, 0x15, 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'y', 'o', 'u', 'r', ' ', 'm', 'i', 'l', 'l', 'i', 'o', 'n', 's', 37 | 0x0, 0x0, 38 | } 39 | 40 | msgBytes []byte = []byte{ 41 | byte(CONNECT << 4), 42 | 60, 43 | 0, // Length MSB (0) 44 | 4, // Length LSB (4) 45 | 'M', 'Q', 'T', 'T', 46 | 4, // Protocol level 4 47 | 206, // connect flags 11001110, will QoS = 01 48 | 0, // Keep Alive MSB (0) 49 | 10, // Keep Alive LSB (10) 50 | 0, // Client ID MSB (0) 51 | 7, // Client ID LSB (7) 52 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 53 | 0, // Will Topic MSB (0) 54 | 4, // Will Topic LSB (4) 55 | 'w', 'i', 'l', 'l', 56 | 0, // Will Message MSB (0) 57 | 12, // Will Message LSB (12) 58 | 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', 59 | 0, // Username ID MSB (0) 60 | 7, // Username ID LSB (7) 61 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 62 | 0, // Password ID MSB (0) 63 | 10, // Password ID LSB (10) 64 | 'v', 'e', 'r', 'y', 's', 'e', 'c', 'r', 'e', 't', 65 | } 66 | ) 67 | 68 | func TestReadLPBytes(t *testing.T) { 69 | total := 0 70 | 71 | for _, str := range lpstrings { 72 | b, n, err := readLPBytes(lpstringBytes[total:]) 73 | 74 | require.NoError(t, err) 75 | require.Equal(t, str, string(b)) 76 | require.Equal(t, len(str)+2, n) 77 | 78 | total += n 79 | } 80 | } 81 | 82 | func TestWriteLPBytes(t *testing.T) { 83 | total := 0 84 | buf := make([]byte, 1000) 85 | 86 | for _, str := range lpstrings { 87 | n, err := writeLPBytes(buf[total:], []byte(str)) 88 | 89 | require.NoError(t, err) 90 | require.Equal(t, 2+len(str), n) 91 | 92 | total += n 93 | } 94 | 95 | require.Equal(t, lpstringBytes, buf[:total]) 96 | } 97 | 98 | func TestMessageTypes(t *testing.T) { 99 | if CONNECT != 1 || 100 | CONNACK != 2 || 101 | PUBLISH != 3 || 102 | PUBACK != 4 || 103 | PUBREC != 5 || 104 | PUBREL != 6 || 105 | PUBCOMP != 7 || 106 | SUBSCRIBE != 8 || 107 | SUBACK != 9 || 108 | UNSUBSCRIBE != 10 || 109 | UNSUBACK != 11 || 110 | PINGREQ != 12 || 111 | PINGRESP != 13 || 112 | DISCONNECT != 14 { 113 | 114 | t.Errorf("Message types have invalid code") 115 | } 116 | } 117 | 118 | func TestQosCodes(t *testing.T) { 119 | if QosAtMostOnce != 0 || QosAtLeastOnce != 1 || QosExactlyOnce != 2 { 120 | t.Errorf("QOS codes invalid") 121 | } 122 | } 123 | 124 | func TestConnackReturnCodes(t *testing.T) { 125 | require.Equal(t, ErrInvalidProtocolVersion.Error(), ConnackCode(1).Error(), "Incorrect ConnackCode error value.") 126 | 127 | require.Equal(t, ErrIdentifierRejected.Error(), ConnackCode(2).Error(), "Incorrect ConnackCode error value.") 128 | 129 | require.Equal(t, ErrServerUnavailable.Error(), ConnackCode(3).Error(), "Incorrect ConnackCode error value.") 130 | 131 | require.Equal(t, ErrBadUsernameOrPassword.Error(), ConnackCode(4).Error(), "Incorrect ConnackCode error value.") 132 | 133 | require.Equal(t, ErrNotAuthorized.Error(), ConnackCode(5).Error(), "Incorrect ConnackCode error value.") 134 | } 135 | 136 | func TestFixedHeaderFlags(t *testing.T) { 137 | type detail struct { 138 | name string 139 | flags byte 140 | } 141 | 142 | details := map[MessageType]detail{ 143 | RESERVED: detail{"RESERVED", 0}, 144 | CONNECT: detail{"CONNECT", 0}, 145 | CONNACK: detail{"CONNACK", 0}, 146 | PUBLISH: detail{"PUBLISH", 0}, 147 | PUBACK: detail{"PUBACK", 0}, 148 | PUBREC: detail{"PUBREC", 0}, 149 | PUBREL: detail{"PUBREL", 2}, 150 | PUBCOMP: detail{"PUBCOMP", 0}, 151 | SUBSCRIBE: detail{"SUBSCRIBE", 2}, 152 | SUBACK: detail{"SUBACK", 0}, 153 | UNSUBSCRIBE: detail{"UNSUBSCRIBE", 2}, 154 | UNSUBACK: detail{"UNSUBACK", 0}, 155 | PINGREQ: detail{"PINGREQ", 0}, 156 | PINGRESP: detail{"PINGRESP", 0}, 157 | DISCONNECT: detail{"DISCONNECT", 0}, 158 | RESERVED2: detail{"RESERVED2", 0}, 159 | } 160 | 161 | for m, d := range details { 162 | if m.Name() != d.name { 163 | t.Errorf("Name mismatch. Expecting %s, got %s.", d.name, m.Name()) 164 | } 165 | 166 | if m.DefaultFlags() != d.flags { 167 | t.Errorf("Flag mismatch for %s. Expecting %d, got %d.", m.Name(), d.flags, m.DefaultFlags()) 168 | } 169 | } 170 | } 171 | 172 | func TestSupportedVersions(t *testing.T) { 173 | for k, v := range SupportedVersions { 174 | if k == 0x03 && v != "MQIsdp" { 175 | t.Errorf("Protocol version and name mismatch. Expect %s, got %s.", "MQIsdp", v) 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /ping_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | ) 22 | 23 | func TestPingreqMessageDecode(t *testing.T) { 24 | msgBytes := []byte{ 25 | byte(PINGREQ << 4), 26 | 0, 27 | } 28 | 29 | msg := NewPingreqMessage() 30 | n, err := msg.Decode(msgBytes) 31 | 32 | require.NoError(t, err, "Error decoding message.") 33 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 34 | require.Equal(t, PINGREQ, msg.Type(), "Error decoding message.") 35 | } 36 | 37 | func TestPingreqMessageEncode(t *testing.T) { 38 | msgBytes := []byte{ 39 | byte(PINGREQ << 4), 40 | 0, 41 | } 42 | 43 | msg := NewPingreqMessage() 44 | 45 | dst := make([]byte, 10) 46 | n, err := msg.Encode(dst) 47 | 48 | require.NoError(t, err, "Error decoding message.") 49 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 50 | require.Equal(t, msgBytes, dst[:n], "Error decoding message.") 51 | } 52 | 53 | func TestPingrespMessageDecode(t *testing.T) { 54 | msgBytes := []byte{ 55 | byte(PINGRESP << 4), 56 | 0, 57 | } 58 | 59 | msg := NewPingrespMessage() 60 | n, err := msg.Decode(msgBytes) 61 | 62 | require.NoError(t, err, "Error decoding message.") 63 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 64 | require.Equal(t, PINGRESP, msg.Type(), "Error decoding message.") 65 | } 66 | 67 | func TestPingrespMessageEncode(t *testing.T) { 68 | msgBytes := []byte{ 69 | byte(PINGRESP << 4), 70 | 0, 71 | } 72 | 73 | msg := NewPingrespMessage() 74 | 75 | dst := make([]byte, 10) 76 | n, err := msg.Encode(dst) 77 | 78 | require.NoError(t, err, "Error decoding message.") 79 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 80 | require.Equal(t, msgBytes, dst[:n], "Error decoding message.") 81 | } 82 | 83 | // test to ensure encoding and decoding are the same 84 | // decode, encode, and decode again 85 | func TestPingreqDecodeEncodeEquiv(t *testing.T) { 86 | msgBytes := []byte{ 87 | byte(PINGREQ << 4), 88 | 0, 89 | } 90 | 91 | msg := NewPingreqMessage() 92 | n, err := msg.Decode(msgBytes) 93 | 94 | require.NoError(t, err, "Error decoding message.") 95 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 96 | 97 | dst := make([]byte, 100) 98 | n2, err := msg.Encode(dst) 99 | 100 | require.NoError(t, err, "Error decoding message.") 101 | require.Equal(t, len(msgBytes), n2, "Error decoding message.") 102 | require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") 103 | 104 | n3, err := msg.Decode(dst) 105 | 106 | require.NoError(t, err, "Error decoding message.") 107 | require.Equal(t, len(msgBytes), n3, "Error decoding message.") 108 | } 109 | 110 | // test to ensure encoding and decoding are the same 111 | // decode, encode, and decode again 112 | func TestPingrespDecodeEncodeEquiv(t *testing.T) { 113 | msgBytes := []byte{ 114 | byte(PINGRESP << 4), 115 | 0, 116 | } 117 | 118 | msg := NewPingrespMessage() 119 | n, err := msg.Decode(msgBytes) 120 | 121 | require.NoError(t, err, "Error decoding message.") 122 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 123 | 124 | dst := make([]byte, 100) 125 | n2, err := msg.Encode(dst) 126 | 127 | require.NoError(t, err, "Error decoding message.") 128 | require.Equal(t, len(msgBytes), n2, "Error decoding message.") 129 | require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") 130 | 131 | n3, err := msg.Decode(dst) 132 | 133 | require.NoError(t, err, "Error decoding message.") 134 | require.Equal(t, len(msgBytes), n3, "Error decoding message.") 135 | } 136 | -------------------------------------------------------------------------------- /pingreq.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | // The PINGREQ Packet is sent from a Client to the Server. It can be used to: 18 | // 1. Indicate to the Server that the Client is alive in the absence of any other 19 | // Control Packets being sent from the Client to the Server. 20 | // 2. Request that the Server responds to confirm that it is alive. 21 | // 3. Exercise the network to indicate that the Network Connection is active. 22 | type PingreqMessage struct { 23 | DisconnectMessage 24 | } 25 | 26 | var _ Message = (*PingreqMessage)(nil) 27 | 28 | // NewPingreqMessage creates a new PINGREQ message. 29 | func NewPingreqMessage() *PingreqMessage { 30 | msg := &PingreqMessage{} 31 | msg.SetType(PINGREQ) 32 | 33 | return msg 34 | } 35 | -------------------------------------------------------------------------------- /pingresp.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | // A PINGRESP Packet is sent by the Server to the Client in response to a PINGREQ 18 | // Packet. It indicates that the Server is alive. 19 | type PingrespMessage struct { 20 | DisconnectMessage 21 | } 22 | 23 | var _ Message = (*PingrespMessage)(nil) 24 | 25 | // NewPingrespMessage creates a new PINGRESP message. 26 | func NewPingrespMessage() *PingrespMessage { 27 | msg := &PingrespMessage{} 28 | msg.SetType(PINGRESP) 29 | 30 | return msg 31 | } 32 | -------------------------------------------------------------------------------- /puback.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | import "fmt" 18 | 19 | // A PUBACK Packet is the response to a PUBLISH Packet with QoS level 1. 20 | type PubackMessage struct { 21 | header 22 | } 23 | 24 | var _ Message = (*PubackMessage)(nil) 25 | 26 | // NewPubackMessage creates a new PUBACK message. 27 | func NewPubackMessage() *PubackMessage { 28 | msg := &PubackMessage{} 29 | msg.SetType(PUBACK) 30 | 31 | return msg 32 | } 33 | 34 | func (this PubackMessage) String() string { 35 | return fmt.Sprintf("%s, Packet ID=%d", this.header, this.packetId) 36 | } 37 | 38 | func (this *PubackMessage) Len() int { 39 | if !this.dirty { 40 | return len(this.dbuf) 41 | } 42 | 43 | ml := this.msglen() 44 | 45 | if err := this.SetRemainingLength(int32(ml)); err != nil { 46 | return 0 47 | } 48 | 49 | return this.header.msglen() + ml 50 | } 51 | 52 | func (this *PubackMessage) Decode(src []byte) (int, error) { 53 | total := 0 54 | 55 | n, err := this.header.decode(src[total:]) 56 | total += n 57 | if err != nil { 58 | return total, err 59 | } 60 | 61 | //this.packetId = binary.BigEndian.Uint16(src[total:]) 62 | this.packetId = src[total : total+2] 63 | total += 2 64 | 65 | this.dirty = false 66 | 67 | return total, nil 68 | } 69 | 70 | func (this *PubackMessage) Encode(dst []byte) (int, error) { 71 | if !this.dirty { 72 | if len(dst) < len(this.dbuf) { 73 | return 0, fmt.Errorf("puback/Encode: Insufficient buffer size. Expecting %d, got %d.", len(this.dbuf), len(dst)) 74 | } 75 | 76 | return copy(dst, this.dbuf), nil 77 | } 78 | 79 | hl := this.header.msglen() 80 | ml := this.msglen() 81 | 82 | if len(dst) < hl+ml { 83 | return 0, fmt.Errorf("puback/Encode: Insufficient buffer size. Expecting %d, got %d.", hl+ml, len(dst)) 84 | } 85 | 86 | if err := this.SetRemainingLength(int32(ml)); err != nil { 87 | return 0, err 88 | } 89 | 90 | total := 0 91 | 92 | n, err := this.header.encode(dst[total:]) 93 | total += n 94 | if err != nil { 95 | return total, err 96 | } 97 | 98 | if copy(dst[total:total+2], this.packetId) != 2 { 99 | dst[total], dst[total+1] = 0, 0 100 | } 101 | total += 2 102 | 103 | return total, nil 104 | } 105 | 106 | func (this *PubackMessage) msglen() int { 107 | // packet ID 108 | return 2 109 | } 110 | -------------------------------------------------------------------------------- /puback_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | ) 22 | 23 | func TestPubackMessageFields(t *testing.T) { 24 | msg := NewPubackMessage() 25 | 26 | msg.SetPacketId(100) 27 | 28 | require.Equal(t, 100, int(msg.PacketId())) 29 | } 30 | 31 | func TestPubackMessageDecode(t *testing.T) { 32 | msgBytes := []byte{ 33 | byte(PUBACK << 4), 34 | 2, 35 | 0, // packet ID MSB (0) 36 | 7, // packet ID LSB (7) 37 | } 38 | 39 | msg := NewPubackMessage() 40 | n, err := msg.Decode(msgBytes) 41 | 42 | require.NoError(t, err, "Error decoding message.") 43 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 44 | require.Equal(t, PUBACK, msg.Type(), "Error decoding message.") 45 | require.Equal(t, 7, int(msg.PacketId()), "Error decoding message.") 46 | } 47 | 48 | // test insufficient bytes 49 | func TestPubackMessageDecode2(t *testing.T) { 50 | msgBytes := []byte{ 51 | byte(PUBACK << 4), 52 | 2, 53 | 7, // packet ID LSB (7) 54 | } 55 | 56 | msg := NewPubackMessage() 57 | _, err := msg.Decode(msgBytes) 58 | 59 | require.Error(t, err) 60 | } 61 | 62 | func TestPubackMessageEncode(t *testing.T) { 63 | msgBytes := []byte{ 64 | byte(PUBACK << 4), 65 | 2, 66 | 0, // packet ID MSB (0) 67 | 7, // packet ID LSB (7) 68 | } 69 | 70 | msg := NewPubackMessage() 71 | msg.SetPacketId(7) 72 | 73 | dst := make([]byte, 10) 74 | n, err := msg.Encode(dst) 75 | 76 | require.NoError(t, err, "Error decoding message.") 77 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 78 | require.Equal(t, msgBytes, dst[:n], "Error decoding message.") 79 | } 80 | 81 | // test to ensure encoding and decoding are the same 82 | // decode, encode, and decode again 83 | func TestPubackDecodeEncodeEquiv(t *testing.T) { 84 | msgBytes := []byte{ 85 | byte(PUBACK << 4), 86 | 2, 87 | 0, // packet ID MSB (0) 88 | 7, // packet ID LSB (7) 89 | } 90 | 91 | msg := NewPubackMessage() 92 | n, err := msg.Decode(msgBytes) 93 | 94 | require.NoError(t, err, "Error decoding message.") 95 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 96 | 97 | dst := make([]byte, 100) 98 | n2, err := msg.Encode(dst) 99 | 100 | require.NoError(t, err, "Error decoding message.") 101 | require.Equal(t, len(msgBytes), n2, "Error decoding message.") 102 | require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") 103 | 104 | n3, err := msg.Decode(dst) 105 | 106 | require.NoError(t, err, "Error decoding message.") 107 | require.Equal(t, len(msgBytes), n3, "Error decoding message.") 108 | } 109 | -------------------------------------------------------------------------------- /pubcomp.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | // The PUBCOMP Packet is the response to a PUBREL Packet. It is the fourth and 18 | // final packet of the QoS 2 protocol exchange. 19 | type PubcompMessage struct { 20 | PubackMessage 21 | } 22 | 23 | var _ Message = (*PubcompMessage)(nil) 24 | 25 | // NewPubcompMessage creates a new PUBCOMP message. 26 | func NewPubcompMessage() *PubcompMessage { 27 | msg := &PubcompMessage{} 28 | msg.SetType(PUBCOMP) 29 | 30 | return msg 31 | } 32 | -------------------------------------------------------------------------------- /pubcomp_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | ) 22 | 23 | func TestPubcompMessageFields(t *testing.T) { 24 | msg := NewPubcompMessage() 25 | 26 | msg.SetPacketId(100) 27 | 28 | require.Equal(t, 100, int(msg.PacketId())) 29 | } 30 | 31 | func TestPubcompMessageDecode(t *testing.T) { 32 | msgBytes := []byte{ 33 | byte(PUBCOMP << 4), 34 | 2, 35 | 0, // packet ID MSB (0) 36 | 7, // packet ID LSB (7) 37 | } 38 | 39 | msg := NewPubcompMessage() 40 | n, err := msg.Decode(msgBytes) 41 | 42 | require.NoError(t, err, "Error decoding message.") 43 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 44 | require.Equal(t, PUBCOMP, msg.Type(), "Error decoding message.") 45 | require.Equal(t, 7, int(msg.PacketId()), "Error decoding message.") 46 | } 47 | 48 | // test insufficient bytes 49 | func TestPubcompMessageDecode2(t *testing.T) { 50 | msgBytes := []byte{ 51 | byte(PUBCOMP << 4), 52 | 2, 53 | 7, // packet ID LSB (7) 54 | } 55 | 56 | msg := NewPubcompMessage() 57 | _, err := msg.Decode(msgBytes) 58 | 59 | require.Error(t, err) 60 | } 61 | 62 | func TestPubcompMessageEncode(t *testing.T) { 63 | msgBytes := []byte{ 64 | byte(PUBCOMP << 4), 65 | 2, 66 | 0, // packet ID MSB (0) 67 | 7, // packet ID LSB (7) 68 | } 69 | 70 | msg := NewPubcompMessage() 71 | msg.SetPacketId(7) 72 | 73 | dst := make([]byte, 10) 74 | n, err := msg.Encode(dst) 75 | 76 | require.NoError(t, err, "Error decoding message.") 77 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 78 | require.Equal(t, msgBytes, dst[:n], "Error decoding message.") 79 | } 80 | 81 | // test to ensure encoding and decoding are the same 82 | // decode, encode, and decode again 83 | func TestPubcompDecodeEncodeEquiv(t *testing.T) { 84 | msgBytes := []byte{ 85 | byte(PUBCOMP << 4), 86 | 2, 87 | 0, // packet ID MSB (0) 88 | 7, // packet ID LSB (7) 89 | } 90 | 91 | msg := NewPubcompMessage() 92 | n, err := msg.Decode(msgBytes) 93 | 94 | require.NoError(t, err, "Error decoding message.") 95 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 96 | 97 | dst := make([]byte, 100) 98 | n2, err := msg.Encode(dst) 99 | 100 | require.NoError(t, err, "Error decoding message.") 101 | require.Equal(t, len(msgBytes), n2, "Error decoding message.") 102 | require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") 103 | 104 | n3, err := msg.Decode(dst) 105 | 106 | require.NoError(t, err, "Error decoding message.") 107 | require.Equal(t, len(msgBytes), n3, "Error decoding message.") 108 | } 109 | -------------------------------------------------------------------------------- /publish.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | import ( 18 | "fmt" 19 | "sync/atomic" 20 | ) 21 | 22 | // A PUBLISH Control Packet is sent from a Client to a Server or from Server to a Client 23 | // to transport an Application Message. 24 | type PublishMessage struct { 25 | header 26 | 27 | topic []byte 28 | payload []byte 29 | } 30 | 31 | var _ Message = (*PublishMessage)(nil) 32 | 33 | // NewPublishMessage creates a new PUBLISH message. 34 | func NewPublishMessage() *PublishMessage { 35 | msg := &PublishMessage{} 36 | msg.SetType(PUBLISH) 37 | 38 | return msg 39 | } 40 | 41 | func (this PublishMessage) String() string { 42 | return fmt.Sprintf("%s, Topic=%q, Packet ID=%d, QoS=%d, Retained=%t, Dup=%t, Payload=%v", 43 | this.header, this.topic, this.packetId, this.QoS(), this.Retain(), this.Dup(), this.payload) 44 | } 45 | 46 | // Dup returns the value specifying the duplicate delivery of a PUBLISH Control Packet. 47 | // If the DUP flag is set to 0, it indicates that this is the first occasion that the 48 | // Client or Server has attempted to send this MQTT PUBLISH Packet. If the DUP flag is 49 | // set to 1, it indicates that this might be re-delivery of an earlier attempt to send 50 | // the Packet. 51 | func (this *PublishMessage) Dup() bool { 52 | return ((this.Flags() >> 3) & 0x1) == 1 53 | } 54 | 55 | // SetDup sets the value specifying the duplicate delivery of a PUBLISH Control Packet. 56 | func (this *PublishMessage) SetDup(v bool) { 57 | if v { 58 | this.mtypeflags[0] |= 0x8 // 00001000 59 | } else { 60 | this.mtypeflags[0] &= 247 // 11110111 61 | } 62 | } 63 | 64 | // Retain returns the value of the RETAIN flag. This flag is only used on the PUBLISH 65 | // Packet. If the RETAIN flag is set to 1, in a PUBLISH Packet sent by a Client to a 66 | // Server, the Server MUST store the Application Message and its QoS, so that it can be 67 | // delivered to future subscribers whose subscriptions match its topic name. 68 | func (this *PublishMessage) Retain() bool { 69 | return (this.Flags() & 0x1) == 1 70 | } 71 | 72 | // SetRetain sets the value of the RETAIN flag. 73 | func (this *PublishMessage) SetRetain(v bool) { 74 | if v { 75 | this.mtypeflags[0] |= 0x1 // 00000001 76 | } else { 77 | this.mtypeflags[0] &= 254 // 11111110 78 | } 79 | } 80 | 81 | // QoS returns the field that indicates the level of assurance for delivery of an 82 | // Application Message. The values are QosAtMostOnce, QosAtLeastOnce and QosExactlyOnce. 83 | func (this *PublishMessage) QoS() byte { 84 | return (this.Flags() >> 1) & 0x3 85 | } 86 | 87 | // SetQoS sets the field that indicates the level of assurance for delivery of an 88 | // Application Message. The values are QosAtMostOnce, QosAtLeastOnce and QosExactlyOnce. 89 | // An error is returned if the value is not one of these. 90 | func (this *PublishMessage) SetQoS(v byte) error { 91 | if v != 0x0 && v != 0x1 && v != 0x2 { 92 | return fmt.Errorf("publish/SetQoS: Invalid QoS %d.", v) 93 | } 94 | 95 | this.mtypeflags[0] = (this.mtypeflags[0] & 249) | (v << 1) // 249 = 11111001 96 | 97 | return nil 98 | } 99 | 100 | // Topic returns the the topic name that identifies the information channel to which 101 | // payload data is published. 102 | func (this *PublishMessage) Topic() []byte { 103 | return this.topic 104 | } 105 | 106 | // SetTopic sets the the topic name that identifies the information channel to which 107 | // payload data is published. An error is returned if ValidTopic() is falbase. 108 | func (this *PublishMessage) SetTopic(v []byte) error { 109 | if !ValidTopic(v) { 110 | return fmt.Errorf("publish/SetTopic: Invalid topic name (%s). Must not be empty or contain wildcard characters", string(v)) 111 | } 112 | 113 | this.topic = v 114 | this.dirty = true 115 | 116 | return nil 117 | } 118 | 119 | // Payload returns the application message that's part of the PUBLISH message. 120 | func (this *PublishMessage) Payload() []byte { 121 | return this.payload 122 | } 123 | 124 | // SetPayload sets the application message that's part of the PUBLISH message. 125 | func (this *PublishMessage) SetPayload(v []byte) { 126 | this.payload = v 127 | this.dirty = true 128 | } 129 | 130 | func (this *PublishMessage) Len() int { 131 | if !this.dirty { 132 | return len(this.dbuf) 133 | } 134 | 135 | ml := this.msglen() 136 | 137 | if err := this.SetRemainingLength(int32(ml)); err != nil { 138 | return 0 139 | } 140 | 141 | return this.header.msglen() + ml 142 | } 143 | 144 | func (this *PublishMessage) Decode(src []byte) (int, error) { 145 | total := 0 146 | 147 | hn, err := this.header.decode(src[total:]) 148 | total += hn 149 | if err != nil { 150 | return total, err 151 | } 152 | 153 | n := 0 154 | 155 | this.topic, n, err = readLPBytes(src[total:]) 156 | total += n 157 | if err != nil { 158 | return total, err 159 | } 160 | 161 | if !ValidTopic(this.topic) { 162 | return total, fmt.Errorf("publish/Decode: Invalid topic name (%s). Must not be empty or contain wildcard characters", string(this.topic)) 163 | } 164 | 165 | // The packet identifier field is only present in the PUBLISH packets where the 166 | // QoS level is 1 or 2 167 | if this.QoS() != 0 { 168 | //this.packetId = binary.BigEndian.Uint16(src[total:]) 169 | this.packetId = src[total : total+2] 170 | total += 2 171 | } 172 | 173 | l := int(this.remlen) - (total - hn) 174 | this.payload = src[total : total+l] 175 | total += len(this.payload) 176 | 177 | this.dirty = false 178 | 179 | return total, nil 180 | } 181 | 182 | func (this *PublishMessage) Encode(dst []byte) (int, error) { 183 | if !this.dirty { 184 | if len(dst) < len(this.dbuf) { 185 | return 0, fmt.Errorf("publish/Encode: Insufficient buffer size. Expecting %d, got %d.", len(this.dbuf), len(dst)) 186 | } 187 | 188 | return copy(dst, this.dbuf), nil 189 | } 190 | 191 | if len(this.topic) == 0 { 192 | return 0, fmt.Errorf("publish/Encode: Topic name is empty.") 193 | } 194 | 195 | if len(this.payload) == 0 { 196 | return 0, fmt.Errorf("publish/Encode: Payload is empty.") 197 | } 198 | 199 | ml := this.msglen() 200 | 201 | if err := this.SetRemainingLength(int32(ml)); err != nil { 202 | return 0, err 203 | } 204 | 205 | hl := this.header.msglen() 206 | 207 | if len(dst) < hl+ml { 208 | return 0, fmt.Errorf("publish/Encode: Insufficient buffer size. Expecting %d, got %d.", hl+ml, len(dst)) 209 | } 210 | 211 | total := 0 212 | 213 | n, err := this.header.encode(dst[total:]) 214 | total += n 215 | if err != nil { 216 | return total, err 217 | } 218 | 219 | n, err = writeLPBytes(dst[total:], this.topic) 220 | total += n 221 | if err != nil { 222 | return total, err 223 | } 224 | 225 | // The packet identifier field is only present in the PUBLISH packets where the QoS level is 1 or 2 226 | if this.QoS() != 0 { 227 | if this.PacketId() == 0 { 228 | this.SetPacketId(uint16(atomic.AddUint64(&gPacketId, 1) & 0xffff)) 229 | //this.packetId = uint16(atomic.AddUint64(&gPacketId, 1) & 0xffff) 230 | } 231 | 232 | n = copy(dst[total:], this.packetId) 233 | //binary.BigEndian.PutUint16(dst[total:], this.packetId) 234 | total += n 235 | } 236 | 237 | copy(dst[total:], this.payload) 238 | total += len(this.payload) 239 | 240 | return total, nil 241 | } 242 | 243 | func (this *PublishMessage) msglen() int { 244 | total := 2 + len(this.topic) + len(this.payload) 245 | if this.QoS() != 0 { 246 | total += 2 247 | } 248 | 249 | return total 250 | } 251 | -------------------------------------------------------------------------------- /publish_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | ) 22 | 23 | func TestPublishMessageHeaderFields(t *testing.T) { 24 | msg := NewPublishMessage() 25 | msg.mtypeflags[0] |= 11 26 | 27 | require.True(t, msg.Dup(), "Incorrect DUP flag.") 28 | require.True(t, msg.Retain(), "Incorrect RETAIN flag.") 29 | require.Equal(t, 1, int(msg.QoS()), "Incorrect QoS.") 30 | 31 | msg.SetDup(false) 32 | 33 | require.False(t, msg.Dup(), "Incorrect DUP flag.") 34 | 35 | msg.SetRetain(false) 36 | 37 | require.False(t, msg.Retain(), "Incorrect RETAIN flag.") 38 | 39 | err := msg.SetQoS(2) 40 | 41 | require.NoError(t, err, "Error setting QoS.") 42 | require.Equal(t, 2, int(msg.QoS()), "Incorrect QoS.") 43 | 44 | err = msg.SetQoS(3) 45 | 46 | require.Error(t, err) 47 | 48 | err = msg.SetQoS(0) 49 | 50 | require.NoError(t, err, "Error setting QoS.") 51 | require.Equal(t, 0, int(msg.QoS()), "Incorrect QoS.") 52 | 53 | msg.SetDup(true) 54 | 55 | require.True(t, msg.Dup(), "Incorrect DUP flag.") 56 | 57 | msg.SetRetain(true) 58 | 59 | require.True(t, msg.Retain(), "Incorrect RETAIN flag.") 60 | } 61 | 62 | func TestPublishMessageFields(t *testing.T) { 63 | msg := NewPublishMessage() 64 | 65 | msg.SetTopic([]byte("coolstuff")) 66 | 67 | require.Equal(t, "coolstuff", string(msg.Topic()), "Error setting message topic.") 68 | 69 | err := msg.SetTopic([]byte("coolstuff/#")) 70 | 71 | require.Error(t, err) 72 | 73 | msg.SetPacketId(100) 74 | 75 | require.Equal(t, 100, int(msg.PacketId()), "Error setting acket ID.") 76 | 77 | msg.SetPayload([]byte("this is a payload to be sent")) 78 | 79 | require.Equal(t, []byte("this is a payload to be sent"), msg.Payload(), "Error setting payload.") 80 | } 81 | 82 | func TestPublishMessageDecode1(t *testing.T) { 83 | msgBytes := []byte{ 84 | byte(PUBLISH<<4) | 2, 85 | 23, 86 | 0, // topic name MSB (0) 87 | 7, // topic name LSB (7) 88 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 89 | 0, // packet ID MSB (0) 90 | 7, // packet ID LSB (7) 91 | 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', 92 | } 93 | 94 | msg := NewPublishMessage() 95 | n, err := msg.Decode(msgBytes) 96 | 97 | require.NoError(t, err, "Error decoding message.") 98 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 99 | require.Equal(t, 7, int(msg.PacketId()), "Error decoding message.") 100 | require.Equal(t, "surgemq", string(msg.Topic()), "Error deocding topic name.") 101 | require.Equal(t, []byte{'s', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e'}, msg.Payload(), "Error deocding payload.") 102 | } 103 | 104 | // test insufficient bytes 105 | func TestPublishMessageDecode2(t *testing.T) { 106 | msgBytes := []byte{ 107 | byte(PUBLISH<<4) | 2, 108 | 26, 109 | 0, // topic name MSB (0) 110 | 7, // topic name LSB (7) 111 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 112 | 0, // packet ID MSB (0) 113 | 7, // packet ID LSB (7) 114 | 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', 115 | } 116 | 117 | msg := NewPublishMessage() 118 | _, err := msg.Decode(msgBytes) 119 | 120 | require.Error(t, err) 121 | } 122 | 123 | // test qos = 0 and no client id 124 | func TestPublishMessageDecode3(t *testing.T) { 125 | msgBytes := []byte{ 126 | byte(PUBLISH << 4), 127 | 21, 128 | 0, // topic name MSB (0) 129 | 7, // topic name LSB (7) 130 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 131 | 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', 132 | } 133 | 134 | msg := NewPublishMessage() 135 | _, err := msg.Decode(msgBytes) 136 | 137 | require.NoError(t, err, "Error decoding message.") 138 | } 139 | 140 | func TestPublishMessageEncode(t *testing.T) { 141 | msgBytes := []byte{ 142 | byte(PUBLISH<<4) | 2, 143 | 23, 144 | 0, // topic name MSB (0) 145 | 7, // topic name LSB (7) 146 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 147 | 0, // packet ID MSB (0) 148 | 7, // packet ID LSB (7) 149 | 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', 150 | } 151 | 152 | msg := NewPublishMessage() 153 | msg.SetTopic([]byte("surgemq")) 154 | msg.SetQoS(1) 155 | msg.SetPacketId(7) 156 | msg.SetPayload([]byte{'s', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e'}) 157 | 158 | dst := make([]byte, 100) 159 | n, err := msg.Encode(dst) 160 | 161 | require.NoError(t, err, "Error decoding message.") 162 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 163 | require.Equal(t, msgBytes, dst[:n], "Error decoding message.") 164 | } 165 | 166 | // test empty topic name 167 | func TestPublishMessageEncode2(t *testing.T) { 168 | msg := NewPublishMessage() 169 | msg.SetTopic([]byte("")) 170 | msg.SetPacketId(7) 171 | msg.SetPayload([]byte{'s', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e'}) 172 | 173 | dst := make([]byte, 100) 174 | _, err := msg.Encode(dst) 175 | require.Error(t, err) 176 | } 177 | 178 | // test encoding qos = 0 and no packet id 179 | func TestPublishMessageEncode3(t *testing.T) { 180 | msgBytes := []byte{ 181 | byte(PUBLISH << 4), 182 | 21, 183 | 0, // topic name MSB (0) 184 | 7, // topic name LSB (7) 185 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 186 | 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', 187 | } 188 | 189 | msg := NewPublishMessage() 190 | msg.SetTopic([]byte("surgemq")) 191 | msg.SetQoS(0) 192 | msg.SetPayload([]byte{'s', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e'}) 193 | 194 | dst := make([]byte, 100) 195 | n, err := msg.Encode(dst) 196 | 197 | require.NoError(t, err, "Error decoding message.") 198 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 199 | require.Equal(t, msgBytes, dst[:n], "Error decoding message.") 200 | } 201 | 202 | // test large message 203 | func TestPublishMessageEncode4(t *testing.T) { 204 | msgBytes := []byte{ 205 | byte(PUBLISH << 4), 206 | 137, 207 | 8, 208 | 0, // topic name MSB (0) 209 | 7, // topic name LSB (7) 210 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 211 | } 212 | 213 | payload := make([]byte, 1024) 214 | msgBytes = append(msgBytes, payload...) 215 | 216 | msg := NewPublishMessage() 217 | msg.SetTopic([]byte("surgemq")) 218 | msg.SetQoS(0) 219 | msg.SetPayload(payload) 220 | 221 | require.Equal(t, len(msgBytes), msg.Len()) 222 | 223 | dst := make([]byte, 1100) 224 | n, err := msg.Encode(dst) 225 | 226 | require.NoError(t, err, "Error decoding message.") 227 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 228 | require.Equal(t, msgBytes, dst[:n], "Error decoding message.") 229 | } 230 | 231 | // test from github issue #2, @mrdg 232 | func TestPublishDecodeEncodeEquiv2(t *testing.T) { 233 | msgBytes := []byte{50, 18, 0, 9, 103, 114, 101, 101, 116, 105, 110, 103, 115, 0, 1, 72, 101, 108, 108, 111} 234 | 235 | msg := NewPublishMessage() 236 | n, err := msg.Decode(msgBytes) 237 | 238 | require.NoError(t, err, "Error decoding message.") 239 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 240 | 241 | dst := make([]byte, 100) 242 | n2, err := msg.Encode(dst) 243 | 244 | require.NoError(t, err, "Error decoding message.") 245 | require.Equal(t, len(msgBytes), n2, "Error decoding message.") 246 | require.Equal(t, msgBytes, dst[:n], "Error decoding message.") 247 | } 248 | 249 | // test to ensure encoding and decoding are the same 250 | // decode, encode, and decode again 251 | func TestPublishDecodeEncodeEquiv(t *testing.T) { 252 | msgBytes := []byte{ 253 | byte(PUBLISH<<4) | 2, 254 | 23, 255 | 0, // topic name MSB (0) 256 | 7, // topic name LSB (7) 257 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 258 | 0, // packet ID MSB (0) 259 | 7, // packet ID LSB (7) 260 | 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', 261 | } 262 | 263 | msg := NewPublishMessage() 264 | 265 | n, err := msg.Decode(msgBytes) 266 | 267 | require.NoError(t, err, "Error decoding message.") 268 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 269 | 270 | dst := make([]byte, 100) 271 | n2, err := msg.Encode(dst) 272 | 273 | require.NoError(t, err, "Error decoding message.") 274 | require.Equal(t, len(msgBytes), n2, "Error decoding message.") 275 | require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") 276 | 277 | n3, err := msg.Decode(dst) 278 | 279 | require.NoError(t, err, "Error decoding message.") 280 | require.Equal(t, len(msgBytes), n3, "Error decoding message.") 281 | } 282 | -------------------------------------------------------------------------------- /pubrec.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | type PubrecMessage struct { 18 | PubackMessage 19 | } 20 | 21 | // A PUBREC Packet is the response to a PUBLISH Packet with QoS 2. It is the second 22 | // packet of the QoS 2 protocol exchange. 23 | var _ Message = (*PubrecMessage)(nil) 24 | 25 | // NewPubrecMessage creates a new PUBREC message. 26 | func NewPubrecMessage() *PubrecMessage { 27 | msg := &PubrecMessage{} 28 | msg.SetType(PUBREC) 29 | 30 | return msg 31 | } 32 | -------------------------------------------------------------------------------- /pubrec_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | ) 22 | 23 | func TestPubrecMessageFields(t *testing.T) { 24 | msg := NewPubrecMessage() 25 | 26 | msg.SetPacketId(100) 27 | 28 | require.Equal(t, 100, int(msg.PacketId())) 29 | } 30 | 31 | func TestPubrecMessageDecode(t *testing.T) { 32 | msgBytes := []byte{ 33 | byte(PUBREC << 4), 34 | 2, 35 | 0, // packet ID MSB (0) 36 | 7, // packet ID LSB (7) 37 | } 38 | 39 | msg := NewPubrecMessage() 40 | n, err := msg.Decode(msgBytes) 41 | 42 | require.NoError(t, err, "Error decoding message.") 43 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 44 | require.Equal(t, PUBREC, msg.Type(), "Error decoding message.") 45 | require.Equal(t, 7, int(msg.PacketId()), "Error decoding message.") 46 | } 47 | 48 | // test insufficient bytes 49 | func TestPubrecMessageDecode2(t *testing.T) { 50 | msgBytes := []byte{ 51 | byte(PUBREC << 4), 52 | 2, 53 | 7, // packet ID LSB (7) 54 | } 55 | 56 | msg := NewPubrecMessage() 57 | _, err := msg.Decode(msgBytes) 58 | 59 | require.Error(t, err) 60 | } 61 | 62 | func TestPubrecMessageEncode(t *testing.T) { 63 | msgBytes := []byte{ 64 | byte(PUBREC << 4), 65 | 2, 66 | 0, // packet ID MSB (0) 67 | 7, // packet ID LSB (7) 68 | } 69 | 70 | msg := NewPubrecMessage() 71 | msg.SetPacketId(7) 72 | 73 | dst := make([]byte, 10) 74 | n, err := msg.Encode(dst) 75 | 76 | require.NoError(t, err, "Error decoding message.") 77 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 78 | require.Equal(t, msgBytes, dst[:n], "Error decoding message.") 79 | } 80 | 81 | // test to ensure encoding and decoding are the same 82 | // decode, encode, and decode again 83 | func TestPubrecDecodeEncodeEquiv(t *testing.T) { 84 | msgBytes := []byte{ 85 | byte(PUBREC << 4), 86 | 2, 87 | 0, // packet ID MSB (0) 88 | 7, // packet ID LSB (7) 89 | } 90 | 91 | msg := NewPubrecMessage() 92 | n, err := msg.Decode(msgBytes) 93 | 94 | require.NoError(t, err, "Error decoding message.") 95 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 96 | 97 | dst := make([]byte, 100) 98 | n2, err := msg.Encode(dst) 99 | 100 | require.NoError(t, err, "Error decoding message.") 101 | require.Equal(t, len(msgBytes), n2, "Error decoding message.") 102 | require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") 103 | 104 | n3, err := msg.Decode(dst) 105 | 106 | require.NoError(t, err, "Error decoding message.") 107 | require.Equal(t, len(msgBytes), n3, "Error decoding message.") 108 | } 109 | -------------------------------------------------------------------------------- /pubrel.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | // A PUBREL Packet is the response to a PUBREC Packet. It is the third packet of the 18 | // QoS 2 protocol exchange. 19 | type PubrelMessage struct { 20 | PubackMessage 21 | } 22 | 23 | var _ Message = (*PubrelMessage)(nil) 24 | 25 | // NewPubrelMessage creates a new PUBREL message. 26 | func NewPubrelMessage() *PubrelMessage { 27 | msg := &PubrelMessage{} 28 | msg.SetType(PUBREL) 29 | 30 | return msg 31 | } 32 | -------------------------------------------------------------------------------- /pubrel_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | ) 22 | 23 | func TestPubrelMessageFields(t *testing.T) { 24 | msg := NewPubrelMessage() 25 | 26 | msg.SetPacketId(100) 27 | 28 | require.Equal(t, 100, int(msg.PacketId())) 29 | } 30 | 31 | func TestPubrelMessageDecode(t *testing.T) { 32 | msgBytes := []byte{ 33 | byte(PUBREL<<4) | 2, 34 | 2, 35 | 0, // packet ID MSB (0) 36 | 7, // packet ID LSB (7) 37 | } 38 | 39 | msg := NewPubrelMessage() 40 | n, err := msg.Decode(msgBytes) 41 | 42 | require.NoError(t, err, "Error decoding message.") 43 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 44 | require.Equal(t, PUBREL, msg.Type(), "Error decoding message.") 45 | require.Equal(t, 7, int(msg.PacketId()), "Error decoding message.") 46 | } 47 | 48 | // test insufficient bytes 49 | func TestPubrelMessageDecode2(t *testing.T) { 50 | msgBytes := []byte{ 51 | byte(PUBREL<<4) | 2, 52 | 2, 53 | 7, // packet ID LSB (7) 54 | } 55 | 56 | msg := NewPubrelMessage() 57 | _, err := msg.Decode(msgBytes) 58 | 59 | require.Error(t, err) 60 | } 61 | 62 | func TestPubrelMessageEncode(t *testing.T) { 63 | msgBytes := []byte{ 64 | byte(PUBREL<<4) | 2, 65 | 2, 66 | 0, // packet ID MSB (0) 67 | 7, // packet ID LSB (7) 68 | } 69 | 70 | msg := NewPubrelMessage() 71 | msg.SetPacketId(7) 72 | 73 | dst := make([]byte, 10) 74 | n, err := msg.Encode(dst) 75 | 76 | require.NoError(t, err, "Error decoding message.") 77 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 78 | require.Equal(t, msgBytes, dst[:n], "Error decoding message.") 79 | } 80 | 81 | // test to ensure encoding and decoding are the same 82 | // decode, encode, and decode again 83 | func TestPubrelDecodeEncodeEquiv(t *testing.T) { 84 | msgBytes := []byte{ 85 | byte(PUBREL<<4) | 2, 86 | 2, 87 | 0, // packet ID MSB (0) 88 | 7, // packet ID LSB (7) 89 | } 90 | 91 | msg := NewPubrelMessage() 92 | n, err := msg.Decode(msgBytes) 93 | 94 | require.NoError(t, err, "Error decoding message.") 95 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 96 | 97 | dst := make([]byte, 100) 98 | n2, err := msg.Encode(dst) 99 | 100 | require.NoError(t, err, "Error decoding message.") 101 | require.Equal(t, len(msgBytes), n2, "Error decoding message.") 102 | require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") 103 | 104 | n3, err := msg.Decode(dst) 105 | 106 | require.NoError(t, err, "Error decoding message.") 107 | require.Equal(t, len(msgBytes), n3, "Error decoding message.") 108 | } 109 | -------------------------------------------------------------------------------- /suback.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | import "fmt" 18 | 19 | // A SUBACK Packet is sent by the Server to the Client to confirm receipt and processing 20 | // of a SUBSCRIBE Packet. 21 | // 22 | // A SUBACK Packet contains a list of return codes, that specify the maximum QoS level 23 | // that was granted in each Subscription that was requested by the SUBSCRIBE. 24 | type SubackMessage struct { 25 | header 26 | 27 | returnCodes []byte 28 | } 29 | 30 | var _ Message = (*SubackMessage)(nil) 31 | 32 | // NewSubackMessage creates a new SUBACK message. 33 | func NewSubackMessage() *SubackMessage { 34 | msg := &SubackMessage{} 35 | msg.SetType(SUBACK) 36 | 37 | return msg 38 | } 39 | 40 | // String returns a string representation of the message. 41 | func (this SubackMessage) String() string { 42 | return fmt.Sprintf("%s, Packet ID=%d, Return Codes=%v", this.header, this.PacketId(), this.returnCodes) 43 | } 44 | 45 | // ReturnCodes returns the list of QoS returns from the subscriptions sent in the SUBSCRIBE message. 46 | func (this *SubackMessage) ReturnCodes() []byte { 47 | return this.returnCodes 48 | } 49 | 50 | // AddReturnCodes sets the list of QoS returns from the subscriptions sent in the SUBSCRIBE message. 51 | // An error is returned if any of the QoS values are not valid. 52 | func (this *SubackMessage) AddReturnCodes(ret []byte) error { 53 | for _, c := range ret { 54 | if c != QosAtMostOnce && c != QosAtLeastOnce && c != QosExactlyOnce && c != QosFailure { 55 | return fmt.Errorf("suback/AddReturnCode: Invalid return code %d. Must be 0, 1, 2, 0x80.", c) 56 | } 57 | 58 | this.returnCodes = append(this.returnCodes, c) 59 | } 60 | 61 | this.dirty = true 62 | 63 | return nil 64 | } 65 | 66 | // AddReturnCode adds a single QoS return value. 67 | func (this *SubackMessage) AddReturnCode(ret byte) error { 68 | return this.AddReturnCodes([]byte{ret}) 69 | } 70 | 71 | func (this *SubackMessage) Len() int { 72 | if !this.dirty { 73 | return len(this.dbuf) 74 | } 75 | 76 | ml := this.msglen() 77 | 78 | if err := this.SetRemainingLength(int32(ml)); err != nil { 79 | return 0 80 | } 81 | 82 | return this.header.msglen() + ml 83 | } 84 | 85 | func (this *SubackMessage) Decode(src []byte) (int, error) { 86 | total := 0 87 | 88 | hn, err := this.header.decode(src[total:]) 89 | total += hn 90 | if err != nil { 91 | return total, err 92 | } 93 | 94 | //this.packetId = binary.BigEndian.Uint16(src[total:]) 95 | this.packetId = src[total : total+2] 96 | total += 2 97 | 98 | l := int(this.remlen) - (total - hn) 99 | this.returnCodes = src[total : total+l] 100 | total += len(this.returnCodes) 101 | 102 | for i, code := range this.returnCodes { 103 | if code != 0x00 && code != 0x01 && code != 0x02 && code != 0x80 { 104 | return total, fmt.Errorf("suback/Decode: Invalid return code %d for topic %d", code, i) 105 | } 106 | } 107 | 108 | this.dirty = false 109 | 110 | return total, nil 111 | } 112 | 113 | func (this *SubackMessage) Encode(dst []byte) (int, error) { 114 | if !this.dirty { 115 | if len(dst) < len(this.dbuf) { 116 | return 0, fmt.Errorf("suback/Encode: Insufficient buffer size. Expecting %d, got %d.", len(this.dbuf), len(dst)) 117 | } 118 | 119 | return copy(dst, this.dbuf), nil 120 | } 121 | 122 | for i, code := range this.returnCodes { 123 | if code != 0x00 && code != 0x01 && code != 0x02 && code != 0x80 { 124 | return 0, fmt.Errorf("suback/Encode: Invalid return code %d for topic %d", code, i) 125 | } 126 | } 127 | 128 | hl := this.header.msglen() 129 | ml := this.msglen() 130 | 131 | if len(dst) < hl+ml { 132 | return 0, fmt.Errorf("suback/Encode: Insufficient buffer size. Expecting %d, got %d.", hl+ml, len(dst)) 133 | } 134 | 135 | if err := this.SetRemainingLength(int32(ml)); err != nil { 136 | return 0, err 137 | } 138 | 139 | total := 0 140 | 141 | n, err := this.header.encode(dst[total:]) 142 | total += n 143 | if err != nil { 144 | return total, err 145 | } 146 | 147 | if copy(dst[total:total+2], this.packetId) != 2 { 148 | dst[total], dst[total+1] = 0, 0 149 | } 150 | total += 2 151 | 152 | copy(dst[total:], this.returnCodes) 153 | total += len(this.returnCodes) 154 | 155 | return total, nil 156 | } 157 | 158 | func (this *SubackMessage) msglen() int { 159 | return 2 + len(this.returnCodes) 160 | } 161 | -------------------------------------------------------------------------------- /suback_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | ) 22 | 23 | func TestSubackMessageFields(t *testing.T) { 24 | msg := NewSubackMessage() 25 | 26 | msg.SetPacketId(100) 27 | require.Equal(t, 100, int(msg.PacketId()), "Error setting packet ID.") 28 | 29 | msg.AddReturnCode(1) 30 | require.Equal(t, 1, len(msg.ReturnCodes()), "Error adding return code.") 31 | 32 | err := msg.AddReturnCode(0x90) 33 | require.Error(t, err) 34 | } 35 | 36 | func TestSubackMessageDecode(t *testing.T) { 37 | msgBytes := []byte{ 38 | byte(SUBACK << 4), 39 | 6, 40 | 0, // packet ID MSB (0) 41 | 7, // packet ID LSB (7) 42 | 0, // return code 1 43 | 1, // return code 2 44 | 2, // return code 3 45 | 0x80, // return code 4 46 | } 47 | 48 | msg := NewSubackMessage() 49 | n, err := msg.Decode(msgBytes) 50 | 51 | require.NoError(t, err, "Error decoding message.") 52 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 53 | require.Equal(t, SUBACK, msg.Type(), "Error decoding message.") 54 | require.Equal(t, 4, len(msg.ReturnCodes()), "Error adding return code.") 55 | } 56 | 57 | // test with wrong return code 58 | func TestSubackMessageDecode2(t *testing.T) { 59 | msgBytes := []byte{ 60 | byte(SUBACK << 4), 61 | 6, 62 | 0, // packet ID MSB (0) 63 | 7, // packet ID LSB (7) 64 | 0, // return code 1 65 | 1, // return code 2 66 | 2, // return code 3 67 | 0x81, // return code 4 68 | } 69 | 70 | msg := NewSubackMessage() 71 | _, err := msg.Decode(msgBytes) 72 | 73 | require.Error(t, err) 74 | } 75 | 76 | func TestSubackMessageEncode(t *testing.T) { 77 | msgBytes := []byte{ 78 | byte(SUBACK << 4), 79 | 6, 80 | 0, // packet ID MSB (0) 81 | 7, // packet ID LSB (7) 82 | 0, // return code 1 83 | 1, // return code 2 84 | 2, // return code 3 85 | 0x80, // return code 4 86 | } 87 | 88 | msg := NewSubackMessage() 89 | msg.SetPacketId(7) 90 | msg.AddReturnCode(0) 91 | msg.AddReturnCode(1) 92 | msg.AddReturnCode(2) 93 | msg.AddReturnCode(0x80) 94 | 95 | dst := make([]byte, 10) 96 | n, err := msg.Encode(dst) 97 | 98 | require.NoError(t, err, "Error decoding message.") 99 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 100 | require.Equal(t, msgBytes, dst[:n], "Error decoding message.") 101 | } 102 | 103 | // test to ensure encoding and decoding are the same 104 | // decode, encode, and decode again 105 | func TestSubackDecodeEncodeEquiv(t *testing.T) { 106 | msgBytes := []byte{ 107 | byte(SUBACK << 4), 108 | 6, 109 | 0, // packet ID MSB (0) 110 | 7, // packet ID LSB (7) 111 | 0, // return code 1 112 | 1, // return code 2 113 | 2, // return code 3 114 | 0x80, // return code 4 115 | } 116 | 117 | msg := NewSubackMessage() 118 | n, err := msg.Decode(msgBytes) 119 | 120 | require.NoError(t, err, "Error decoding message.") 121 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 122 | 123 | dst := make([]byte, 100) 124 | n2, err := msg.Encode(dst) 125 | 126 | require.NoError(t, err, "Error decoding message.") 127 | require.Equal(t, len(msgBytes), n2, "Error decoding message.") 128 | require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") 129 | 130 | n3, err := msg.Decode(dst) 131 | 132 | require.NoError(t, err, "Error decoding message.") 133 | require.Equal(t, len(msgBytes), n3, "Error decoding message.") 134 | } 135 | -------------------------------------------------------------------------------- /subscribe.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "sync/atomic" 21 | ) 22 | 23 | // The SUBSCRIBE Packet is sent from the Client to the Server to create one or more 24 | // Subscriptions. Each Subscription registers a Client’s interest in one or more 25 | // Topics. The Server sends PUBLISH Packets to the Client in order to forward 26 | // Application Messages that were published to Topics that match these Subscriptions. 27 | // The SUBSCRIBE Packet also specifies (for each Subscription) the maximum QoS with 28 | // which the Server can send Application Messages to the Client. 29 | type SubscribeMessage struct { 30 | header 31 | 32 | topics [][]byte 33 | qos []byte 34 | } 35 | 36 | var _ Message = (*SubscribeMessage)(nil) 37 | 38 | // NewSubscribeMessage creates a new SUBSCRIBE message. 39 | func NewSubscribeMessage() *SubscribeMessage { 40 | msg := &SubscribeMessage{} 41 | msg.SetType(SUBSCRIBE) 42 | 43 | return msg 44 | } 45 | 46 | func (this SubscribeMessage) String() string { 47 | msgstr := fmt.Sprintf("%s, Packet ID=%d", this.header, this.PacketId()) 48 | 49 | for i, t := range this.topics { 50 | msgstr = fmt.Sprintf("%s, Topic[%d]=%q/%d", msgstr, i, string(t), this.qos[i]) 51 | } 52 | 53 | return msgstr 54 | } 55 | 56 | // Topics returns a list of topics sent by the Client. 57 | func (this *SubscribeMessage) Topics() [][]byte { 58 | return this.topics 59 | } 60 | 61 | // AddTopic adds a single topic to the message, along with the corresponding QoS. 62 | // An error is returned if QoS is invalid. 63 | func (this *SubscribeMessage) AddTopic(topic []byte, qos byte) error { 64 | if !ValidQos(qos) { 65 | return fmt.Errorf("Invalid QoS %d", qos) 66 | } 67 | 68 | var i int 69 | var t []byte 70 | var found bool 71 | 72 | for i, t = range this.topics { 73 | if bytes.Equal(t, topic) { 74 | found = true 75 | break 76 | } 77 | } 78 | 79 | if found { 80 | this.qos[i] = qos 81 | return nil 82 | } 83 | 84 | this.topics = append(this.topics, topic) 85 | this.qos = append(this.qos, qos) 86 | this.dirty = true 87 | 88 | return nil 89 | } 90 | 91 | // RemoveTopic removes a single topic from the list of existing ones in the message. 92 | // If topic does not exist it just does nothing. 93 | func (this *SubscribeMessage) RemoveTopic(topic []byte) { 94 | var i int 95 | var t []byte 96 | var found bool 97 | 98 | for i, t = range this.topics { 99 | if bytes.Equal(t, topic) { 100 | found = true 101 | break 102 | } 103 | } 104 | 105 | if found { 106 | this.topics = append(this.topics[:i], this.topics[i+1:]...) 107 | this.qos = append(this.qos[:i], this.qos[i+1:]...) 108 | } 109 | 110 | this.dirty = true 111 | } 112 | 113 | // TopicExists checks to see if a topic exists in the list. 114 | func (this *SubscribeMessage) TopicExists(topic []byte) bool { 115 | for _, t := range this.topics { 116 | if bytes.Equal(t, topic) { 117 | return true 118 | } 119 | } 120 | 121 | return false 122 | } 123 | 124 | // TopicQos returns the QoS level of a topic. If topic does not exist, QosFailure 125 | // is returned. 126 | func (this *SubscribeMessage) TopicQos(topic []byte) byte { 127 | for i, t := range this.topics { 128 | if bytes.Equal(t, topic) { 129 | return this.qos[i] 130 | } 131 | } 132 | 133 | return QosFailure 134 | } 135 | 136 | // Qos returns the list of QoS current in the message. 137 | func (this *SubscribeMessage) Qos() []byte { 138 | return this.qos 139 | } 140 | 141 | func (this *SubscribeMessage) Len() int { 142 | if !this.dirty { 143 | return len(this.dbuf) 144 | } 145 | 146 | ml := this.msglen() 147 | 148 | if err := this.SetRemainingLength(int32(ml)); err != nil { 149 | return 0 150 | } 151 | 152 | return this.header.msglen() + ml 153 | } 154 | 155 | func (this *SubscribeMessage) Decode(src []byte) (int, error) { 156 | total := 0 157 | 158 | hn, err := this.header.decode(src[total:]) 159 | total += hn 160 | if err != nil { 161 | return total, err 162 | } 163 | 164 | //this.packetId = binary.BigEndian.Uint16(src[total:]) 165 | this.packetId = src[total : total+2] 166 | total += 2 167 | 168 | remlen := int(this.remlen) - (total - hn) 169 | for remlen > 0 { 170 | t, n, err := readLPBytes(src[total:]) 171 | total += n 172 | if err != nil { 173 | return total, err 174 | } 175 | 176 | this.topics = append(this.topics, t) 177 | 178 | this.qos = append(this.qos, src[total]) 179 | total++ 180 | 181 | remlen = remlen - n - 1 182 | } 183 | 184 | if len(this.topics) == 0 { 185 | return 0, fmt.Errorf("subscribe/Decode: Empty topic list") 186 | } 187 | 188 | this.dirty = false 189 | 190 | return total, nil 191 | } 192 | 193 | func (this *SubscribeMessage) Encode(dst []byte) (int, error) { 194 | if !this.dirty { 195 | if len(dst) < len(this.dbuf) { 196 | return 0, fmt.Errorf("subscribe/Encode: Insufficient buffer size. Expecting %d, got %d.", len(this.dbuf), len(dst)) 197 | } 198 | 199 | return copy(dst, this.dbuf), nil 200 | } 201 | 202 | hl := this.header.msglen() 203 | ml := this.msglen() 204 | 205 | if len(dst) < hl+ml { 206 | return 0, fmt.Errorf("subscribe/Encode: Insufficient buffer size. Expecting %d, got %d.", hl+ml, len(dst)) 207 | } 208 | 209 | if err := this.SetRemainingLength(int32(ml)); err != nil { 210 | return 0, err 211 | } 212 | 213 | total := 0 214 | 215 | n, err := this.header.encode(dst[total:]) 216 | total += n 217 | if err != nil { 218 | return total, err 219 | } 220 | 221 | if this.PacketId() == 0 { 222 | this.SetPacketId(uint16(atomic.AddUint64(&gPacketId, 1) & 0xffff)) 223 | //this.packetId = uint16(atomic.AddUint64(&gPacketId, 1) & 0xffff) 224 | } 225 | 226 | n = copy(dst[total:], this.packetId) 227 | //binary.BigEndian.PutUint16(dst[total:], this.packetId) 228 | total += n 229 | 230 | for i, t := range this.topics { 231 | n, err := writeLPBytes(dst[total:], t) 232 | total += n 233 | if err != nil { 234 | return total, err 235 | } 236 | 237 | dst[total] = this.qos[i] 238 | total++ 239 | } 240 | 241 | return total, nil 242 | } 243 | 244 | func (this *SubscribeMessage) msglen() int { 245 | // packet ID 246 | total := 2 247 | 248 | for _, t := range this.topics { 249 | total += 2 + len(t) + 1 250 | } 251 | 252 | return total 253 | } 254 | -------------------------------------------------------------------------------- /subscribe_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | ) 22 | 23 | func TestSubscribeMessageFields(t *testing.T) { 24 | msg := NewSubscribeMessage() 25 | 26 | msg.SetPacketId(100) 27 | require.Equal(t, 100, int(msg.PacketId()), "Error setting packet ID.") 28 | 29 | msg.AddTopic([]byte("/a/b/#/c"), 1) 30 | require.Equal(t, 1, len(msg.Topics()), "Error adding topic.") 31 | 32 | require.False(t, msg.TopicExists([]byte("a/b")), "Topic should not exist.") 33 | 34 | msg.RemoveTopic([]byte("/a/b/#/c")) 35 | require.False(t, msg.TopicExists([]byte("/a/b/#/c")), "Topic should not exist.") 36 | } 37 | 38 | func TestSubscribeMessageDecode(t *testing.T) { 39 | msgBytes := []byte{ 40 | byte(SUBSCRIBE<<4) | 2, 41 | 36, 42 | 0, // packet ID MSB (0) 43 | 7, // packet ID LSB (7) 44 | 0, // topic name MSB (0) 45 | 7, // topic name LSB (7) 46 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 47 | 0, // QoS 48 | 0, // topic name MSB (0) 49 | 8, // topic name LSB (8) 50 | '/', 'a', '/', 'b', '/', '#', '/', 'c', 51 | 1, // QoS 52 | 0, // topic name MSB (0) 53 | 10, // topic name LSB (10) 54 | '/', 'a', '/', 'b', '/', '#', '/', 'c', 'd', 'd', 55 | 2, // QoS 56 | } 57 | 58 | msg := NewSubscribeMessage() 59 | n, err := msg.Decode(msgBytes) 60 | 61 | require.NoError(t, err, "Error decoding message.") 62 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 63 | require.Equal(t, SUBSCRIBE, msg.Type(), "Error decoding message.") 64 | require.Equal(t, 3, len(msg.Topics()), "Error decoding topics.") 65 | require.True(t, msg.TopicExists([]byte("surgemq")), "Topic 'surgemq' should exist.") 66 | require.Equal(t, 0, int(msg.TopicQos([]byte("surgemq"))), "Incorrect topic qos.") 67 | require.True(t, msg.TopicExists([]byte("/a/b/#/c")), "Topic '/a/b/#/c' should exist.") 68 | require.Equal(t, 1, int(msg.TopicQos([]byte("/a/b/#/c"))), "Incorrect topic qos.") 69 | require.True(t, msg.TopicExists([]byte("/a/b/#/cdd")), "Topic '/a/b/#/c' should exist.") 70 | require.Equal(t, 2, int(msg.TopicQos([]byte("/a/b/#/cdd"))), "Incorrect topic qos.") 71 | } 72 | 73 | // test empty topic list 74 | func TestSubscribeMessageDecode2(t *testing.T) { 75 | msgBytes := []byte{ 76 | byte(SUBSCRIBE<<4) | 2, 77 | 2, 78 | 0, // packet ID MSB (0) 79 | 7, // packet ID LSB (7) 80 | } 81 | 82 | msg := NewSubscribeMessage() 83 | _, err := msg.Decode(msgBytes) 84 | 85 | require.Error(t, err) 86 | } 87 | 88 | func TestSubscribeMessageEncode(t *testing.T) { 89 | msgBytes := []byte{ 90 | byte(SUBSCRIBE<<4) | 2, 91 | 36, 92 | 0, // packet ID MSB (0) 93 | 7, // packet ID LSB (7) 94 | 0, // topic name MSB (0) 95 | 7, // topic name LSB (7) 96 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 97 | 0, // QoS 98 | 0, // topic name MSB (0) 99 | 8, // topic name LSB (8) 100 | '/', 'a', '/', 'b', '/', '#', '/', 'c', 101 | 1, // QoS 102 | 0, // topic name MSB (0) 103 | 10, // topic name LSB (10) 104 | '/', 'a', '/', 'b', '/', '#', '/', 'c', 'd', 'd', 105 | 2, // QoS 106 | } 107 | 108 | msg := NewSubscribeMessage() 109 | msg.SetPacketId(7) 110 | msg.AddTopic([]byte("surgemq"), 0) 111 | msg.AddTopic([]byte("/a/b/#/c"), 1) 112 | msg.AddTopic([]byte("/a/b/#/cdd"), 2) 113 | 114 | dst := make([]byte, 100) 115 | n, err := msg.Encode(dst) 116 | 117 | require.NoError(t, err, "Error decoding message.") 118 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 119 | require.Equal(t, msgBytes, dst[:n], "Error decoding message.") 120 | } 121 | 122 | // test to ensure encoding and decoding are the same 123 | // decode, encode, and decode again 124 | func TestSubscribeDecodeEncodeEquiv(t *testing.T) { 125 | msgBytes := []byte{ 126 | byte(SUBSCRIBE<<4) | 2, 127 | 36, 128 | 0, // packet ID MSB (0) 129 | 7, // packet ID LSB (7) 130 | 0, // topic name MSB (0) 131 | 7, // topic name LSB (7) 132 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 133 | 0, // QoS 134 | 0, // topic name MSB (0) 135 | 8, // topic name LSB (8) 136 | '/', 'a', '/', 'b', '/', '#', '/', 'c', 137 | 1, // QoS 138 | 0, // topic name MSB (0) 139 | 10, // topic name LSB (10) 140 | '/', 'a', '/', 'b', '/', '#', '/', 'c', 'd', 'd', 141 | 2, // QoS 142 | } 143 | 144 | msg := NewSubscribeMessage() 145 | n, err := msg.Decode(msgBytes) 146 | 147 | require.NoError(t, err, "Error decoding message.") 148 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 149 | 150 | dst := make([]byte, 100) 151 | n2, err := msg.Encode(dst) 152 | 153 | require.NoError(t, err, "Error decoding message.") 154 | require.Equal(t, len(msgBytes), n2, "Error decoding message.") 155 | require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") 156 | 157 | n3, err := msg.Decode(dst) 158 | 159 | require.NoError(t, err, "Error decoding message.") 160 | require.Equal(t, len(msgBytes), n3, "Error decoding message.") 161 | } 162 | -------------------------------------------------------------------------------- /unsuback.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | // The UNSUBACK Packet is sent by the Server to the Client to confirm receipt of an 18 | // UNSUBSCRIBE Packet. 19 | type UnsubackMessage struct { 20 | PubackMessage 21 | } 22 | 23 | var _ Message = (*UnsubackMessage)(nil) 24 | 25 | // NewUnsubackMessage creates a new UNSUBACK message. 26 | func NewUnsubackMessage() *UnsubackMessage { 27 | msg := &UnsubackMessage{} 28 | msg.SetType(UNSUBACK) 29 | 30 | return msg 31 | } 32 | -------------------------------------------------------------------------------- /unsuback_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | ) 22 | 23 | func TestUnsubackMessageFields(t *testing.T) { 24 | msg := NewUnsubackMessage() 25 | 26 | msg.SetPacketId(100) 27 | 28 | require.Equal(t, 100, int(msg.PacketId())) 29 | } 30 | 31 | func TestUnsubackMessageDecode(t *testing.T) { 32 | msgBytes := []byte{ 33 | byte(UNSUBACK << 4), 34 | 2, 35 | 0, // packet ID MSB (0) 36 | 7, // packet ID LSB (7) 37 | } 38 | 39 | msg := NewUnsubackMessage() 40 | n, err := msg.Decode(msgBytes) 41 | 42 | require.NoError(t, err, "Error decoding message.") 43 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 44 | require.Equal(t, UNSUBACK, msg.Type(), "Error decoding message.") 45 | require.Equal(t, 7, int(msg.PacketId()), "Error decoding message.") 46 | } 47 | 48 | // test insufficient bytes 49 | func TestUnsubackMessageDecode2(t *testing.T) { 50 | msgBytes := []byte{ 51 | byte(UNSUBACK << 4), 52 | 2, 53 | 7, // packet ID LSB (7) 54 | } 55 | 56 | msg := NewUnsubackMessage() 57 | _, err := msg.Decode(msgBytes) 58 | 59 | require.Error(t, err) 60 | } 61 | 62 | func TestUnsubackMessageEncode(t *testing.T) { 63 | msgBytes := []byte{ 64 | byte(UNSUBACK << 4), 65 | 2, 66 | 0, // packet ID MSB (0) 67 | 7, // packet ID LSB (7) 68 | } 69 | 70 | msg := NewUnsubackMessage() 71 | msg.SetPacketId(7) 72 | 73 | dst := make([]byte, 10) 74 | n, err := msg.Encode(dst) 75 | 76 | require.NoError(t, err, "Error decoding message.") 77 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 78 | require.Equal(t, msgBytes, dst[:n], "Error decoding message.") 79 | } 80 | 81 | // test to ensure encoding and decoding are the same 82 | // decode, encode, and decode again 83 | func TestUnsubackDecodeEncodeEquiv(t *testing.T) { 84 | msgBytes := []byte{ 85 | byte(UNSUBACK << 4), 86 | 2, 87 | 0, // packet ID MSB (0) 88 | 7, // packet ID LSB (7) 89 | } 90 | 91 | msg := NewUnsubackMessage() 92 | n, err := msg.Decode(msgBytes) 93 | 94 | require.NoError(t, err, "Error decoding message.") 95 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 96 | 97 | dst := make([]byte, 100) 98 | n2, err := msg.Encode(dst) 99 | 100 | require.NoError(t, err, "Error decoding message.") 101 | require.Equal(t, len(msgBytes), n2, "Error decoding message.") 102 | require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") 103 | 104 | n3, err := msg.Decode(dst) 105 | 106 | require.NoError(t, err, "Error decoding message.") 107 | require.Equal(t, len(msgBytes), n3, "Error decoding message.") 108 | } 109 | -------------------------------------------------------------------------------- /unsubscribe.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "sync/atomic" 21 | ) 22 | 23 | // An UNSUBSCRIBE Packet is sent by the Client to the Server, to unsubscribe from topics. 24 | type UnsubscribeMessage struct { 25 | header 26 | 27 | topics [][]byte 28 | } 29 | 30 | var _ Message = (*UnsubscribeMessage)(nil) 31 | 32 | // NewUnsubscribeMessage creates a new UNSUBSCRIBE message. 33 | func NewUnsubscribeMessage() *UnsubscribeMessage { 34 | msg := &UnsubscribeMessage{} 35 | msg.SetType(UNSUBSCRIBE) 36 | 37 | return msg 38 | } 39 | 40 | func (this UnsubscribeMessage) String() string { 41 | msgstr := fmt.Sprintf("%s", this.header) 42 | 43 | for i, t := range this.topics { 44 | msgstr = fmt.Sprintf("%s, Topic%d=%s", msgstr, i, string(t)) 45 | } 46 | 47 | return msgstr 48 | } 49 | 50 | // Topics returns a list of topics sent by the Client. 51 | func (this *UnsubscribeMessage) Topics() [][]byte { 52 | return this.topics 53 | } 54 | 55 | // AddTopic adds a single topic to the message. 56 | func (this *UnsubscribeMessage) AddTopic(topic []byte) { 57 | if this.TopicExists(topic) { 58 | return 59 | } 60 | 61 | this.topics = append(this.topics, topic) 62 | this.dirty = true 63 | } 64 | 65 | // RemoveTopic removes a single topic from the list of existing ones in the message. 66 | // If topic does not exist it just does nothing. 67 | func (this *UnsubscribeMessage) RemoveTopic(topic []byte) { 68 | var i int 69 | var t []byte 70 | var found bool 71 | 72 | for i, t = range this.topics { 73 | if bytes.Equal(t, topic) { 74 | found = true 75 | break 76 | } 77 | } 78 | 79 | if found { 80 | this.topics = append(this.topics[:i], this.topics[i+1:]...) 81 | } 82 | 83 | this.dirty = true 84 | } 85 | 86 | // TopicExists checks to see if a topic exists in the list. 87 | func (this *UnsubscribeMessage) TopicExists(topic []byte) bool { 88 | for _, t := range this.topics { 89 | if bytes.Equal(t, topic) { 90 | return true 91 | } 92 | } 93 | 94 | return false 95 | } 96 | 97 | func (this *UnsubscribeMessage) Len() int { 98 | if !this.dirty { 99 | return len(this.dbuf) 100 | } 101 | 102 | ml := this.msglen() 103 | 104 | if err := this.SetRemainingLength(int32(ml)); err != nil { 105 | return 0 106 | } 107 | 108 | return this.header.msglen() + ml 109 | } 110 | 111 | // Decode reads from the io.Reader parameter until a full message is decoded, or 112 | // when io.Reader returns EOF or error. The first return value is the number of 113 | // bytes read from io.Reader. The second is error if Decode encounters any problems. 114 | func (this *UnsubscribeMessage) Decode(src []byte) (int, error) { 115 | total := 0 116 | 117 | hn, err := this.header.decode(src[total:]) 118 | total += hn 119 | if err != nil { 120 | return total, err 121 | } 122 | 123 | //this.packetId = binary.BigEndian.Uint16(src[total:]) 124 | this.packetId = src[total : total+2] 125 | total += 2 126 | 127 | remlen := int(this.remlen) - (total - hn) 128 | for remlen > 0 { 129 | t, n, err := readLPBytes(src[total:]) 130 | total += n 131 | if err != nil { 132 | return total, err 133 | } 134 | 135 | this.topics = append(this.topics, t) 136 | remlen = remlen - n - 1 137 | } 138 | 139 | if len(this.topics) == 0 { 140 | return 0, fmt.Errorf("unsubscribe/Decode: Empty topic list") 141 | } 142 | 143 | this.dirty = false 144 | 145 | return total, nil 146 | } 147 | 148 | // Encode returns an io.Reader in which the encoded bytes can be read. The second 149 | // return value is the number of bytes encoded, so the caller knows how many bytes 150 | // there will be. If Encode returns an error, then the first two return values 151 | // should be considered invalid. 152 | // Any changes to the message after Encode() is called will invalidate the io.Reader. 153 | func (this *UnsubscribeMessage) Encode(dst []byte) (int, error) { 154 | if !this.dirty { 155 | if len(dst) < len(this.dbuf) { 156 | return 0, fmt.Errorf("unsubscribe/Encode: Insufficient buffer size. Expecting %d, got %d.", len(this.dbuf), len(dst)) 157 | } 158 | 159 | return copy(dst, this.dbuf), nil 160 | } 161 | 162 | hl := this.header.msglen() 163 | ml := this.msglen() 164 | 165 | if len(dst) < hl+ml { 166 | return 0, fmt.Errorf("unsubscribe/Encode: Insufficient buffer size. Expecting %d, got %d.", hl+ml, len(dst)) 167 | } 168 | 169 | if err := this.SetRemainingLength(int32(ml)); err != nil { 170 | return 0, err 171 | } 172 | 173 | total := 0 174 | 175 | n, err := this.header.encode(dst[total:]) 176 | total += n 177 | if err != nil { 178 | return total, err 179 | } 180 | 181 | if this.PacketId() == 0 { 182 | this.SetPacketId(uint16(atomic.AddUint64(&gPacketId, 1) & 0xffff)) 183 | //this.packetId = uint16(atomic.AddUint64(&gPacketId, 1) & 0xffff) 184 | } 185 | 186 | n = copy(dst[total:], this.packetId) 187 | //binary.BigEndian.PutUint16(dst[total:], this.packetId) 188 | total += n 189 | 190 | for _, t := range this.topics { 191 | n, err := writeLPBytes(dst[total:], t) 192 | total += n 193 | if err != nil { 194 | return total, err 195 | } 196 | } 197 | 198 | return total, nil 199 | } 200 | 201 | func (this *UnsubscribeMessage) msglen() int { 202 | // packet ID 203 | total := 2 204 | 205 | for _, t := range this.topics { 206 | total += 2 + len(t) 207 | } 208 | 209 | return total 210 | } 211 | -------------------------------------------------------------------------------- /unsubscribe_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package message 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | ) 22 | 23 | func TestUnsubscribeMessageFields(t *testing.T) { 24 | msg := NewUnsubscribeMessage() 25 | 26 | msg.SetPacketId(100) 27 | require.Equal(t, 100, int(msg.PacketId()), "Error setting packet ID.") 28 | 29 | msg.AddTopic([]byte("/a/b/#/c")) 30 | require.Equal(t, 1, len(msg.Topics()), "Error adding topic.") 31 | 32 | msg.AddTopic([]byte("/a/b/#/c")) 33 | require.Equal(t, 1, len(msg.Topics()), "Error adding duplicate topic.") 34 | 35 | msg.RemoveTopic([]byte("/a/b/#/c")) 36 | require.False(t, msg.TopicExists([]byte("/a/b/#/c")), "Topic should not exist.") 37 | 38 | require.False(t, msg.TopicExists([]byte("a/b")), "Topic should not exist.") 39 | 40 | msg.RemoveTopic([]byte("/a/b/#/c")) 41 | require.False(t, msg.TopicExists([]byte("/a/b/#/c")), "Topic should not exist.") 42 | } 43 | 44 | func TestUnsubscribeMessageDecode(t *testing.T) { 45 | msgBytes := []byte{ 46 | byte(UNSUBSCRIBE<<4) | 2, 47 | 33, 48 | 0, // packet ID MSB (0) 49 | 7, // packet ID LSB (7) 50 | 0, // topic name MSB (0) 51 | 7, // topic name LSB (7) 52 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 53 | 0, // topic name MSB (0) 54 | 8, // topic name LSB (8) 55 | '/', 'a', '/', 'b', '/', '#', '/', 'c', 56 | 0, // topic name MSB (0) 57 | 10, // topic name LSB (10) 58 | '/', 'a', '/', 'b', '/', '#', '/', 'c', 'd', 'd', 59 | } 60 | 61 | msg := NewUnsubscribeMessage() 62 | n, err := msg.Decode(msgBytes) 63 | 64 | require.NoError(t, err, "Error decoding message.") 65 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 66 | require.Equal(t, UNSUBSCRIBE, msg.Type(), "Error decoding message.") 67 | require.Equal(t, 3, len(msg.Topics()), "Error decoding topics.") 68 | require.True(t, msg.TopicExists([]byte("surgemq")), "Topic 'surgemq' should exist.") 69 | require.True(t, msg.TopicExists([]byte("/a/b/#/c")), "Topic '/a/b/#/c' should exist.") 70 | require.True(t, msg.TopicExists([]byte("/a/b/#/cdd")), "Topic '/a/b/#/c' should exist.") 71 | } 72 | 73 | // test empty topic list 74 | func TestUnsubscribeMessageDecode2(t *testing.T) { 75 | msgBytes := []byte{ 76 | byte(UNSUBSCRIBE<<4) | 2, 77 | 2, 78 | 0, // packet ID MSB (0) 79 | 7, // packet ID LSB (7) 80 | } 81 | 82 | msg := NewUnsubscribeMessage() 83 | _, err := msg.Decode(msgBytes) 84 | 85 | require.Error(t, err) 86 | } 87 | 88 | func TestUnsubscribeMessageEncode(t *testing.T) { 89 | msgBytes := []byte{ 90 | byte(UNSUBSCRIBE<<4) | 2, 91 | 33, 92 | 0, // packet ID MSB (0) 93 | 7, // packet ID LSB (7) 94 | 0, // topic name MSB (0) 95 | 7, // topic name LSB (7) 96 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 97 | 0, // topic name MSB (0) 98 | 8, // topic name LSB (8) 99 | '/', 'a', '/', 'b', '/', '#', '/', 'c', 100 | 0, // topic name MSB (0) 101 | 10, // topic name LSB (10) 102 | '/', 'a', '/', 'b', '/', '#', '/', 'c', 'd', 'd', 103 | } 104 | 105 | msg := NewUnsubscribeMessage() 106 | msg.SetPacketId(7) 107 | msg.AddTopic([]byte("surgemq")) 108 | msg.AddTopic([]byte("/a/b/#/c")) 109 | msg.AddTopic([]byte("/a/b/#/cdd")) 110 | 111 | dst := make([]byte, 100) 112 | n, err := msg.Encode(dst) 113 | 114 | require.NoError(t, err, "Error decoding message.") 115 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 116 | require.Equal(t, msgBytes, dst[:n], "Error decoding message.") 117 | } 118 | 119 | // test to ensure encoding and decoding are the same 120 | // decode, encode, and decode again 121 | func TestUnsubscribeDecodeEncodeEquiv(t *testing.T) { 122 | msgBytes := []byte{ 123 | byte(UNSUBSCRIBE<<4) | 2, 124 | 33, 125 | 0, // packet ID MSB (0) 126 | 7, // packet ID LSB (7) 127 | 0, // topic name MSB (0) 128 | 7, // topic name LSB (7) 129 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 130 | 0, // topic name MSB (0) 131 | 8, // topic name LSB (8) 132 | '/', 'a', '/', 'b', '/', '#', '/', 'c', 133 | 0, // topic name MSB (0) 134 | 10, // topic name LSB (10) 135 | '/', 'a', '/', 'b', '/', '#', '/', 'c', 'd', 'd', 136 | } 137 | 138 | msg := NewUnsubscribeMessage() 139 | n, err := msg.Decode(msgBytes) 140 | 141 | require.NoError(t, err, "Error decoding message.") 142 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 143 | 144 | dst := make([]byte, 100) 145 | n2, err := msg.Encode(dst) 146 | 147 | require.NoError(t, err, "Error decoding message.") 148 | require.Equal(t, len(msgBytes), n2, "Error decoding message.") 149 | require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") 150 | 151 | n3, err := msg.Decode(dst) 152 | 153 | require.NoError(t, err, "Error decoding message.") 154 | require.Equal(t, len(msgBytes), n3, "Error decoding message.") 155 | } 156 | --------------------------------------------------------------------------------