// SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2022 mochi-mqtt, mochi-co // SPDX-FileContributor: mochi-co package packets import ( "bytes" "testing" "github.com/stretchr/testify/require" ) type fixedHeaderTable struct { desc string rawBytes []byte header FixedHeader packetError bool expect error } var fixedHeaderExpected = []fixedHeaderTable{ { desc: "connect", rawBytes: []byte{Connect << 4, 0x00}, header: FixedHeader{Type: Connect, Dup: false, Qos: 0, Retain: false, Remaining: 0}, }, { desc: "connack", rawBytes: []byte{Connack << 4, 0x00}, header: FixedHeader{Type: Connack, Dup: false, Qos: 0, Retain: false, Remaining: 0}, }, { desc: "publish", rawBytes: []byte{Publish << 4, 0x00}, header: FixedHeader{Type: Publish, Dup: false, Qos: 0, Retain: false, Remaining: 0}, }, { desc: "publish qos 1", rawBytes: []byte{Publish<<4 | 1<<1, 0x00}, header: FixedHeader{Type: Publish, Dup: false, Qos: 1, Retain: false, Remaining: 0}, }, { desc: "publish qos 1 retain", rawBytes: []byte{Publish<<4 | 1<<1 | 1, 0x00}, header: FixedHeader{Type: Publish, Dup: false, Qos: 1, Retain: true, Remaining: 0}, }, { desc: "publish qos 2", rawBytes: []byte{Publish<<4 | 2<<1, 0x00}, header: FixedHeader{Type: Publish, Dup: false, Qos: 2, Retain: false, Remaining: 0}, }, { desc: "publish qos 2 retain", rawBytes: []byte{Publish<<4 | 2<<1 | 1, 0x00}, header: FixedHeader{Type: Publish, Dup: false, Qos: 2, Retain: true, Remaining: 0}, }, { desc: "publish dup qos 0", rawBytes: []byte{Publish<<4 | 1<<3, 0x00}, header: FixedHeader{Type: Publish, Dup: true, Qos: 0, Retain: false, Remaining: 0}, expect: ErrProtocolViolationDupNoQos, }, { desc: "publish dup qos 0 retain", rawBytes: []byte{Publish<<4 | 1<<3 | 1, 0x00}, header: FixedHeader{Type: Publish, Dup: true, Qos: 0, Retain: true, Remaining: 0}, expect: ErrProtocolViolationDupNoQos, }, { desc: "publish dup qos 1 retain", rawBytes: []byte{Publish<<4 | 1<<3 | 1<<1 | 1, 0x00}, header: FixedHeader{Type: Publish, Dup: true, Qos: 1, Retain: true, Remaining: 0}, }, { desc: "publish dup qos 2 retain", rawBytes: []byte{Publish<<4 | 1<<3 | 2<<1 | 1, 0x00}, header: FixedHeader{Type: Publish, Dup: true, Qos: 2, Retain: true, Remaining: 0}, }, { desc: "puback", rawBytes: []byte{Puback << 4, 0x00}, header: FixedHeader{Type: Puback, Dup: false, Qos: 0, Retain: false, Remaining: 0}, }, { desc: "pubrec", rawBytes: []byte{Pubrec << 4, 0x00}, header: FixedHeader{Type: Pubrec, Dup: false, Qos: 0, Retain: false, Remaining: 0}, }, { desc: "pubrel", rawBytes: []byte{Pubrel<<4 | 1<<1, 0x00}, header: FixedHeader{Type: Pubrel, Dup: false, Qos: 1, Retain: false, Remaining: 0}, }, { desc: "pubcomp", rawBytes: []byte{Pubcomp << 4, 0x00}, header: FixedHeader{Type: Pubcomp, Dup: false, Qos: 0, Retain: false, Remaining: 0}, }, { desc: "subscribe", rawBytes: []byte{Subscribe<<4 | 1<<1, 0x00}, header: FixedHeader{Type: Subscribe, Dup: false, Qos: 1, Retain: false, Remaining: 0}, }, { desc: "suback", rawBytes: []byte{Suback << 4, 0x00}, header: FixedHeader{Type: Suback, Dup: false, Qos: 0, Retain: false, Remaining: 0}, }, { desc: "unsubscribe", rawBytes: []byte{Unsubscribe<<4 | 1<<1, 0x00}, header: FixedHeader{Type: Unsubscribe, Dup: false, Qos: 1, Retain: false, Remaining: 0}, }, { desc: "unsuback", rawBytes: []byte{Unsuback << 4, 0x00}, header: FixedHeader{Type: Unsuback, Dup: false, Qos: 0, Retain: false, Remaining: 0}, }, { desc: "pingreq", rawBytes: []byte{Pingreq << 4, 0x00}, header: FixedHeader{Type: Pingreq, Dup: false, Qos: 0, Retain: false, Remaining: 0}, }, { desc: "pingresp", rawBytes: []byte{Pingresp << 4, 0x00}, header: FixedHeader{Type: Pingresp, Dup: false, Qos: 0, Retain: false, Remaining: 0}, }, { desc: "disconnect", rawBytes: []byte{Disconnect << 4, 0x00}, header: FixedHeader{Type: Disconnect, Dup: false, Qos: 0, Retain: false, Remaining: 0}, }, { desc: "auth", rawBytes: []byte{Auth << 4, 0x00}, header: FixedHeader{Type: Auth, Dup: false, Qos: 0, Retain: false, Remaining: 0}, }, // remaining length { desc: "remaining length 10", rawBytes: []byte{Publish << 4, 0x0a}, header: FixedHeader{Type: Publish, Dup: false, Qos: 0, Retain: false, Remaining: 10}, }, { desc: "remaining length 512", rawBytes: []byte{Publish << 4, 0x80, 0x04}, header: FixedHeader{Type: Publish, Dup: false, Qos: 0, Retain: false, Remaining: 512}, }, { desc: "remaining length 978", rawBytes: []byte{Publish << 4, 0xd2, 0x07}, header: FixedHeader{Type: Publish, Dup: false, Qos: 0, Retain: false, Remaining: 978}, }, { desc: "remaining length 20202", rawBytes: []byte{Publish << 4, 0x86, 0x9d, 0x01}, header: FixedHeader{Type: Publish, Dup: false, Qos: 0, Retain: false, Remaining: 20102}, }, { desc: "remaining length oversize", rawBytes: []byte{Publish << 4, 0xd5, 0x86, 0xf9, 0x9e, 0x01}, header: FixedHeader{Type: Publish, Dup: false, Qos: 0, Retain: false, Remaining: 333333333}, packetError: true, }, // Invalid flags for packet { desc: "invalid type dup is true", rawBytes: []byte{Connect<<4 | 1<<3, 0x00}, header: FixedHeader{Type: Connect, Dup: true, Qos: 0, Retain: false, Remaining: 0}, expect: ErrMalformedFlags, }, { desc: "invalid type qos is 1", rawBytes: []byte{Connect<<4 | 1<<1, 0x00}, header: FixedHeader{Type: Connect, Dup: false, Qos: 1, Retain: false, Remaining: 0}, expect: ErrMalformedFlags, }, { desc: "invalid type retain is true", rawBytes: []byte{Connect<<4 | 1, 0x00}, header: FixedHeader{Type: Connect, Dup: false, Qos: 0, Retain: true, Remaining: 0}, expect: ErrMalformedFlags, }, { desc: "invalid publish qos bits 1 + 2 set", rawBytes: []byte{Publish<<4 | 1<<1 | 1<<2, 0x00}, header: FixedHeader{Type: Publish}, expect: ErrProtocolViolationQosOutOfRange, }, { desc: "invalid pubrel bits 3,2,1,0 should be 0,0,1,0", rawBytes: []byte{Pubrel<<4 | 1<<2 | 1<<0, 0x00}, header: FixedHeader{Type: Pubrel, Qos: 1}, expect: ErrMalformedFlags, }, { desc: "invalid subscribe bits 3,2,1,0 should be 0,0,1,0", rawBytes: []byte{Subscribe<<4 | 1<<2, 0x00}, header: FixedHeader{Type: Subscribe, Qos: 1}, expect: ErrMalformedFlags, }, } func TestFixedHeaderEncode(t *testing.T) { for _, wanted := range fixedHeaderExpected { t.Run(wanted.desc, func(t *testing.T) { buf := new(bytes.Buffer) wanted.header.Encode(buf) if wanted.expect == nil { require.Equal(t, len(wanted.rawBytes), len(buf.Bytes())) require.EqualValues(t, wanted.rawBytes, buf.Bytes()) } }) } } func TestFixedHeaderDecode(t *testing.T) { for _, wanted := range fixedHeaderExpected { t.Run(wanted.desc, func(t *testing.T) { fh := new(FixedHeader) err := fh.Decode(wanted.rawBytes[0]) if wanted.expect != nil { require.Equal(t, wanted.expect, err) } else { require.NoError(t, err) require.Equal(t, wanted.header.Type, fh.Type) require.Equal(t, wanted.header.Dup, fh.Dup) require.Equal(t, wanted.header.Qos, fh.Qos) require.Equal(t, wanted.header.Retain, fh.Retain) } }) } }