mirror of
https://github.com/fhmq/hmq.git
synced 2026-04-27 03:58:33 +00:00
del message
This commit is contained in:
236
broker/packet.go
236
broker/packet.go
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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 <email address>
|
|
||||||
# The email address is not required for organizations.
|
|
||||||
# Please keep the list sorted.
|
|
||||||
|
|
||||||
|
|
||||||
# Individual Persons
|
|
||||||
|
|
||||||
Jian Zhen <zhenjl@gmail.com>
|
|
||||||
|
|
||||||
# Organizations
|
|
||||||
@@ -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".
|
|
||||||
@@ -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.
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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.")
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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.")
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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.")
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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.")
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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.")
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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.")
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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.")
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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.")
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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.")
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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.")
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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.")
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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.")
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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.")
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user