Files
mochi-mqtt/packets/tpackets.go
JB 300152413c Ignore retain as published v3 (#142)
* Optimise Capabilities struct alignment

* Only use RetainAsPublished for v5 clients
2023-01-13 23:38:49 +00:00

3813 lines
90 KiB
Go

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2022 mochi-co
// SPDX-FileContributor: mochi-co
package packets
// TPacketCase contains data for cross-checking the encoding and decoding
// of packets and expected scenarios.
type TPacketCase struct {
RawBytes []byte // the bytes that make the packet
ActualBytes []byte // the actual byte array that is created in the event of a byte mutation
Group string // a group that should run the test, blank for all
Desc string // a description of the test
FailFirst error // expected fail result to be run immediately after the method is called
Packet *Packet // the packet that is Expected
ActualPacket *Packet // the actual packet after mutations
Expect error // generic Expected fail result to be checked
Isolate bool // isolate can be used to isolate a test
Primary bool // primary is a test that should be run using readPackets
Case byte // the identifying byte of the case
}
// TPacketCases is a slice of TPacketCase.
type TPacketCases []TPacketCase
// Get returns a case matching a given T byte.
func (f TPacketCases) Get(b byte) TPacketCase {
for _, v := range f {
if v.Case == b {
return v
}
}
return TPacketCase{}
}
const (
TConnectMqtt31 byte = iota
TConnectMqtt311
TConnectMqtt5
TConnectMqtt5LWT
TConnectClean
TConnectCleanLWT
TConnectUserPass
TConnectUserPassLWT
TConnectMalProtocolName
TConnectMalProtocolVersion
TConnectMalFlags
TConnectMalKeepalive
TConnectMalClientID
TConnectMalWillTopic
TConnectMalWillFlag
TConnectMalUsername
TConnectMalPassword
TConnectMalFixedHeader
TConnectMalReservedBit
TConnectMalProperties
TConnectMalWillProperties
TConnectInvalidProtocolName
TConnectInvalidProtocolVersion
TConnectInvalidProtocolVersion2
TConnectInvalidReservedBit
TConnectInvalidClientIDTooLong
TConnectInvalidPasswordNoUsername
TConnectInvalidFlagNoUsername
TConnectInvalidFlagNoPassword
TConnectInvalidUsernameNoFlag
TConnectInvalidPasswordNoFlag
TConnectInvalidUsernameTooLong
TConnectInvalidPasswordTooLong
TConnectInvalidWillFlagNoPayload
TConnectInvalidWillFlagQosOutOfRange
TConnectInvalidWillSurplusRetain
TConnectNotCleanNoClientID
TConnectSpecInvalidUTF8D800
TConnectSpecInvalidUTF8DFFF
TConnectSpecInvalidUTF80000
TConnectSpecInvalidUTF8NoSkip
TConnackAcceptedNoSession
TConnackAcceptedSessionExists
TConnackAcceptedMqtt5
TConnackAcceptedAdjustedExpiryInterval
TConnackMinMqtt5
TConnackMinCleanMqtt5
TConnackInvalidMinMqtt5
TConnackBadProtocolVersion
TConnackProtocolViolationNoSession
TConnackBadClientID
TConnackServerUnavailable
TConnackBadUsernamePassword
TConnackBadUsernamePasswordNoSession
TConnackMqtt5BadUsernamePasswordNoSession
TConnackNotAuthorised
TConnackMalSessionPresent
TConnackMalReturnCode
TConnackMalProperties
TConnackDropProperties
TConnackDropPropertiesPartial
TPublishNoPayload
TPublishBasic
TPublishBasicTopicAliasOnly
TPublishBasicMqtt5
TPublishMqtt5
TPublishQos1
TPublishQos1NoPayload
TPublishQos1Dup
TPublishQos2
TPublishQos2Mqtt5
TPublishQos2Upgraded
TPublishSubscriberIdentifier
TPublishRetain
TPublishRetainMqtt5
TPublishDup
TPublishMalTopicName
TPublishMalPacketID
TPublishMalProperties
TPublishCopyBasic
TPublishSpecQos0NoPacketID
TPublishSpecQosMustPacketID
TPublishDropOversize
TPublishInvalidQos0NoPacketID
TPublishInvalidQosMustPacketID
TPublishInvalidSurplusSubID
TPublishInvalidSurplusWildcard
TPublishInvalidSurplusWildcard2
TPublishInvalidNoTopic
TPublishInvalidTopicAlias
TPublishInvalidExcessTopicAlias
TPublishSpecDenySysTopic
TPuback
TPubackMqtt5
TPubackMalPacketID
TPubackMalProperties
TPubrec
TPubrecMqtt5
TPubrecMqtt5IDInUse
TPubrecMalPacketID
TPubrecMalProperties
TPubrecMalReasonCode
TPubrecInvalidReason
TPubrel
TPubrelMqtt5
TPubrelMqtt5AckNoPacket
TPubrelMalPacketID
TPubrelMalProperties
TPubrelInvalidReason
TPubcomp
TPubcompMqtt5
TPubcompMqtt5AckNoPacket
TPubcompMalPacketID
TPubcompMalProperties
TPubcompInvalidReason
TSubscribe
TSubscribeMany
TSubscribeMqtt5
TSubscribeRetainHandling1
TSubscribeRetainHandling2
TSubscribeRetainAsPublished
TSubscribeMalPacketID
TSubscribeMalTopic
TSubscribeMalQos
TSubscribeMalQosRange
TSubscribeMalProperties
TSubscribeInvalidQosMustPacketID
TSubscribeSpecQosMustPacketID
TSubscribeInvalidNoFilters
TSubscribeInvalidSharedNoLocal
TSubscribeInvalidFilter
TSubscribeInvalidIdentifierOversize
TSuback
TSubackMany
TSubackDeny
TSubackUnspecifiedError
TSubackUnspecifiedErrorMqtt5
TSubackMqtt5
TSubackPacketIDInUse
TSubackInvalidFilter
TSubackInvalidSharedNoLocal
TSubackMalPacketID
TSubackMalProperties
TUnsubscribe
TUnsubscribeMany
TUnsubscribeMqtt5
TUnsubscribeDropProperties
TUnsubscribeMalPacketID
TUnsubscribeMalTopicName
TUnsubscribeMalProperties
TUnsubscribeInvalidQosMustPacketID
TUnsubscribeSpecQosMustPacketID
TUnsubscribeInvalidNoFilters
TUnsuback
TUnsubackMany
TUnsubackMqtt5
TUnsubackPacketIDInUse
TUnsubackMalPacketID
TUnsubackMalProperties
TPingreq
TPingresp
TDisconnect
TDisconnectTakeover
TDisconnectMqtt5
TDisconnectNormalMqtt5
TDisconnectSecondConnect
TDisconnectReceiveMaximum
TDisconnectDropProperties
TDisconnectShuttingDown
TDisconnectMalProperties
TDisconnectMalReasonCode
TDisconnectZeroNonZeroExpiry
TAuth
TAuthMalReasonCode
TAuthMalProperties
TAuthInvalidReason
TAuthInvalidReason2
)
// TPacketData contains individual encoding and decoding scenarios for each packet type.
var TPacketData = map[byte]TPacketCases{
Connect: {
{
Case: TConnectMqtt31,
Desc: "mqtt v3.1",
Primary: true,
RawBytes: []byte{
Connect << 4, 17, // Fixed header
0, 6, // Protocol Name - MSB+LSB
'M', 'Q', 'I', 's', 'd', 'p', // Protocol Name
3, // Protocol Version
0, // Packet Flags
0, 30, // Keepalive
0, 3, // Client ID - MSB+LSB
'z', 'e', 'n', // Client ID "zen"
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Connect,
Remaining: 17,
},
ProtocolVersion: 3,
Connect: ConnectParams{
ProtocolName: []byte("MQIsdp"),
Clean: false,
Keepalive: 30,
ClientIdentifier: "zen",
},
},
},
{
Case: TConnectMqtt311,
Desc: "mqtt v3.1.1",
Primary: true,
RawBytes: []byte{
Connect << 4, 16, // Fixed header
0, 4, // Protocol Name - MSB+LSB
'M', 'Q', 'T', 'T', // Protocol Name
4, // Protocol Version
0, // Packet Flags
0, 60, // Keepalive
0, 4, // Client ID - MSB+LSB
'z', 'e', 'n', '3', // Client ID "zen"
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Connect,
Remaining: 16,
},
ProtocolVersion: 4,
Connect: ConnectParams{
ProtocolName: []byte("MQTT"),
Clean: false,
Keepalive: 60,
ClientIdentifier: "zen3",
},
},
},
{
Case: TConnectMqtt5,
Desc: "mqtt v5",
Primary: true,
RawBytes: []byte{
Connect << 4, 87, // Fixed header
0, 4, // Protocol Name - MSB+LSB
'M', 'Q', 'T', 'T', // Protocol Name
5, // Protocol Version
0, // Packet Flags
0, 30, // Keepalive
// Properties
71, // length
17, 0, 0, 0, 120, // Session Expiry Interval (17)
21, 0, 5, 'S', 'H', 'A', '-', '1', // Authentication Method (21)
22, 0, 9, 'a', 'u', 't', 'h', '-', 'd', 'a', 't', 'a', // Authentication Data (22)
23, 1, // Request Problem Info (23)
25, 1, // Request Response Info (25)
33, 1, 244, // Receive Maximum (33)
34, 3, 231, // Topic Alias Maximum (34)
38, // User Properties (38)
0, 5, 'h', 'e', 'l', 'l', 'o',
0, 6, 228, 184, 150, 231, 149, 140,
38, // User Properties (38)
0, 4, 'k', 'e', 'y', '2',
0, 6, 'v', 'a', 'l', 'u', 'e', '2',
39, 0, 0, 125, 0, // Maximum Packet Size (39)
0, 3, // Client ID - MSB+LSB
'z', 'e', 'n', // Client ID "zen"
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Connect,
Remaining: 87,
},
ProtocolVersion: 5,
Connect: ConnectParams{
ProtocolName: []byte("MQTT"),
Clean: false,
Keepalive: 30,
ClientIdentifier: "zen",
},
Properties: Properties{
SessionExpiryInterval: uint32(120),
SessionExpiryIntervalFlag: true,
AuthenticationMethod: "SHA-1",
AuthenticationData: []byte("auth-data"),
RequestProblemInfo: byte(1),
RequestProblemInfoFlag: true,
RequestResponseInfo: byte(1),
ReceiveMaximum: uint16(500),
TopicAliasMaximum: uint16(999),
User: []UserProperty{
{
Key: "hello",
Val: "世界",
},
{
Key: "key2",
Val: "value2",
},
},
MaximumPacketSize: uint32(32000),
},
},
},
{
Case: TConnectClean,
Desc: "mqtt 3.1.1, clean session",
RawBytes: []byte{
Connect << 4, 15, // Fixed header
0, 4, // Protocol Name - MSB+LSB
'M', 'Q', 'T', 'T', // Protocol Name
4, // Protocol Version
2, // Packet Flags
0, 45, // Keepalive
0, 3, // Client ID - MSB+LSB
'z', 'e', 'n', // Client ID "zen"
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Connect,
Remaining: 15,
},
ProtocolVersion: 4,
Connect: ConnectParams{
ProtocolName: []byte("MQTT"),
Clean: true,
Keepalive: 45,
ClientIdentifier: "zen",
},
},
},
{
Case: TConnectMqtt5LWT,
Desc: "mqtt 5 clean session, lwt",
RawBytes: []byte{
Connect << 4, 47, // Fixed header
0, 4, // Protocol Name - MSB+LSB
'M', 'Q', 'T', 'T', // Protocol Name
5, // Protocol Version
14, // Packet Flags
0, 30, // Keepalive
// Properties
10, // length
17, 0, 0, 0, 120, // Session Expiry Interval (17)
39, 0, 0, 125, 0, // Maximum Packet Size (39)
0, 3, // Client ID - MSB+LSB
'z', 'e', 'n', // Client ID "zen"
5, // will properties length
24, 0, 0, 2, 88, // will delay interval (24)
0, 3, // Will Topic - MSB+LSB
'l', 'w', 't',
0, 8, // Will Message MSB+LSB
'n', 'o', 't', 'a', 'g', 'a', 'i', 'n',
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Connect,
Remaining: 42,
},
Connect: ConnectParams{
ProtocolName: []byte("MQTT"),
Clean: true,
Keepalive: 30,
ClientIdentifier: "zen",
WillFlag: true,
WillTopic: "lwt",
WillPayload: []byte("notagain"),
WillQos: 1,
WillProperties: Properties{
WillDelayInterval: uint32(600),
},
},
Properties: Properties{
SessionExpiryInterval: uint32(120),
SessionExpiryIntervalFlag: true,
MaximumPacketSize: uint32(32000),
},
},
},
{
Case: TConnectUserPass,
Desc: "mqtt 3.1.1, username, password",
RawBytes: []byte{
Connect << 4, 28, // Fixed header
0, 4, // Protocol Name - MSB+LSB
'M', 'Q', 'T', 'T', // Protocol Name
4, // Protocol Version
194, // Packet Flags
0, 20, // Keepalive
0, 3, // Client ID - MSB+LSB
'z', 'e', 'n', // Client ID "zen"
0, 5, // Username MSB+LSB
'm', 'o', 'c', 'h', 'i',
0, 4, // Password MSB+LSB
',', '.', '/', ';',
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Connect,
Remaining: 28,
},
ProtocolVersion: 4,
Connect: ConnectParams{
ProtocolName: []byte("MQTT"),
Clean: true,
Keepalive: 20,
ClientIdentifier: "zen",
UsernameFlag: true,
PasswordFlag: true,
Username: []byte("mochi"),
Password: []byte(",./;"),
},
},
},
{
Case: TConnectUserPassLWT,
Desc: "mqtt 3.1.1, username, password, lwt",
Primary: true,
RawBytes: []byte{
Connect << 4, 44, // Fixed header
0, 4, // Protocol Name - MSB+LSB
'M', 'Q', 'T', 'T', // Protocol Name
4, // Protocol Version
206, // Packet Flags
0, 120, // Keepalive
0, 3, // Client ID - MSB+LSB
'z', 'e', 'n', // Client ID "zen"
0, 3, // Will Topic - MSB+LSB
'l', 'w', 't',
0, 9, // Will Message MSB+LSB
'n', 'o', 't', ' ', 'a', 'g', 'a', 'i', 'n',
0, 5, // Username MSB+LSB
'm', 'o', 'c', 'h', 'i',
0, 4, // Password MSB+LSB
',', '.', '/', ';',
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Connect,
Remaining: 44,
},
ProtocolVersion: 4,
Connect: ConnectParams{
ProtocolName: []byte("MQTT"),
Clean: true,
Keepalive: 120,
ClientIdentifier: "zen",
UsernameFlag: true,
PasswordFlag: true,
Username: []byte("mochi"),
Password: []byte(",./;"),
WillFlag: true,
WillTopic: "lwt",
WillPayload: []byte("not again"),
WillQos: 1,
},
},
},
// Fail States
{
Case: TConnectMalProtocolName,
Desc: "malformed protocol name",
Group: "decode",
FailFirst: ErrMalformedProtocolName,
RawBytes: []byte{
Connect << 4, 0, // Fixed header
0, 7, // Protocol Name - MSB+LSB
'M', 'Q', 'I', 's', 'd', // Protocol Name
},
},
{
Case: TConnectMalProtocolVersion,
Desc: "malformed protocol version",
Group: "decode",
FailFirst: ErrMalformedProtocolVersion,
RawBytes: []byte{
Connect << 4, 0, // Fixed header
0, 4, // Protocol Name - MSB+LSB
'M', 'Q', 'T', 'T', // Protocol Name
},
},
{
Case: TConnectMalFlags,
Desc: "malformed flags",
Group: "decode",
FailFirst: ErrMalformedFlags,
RawBytes: []byte{
Connect << 4, 0, // Fixed header
0, 4, // Protocol Name - MSB+LSB
'M', 'Q', 'T', 'T', // Protocol Name
4, // Protocol Version
},
},
{
Case: TConnectMalKeepalive,
Desc: "malformed keepalive",
Group: "decode",
FailFirst: ErrMalformedKeepalive,
RawBytes: []byte{
Connect << 4, 0, // Fixed header
0, 4, // Protocol Name - MSB+LSB
'M', 'Q', 'T', 'T', // Protocol Name
4, // Protocol Version
0, // Flags
},
},
{
Case: TConnectMalClientID,
Desc: "malformed client id",
Group: "decode",
FailFirst: ErrClientIdentifierNotValid,
RawBytes: []byte{
Connect << 4, 0, // Fixed header
0, 4, // Protocol Name - MSB+LSB
'M', 'Q', 'T', 'T', // Protocol Name
4, // Protocol Version
0, // Flags
0, 20, // Keepalive
0, 3, // Client ID - MSB+LSB
'z', 'e', // Client ID "zen"
},
},
{
Case: TConnectMalWillTopic,
Desc: "malformed will topic",
Group: "decode",
FailFirst: ErrMalformedWillTopic,
RawBytes: []byte{
Connect << 4, 0, // Fixed header
0, 4, // Protocol Name - MSB+LSB
'M', 'Q', 'T', 'T', // Protocol Name
4, // Protocol Version
14, // Flags
0, 20, // Keepalive
0, 3, // Client ID - MSB+LSB
'z', 'e', 'n', // Client ID "zen"
0, 6, // Will Topic - MSB+LSB
'l',
},
},
{
Case: TConnectMalWillFlag,
Desc: "malformed will flag",
Group: "decode",
FailFirst: ErrMalformedWillPayload,
RawBytes: []byte{
Connect << 4, 0, // Fixed header
0, 4, // Protocol Name - MSB+LSB
'M', 'Q', 'T', 'T', // Protocol Name
4, // Protocol Version
14, // Flags
0, 20, // Keepalive
0, 3, // Client ID - MSB+LSB
'z', 'e', 'n', // Client ID "zen"
0, 3, // Will Topic - MSB+LSB
'l', 'w', 't',
0, 9, // Will Message MSB+LSB
'n', 'o', 't', ' ', 'a',
},
},
{
Case: TConnectMalUsername,
Desc: "malformed username",
Group: "decode",
FailFirst: ErrMalformedUsername,
RawBytes: []byte{
Connect << 4, 0, // Fixed header
0, 4, // Protocol Name - MSB+LSB
'M', 'Q', 'T', 'T', // Protocol Name
4, // Protocol Version
206, // Flags
0, 20, // Keepalive
0, 3, // Client ID - MSB+LSB
'z', 'e', 'n', // Client ID "zen"
0, 3, // Will Topic - MSB+LSB
'l', 'w', 't',
0, 9, // Will Message MSB+LSB
'n', 'o', 't', ' ', 'a', 'g', 'a', 'i', 'n',
0, 5, // Username MSB+LSB
'm', 'o', 'c',
},
},
{
Case: TConnectMalPassword,
Desc: "malformed password",
Group: "decode",
FailFirst: ErrMalformedPassword,
RawBytes: []byte{
Connect << 4, 0, // Fixed header
0, 4, // Protocol Name - MSB+LSB
'M', 'Q', 'T', 'T', // Protocol Name
4, // Protocol Version
206, // Flags
0, 20, // Keepalive
0, 3, // Client ID - MSB+LSB
'z', 'e', 'n', // Client ID "zen"
0, 3, // Will Topic - MSB+LSB
'l', 'w', 't',
0, 9, // Will Message MSB+LSB
'n', 'o', 't', ' ', 'a', 'g', 'a', 'i', 'n',
0, 5, // Username MSB+LSB
'm', 'o', 'c', 'h', 'i',
0, 4, // Password MSB+LSB
',', '.',
},
},
{
Case: TConnectMalFixedHeader,
Desc: "malformed fixedheader oversize",
Group: "decode",
FailFirst: ErrMalformedProtocolName, // packet test doesn't test fixedheader oversize
RawBytes: []byte{
Connect << 4, 255, 255, 255, 255, 255, // Fixed header
},
},
{
Case: TConnectMalReservedBit,
Desc: "reserved bit not 0",
Group: "nodecode",
FailFirst: ErrProtocolViolation,
RawBytes: []byte{
Connect << 4, 15, // Fixed header
0, 4, // Protocol Name - MSB+LSB
'M', 'Q', 'T', 'T', // Protocol Name
4, // Protocol Version
1, // Packet Flags
0, 45, // Keepalive
0, 3, // Client ID - MSB+LSB
'z', 'e', 'n',
},
},
{
Case: TConnectMalProperties,
Desc: "malformed properties",
Group: "decode",
FailFirst: ErrMalformedProperties,
RawBytes: []byte{
Connect << 4, 47, // Fixed header
0, 4, // Protocol Name - MSB+LSB
'M', 'Q', 'T', 'T', // Protocol Name
5, // Protocol Version
14, // Packet Flags
0, 30, // Keepalive
10,
},
Packet: &Packet{
ProtocolVersion: 5,
},
},
{
Case: TConnectMalWillProperties,
Desc: "malformed will properties",
Group: "decode",
FailFirst: ErrMalformedWillProperties,
RawBytes: []byte{
Connect << 4, 47, // Fixed header
0, 4, // Protocol Name - MSB+LSB
'M', 'Q', 'T', 'T', // Protocol Name
5, // Protocol Version
14, // Packet Flags
0, 30, // Keepalive
10, // length
17, 0, 0, 0, 120, // Session Expiry Interval (17)
39, 0, 0, 125, 0, // Maximum Packet Size (39)
0, 3, // Client ID - MSB+LSB
'z', 'e', 'n', // Client ID "zen"
5, // will properties length
},
Packet: &Packet{
ProtocolVersion: 5,
},
},
// Validation Tests
{
Case: TConnectInvalidProtocolName,
Desc: "invalid protocol name",
Group: "validate",
Expect: ErrProtocolViolationProtocolName,
Packet: &Packet{
FixedHeader: FixedHeader{Type: Connect},
Connect: ConnectParams{
ProtocolName: []byte("stuff"),
},
},
},
{
Case: TConnectInvalidProtocolVersion,
Desc: "invalid protocol version",
Group: "validate",
Expect: ErrProtocolViolationProtocolVersion,
Packet: &Packet{
FixedHeader: FixedHeader{Type: Connect},
ProtocolVersion: 2,
Connect: ConnectParams{
ProtocolName: []byte("MQTT"),
},
},
},
{
Case: TConnectInvalidProtocolVersion2,
Desc: "invalid protocol version",
Group: "validate",
Expect: ErrProtocolViolationProtocolVersion,
Packet: &Packet{
FixedHeader: FixedHeader{Type: Connect},
ProtocolVersion: 2,
Connect: ConnectParams{
ProtocolName: []byte("MQIsdp"),
},
},
},
{
Case: TConnectInvalidReservedBit,
Desc: "reserved bit not 0",
Group: "validate",
Expect: ErrProtocolViolationReservedBit,
Packet: &Packet{
FixedHeader: FixedHeader{Type: Connect},
ProtocolVersion: 4,
Connect: ConnectParams{
ProtocolName: []byte("MQTT"),
},
ReservedBit: 1,
},
},
{
Case: TConnectInvalidClientIDTooLong,
Desc: "client id too long",
Group: "validate",
Expect: ErrClientIdentifierNotValid,
Packet: &Packet{
FixedHeader: FixedHeader{Type: Connect},
ProtocolVersion: 4,
Connect: ConnectParams{
ProtocolName: []byte("MQTT"),
ClientIdentifier: func() string {
return string(make([]byte, 65536))
}(),
},
},
},
{
Case: TConnectInvalidFlagNoUsername,
Desc: "has username flag but no username",
Group: "validate",
Expect: ErrProtocolViolationFlagNoUsername,
Packet: &Packet{
FixedHeader: FixedHeader{Type: Connect},
ProtocolVersion: 4,
Connect: ConnectParams{
ProtocolName: []byte("MQTT"),
UsernameFlag: true,
},
},
},
{
Case: TConnectInvalidUsernameNoFlag,
Desc: "has username but no flag",
Group: "validate",
Expect: ErrProtocolViolationUsernameNoFlag,
Packet: &Packet{
FixedHeader: FixedHeader{Type: Connect},
ProtocolVersion: 4,
Connect: ConnectParams{
ProtocolName: []byte("MQTT"),
Username: []byte("username"),
},
},
},
{
Case: TConnectInvalidFlagNoPassword,
Desc: "has password flag but no password",
Group: "validate",
Expect: ErrProtocolViolationFlagNoPassword,
Packet: &Packet{
FixedHeader: FixedHeader{Type: Connect},
ProtocolVersion: 4,
Connect: ConnectParams{
ProtocolName: []byte("MQTT"),
PasswordFlag: true,
},
},
},
{
Case: TConnectInvalidPasswordNoFlag,
Desc: "has password flag but no password",
Group: "validate",
Expect: ErrProtocolViolationPasswordNoFlag,
Packet: &Packet{
FixedHeader: FixedHeader{Type: Connect},
ProtocolVersion: 4,
Connect: ConnectParams{
ProtocolName: []byte("MQTT"),
Password: []byte("password"),
},
},
},
{
Case: TConnectInvalidUsernameTooLong,
Desc: "username too long",
Group: "validate",
Expect: ErrProtocolViolationUsernameTooLong,
Packet: &Packet{
FixedHeader: FixedHeader{Type: Connect},
ProtocolVersion: 4,
Connect: ConnectParams{
ProtocolName: []byte("MQTT"),
UsernameFlag: true,
Username: func() []byte {
return make([]byte, 65536)
}(),
},
},
},
{
Case: TConnectInvalidPasswordTooLong,
Desc: "password too long",
Group: "validate",
Expect: ErrProtocolViolationPasswordTooLong,
Packet: &Packet{
FixedHeader: FixedHeader{Type: Connect},
ProtocolVersion: 4,
Connect: ConnectParams{
ProtocolName: []byte("MQTT"),
UsernameFlag: true,
Username: []byte{},
PasswordFlag: true,
Password: func() []byte {
return make([]byte, 65536)
}(),
},
},
},
{
Case: TConnectInvalidWillFlagNoPayload,
Desc: "will flag no payload",
Group: "validate",
Expect: ErrProtocolViolationWillFlagNoPayload,
Packet: &Packet{
FixedHeader: FixedHeader{Type: Connect},
ProtocolVersion: 4,
Connect: ConnectParams{
ProtocolName: []byte("MQTT"),
WillFlag: true,
},
},
},
{
Case: TConnectInvalidWillFlagQosOutOfRange,
Desc: "will flag no payload",
Group: "validate",
Expect: ErrProtocolViolationQosOutOfRange,
Packet: &Packet{
FixedHeader: FixedHeader{Type: Connect},
ProtocolVersion: 4,
Connect: ConnectParams{
ProtocolName: []byte("MQTT"),
WillFlag: true,
WillTopic: "a/b/c",
WillPayload: []byte{'b'},
WillQos: 4,
},
},
},
{
Case: TConnectInvalidWillSurplusRetain,
Desc: "no will flag surplus retain",
Group: "validate",
Expect: ErrProtocolViolationWillFlagSurplusRetain,
Packet: &Packet{
FixedHeader: FixedHeader{Type: Connect},
ProtocolVersion: 4,
Connect: ConnectParams{
ProtocolName: []byte("MQTT"),
WillRetain: true,
},
},
},
// Spec Tests
{
Case: TConnectSpecInvalidUTF8D800,
Desc: "invalid utf8 string (a) - code point U+D800",
Group: "decode",
FailFirst: ErrClientIdentifierNotValid,
RawBytes: []byte{
Connect << 4, 0, // Fixed header
0, 4, // Protocol Name - MSB+LSB
'M', 'Q', 'T', 'T', // Protocol Name
4, // Protocol Version
0, // Flags
0, 20, // Keepalive
0, 4, // Client ID - MSB+LSB
'e', 0xed, 0xa0, 0x80, // Client id bearing U+D800
},
},
{
Case: TConnectSpecInvalidUTF8DFFF,
Desc: "invalid utf8 string (b) - code point U+DFFF",
Group: "decode",
FailFirst: ErrClientIdentifierNotValid,
RawBytes: []byte{
Connect << 4, 0, // Fixed header
0, 4, // Protocol Name - MSB+LSB
'M', 'Q', 'T', 'T', // Protocol Name
4, // Protocol Version
0, // Flags
0, 20, // Keepalive
0, 4, // Client ID - MSB+LSB
'e', 0xed, 0xa3, 0xbf, // Client id bearing U+D8FF
},
},
{
Case: TConnectSpecInvalidUTF80000,
Desc: "invalid utf8 string (c) - code point U+0000",
Group: "decode",
FailFirst: ErrClientIdentifierNotValid,
RawBytes: []byte{
Connect << 4, 0, // Fixed header
0, 4, // Protocol Name - MSB+LSB
'M', 'Q', 'T', 'T', // Protocol Name
4, // Protocol Version
0, // Flags
0, 20, // Keepalive
0, 3, // Client ID - MSB+LSB
'e', 0xc0, 0x80, // Client id bearing U+0000
},
},
{
Case: TConnectSpecInvalidUTF8NoSkip,
Desc: "utf8 string must not skip or strip code point U+FEFF",
//Group: "decode",
//FailFirst: ErrMalformedClientID,
RawBytes: []byte{
Connect << 4, 18, // Fixed header
0, 4, // Protocol Name - MSB+LSB
'M', 'Q', 'T', 'T', // Protocol Name
4, // Protocol Version
0, // Flags
0, 20, // Keepalive
0, 6, // Client ID - MSB+LSB
'e', 'b', 0xEF, 0xBB, 0xBF, 'd', // Client id bearing U+FEFF
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Connect,
Remaining: 16,
},
ProtocolVersion: 4,
Connect: ConnectParams{
ProtocolName: []byte("MQTT"),
Keepalive: 20,
ClientIdentifier: string([]byte{'e', 'b', 0xEF, 0xBB, 0xBF, 'd'}),
},
},
},
},
Connack: {
{
Case: TConnackAcceptedNoSession,
Desc: "accepted, no session",
Primary: true,
RawBytes: []byte{
Connack << 4, 2, // fixed header
0, // No existing session
CodeSuccess.Code,
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Connack,
Remaining: 2,
},
SessionPresent: false,
ReasonCode: CodeSuccess.Code,
},
},
{
Case: TConnackAcceptedSessionExists,
Desc: "accepted, session exists",
Primary: true,
RawBytes: []byte{
Connack << 4, 2, // fixed header
1, // Session present
CodeSuccess.Code,
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Connack,
Remaining: 2,
},
SessionPresent: true,
ReasonCode: CodeSuccess.Code,
},
},
{
Case: TConnackAcceptedAdjustedExpiryInterval,
Desc: "accepted, no session, adjusted expiry interval mqtt5",
Primary: true,
RawBytes: []byte{
Connack << 4, 11, // fixed header
0, // Session present
CodeSuccess.Code,
8, // length
17, 0, 0, 0, 120, // Session Expiry Interval (17)
19, 0, 10, // Server Keep Alive (19)
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Connack,
Remaining: 11,
},
ReasonCode: CodeSuccess.Code,
Properties: Properties{
SessionExpiryInterval: uint32(120),
SessionExpiryIntervalFlag: true,
ServerKeepAlive: uint16(10),
ServerKeepAliveFlag: true,
},
},
},
{
Case: TConnackAcceptedMqtt5,
Desc: "accepted no session mqtt5",
Primary: true,
RawBytes: []byte{
Connack << 4, 124, // fixed header
0, // No existing session
CodeSuccess.Code,
// Properties
121, // length
17, 0, 0, 0, 120, // Session Expiry Interval (17)
18, 0, 8, 'm', 'o', 'c', 'h', 'i', '-', 'v', '5', // Assigned Client ID (18)
19, 0, 20, // Server Keep Alive (19)
21, 0, 5, 'S', 'H', 'A', '-', '1', // Authentication Method (21)
22, 0, 9, 'a', 'u', 't', 'h', '-', 'd', 'a', 't', 'a', // Authentication Data (22)
26, 0, 8, 'r', 'e', 's', 'p', 'o', 'n', 's', 'e', // Response Info (26)
28, 0, 7, 'm', 'o', 'c', 'h', 'i', '-', '2', // Server Reference (28)
31, 0, 6, 'r', 'e', 'a', 's', 'o', 'n', // Reason String (31)
33, 1, 244, // Receive Maximum (33)
34, 3, 231, // Topic Alias Maximum (34)
36, 1, // Maximum Qos (36)
37, 1, // Retain Available (37)
38, // User Properties (38)
0, 5, 'h', 'e', 'l', 'l', 'o',
0, 6, 228, 184, 150, 231, 149, 140,
38, // User Properties (38)
0, 4, 'k', 'e', 'y', '2',
0, 6, 'v', 'a', 'l', 'u', 'e', '2',
39, 0, 0, 125, 0, // Maximum Packet Size (39)
40, 1, // Wildcard Subscriptions Available (40)
41, 1, // Subscription ID Available (41)
42, 1, // Shared Subscriptions Available (42)
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Connack,
Remaining: 124,
},
SessionPresent: false,
ReasonCode: CodeSuccess.Code,
Properties: Properties{
SessionExpiryInterval: uint32(120),
SessionExpiryIntervalFlag: true,
AssignedClientID: "mochi-v5",
ServerKeepAlive: uint16(20),
ServerKeepAliveFlag: true,
AuthenticationMethod: "SHA-1",
AuthenticationData: []byte("auth-data"),
ResponseInfo: "response",
ServerReference: "mochi-2",
ReasonString: "reason",
ReceiveMaximum: uint16(500),
TopicAliasMaximum: uint16(999),
MaximumQos: byte(1),
MaximumQosFlag: true,
RetainAvailable: byte(1),
RetainAvailableFlag: true,
User: []UserProperty{
{
Key: "hello",
Val: "世界",
},
{
Key: "key2",
Val: "value2",
},
},
MaximumPacketSize: uint32(32000),
WildcardSubAvailable: byte(1),
WildcardSubAvailableFlag: true,
SubIDAvailable: byte(1),
SubIDAvailableFlag: true,
SharedSubAvailable: byte(1),
SharedSubAvailableFlag: true,
},
},
},
{
Case: TConnackMinMqtt5,
Desc: "accepted min properties mqtt5",
Primary: true,
RawBytes: []byte{
Connack << 4, 16, // fixed header
1, // existing session
CodeSuccess.Code,
13, // Properties length
18, 0, 5, 'm', 'o', 'c', 'h', 'i', // Assigned Client ID (18)
19, 0, 20, // Server Keep Alive (19)
36, 1, // Maximum Qos (36)
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Connack,
Remaining: 16,
},
SessionPresent: true,
ReasonCode: CodeSuccess.Code,
Properties: Properties{
ServerKeepAlive: uint16(20),
ServerKeepAliveFlag: true,
AssignedClientID: "mochi",
MaximumQos: byte(1),
MaximumQosFlag: true,
},
},
},
{
Case: TConnackMinCleanMqtt5,
Desc: "accepted min properties mqtt5b",
Primary: true,
RawBytes: []byte{
Connack << 4, 6, // fixed header
0, // existing session
CodeSuccess.Code,
3, // Properties length
19, 0, 10, // server keepalive
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Connack,
Remaining: 16,
},
SessionPresent: false,
ReasonCode: CodeSuccess.Code,
Properties: Properties{
ServerKeepAlive: uint16(10),
ServerKeepAliveFlag: true,
},
},
},
{
Case: TConnackInvalidMinMqtt5,
Desc: "failure min properties mqtt5",
Primary: true,
RawBytes: append([]byte{
Connack << 4, 26, // fixed header
0, // No existing session
ErrUnspecifiedError.Code,
// Properties
23, // length
19, 0, 20, // Server Keep Alive (19)
31, 0, 17, // Reason String (31)
}, []byte(ErrUnspecifiedError.Reason)...),
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Connack,
Remaining: 25,
},
SessionPresent: false,
ReasonCode: ErrUnspecifiedError.Code,
Properties: Properties{
ServerKeepAlive: uint16(20),
ServerKeepAliveFlag: true,
ReasonString: ErrUnspecifiedError.Reason,
},
},
},
{
Case: TConnackProtocolViolationNoSession,
Desc: "miscellaneous protocol violation",
RawBytes: []byte{
Connack << 4, 2, // fixed header
0, // Session present
ErrProtocolViolation.Code,
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Connack,
Remaining: 2,
},
ReasonCode: ErrProtocolViolation.Code,
},
},
{
Case: TConnackBadProtocolVersion,
Desc: "bad protocol version",
RawBytes: []byte{
Connack << 4, 2, // fixed header
1, // Session present
ErrProtocolViolationProtocolVersion.Code,
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Connack,
Remaining: 2,
},
SessionPresent: true,
ReasonCode: ErrProtocolViolationProtocolVersion.Code,
},
},
{
Case: TConnackBadClientID,
Desc: "bad client id",
RawBytes: []byte{
Connack << 4, 2, // fixed header
1, // Session present
ErrClientIdentifierNotValid.Code,
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Connack,
Remaining: 2,
},
SessionPresent: true,
ReasonCode: ErrClientIdentifierNotValid.Code,
},
},
{
Case: TConnackServerUnavailable,
Desc: "server unavailable",
RawBytes: []byte{
Connack << 4, 2, // fixed header
1, // Session present
ErrServerUnavailable.Code,
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Connack,
Remaining: 2,
},
SessionPresent: true,
ReasonCode: ErrServerUnavailable.Code,
},
},
{
Case: TConnackBadUsernamePassword,
Desc: "bad username or password",
RawBytes: []byte{
Connack << 4, 2, // fixed header
1, // Session present
ErrBadUsernameOrPassword.Code,
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Connack,
Remaining: 2,
},
SessionPresent: true,
ReasonCode: ErrBadUsernameOrPassword.Code,
},
},
{
Case: TConnackBadUsernamePasswordNoSession,
Desc: "bad username or password no session",
RawBytes: []byte{
Connack << 4, 2, // fixed header
0, // No session present
Err3NotAuthorized.Code, // use v3 remapping
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Connack,
Remaining: 2,
},
ReasonCode: Err3NotAuthorized.Code,
},
},
{
Case: TConnackMqtt5BadUsernamePasswordNoSession,
Desc: "mqtt5 bad username or password no session",
RawBytes: []byte{
Connack << 4, 3, // fixed header
0, // No session present
ErrBadUsernameOrPassword.Code,
0,
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Connack,
Remaining: 2,
},
ReasonCode: ErrBadUsernameOrPassword.Code,
},
},
{
Case: TConnackNotAuthorised,
Desc: "not authorised",
RawBytes: []byte{
Connack << 4, 2, // fixed header
1, // Session present
ErrNotAuthorized.Code,
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Connack,
Remaining: 2,
},
SessionPresent: true,
ReasonCode: ErrNotAuthorized.Code,
},
},
{
Case: TConnackDropProperties,
Desc: "drop oversize properties",
Group: "encode",
RawBytes: []byte{
Connack << 4, 40, // fixed header
0, // No existing session
CodeSuccess.Code,
19, // length
28, 0, 7, 'm', 'o', 'c', 'h', 'i', '-', '2', // Server Reference (28)
31, 0, 6, 'r', 'e', 'a', 's', 'o', 'n', // Reason String (31)
38, // User Properties (38)
0, 5, 'h', 'e', 'l', 'l', 'o',
0, 6, 228, 184, 150, 231, 149, 140,
},
ActualBytes: []byte{
Connack << 4, 13, // fixed header
0, // No existing session
CodeSuccess.Code,
10, // length
28, 0, 7, 'm', 'o', 'c', 'h', 'i', '-', '2', // Server Reference (28)
},
Packet: &Packet{
Mods: Mods{
MaxSize: 5,
},
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Connack,
Remaining: 40,
},
ReasonCode: CodeSuccess.Code,
Properties: Properties{
ReasonString: "reason",
ServerReference: "mochi-2",
User: []UserProperty{
{
Key: "hello",
Val: "世界",
},
},
},
},
},
{
Case: TConnackDropPropertiesPartial,
Desc: "drop oversize properties partial",
Group: "encode",
RawBytes: []byte{
Connack << 4, 40, // fixed header
0, // No existing session
CodeSuccess.Code,
19, // length
28, 0, 7, 'm', 'o', 'c', 'h', 'i', '-', '2', // Server Reference (28)
31, 0, 6, 'r', 'e', 'a', 's', 'o', 'n', // Reason String (31)
38, // User Properties (38)
0, 5, 'h', 'e', 'l', 'l', 'o',
0, 6, 228, 184, 150, 231, 149, 140,
},
ActualBytes: []byte{
Connack << 4, 22, // fixed header
0, // No existing session
CodeSuccess.Code,
19, // length
28, 0, 7, 'm', 'o', 'c', 'h', 'i', '-', '2', // Server Reference (28)
31, 0, 6, 'r', 'e', 'a', 's', 'o', 'n', // Reason String (31)
},
Packet: &Packet{
Mods: Mods{
MaxSize: 18,
},
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Connack,
Remaining: 40,
},
ReasonCode: CodeSuccess.Code,
Properties: Properties{
ReasonString: "reason",
ServerReference: "mochi-2",
User: []UserProperty{
{
Key: "hello",
Val: "世界",
},
},
},
},
},
// Fail States
{
Case: TConnackMalSessionPresent,
Desc: "malformed session present",
Group: "decode",
FailFirst: ErrMalformedSessionPresent,
RawBytes: []byte{
Connect << 4, 2, // Fixed header
},
},
{
Case: TConnackMalReturnCode,
Desc: "malformed bad return Code",
Group: "decode",
//Primary: true,
FailFirst: ErrMalformedReasonCode,
RawBytes: []byte{
Connect << 4, 2, // Fixed header
0,
},
},
{
Case: TConnackMalProperties,
Desc: "malformed properties",
Group: "decode",
FailFirst: ErrMalformedProperties,
RawBytes: []byte{
Connack << 4, 40, // fixed header
0, // No existing session
CodeSuccess.Code,
19, // length
},
Packet: &Packet{
ProtocolVersion: 5,
},
},
},
Publish: {
{
Case: TPublishNoPayload,
Desc: "no payload",
Primary: true,
RawBytes: []byte{
Publish << 4, 7, // Fixed header
0, 5, // Topic Name - LSB+MSB
'a', '/', 'b', '/', 'c', // Topic Name
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Publish,
Remaining: 7,
},
TopicName: "a/b/c",
Payload: []byte{},
},
},
{
Case: TPublishBasic,
Desc: "basic",
Primary: true,
RawBytes: []byte{
Publish << 4, 18, // Fixed header
0, 5, // Topic Name - LSB+MSB
'a', '/', 'b', '/', 'c', // Topic Name
'h', 'e', 'l', 'l', 'o', ' ', 'm', 'o', 'c', 'h', 'i', // Payload
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Publish,
Remaining: 18,
},
TopicName: "a/b/c",
Payload: []byte("hello mochi"),
},
},
{
Case: TPublishMqtt5,
Desc: "mqtt v5",
Primary: true,
RawBytes: []byte{
Publish << 4, 77, // Fixed header
0, 5, // Topic Name - LSB+MSB
'a', '/', 'b', '/', 'c', // Topic Name
58, // length
1, 1, // Payload Format (1)
2, 0, 0, 0, 2, // Message Expiry (2)
3, 0, 10, 't', 'e', 'x', 't', '/', 'p', 'l', 'a', 'i', 'n', // Content Type (3)
8, 0, 5, 'a', '/', 'b', '/', 'c', // Response Topic (8)
9, 0, 4, 'd', 'a', 't', 'a', // Correlations Data (9)
11, 202, 212, 19, // Subscription Identifier (11)
35, 0, 3, // Topic Alias (35)
38, // User Properties (38)
0, 5, 'h', 'e', 'l', 'l', 'o',
0, 6, 228, 184, 150, 231, 149, 140,
'h', 'e', 'l', 'l', 'o', ' ', 'm', 'o', 'c', 'h', 'i', // Payload
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Publish,
Remaining: 77,
},
TopicName: "a/b/c",
Properties: Properties{
PayloadFormat: byte(1), // UTF-8 Format
PayloadFormatFlag: true,
MessageExpiryInterval: uint32(2),
ContentType: "text/plain",
ResponseTopic: "a/b/c",
CorrelationData: []byte("data"),
SubscriptionIdentifier: []int{322122},
TopicAlias: uint16(3),
TopicAliasFlag: true,
User: []UserProperty{
{
Key: "hello",
Val: "世界",
},
},
},
Payload: []byte("hello mochi"),
},
},
{
Case: TPublishBasicTopicAliasOnly,
Desc: "mqtt v5 topic alias only",
Primary: true,
RawBytes: []byte{
Publish << 4, 17, // Fixed header
0, 0, // Topic Name - LSB+MSB
3, // length
35, 0, 1, // Topic Alias (35)
'h', 'e', 'l', 'l', 'o', ' ', 'm', 'o', 'c', 'h', 'i', // Payload
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Publish,
Remaining: 17,
},
Properties: Properties{
TopicAlias: 1,
TopicAliasFlag: true,
},
Payload: []byte("hello mochi"),
},
},
{
Case: TPublishBasicMqtt5,
Desc: "mqtt basic v5",
Primary: true,
RawBytes: []byte{
Publish << 4, 22, // Fixed header
0, 5, // Topic Name - LSB+MSB
'a', '/', 'b', '/', 'c', // Topic Name
3, // length
35, 0, 1, // Topic Alias (35)
'h', 'e', 'l', 'l', 'o', ' ', 'm', 'o', 'c', 'h', 'i', // Payload
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Publish,
Remaining: 22,
},
TopicName: "a/b/c",
Properties: Properties{
TopicAlias: uint16(1),
TopicAliasFlag: true,
},
Payload: []byte("hello mochi"),
},
},
{
Case: TPublishQos1,
Desc: "qos:1, packet id",
Primary: true,
RawBytes: []byte{
Publish<<4 | 1<<1, 20, // Fixed header
0, 5, // Topic Name - LSB+MSB
'a', '/', 'b', '/', 'c', // Topic Name
0, 7, // Packet ID - LSB+MSB
'h', 'e', 'l', 'l', 'o', ' ', 'm', 'o', 'c', 'h', 'i', // Payload
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Publish,
Qos: 1,
Remaining: 20,
},
TopicName: "a/b/c",
Payload: []byte("hello mochi"),
PacketID: 7,
},
},
{
Case: TPublishQos1Dup,
Desc: "qos:1, dup:true, packet id",
Primary: true,
RawBytes: []byte{
Publish<<4 | 2 | 8, 20, // Fixed header
0, 5, // Topic Name - LSB+MSB
'a', '/', 'b', '/', 'c', // Topic Name
0, 7, // Packet ID - LSB+MSB
'h', 'e', 'l', 'l', 'o', ' ', 'm', 'o', 'c', 'h', 'i', // Payload
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Publish,
Qos: 1,
Remaining: 20,
Dup: true,
},
TopicName: "a/b/c",
Payload: []byte("hello mochi"),
PacketID: 7,
},
},
{
Case: TPublishQos1NoPayload,
Desc: "qos:1, packet id, no payload",
Primary: true,
RawBytes: []byte{
Publish<<4 | 2, 9, // Fixed header
0, 5, // Topic Name - LSB+MSB
'y', '/', 'u', '/', 'i', // Topic Name
0, 7, // Packet ID - LSB+MSB
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Publish,
Qos: 1,
Remaining: 9,
},
TopicName: "y/u/i",
PacketID: 7,
Payload: []byte{},
},
},
{
Case: TPublishQos2,
Desc: "qos:2, packet id",
Primary: true,
RawBytes: []byte{
Publish<<4 | 2<<1, 14, // Fixed header
0, 5, // Topic Name - LSB+MSB
'a', '/', 'b', '/', 'c', // Topic Name
0, 7, // Packet ID - LSB+MSB
'h', 'e', 'l', 'l', 'o', // Payload
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Publish,
Qos: 2,
Remaining: 14,
},
TopicName: "a/b/c",
Payload: []byte("hello"),
PacketID: 7,
},
},
{
Case: TPublishQos2Mqtt5,
Desc: "mqtt v5",
Primary: true,
RawBytes: []byte{
Publish<<4 | 2<<1, 37, // Fixed header
0, 5, // Topic Name - LSB+MSB
'a', '/', 'b', '/', 'c', // Topic Name
0, 7, // Packet ID - LSB+MSB
// Properties
16, // length
38, // User Properties (38)
0, 5, 'h', 'e', 'l', 'l', 'o',
0, 6, 228, 184, 150, 231, 149, 140,
'h', 'e', 'l', 'l', 'o', ' ', 'm', 'o', 'c', 'h', 'i', // Payload
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Publish,
Remaining: 37,
Qos: 2,
},
PacketID: 7,
TopicName: "a/b/c",
Properties: Properties{
User: []UserProperty{
{
Key: "hello",
Val: "世界",
},
},
},
Payload: []byte("hello mochi"),
},
},
{
Case: TPublishSubscriberIdentifier,
Desc: "subscription identifiers",
Primary: true,
RawBytes: []byte{
Publish << 4, 23, // Fixed header
0, 5, // Topic Name - LSB+MSB
'a', '/', 'b', '/', 'c', // Topic Name
4, // properties length
11, 2, // Subscription Identifier (11)
11, 3, // Subscription Identifier (11)
'h', 'e', 'l', 'l', 'o', ' ', 'm', 'o', 'c', 'h', 'i', // Payload
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Publish,
Remaining: 23,
},
TopicName: "a/b/c",
Properties: Properties{
SubscriptionIdentifier: []int{2, 3},
},
Payload: []byte("hello mochi"),
},
},
{
Case: TPublishQos2Upgraded,
Desc: "qos:2, upgraded from publish to qos2 sub",
Primary: true,
RawBytes: []byte{
Publish<<4 | 2<<1, 20, // Fixed header
0, 5, // Topic Name - LSB+MSB
'a', '/', 'b', '/', 'c', // Topic Name
0, 1, // Packet ID - LSB+MSB
'h', 'e', 'l', 'l', 'o', ' ', 'm', 'o', 'c', 'h', 'i', // Payload
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Publish,
Qos: 2,
Remaining: 18,
},
TopicName: "a/b/c",
Payload: []byte("hello mochi"),
PacketID: 1,
},
},
{
Case: TPublishRetain,
Desc: "retain",
RawBytes: []byte{
Publish<<4 | 1<<0, 18, // Fixed header
0, 5, // Topic Name - LSB+MSB
'a', '/', 'b', '/', 'c', // Topic Name
'h', 'e', 'l', 'l', 'o', ' ', 'm', 'o', 'c', 'h', 'i', // Payload
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Publish,
Retain: true,
},
TopicName: "a/b/c",
Payload: []byte("hello mochi"),
},
},
{
Case: TPublishRetainMqtt5,
Desc: "retain mqtt5",
RawBytes: []byte{
Publish<<4 | 1<<0, 19, // Fixed header
0, 5, // Topic Name - LSB+MSB
'a', '/', 'b', '/', 'c', // Topic Name
0, // properties length
'h', 'e', 'l', 'l', 'o', ' ', 'm', 'o', 'c', 'h', 'i', // Payload
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Publish,
Retain: true,
Remaining: 19,
},
TopicName: "a/b/c",
Properties: Properties{},
Payload: []byte("hello mochi"),
},
},
{
Case: TPublishDup,
Desc: "dup",
RawBytes: []byte{
Publish<<4 | 8, 10, // Fixed header
0, 3, // Topic Name - LSB+MSB
'a', '/', 'b', // Topic Name
'h', 'e', 'l', 'l', 'o', // Payload
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Publish,
Dup: true,
},
TopicName: "a/b",
Payload: []byte("hello"),
},
},
// Fail States
{
Case: TPublishMalTopicName,
Desc: "malformed topic name",
Group: "decode",
FailFirst: ErrMalformedTopic,
RawBytes: []byte{
Publish << 4, 7, // Fixed header
0, 5, // Topic Name - LSB+MSB
'a', '/',
0, 11, // Packet ID - LSB+MSB
},
},
{
Case: TPublishMalPacketID,
Desc: "malformed packet id",
Group: "decode",
FailFirst: ErrMalformedPacketID,
RawBytes: []byte{
Publish<<4 | 2, 7, // Fixed header
0, 5, // Topic Name - LSB+MSB
'x', '/', 'y', '/', 'z', // Topic Name
0, // Packet ID - LSB+MSB
},
},
{
Case: TPublishMalProperties,
Desc: "malformed properties",
Group: "decode",
FailFirst: ErrMalformedProperties,
RawBytes: []byte{
Publish << 4, 35, // Fixed header
0, 5, // Topic Name - LSB+MSB
'a', '/', 'b', '/', 'c', // Topic Name
16,
},
Packet: &Packet{
ProtocolVersion: 5,
},
},
// Copy tests
{
Case: TPublishCopyBasic,
Desc: "basic copyable",
Group: "copy",
RawBytes: []byte{
Publish << 4, 18, // Fixed header
0, 5, // Topic Name - LSB+MSB
'z', '/', 'e', '/', 'n', // Topic Name
'm', 'o', 'c', 'h', 'i', ' ', 'm', 'o', 'c', 'h', 'i', // Payload
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Publish,
Dup: true,
Retain: true,
Qos: 1,
},
TopicName: "z/e/n",
Payload: []byte("mochi mochi"),
},
},
// Spec tests
{
Case: TPublishSpecQos0NoPacketID,
Desc: "packet id must be 0 if qos is 0 (a)",
Group: "encode",
// this version tests for correct byte array mutuation.
// this does not check if -incoming- Packets are parsed as correct,
// it is impossible for the parser to determine if the payload start is incorrect.
RawBytes: []byte{
Publish << 4, 12, // Fixed header
0, 5, // Topic Name - LSB+MSB
'a', '/', 'b', '/', 'c', // Topic Name
0, 3, // Packet ID - LSB+MSB
'h', 'e', 'l', 'l', 'o', // Payload
},
ActualBytes: []byte{
Publish << 4, 12, // Fixed header
0, 5, // Topic Name - LSB+MSB
'a', '/', 'b', '/', 'c', // Topic Name
// Packet ID is removed.
'h', 'e', 'l', 'l', 'o', // Payload
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Publish,
Remaining: 12,
},
TopicName: "a/b/c",
Payload: []byte("hello"),
},
},
{
Case: TPublishSpecQosMustPacketID,
Desc: "no packet id with qos > 0",
Group: "encode",
Expect: ErrProtocolViolationNoPacketID,
RawBytes: []byte{
Publish<<4 | 2, 14, // Fixed header
0, 5, // Topic Name - LSB+MSB
'a', '/', 'b', '/', 'c', // Topic Name
0, 0, // Packet ID - LSB+MSB
'h', 'e', 'l', 'l', 'o', // Payload
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Publish,
Qos: 1,
},
TopicName: "a/b/c",
Payload: []byte("hello"),
PacketID: 0,
},
},
{
Case: TPublishDropOversize,
Desc: "drop oversized publish packet",
Group: "encode",
FailFirst: ErrPacketTooLarge,
RawBytes: []byte{
Publish << 4, 10, // Fixed header
0, 3, // Topic Name - LSB+MSB
'a', '/', 'b', // Topic Name
'h', 'e', 'l', 'l', 'o', // Payload
},
Packet: &Packet{
Mods: Mods{
MaxSize: 2,
},
FixedHeader: FixedHeader{
Type: Publish,
},
TopicName: "a/b",
Payload: []byte("hello"),
},
},
// Validation Tests
{
Case: TPublishInvalidQos0NoPacketID,
Desc: "packet id must be 0 if qos is 0 (b)",
Group: "validate",
Expect: ErrProtocolViolationSurplusPacketID,
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Publish,
Remaining: 12,
Qos: 0,
},
TopicName: "a/b/c",
Payload: []byte("hello"),
PacketID: 3,
},
},
{
Case: TPublishInvalidQosMustPacketID,
Desc: "no packet id with qos > 0",
Group: "validate",
Expect: ErrProtocolViolationNoPacketID,
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Publish,
Qos: 1,
},
PacketID: 0,
},
},
{
Case: TPublishInvalidSurplusSubID,
Desc: "surplus subscription identifier",
Group: "validate",
Expect: ErrProtocolViolationSurplusSubID,
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Publish,
},
Properties: Properties{
SubscriptionIdentifier: []int{1},
},
TopicName: "a/b",
},
},
{
Case: TPublishInvalidSurplusWildcard,
Desc: "topic contains wildcards",
Group: "validate",
Expect: ErrProtocolViolationSurplusWildcard,
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Publish,
},
TopicName: "a/+",
},
},
{
Case: TPublishInvalidSurplusWildcard2,
Desc: "topic contains wildcards 2",
Group: "validate",
Expect: ErrProtocolViolationSurplusWildcard,
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Publish,
},
TopicName: "a/#",
},
},
{
Case: TPublishInvalidNoTopic,
Desc: "no topic or alias specified",
Group: "validate",
Expect: ErrProtocolViolationNoTopic,
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Publish,
},
},
},
{
Case: TPublishInvalidExcessTopicAlias,
Desc: "topic alias over maximum",
Group: "validate",
Expect: ErrTopicAliasInvalid,
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Publish,
},
Properties: Properties{
TopicAlias: 1025,
},
TopicName: "a/b",
},
},
{
Case: TPublishInvalidTopicAlias,
Desc: "topic alias flag and no alias",
Group: "validate",
Expect: ErrTopicAliasInvalid,
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Publish,
},
Properties: Properties{
TopicAliasFlag: true,
TopicAlias: 0,
},
TopicName: "a/b/",
},
},
{
Case: TPublishSpecDenySysTopic,
Desc: "deny publishing to $SYS topics",
Group: "validate",
Expect: CodeSuccess,
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Publish,
},
TopicName: "$SYS/any",
Payload: []byte("y"),
},
RawBytes: []byte{
Publish << 4, 11, // Fixed header
0, 5, // Topic Name - LSB+MSB
'$', 'S', 'Y', 'S', '/', 'a', 'n', 'y', // Topic Name
'y', // Payload
},
},
},
Puback: {
{
Case: TPuback,
Desc: "puback",
Primary: true,
RawBytes: []byte{
Puback << 4, 2, // Fixed header
0, 7, // Packet ID - LSB+MSB
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Puback,
Remaining: 2,
},
PacketID: 7,
},
},
{
Case: TPubackMqtt5,
Desc: "mqtt5",
Primary: true,
RawBytes: []byte{
Puback << 4, 20, // Fixed header
0, 7, // Packet ID - LSB+MSB
CodeGrantedQos0.Code, // Reason Code
16, // Properties Length
// 31, 0, 6, 'r', 'e', 'a', 's', 'o', 'n', // Reason String (31)
38, // User Properties (38)
0, 5, 'h', 'e', 'l', 'l', 'o',
0, 6, 228, 184, 150, 231, 149, 140,
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Puback,
Remaining: 20,
},
PacketID: 7,
ReasonCode: CodeGrantedQos0.Code,
Properties: Properties{
User: []UserProperty{
{
Key: "hello",
Val: "世界",
},
},
},
},
},
// Fail states
{
Case: TPubackMalPacketID,
Desc: "malformed packet id",
Group: "decode",
FailFirst: ErrMalformedPacketID,
RawBytes: []byte{
Puback << 4, 2, // Fixed header
0, // Packet ID - LSB+MSB
},
},
{
Case: TPubackMalProperties,
Desc: "malformed properties",
Group: "decode",
FailFirst: ErrMalformedProperties,
RawBytes: []byte{
Puback << 4, 20, // Fixed header
0, 7, // Packet ID - LSB+MSB
CodeGrantedQos0.Code, // Reason Code
16,
},
Packet: &Packet{
ProtocolVersion: 5,
},
},
},
Pubrec: {
{
Case: TPubrec,
Desc: "pubrec",
Primary: true,
RawBytes: []byte{
Pubrec << 4, 2, // Fixed header
0, 7, // Packet ID - LSB+MSB
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Pubrec,
Remaining: 2,
},
PacketID: 7,
},
},
{
Case: TPubrecMqtt5,
Desc: "pubrec mqtt5",
Primary: true,
RawBytes: []byte{
Pubrec << 4, 20, // Fixed header
0, 7, // Packet ID - LSB+MSB
CodeSuccess.Code, // Reason Code
16, // Properties Length
38, // User Properties (38)
0, 5, 'h', 'e', 'l', 'l', 'o',
0, 6, 228, 184, 150, 231, 149, 140,
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Pubrec,
Remaining: 20,
},
PacketID: 7,
ReasonCode: CodeSuccess.Code,
Properties: Properties{
User: []UserProperty{
{
Key: "hello",
Val: "世界",
},
},
},
},
},
{
Case: TPubrecMqtt5IDInUse,
Desc: "packet id in use mqtt5",
Primary: true,
RawBytes: []byte{
Pubrec << 4, 31, // Fixed header
0, 7, // Packet ID - LSB+MSB
ErrPacketIdentifierInUse.Code, // Reason Code
27, // Properties Length
31, 0, 24, 'p', 'a', 'c', 'k', 'e', 't',
' ', 'i', 'd', 'e', 'n', 't', 'i', 'f', 'i', 'e', 'r',
' ', 'i', 'n',
' ', 'u', 's', 'e', // Reason String (31)
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Pubrec,
Remaining: 31,
},
PacketID: 7,
ReasonCode: ErrPacketIdentifierInUse.Code,
Properties: Properties{
ReasonString: ErrPacketIdentifierInUse.Reason,
},
},
},
{
Case: TPubrecMalReasonCode,
Desc: "malformed reason code",
Group: "decode",
FailFirst: ErrMalformedReasonCode,
RawBytes: []byte{
Pubrec << 4, 31, // Fixed header
0, 7, // Packet ID - LSB+MSB
},
Packet: &Packet{
ProtocolVersion: 5,
},
},
// Validation
{
Case: TPubrecInvalidReason,
Desc: "invalid reason code",
Group: "validate",
FailFirst: ErrProtocolViolationInvalidReason,
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Pubrec,
},
PacketID: 7,
ReasonCode: ErrConnectionRateExceeded.Code,
},
},
// Fail states
{
Case: TPubrecMalPacketID,
Desc: "malformed packet id",
Group: "decode",
FailFirst: ErrMalformedPacketID,
RawBytes: []byte{
Pubrec << 4, 2, // Fixed header
0, // Packet ID - LSB+MSB
},
},
{
Case: TPubrecMalProperties,
Desc: "malformed properties",
Group: "decode",
FailFirst: ErrMalformedProperties,
RawBytes: []byte{
Pubrec << 4, 31, // Fixed header
0, 7, // Packet ID - LSB+MSB
ErrPacketIdentifierInUse.Code, // Reason Code
27,
},
Packet: &Packet{
ProtocolVersion: 5,
},
},
},
Pubrel: {
{
Case: TPubrel,
Desc: "pubrel",
Primary: true,
RawBytes: []byte{
Pubrel<<4 | 1<<1, 2, // Fixed header
0, 7, // Packet ID - LSB+MSB
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Pubrel,
Remaining: 2,
Qos: 1,
},
PacketID: 7,
},
},
{
Case: TPubrelMqtt5,
Desc: "pubrel mqtt5",
Primary: true,
RawBytes: []byte{
Pubrel<<4 | 1<<1, 20, // Fixed header
0, 7, // Packet ID - LSB+MSB
CodeSuccess.Code, // Reason Code
16, // Properties Length
38, // User Properties (38)
0, 5, 'h', 'e', 'l', 'l', 'o',
0, 6, 228, 184, 150, 231, 149, 140,
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Pubrel,
Remaining: 20,
Qos: 1,
},
PacketID: 7,
ReasonCode: CodeSuccess.Code,
Properties: Properties{
User: []UserProperty{
{
Key: "hello",
Val: "世界",
},
},
},
},
},
{
Case: TPubrelMqtt5AckNoPacket,
Desc: "mqtt5 no packet id ack",
Primary: true,
RawBytes: append([]byte{
Pubrel<<4 | 1<<1, 34, // Fixed header
0, 7, // Packet ID - LSB+MSB
ErrPacketIdentifierNotFound.Code, // Reason Code
30, // Properties Length
31, 0, byte(len(ErrPacketIdentifierNotFound.Reason)),
}, []byte(ErrPacketIdentifierNotFound.Reason)...),
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Pubrel,
Remaining: 34,
Qos: 1,
},
PacketID: 7,
ReasonCode: ErrPacketIdentifierNotFound.Code,
Properties: Properties{
ReasonString: ErrPacketIdentifierNotFound.Reason,
},
},
},
// Validation
{
Case: TPubrelInvalidReason,
Desc: "invalid reason code",
Group: "validate",
FailFirst: ErrProtocolViolationInvalidReason,
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Pubrel,
},
PacketID: 7,
ReasonCode: ErrConnectionRateExceeded.Code,
},
},
// Fail states
{
Case: TPubrelMalPacketID,
Desc: "malformed packet id",
Group: "decode",
FailFirst: ErrMalformedPacketID,
RawBytes: []byte{
Pubrel << 4, 2, // Fixed header
0, // Packet ID - LSB+MSB
},
},
{
Case: TPubrelMalProperties,
Desc: "malformed properties",
Group: "decode",
FailFirst: ErrMalformedProperties,
RawBytes: []byte{
Pubrel<<4 | 1<<1, 20, // Fixed header
0, 7, // Packet ID - LSB+MSB
CodeSuccess.Code, // Reason Code
16,
},
Packet: &Packet{
ProtocolVersion: 5,
},
},
},
Pubcomp: {
{
Case: TPubcomp,
Desc: "pubcomp",
Primary: true,
RawBytes: []byte{
Pubcomp << 4, 2, // Fixed header
0, 7, // Packet ID - LSB+MSB
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Pubcomp,
Remaining: 2,
},
PacketID: 7,
},
},
{
Case: TPubcompMqtt5,
Desc: "mqtt5",
Primary: true,
RawBytes: []byte{
Pubcomp << 4, 20, // Fixed header
0, 7, // Packet ID - LSB+MSB
CodeSuccess.Code, // Reason Code
16, // Properties Length
38, // User Properties (38)
0, 5, 'h', 'e', 'l', 'l', 'o',
0, 6, 228, 184, 150, 231, 149, 140,
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Pubcomp,
Remaining: 20,
},
PacketID: 7,
ReasonCode: CodeSuccess.Code,
Properties: Properties{
User: []UserProperty{
{
Key: "hello",
Val: "世界",
},
},
},
},
},
{
Case: TPubcompMqtt5AckNoPacket,
Desc: "mqtt5 no packet id ack",
Primary: true,
RawBytes: append([]byte{
Pubcomp << 4, 34, // Fixed header
0, 7, // Packet ID - LSB+MSB
ErrPacketIdentifierNotFound.Code, // Reason Code
30, // Properties Length
31, 0, byte(len(ErrPacketIdentifierNotFound.Reason)),
}, []byte(ErrPacketIdentifierNotFound.Reason)...),
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Pubcomp,
Remaining: 34,
},
PacketID: 7,
ReasonCode: ErrPacketIdentifierNotFound.Code,
Properties: Properties{
ReasonString: ErrPacketIdentifierNotFound.Reason,
},
},
},
// Validation
{
Case: TPubcompInvalidReason,
Desc: "invalid reason code",
Group: "validate",
FailFirst: ErrProtocolViolationInvalidReason,
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Pubcomp,
},
ReasonCode: ErrConnectionRateExceeded.Code,
},
},
// Fail states
{
Case: TPubcompMalPacketID,
Desc: "malformed packet id",
Group: "decode",
FailFirst: ErrMalformedPacketID,
RawBytes: []byte{
Pubcomp << 4, 2, // Fixed header
0, // Packet ID - LSB+MSB
},
},
{
Case: TPubcompMalProperties,
Desc: "malformed properties",
Group: "decode",
FailFirst: ErrMalformedProperties,
RawBytes: []byte{
Pubcomp << 4, 34, // Fixed header
0, 7, // Packet ID - LSB+MSB
ErrPacketIdentifierNotFound.Code, // Reason Code
22,
},
Packet: &Packet{
ProtocolVersion: 5,
},
},
},
Subscribe: {
{
Case: TSubscribe,
Desc: "subscribe",
Primary: true,
RawBytes: []byte{
Subscribe<<4 | 1<<1, 10, // Fixed header
0, 15, // Packet ID - LSB+MSB
0, 5, // Topic Name - LSB+MSB
'a', '/', 'b', '/', 'c', // Topic Name
0, // QoS
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Subscribe,
Remaining: 10,
Qos: 1,
},
PacketID: 15,
Filters: Subscriptions{
{Filter: "a/b/c"},
},
},
},
{
Case: TSubscribeMany,
Desc: "many",
Primary: true,
RawBytes: []byte{
Subscribe<<4 | 1<<1, 30, // Fixed header
0, 15, // Packet ID - LSB+MSB
0, 3, // Topic Name - LSB+MSB
'a', '/', 'b', // Topic Name
0, // QoS
0, 11, // Topic Name - LSB+MSB
'd', '/', 'e', '/', 'f', '/', 'g', '/', 'h', '/', 'i', // Topic Name
1, // QoS
0, 5, // Topic Name - LSB+MSB
'x', '/', 'y', '/', 'z', // Topic Name
2, // QoS
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Subscribe,
Remaining: 30,
Qos: 1,
},
PacketID: 15,
Filters: Subscriptions{
{Filter: "a/b", Qos: 0},
{Filter: "d/e/f/g/h/i", Qos: 1},
{Filter: "x/y/z", Qos: 2},
},
},
},
{
Case: TSubscribeMqtt5,
Desc: "mqtt5",
Primary: true,
RawBytes: []byte{
Subscribe<<4 | 1<<1, 31, // Fixed header
0, 15, // Packet ID - LSB+MSB
20,
11, 202, 212, 19, // Subscription Identifier (11)
38, // User Properties (38)
0, 5, 'h', 'e', 'l', 'l', 'o',
0, 6, 228, 184, 150, 231, 149, 140,
0, 5, 'a', '/', 'b', '/', 'c', // Topic Name
46, // subscription options
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Subscribe,
Remaining: 31,
Qos: 1,
},
PacketID: 15,
Filters: Subscriptions{
{
Filter: "a/b/c",
Qos: 2,
NoLocal: true,
RetainAsPublished: true,
RetainHandling: 2,
Identifier: 322122,
},
},
Properties: Properties{
SubscriptionIdentifier: []int{322122},
User: []UserProperty{
{
Key: "hello",
Val: "世界",
},
},
},
},
},
{
Case: TSubscribeRetainHandling1,
Desc: "retain handling 1",
Primary: true,
RawBytes: []byte{
Subscribe<<4 | 1<<1, 11, // Fixed header
0, 15, // Packet ID - LSB+MSB
0, // no properties
0, 5, // Topic Name - LSB+MSB
'a', '/', 'b', '/', 'c', // Topic Name
0 | 1<<4, // subscription options
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Subscribe,
Remaining: 11,
Qos: 1,
},
PacketID: 15,
Filters: Subscriptions{
{
Filter: "a/b/c",
RetainHandling: 1,
},
},
},
},
{
Case: TSubscribeRetainHandling2,
Desc: "retain handling 2",
Primary: true,
RawBytes: []byte{
Subscribe<<4 | 1<<1, 11, // Fixed header
0, 15, // Packet ID - LSB+MSB
0, // no properties
0, 5, // Topic Name - LSB+MSB
'a', '/', 'b', '/', 'c', // Topic Name
0 | 2<<4, // subscription options
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Subscribe,
Remaining: 11,
Qos: 1,
},
PacketID: 15,
Filters: Subscriptions{
{
Filter: "a/b/c",
RetainHandling: 2,
},
},
},
},
{
Case: TSubscribeRetainAsPublished,
Desc: "retain as published",
Primary: true,
RawBytes: []byte{
Subscribe<<4 | 1<<1, 11, // Fixed header
0, 15, // Packet ID - LSB+MSB
0, // no properties
0, 5, // Topic Name - LSB+MSB
'a', '/', 'b', '/', 'c', // Topic Name
0 | 1<<3, // subscription options
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Subscribe,
Remaining: 11,
Qos: 1,
},
PacketID: 15,
Filters: Subscriptions{
{
Filter: "a/b/c",
RetainAsPublished: true,
},
},
},
},
{
Case: TSubscribeInvalidFilter,
Desc: "invalid filter",
Group: "reference",
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Subscribe,
Qos: 1,
},
PacketID: 15,
Filters: Subscriptions{
{Filter: "$SHARE/#", Identifier: 5},
},
},
},
{
Case: TSubscribeInvalidSharedNoLocal,
Desc: "shared and no local",
Group: "reference",
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Subscribe,
Qos: 1,
},
PacketID: 15,
Filters: Subscriptions{
{Filter: "$SHARE/tmp/a/b/c", Identifier: 5, NoLocal: true},
},
},
},
// Fail states
{
Case: TSubscribeMalPacketID,
Desc: "malformed packet id",
Group: "decode",
FailFirst: ErrMalformedPacketID,
RawBytes: []byte{
Subscribe<<4 | 1<<1, 2, // Fixed header
0, // Packet ID - LSB+MSB
},
},
{
Case: TSubscribeMalTopic,
Desc: "malformed topic",
Group: "decode",
FailFirst: ErrMalformedTopic,
RawBytes: []byte{
Subscribe<<4 | 1<<1, 2, // Fixed header
0, 21, // Packet ID - LSB+MSB
0, 3, // Topic Name - LSB+MSB
'a', '/',
},
},
{
Case: TSubscribeMalQos,
Desc: "malformed subscribe - qos",
Group: "decode",
FailFirst: ErrMalformedQos,
RawBytes: []byte{
Subscribe<<4 | 1<<1, 2, // Fixed header
0, 22, // Packet ID - LSB+MSB
0, 3, // Topic Name - LSB+MSB
'j', '/', 'b', // Topic Name
},
},
{
Case: TSubscribeMalQosRange,
Desc: "malformed qos out of range",
Group: "decode",
FailFirst: ErrProtocolViolationQosOutOfRange,
RawBytes: []byte{
Subscribe<<4 | 1<<1, 2, // Fixed header
0, 22, // Packet ID - LSB+MSB
0, 3, // Topic Name - LSB+MSB
'c', '/', 'd', // Topic Name
5, // QoS
},
},
{
Case: TSubscribeMalProperties,
Desc: "malformed properties",
Group: "decode",
FailFirst: ErrMalformedProperties,
RawBytes: []byte{
Subscribe << 4, 11, // Fixed header
0, 15, // Packet ID - LSB+MSB
4,
},
Packet: &Packet{
ProtocolVersion: 5,
},
},
// Validation
{
Case: TSubscribeInvalidQosMustPacketID,
Desc: "no packet id with qos > 0",
Group: "validate",
Expect: ErrProtocolViolationNoPacketID,
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Subscribe,
Qos: 1,
},
PacketID: 0,
Filters: Subscriptions{
{Filter: "a/b"},
},
},
},
{
Case: TSubscribeInvalidNoFilters,
Desc: "no filters",
Group: "validate",
Expect: ErrProtocolViolationNoFilters,
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Subscribe,
Qos: 1,
},
PacketID: 2,
},
},
{
Case: TSubscribeInvalidIdentifierOversize,
Desc: "oversize identifier",
Group: "validate",
Expect: ErrProtocolViolationOversizeSubID,
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Subscribe,
Qos: 1,
},
PacketID: 2,
Filters: Subscriptions{
{Filter: "a/b", Identifier: 5},
{Filter: "d/f", Identifier: 268435456},
},
},
},
// Spec tests
{
Case: TSubscribeSpecQosMustPacketID,
Desc: "no packet id with qos > 0",
Group: "encode",
Expect: ErrProtocolViolationNoPacketID,
RawBytes: []byte{
Subscribe<<4 | 1<<1, 10, // Fixed header
0, 0, // Packet ID - LSB+MSB
0, 5, // Topic Name - LSB+MSB
'a', '/', 'b', '/', 'c', // Topic Name
1, // QoS
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Subscribe,
Qos: 1,
Remaining: 10,
},
Filters: Subscriptions{
{Filter: "a/b/c", Qos: 1},
},
PacketID: 0,
},
},
},
Suback: {
{
Case: TSuback,
Desc: "suback",
Primary: true,
RawBytes: []byte{
Suback << 4, 3, // Fixed header
0, 15, // Packet ID - LSB+MSB
0, // Return Code QoS 0
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Suback,
Remaining: 3,
},
PacketID: 15,
ReasonCodes: []byte{0},
},
},
{
Case: TSubackMany,
Desc: "many",
Primary: true,
RawBytes: []byte{
Suback << 4, 6, // Fixed header
0, 15, // Packet ID - LSB+MSB
0, // Return Code QoS 0
1, // Return Code QoS 1
2, // Return Code QoS 2
0x80, // Return Code fail
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Suback,
Remaining: 6,
},
PacketID: 15,
ReasonCodes: []byte{0, 1, 2, 0x80},
},
},
{
Case: TSubackDeny,
Desc: "deny mqtt5",
Primary: true,
RawBytes: []byte{
Suback << 4, 4, // Fixed header
0, 15, // Packet ID - LSB+MSB
0, // no properties
ErrNotAuthorized.Code, // Return Code QoS 0
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Suback,
Remaining: 4,
},
PacketID: 15,
ReasonCodes: []byte{ErrNotAuthorized.Code},
},
},
{
Case: TSubackUnspecifiedError,
Desc: "unspecified error",
Primary: true,
RawBytes: []byte{
Suback << 4, 3, // Fixed header
0, 15, // Packet ID - LSB+MSB
ErrUnspecifiedError.Code, // Return Code QoS 0
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Suback,
Remaining: 3,
},
PacketID: 15,
ReasonCodes: []byte{ErrUnspecifiedError.Code},
},
},
{
Case: TSubackUnspecifiedErrorMqtt5,
Desc: "unspecified error mqtt5",
Primary: true,
RawBytes: []byte{
Suback << 4, 4, // Fixed header
0, 15, // Packet ID - LSB+MSB
0, // no properties
ErrUnspecifiedError.Code, // Return Code QoS 0
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Suback,
Remaining: 4,
},
PacketID: 15,
ReasonCodes: []byte{ErrUnspecifiedError.Code},
},
},
{
Case: TSubackMqtt5,
Desc: "mqtt5",
Primary: true,
RawBytes: []byte{
Suback << 4, 20, // Fixed header
0, 15, // Packet ID
16, // Properties Length
38, // User Properties (38)
0, 5, 'h', 'e', 'l', 'l', 'o',
0, 6, 228, 184, 150, 231, 149, 140,
CodeGrantedQos2.Code, // Return Code QoS 0
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Suback,
Remaining: 20,
},
PacketID: 15,
ReasonCodes: []byte{CodeGrantedQos2.Code},
Properties: Properties{
User: []UserProperty{
{
Key: "hello",
Val: "世界",
},
},
},
},
},
{
Case: TSubackPacketIDInUse,
Desc: "packet id in use",
Primary: true,
RawBytes: []byte{
Suback << 4, 47, // Fixed header
0, 15, // Packet ID
43, // Properties Length
31, 0, 24, 'p', 'a', 'c', 'k', 'e', 't',
' ', 'i', 'd', 'e', 'n', 't', 'i', 'f', 'i', 'e', 'r',
' ', 'i', 'n',
' ', 'u', 's', 'e', // Reason String (31)
38, // User Properties (38)
0, 5, 'h', 'e', 'l', 'l', 'o',
0, 6, 228, 184, 150, 231, 149, 140,
ErrPacketIdentifierInUse.Code,
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Suback,
Remaining: 47,
},
PacketID: 15,
ReasonCodes: []byte{ErrPacketIdentifierInUse.Code},
Properties: Properties{
ReasonString: ErrPacketIdentifierInUse.Reason,
User: []UserProperty{
{
Key: "hello",
Val: "世界",
},
},
},
},
},
// Fail states
{
Case: TSubackMalPacketID,
Desc: "malformed packet id",
Group: "decode",
FailFirst: ErrMalformedPacketID,
RawBytes: []byte{
Suback << 4, 2, // Fixed header
0, // Packet ID - LSB+MSB
},
},
{
Case: TSubackMalProperties,
Desc: "malformed properties",
Group: "decode",
FailFirst: ErrMalformedProperties,
RawBytes: []byte{
Suback << 4, 47,
0, 15,
43,
},
Packet: &Packet{
ProtocolVersion: 5,
},
},
{
Case: TSubackInvalidFilter,
Desc: "malformed packet id",
Group: "reference",
RawBytes: []byte{
Suback << 4, 4,
0, 15,
0, // no properties
ErrTopicFilterInvalid.Code,
},
Packet: &Packet{
ProtocolVersion: 5,
},
},
{
Case: TSubackInvalidSharedNoLocal,
Desc: "invalid shared no local",
Group: "reference",
RawBytes: []byte{
Suback << 4, 4,
0, 15,
0, // no properties
ErrProtocolViolationInvalidSharedNoLocal.Code,
},
Packet: &Packet{
ProtocolVersion: 5,
},
},
},
Unsubscribe: {
{
Case: TUnsubscribe,
Desc: "unsubscribe",
Primary: true,
RawBytes: []byte{
Unsubscribe<<4 | 1<<1, 9, // Fixed header
0, 15, // Packet ID - LSB+MSB
0, 5, // Topic Name - LSB+MSB
'a', '/', 'b', '/', 'c', // Topic Name
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Unsubscribe,
Remaining: 9,
Qos: 1,
},
PacketID: 15,
Filters: Subscriptions{
{Filter: "a/b/c"},
},
},
},
{
Case: TUnsubscribeMany,
Desc: "unsubscribe many",
Primary: true,
RawBytes: []byte{
Unsubscribe<<4 | 1<<1, 27, // Fixed header
0, 35, // Packet ID - LSB+MSB
0, 3, // Topic Name - LSB+MSB
'a', '/', 'b', // Topic Name
0, 11, // Topic Name - LSB+MSB
'd', '/', 'e', '/', 'f', '/', 'g', '/', 'h', '/', 'i', // Topic Name
0, 5, // Topic Name - LSB+MSB
'x', '/', 'y', '/', 'z', // Topic Name
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Unsubscribe,
Remaining: 27,
Qos: 1,
},
PacketID: 35,
Filters: Subscriptions{
{Filter: "a/b"},
{Filter: "d/e/f/g/h/i"},
{Filter: "x/y/z"},
},
},
},
{
Case: TUnsubscribeMqtt5,
Desc: "mqtt5",
Primary: true,
RawBytes: []byte{
Unsubscribe<<4 | 1<<1, 31, // Fixed header
0, 15, // Packet ID - LSB+MSB
16,
38, // User Properties (38)
0, 5, 'h', 'e', 'l', 'l', 'o',
0, 6, 228, 184, 150, 231, 149, 140,
0, 3, // Topic Name - LSB+MSB
'a', '/', 'b',
0, 5, // Topic Name - LSB+MSB
'x', '/', 'y', '/', 'w',
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Unsubscribe,
Remaining: 31,
Qos: 1,
},
PacketID: 15,
Properties: Properties{
User: []UserProperty{
{
Key: "hello",
Val: "世界",
},
},
},
Filters: Subscriptions{
{Filter: "a/b"},
{Filter: "x/y/w"},
},
},
},
// Fail states
{
Case: TUnsubscribeMalPacketID,
Desc: "malformed packet id",
Group: "decode",
FailFirst: ErrMalformedPacketID,
RawBytes: []byte{
Unsubscribe << 4, 2, // Fixed header
0, // Packet ID - LSB+MSB
},
},
{
Case: TUnsubscribeMalTopicName,
Desc: "malformed topic",
Group: "decode",
FailFirst: ErrMalformedTopic,
RawBytes: []byte{
Unsubscribe << 4, 2, // Fixed header
0, 21, // Packet ID - LSB+MSB
0, 3, // Topic Name - LSB+MSB
'a', '/',
},
},
{
Case: TUnsubscribeMalProperties,
Desc: "malformed properties",
Group: "decode",
FailFirst: ErrMalformedProperties,
RawBytes: []byte{
Unsubscribe<<4 | 1<<1, 31, // Fixed header
0, 15, // Packet ID - LSB+MSB
16,
},
Packet: &Packet{
ProtocolVersion: 5,
},
},
{
Case: TUnsubscribeInvalidQosMustPacketID,
Desc: "no packet id with qos > 0",
Group: "validate",
Expect: ErrProtocolViolationNoPacketID,
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Unsubscribe,
Qos: 1,
},
PacketID: 0,
Filters: Subscriptions{
Subscription{Filter: "a/b"},
},
},
},
{
Case: TUnsubscribeInvalidNoFilters,
Desc: "no filters",
Group: "validate",
Expect: ErrProtocolViolationNoFilters,
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Unsubscribe,
Qos: 1,
},
PacketID: 2,
},
},
{
Case: TUnsubscribeSpecQosMustPacketID,
Desc: "no packet id with qos > 0",
Group: "encode",
Expect: ErrProtocolViolationNoPacketID,
RawBytes: []byte{
Unsubscribe<<4 | 1<<1, 9, // Fixed header
0, 0, // Packet ID - LSB+MSB
0, 5, // Topic Name - LSB+MSB
'a', '/', 'b', '/', 'c', // Topic Name
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Unsubscribe,
Qos: 1,
Remaining: 9,
},
PacketID: 0,
Filters: Subscriptions{
{Filter: "a/b/c"},
},
},
},
},
Unsuback: {
{
Case: TUnsuback,
Desc: "unsuback",
Primary: true,
RawBytes: []byte{
Unsuback << 4, 2, // Fixed header
0, 15, // Packet ID - LSB+MSB
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Unsuback,
Remaining: 2,
},
PacketID: 15,
},
},
{
Case: TUnsubackMany,
Desc: "unsuback many",
Primary: true,
RawBytes: []byte{
Unsuback << 4, 5, // Fixed header
0, 15, // Packet ID - LSB+MSB
0,
CodeSuccess.Code, CodeSuccess.Code,
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Unsuback,
Remaining: 5,
},
PacketID: 15,
ReasonCodes: []byte{CodeSuccess.Code, CodeSuccess.Code},
},
},
{
Case: TUnsubackMqtt5,
Desc: "mqtt5",
Primary: true,
RawBytes: []byte{
Unsuback << 4, 21, // Fixed header
0, 15, // Packet ID - LSB+MSB
16, // Properties Length
38, // User Properties (38)
0, 5, 'h', 'e', 'l', 'l', 'o',
0, 6, 228, 184, 150, 231, 149, 140,
CodeSuccess.Code, CodeNoSubscriptionExisted.Code,
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Unsuback,
Remaining: 21,
},
PacketID: 15,
ReasonCodes: []byte{CodeSuccess.Code, CodeNoSubscriptionExisted.Code},
Properties: Properties{
User: []UserProperty{
{
Key: "hello",
Val: "世界",
},
},
},
},
},
{
Case: TUnsubackPacketIDInUse,
Desc: "packet id in use",
Primary: true,
RawBytes: []byte{
Unsuback << 4, 48, // Fixed header
0, 15, // Packet ID - LSB+MSB
43, // Properties Length
31, 0, 24, 'p', 'a', 'c', 'k', 'e', 't',
' ', 'i', 'd', 'e', 'n', 't', 'i', 'f', 'i', 'e', 'r',
' ', 'i', 'n',
' ', 'u', 's', 'e', // Reason String (31)
38, // User Properties (38)
0, 5, 'h', 'e', 'l', 'l', 'o',
0, 6, 228, 184, 150, 231, 149, 140,
ErrPacketIdentifierInUse.Code, ErrPacketIdentifierInUse.Code,
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Unsuback,
Remaining: 48,
},
PacketID: 15,
ReasonCodes: []byte{ErrPacketIdentifierInUse.Code, ErrPacketIdentifierInUse.Code},
Properties: Properties{
ReasonString: ErrPacketIdentifierInUse.Reason,
User: []UserProperty{
{
Key: "hello",
Val: "世界",
},
},
},
},
},
// Fail states
{
Case: TUnsubackMalPacketID,
Desc: "malformed packet id",
Group: "decode",
FailFirst: ErrMalformedPacketID,
RawBytes: []byte{
Unsuback << 4, 2, // Fixed header
0, // Packet ID - LSB+MSB
},
},
{
Case: TUnsubackMalProperties,
Desc: "malformed properties",
Group: "decode",
FailFirst: ErrMalformedProperties,
RawBytes: []byte{
Unsuback << 4, 48, // Fixed header
0, 15, // Packet ID - LSB+MSB
43,
},
Packet: &Packet{
ProtocolVersion: 5,
},
},
},
Pingreq: {
{
Case: TPingreq,
Desc: "ping request",
Primary: true,
RawBytes: []byte{
Pingreq << 4, 0, // fixed header
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Pingreq,
Remaining: 0,
},
},
},
},
Pingresp: {
{
Case: TPingresp,
Desc: "ping response",
Primary: true,
RawBytes: []byte{
Pingresp << 4, 0, // fixed header
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Pingresp,
Remaining: 0,
},
},
},
},
Disconnect: {
{
Case: TDisconnect,
Desc: "disconnect",
Primary: true,
RawBytes: []byte{
Disconnect << 4, 0, // fixed header
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Disconnect,
Remaining: 0,
},
},
},
{
Case: TDisconnectTakeover,
Desc: "takeover",
Primary: true,
RawBytes: append([]byte{
Disconnect << 4, 21, // fixed header
ErrSessionTakenOver.Code, // Reason Code
19, // Properties Length
31, 0, 16, // Reason String (31)
}, []byte(ErrSessionTakenOver.Reason)...),
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Disconnect,
Remaining: 0,
},
ReasonCode: ErrSessionTakenOver.Code,
Properties: Properties{
ReasonString: ErrSessionTakenOver.Reason,
},
},
},
{
Case: TDisconnectShuttingDown,
Desc: "shutting down",
Primary: true,
RawBytes: append([]byte{
Disconnect << 4, 25, // fixed header
ErrServerShuttingDown.Code, // Reason Code
23, // Properties Length
31, 0, 20, // Reason String (31)
}, []byte(ErrServerShuttingDown.Reason)...),
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Disconnect,
Remaining: 0,
},
ReasonCode: ErrServerShuttingDown.Code,
Properties: Properties{
ReasonString: ErrServerShuttingDown.Reason,
},
},
},
{
Case: TDisconnectMqtt5,
Desc: "mqtt5",
Primary: true,
RawBytes: append([]byte{
Disconnect << 4, 22, // fixed header
CodeDisconnect.Code, // Reason Code
20, // Properties Length
17, 0, 0, 0, 120, // Session Expiry Interval (17)
31, 0, 12, // Reason String (31)
}, []byte(CodeDisconnect.Reason)...),
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Disconnect,
Remaining: 22,
},
ReasonCode: CodeDisconnect.Code,
Properties: Properties{
ReasonString: CodeDisconnect.Reason,
SessionExpiryInterval: 120,
SessionExpiryIntervalFlag: true,
},
},
},
{
Case: TDisconnectSecondConnect,
Desc: "second connect packet mqtt5",
RawBytes: append([]byte{
Disconnect << 4, 46, // fixed header
ErrProtocolViolationSecondConnect.Code,
44,
31, 0, 41, // Reason String (31)
}, []byte(ErrProtocolViolationSecondConnect.Reason)...),
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Disconnect,
Remaining: 45,
},
ReasonCode: ErrProtocolViolationSecondConnect.Code,
Properties: Properties{
ReasonString: ErrProtocolViolationSecondConnect.Reason,
},
},
},
{
Case: TDisconnectZeroNonZeroExpiry,
Desc: "zero non zero expiry",
RawBytes: []byte{
Disconnect << 4, 2, // fixed header
ErrProtocolViolationZeroNonZeroExpiry.Code,
0,
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Disconnect,
Remaining: 2,
},
ReasonCode: ErrProtocolViolationZeroNonZeroExpiry.Code,
},
},
{
Case: TDisconnectReceiveMaximum,
Desc: "receive maximum mqtt5",
RawBytes: append([]byte{
Disconnect << 4, 29, // fixed header
ErrReceiveMaximum.Code,
27, // Properties Length
31, 0, 24, // Reason String (31)
}, []byte(ErrReceiveMaximum.Reason)...),
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Disconnect,
Remaining: 29,
},
ReasonCode: ErrReceiveMaximum.Code,
Properties: Properties{
ReasonString: ErrReceiveMaximum.Reason,
},
},
},
{
Case: TDisconnectDropProperties,
Desc: "drop oversize properties partial",
Group: "encode",
RawBytes: []byte{
Disconnect << 4, 39, // fixed header
CodeDisconnect.Code,
19, // length
28, 0, 7, 'm', 'o', 'c', 'h', 'i', '-', '2', // Server Reference (28)
31, 0, 6, 'r', 'e', 'a', 's', 'o', 'n', // Reason String (31)
38, // User Properties (38)
0, 5, 'h', 'e', 'l', 'l', 'o',
0, 6, 228, 184, 150, 231, 149, 140,
},
ActualBytes: []byte{
Disconnect << 4, 12, // fixed header
CodeDisconnect.Code,
10, // length
28, 0, 7, 'm', 'o', 'c', 'h', 'i', '-', '2', // Server Reference (28)
},
Packet: &Packet{
Mods: Mods{
MaxSize: 3,
},
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Disconnect,
Remaining: 40,
},
ReasonCode: CodeSuccess.Code,
Properties: Properties{
ReasonString: "reason",
ServerReference: "mochi-2",
User: []UserProperty{
{
Key: "hello",
Val: "世界",
},
},
},
},
},
// fail states
{
Case: TDisconnectMalProperties,
Desc: "malformed properties",
Group: "decode",
FailFirst: ErrMalformedProperties,
RawBytes: []byte{
Disconnect << 4, 48, // fixed header
CodeDisconnect.Code, // Reason Code
46,
},
Packet: &Packet{
ProtocolVersion: 5,
},
},
{
Case: TDisconnectMalReasonCode,
Desc: "malformed reason code",
Group: "decode",
FailFirst: ErrMalformedReasonCode,
RawBytes: []byte{
Disconnect << 4, 48, // fixed header
},
Packet: &Packet{
ProtocolVersion: 5,
},
},
},
Auth: {
{
Case: TAuth,
Desc: "auth",
Primary: true,
RawBytes: []byte{
Auth << 4, 47,
CodeSuccess.Code, // reason code
45,
21, 0, 5, 'S', 'H', 'A', '-', '1', // Authentication Method (21)
22, 0, 9, 'a', 'u', 't', 'h', '-', 'd', 'a', 't', 'a', // Authentication Data (22)
31, 0, 6, 'r', 'e', 'a', 's', 'o', 'n', // Reason String (31)
38, // User Properties (38)
0, 5, 'h', 'e', 'l', 'l', 'o',
0, 6, 228, 184, 150, 231, 149, 140,
},
Packet: &Packet{
ProtocolVersion: 5,
FixedHeader: FixedHeader{
Type: Auth,
Remaining: 47,
},
ReasonCode: CodeSuccess.Code,
Properties: Properties{
AuthenticationMethod: "SHA-1",
AuthenticationData: []byte("auth-data"),
ReasonString: "reason",
User: []UserProperty{
{
Key: "hello",
Val: "世界",
},
},
},
},
},
{
Case: TAuthMalReasonCode,
Desc: "malformed reason code",
Group: "decode",
FailFirst: ErrMalformedReasonCode,
RawBytes: []byte{
Auth << 4, 47,
},
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Auth,
},
ReasonCode: CodeNoMatchingSubscribers.Code,
},
},
// fail states
{
Case: TAuthMalProperties,
Desc: "malformed properties",
Group: "decode",
FailFirst: ErrMalformedProperties,
RawBytes: []byte{
Auth << 4, 3,
CodeSuccess.Code,
12,
},
Packet: &Packet{
ProtocolVersion: 5,
},
},
// Validation
{
Case: TAuthInvalidReason,
Desc: "invalid reason code",
Group: "validate",
Expect: ErrProtocolViolationInvalidReason,
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Auth,
},
ReasonCode: CodeNoMatchingSubscribers.Code,
},
},
{
Case: TAuthInvalidReason2,
Desc: "invalid reason code",
Group: "validate",
Expect: ErrProtocolViolationInvalidReason,
Packet: &Packet{
FixedHeader: FixedHeader{
Type: Auth,
},
ReasonCode: CodeNoMatchingSubscribers.Code,
},
},
},
}