// 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
	TConnectZeroByteUsername
	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, 15, // Fixed header
				0, 4, // Protocol Name - MSB+LSB
				'M', 'Q', 'T', 'T', // Protocol Name
				4,     // Protocol Version
				0,     // Packet Flags
				0, 60, // 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:            false,
					Keepalive:        60,
					ClientIdentifier: "zen",
				},
			},
		},
		{
			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
				0 | 1<<6 | 1<<7, // 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:            false,
					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,
				},
			},
		},
		{
			Case:  TConnectZeroByteUsername,
			Desc:  "username flag but 0 byte username",
			Group: "decode",
			RawBytes: []byte{
				Connect << 4, 23, // Fixed header
				0, 4, // Protocol Name - MSB+LSB
				'M', 'Q', 'T', 'T', // Protocol Name
				5,     // Protocol Version
				130,   // Packet Flags
				0, 30, // Keepalive
				5,                // length
				17, 0, 0, 0, 120, // Session Expiry Interval (17)
				0, 3, // Client ID - MSB+LSB
				'z', 'e', 'n', // Client ID "zen"
				0, 0, // Username MSB+LSB
			},
			Packet: &Packet{
				FixedHeader: FixedHeader{
					Type:      Connect,
					Remaining: 23,
				},
				ProtocolVersion: 5,
				Connect: ConnectParams{
					ProtocolName:     []byte("MQTT"),
					Clean:            true,
					Keepalive:        30,
					ClientIdentifier: "zen",
					Username:         []byte{},
					UsernameFlag:     true,
				},
				Properties: Properties{
					SessionExpiryInterval:     uint32(120),
					SessionExpiryIntervalFlag: true,
				},
			},
		},

		// 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:      TConnectInvalidFlagNoUsername,
			Desc:      "username flag with no username bytes",
			Group:     "decode",
			FailFirst: ErrProtocolViolationFlagNoUsername,
			RawBytes: []byte{
				Connect << 4, 17, // Fixed header
				0, 4, // Protocol Name - MSB+LSB
				'M', 'Q', 'T', 'T', // Protocol Name
				5,     // Protocol Version
				130,   // Flags
				0, 20, // Keepalive
				0,
				0, 3, // Client ID - MSB+LSB
				'z', 'e', 'n', // Client ID "zen"
			},
		},
		{
			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:   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,
			},
		},
	},
}
