package mqtt import ( "bufio" "encoding/binary" "errors" "fmt" "io" ) // MQTT 3.1.1 control packet types (§2.2.1). Only the ones this client needs to // send or recognize are named; anything else on the wire is skipped. const ( pktConnect byte = 1 pktConnack byte = 2 pktPublish byte = 3 pktPuback byte = 4 pktPubrec byte = 5 pktPubrel byte = 6 pktPubcomp byte = 7 pktSubscribe byte = 8 pktSuback byte = 9 pktUnsubscribe byte = 10 pktUnsuback byte = 11 pktPingreq byte = 12 pktPingresp byte = 13 pktDisconnect byte = 14 ) // protocolName / protocolLevel identify MQTT 3.1.1 in the CONNECT packet (§3.1.2). const ( protocolName = "MQTT" protocolLevel = 4 ) // maxRemainingLength is the largest value the four-byte varint can express // (§2.2.3). maxPacketBytes is the much tighter ceiling this client accepts from a // broker: Greencell telemetry frames are a few dozen bytes, so a megabyte is // generous while still bounding a hostile or broken peer. const ( maxRemainingLength = 268435455 maxPacketBytes = 1 << 20 ) // errPacketTooLarge is returned when a broker announces a packet beyond // maxPacketBytes; the connection is unusable afterwards because the stream can no // longer be framed, so the read loop treats it as fatal. var errPacketTooLarge = errors.New("mqtt: packet exceeds size limit") // packet is one decoded control packet: its type, the four header flag bits, and // the variable header plus payload as a single buffer for the caller to parse. type packet struct { typ byte flags byte body []byte } // encodeUint16 appends a two-byte big-endian integer, the encoding MQTT uses for // packet identifiers and the keep-alive. func encodeUint16(b []byte, v uint16) []byte { return binary.BigEndian.AppendUint16(b, v) } // encodeString appends an MQTT UTF-8 string: a two-byte length followed by the // bytes (§1.5.3). Strings longer than 65535 bytes cannot be represented and are // rejected by the callers that build packets. func encodeString(b []byte, s string) []byte { b = encodeUint16(b, uint16(len(s))) return append(b, s...) } // encodeRemainingLength appends the variable-length integer that gives the number // of bytes after the fixed header (§2.2.3). func encodeRemainingLength(b []byte, n int) []byte { for { digit := byte(n % 128) n /= 128 if n > 0 { digit |= 0x80 } b = append(b, digit) if n == 0 { return b } } } // buildPacket frames a body as a complete control packet. func buildPacket(typ, flags byte, body []byte) []byte { out := make([]byte, 0, len(body)+5) out = append(out, typ<<4|flags) out = encodeRemainingLength(out, len(body)) return append(out, body...) } // readRemainingLength decodes the fixed header's variable-length integer. func readRemainingLength(br *bufio.Reader) (int, error) { var ( value int multiplier = 1 ) for i := 0; i < 4; i++ { digit, err := br.ReadByte() if err != nil { return 0, err } value += int(digit&0x7F) * multiplier if digit&0x80 == 0 { return value, nil } multiplier *= 128 } return 0, errors.New("mqtt: malformed remaining length") } // readPacket reads one whole control packet off the wire. func readPacket(br *bufio.Reader) (packet, error) { head, err := br.ReadByte() if err != nil { return packet{}, err } n, err := readRemainingLength(br) if err != nil { return packet{}, err } if n > maxPacketBytes { return packet{}, fmt.Errorf("%w: %d bytes", errPacketTooLarge, n) } body := make([]byte, n) if _, err := io.ReadFull(br, body); err != nil { return packet{}, err } return packet{typ: head >> 4, flags: head & 0x0F, body: body}, nil } // readString decodes an MQTT UTF-8 string from the front of b and returns it with // the remainder. func readString(b []byte) (string, []byte, error) { if len(b) < 2 { return "", nil, errors.New("mqtt: truncated string length") } n := int(binary.BigEndian.Uint16(b)) if len(b) < 2+n { return "", nil, errors.New("mqtt: truncated string") } return string(b[2 : 2+n]), b[2+n:], nil } // connackError maps a CONNACK return code (§3.2.2.3) to an error, or nil when the // broker accepted the connection. func connackError(code byte) error { switch code { case 0: return nil case 1: return errors.New("mqtt: broker refused the connection: unacceptable protocol version") case 2: return errors.New("mqtt: broker refused the connection: client identifier rejected") case 3: return errors.New("mqtt: broker refused the connection: server unavailable") case 4: return errors.New("mqtt: broker refused the connection: bad username or password") case 5: return errors.New("mqtt: broker refused the connection: not authorized") default: return fmt.Errorf("mqtt: broker refused the connection: code %d", code) } }