117 lines
3.3 KiB
Go
117 lines
3.3 KiB
Go
|
package packets
|
||
|
|
||
|
import (
|
||
|
"encoding/binary"
|
||
|
"unicode/utf8"
|
||
|
"unsafe"
|
||
|
)
|
||
|
|
||
|
// bytesToString provides a zero-alloc, no-copy byte to string conversion.
|
||
|
// via https://github.com/golang/go/issues/25484#issuecomment-391415660
|
||
|
func bytesToString(bs []byte) string {
|
||
|
return *(*string)(unsafe.Pointer(&bs))
|
||
|
}
|
||
|
|
||
|
// decodeUint16 extracts the value of two bytes from a byte array.
|
||
|
func decodeUint16(buf []byte, offset int) (uint16, int, error) {
|
||
|
if len(buf) < offset+2 {
|
||
|
return 0, 0, ErrOffsetUintOutOfRange
|
||
|
}
|
||
|
|
||
|
return binary.BigEndian.Uint16(buf[offset : offset+2]), offset + 2, nil
|
||
|
}
|
||
|
|
||
|
// decodeString extracts a string from a byte array, beginning at an offset.
|
||
|
func decodeString(buf []byte, offset int) (string, int, error) {
|
||
|
b, n, err := decodeBytes(buf, offset)
|
||
|
if err != nil {
|
||
|
return "", 0, err
|
||
|
}
|
||
|
|
||
|
if !validUTF8(b) {
|
||
|
return "", 0, ErrOffsetStrInvalidUTF8
|
||
|
}
|
||
|
|
||
|
return bytesToString(b), n, nil
|
||
|
}
|
||
|
|
||
|
// decodeBytes extracts a byte array from a byte array, beginning at an offset. Used primarily for message payloads.
|
||
|
func decodeBytes(buf []byte, offset int) ([]byte, int, error) {
|
||
|
length, next, err := decodeUint16(buf, offset)
|
||
|
if err != nil {
|
||
|
return make([]byte, 0, 0), 0, err
|
||
|
}
|
||
|
|
||
|
if next+int(length) > len(buf) {
|
||
|
return make([]byte, 0, 0), 0, ErrOffsetBytesOutOfRange
|
||
|
}
|
||
|
|
||
|
// Note: there is no validUTF8() test for []byte payloads
|
||
|
|
||
|
return buf[next : next+int(length)], next + int(length), nil
|
||
|
}
|
||
|
|
||
|
// decodeByte extracts the value of a byte from a byte array.
|
||
|
func decodeByte(buf []byte, offset int) (byte, int, error) {
|
||
|
if len(buf) <= offset {
|
||
|
return 0, 0, ErrOffsetByteOutOfRange
|
||
|
}
|
||
|
return buf[offset], offset + 1, nil
|
||
|
}
|
||
|
|
||
|
// decodeByteBool extracts the value of a byte from a byte array and returns a bool.
|
||
|
func decodeByteBool(buf []byte, offset int) (bool, int, error) {
|
||
|
if len(buf) <= offset {
|
||
|
return false, 0, ErrOffsetBoolOutOfRange
|
||
|
}
|
||
|
return 1&buf[offset] > 0, offset + 1, nil
|
||
|
}
|
||
|
|
||
|
// encodeBool returns a byte instead of a bool.
|
||
|
func encodeBool(b bool) byte {
|
||
|
if b {
|
||
|
return 1
|
||
|
}
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
// encodeBytes encodes a byte array to a byte array. Used primarily for message payloads.
|
||
|
func encodeBytes(val []byte) []byte {
|
||
|
// In many circumstances the number of bytes being encoded is small.
|
||
|
// Setting the cap to a low amount allows us to account for those without
|
||
|
// triggering allocation growth on append unless we need to.
|
||
|
buf := make([]byte, 2, 32)
|
||
|
binary.BigEndian.PutUint16(buf, uint16(len(val)))
|
||
|
return append(buf, val...)
|
||
|
}
|
||
|
|
||
|
// encodeUint16 encodes a uint16 value to a byte array.
|
||
|
func encodeUint16(val uint16) []byte {
|
||
|
buf := make([]byte, 2)
|
||
|
binary.BigEndian.PutUint16(buf, val)
|
||
|
return buf
|
||
|
}
|
||
|
|
||
|
// encodeString encodes a string to a byte array.
|
||
|
func encodeString(val string) []byte {
|
||
|
// Like encodeBytes, we set the cap to a small number to avoid
|
||
|
// triggering allocation growth on append unless we absolutely need to.
|
||
|
buf := make([]byte, 2, 32)
|
||
|
binary.BigEndian.PutUint16(buf, uint16(len(val)))
|
||
|
return append(buf, []byte(val)...)
|
||
|
}
|
||
|
|
||
|
// validUTF8 checks if the byte array contains valid UTF-8 characters, specifically
|
||
|
// conforming to the MQTT specification requirements.
|
||
|
func validUTF8(b []byte) bool {
|
||
|
// [MQTT-1.4.0-1] The character data in a UTF-8 encoded string MUST be well-formed UTF-8...
|
||
|
if !utf8.Valid(b) {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// [MQTT-1.4.0-2] A UTF-8 encoded string MUST NOT include an encoding of the null character U+0000...
|
||
|
// ...
|
||
|
return true
|
||
|
|
||
|
}
|