diff --git a/broker/packet.go b/broker/packet.go deleted file mode 100644 index 28715ef..0000000 --- a/broker/packet.go +++ /dev/null @@ -1,236 +0,0 @@ -package broker - -import ( - "errors" - "hmq/lib/message" - "io" - "net" - - log "github.com/cihub/seelog" -) - -func checkError(desc string, err error) { - if err != nil { - log.Error(desc, " : ", err) - } -} - -func ReadPacket(conn net.Conn) ([]byte, error) { - if conn == nil { - return nil, errors.New("conn is null") - } - // conn.SetReadDeadline(t) - var buf []byte - // read fix header - b := make([]byte, 1) - _, err := io.ReadFull(conn, b) - if err != nil { - return nil, err - } - buf = append(buf, b...) - // read rem msg length - rembuf, remlen := decodeLength(conn) - buf = append(buf, rembuf...) - // read rem msg - packetBytes := make([]byte, remlen) - _, err = io.ReadFull(conn, packetBytes) - if err != nil { - return nil, err - } - buf = append(buf, packetBytes...) - // log.Info("len buf: ", len(buf)) - return buf, nil -} - -func decodeLength(r io.Reader) ([]byte, int) { - var rLength uint32 - var multiplier uint32 - var buf []byte - b := make([]byte, 1) - for { - io.ReadFull(r, b) - digit := b[0] - buf = append(buf, b[0]) - rLength |= uint32(digit&127) << multiplier - if (digit & 128) == 0 { - break - } - multiplier += 7 - - } - return buf, int(rLength) -} - -func DecodeMessage(buf []byte) (message.Message, error) { - msgType := uint8(buf[0] & 0xF0 >> 4) - switch msgType { - case CONNECT: - return DecodeConnectMessage(buf) - case CONNACK: - return DecodeConnackMessage(buf) - case PUBLISH: - return DecodePublishMessage(buf) - case PUBACK: - return DecodePubackMessage(buf) - case PUBCOMP: - return DecodePubcompMessage(buf) - case PUBREC: - return DecodePubrecMessage(buf) - case PUBREL: - return DecodePubrelMessage(buf) - case SUBSCRIBE: - return DecodeSubscribeMessage(buf) - case SUBACK: - return DecodeSubackMessage(buf) - case UNSUBSCRIBE: - return DecodeUnsubscribeMessage(buf) - case UNSUBACK: - return DecodeUnsubackMessage(buf) - case PINGREQ: - return DecodePingreqMessage(buf) - case PINGRESP: - return DecodePingrespMessage(buf) - case DISCONNECT: - return DecodeDisconnectMessage(buf) - default: - return nil, errors.New("error message type") - } -} - -func DecodeConnectMessage(buf []byte) (*message.ConnectMessage, error) { - connMsg := message.NewConnectMessage() - _, err := connMsg.Decode(buf) - if err != nil { - if !message.ValidConnackError(err) { - return nil, errors.New("Connect message format error, " + err.Error()) - } - return nil, errors.New("Deode connect message error, " + err.Error()) - } - return connMsg, nil -} - -func DecodeConnackMessage(buf []byte) (*message.ConnackMessage, error) { - msg := message.NewConnackMessage() - _, err := msg.Decode(buf) - if err != nil { - return nil, errors.New("Decode Connack message error, " + err.Error()) - } - return msg, nil -} - -func DecodePublishMessage(buf []byte) (*message.PublishMessage, error) { - msg := message.NewPublishMessage() - _, err := msg.Decode(buf) - if err != nil { - return nil, errors.New("Decode Publish message error, " + err.Error()) - } - return msg, nil -} - -func DecodePubackMessage(buf []byte) (*message.PubackMessage, error) { - msg := message.NewPubackMessage() - _, err := msg.Decode(buf) - if err != nil { - return nil, errors.New("Decode Puback message error, " + err.Error()) - } - return msg, nil -} - -func DecodePubrecMessage(buf []byte) (*message.PubrecMessage, error) { - msg := message.NewPubrecMessage() - _, err := msg.Decode(buf) - if err != nil { - return nil, errors.New("Decode Pubrec message error, " + err.Error()) - } - return msg, nil -} - -func DecodePubrelMessage(buf []byte) (*message.PubrelMessage, error) { - msg := message.NewPubrelMessage() - _, err := msg.Decode(buf) - if err != nil { - return nil, errors.New("Decode Pubrel message error, " + err.Error()) - } - return msg, nil -} - -func DecodePubcompMessage(buf []byte) (*message.PubcompMessage, error) { - msg := message.NewPubcompMessage() - _, err := msg.Decode(buf) - if err != nil { - return nil, errors.New("Decode Pubcomp message error, " + err.Error()) - } - return msg, nil -} - -func DecodeSubscribeMessage(buf []byte) (*message.SubscribeMessage, error) { - msg := message.NewSubscribeMessage() - _, err := msg.Decode(buf) - if err != nil { - return nil, errors.New("Decode Subscribe message error, " + err.Error()) - } - return msg, nil -} - -func DecodeSubackMessage(buf []byte) (*message.SubackMessage, error) { - msg := message.NewSubackMessage() - _, err := msg.Decode(buf) - if err != nil { - return nil, errors.New("Decode Suback message error, " + err.Error()) - } - return msg, nil -} - -func DecodeUnsubscribeMessage(buf []byte) (*message.UnsubscribeMessage, error) { - msg := message.NewUnsubscribeMessage() - _, err := msg.Decode(buf) - if err != nil { - return nil, errors.New("Decode Unsubscribe message error, " + err.Error()) - } - return msg, nil -} - -func DecodeUnsubackMessage(buf []byte) (*message.UnsubackMessage, error) { - msg := message.NewUnsubackMessage() - _, err := msg.Decode(buf) - if err != nil { - return nil, errors.New("Decode Unsuback message error, " + err.Error()) - } - return msg, nil -} - -func DecodePingreqMessage(buf []byte) (*message.PingreqMessage, error) { - msg := message.NewPingreqMessage() - _, err := msg.Decode(buf) - if err != nil { - return nil, errors.New("Decode Pingreq message error, " + err.Error()) - } - return msg, nil -} - -func DecodePingrespMessage(buf []byte) (*message.PingrespMessage, error) { - msg := message.NewPingrespMessage() - _, err := msg.Decode(buf) - if err != nil { - return nil, errors.New("Decode Pingresp message error, " + err.Error()) - } - return msg, nil -} - -func DecodeDisconnectMessage(buf []byte) (*message.DisconnectMessage, error) { - msg := message.NewDisconnectMessage() - _, err := msg.Decode(buf) - if err != nil { - return nil, errors.New("Decode Disconnect message error, " + err.Error()) - } - return msg, nil -} - -func EncodeMessage(msg message.Message) ([]byte, error) { - buf := make([]byte, msg.Len()) - _, err := msg.Encode(buf) - if err != nil { - return nil, err - } - return buf, nil -} diff --git a/hmq b/hmq index de70a21..919ad64 100755 Binary files a/hmq and b/hmq differ diff --git a/lib/message/AUTHORS b/lib/message/AUTHORS deleted file mode 100755 index 8660f5c..0000000 --- a/lib/message/AUTHORS +++ /dev/null @@ -1,16 +0,0 @@ -# This is the official list of SurgeMQ authors for copyright purposes. - -# If you are submitting a patch, please add your name or the name of the -# organization which holds the copyright to this list in alphabetical order. - -# Names should be added to this file as -# Name -# The email address is not required for organizations. -# Please keep the list sorted. - - -# Individual Persons - -Jian Zhen - -# Organizations diff --git a/lib/message/CONTRIBUTING.md b/lib/message/CONTRIBUTING.md deleted file mode 100755 index fcf4f2b..0000000 --- a/lib/message/CONTRIBUTING.md +++ /dev/null @@ -1,36 +0,0 @@ -# Contributing Guidelines - -## Reporting Issues - -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). - -Please provide the following minimum information: -* Your SurgeMQ version (or git SHA) -* Your Go version (run `go version` in your console) -* A detailed issue description -* Error Log if present -* If possible, a short example - - -## Contributing Code - -By contributing to this project, you share your code under the Apache License, Version 2.0, as specified in the LICENSE file. -Don't forget to add yourself to the AUTHORS file. - -### Pull Requests Checklist - -Please check the following points before submitting your pull request: -- [x] Code compiles correctly -- [x] Created tests, if possible -- [x] All tests pass -- [x] Extended the README / documentation, if necessary -- [x] Added yourself to the AUTHORS file - -### Code Review - -Everyone is invited to review and comment on pull requests. -If it looks fine to you, comment with "LGTM" (Looks good to me). - -If changes are required, notice the reviewers with "PTAL" (Please take another look) after committing the fixes. - -Before merging the Pull Request, at least one [team member](https://github.com/orgs/surgemq/people) must have commented with "LGTM". diff --git a/lib/message/LICENSE b/lib/message/LICENSE deleted file mode 100755 index ad410e1..0000000 --- a/lib/message/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ -Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file diff --git a/lib/message/README.md b/lib/message/README.md deleted file mode 100755 index 6def35e..0000000 --- a/lib/message/README.md +++ /dev/null @@ -1,136 +0,0 @@ -Package message is an encoder/decoder library for MQTT 3.1 and 3.1.1 messages. You can -find the MQTT specs at the following locations: - -> 3.1.1 - http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/ -> 3.1 - http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html - -From the spec: - -> MQTT is a Client Server publish/subscribe messaging transport protocol. It is -> light weight, open, simple, and designed so as to be easy to implement. These -> characteristics make it ideal for use in many situations, including constrained -> environments such as for communication in Machine to Machine (M2M) and Internet -> of Things (IoT) contexts where a small code footprint is required and/or network -> bandwidth is at a premium. -> -> The MQTT protocol works by exchanging a series of MQTT messages in a defined way. -> The protocol runs over TCP/IP, or over other network protocols that provide -> ordered, lossless, bi-directional connections. - - -There are two main items to take note in this package. The first is - -``` - type MessageType byte -``` - -MessageType is the type representing the MQTT packet types. In the MQTT spec, MQTT -control packet type is represented as a 4-bit unsigned value. MessageType receives -several methods that returns string representations of the names and descriptions. - -Also, one of the methods is New(). It returns a new Message object based on the mtype -parameter. For example: - -``` - m, err := CONNECT.New() - msg := m.(*ConnectMessage) -``` - -This would return a PublishMessage struct, but mapped to the Message interface. You can -then type assert it back to a *PublishMessage. Another way to create a new -PublishMessage is to call - -``` - msg := NewConnectMessage() -``` - -Every message type has a New function that returns a new message. The list of available -message types are defined as constants below. - -As you may have noticed, the second important item is the Message interface. It defines -several methods that are common to all messages, including Name(), Desc(), and Type(). -Most importantly, it also defines the Encode() and Decode() methods. - -``` - Encode() (io.Reader, int, error) - Decode(io.Reader) (int, error) -``` - -Encode returns an io.Reader in which the encoded bytes can be read. The second return -value is the number of bytes encoded, so the caller knows how many bytes there will be. -If Encode returns an error, then the first two return values should be considered invalid. -Any changes to the message after Encode() is called will invalidate the io.Reader. - -Decode reads from the io.Reader parameter until a full message is decoded, or when io.Reader -returns EOF or error. The first return value is the number of bytes read from io.Reader. -The second is error if Decode encounters any problems. - -With these in mind, we can now do: - -``` - // Create a new CONNECT message - msg := NewConnectMessage() - - // Set the appropriate parameters - msg.SetWillQos(1) - msg.SetVersion(4) - msg.SetCleanSession(true) - msg.SetClientId([]byte("surgemq")) - msg.SetKeepAlive(10) - msg.SetWillTopic([]byte("will")) - msg.SetWillMessage([]byte("send me home")) - msg.SetUsername([]byte("surgemq")) - msg.SetPassword([]byte("verysecret")) - - // Encode the message and get the io.Reader - r, n, err := msg.Encode() - if err == nil { - return err - } - - // Write n bytes into the connection - m, err := io.CopyN(conn, r, int64(n)) - if err != nil { - return err - } - - fmt.Printf("Sent %d bytes of %s message", m, msg.Name()) -``` - -To receive a CONNECT message from a connection, we can do: - -``` - // Create a new CONNECT message - msg := NewConnectMessage() - - // Decode the message by reading from conn - n, err := msg.Decode(conn) -``` - -If you don't know what type of message is coming down the pipe, you can do something like this: - -``` - // Create a buffered IO reader for the connection - br := bufio.NewReader(conn) - - // Peek at the first byte, which contains the message type - b, err := br.Peek(1) - if err != nil { - return err - } - - // Extract the type from the first byte - t := MessageType(b[0] >> 4) - - // Create a new message - msg, err := t.New() - if err != nil { - return err - } - - // Decode it from the bufio.Reader - n, err := msg.Decode(br) - if err != nil { - return err - } -``` diff --git a/lib/message/connack.go b/lib/message/connack.go deleted file mode 100755 index 75dd720..0000000 --- a/lib/message/connack.go +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -import "fmt" - -// The CONNACK Packet is the packet sent by the Server in response to a CONNECT Packet -// received from a Client. The first packet sent from the Server to the Client MUST -// be a CONNACK Packet [MQTT-3.2.0-1]. -// -// If the Client does not receive a CONNACK Packet from the Server within a reasonable -// amount of time, the Client SHOULD close the Network Connection. A "reasonable" amount -// of time depends on the type of application and the communications infrastructure. -type ConnackMessage struct { - header - - sessionPresent bool - returnCode ConnackCode -} - -var _ Message = (*ConnackMessage)(nil) - -// NewConnackMessage creates a new CONNACK message -func NewConnackMessage() *ConnackMessage { - msg := &ConnackMessage{} - msg.SetType(CONNACK) - - return msg -} - -// String returns a string representation of the CONNACK message -func (this ConnackMessage) String() string { - return fmt.Sprintf("%s, Session Present=%t, Return code=%q\n", this.header, this.sessionPresent, this.returnCode) -} - -// SessionPresent returns the session present flag value -func (this *ConnackMessage) SessionPresent() bool { - return this.sessionPresent -} - -// SetSessionPresent sets the value of the session present flag -func (this *ConnackMessage) SetSessionPresent(v bool) { - if v { - this.sessionPresent = true - } else { - this.sessionPresent = false - } - - this.dirty = true -} - -// ReturnCode returns the return code received for the CONNECT message. The return -// type is an error -func (this *ConnackMessage) ReturnCode() ConnackCode { - return this.returnCode -} - -func (this *ConnackMessage) SetReturnCode(ret ConnackCode) { - this.returnCode = ret - this.dirty = true -} - -func (this *ConnackMessage) Len() int { - if !this.dirty { - return len(this.dbuf) - } - - ml := this.msglen() - - if err := this.SetRemainingLength(int32(ml)); err != nil { - return 0 - } - - return this.header.msglen() + ml -} - -func (this *ConnackMessage) Decode(src []byte) (int, error) { - total := 0 - - n, err := this.header.decode(src) - total += n - if err != nil { - return total, err - } - - b := src[total] - - if b&254 != 0 { - return 0, fmt.Errorf("connack/Decode: Bits 7-1 in Connack Acknowledge Flags byte (1) are not 0") - } - - this.sessionPresent = b&0x1 == 1 - total++ - - b = src[total] - - // Read return code - if b > 5 { - return 0, fmt.Errorf("connack/Decode: Invalid CONNACK return code (%d)", b) - } - - this.returnCode = ConnackCode(b) - total++ - - this.dirty = false - - return total, nil -} - -func (this *ConnackMessage) Encode(dst []byte) (int, error) { - if !this.dirty { - if len(dst) < len(this.dbuf) { - return 0, fmt.Errorf("connack/Encode: Insufficient buffer size. Expecting %d, got %d.", len(this.dbuf), len(dst)) - } - - return copy(dst, this.dbuf), nil - } - - // CONNACK remaining length fixed at 2 bytes - hl := this.header.msglen() - ml := this.msglen() - - if len(dst) < hl+ml { - return 0, fmt.Errorf("connack/Encode: Insufficient buffer size. Expecting %d, got %d.", hl+ml, len(dst)) - } - - if err := this.SetRemainingLength(int32(ml)); err != nil { - return 0, err - } - - total := 0 - - n, err := this.header.encode(dst[total:]) - total += n - if err != nil { - return 0, err - } - - if this.sessionPresent { - dst[total] = 1 - } - total++ - - if this.returnCode > 5 { - return total, fmt.Errorf("connack/Encode: Invalid CONNACK return code (%d)", this.returnCode) - } - - dst[total] = this.returnCode.Value() - total++ - - return total, nil -} - -func (this *ConnackMessage) msglen() int { - return 2 -} diff --git a/lib/message/connack_test.go b/lib/message/connack_test.go deleted file mode 100755 index 26ab3e5..0000000 --- a/lib/message/connack_test.go +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestConnackMessageFields(t *testing.T) { - msg := NewConnackMessage() - - msg.SetSessionPresent(true) - require.True(t, msg.SessionPresent(), "Error setting session present flag.") - - msg.SetSessionPresent(false) - require.False(t, msg.SessionPresent(), "Error setting session present flag.") - - msg.SetReturnCode(ConnectionAccepted) - require.Equal(t, ConnectionAccepted, msg.ReturnCode(), "Error setting return code.") -} - -func TestConnackMessageDecode(t *testing.T) { - msgBytes := []byte{ - byte(CONNACK << 4), - 2, - 0, // session not present - 0, // connection accepted - } - - msg := NewConnackMessage() - - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.False(t, msg.SessionPresent(), "Error decoding session present flag.") - require.Equal(t, ConnectionAccepted, msg.ReturnCode(), "Error decoding return code.") -} - -// testing wrong message length -func TestConnackMessageDecode2(t *testing.T) { - msgBytes := []byte{ - byte(CONNACK << 4), - 3, - 0, // session not present - 0, // connection accepted - } - - msg := NewConnackMessage() - - _, err := msg.Decode(msgBytes) - require.Error(t, err, "Error decoding message.") -} - -// testing wrong message size -func TestConnackMessageDecode3(t *testing.T) { - msgBytes := []byte{ - byte(CONNACK << 4), - 2, - 0, // session not present - } - - msg := NewConnackMessage() - - _, err := msg.Decode(msgBytes) - require.Error(t, err, "Error decoding message.") -} - -// testing wrong reserve bits -func TestConnackMessageDecode4(t *testing.T) { - msgBytes := []byte{ - byte(CONNACK << 4), - 2, - 64, // <- wrong size - 0, // connection accepted - } - - msg := NewConnackMessage() - - _, err := msg.Decode(msgBytes) - require.Error(t, err, "Error decoding message.") -} - -// testing invalid return code -func TestConnackMessageDecode5(t *testing.T) { - msgBytes := []byte{ - byte(CONNACK << 4), - 2, - 0, - 6, // <- wrong code - } - - msg := NewConnackMessage() - - _, err := msg.Decode(msgBytes) - require.Error(t, err, "Error decoding message.") -} - -func TestConnackMessageEncode(t *testing.T) { - msgBytes := []byte{ - byte(CONNACK << 4), - 2, - 1, // session present - 0, // connection accepted - } - - msg := NewConnackMessage() - msg.SetReturnCode(ConnectionAccepted) - msg.SetSessionPresent(true) - - dst := make([]byte, 10) - n, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error encoding message.") - require.Equal(t, msgBytes, dst[:n], "Error encoding connack message.") -} - -// test to ensure encoding and decoding are the same -// decode, encode, and decode again -func TestConnackDecodeEncodeEquiv(t *testing.T) { - msgBytes := []byte{ - byte(CONNACK << 4), - 2, - 0, // session not present - 0, // connection accepted - } - - msg := NewConnackMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - - dst := make([]byte, 100) - n2, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n2, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") - - n3, err := msg.Decode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n3, "Error decoding message.") -} diff --git a/lib/message/connackcode.go b/lib/message/connackcode.go deleted file mode 100755 index 37d24e4..0000000 --- a/lib/message/connackcode.go +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -// ConnackCode is the type representing the return code in the CONNACK message, -// returned after the initial CONNECT message -type ConnackCode byte - -const ( - // Connection accepted - ConnectionAccepted ConnackCode = iota - - // The Server does not support the level of the MQTT protocol requested by the Client - ErrInvalidProtocolVersion - - // The Client identifier is correct UTF-8 but not allowed by the server - ErrIdentifierRejected - - // The Network Connection has been made but the MQTT service is unavailable - ErrServerUnavailable - - // The data in the user name or password is malformed - ErrBadUsernameOrPassword - - // The Client is not authorized to connect - ErrNotAuthorized -) - -// Value returns the value of the ConnackCode, which is just the byte representation -func (this ConnackCode) Value() byte { - return byte(this) -} - -// Desc returns the description of the ConnackCode -func (this ConnackCode) Desc() string { - switch this { - case 0: - return "Connection accepted" - case 1: - return "The Server does not support the level of the MQTT protocol requested by the Client" - case 2: - return "The Client identifier is correct UTF-8 but not allowed by the server" - case 3: - return "The Network Connection has been made but the MQTT service is unavailable" - case 4: - return "The data in the user name or password is malformed" - case 5: - return "The Client is not authorized to connect" - } - - return "" -} - -// Valid checks to see if the ConnackCode is valid. Currently valid codes are <= 5 -func (this ConnackCode) Valid() bool { - return this <= 5 -} - -// Error returns the corresonding error string for the ConnackCode -func (this ConnackCode) Error() string { - switch this { - case 0: - return "Connection accepted" - case 1: - return "Connection Refused, unacceptable protocol version" - case 2: - return "Connection Refused, identifier rejected" - case 3: - return "Connection Refused, Server unavailable" - case 4: - return "Connection Refused, bad user name or password" - case 5: - return "Connection Refused, not authorized" - } - - return "Unknown error" -} diff --git a/lib/message/connect.go b/lib/message/connect.go deleted file mode 100755 index 05c26c1..0000000 --- a/lib/message/connect.go +++ /dev/null @@ -1,635 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -import ( - "encoding/binary" - "fmt" - "regexp" -) - -var clientIdRegexp *regexp.Regexp - -func init() { - // Added space for Paho compliance test - // Added underscore (_) for MQTT C client test - clientIdRegexp = regexp.MustCompile("^[0-9a-zA-Z _]*$") -} - -// After a Network Connection is established by a Client to a Server, the first Packet -// sent from the Client to the Server MUST be a CONNECT Packet [MQTT-3.1.0-1]. -// -// A Client can only send the CONNECT Packet once over a Network Connection. The Server -// MUST process a second CONNECT Packet sent from a Client as a protocol violation and -// disconnect the Client [MQTT-3.1.0-2]. See section 4.8 for information about -// handling errors. -type ConnectMessage struct { - header - - // 7: username flag - // 6: password flag - // 5: will retain - // 4-3: will QoS - // 2: will flag - // 1: clean session - // 0: reserved - connectFlags byte - - version byte - - keepAlive uint16 - - protoName, - clientId, - willTopic, - willMessage, - username, - password []byte -} - -var _ Message = (*ConnectMessage)(nil) - -// NewConnectMessage creates a new CONNECT message. -func NewConnectMessage() *ConnectMessage { - msg := &ConnectMessage{} - msg.SetType(CONNECT) - - return msg -} - -// String returns a string representation of the CONNECT message -func (this ConnectMessage) String() string { - return fmt.Sprintf("%s, Connect Flags=%08b, Version=%d, KeepAlive=%d, Client ID=%q, Will Topic=%q, Will Message=%q, Username=%q, Password=%q", - this.header, - this.connectFlags, - this.Version(), - this.KeepAlive(), - this.ClientId(), - this.WillTopic(), - this.WillMessage(), - this.Username(), - this.Password(), - ) -} - -// Version returns the the 8 bit unsigned value that represents the revision level -// of the protocol used by the Client. The value of the Protocol Level field for -// the version 3.1.1 of the protocol is 4 (0x04). -func (this *ConnectMessage) Version() byte { - return this.version -} - -// SetVersion sets the version value of the CONNECT message -func (this *ConnectMessage) SetVersion(v byte) error { - if _, ok := SupportedVersions[v]; !ok { - return fmt.Errorf("connect/SetVersion: Invalid version number %d", v) - } - - this.version = v - this.dirty = true - - return nil -} - -// CleanSession returns the bit that specifies the handling of the Session state. -// The Client and Server can store Session state to enable reliable messaging to -// continue across a sequence of Network Connections. This bit is used to control -// the lifetime of the Session state. -func (this *ConnectMessage) CleanSession() bool { - return ((this.connectFlags >> 1) & 0x1) == 1 -} - -// SetCleanSession sets the bit that specifies the handling of the Session state. -func (this *ConnectMessage) SetCleanSession(v bool) { - if v { - this.connectFlags |= 0x2 // 00000010 - } else { - this.connectFlags &= 253 // 11111101 - } - - this.dirty = true -} - -// WillFlag returns the bit that specifies whether a Will Message should be stored -// on the server. If the Will Flag is set to 1 this indicates that, if the Connect -// request is accepted, a Will Message MUST be stored on the Server and associated -// with the Network Connection. -func (this *ConnectMessage) WillFlag() bool { - return ((this.connectFlags >> 2) & 0x1) == 1 -} - -// SetWillFlag sets the bit that specifies whether a Will Message should be stored -// on the server. -func (this *ConnectMessage) SetWillFlag(v bool) { - if v { - this.connectFlags |= 0x4 // 00000100 - } else { - this.connectFlags &= 251 // 11111011 - } - - this.dirty = true -} - -// WillQos returns the two bits that specify the QoS level to be used when publishing -// the Will Message. -func (this *ConnectMessage) WillQos() byte { - return (this.connectFlags >> 3) & 0x3 -} - -// SetWillQos sets the two bits that specify the QoS level to be used when publishing -// the Will Message. -func (this *ConnectMessage) SetWillQos(qos byte) error { - if qos != QosAtMostOnce && qos != QosAtLeastOnce && qos != QosExactlyOnce { - return fmt.Errorf("connect/SetWillQos: Invalid QoS level %d", qos) - } - - this.connectFlags = (this.connectFlags & 231) | (qos << 3) // 231 = 11100111 - this.dirty = true - - return nil -} - -// WillRetain returns the bit specifies if the Will Message is to be Retained when it -// is published. -func (this *ConnectMessage) WillRetain() bool { - return ((this.connectFlags >> 5) & 0x1) == 1 -} - -// SetWillRetain sets the bit specifies if the Will Message is to be Retained when it -// is published. -func (this *ConnectMessage) SetWillRetain(v bool) { - if v { - this.connectFlags |= 32 // 00100000 - } else { - this.connectFlags &= 223 // 11011111 - } - - this.dirty = true -} - -// UsernameFlag returns the bit that specifies whether a user name is present in the -// payload. -func (this *ConnectMessage) UsernameFlag() bool { - return ((this.connectFlags >> 7) & 0x1) == 1 -} - -// SetUsernameFlag sets the bit that specifies whether a user name is present in the -// payload. -func (this *ConnectMessage) SetUsernameFlag(v bool) { - if v { - this.connectFlags |= 128 // 10000000 - } else { - this.connectFlags &= 127 // 01111111 - } - - this.dirty = true -} - -// PasswordFlag returns the bit that specifies whether a password is present in the -// payload. -func (this *ConnectMessage) PasswordFlag() bool { - return ((this.connectFlags >> 6) & 0x1) == 1 -} - -// SetPasswordFlag sets the bit that specifies whether a password is present in the -// payload. -func (this *ConnectMessage) SetPasswordFlag(v bool) { - if v { - this.connectFlags |= 64 // 01000000 - } else { - this.connectFlags &= 191 // 10111111 - } - - this.dirty = true -} - -// KeepAlive returns a time interval measured in seconds. Expressed as a 16-bit word, -// it is the maximum time interval that is permitted to elapse between the point at -// which the Client finishes transmitting one Control Packet and the point it starts -// sending the next. -func (this *ConnectMessage) KeepAlive() uint16 { - return this.keepAlive -} - -// SetKeepAlive sets the time interval in which the server should keep the connection -// alive. -func (this *ConnectMessage) SetKeepAlive(v uint16) { - this.keepAlive = v - - this.dirty = true -} - -// ClientId returns an ID that identifies the Client to the Server. Each Client -// connecting to the Server has a unique ClientId. The ClientId MUST be used by -// Clients and by Servers to identify state that they hold relating to this MQTT -// Session between the Client and the Server -func (this *ConnectMessage) ClientId() []byte { - return this.clientId -} - -// SetClientId sets an ID that identifies the Client to the Server. -func (this *ConnectMessage) SetClientId(v []byte) error { - if len(v) > 0 && !this.validClientId(v) { - return ErrIdentifierRejected - } - - this.clientId = v - this.dirty = true - - return nil -} - -// WillTopic returns the topic in which the Will Message should be published to. -// If the Will Flag is set to 1, the Will Topic must be in the payload. -func (this *ConnectMessage) WillTopic() []byte { - return this.willTopic -} - -// SetWillTopic sets the topic in which the Will Message should be published to. -func (this *ConnectMessage) SetWillTopic(v []byte) { - this.willTopic = v - - if len(v) > 0 { - this.SetWillFlag(true) - } else if len(this.willMessage) == 0 { - this.SetWillFlag(false) - } - - this.dirty = true -} - -// WillMessage returns the Will Message that is to be published to the Will Topic. -func (this *ConnectMessage) WillMessage() []byte { - return this.willMessage -} - -// SetWillMessage sets the Will Message that is to be published to the Will Topic. -func (this *ConnectMessage) SetWillMessage(v []byte) { - this.willMessage = v - - if len(v) > 0 { - this.SetWillFlag(true) - } else if len(this.willTopic) == 0 { - this.SetWillFlag(false) - } - - this.dirty = true -} - -// Username returns the username from the payload. If the User Name Flag is set to 1, -// this must be in the payload. It can be used by the Server for authentication and -// authorization. -func (this *ConnectMessage) Username() []byte { - return this.username -} - -// SetUsername sets the username for authentication. -func (this *ConnectMessage) SetUsername(v []byte) { - this.username = v - - if len(v) > 0 { - this.SetUsernameFlag(true) - } else { - this.SetUsernameFlag(false) - } - - this.dirty = true -} - -// Password returns the password from the payload. If the Password Flag is set to 1, -// this must be in the payload. It can be used by the Server for authentication and -// authorization. -func (this *ConnectMessage) Password() []byte { - return this.password -} - -// SetPassword sets the username for authentication. -func (this *ConnectMessage) SetPassword(v []byte) { - this.password = v - - if len(v) > 0 { - this.SetPasswordFlag(true) - } else { - this.SetPasswordFlag(false) - } - - this.dirty = true -} - -func (this *ConnectMessage) Len() int { - if !this.dirty { - return len(this.dbuf) - } - - ml := this.msglen() - - if err := this.SetRemainingLength(int32(ml)); err != nil { - return 0 - } - - return this.header.msglen() + ml -} - -// For the CONNECT message, the error returned could be a ConnackReturnCode, so -// be sure to check that. Otherwise it's a generic error. If a generic error is -// returned, this Message should be considered invalid. -// -// Caller should call ValidConnackError(err) to see if the returned error is -// a Connack error. If so, caller should send the Client back the corresponding -// CONNACK message. -func (this *ConnectMessage) Decode(src []byte) (int, error) { - total := 0 - - n, err := this.header.decode(src[total:]) - if err != nil { - return total + n, err - } - total += n - - if n, err = this.decodeMessage(src[total:]); err != nil { - return total + n, err - } - total += n - - this.dirty = false - - return total, nil -} - -func (this *ConnectMessage) Encode(dst []byte) (int, error) { - if !this.dirty { - if len(dst) < len(this.dbuf) { - return 0, fmt.Errorf("connect/Encode: Insufficient buffer size. Expecting %d, got %d.", len(this.dbuf), len(dst)) - } - - return copy(dst, this.dbuf), nil - } - - if this.Type() != CONNECT { - return 0, fmt.Errorf("connect/Encode: Invalid message type. Expecting %d, got %d", CONNECT, this.Type()) - } - - _, ok := SupportedVersions[this.version] - if !ok { - return 0, ErrInvalidProtocolVersion - } - - hl := this.header.msglen() - ml := this.msglen() - - if len(dst) < hl+ml { - return 0, fmt.Errorf("connect/Encode: Insufficient buffer size. Expecting %d, got %d.", hl+ml, len(dst)) - } - - if err := this.SetRemainingLength(int32(ml)); err != nil { - return 0, err - } - - total := 0 - - n, err := this.header.encode(dst[total:]) - total += n - if err != nil { - return total, err - } - - n, err = this.encodeMessage(dst[total:]) - total += n - if err != nil { - return total, err - } - - return total, nil -} - -func (this *ConnectMessage) encodeMessage(dst []byte) (int, error) { - total := 0 - - n, err := writeLPBytes(dst[total:], []byte(SupportedVersions[this.version])) - total += n - if err != nil { - return total, err - } - - dst[total] = this.version - total += 1 - - dst[total] = this.connectFlags - total += 1 - - binary.BigEndian.PutUint16(dst[total:], this.keepAlive) - total += 2 - - n, err = writeLPBytes(dst[total:], this.clientId) - total += n - if err != nil { - return total, err - } - - if this.WillFlag() { - n, err = writeLPBytes(dst[total:], this.willTopic) - total += n - if err != nil { - return total, err - } - - n, err = writeLPBytes(dst[total:], this.willMessage) - total += n - if err != nil { - return total, err - } - } - - // According to the 3.1 spec, it's possible that the usernameFlag is set, - // but the username string is missing. - if this.UsernameFlag() && len(this.username) > 0 { - n, err = writeLPBytes(dst[total:], this.username) - total += n - if err != nil { - return total, err - } - } - - // According to the 3.1 spec, it's possible that the passwordFlag is set, - // but the password string is missing. - if this.PasswordFlag() && len(this.password) > 0 { - n, err = writeLPBytes(dst[total:], this.password) - total += n - if err != nil { - return total, err - } - } - - return total, nil -} - -func (this *ConnectMessage) decodeMessage(src []byte) (int, error) { - var err error - n, total := 0, 0 - - this.protoName, n, err = readLPBytes(src[total:]) - total += n - if err != nil { - return total, err - } - - this.version = src[total] - total++ - - if verstr, ok := SupportedVersions[this.version]; !ok { - return total, ErrInvalidProtocolVersion - } else if verstr != string(this.protoName) { - return total, ErrInvalidProtocolVersion - } - - this.connectFlags = src[total] - total++ - - if this.connectFlags&0x1 != 0 { - return total, fmt.Errorf("connect/decodeMessage: Connect Flags reserved bit 0 is not 0") - } - - if this.WillQos() > QosExactlyOnce { - return total, fmt.Errorf("connect/decodeMessage: Invalid QoS level (%d) for %s message", this.WillQos(), this.Name()) - } - - if !this.WillFlag() && (this.WillRetain() || this.WillQos() != QosAtMostOnce) { - 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()) - } - - if this.UsernameFlag() && !this.PasswordFlag() { - return total, fmt.Errorf("connect/decodeMessage: Username flag is set but Password flag is not set") - } - - if len(src[total:]) < 2 { - return 0, fmt.Errorf("connect/decodeMessage: Insufficient buffer size. Expecting %d, got %d.", 2, len(src[total:])) - } - - this.keepAlive = binary.BigEndian.Uint16(src[total:]) - total += 2 - - this.clientId, n, err = readLPBytes(src[total:]) - total += n - if err != nil { - return total, err - } - - // If the Client supplies a zero-byte ClientId, the Client MUST also set CleanSession to 1 - if len(this.clientId) == 0 && !this.CleanSession() { - return total, ErrIdentifierRejected - } - - // The ClientId must contain only characters 0-9, a-z, and A-Z - // We also support ClientId longer than 23 encoded bytes - // We do not support ClientId outside of the above characters - if len(this.clientId) > 0 && !this.validClientId(this.clientId) { - return total, ErrIdentifierRejected - } - - if this.WillFlag() { - this.willTopic, n, err = readLPBytes(src[total:]) - total += n - if err != nil { - return total, err - } - - this.willMessage, n, err = readLPBytes(src[total:]) - total += n - if err != nil { - return total, err - } - } - - // According to the 3.1 spec, it's possible that the passwordFlag is set, - // but the password string is missing. - if this.UsernameFlag() && len(src[total:]) > 0 { - this.username, n, err = readLPBytes(src[total:]) - total += n - if err != nil { - return total, err - } - } - - // According to the 3.1 spec, it's possible that the passwordFlag is set, - // but the password string is missing. - if this.PasswordFlag() && len(src[total:]) > 0 { - this.password, n, err = readLPBytes(src[total:]) - total += n - if err != nil { - return total, err - } - } - - return total, nil -} - -func (this *ConnectMessage) msglen() int { - total := 0 - - verstr, ok := SupportedVersions[this.version] - if !ok { - return total - } - - // 2 bytes protocol name length - // n bytes protocol name - // 1 byte protocol version - // 1 byte connect flags - // 2 bytes keep alive timer - total += 2 + len(verstr) + 1 + 1 + 2 - - // Add the clientID length, 2 is the length prefix - total += 2 + len(this.clientId) - - // Add the will topic and will message length, and the length prefixes - if this.WillFlag() { - total += 2 + len(this.willTopic) + 2 + len(this.willMessage) - } - - // Add the username length - // According to the 3.1 spec, it's possible that the usernameFlag is set, - // but the user name string is missing. - if this.UsernameFlag() && len(this.username) > 0 { - total += 2 + len(this.username) - } - - // Add the password length - // According to the 3.1 spec, it's possible that the passwordFlag is set, - // but the password string is missing. - if this.PasswordFlag() && len(this.password) > 0 { - total += 2 + len(this.password) - } - - return total -} - -// validClientId checks the client ID, which is a slice of bytes, to see if it's valid. -// Client ID is valid if it meets the requirement from the MQTT spec: -// The Server MUST allow ClientIds which are between 1 and 23 UTF-8 encoded bytes in length, -// and that contain only the characters -// -// "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" -func (this *ConnectMessage) validClientId(cid []byte) bool { - // Fixed https://github.com/surgemq/surgemq/issues/4 - //if len(cid) > 23 { - // return false - //} - - if this.Version() == 0x3 { - return true - } - - return clientIdRegexp.Match(cid) -} diff --git a/lib/message/connect_test.go b/lib/message/connect_test.go deleted file mode 100755 index a893cbf..0000000 --- a/lib/message/connect_test.go +++ /dev/null @@ -1,373 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestConnectMessageFields(t *testing.T) { - msg := NewConnectMessage() - - err := msg.SetVersion(0x3) - require.NoError(t, err, "Error setting message version.") - - require.Equal(t, 0x3, int(msg.Version()), "Incorrect version number") - - err = msg.SetVersion(0x5) - require.Error(t, err) - - msg.SetCleanSession(true) - require.True(t, msg.CleanSession(), "Error setting clean session flag.") - - msg.SetCleanSession(false) - require.False(t, msg.CleanSession(), "Error setting clean session flag.") - - msg.SetWillFlag(true) - require.True(t, msg.WillFlag(), "Error setting will flag.") - - msg.SetWillFlag(false) - require.False(t, msg.WillFlag(), "Error setting will flag.") - - msg.SetWillRetain(true) - require.True(t, msg.WillRetain(), "Error setting will retain.") - - msg.SetWillRetain(false) - require.False(t, msg.WillRetain(), "Error setting will retain.") - - msg.SetPasswordFlag(true) - require.True(t, msg.PasswordFlag(), "Error setting password flag.") - - msg.SetPasswordFlag(false) - require.False(t, msg.PasswordFlag(), "Error setting password flag.") - - msg.SetUsernameFlag(true) - require.True(t, msg.UsernameFlag(), "Error setting username flag.") - - msg.SetUsernameFlag(false) - require.False(t, msg.UsernameFlag(), "Error setting username flag.") - - msg.SetWillQos(1) - require.Equal(t, 1, int(msg.WillQos()), "Error setting will QoS.") - - err = msg.SetWillQos(4) - require.Error(t, err) - - err = msg.SetClientId([]byte("j0j0jfajf02j0asdjf")) - require.NoError(t, err, "Error setting client ID") - - require.Equal(t, "j0j0jfajf02j0asdjf", string(msg.ClientId()), "Error setting client ID.") - - err = msg.SetClientId([]byte("this is good for v3")) - require.NoError(t, err) - - msg.SetVersion(0x4) - - err = msg.SetClientId([]byte("this is no good for v4!")) - require.Error(t, err) - - msg.SetVersion(0x3) - - msg.SetWillTopic([]byte("willtopic")) - require.Equal(t, "willtopic", string(msg.WillTopic()), "Error setting will topic.") - - require.True(t, msg.WillFlag(), "Error setting will flag.") - - msg.SetWillTopic([]byte("")) - require.Equal(t, "", string(msg.WillTopic()), "Error setting will topic.") - - require.False(t, msg.WillFlag(), "Error setting will flag.") - - msg.SetWillMessage([]byte("this is a will message")) - require.Equal(t, "this is a will message", string(msg.WillMessage()), "Error setting will message.") - - require.True(t, msg.WillFlag(), "Error setting will flag.") - - msg.SetWillMessage([]byte("")) - require.Equal(t, "", string(msg.WillMessage()), "Error setting will topic.") - - require.False(t, msg.WillFlag(), "Error setting will flag.") - - msg.SetWillTopic([]byte("willtopic")) - msg.SetWillMessage([]byte("this is a will message")) - msg.SetWillTopic([]byte("")) - require.True(t, msg.WillFlag(), "Error setting will topic.") - - msg.SetUsername([]byte("myname")) - require.Equal(t, "myname", string(msg.Username()), "Error setting will message.") - - require.True(t, msg.UsernameFlag(), "Error setting will flag.") - - msg.SetUsername([]byte("")) - require.Equal(t, "", string(msg.Username()), "Error setting will message.") - - require.False(t, msg.UsernameFlag(), "Error setting will flag.") - - msg.SetPassword([]byte("myname")) - require.Equal(t, "myname", string(msg.Password()), "Error setting will message.") - - require.True(t, msg.PasswordFlag(), "Error setting will flag.") - - msg.SetPassword([]byte("")) - require.Equal(t, "", string(msg.Password()), "Error setting will message.") - - require.False(t, msg.PasswordFlag(), "Error setting will flag.") -} - -func TestConnectMessageDecode(t *testing.T) { - msgBytes := []byte{ - byte(CONNECT << 4), - 60, - 0, // Length MSB (0) - 4, // Length LSB (4) - 'M', 'Q', 'T', 'T', - 4, // Protocol level 4 - 206, // connect flags 11001110, will QoS = 01 - 0, // Keep Alive MSB (0) - 10, // Keep Alive LSB (10) - 0, // Client ID MSB (0) - 7, // Client ID LSB (7) - 's', 'u', 'r', 'g', 'e', 'm', 'q', - 0, // Will Topic MSB (0) - 4, // Will Topic LSB (4) - 'w', 'i', 'l', 'l', - 0, // Will Message MSB (0) - 12, // Will Message LSB (12) - 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', - 0, // Username ID MSB (0) - 7, // Username ID LSB (7) - 's', 'u', 'r', 'g', 'e', 'm', 'q', - 0, // Password ID MSB (0) - 10, // Password ID LSB (10) - 'v', 'e', 'r', 'y', 's', 'e', 'c', 'r', 'e', 't', - } - - msg := NewConnectMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, 206, int(msg.connectFlags), "Incorrect flag value.") - require.Equal(t, 10, int(msg.KeepAlive()), "Incorrect KeepAlive value.") - require.Equal(t, "surgemq", string(msg.ClientId()), "Incorrect client ID value.") - require.Equal(t, "will", string(msg.WillTopic()), "Incorrect will topic value.") - require.Equal(t, "send me home", string(msg.WillMessage()), "Incorrect will message value.") - require.Equal(t, "surgemq", string(msg.Username()), "Incorrect username value.") - require.Equal(t, "verysecret", string(msg.Password()), "Incorrect password value.") -} - -func TestConnectMessageDecode2(t *testing.T) { - // missing last byte 't' - msgBytes := []byte{ - byte(CONNECT << 4), - 60, - 0, // Length MSB (0) - 4, // Length LSB (4) - 'M', 'Q', 'T', 'T', - 4, // Protocol level 4 - 206, // connect flags 11001110, will QoS = 01 - 0, // Keep Alive MSB (0) - 10, // Keep Alive LSB (10) - 0, // Client ID MSB (0) - 7, // Client ID LSB (7) - 's', 'u', 'r', 'g', 'e', 'm', 'q', - 0, // Will Topic MSB (0) - 4, // Will Topic LSB (4) - 'w', 'i', 'l', 'l', - 0, // Will Message MSB (0) - 12, // Will Message LSB (12) - 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', - 0, // Username ID MSB (0) - 7, // Username ID LSB (7) - 's', 'u', 'r', 'g', 'e', 'm', 'q', - 0, // Password ID MSB (0) - 10, // Password ID LSB (10) - 'v', 'e', 'r', 'y', 's', 'e', 'c', 'r', 'e', - } - - msg := NewConnectMessage() - _, err := msg.Decode(msgBytes) - - require.Error(t, err) -} - -func TestConnectMessageDecode3(t *testing.T) { - // extra bytes - msgBytes := []byte{ - byte(CONNECT << 4), - 60, - 0, // Length MSB (0) - 4, // Length LSB (4) - 'M', 'Q', 'T', 'T', - 4, // Protocol level 4 - 206, // connect flags 11001110, will QoS = 01 - 0, // Keep Alive MSB (0) - 10, // Keep Alive LSB (10) - 0, // Client ID MSB (0) - 7, // Client ID LSB (7) - 's', 'u', 'r', 'g', 'e', 'm', 'q', - 0, // Will Topic MSB (0) - 4, // Will Topic LSB (4) - 'w', 'i', 'l', 'l', - 0, // Will Message MSB (0) - 12, // Will Message LSB (12) - 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', - 0, // Username ID MSB (0) - 7, // Username ID LSB (7) - 's', 'u', 'r', 'g', 'e', 'm', 'q', - 0, // Password ID MSB (0) - 10, // Password ID LSB (10) - 'v', 'e', 'r', 'y', 's', 'e', 'c', 'r', 'e', 't', - 'e', 'x', 't', 'r', 'a', - } - - msg := NewConnectMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err) - require.Equal(t, 62, n) -} - -func TestConnectMessageDecode4(t *testing.T) { - // missing client Id, clean session == 0 - msgBytes := []byte{ - byte(CONNECT << 4), - 53, - 0, // Length MSB (0) - 4, // Length LSB (4) - 'M', 'Q', 'T', 'T', - 4, // Protocol level 4 - 204, // connect flags 11001110, will QoS = 01 - 0, // Keep Alive MSB (0) - 10, // Keep Alive LSB (10) - 0, // Client ID MSB (0) - 0, // Client ID LSB (0) - 0, // Will Topic MSB (0) - 4, // Will Topic LSB (4) - 'w', 'i', 'l', 'l', - 0, // Will Message MSB (0) - 12, // Will Message LSB (12) - 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', - 0, // Username ID MSB (0) - 7, // Username ID LSB (7) - 's', 'u', 'r', 'g', 'e', 'm', 'q', - 0, // Password ID MSB (0) - 10, // Password ID LSB (10) - 'v', 'e', 'r', 'y', 's', 'e', 'c', 'r', 'e', 't', - } - - msg := NewConnectMessage() - _, err := msg.Decode(msgBytes) - - require.Error(t, err) -} - -func TestConnectMessageEncode(t *testing.T) { - msgBytes := []byte{ - byte(CONNECT << 4), - 60, - 0, // Length MSB (0) - 4, // Length LSB (4) - 'M', 'Q', 'T', 'T', - 4, // Protocol level 4 - 206, // connect flags 11001110, will QoS = 01 - 0, // Keep Alive MSB (0) - 10, // Keep Alive LSB (10) - 0, // Client ID MSB (0) - 7, // Client ID LSB (7) - 's', 'u', 'r', 'g', 'e', 'm', 'q', - 0, // Will Topic MSB (0) - 4, // Will Topic LSB (4) - 'w', 'i', 'l', 'l', - 0, // Will Message MSB (0) - 12, // Will Message LSB (12) - 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', - 0, // Username ID MSB (0) - 7, // Username ID LSB (7) - 's', 'u', 'r', 'g', 'e', 'm', 'q', - 0, // Password ID MSB (0) - 10, // Password ID LSB (10) - 'v', 'e', 'r', 'y', 's', 'e', 'c', 'r', 'e', 't', - } - - msg := NewConnectMessage() - msg.SetWillQos(1) - msg.SetVersion(4) - msg.SetCleanSession(true) - msg.SetClientId([]byte("surgemq")) - msg.SetKeepAlive(10) - msg.SetWillTopic([]byte("will")) - msg.SetWillMessage([]byte("send me home")) - msg.SetUsername([]byte("surgemq")) - msg.SetPassword([]byte("verysecret")) - - dst := make([]byte, 100) - n, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n], "Error decoding message.") -} - -// test to ensure encoding and decoding are the same -// decode, encode, and decode again -func TestConnectDecodeEncodeEquiv(t *testing.T) { - msgBytes := []byte{ - byte(CONNECT << 4), - 60, - 0, // Length MSB (0) - 4, // Length LSB (4) - 'M', 'Q', 'T', 'T', - 4, // Protocol level 4 - 206, // connect flags 11001110, will QoS = 01 - 0, // Keep Alive MSB (0) - 10, // Keep Alive LSB (10) - 0, // Client ID MSB (0) - 7, // Client ID LSB (7) - 's', 'u', 'r', 'g', 'e', 'm', 'q', - 0, // Will Topic MSB (0) - 4, // Will Topic LSB (4) - 'w', 'i', 'l', 'l', - 0, // Will Message MSB (0) - 12, // Will Message LSB (12) - 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', - 0, // Username ID MSB (0) - 7, // Username ID LSB (7) - 's', 'u', 'r', 'g', 'e', 'm', 'q', - 0, // Password ID MSB (0) - 10, // Password ID LSB (10) - 'v', 'e', 'r', 'y', 's', 'e', 'c', 'r', 'e', 't', - } - - msg := NewConnectMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - - dst := make([]byte, 100) - n2, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n2, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") - - n3, err := msg.Decode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n3, "Error decoding message.") -} diff --git a/lib/message/disconnect.go b/lib/message/disconnect.go deleted file mode 100755 index 0e34589..0000000 --- a/lib/message/disconnect.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -import "fmt" - -// The DISCONNECT Packet is the final Control Packet sent from the Client to the Server. -// It indicates that the Client is disconnecting cleanly. -type DisconnectMessage struct { - header -} - -var _ Message = (*DisconnectMessage)(nil) - -// NewDisconnectMessage creates a new DISCONNECT message. -func NewDisconnectMessage() *DisconnectMessage { - msg := &DisconnectMessage{} - msg.SetType(DISCONNECT) - - return msg -} - -func (this *DisconnectMessage) Decode(src []byte) (int, error) { - return this.header.decode(src) -} - -func (this *DisconnectMessage) Encode(dst []byte) (int, error) { - if !this.dirty { - if len(dst) < len(this.dbuf) { - return 0, fmt.Errorf("disconnect/Encode: Insufficient buffer size. Expecting %d, got %d.", len(this.dbuf), len(dst)) - } - - return copy(dst, this.dbuf), nil - } - - return this.header.encode(dst) -} diff --git a/lib/message/disconnect_test.go b/lib/message/disconnect_test.go deleted file mode 100755 index 6907cc7..0000000 --- a/lib/message/disconnect_test.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestDisconnectMessageDecode(t *testing.T) { - msgBytes := []byte{ - byte(DISCONNECT << 4), - 0, - } - - msg := NewDisconnectMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, DISCONNECT, msg.Type(), "Error decoding message.") -} - -func TestDisconnectMessageEncode(t *testing.T) { - msgBytes := []byte{ - byte(DISCONNECT << 4), - 0, - } - - msg := NewDisconnectMessage() - - dst := make([]byte, 10) - n, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n], "Error decoding message.") -} - -// test to ensure encoding and decoding are the same -// decode, encode, and decode again -func TestDisconnectDecodeEncodeEquiv(t *testing.T) { - msgBytes := []byte{ - byte(DISCONNECT << 4), - 0, - } - - msg := NewDisconnectMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - - dst := make([]byte, 100) - n2, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n2, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") - - n3, err := msg.Decode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n3, "Error decoding message.") -} diff --git a/lib/message/doc.go b/lib/message/doc.go deleted file mode 100755 index 1d4176b..0000000 --- a/lib/message/doc.go +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/* -Package message is an encoder/decoder library for MQTT 3.1 and 3.1.1 messages. You can -find the MQTT specs at the following locations: - - 3.1.1 - http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/ - 3.1 - http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html - -From the spec: - - MQTT is a Client Server publish/subscribe messaging transport protocol. It is - light weight, open, simple, and designed so as to be easy to implement. These - characteristics make it ideal for use in many situations, including constrained - environments such as for communication in Machine to Machine (M2M) and Internet - of Things (IoT) contexts where a small code footprint is required and/or network - bandwidth is at a premium. - - The MQTT protocol works by exchanging a series of MQTT messages in a defined way. - The protocol runs over TCP/IP, or over other network protocols that provide - ordered, lossless, bi-directional connections. - - -There are two main items to take note in this package. The first is - - type MessageType byte - -MessageType is the type representing the MQTT packet types. In the MQTT spec, MQTT -control packet type is represented as a 4-bit unsigned value. MessageType receives -several methods that returns string representations of the names and descriptions. - -Also, one of the methods is New(). It returns a new Message object based on the mtype -parameter. For example: - - m, err := CONNECT.New() - msg := m.(*ConnectMessage) - -This would return a PublishMessage struct, but mapped to the Message interface. You can -then type assert it back to a *PublishMessage. Another way to create a new -PublishMessage is to call - - msg := NewConnectMessage() - -Every message type has a New function that returns a new message. The list of available -message types are defined as constants below. - -As you may have noticed, the second important item is the Message interface. It defines -several methods that are common to all messages, including Name(), Desc(), and Type(). -Most importantly, it also defines the Encode() and Decode() methods. - - Encode() (io.Reader, int, error) - Decode(io.Reader) (int, error) - -Encode returns an io.Reader in which the encoded bytes can be read. The second return -value is the number of bytes encoded, so the caller knows how many bytes there will be. -If Encode returns an error, then the first two return values should be considered invalid. -Any changes to the message after Encode() is called will invalidate the io.Reader. - -Decode reads from the io.Reader parameter until a full message is decoded, or when io.Reader -returns EOF or error. The first return value is the number of bytes read from io.Reader. -The second is error if Decode encounters any problems. - -With these in mind, we can now do: - - // Create a new CONNECT message - msg := NewConnectMessage() - - // Set the appropriate parameters - msg.SetWillQos(1) - msg.SetVersion(4) - msg.SetCleanSession(true) - msg.SetClientId([]byte("surgemq")) - msg.SetKeepAlive(10) - msg.SetWillTopic([]byte("will")) - msg.SetWillMessage([]byte("send me home")) - msg.SetUsername([]byte("surgemq")) - msg.SetPassword([]byte("verysecret")) - - // Encode the message and get the io.Reader - r, n, err := msg.Encode() - if err == nil { - return err - } - - // Write n bytes into the connection - m, err := io.CopyN(conn, r, int64(n)) - if err != nil { - return err - } - - fmt.Printf("Sent %d bytes of %s message", m, msg.Name()) - -To receive a CONNECT message from a connection, we can do: - - // Create a new CONNECT message - msg := NewConnectMessage() - - // Decode the message by reading from conn - n, err := msg.Decode(conn) - -If you don't know what type of message is coming down the pipe, you can do something like this: - - // Create a buffered IO reader for the connection - br := bufio.NewReader(conn) - - // Peek at the first byte, which contains the message type - b, err := br.Peek(1) - if err != nil { - return err - } - - // Extract the type from the first byte - t := MessageType(b[0] >> 4) - - // Create a new message - msg, err := t.New() - if err != nil { - return err - } - - // Decode it from the bufio.Reader - n, err := msg.Decode(br) - if err != nil { - return err - } - - -*/ -package message diff --git a/lib/message/header.go b/lib/message/header.go deleted file mode 100755 index c45821f..0000000 --- a/lib/message/header.go +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -import ( - "encoding/binary" - "fmt" -) - -var ( - gPacketId uint64 = 0 -) - -// Fixed header -// - 1 byte for control packet type (bits 7-4) and flags (bits 3-0) -// - up to 4 byte for remaining length -type header struct { - // Header fields - //mtype MessageType - //flags byte - remlen int32 - - // mtypeflags is the first byte of the buffer, 4 bits for mtype, 4 bits for flags - mtypeflags []byte - - // Some messages need packet ID, 2 byte uint16 - packetId []byte - - // Points to the decoding buffer - dbuf []byte - - // Whether the message has changed since last decode - dirty bool -} - -// String returns a string representation of the message. -func (this header) String() string { - return fmt.Sprintf("Type=%q, Flags=%08b, Remaining Length=%d", this.Type().Name(), this.Flags(), this.remlen) -} - -// Name returns a string representation of the message type. Examples include -// "PUBLISH", "SUBSCRIBE", and others. This is statically defined for each of -// the message types and cannot be changed. -func (this *header) Name() string { - return this.Type().Name() -} - -// Desc returns a string description of the message type. For example, a -// CONNECT message would return "Client request to connect to Server." These -// descriptions are statically defined (copied from the MQTT spec) and cannot -// be changed. -func (this *header) Desc() string { - return this.Type().Desc() -} - -// Type returns the MessageType of the Message. The retured value should be one -// of the constants defined for MessageType. -func (this *header) Type() MessageType { - //return this.mtype - if len(this.mtypeflags) != 1 { - this.mtypeflags = make([]byte, 1) - this.dirty = true - } - - return MessageType(this.mtypeflags[0] >> 4) -} - -// SetType sets the message type of this message. It also correctly sets the -// default flags for the message type. It returns an error if the type is invalid. -func (this *header) SetType(mtype MessageType) error { - if !mtype.Valid() { - return fmt.Errorf("header/SetType: Invalid control packet type %d", mtype) - } - - // Notice we don't set the message to be dirty when we are not allocating a new - // buffer. In this case, it means the buffer is probably a sub-slice of another - // slice. If that's the case, then during encoding we would have copied the whole - // backing buffer anyway. - if len(this.mtypeflags) != 1 { - this.mtypeflags = make([]byte, 1) - this.dirty = true - } - - this.mtypeflags[0] = byte(mtype)<<4 | (mtype.DefaultFlags() & 0xf) - - return nil -} - -// Flags returns the fixed header flags for this message. -func (this *header) Flags() byte { - //return this.flags - return this.mtypeflags[0] & 0x0f -} - -// RemainingLength returns the length of the non-fixed-header part of the message. -func (this *header) RemainingLength() int32 { - return this.remlen -} - -// SetRemainingLength sets the length of the non-fixed-header part of the message. -// It returns error if the length is greater than 268435455, which is the max -// message length as defined by the MQTT spec. -func (this *header) SetRemainingLength(remlen int32) error { - if remlen > maxRemainingLength || remlen < 0 { - return fmt.Errorf("header/SetLength: Remaining length (%d) out of bound (max %d, min 0)", remlen, maxRemainingLength) - } - - this.remlen = remlen - this.dirty = true - - return nil -} - -func (this *header) Len() int { - return this.msglen() -} - -// PacketId returns the ID of the packet. -func (this *header) PacketId() uint16 { - if len(this.packetId) == 2 { - return binary.BigEndian.Uint16(this.packetId) - } - - return 0 -} - -// SetPacketId sets the ID of the packet. -func (this *header) SetPacketId(v uint16) { - // If setting to 0, nothing to do, move on - if v == 0 { - return - } - - // If packetId buffer is not 2 bytes (uint16), then we allocate a new one and - // make dirty. Then we encode the packet ID into the buffer. - if len(this.packetId) != 2 { - this.packetId = make([]byte, 2) - this.dirty = true - } - - // Notice we don't set the message to be dirty when we are not allocating a new - // buffer. In this case, it means the buffer is probably a sub-slice of another - // slice. If that's the case, then during encoding we would have copied the whole - // backing buffer anyway. - binary.BigEndian.PutUint16(this.packetId, v) -} - -func (this *header) encode(dst []byte) (int, error) { - ml := this.msglen() - - if len(dst) < ml { - return 0, fmt.Errorf("header/Encode: Insufficient buffer size. Expecting %d, got %d.", ml, len(dst)) - } - - total := 0 - - if this.remlen > maxRemainingLength || this.remlen < 0 { - return total, fmt.Errorf("header/Encode: Remaining length (%d) out of bound (max %d, min 0)", this.remlen, maxRemainingLength) - } - - if !this.Type().Valid() { - return total, fmt.Errorf("header/Encode: Invalid message type %d", this.Type()) - } - - dst[total] = this.mtypeflags[0] - total += 1 - - n := binary.PutUvarint(dst[total:], uint64(this.remlen)) - total += n - - return total, nil -} - -// Decode reads from the io.Reader parameter until a full message is decoded, or -// when io.Reader returns EOF or error. The first return value is the number of -// bytes read from io.Reader. The second is error if Decode encounters any problems. -func (this *header) decode(src []byte) (int, error) { - total := 0 - - this.dbuf = src - - mtype := this.Type() - //mtype := MessageType(0) - - this.mtypeflags = src[total : total+1] - //mtype := MessageType(src[total] >> 4) - if !this.Type().Valid() { - return total, fmt.Errorf("header/Decode: Invalid message type %d.", mtype) - } - - if mtype != this.Type() { - return total, fmt.Errorf("header/Decode: Invalid message type %d. Expecting %d.", this.Type(), mtype) - } - - //this.flags = src[total] & 0x0f - if this.Type() != PUBLISH && this.Flags() != this.Type().DefaultFlags() { - return total, fmt.Errorf("header/Decode: Invalid message (%d) flags. Expecting %d, got %d", this.Type(), this.Type().DefaultFlags(), this.Flags()) - } - - if this.Type() == PUBLISH && !ValidQos((this.Flags()>>1)&0x3) { - return total, fmt.Errorf("header/Decode: Invalid QoS (%d) for PUBLISH message.", (this.Flags()>>1)&0x3) - } - - total++ - - remlen, m := binary.Uvarint(src[total:]) - total += m - this.remlen = int32(remlen) - - if this.remlen > maxRemainingLength || remlen < 0 { - return total, fmt.Errorf("header/Decode: Remaining length (%d) out of bound (max %d, min 0)", this.remlen, maxRemainingLength) - } - - if int(this.remlen) > len(src[total:]) { - return total, fmt.Errorf("header/Decode: Remaining length (%d) is greater than remaining buffer (%d)", this.remlen, len(src[total:])) - } - - return total, nil -} - -func (this *header) msglen() int { - // message type and flag byte - total := 1 - - if this.remlen <= 127 { - total += 1 - } else if this.remlen <= 16383 { - total += 2 - } else if this.remlen <= 2097151 { - total += 3 - } else { - total += 4 - } - - return total -} diff --git a/lib/message/header_test.go b/lib/message/header_test.go deleted file mode 100755 index 3025dc5..0000000 --- a/lib/message/header_test.go +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestMessageHeaderFields(t *testing.T) { - header := &header{} - - header.SetRemainingLength(33) - - require.Equal(t, int32(33), header.RemainingLength()) - - err := header.SetRemainingLength(268435456) - - require.Error(t, err) - - err = header.SetRemainingLength(-1) - - require.Error(t, err) - - err = header.SetType(RESERVED) - - require.Error(t, err) - - err = header.SetType(PUBREL) - - require.NoError(t, err) - require.Equal(t, PUBREL, header.Type()) - require.Equal(t, "PUBREL", header.Name()) - require.Equal(t, 2, int(header.Flags())) -} - -// Not enough bytes -func TestMessageHeaderDecode(t *testing.T) { - buf := []byte{0x6f, 193, 2} - header := &header{} - - _, err := header.decode(buf) - require.Error(t, err) -} - -// Remaining length too big -func TestMessageHeaderDecode2(t *testing.T) { - buf := []byte{0x62, 0xff, 0xff, 0xff, 0xff} - header := &header{} - - _, err := header.decode(buf) - require.Error(t, err) -} - -func TestMessageHeaderDecode3(t *testing.T) { - buf := []byte{0x62, 0xff} - header := &header{} - - _, err := header.decode(buf) - require.Error(t, err) -} - -func TestMessageHeaderDecode4(t *testing.T) { - buf := []byte{0x62, 0xff, 0xff, 0xff, 0x7f} - header := &header{ - mtypeflags: []byte{6<<4 | 2}, - //mtype: 6, - //flags: 2, - } - - n, err := header.decode(buf) - - require.Error(t, err) - require.Equal(t, 5, n) - require.Equal(t, maxRemainingLength, header.RemainingLength()) -} - -func TestMessageHeaderDecode5(t *testing.T) { - buf := []byte{0x62, 0xff, 0x7f} - header := &header{ - mtypeflags: []byte{6<<4 | 2}, - //mtype: 6, - //flags: 2, - } - - n, err := header.decode(buf) - require.Error(t, err) - require.Equal(t, 3, n) -} - -func TestMessageHeaderEncode1(t *testing.T) { - header := &header{} - headerBytes := []byte{0x62, 193, 2} - - err := header.SetType(PUBREL) - - require.NoError(t, err) - - err = header.SetRemainingLength(321) - - require.NoError(t, err) - - buf := make([]byte, 3) - n, err := header.encode(buf) - - require.NoError(t, err) - require.Equal(t, 3, n) - require.Equal(t, headerBytes, buf) -} - -func TestMessageHeaderEncode2(t *testing.T) { - header := &header{} - - err := header.SetType(PUBREL) - require.NoError(t, err) - - header.remlen = 268435456 - - buf := make([]byte, 5) - _, err = header.encode(buf) - - require.Error(t, err) -} - -func TestMessageHeaderEncode3(t *testing.T) { - header := &header{} - headerBytes := []byte{0x62, 0xff, 0xff, 0xff, 0x7f} - - err := header.SetType(PUBREL) - - require.NoError(t, err) - - err = header.SetRemainingLength(maxRemainingLength) - - require.NoError(t, err) - - buf := make([]byte, 5) - n, err := header.encode(buf) - - require.NoError(t, err) - require.Equal(t, 5, n) - require.Equal(t, headerBytes, buf) -} - -func TestMessageHeaderEncode4(t *testing.T) { - header := &header{ - mtypeflags: []byte{byte(RESERVED2) << 4}, - //mtype: 6, - //flags: 2, - } - - buf := make([]byte, 5) - _, err := header.encode(buf) - require.Error(t, err) -} - -/* -// This test is to ensure that an empty message is at least 2 bytes long -func TestMessageHeaderEncode5(t *testing.T) { - msg := NewPingreqMessage() - - dst, n, err := msg.encode() - if err != nil { - t.Errorf("Error encoding PINGREQ message: %v", err) - } else if n != 2 { - t.Errorf("Incorrect result. Expecting length of 2 bytes, got %d.", dst.(*bytes.Buffer).Len()) - } -} -*/ diff --git a/lib/message/message.go b/lib/message/message.go deleted file mode 100755 index a8e3bb8..0000000 --- a/lib/message/message.go +++ /dev/null @@ -1,410 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -import ( - "bytes" - "encoding/binary" - "fmt" -) - -const ( - maxLPString uint16 = 65535 - maxFixedHeaderLength int = 5 - maxRemainingLength int32 = 268435455 // bytes, or 256 MB -) - -const ( - // QoS 0: At most once delivery - // The message is delivered according to the capabilities of the underlying network. - // No response is sent by the receiver and no retry is performed by the sender. The - // message arrives at the receiver either once or not at all. - QosAtMostOnce byte = iota - - // QoS 1: At least once delivery - // This quality of service ensures that the message arrives at the receiver at least once. - // A QoS 1 PUBLISH Packet has a Packet Identifier in its variable header and is acknowledged - // by a PUBACK Packet. Section 2.3.1 provides more information about Packet Identifiers. - QosAtLeastOnce - - // QoS 2: Exactly once delivery - // This is the highest quality of service, for use when neither loss nor duplication of - // messages are acceptable. There is an increased overhead associated with this quality of - // service. - QosExactlyOnce - - // QosFailure is a return value for a subscription if there's a problem while subscribing - // to a specific topic. - QosFailure = 0x80 -) - -// SupportedVersions is a map of the version number (0x3 or 0x4) to the version string, -// "MQIsdp" for 0x3, and "MQTT" for 0x4. -var SupportedVersions map[byte]string = map[byte]string{ - 0x3: "MQIsdp", - 0x4: "MQTT", -} - -// MessageType is the type representing the MQTT packet types. In the MQTT spec, -// MQTT control packet type is represented as a 4-bit unsigned value. -type MessageType byte - -// Message is an interface defined for all MQTT message types. -type Message interface { - // Name returns a string representation of the message type. Examples include - // "PUBLISH", "SUBSCRIBE", and others. This is statically defined for each of - // the message types and cannot be changed. - Name() string - - // Desc returns a string description of the message type. For example, a - // CONNECT message would return "Client request to connect to Server." These - // descriptions are statically defined (copied from the MQTT spec) and cannot - // be changed. - Desc() string - - // Type returns the MessageType of the Message. The retured value should be one - // of the constants defined for MessageType. - Type() MessageType - - // PacketId returns the packet ID of the Message. The retured value is 0 if - // there's no packet ID for this message type. Otherwise non-0. - PacketId() uint16 - - // Encode writes the message bytes into the byte array from the argument. It - // returns the number of bytes encoded and whether there's any errors along - // the way. If there's any errors, then the byte slice and count should be - // considered invalid. - Encode([]byte) (int, error) - - // Decode reads the bytes in the byte slice from the argument. It returns the - // total number of bytes decoded, and whether there's any errors during the - // process. The byte slice MUST NOT be modified during the duration of this - // message being available since the byte slice is internally stored for - // references. - Decode([]byte) (int, error) - - Len() int -} - -const ( - // RESERVED is a reserved value and should be considered an invalid message type - RESERVED MessageType = iota - - // CONNECT: Client to Server. Client request to connect to Server. - CONNECT - - // CONNACK: Server to Client. Connect acknowledgement. - CONNACK - - // PUBLISH: Client to Server, or Server to Client. Publish message. - PUBLISH - - // PUBACK: Client to Server, or Server to Client. Publish acknowledgment for - // QoS 1 messages. - PUBACK - - // PUBACK: Client to Server, or Server to Client. Publish received for QoS 2 messages. - // Assured delivery part 1. - PUBREC - - // PUBREL: Client to Server, or Server to Client. Publish release for QoS 2 messages. - // Assured delivery part 1. - PUBREL - - // PUBCOMP: Client to Server, or Server to Client. Publish complete for QoS 2 messages. - // Assured delivery part 3. - PUBCOMP - - // SUBSCRIBE: Client to Server. Client subscribe request. - SUBSCRIBE - - // SUBACK: Server to Client. Subscribe acknowledgement. - SUBACK - - // UNSUBSCRIBE: Client to Server. Unsubscribe request. - UNSUBSCRIBE - - // UNSUBACK: Server to Client. Unsubscribe acknowlegment. - UNSUBACK - - // PINGREQ: Client to Server. PING request. - PINGREQ - - // PINGRESP: Server to Client. PING response. - PINGRESP - - // DISCONNECT: Client to Server. Client is disconnecting. - DISCONNECT - - // RESERVED2 is a reserved value and should be considered an invalid message type. - RESERVED2 -) - -func (this MessageType) String() string { - return this.Name() -} - -// Name returns the name of the message type. It should correspond to one of the -// constant values defined for MessageType. It is statically defined and cannot -// be changed. -func (this MessageType) Name() string { - switch this { - case RESERVED: - return "RESERVED" - case CONNECT: - return "CONNECT" - case CONNACK: - return "CONNACK" - case PUBLISH: - return "PUBLISH" - case PUBACK: - return "PUBACK" - case PUBREC: - return "PUBREC" - case PUBREL: - return "PUBREL" - case PUBCOMP: - return "PUBCOMP" - case SUBSCRIBE: - return "SUBSCRIBE" - case SUBACK: - return "SUBACK" - case UNSUBSCRIBE: - return "UNSUBSCRIBE" - case UNSUBACK: - return "UNSUBACK" - case PINGREQ: - return "PINGREQ" - case PINGRESP: - return "PINGRESP" - case DISCONNECT: - return "DISCONNECT" - case RESERVED2: - return "RESERVED2" - } - - return "UNKNOWN" -} - -// Desc returns the description of the message type. It is statically defined (copied -// from MQTT spec) and cannot be changed. -func (this MessageType) Desc() string { - switch this { - case RESERVED: - return "Reserved" - case CONNECT: - return "Client request to connect to Server" - case CONNACK: - return "Connect acknowledgement" - case PUBLISH: - return "Publish message" - case PUBACK: - return "Publish acknowledgement" - case PUBREC: - return "Publish received (assured delivery part 1)" - case PUBREL: - return "Publish release (assured delivery part 2)" - case PUBCOMP: - return "Publish complete (assured delivery part 3)" - case SUBSCRIBE: - return "Client subscribe request" - case SUBACK: - return "Subscribe acknowledgement" - case UNSUBSCRIBE: - return "Unsubscribe request" - case UNSUBACK: - return "Unsubscribe acknowledgement" - case PINGREQ: - return "PING request" - case PINGRESP: - return "PING response" - case DISCONNECT: - return "Client is disconnecting" - case RESERVED2: - return "Reserved" - } - - return "UNKNOWN" -} - -// DefaultFlags returns the default flag values for the message type, as defined by -// the MQTT spec. -func (this MessageType) DefaultFlags() byte { - switch this { - case RESERVED: - return 0 - case CONNECT: - return 0 - case CONNACK: - return 0 - case PUBLISH: - return 0 - case PUBACK: - return 0 - case PUBREC: - return 0 - case PUBREL: - return 2 - case PUBCOMP: - return 0 - case SUBSCRIBE: - return 2 - case SUBACK: - return 0 - case UNSUBSCRIBE: - return 2 - case UNSUBACK: - return 0 - case PINGREQ: - return 0 - case PINGRESP: - return 0 - case DISCONNECT: - return 0 - case RESERVED2: - return 0 - } - - return 0 -} - -// New creates a new message based on the message type. It is a shortcut to call -// one of the New*Message functions. If an error is returned then the message type -// is invalid. -func (this MessageType) New() (Message, error) { - switch this { - case CONNECT: - return NewConnectMessage(), nil - case CONNACK: - return NewConnackMessage(), nil - case PUBLISH: - return NewPublishMessage(), nil - case PUBACK: - return NewPubackMessage(), nil - case PUBREC: - return NewPubrecMessage(), nil - case PUBREL: - return NewPubrelMessage(), nil - case PUBCOMP: - return NewPubcompMessage(), nil - case SUBSCRIBE: - return NewSubscribeMessage(), nil - case SUBACK: - return NewSubackMessage(), nil - case UNSUBSCRIBE: - return NewUnsubscribeMessage(), nil - case UNSUBACK: - return NewUnsubackMessage(), nil - case PINGREQ: - return NewPingreqMessage(), nil - case PINGRESP: - return NewPingrespMessage(), nil - case DISCONNECT: - return NewDisconnectMessage(), nil - } - - return nil, fmt.Errorf("msgtype/NewMessage: Invalid message type %d", this) -} - -// Valid returns a boolean indicating whether the message type is valid or not. -func (this MessageType) Valid() bool { - return this > RESERVED && this < RESERVED2 -} - -// ValidTopic checks the topic, which is a slice of bytes, to see if it's valid. Topic is -// considered valid if it's longer than 0 bytes, and doesn't contain any wildcard characters -// such as + and #. -func ValidTopic(topic []byte) bool { - return len(topic) > 0 && bytes.IndexByte(topic, '#') == -1 && bytes.IndexByte(topic, '+') == -1 -} - -// ValidQos checks the QoS value to see if it's valid. Valid QoS are QosAtMostOnce, -// QosAtLeastonce, and QosExactlyOnce. -func ValidQos(qos byte) bool { - return qos == QosAtMostOnce || qos == QosAtLeastOnce || qos == QosExactlyOnce -} - -// ValidVersion checks to see if the version is valid. Current supported versions include 0x3 and 0x4. -func ValidVersion(v byte) bool { - _, ok := SupportedVersions[v] - return ok -} - -// ValidConnackError checks to see if the error is a Connack Error or not -func ValidConnackError(err error) bool { - return err == ErrInvalidProtocolVersion || err == ErrIdentifierRejected || - err == ErrServerUnavailable || err == ErrBadUsernameOrPassword || err == ErrNotAuthorized -} - -func ValidConnackErrorEx(err error) (bool, ConnackCode) { - if err == ErrInvalidProtocolVersion { - return true, ErrInvalidProtocolVersion - } - if err == ErrIdentifierRejected { - return true, ErrIdentifierRejected - } - if err == ErrServerUnavailable { - return true, ErrServerUnavailable - } - if err == ErrBadUsernameOrPassword { - return true, ErrBadUsernameOrPassword - } - if err == ErrNotAuthorized { - return true, ErrNotAuthorized - } - return false, ConnectionAccepted - -} - -// Read length prefixed bytes -func readLPBytes(buf []byte) ([]byte, int, error) { - if len(buf) < 2 { - return nil, 0, fmt.Errorf("utils/readLPBytes: Insufficient buffer size. Expecting %d, got %d.", 2, len(buf)) - } - - n, total := 0, 0 - - n = int(binary.BigEndian.Uint16(buf)) - total += 2 - - if len(buf) < n { - return nil, total, fmt.Errorf("utils/readLPBytes: Insufficient buffer size. Expecting %d, got %d.", n, len(buf)) - } - - total += n - - return buf[2:total], total, nil -} - -// Write length prefixed bytes -func writeLPBytes(buf []byte, b []byte) (int, error) { - total, n := 0, len(b) - - if n > int(maxLPString) { - return 0, fmt.Errorf("utils/writeLPBytes: Length (%d) greater than %d bytes.", n, maxLPString) - } - - if len(buf) < 2+n { - return 0, fmt.Errorf("utils/writeLPBytes: Insufficient buffer size. Expecting %d, got %d.", 2+n, len(buf)) - } - - binary.BigEndian.PutUint16(buf, uint16(n)) - total += 2 - - copy(buf[total:], b) - total += n - - return total, nil -} diff --git a/lib/message/message_test.go b/lib/message/message_test.go deleted file mode 100755 index 92dcc97..0000000 --- a/lib/message/message_test.go +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -var ( - lpstrings []string = []string{ - "this is a test", - "hope it succeeds", - "but just in case", - "send me your millions", - "", - } - - lpstringBytes []byte = []byte{ - 0x0, 0xe, 't', 'h', 'i', 's', ' ', 'i', 's', ' ', 'a', ' ', 't', 'e', 's', 't', - 0x0, 0x10, 'h', 'o', 'p', 'e', ' ', 'i', 't', ' ', 's', 'u', 'c', 'c', 'e', 'e', 'd', 's', - 0x0, 0x10, 'b', 'u', 't', ' ', 'j', 'u', 's', 't', ' ', 'i', 'n', ' ', 'c', 'a', 's', 'e', - 0x0, 0x15, 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'y', 'o', 'u', 'r', ' ', 'm', 'i', 'l', 'l', 'i', 'o', 'n', 's', - 0x0, 0x0, - } - - msgBytes []byte = []byte{ - byte(CONNECT << 4), - 60, - 0, // Length MSB (0) - 4, // Length LSB (4) - 'M', 'Q', 'T', 'T', - 4, // Protocol level 4 - 206, // connect flags 11001110, will QoS = 01 - 0, // Keep Alive MSB (0) - 10, // Keep Alive LSB (10) - 0, // Client ID MSB (0) - 7, // Client ID LSB (7) - 's', 'u', 'r', 'g', 'e', 'm', 'q', - 0, // Will Topic MSB (0) - 4, // Will Topic LSB (4) - 'w', 'i', 'l', 'l', - 0, // Will Message MSB (0) - 12, // Will Message LSB (12) - 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', - 0, // Username ID MSB (0) - 7, // Username ID LSB (7) - 's', 'u', 'r', 'g', 'e', 'm', 'q', - 0, // Password ID MSB (0) - 10, // Password ID LSB (10) - 'v', 'e', 'r', 'y', 's', 'e', 'c', 'r', 'e', 't', - } -) - -func TestReadLPBytes(t *testing.T) { - total := 0 - - for _, str := range lpstrings { - b, n, err := readLPBytes(lpstringBytes[total:]) - - require.NoError(t, err) - require.Equal(t, str, string(b)) - require.Equal(t, len(str)+2, n) - - total += n - } -} - -func TestWriteLPBytes(t *testing.T) { - total := 0 - buf := make([]byte, 1000) - - for _, str := range lpstrings { - n, err := writeLPBytes(buf[total:], []byte(str)) - - require.NoError(t, err) - require.Equal(t, 2+len(str), n) - - total += n - } - - require.Equal(t, lpstringBytes, buf[:total]) -} - -func TestMessageTypes(t *testing.T) { - if CONNECT != 1 || - CONNACK != 2 || - PUBLISH != 3 || - PUBACK != 4 || - PUBREC != 5 || - PUBREL != 6 || - PUBCOMP != 7 || - SUBSCRIBE != 8 || - SUBACK != 9 || - UNSUBSCRIBE != 10 || - UNSUBACK != 11 || - PINGREQ != 12 || - PINGRESP != 13 || - DISCONNECT != 14 { - - t.Errorf("Message types have invalid code") - } -} - -func TestQosCodes(t *testing.T) { - if QosAtMostOnce != 0 || QosAtLeastOnce != 1 || QosExactlyOnce != 2 { - t.Errorf("QOS codes invalid") - } -} - -func TestConnackReturnCodes(t *testing.T) { - require.Equal(t, ErrInvalidProtocolVersion.Error(), ConnackCode(1).Error(), "Incorrect ConnackCode error value.") - - require.Equal(t, ErrIdentifierRejected.Error(), ConnackCode(2).Error(), "Incorrect ConnackCode error value.") - - require.Equal(t, ErrServerUnavailable.Error(), ConnackCode(3).Error(), "Incorrect ConnackCode error value.") - - require.Equal(t, ErrBadUsernameOrPassword.Error(), ConnackCode(4).Error(), "Incorrect ConnackCode error value.") - - require.Equal(t, ErrNotAuthorized.Error(), ConnackCode(5).Error(), "Incorrect ConnackCode error value.") -} - -func TestFixedHeaderFlags(t *testing.T) { - type detail struct { - name string - flags byte - } - - details := map[MessageType]detail{ - RESERVED: detail{"RESERVED", 0}, - CONNECT: detail{"CONNECT", 0}, - CONNACK: detail{"CONNACK", 0}, - PUBLISH: detail{"PUBLISH", 0}, - PUBACK: detail{"PUBACK", 0}, - PUBREC: detail{"PUBREC", 0}, - PUBREL: detail{"PUBREL", 2}, - PUBCOMP: detail{"PUBCOMP", 0}, - SUBSCRIBE: detail{"SUBSCRIBE", 2}, - SUBACK: detail{"SUBACK", 0}, - UNSUBSCRIBE: detail{"UNSUBSCRIBE", 2}, - UNSUBACK: detail{"UNSUBACK", 0}, - PINGREQ: detail{"PINGREQ", 0}, - PINGRESP: detail{"PINGRESP", 0}, - DISCONNECT: detail{"DISCONNECT", 0}, - RESERVED2: detail{"RESERVED2", 0}, - } - - for m, d := range details { - if m.Name() != d.name { - t.Errorf("Name mismatch. Expecting %s, got %s.", d.name, m.Name()) - } - - if m.DefaultFlags() != d.flags { - t.Errorf("Flag mismatch for %s. Expecting %d, got %d.", m.Name(), d.flags, m.DefaultFlags()) - } - } -} - -func TestSupportedVersions(t *testing.T) { - for k, v := range SupportedVersions { - if k == 0x03 && v != "MQIsdp" { - t.Errorf("Protocol version and name mismatch. Expect %s, got %s.", "MQIsdp", v) - } - } -} diff --git a/lib/message/ping_test.go b/lib/message/ping_test.go deleted file mode 100755 index 03cc03d..0000000 --- a/lib/message/ping_test.go +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestPingreqMessageDecode(t *testing.T) { - msgBytes := []byte{ - byte(PINGREQ << 4), - 0, - } - - msg := NewPingreqMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, PINGREQ, msg.Type(), "Error decoding message.") -} - -func TestPingreqMessageEncode(t *testing.T) { - msgBytes := []byte{ - byte(PINGREQ << 4), - 0, - } - - msg := NewPingreqMessage() - - dst := make([]byte, 10) - n, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n], "Error decoding message.") -} - -func TestPingrespMessageDecode(t *testing.T) { - msgBytes := []byte{ - byte(PINGRESP << 4), - 0, - } - - msg := NewPingrespMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, PINGRESP, msg.Type(), "Error decoding message.") -} - -func TestPingrespMessageEncode(t *testing.T) { - msgBytes := []byte{ - byte(PINGRESP << 4), - 0, - } - - msg := NewPingrespMessage() - - dst := make([]byte, 10) - n, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n], "Error decoding message.") -} - -// test to ensure encoding and decoding are the same -// decode, encode, and decode again -func TestPingreqDecodeEncodeEquiv(t *testing.T) { - msgBytes := []byte{ - byte(PINGREQ << 4), - 0, - } - - msg := NewPingreqMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - - dst := make([]byte, 100) - n2, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n2, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") - - n3, err := msg.Decode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n3, "Error decoding message.") -} - -// test to ensure encoding and decoding are the same -// decode, encode, and decode again -func TestPingrespDecodeEncodeEquiv(t *testing.T) { - msgBytes := []byte{ - byte(PINGRESP << 4), - 0, - } - - msg := NewPingrespMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - - dst := make([]byte, 100) - n2, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n2, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") - - n3, err := msg.Decode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n3, "Error decoding message.") -} diff --git a/lib/message/pingreq.go b/lib/message/pingreq.go deleted file mode 100755 index 75ed0a9..0000000 --- a/lib/message/pingreq.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -// The PINGREQ Packet is sent from a Client to the Server. It can be used to: -// 1. Indicate to the Server that the Client is alive in the absence of any other -// Control Packets being sent from the Client to the Server. -// 2. Request that the Server responds to confirm that it is alive. -// 3. Exercise the network to indicate that the Network Connection is active. -type PingreqMessage struct { - DisconnectMessage -} - -var _ Message = (*PingreqMessage)(nil) - -// NewPingreqMessage creates a new PINGREQ message. -func NewPingreqMessage() *PingreqMessage { - msg := &PingreqMessage{} - msg.SetType(PINGREQ) - - return msg -} diff --git a/lib/message/pingresp.go b/lib/message/pingresp.go deleted file mode 100755 index 2e4cb97..0000000 --- a/lib/message/pingresp.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -// A PINGRESP Packet is sent by the Server to the Client in response to a PINGREQ -// Packet. It indicates that the Server is alive. -type PingrespMessage struct { - DisconnectMessage -} - -var _ Message = (*PingrespMessage)(nil) - -// NewPingrespMessage creates a new PINGRESP message. -func NewPingrespMessage() *PingrespMessage { - msg := &PingrespMessage{} - msg.SetType(PINGRESP) - - return msg -} diff --git a/lib/message/puback.go b/lib/message/puback.go deleted file mode 100755 index b6e434c..0000000 --- a/lib/message/puback.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -import "fmt" - -// A PUBACK Packet is the response to a PUBLISH Packet with QoS level 1. -type PubackMessage struct { - header -} - -var _ Message = (*PubackMessage)(nil) - -// NewPubackMessage creates a new PUBACK message. -func NewPubackMessage() *PubackMessage { - msg := &PubackMessage{} - msg.SetType(PUBACK) - - return msg -} - -func (this PubackMessage) String() string { - return fmt.Sprintf("%s, Packet ID=%d", this.header, this.packetId) -} - -func (this *PubackMessage) Len() int { - if !this.dirty { - return len(this.dbuf) - } - - ml := this.msglen() - - if err := this.SetRemainingLength(int32(ml)); err != nil { - return 0 - } - - return this.header.msglen() + ml -} - -func (this *PubackMessage) Decode(src []byte) (int, error) { - total := 0 - - n, err := this.header.decode(src[total:]) - total += n - if err != nil { - return total, err - } - - //this.packetId = binary.BigEndian.Uint16(src[total:]) - this.packetId = src[total : total+2] - total += 2 - - this.dirty = false - - return total, nil -} - -func (this *PubackMessage) Encode(dst []byte) (int, error) { - if !this.dirty { - if len(dst) < len(this.dbuf) { - return 0, fmt.Errorf("puback/Encode: Insufficient buffer size. Expecting %d, got %d.", len(this.dbuf), len(dst)) - } - - return copy(dst, this.dbuf), nil - } - - hl := this.header.msglen() - ml := this.msglen() - - if len(dst) < hl+ml { - return 0, fmt.Errorf("puback/Encode: Insufficient buffer size. Expecting %d, got %d.", hl+ml, len(dst)) - } - - if err := this.SetRemainingLength(int32(ml)); err != nil { - return 0, err - } - - total := 0 - - n, err := this.header.encode(dst[total:]) - total += n - if err != nil { - return total, err - } - - if copy(dst[total:total+2], this.packetId) != 2 { - dst[total], dst[total+1] = 0, 0 - } - total += 2 - - return total, nil -} - -func (this *PubackMessage) msglen() int { - // packet ID - return 2 -} diff --git a/lib/message/puback_test.go b/lib/message/puback_test.go deleted file mode 100755 index d5cffff..0000000 --- a/lib/message/puback_test.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestPubackMessageFields(t *testing.T) { - msg := NewPubackMessage() - - msg.SetPacketId(100) - - require.Equal(t, 100, int(msg.PacketId())) -} - -func TestPubackMessageDecode(t *testing.T) { - msgBytes := []byte{ - byte(PUBACK << 4), - 2, - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - } - - msg := NewPubackMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, PUBACK, msg.Type(), "Error decoding message.") - require.Equal(t, 7, int(msg.PacketId()), "Error decoding message.") -} - -// test insufficient bytes -func TestPubackMessageDecode2(t *testing.T) { - msgBytes := []byte{ - byte(PUBACK << 4), - 2, - 7, // packet ID LSB (7) - } - - msg := NewPubackMessage() - _, err := msg.Decode(msgBytes) - - require.Error(t, err) -} - -func TestPubackMessageEncode(t *testing.T) { - msgBytes := []byte{ - byte(PUBACK << 4), - 2, - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - } - - msg := NewPubackMessage() - msg.SetPacketId(7) - - dst := make([]byte, 10) - n, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n], "Error decoding message.") -} - -// test to ensure encoding and decoding are the same -// decode, encode, and decode again -func TestPubackDecodeEncodeEquiv(t *testing.T) { - msgBytes := []byte{ - byte(PUBACK << 4), - 2, - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - } - - msg := NewPubackMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - - dst := make([]byte, 100) - n2, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n2, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") - - n3, err := msg.Decode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n3, "Error decoding message.") -} diff --git a/lib/message/pubcomp.go b/lib/message/pubcomp.go deleted file mode 100755 index 40430fb..0000000 --- a/lib/message/pubcomp.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -// The PUBCOMP Packet is the response to a PUBREL Packet. It is the fourth and -// final packet of the QoS 2 protocol exchange. -type PubcompMessage struct { - PubackMessage -} - -var _ Message = (*PubcompMessage)(nil) - -// NewPubcompMessage creates a new PUBCOMP message. -func NewPubcompMessage() *PubcompMessage { - msg := &PubcompMessage{} - msg.SetType(PUBCOMP) - - return msg -} diff --git a/lib/message/pubcomp_test.go b/lib/message/pubcomp_test.go deleted file mode 100755 index f4caf1e..0000000 --- a/lib/message/pubcomp_test.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestPubcompMessageFields(t *testing.T) { - msg := NewPubcompMessage() - - msg.SetPacketId(100) - - require.Equal(t, 100, int(msg.PacketId())) -} - -func TestPubcompMessageDecode(t *testing.T) { - msgBytes := []byte{ - byte(PUBCOMP << 4), - 2, - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - } - - msg := NewPubcompMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, PUBCOMP, msg.Type(), "Error decoding message.") - require.Equal(t, 7, int(msg.PacketId()), "Error decoding message.") -} - -// test insufficient bytes -func TestPubcompMessageDecode2(t *testing.T) { - msgBytes := []byte{ - byte(PUBCOMP << 4), - 2, - 7, // packet ID LSB (7) - } - - msg := NewPubcompMessage() - _, err := msg.Decode(msgBytes) - - require.Error(t, err) -} - -func TestPubcompMessageEncode(t *testing.T) { - msgBytes := []byte{ - byte(PUBCOMP << 4), - 2, - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - } - - msg := NewPubcompMessage() - msg.SetPacketId(7) - - dst := make([]byte, 10) - n, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n], "Error decoding message.") -} - -// test to ensure encoding and decoding are the same -// decode, encode, and decode again -func TestPubcompDecodeEncodeEquiv(t *testing.T) { - msgBytes := []byte{ - byte(PUBCOMP << 4), - 2, - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - } - - msg := NewPubcompMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - - dst := make([]byte, 100) - n2, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n2, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") - - n3, err := msg.Decode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n3, "Error decoding message.") -} diff --git a/lib/message/publish.go b/lib/message/publish.go deleted file mode 100755 index ff7e161..0000000 --- a/lib/message/publish.go +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -import ( - "fmt" - "sync/atomic" -) - -// A PUBLISH Control Packet is sent from a Client to a Server or from Server to a Client -// to transport an Application Message. -type PublishMessage struct { - header - - topic []byte - payload []byte -} - -var _ Message = (*PublishMessage)(nil) - -// NewPublishMessage creates a new PUBLISH message. -func NewPublishMessage() *PublishMessage { - msg := &PublishMessage{} - msg.SetType(PUBLISH) - - return msg -} - -func (this PublishMessage) String() string { - return fmt.Sprintf("%s, Topic=%q, Packet ID=%d, QoS=%d, Retained=%t, Dup=%t, Payload=%v", - this.header, this.topic, this.packetId, this.QoS(), this.Retain(), this.Dup(), this.payload) -} - -// Dup returns the value specifying the duplicate delivery of a PUBLISH Control Packet. -// If the DUP flag is set to 0, it indicates that this is the first occasion that the -// Client or Server has attempted to send this MQTT PUBLISH Packet. If the DUP flag is -// set to 1, it indicates that this might be re-delivery of an earlier attempt to send -// the Packet. -func (this *PublishMessage) Dup() bool { - return ((this.Flags() >> 3) & 0x1) == 1 -} - -// SetDup sets the value specifying the duplicate delivery of a PUBLISH Control Packet. -func (this *PublishMessage) SetDup(v bool) { - if v { - this.mtypeflags[0] |= 0x8 // 00001000 - } else { - this.mtypeflags[0] &= 247 // 11110111 - } -} - -// Retain returns the value of the RETAIN flag. This flag is only used on the PUBLISH -// Packet. If the RETAIN flag is set to 1, in a PUBLISH Packet sent by a Client to a -// Server, the Server MUST store the Application Message and its QoS, so that it can be -// delivered to future subscribers whose subscriptions match its topic name. -func (this *PublishMessage) Retain() bool { - return (this.Flags() & 0x1) == 1 -} - -// SetRetain sets the value of the RETAIN flag. -func (this *PublishMessage) SetRetain(v bool) { - if v { - this.mtypeflags[0] |= 0x1 // 00000001 - } else { - this.mtypeflags[0] &= 254 // 11111110 - } -} - -// QoS returns the field that indicates the level of assurance for delivery of an -// Application Message. The values are QosAtMostOnce, QosAtLeastOnce and QosExactlyOnce. -func (this *PublishMessage) QoS() byte { - return (this.Flags() >> 1) & 0x3 -} - -// SetQoS sets the field that indicates the level of assurance for delivery of an -// Application Message. The values are QosAtMostOnce, QosAtLeastOnce and QosExactlyOnce. -// An error is returned if the value is not one of these. -func (this *PublishMessage) SetQoS(v byte) error { - if v != 0x0 && v != 0x1 && v != 0x2 { - return fmt.Errorf("publish/SetQoS: Invalid QoS %d.", v) - } - - this.mtypeflags[0] = (this.mtypeflags[0] & 249) | (v << 1) // 249 = 11111001 - - return nil -} - -// Topic returns the the topic name that identifies the information channel to which -// payload data is published. -func (this *PublishMessage) Topic() []byte { - return this.topic -} - -// SetTopic sets the the topic name that identifies the information channel to which -// payload data is published. An error is returned if ValidTopic() is falbase. -func (this *PublishMessage) SetTopic(v []byte) error { - if !ValidTopic(v) { - return fmt.Errorf("publish/SetTopic: Invalid topic name (%s). Must not be empty or contain wildcard characters", string(v)) - } - - this.topic = v - this.dirty = true - - return nil -} - -// Payload returns the application message that's part of the PUBLISH message. -func (this *PublishMessage) Payload() []byte { - return this.payload -} - -// SetPayload sets the application message that's part of the PUBLISH message. -func (this *PublishMessage) SetPayload(v []byte) { - this.payload = v - this.dirty = true -} - -func (this *PublishMessage) Len() int { - if !this.dirty { - return len(this.dbuf) - } - - ml := this.msglen() - - if err := this.SetRemainingLength(int32(ml)); err != nil { - return 0 - } - - return this.header.msglen() + ml -} - -func (this *PublishMessage) Decode(src []byte) (int, error) { - total := 0 - - hn, err := this.header.decode(src[total:]) - total += hn - if err != nil { - return total, err - } - - n := 0 - - this.topic, n, err = readLPBytes(src[total:]) - total += n - if err != nil { - return total, err - } - - if !ValidTopic(this.topic) { - return total, fmt.Errorf("publish/Decode: Invalid topic name (%s). Must not be empty or contain wildcard characters", string(this.topic)) - } - - // The packet identifier field is only present in the PUBLISH packets where the - // QoS level is 1 or 2 - if this.QoS() != 0 { - //this.packetId = binary.BigEndian.Uint16(src[total:]) - this.packetId = src[total : total+2] - total += 2 - } - - l := int(this.remlen) - (total - hn) - this.payload = src[total : total+l] - total += len(this.payload) - - this.dirty = false - - return total, nil -} - -func (this *PublishMessage) Encode(dst []byte) (int, error) { - if !this.dirty { - if len(dst) < len(this.dbuf) { - return 0, fmt.Errorf("publish/Encode: Insufficient buffer size. Expecting %d, got %d.", len(this.dbuf), len(dst)) - } - - return copy(dst, this.dbuf), nil - } - - if len(this.topic) == 0 { - return 0, fmt.Errorf("publish/Encode: Topic name is empty.") - } - - if len(this.payload) == 0 { - return 0, fmt.Errorf("publish/Encode: Payload is empty.") - } - - ml := this.msglen() - - if err := this.SetRemainingLength(int32(ml)); err != nil { - return 0, err - } - - hl := this.header.msglen() - - if len(dst) < hl+ml { - return 0, fmt.Errorf("publish/Encode: Insufficient buffer size. Expecting %d, got %d.", hl+ml, len(dst)) - } - - total := 0 - - n, err := this.header.encode(dst[total:]) - total += n - if err != nil { - return total, err - } - - n, err = writeLPBytes(dst[total:], this.topic) - total += n - if err != nil { - return total, err - } - - // The packet identifier field is only present in the PUBLISH packets where the QoS level is 1 or 2 - if this.QoS() != 0 { - if this.PacketId() == 0 { - this.SetPacketId(uint16(atomic.AddUint64(&gPacketId, 1) & 0xffff)) - //this.packetId = uint16(atomic.AddUint64(&gPacketId, 1) & 0xffff) - } - - n = copy(dst[total:], this.packetId) - //binary.BigEndian.PutUint16(dst[total:], this.packetId) - total += n - } - - copy(dst[total:], this.payload) - total += len(this.payload) - - return total, nil -} - -func (this *PublishMessage) msglen() int { - total := 2 + len(this.topic) + len(this.payload) - if this.QoS() != 0 { - total += 2 - } - - return total -} diff --git a/lib/message/publish_test.go b/lib/message/publish_test.go deleted file mode 100755 index c4d2654..0000000 --- a/lib/message/publish_test.go +++ /dev/null @@ -1,281 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestPublishMessageHeaderFields(t *testing.T) { - msg := NewPublishMessage() - msg.mtypeflags[0] |= 11 - - require.True(t, msg.Dup(), "Incorrect DUP flag.") - require.True(t, msg.Retain(), "Incorrect RETAIN flag.") - require.Equal(t, 1, int(msg.QoS()), "Incorrect QoS.") - - msg.SetDup(false) - - require.False(t, msg.Dup(), "Incorrect DUP flag.") - - msg.SetRetain(false) - - require.False(t, msg.Retain(), "Incorrect RETAIN flag.") - - err := msg.SetQoS(2) - - require.NoError(t, err, "Error setting QoS.") - require.Equal(t, 2, int(msg.QoS()), "Incorrect QoS.") - - err = msg.SetQoS(3) - - require.Error(t, err) - - err = msg.SetQoS(0) - - require.NoError(t, err, "Error setting QoS.") - require.Equal(t, 0, int(msg.QoS()), "Incorrect QoS.") - - msg.SetDup(true) - - require.True(t, msg.Dup(), "Incorrect DUP flag.") - - msg.SetRetain(true) - - require.True(t, msg.Retain(), "Incorrect RETAIN flag.") -} - -func TestPublishMessageFields(t *testing.T) { - msg := NewPublishMessage() - - msg.SetTopic([]byte("coolstuff")) - - require.Equal(t, "coolstuff", string(msg.Topic()), "Error setting message topic.") - - err := msg.SetTopic([]byte("coolstuff/#")) - - require.Error(t, err) - - msg.SetPacketId(100) - - require.Equal(t, 100, int(msg.PacketId()), "Error setting acket ID.") - - msg.SetPayload([]byte("this is a payload to be sent")) - - require.Equal(t, []byte("this is a payload to be sent"), msg.Payload(), "Error setting payload.") -} - -func TestPublishMessageDecode1(t *testing.T) { - msgBytes := []byte{ - byte(PUBLISH<<4) | 2, - 23, - 0, // topic name MSB (0) - 7, // topic name LSB (7) - 's', 'u', 'r', 'g', 'e', 'm', 'q', - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', - } - - msg := NewPublishMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, 7, int(msg.PacketId()), "Error decoding message.") - require.Equal(t, "surgemq", string(msg.Topic()), "Error deocding topic name.") - require.Equal(t, []byte{'s', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e'}, msg.Payload(), "Error deocding payload.") -} - -// test insufficient bytes -func TestPublishMessageDecode2(t *testing.T) { - msgBytes := []byte{ - byte(PUBLISH<<4) | 2, - 26, - 0, // topic name MSB (0) - 7, // topic name LSB (7) - 's', 'u', 'r', 'g', 'e', 'm', 'q', - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', - } - - msg := NewPublishMessage() - _, err := msg.Decode(msgBytes) - - require.Error(t, err) -} - -// test qos = 0 and no client id -func TestPublishMessageDecode3(t *testing.T) { - msgBytes := []byte{ - byte(PUBLISH << 4), - 21, - 0, // topic name MSB (0) - 7, // topic name LSB (7) - 's', 'u', 'r', 'g', 'e', 'm', 'q', - 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', - } - - msg := NewPublishMessage() - _, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") -} - -func TestPublishMessageEncode(t *testing.T) { - msgBytes := []byte{ - byte(PUBLISH<<4) | 2, - 23, - 0, // topic name MSB (0) - 7, // topic name LSB (7) - 's', 'u', 'r', 'g', 'e', 'm', 'q', - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', - } - - msg := NewPublishMessage() - msg.SetTopic([]byte("surgemq")) - msg.SetQoS(1) - msg.SetPacketId(7) - msg.SetPayload([]byte{'s', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e'}) - - dst := make([]byte, 100) - n, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n], "Error decoding message.") -} - -// test empty topic name -func TestPublishMessageEncode2(t *testing.T) { - msg := NewPublishMessage() - msg.SetTopic([]byte("")) - msg.SetPacketId(7) - msg.SetPayload([]byte{'s', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e'}) - - dst := make([]byte, 100) - _, err := msg.Encode(dst) - require.Error(t, err) -} - -// test encoding qos = 0 and no packet id -func TestPublishMessageEncode3(t *testing.T) { - msgBytes := []byte{ - byte(PUBLISH << 4), - 21, - 0, // topic name MSB (0) - 7, // topic name LSB (7) - 's', 'u', 'r', 'g', 'e', 'm', 'q', - 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', - } - - msg := NewPublishMessage() - msg.SetTopic([]byte("surgemq")) - msg.SetQoS(0) - msg.SetPayload([]byte{'s', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e'}) - - dst := make([]byte, 100) - n, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n], "Error decoding message.") -} - -// test large message -func TestPublishMessageEncode4(t *testing.T) { - msgBytes := []byte{ - byte(PUBLISH << 4), - 137, - 8, - 0, // topic name MSB (0) - 7, // topic name LSB (7) - 's', 'u', 'r', 'g', 'e', 'm', 'q', - } - - payload := make([]byte, 1024) - msgBytes = append(msgBytes, payload...) - - msg := NewPublishMessage() - msg.SetTopic([]byte("surgemq")) - msg.SetQoS(0) - msg.SetPayload(payload) - - require.Equal(t, len(msgBytes), msg.Len()) - - dst := make([]byte, 1100) - n, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n], "Error decoding message.") -} - -// test from github issue #2, @mrdg -func TestPublishDecodeEncodeEquiv2(t *testing.T) { - msgBytes := []byte{50, 18, 0, 9, 103, 114, 101, 101, 116, 105, 110, 103, 115, 0, 1, 72, 101, 108, 108, 111} - - msg := NewPublishMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - - dst := make([]byte, 100) - n2, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n2, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n], "Error decoding message.") -} - -// test to ensure encoding and decoding are the same -// decode, encode, and decode again -func TestPublishDecodeEncodeEquiv(t *testing.T) { - msgBytes := []byte{ - byte(PUBLISH<<4) | 2, - 23, - 0, // topic name MSB (0) - 7, // topic name LSB (7) - 's', 'u', 'r', 'g', 'e', 'm', 'q', - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', - } - - msg := NewPublishMessage() - - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - - dst := make([]byte, 100) - n2, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n2, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") - - n3, err := msg.Decode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n3, "Error decoding message.") -} diff --git a/lib/message/pubrec.go b/lib/message/pubrec.go deleted file mode 100755 index 2d2b900..0000000 --- a/lib/message/pubrec.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -type PubrecMessage struct { - PubackMessage -} - -// A PUBREC Packet is the response to a PUBLISH Packet with QoS 2. It is the second -// packet of the QoS 2 protocol exchange. -var _ Message = (*PubrecMessage)(nil) - -// NewPubrecMessage creates a new PUBREC message. -func NewPubrecMessage() *PubrecMessage { - msg := &PubrecMessage{} - msg.SetType(PUBREC) - - return msg -} diff --git a/lib/message/pubrec_test.go b/lib/message/pubrec_test.go deleted file mode 100755 index 2d8e641..0000000 --- a/lib/message/pubrec_test.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestPubrecMessageFields(t *testing.T) { - msg := NewPubrecMessage() - - msg.SetPacketId(100) - - require.Equal(t, 100, int(msg.PacketId())) -} - -func TestPubrecMessageDecode(t *testing.T) { - msgBytes := []byte{ - byte(PUBREC << 4), - 2, - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - } - - msg := NewPubrecMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, PUBREC, msg.Type(), "Error decoding message.") - require.Equal(t, 7, int(msg.PacketId()), "Error decoding message.") -} - -// test insufficient bytes -func TestPubrecMessageDecode2(t *testing.T) { - msgBytes := []byte{ - byte(PUBREC << 4), - 2, - 7, // packet ID LSB (7) - } - - msg := NewPubrecMessage() - _, err := msg.Decode(msgBytes) - - require.Error(t, err) -} - -func TestPubrecMessageEncode(t *testing.T) { - msgBytes := []byte{ - byte(PUBREC << 4), - 2, - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - } - - msg := NewPubrecMessage() - msg.SetPacketId(7) - - dst := make([]byte, 10) - n, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n], "Error decoding message.") -} - -// test to ensure encoding and decoding are the same -// decode, encode, and decode again -func TestPubrecDecodeEncodeEquiv(t *testing.T) { - msgBytes := []byte{ - byte(PUBREC << 4), - 2, - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - } - - msg := NewPubrecMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - - dst := make([]byte, 100) - n2, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n2, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") - - n3, err := msg.Decode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n3, "Error decoding message.") -} diff --git a/lib/message/pubrel.go b/lib/message/pubrel.go deleted file mode 100755 index d78c083..0000000 --- a/lib/message/pubrel.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -// A PUBREL Packet is the response to a PUBREC Packet. It is the third packet of the -// QoS 2 protocol exchange. -type PubrelMessage struct { - PubackMessage -} - -var _ Message = (*PubrelMessage)(nil) - -// NewPubrelMessage creates a new PUBREL message. -func NewPubrelMessage() *PubrelMessage { - msg := &PubrelMessage{} - msg.SetType(PUBREL) - - return msg -} diff --git a/lib/message/pubrel_test.go b/lib/message/pubrel_test.go deleted file mode 100755 index 958797c..0000000 --- a/lib/message/pubrel_test.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestPubrelMessageFields(t *testing.T) { - msg := NewPubrelMessage() - - msg.SetPacketId(100) - - require.Equal(t, 100, int(msg.PacketId())) -} - -func TestPubrelMessageDecode(t *testing.T) { - msgBytes := []byte{ - byte(PUBREL<<4) | 2, - 2, - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - } - - msg := NewPubrelMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, PUBREL, msg.Type(), "Error decoding message.") - require.Equal(t, 7, int(msg.PacketId()), "Error decoding message.") -} - -// test insufficient bytes -func TestPubrelMessageDecode2(t *testing.T) { - msgBytes := []byte{ - byte(PUBREL<<4) | 2, - 2, - 7, // packet ID LSB (7) - } - - msg := NewPubrelMessage() - _, err := msg.Decode(msgBytes) - - require.Error(t, err) -} - -func TestPubrelMessageEncode(t *testing.T) { - msgBytes := []byte{ - byte(PUBREL<<4) | 2, - 2, - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - } - - msg := NewPubrelMessage() - msg.SetPacketId(7) - - dst := make([]byte, 10) - n, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n], "Error decoding message.") -} - -// test to ensure encoding and decoding are the same -// decode, encode, and decode again -func TestPubrelDecodeEncodeEquiv(t *testing.T) { - msgBytes := []byte{ - byte(PUBREL<<4) | 2, - 2, - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - } - - msg := NewPubrelMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - - dst := make([]byte, 100) - n2, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n2, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") - - n3, err := msg.Decode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n3, "Error decoding message.") -} diff --git a/lib/message/suback.go b/lib/message/suback.go deleted file mode 100755 index 31c320d..0000000 --- a/lib/message/suback.go +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -import "fmt" - -// A SUBACK Packet is sent by the Server to the Client to confirm receipt and processing -// of a SUBSCRIBE Packet. -// -// A SUBACK Packet contains a list of return codes, that specify the maximum QoS level -// that was granted in each Subscription that was requested by the SUBSCRIBE. -type SubackMessage struct { - header - - returnCodes []byte -} - -var _ Message = (*SubackMessage)(nil) - -// NewSubackMessage creates a new SUBACK message. -func NewSubackMessage() *SubackMessage { - msg := &SubackMessage{} - msg.SetType(SUBACK) - - return msg -} - -// String returns a string representation of the message. -func (this SubackMessage) String() string { - return fmt.Sprintf("%s, Packet ID=%d, Return Codes=%v", this.header, this.PacketId(), this.returnCodes) -} - -// ReturnCodes returns the list of QoS returns from the subscriptions sent in the SUBSCRIBE message. -func (this *SubackMessage) ReturnCodes() []byte { - return this.returnCodes -} - -// AddReturnCodes sets the list of QoS returns from the subscriptions sent in the SUBSCRIBE message. -// An error is returned if any of the QoS values are not valid. -func (this *SubackMessage) AddReturnCodes(ret []byte) error { - for _, c := range ret { - if c != QosAtMostOnce && c != QosAtLeastOnce && c != QosExactlyOnce && c != QosFailure { - return fmt.Errorf("suback/AddReturnCode: Invalid return code %d. Must be 0, 1, 2, 0x80.", c) - } - - this.returnCodes = append(this.returnCodes, c) - } - - this.dirty = true - - return nil -} - -// AddReturnCode adds a single QoS return value. -func (this *SubackMessage) AddReturnCode(ret byte) error { - return this.AddReturnCodes([]byte{ret}) -} - -func (this *SubackMessage) Len() int { - if !this.dirty { - return len(this.dbuf) - } - - ml := this.msglen() - - if err := this.SetRemainingLength(int32(ml)); err != nil { - return 0 - } - - return this.header.msglen() + ml -} - -func (this *SubackMessage) Decode(src []byte) (int, error) { - total := 0 - - hn, err := this.header.decode(src[total:]) - total += hn - if err != nil { - return total, err - } - - //this.packetId = binary.BigEndian.Uint16(src[total:]) - this.packetId = src[total : total+2] - total += 2 - - l := int(this.remlen) - (total - hn) - this.returnCodes = src[total : total+l] - total += len(this.returnCodes) - - for i, code := range this.returnCodes { - if code != 0x00 && code != 0x01 && code != 0x02 && code != 0x80 { - return total, fmt.Errorf("suback/Decode: Invalid return code %d for topic %d", code, i) - } - } - - this.dirty = false - - return total, nil -} - -func (this *SubackMessage) Encode(dst []byte) (int, error) { - if !this.dirty { - if len(dst) < len(this.dbuf) { - return 0, fmt.Errorf("suback/Encode: Insufficient buffer size. Expecting %d, got %d.", len(this.dbuf), len(dst)) - } - - return copy(dst, this.dbuf), nil - } - - for i, code := range this.returnCodes { - if code != 0x00 && code != 0x01 && code != 0x02 && code != 0x80 { - return 0, fmt.Errorf("suback/Encode: Invalid return code %d for topic %d", code, i) - } - } - - hl := this.header.msglen() - ml := this.msglen() - - if len(dst) < hl+ml { - return 0, fmt.Errorf("suback/Encode: Insufficient buffer size. Expecting %d, got %d.", hl+ml, len(dst)) - } - - if err := this.SetRemainingLength(int32(ml)); err != nil { - return 0, err - } - - total := 0 - - n, err := this.header.encode(dst[total:]) - total += n - if err != nil { - return total, err - } - - if copy(dst[total:total+2], this.packetId) != 2 { - dst[total], dst[total+1] = 0, 0 - } - total += 2 - - copy(dst[total:], this.returnCodes) - total += len(this.returnCodes) - - return total, nil -} - -func (this *SubackMessage) msglen() int { - return 2 + len(this.returnCodes) -} diff --git a/lib/message/suback_test.go b/lib/message/suback_test.go deleted file mode 100755 index 11015fd..0000000 --- a/lib/message/suback_test.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestSubackMessageFields(t *testing.T) { - msg := NewSubackMessage() - - msg.SetPacketId(100) - require.Equal(t, 100, int(msg.PacketId()), "Error setting packet ID.") - - msg.AddReturnCode(1) - require.Equal(t, 1, len(msg.ReturnCodes()), "Error adding return code.") - - err := msg.AddReturnCode(0x90) - require.Error(t, err) -} - -func TestSubackMessageDecode(t *testing.T) { - msgBytes := []byte{ - byte(SUBACK << 4), - 6, - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - 0, // return code 1 - 1, // return code 2 - 2, // return code 3 - 0x80, // return code 4 - } - - msg := NewSubackMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, SUBACK, msg.Type(), "Error decoding message.") - require.Equal(t, 4, len(msg.ReturnCodes()), "Error adding return code.") -} - -// test with wrong return code -func TestSubackMessageDecode2(t *testing.T) { - msgBytes := []byte{ - byte(SUBACK << 4), - 6, - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - 0, // return code 1 - 1, // return code 2 - 2, // return code 3 - 0x81, // return code 4 - } - - msg := NewSubackMessage() - _, err := msg.Decode(msgBytes) - - require.Error(t, err) -} - -func TestSubackMessageEncode(t *testing.T) { - msgBytes := []byte{ - byte(SUBACK << 4), - 6, - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - 0, // return code 1 - 1, // return code 2 - 2, // return code 3 - 0x80, // return code 4 - } - - msg := NewSubackMessage() - msg.SetPacketId(7) - msg.AddReturnCode(0) - msg.AddReturnCode(1) - msg.AddReturnCode(2) - msg.AddReturnCode(0x80) - - dst := make([]byte, 10) - n, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n], "Error decoding message.") -} - -// test to ensure encoding and decoding are the same -// decode, encode, and decode again -func TestSubackDecodeEncodeEquiv(t *testing.T) { - msgBytes := []byte{ - byte(SUBACK << 4), - 6, - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - 0, // return code 1 - 1, // return code 2 - 2, // return code 3 - 0x80, // return code 4 - } - - msg := NewSubackMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - - dst := make([]byte, 100) - n2, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n2, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") - - n3, err := msg.Decode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n3, "Error decoding message.") -} diff --git a/lib/message/subscribe.go b/lib/message/subscribe.go deleted file mode 100755 index 2d3dedc..0000000 --- a/lib/message/subscribe.go +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -import ( - "bytes" - "fmt" - "sync/atomic" -) - -// The SUBSCRIBE Packet is sent from the Client to the Server to create one or more -// Subscriptions. Each Subscription registers a Client’s interest in one or more -// Topics. The Server sends PUBLISH Packets to the Client in order to forward -// Application Messages that were published to Topics that match these Subscriptions. -// The SUBSCRIBE Packet also specifies (for each Subscription) the maximum QoS with -// which the Server can send Application Messages to the Client. -type SubscribeMessage struct { - header - - topics [][]byte - qos []byte -} - -var _ Message = (*SubscribeMessage)(nil) - -// NewSubscribeMessage creates a new SUBSCRIBE message. -func NewSubscribeMessage() *SubscribeMessage { - msg := &SubscribeMessage{} - msg.SetType(SUBSCRIBE) - - return msg -} - -func (this SubscribeMessage) String() string { - msgstr := fmt.Sprintf("%s, Packet ID=%d", this.header, this.PacketId()) - - for i, t := range this.topics { - msgstr = fmt.Sprintf("%s, Topic[%d]=%q/%d", msgstr, i, string(t), this.qos[i]) - } - - return msgstr -} - -// Topics returns a list of topics sent by the Client. -func (this *SubscribeMessage) Topics() [][]byte { - return this.topics -} - -// AddTopic adds a single topic to the message, along with the corresponding QoS. -// An error is returned if QoS is invalid. -func (this *SubscribeMessage) AddTopic(topic []byte, qos byte) error { - if !ValidQos(qos) { - return fmt.Errorf("Invalid QoS %d", qos) - } - - var i int - var t []byte - var found bool - - for i, t = range this.topics { - if bytes.Equal(t, topic) { - found = true - break - } - } - - if found { - this.qos[i] = qos - return nil - } - - this.topics = append(this.topics, topic) - this.qos = append(this.qos, qos) - this.dirty = true - - return nil -} - -// RemoveTopic removes a single topic from the list of existing ones in the message. -// If topic does not exist it just does nothing. -func (this *SubscribeMessage) RemoveTopic(topic []byte) { - var i int - var t []byte - var found bool - - for i, t = range this.topics { - if bytes.Equal(t, topic) { - found = true - break - } - } - - if found { - this.topics = append(this.topics[:i], this.topics[i+1:]...) - this.qos = append(this.qos[:i], this.qos[i+1:]...) - } - - this.dirty = true -} - -// TopicExists checks to see if a topic exists in the list. -func (this *SubscribeMessage) TopicExists(topic []byte) bool { - for _, t := range this.topics { - if bytes.Equal(t, topic) { - return true - } - } - - return false -} - -// TopicQos returns the QoS level of a topic. If topic does not exist, QosFailure -// is returned. -func (this *SubscribeMessage) TopicQos(topic []byte) byte { - for i, t := range this.topics { - if bytes.Equal(t, topic) { - return this.qos[i] - } - } - - return QosFailure -} - -// Qos returns the list of QoS current in the message. -func (this *SubscribeMessage) Qos() []byte { - return this.qos -} - -func (this *SubscribeMessage) Len() int { - if !this.dirty { - return len(this.dbuf) - } - - ml := this.msglen() - - if err := this.SetRemainingLength(int32(ml)); err != nil { - return 0 - } - - return this.header.msglen() + ml -} - -func (this *SubscribeMessage) Decode(src []byte) (int, error) { - total := 0 - - hn, err := this.header.decode(src[total:]) - total += hn - if err != nil { - return total, err - } - - //this.packetId = binary.BigEndian.Uint16(src[total:]) - this.packetId = src[total : total+2] - total += 2 - - remlen := int(this.remlen) - (total - hn) - for remlen > 0 { - t, n, err := readLPBytes(src[total:]) - total += n - if err != nil { - return total, err - } - - this.topics = append(this.topics, t) - - this.qos = append(this.qos, src[total]) - total++ - - remlen = remlen - n - 1 - } - - if len(this.topics) == 0 { - return 0, fmt.Errorf("subscribe/Decode: Empty topic list") - } - - this.dirty = false - - return total, nil -} - -func (this *SubscribeMessage) Encode(dst []byte) (int, error) { - if !this.dirty { - if len(dst) < len(this.dbuf) { - return 0, fmt.Errorf("subscribe/Encode: Insufficient buffer size. Expecting %d, got %d.", len(this.dbuf), len(dst)) - } - - return copy(dst, this.dbuf), nil - } - - hl := this.header.msglen() - ml := this.msglen() - - if len(dst) < hl+ml { - return 0, fmt.Errorf("subscribe/Encode: Insufficient buffer size. Expecting %d, got %d.", hl+ml, len(dst)) - } - - if err := this.SetRemainingLength(int32(ml)); err != nil { - return 0, err - } - - total := 0 - - n, err := this.header.encode(dst[total:]) - total += n - if err != nil { - return total, err - } - - if this.PacketId() == 0 { - this.SetPacketId(uint16(atomic.AddUint64(&gPacketId, 1) & 0xffff)) - //this.packetId = uint16(atomic.AddUint64(&gPacketId, 1) & 0xffff) - } - - n = copy(dst[total:], this.packetId) - //binary.BigEndian.PutUint16(dst[total:], this.packetId) - total += n - - for i, t := range this.topics { - n, err := writeLPBytes(dst[total:], t) - total += n - if err != nil { - return total, err - } - - dst[total] = this.qos[i] - total++ - } - - return total, nil -} - -func (this *SubscribeMessage) msglen() int { - // packet ID - total := 2 - - for _, t := range this.topics { - total += 2 + len(t) + 1 - } - - return total -} diff --git a/lib/message/subscribe_test.go b/lib/message/subscribe_test.go deleted file mode 100755 index af42f65..0000000 --- a/lib/message/subscribe_test.go +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestSubscribeMessageFields(t *testing.T) { - msg := NewSubscribeMessage() - - msg.SetPacketId(100) - require.Equal(t, 100, int(msg.PacketId()), "Error setting packet ID.") - - msg.AddTopic([]byte("/a/b/#/c"), 1) - require.Equal(t, 1, len(msg.Topics()), "Error adding topic.") - - require.False(t, msg.TopicExists([]byte("a/b")), "Topic should not exist.") - - msg.RemoveTopic([]byte("/a/b/#/c")) - require.False(t, msg.TopicExists([]byte("/a/b/#/c")), "Topic should not exist.") -} - -func TestSubscribeMessageDecode(t *testing.T) { - msgBytes := []byte{ - byte(SUBSCRIBE<<4) | 2, - 36, - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - 0, // topic name MSB (0) - 7, // topic name LSB (7) - 's', 'u', 'r', 'g', 'e', 'm', 'q', - 0, // QoS - 0, // topic name MSB (0) - 8, // topic name LSB (8) - '/', 'a', '/', 'b', '/', '#', '/', 'c', - 1, // QoS - 0, // topic name MSB (0) - 10, // topic name LSB (10) - '/', 'a', '/', 'b', '/', '#', '/', 'c', 'd', 'd', - 2, // QoS - } - - msg := NewSubscribeMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, SUBSCRIBE, msg.Type(), "Error decoding message.") - require.Equal(t, 3, len(msg.Topics()), "Error decoding topics.") - require.True(t, msg.TopicExists([]byte("surgemq")), "Topic 'surgemq' should exist.") - require.Equal(t, 0, int(msg.TopicQos([]byte("surgemq"))), "Incorrect topic qos.") - require.True(t, msg.TopicExists([]byte("/a/b/#/c")), "Topic '/a/b/#/c' should exist.") - require.Equal(t, 1, int(msg.TopicQos([]byte("/a/b/#/c"))), "Incorrect topic qos.") - require.True(t, msg.TopicExists([]byte("/a/b/#/cdd")), "Topic '/a/b/#/c' should exist.") - require.Equal(t, 2, int(msg.TopicQos([]byte("/a/b/#/cdd"))), "Incorrect topic qos.") -} - -// test empty topic list -func TestSubscribeMessageDecode2(t *testing.T) { - msgBytes := []byte{ - byte(SUBSCRIBE<<4) | 2, - 2, - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - } - - msg := NewSubscribeMessage() - _, err := msg.Decode(msgBytes) - - require.Error(t, err) -} - -func TestSubscribeMessageEncode(t *testing.T) { - msgBytes := []byte{ - byte(SUBSCRIBE<<4) | 2, - 36, - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - 0, // topic name MSB (0) - 7, // topic name LSB (7) - 's', 'u', 'r', 'g', 'e', 'm', 'q', - 0, // QoS - 0, // topic name MSB (0) - 8, // topic name LSB (8) - '/', 'a', '/', 'b', '/', '#', '/', 'c', - 1, // QoS - 0, // topic name MSB (0) - 10, // topic name LSB (10) - '/', 'a', '/', 'b', '/', '#', '/', 'c', 'd', 'd', - 2, // QoS - } - - msg := NewSubscribeMessage() - msg.SetPacketId(7) - msg.AddTopic([]byte("surgemq"), 0) - msg.AddTopic([]byte("/a/b/#/c"), 1) - msg.AddTopic([]byte("/a/b/#/cdd"), 2) - - dst := make([]byte, 100) - n, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n], "Error decoding message.") -} - -// test to ensure encoding and decoding are the same -// decode, encode, and decode again -func TestSubscribeDecodeEncodeEquiv(t *testing.T) { - msgBytes := []byte{ - byte(SUBSCRIBE<<4) | 2, - 36, - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - 0, // topic name MSB (0) - 7, // topic name LSB (7) - 's', 'u', 'r', 'g', 'e', 'm', 'q', - 0, // QoS - 0, // topic name MSB (0) - 8, // topic name LSB (8) - '/', 'a', '/', 'b', '/', '#', '/', 'c', - 1, // QoS - 0, // topic name MSB (0) - 10, // topic name LSB (10) - '/', 'a', '/', 'b', '/', '#', '/', 'c', 'd', 'd', - 2, // QoS - } - - msg := NewSubscribeMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - - dst := make([]byte, 100) - n2, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n2, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") - - n3, err := msg.Decode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n3, "Error decoding message.") -} diff --git a/lib/message/unsuback.go b/lib/message/unsuback.go deleted file mode 100755 index 2346a82..0000000 --- a/lib/message/unsuback.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -// The UNSUBACK Packet is sent by the Server to the Client to confirm receipt of an -// UNSUBSCRIBE Packet. -type UnsubackMessage struct { - PubackMessage -} - -var _ Message = (*UnsubackMessage)(nil) - -// NewUnsubackMessage creates a new UNSUBACK message. -func NewUnsubackMessage() *UnsubackMessage { - msg := &UnsubackMessage{} - msg.SetType(UNSUBACK) - - return msg -} diff --git a/lib/message/unsuback_test.go b/lib/message/unsuback_test.go deleted file mode 100755 index 50336b9..0000000 --- a/lib/message/unsuback_test.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestUnsubackMessageFields(t *testing.T) { - msg := NewUnsubackMessage() - - msg.SetPacketId(100) - - require.Equal(t, 100, int(msg.PacketId())) -} - -func TestUnsubackMessageDecode(t *testing.T) { - msgBytes := []byte{ - byte(UNSUBACK << 4), - 2, - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - } - - msg := NewUnsubackMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, UNSUBACK, msg.Type(), "Error decoding message.") - require.Equal(t, 7, int(msg.PacketId()), "Error decoding message.") -} - -// test insufficient bytes -func TestUnsubackMessageDecode2(t *testing.T) { - msgBytes := []byte{ - byte(UNSUBACK << 4), - 2, - 7, // packet ID LSB (7) - } - - msg := NewUnsubackMessage() - _, err := msg.Decode(msgBytes) - - require.Error(t, err) -} - -func TestUnsubackMessageEncode(t *testing.T) { - msgBytes := []byte{ - byte(UNSUBACK << 4), - 2, - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - } - - msg := NewUnsubackMessage() - msg.SetPacketId(7) - - dst := make([]byte, 10) - n, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n], "Error decoding message.") -} - -// test to ensure encoding and decoding are the same -// decode, encode, and decode again -func TestUnsubackDecodeEncodeEquiv(t *testing.T) { - msgBytes := []byte{ - byte(UNSUBACK << 4), - 2, - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - } - - msg := NewUnsubackMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - - dst := make([]byte, 100) - n2, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n2, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") - - n3, err := msg.Decode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n3, "Error decoding message.") -} diff --git a/lib/message/unsubscribe.go b/lib/message/unsubscribe.go deleted file mode 100755 index 3eb728a..0000000 --- a/lib/message/unsubscribe.go +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -import ( - "bytes" - "fmt" - "sync/atomic" -) - -// An UNSUBSCRIBE Packet is sent by the Client to the Server, to unsubscribe from topics. -type UnsubscribeMessage struct { - header - - topics [][]byte -} - -var _ Message = (*UnsubscribeMessage)(nil) - -// NewUnsubscribeMessage creates a new UNSUBSCRIBE message. -func NewUnsubscribeMessage() *UnsubscribeMessage { - msg := &UnsubscribeMessage{} - msg.SetType(UNSUBSCRIBE) - - return msg -} - -func (this UnsubscribeMessage) String() string { - msgstr := fmt.Sprintf("%s", this.header) - - for i, t := range this.topics { - msgstr = fmt.Sprintf("%s, Topic%d=%s", msgstr, i, string(t)) - } - - return msgstr -} - -// Topics returns a list of topics sent by the Client. -func (this *UnsubscribeMessage) Topics() [][]byte { - return this.topics -} - -// AddTopic adds a single topic to the message. -func (this *UnsubscribeMessage) AddTopic(topic []byte) { - if this.TopicExists(topic) { - return - } - - this.topics = append(this.topics, topic) - this.dirty = true -} - -// RemoveTopic removes a single topic from the list of existing ones in the message. -// If topic does not exist it just does nothing. -func (this *UnsubscribeMessage) RemoveTopic(topic []byte) { - var i int - var t []byte - var found bool - - for i, t = range this.topics { - if bytes.Equal(t, topic) { - found = true - break - } - } - - if found { - this.topics = append(this.topics[:i], this.topics[i+1:]...) - } - - this.dirty = true -} - -// TopicExists checks to see if a topic exists in the list. -func (this *UnsubscribeMessage) TopicExists(topic []byte) bool { - for _, t := range this.topics { - if bytes.Equal(t, topic) { - return true - } - } - - return false -} - -func (this *UnsubscribeMessage) Len() int { - if !this.dirty { - return len(this.dbuf) - } - - ml := this.msglen() - - if err := this.SetRemainingLength(int32(ml)); err != nil { - return 0 - } - - return this.header.msglen() + ml -} - -// Decode reads from the io.Reader parameter until a full message is decoded, or -// when io.Reader returns EOF or error. The first return value is the number of -// bytes read from io.Reader. The second is error if Decode encounters any problems. -func (this *UnsubscribeMessage) Decode(src []byte) (int, error) { - total := 0 - - hn, err := this.header.decode(src[total:]) - total += hn - if err != nil { - return total, err - } - - //this.packetId = binary.BigEndian.Uint16(src[total:]) - this.packetId = src[total : total+2] - total += 2 - - remlen := int(this.remlen) - (total - hn) - for remlen > 0 { - t, n, err := readLPBytes(src[total:]) - total += n - if err != nil { - return total, err - } - - this.topics = append(this.topics, t) - remlen = remlen - n - 1 - } - - if len(this.topics) == 0 { - return 0, fmt.Errorf("unsubscribe/Decode: Empty topic list") - } - - this.dirty = false - - return total, nil -} - -// Encode returns an io.Reader in which the encoded bytes can be read. The second -// return value is the number of bytes encoded, so the caller knows how many bytes -// there will be. If Encode returns an error, then the first two return values -// should be considered invalid. -// Any changes to the message after Encode() is called will invalidate the io.Reader. -func (this *UnsubscribeMessage) Encode(dst []byte) (int, error) { - if !this.dirty { - if len(dst) < len(this.dbuf) { - return 0, fmt.Errorf("unsubscribe/Encode: Insufficient buffer size. Expecting %d, got %d.", len(this.dbuf), len(dst)) - } - - return copy(dst, this.dbuf), nil - } - - hl := this.header.msglen() - ml := this.msglen() - - if len(dst) < hl+ml { - return 0, fmt.Errorf("unsubscribe/Encode: Insufficient buffer size. Expecting %d, got %d.", hl+ml, len(dst)) - } - - if err := this.SetRemainingLength(int32(ml)); err != nil { - return 0, err - } - - total := 0 - - n, err := this.header.encode(dst[total:]) - total += n - if err != nil { - return total, err - } - - if this.PacketId() == 0 { - this.SetPacketId(uint16(atomic.AddUint64(&gPacketId, 1) & 0xffff)) - //this.packetId = uint16(atomic.AddUint64(&gPacketId, 1) & 0xffff) - } - - n = copy(dst[total:], this.packetId) - //binary.BigEndian.PutUint16(dst[total:], this.packetId) - total += n - - for _, t := range this.topics { - n, err := writeLPBytes(dst[total:], t) - total += n - if err != nil { - return total, err - } - } - - return total, nil -} - -func (this *UnsubscribeMessage) msglen() int { - // packet ID - total := 2 - - for _, t := range this.topics { - total += 2 + len(t) - } - - return total -} diff --git a/lib/message/unsubscribe_test.go b/lib/message/unsubscribe_test.go deleted file mode 100755 index df9e2dd..0000000 --- a/lib/message/unsubscribe_test.go +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package message - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestUnsubscribeMessageFields(t *testing.T) { - msg := NewUnsubscribeMessage() - - msg.SetPacketId(100) - require.Equal(t, 100, int(msg.PacketId()), "Error setting packet ID.") - - msg.AddTopic([]byte("/a/b/#/c")) - require.Equal(t, 1, len(msg.Topics()), "Error adding topic.") - - msg.AddTopic([]byte("/a/b/#/c")) - require.Equal(t, 1, len(msg.Topics()), "Error adding duplicate topic.") - - msg.RemoveTopic([]byte("/a/b/#/c")) - require.False(t, msg.TopicExists([]byte("/a/b/#/c")), "Topic should not exist.") - - require.False(t, msg.TopicExists([]byte("a/b")), "Topic should not exist.") - - msg.RemoveTopic([]byte("/a/b/#/c")) - require.False(t, msg.TopicExists([]byte("/a/b/#/c")), "Topic should not exist.") -} - -func TestUnsubscribeMessageDecode(t *testing.T) { - msgBytes := []byte{ - byte(UNSUBSCRIBE<<4) | 2, - 33, - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - 0, // topic name MSB (0) - 7, // topic name LSB (7) - 's', 'u', 'r', 'g', 'e', 'm', 'q', - 0, // topic name MSB (0) - 8, // topic name LSB (8) - '/', 'a', '/', 'b', '/', '#', '/', 'c', - 0, // topic name MSB (0) - 10, // topic name LSB (10) - '/', 'a', '/', 'b', '/', '#', '/', 'c', 'd', 'd', - } - - msg := NewUnsubscribeMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, UNSUBSCRIBE, msg.Type(), "Error decoding message.") - require.Equal(t, 3, len(msg.Topics()), "Error decoding topics.") - require.True(t, msg.TopicExists([]byte("surgemq")), "Topic 'surgemq' should exist.") - require.True(t, msg.TopicExists([]byte("/a/b/#/c")), "Topic '/a/b/#/c' should exist.") - require.True(t, msg.TopicExists([]byte("/a/b/#/cdd")), "Topic '/a/b/#/c' should exist.") -} - -// test empty topic list -func TestUnsubscribeMessageDecode2(t *testing.T) { - msgBytes := []byte{ - byte(UNSUBSCRIBE<<4) | 2, - 2, - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - } - - msg := NewUnsubscribeMessage() - _, err := msg.Decode(msgBytes) - - require.Error(t, err) -} - -func TestUnsubscribeMessageEncode(t *testing.T) { - msgBytes := []byte{ - byte(UNSUBSCRIBE<<4) | 2, - 33, - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - 0, // topic name MSB (0) - 7, // topic name LSB (7) - 's', 'u', 'r', 'g', 'e', 'm', 'q', - 0, // topic name MSB (0) - 8, // topic name LSB (8) - '/', 'a', '/', 'b', '/', '#', '/', 'c', - 0, // topic name MSB (0) - 10, // topic name LSB (10) - '/', 'a', '/', 'b', '/', '#', '/', 'c', 'd', 'd', - } - - msg := NewUnsubscribeMessage() - msg.SetPacketId(7) - msg.AddTopic([]byte("surgemq")) - msg.AddTopic([]byte("/a/b/#/c")) - msg.AddTopic([]byte("/a/b/#/cdd")) - - dst := make([]byte, 100) - n, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n], "Error decoding message.") -} - -// test to ensure encoding and decoding are the same -// decode, encode, and decode again -func TestUnsubscribeDecodeEncodeEquiv(t *testing.T) { - msgBytes := []byte{ - byte(UNSUBSCRIBE<<4) | 2, - 33, - 0, // packet ID MSB (0) - 7, // packet ID LSB (7) - 0, // topic name MSB (0) - 7, // topic name LSB (7) - 's', 'u', 'r', 'g', 'e', 'm', 'q', - 0, // topic name MSB (0) - 8, // topic name LSB (8) - '/', 'a', '/', 'b', '/', '#', '/', 'c', - 0, // topic name MSB (0) - 10, // topic name LSB (10) - '/', 'a', '/', 'b', '/', '#', '/', 'c', 'd', 'd', - } - - msg := NewUnsubscribeMessage() - n, err := msg.Decode(msgBytes) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n, "Error decoding message.") - - dst := make([]byte, 100) - n2, err := msg.Encode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n2, "Error decoding message.") - require.Equal(t, msgBytes, dst[:n2], "Error decoding message.") - - n3, err := msg.Decode(dst) - - require.NoError(t, err, "Error decoding message.") - require.Equal(t, len(msgBytes), n3, "Error decoding message.") -}