681 lines
16 KiB
Go
681 lines
16 KiB
Go
package packets
|
||
|
||
import (
|
||
"bufio"
|
||
"bytes"
|
||
"encoding/binary"
|
||
"errors"
|
||
"io"
|
||
"unicode/utf8"
|
||
|
||
"github.com/winc-link/hummingbird/internal/pkg/codes"
|
||
)
|
||
|
||
// Error type
|
||
var (
|
||
ErrInvalUTF8String = errors.New("invalid utf-8 string")
|
||
)
|
||
|
||
// MQTT Version
|
||
type Version = byte
|
||
|
||
type QoS = byte
|
||
|
||
var version2protoName = map[Version][]byte{
|
||
Version31: {'M', 'Q', 'I', 's', 'd', 'p'},
|
||
Version311: {'M', 'Q', 'T', 'T'},
|
||
Version5: {'M', 'Q', 'T', 'T'},
|
||
}
|
||
|
||
const (
|
||
Version31 Version = 0x03
|
||
Version311 Version = 0x04
|
||
Version5 Version = 0x05
|
||
// The maximum packet size of a MQTT packet
|
||
MaximumSize = 268435456
|
||
)
|
||
|
||
//Packet type
|
||
const (
|
||
RESERVED = iota
|
||
CONNECT
|
||
CONNACK
|
||
PUBLISH
|
||
PUBACK
|
||
PUBREC
|
||
PUBREL
|
||
PUBCOMP
|
||
SUBSCRIBE
|
||
SUBACK
|
||
UNSUBSCRIBE
|
||
UNSUBACK
|
||
PINGREQ
|
||
PINGRESP
|
||
DISCONNECT
|
||
AUTH
|
||
)
|
||
|
||
// QoS levels & Subscribe failure
|
||
const (
|
||
Qos0 uint8 = 0x00
|
||
Qos1 uint8 = 0x01
|
||
Qos2 uint8 = 0x02
|
||
SubscribeFailure = 0x80
|
||
)
|
||
|
||
// Flag in the FixHeader
|
||
const (
|
||
FlagReserved = 0
|
||
FlagSubscribe = 2
|
||
FlagUnsubscribe = 2
|
||
FlagPubrel = 2
|
||
)
|
||
|
||
//PacketID is the type of packet identifier
|
||
type PacketID = uint16
|
||
|
||
//Max & min packet ID
|
||
const (
|
||
MaxPacketID PacketID = 65535
|
||
MinPacketID PacketID = 1
|
||
)
|
||
|
||
type PayloadFormat = byte
|
||
|
||
const (
|
||
PayloadFormatBytes PayloadFormat = 0
|
||
PayloadFormatString PayloadFormat = 1
|
||
)
|
||
|
||
func IsVersion3X(v Version) bool {
|
||
return v == Version311 || v == Version31
|
||
}
|
||
|
||
func IsVersion5(v Version) bool {
|
||
return v == Version5
|
||
}
|
||
|
||
// FixHeader represents the FixHeader of the MQTT packet
|
||
type FixHeader struct {
|
||
PacketType byte
|
||
Flags byte
|
||
RemainLength int
|
||
}
|
||
|
||
// Packet defines the interface for structs intended to hold
|
||
// decoded MQTT packets, either from being read or before being
|
||
// written
|
||
type Packet interface {
|
||
// Pack encodes the packet struct into bytes and writes it into io.Writer.
|
||
Pack(w io.Writer) error
|
||
// Unpack read the packet bytes from io.Reader and decodes it into the packet struct
|
||
Unpack(r io.Reader) error
|
||
// String is mainly used in logging, debugging and testing.
|
||
String() string
|
||
}
|
||
|
||
// Topic represents the MQTT Topic
|
||
type Topic struct {
|
||
SubOptions
|
||
Name string
|
||
}
|
||
|
||
// SubOptions is the subscription option of subscriptions.
|
||
// For details: https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Subscription_Options
|
||
type SubOptions struct {
|
||
// Qos is the QoS level of the subscription.
|
||
// 0 = At most once delivery
|
||
// 1 = At least once delivery
|
||
// 2 = Exactly once delivery
|
||
Qos uint8
|
||
// RetainHandling specifies whether retained messages are sent when the subscription is established.
|
||
// 0 = Send retained messages at the time of the subscribe
|
||
// 1 = Send retained messages at subscribe only if the subscription does not currently exist
|
||
// 2 = Do not send retained messages at the time of the subscribe
|
||
RetainHandling byte
|
||
// NoLocal is the No Local option.
|
||
// If the value is 1, Application Messages MUST NOT be forwarded to a connection with a ClientID equal to the ClientID of the publishing connection
|
||
NoLocal bool
|
||
// RetainAsPublished is the Retain As Published option.
|
||
// If 1, Application Messages forwarded using this subscription keep the RETAIN flag they were published with.
|
||
// If 0, Application Messages forwarded using this subscription have the RETAIN flag set to 0. Retained messages sent when the subscription is established have the RETAIN flag set to 1.
|
||
RetainAsPublished bool
|
||
}
|
||
|
||
// Reader is used to read data from bufio.Reader and create MQTT packet instance.
|
||
type Reader struct {
|
||
bufr *bufio.Reader
|
||
version Version
|
||
}
|
||
|
||
// Writer is used to encode MQTT packet into bytes and write it to bufio.Writer.
|
||
type Writer struct {
|
||
bufw *bufio.Writer
|
||
}
|
||
|
||
// Flush writes any buffered data to the underlying io.Writer.
|
||
func (w *Writer) Flush() error {
|
||
return w.bufw.Flush()
|
||
}
|
||
|
||
// ReadWriter warps Reader and Writer.
|
||
type ReadWriter struct {
|
||
*Reader
|
||
*Writer
|
||
}
|
||
|
||
// NewReader returns a new Reader.
|
||
func NewReader(r io.Reader) *Reader {
|
||
if bufr, ok := r.(*bufio.Reader); ok {
|
||
return &Reader{bufr: bufr, version: Version311}
|
||
}
|
||
return &Reader{bufr: bufio.NewReaderSize(r, 2048), version: Version311}
|
||
}
|
||
|
||
func (r *Reader) SetVersion(version Version) {
|
||
r.version = version
|
||
}
|
||
|
||
// NewWriter returns a new Writer.
|
||
func NewWriter(w io.Writer) *Writer {
|
||
if bufw, ok := w.(*bufio.Writer); ok {
|
||
return &Writer{bufw: bufw}
|
||
}
|
||
return &Writer{bufw: bufio.NewWriterSize(w, 2048)}
|
||
}
|
||
|
||
// ReadPacket reads data from Reader and returns a Packet instance.
|
||
// If any errors occurs, returns nil, error
|
||
func (r *Reader) ReadPacket() (Packet, error) {
|
||
first, err := r.bufr.ReadByte()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
fh := &FixHeader{PacketType: first >> 4, Flags: first & 15} //设置FixHeader
|
||
length, err := EncodeRemainLength(r.bufr)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
fh.RemainLength = length
|
||
packet, err := NewPacket(fh, r.version, r.bufr)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if p, ok := packet.(*Connect); ok {
|
||
r.version = p.Version
|
||
}
|
||
return packet, err
|
||
}
|
||
|
||
// WritePacket writes the packet bytes to the Writer.
|
||
// Call Flush after WritePacket to flush buffered data to the underlying io.Writer.
|
||
func (w *Writer) WritePacket(packet Packet) error {
|
||
err := packet.Pack(w.bufw)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// WriteRaw write raw bytes to the Writer.
|
||
// Call Flush after WriteRaw to flush buffered data to the underlying io.Writer.
|
||
func (w *Writer) WriteRaw(b []byte) error {
|
||
_, err := w.bufw.Write(b)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// WriteAndFlush writes and flush the packet bytes to the underlying io.Writer.
|
||
func (w *Writer) WriteAndFlush(packet Packet) error {
|
||
err := packet.Pack(w.bufw)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return w.Flush()
|
||
}
|
||
|
||
// Pack encodes the FixHeader struct into bytes and writes it into io.Writer.
|
||
func (fh *FixHeader) Pack(w io.Writer) error {
|
||
var err error
|
||
b := make([]byte, 1)
|
||
packetType := fh.PacketType << 4
|
||
b[0] = packetType | fh.Flags
|
||
length, err := DecodeRemainLength(fh.RemainLength)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
b = append(b, length...)
|
||
_, err = w.Write(b)
|
||
return err
|
||
}
|
||
|
||
//DecodeRemainLength 将remain length 转成byte表示
|
||
//
|
||
//DecodeRemainLength puts the length int into bytes
|
||
func DecodeRemainLength(length int) ([]byte, error) {
|
||
var result []byte
|
||
if length < 128 {
|
||
result = make([]byte, 1)
|
||
} else if length < 16384 {
|
||
result = make([]byte, 2)
|
||
} else if length < 2097152 {
|
||
result = make([]byte, 3)
|
||
} else if length < 268435456 {
|
||
result = make([]byte, 4)
|
||
} else {
|
||
return nil, codes.ErrMalformed
|
||
}
|
||
var i int
|
||
for {
|
||
encodedByte := length % 128
|
||
length = length / 128
|
||
// if there are more data to encode, set the top bit of this byte
|
||
if length > 0 {
|
||
encodedByte = encodedByte | 128
|
||
}
|
||
result[i] = byte(encodedByte)
|
||
i++
|
||
if length <= 0 {
|
||
break
|
||
}
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
// EncodeRemainLength 读remainLength,如果格式错误返回 error
|
||
//
|
||
// EncodeRemainLength reads the remain length bytes from bufio.Reader and returns length int.
|
||
func EncodeRemainLength(r io.ByteReader) (int, error) {
|
||
var vbi uint32
|
||
var multiplier uint32
|
||
for {
|
||
digit, err := r.ReadByte()
|
||
if err != nil && err != io.EOF {
|
||
return 0, err
|
||
}
|
||
vbi |= uint32(digit&127) << multiplier
|
||
if vbi > 268435455 {
|
||
return 0, codes.ErrMalformed
|
||
}
|
||
if (digit & 128) == 0 {
|
||
break
|
||
}
|
||
multiplier += 7
|
||
}
|
||
return int(vbi), nil
|
||
}
|
||
|
||
// EncodeUTF8String encodes the bytes into UTF-8 encoded strings, returns the encoded bytes, bytes size and error.
|
||
func EncodeUTF8String(buf []byte) (b []byte, size int, err error) {
|
||
buflen := len(buf)
|
||
if buflen > 65535 {
|
||
return nil, 0, codes.ErrMalformed
|
||
}
|
||
//length := int(binary.BigEndian.Uint16(buf[0:2]))
|
||
bufw := make([]byte, 2, 2+buflen)
|
||
binary.BigEndian.PutUint16(bufw, uint16(buflen))
|
||
bufw = append(bufw, buf...)
|
||
return bufw, 2 + buflen, nil
|
||
}
|
||
|
||
func readUint16(r *bytes.Buffer) (uint16, error) {
|
||
if r.Len() < 2 {
|
||
return 0, codes.ErrMalformed
|
||
}
|
||
return binary.BigEndian.Uint16(r.Next(2)), nil
|
||
}
|
||
|
||
func writeUint16(w *bytes.Buffer, i uint16) {
|
||
w.WriteByte(byte(i >> 8))
|
||
w.WriteByte(byte(i))
|
||
}
|
||
func writeUint32(w *bytes.Buffer, i uint32) {
|
||
w.WriteByte(byte(i >> 24))
|
||
w.WriteByte(byte(i >> 16))
|
||
w.WriteByte(byte(i >> 8))
|
||
w.WriteByte(byte(i))
|
||
}
|
||
|
||
func readUint32(r *bytes.Buffer) (uint32, error) {
|
||
if r.Len() < 4 {
|
||
return 0, codes.ErrMalformed
|
||
}
|
||
return binary.BigEndian.Uint32(r.Next(4)), nil
|
||
}
|
||
|
||
func readBinary(r *bytes.Buffer) (b []byte, err error) {
|
||
return readUTF8String(false, r)
|
||
}
|
||
|
||
func readUTF8String(mustUTF8 bool, r *bytes.Buffer) (b []byte, err error) {
|
||
if r.Len() < 2 {
|
||
return nil, codes.ErrMalformed
|
||
}
|
||
length := int(binary.BigEndian.Uint16(r.Next(2)))
|
||
if r.Len() < length {
|
||
return nil, codes.ErrMalformed
|
||
}
|
||
payload := r.Next(length)
|
||
if mustUTF8 {
|
||
if !ValidUTF8(payload) {
|
||
return nil, codes.ErrMalformed
|
||
}
|
||
}
|
||
return payload, nil
|
||
}
|
||
|
||
// the length of s cannot be greater than 65535
|
||
func writeUTF8String(w *bytes.Buffer, s []byte) {
|
||
writeUint16(w, uint16(len(s)))
|
||
w.Write(s)
|
||
}
|
||
func writeBinary(w *bytes.Buffer, b []byte) {
|
||
writeUint16(w, uint16(len(b)))
|
||
w.Write(b)
|
||
}
|
||
|
||
// DecodeUTF8String decodes the UTF-8 encoded strings into bytes, returns the decoded bytes, bytes size and error.
|
||
func DecodeUTF8String(buf []byte) (b []byte, size int, err error) {
|
||
buflen := len(buf)
|
||
if buflen < 2 {
|
||
return nil, 0, ErrInvalUTF8String
|
||
}
|
||
length := int(binary.BigEndian.Uint16(buf[0:2]))
|
||
if buflen < length+2 {
|
||
return nil, 0, ErrInvalUTF8String
|
||
}
|
||
payload := buf[2 : length+2]
|
||
if !ValidUTF8(payload) {
|
||
return nil, 0, ErrInvalUTF8String
|
||
}
|
||
|
||
return payload, length + 2, nil
|
||
}
|
||
|
||
// NewPacket returns a packet representing the decoded MQTT packet and an error.
|
||
func NewPacket(fh *FixHeader, version Version, r io.Reader) (Packet, error) {
|
||
switch fh.PacketType {
|
||
case CONNECT:
|
||
return NewConnectPacket(fh, version, r)
|
||
case CONNACK:
|
||
return NewConnackPacket(fh, version, r)
|
||
case PUBLISH:
|
||
return NewPublishPacket(fh, version, r)
|
||
case PUBACK:
|
||
return NewPubackPacket(fh, version, r)
|
||
case PUBREC:
|
||
return NewPubrecPacket(fh, version, r)
|
||
case PUBREL:
|
||
return NewPubrelPacket(fh, r)
|
||
case PUBCOMP:
|
||
return NewPubcompPacket(fh, version, r)
|
||
case SUBSCRIBE:
|
||
return NewSubscribePacket(fh, version, r)
|
||
case SUBACK:
|
||
return NewSubackPacket(fh, version, r)
|
||
case UNSUBSCRIBE:
|
||
return NewUnsubscribePacket(fh, version, r)
|
||
case PINGREQ:
|
||
return NewPingreqPacket(fh, r)
|
||
case DISCONNECT:
|
||
return NewDisConnectPackets(fh, version, r)
|
||
case UNSUBACK:
|
||
return NewUnsubackPacket(fh, version, r)
|
||
case PINGRESP:
|
||
return NewPingrespPacket(fh, r)
|
||
case AUTH:
|
||
return NewAuthPacket(fh, r)
|
||
default:
|
||
return nil, codes.ErrProtocol
|
||
|
||
}
|
||
}
|
||
|
||
// ValidUTF8 验证是否utf8
|
||
//
|
||
// ValidUTF8 returns whether the given bytes is in UTF-8 form.
|
||
func ValidUTF8(p []byte) bool {
|
||
for {
|
||
if len(p) == 0 {
|
||
return true
|
||
}
|
||
ru, size := utf8.DecodeRune(p)
|
||
if ru >= '\u0000' && ru <= '\u001f' { //[MQTT-1.5.3-2]
|
||
return false
|
||
}
|
||
if ru >= '\u007f' && ru <= '\u009f' {
|
||
return false
|
||
}
|
||
if ru == utf8.RuneError {
|
||
return false
|
||
}
|
||
if !utf8.ValidRune(ru) {
|
||
return false
|
||
}
|
||
if size == 0 {
|
||
return true
|
||
}
|
||
p = p[size:]
|
||
}
|
||
}
|
||
|
||
// ValidTopicName returns whether the bytes is a valid non-shared topic filter.[MQTT-4.7.1-1].
|
||
func ValidTopicName(mustUTF8 bool, p []byte) bool {
|
||
for len(p) > 0 {
|
||
ru, size := utf8.DecodeRune(p)
|
||
if mustUTF8 && ru == utf8.RuneError {
|
||
return false
|
||
}
|
||
if size == 1 {
|
||
//主题名不允许使用通配符
|
||
if p[0] == byte('+') || p[0] == byte('#') {
|
||
return false
|
||
}
|
||
}
|
||
p = p[size:]
|
||
}
|
||
return true
|
||
}
|
||
|
||
// ValidV5Topic returns whether the given bytes is a valid MQTT V5 topic
|
||
func ValidV5Topic(p []byte) bool {
|
||
if len(p) == 0 {
|
||
return false
|
||
}
|
||
if bytes.HasPrefix(p, []byte("$share/")) {
|
||
if len(p) < 9 {
|
||
return false
|
||
}
|
||
if p[7] != '/' {
|
||
subp := p[7:]
|
||
for len(subp) > 0 {
|
||
ru, size := utf8.DecodeRune(subp)
|
||
if ru == utf8.RuneError {
|
||
return false
|
||
}
|
||
if size == 1 {
|
||
if subp[0] == '/' {
|
||
return ValidTopicFilter(true, subp[1:])
|
||
}
|
||
if subp[0] == byte('+') || subp[0] == byte('#') {
|
||
return false
|
||
}
|
||
}
|
||
subp = subp[size:]
|
||
}
|
||
|
||
}
|
||
return false
|
||
}
|
||
return ValidTopicFilter(true, p)
|
||
}
|
||
|
||
// ValidTopicFilter 验证主题过滤器是否合法
|
||
//
|
||
// ValidTopicFilter returns whether the bytes is a valid topic filter. [MQTT-4.7.1-2] [MQTT-4.7.1-3]
|
||
// ValidTopicFilter 验证主题过滤器是否合法
|
||
//
|
||
// ValidTopicFilter returns whether the bytes is a valid topic filter. [MQTT-4.7.1-2] [MQTT-4.7.1-3]
|
||
func ValidTopicFilter(mustUTF8 bool, p []byte) bool {
|
||
if len(p) == 0 {
|
||
return false
|
||
}
|
||
var prevByte byte //前一个字节
|
||
var isSetPrevByte bool
|
||
|
||
for len(p) > 0 {
|
||
ru, size := utf8.DecodeRune(p)
|
||
if mustUTF8 && ru == utf8.RuneError {
|
||
return false
|
||
}
|
||
plen := len(p)
|
||
if p[0] == byte('#') && plen != 1 { // #一定是最后一个字符
|
||
return false
|
||
}
|
||
if size == 1 && isSetPrevByte {
|
||
// + 前(如果有前后字节),一定是'/' [MQTT-4.7.1-2] [MQTT-4.7.1-3]
|
||
if (p[0] == byte('+') || p[0] == byte('#')) && prevByte != byte('/') {
|
||
return false
|
||
}
|
||
|
||
if plen > 1 { // p[0] 不是最后一个字节
|
||
if p[0] == byte('+') && p[1] != byte('/') { // + 后(如果有字节),一定是 '/'
|
||
return false
|
||
}
|
||
}
|
||
}
|
||
prevByte = p[0]
|
||
isSetPrevByte = true
|
||
p = p[size:]
|
||
}
|
||
return true
|
||
}
|
||
|
||
// TopicMatch 返回topic和topic filter是否
|
||
//
|
||
// TopicMatch returns whether the topic and topic filter is matched.
|
||
func TopicMatch(topic []byte, topicFilter []byte) bool {
|
||
var spos int
|
||
var tpos int
|
||
var sublen int
|
||
var topiclen int
|
||
var multilevelWildcard bool //是否是多层的通配符
|
||
sublen = len(topicFilter)
|
||
topiclen = len(topic)
|
||
if sublen == 0 || topiclen == 0 {
|
||
return false
|
||
}
|
||
if (topicFilter[0] == '$' && topic[0] != '$') || (topic[0] == '$' && topicFilter[0] != '$') {
|
||
return false
|
||
}
|
||
for {
|
||
//e.g. ([]byte("foo/bar"),[]byte("foo/+/#")
|
||
if spos < sublen && tpos <= topiclen {
|
||
if tpos != topiclen && topicFilter[spos] == topic[tpos] { // sublen是订阅 topiclen是发布,首字母匹配
|
||
if tpos == topiclen-1 { //遍历到topic的最后一个字节
|
||
/* Check for e.g. foo matching foo/# */
|
||
if spos == sublen-3 && topicFilter[spos+1] == '/' && topicFilter[spos+2] == '#' {
|
||
return true
|
||
}
|
||
}
|
||
spos++
|
||
tpos++
|
||
if spos == sublen && tpos == topiclen { //长度相等,内容相同,匹配
|
||
return true
|
||
} else if tpos == topiclen && spos == sublen-1 && topicFilter[spos] == '+' {
|
||
//订阅topic比发布topic多一个字节,并且多出来的内容是+ ,比如: sub: foo/+ ,topic: foo/
|
||
if spos > 0 && topicFilter[spos-1] != '/' {
|
||
return false
|
||
}
|
||
spos++
|
||
return true
|
||
}
|
||
} else {
|
||
if topicFilter[spos] == '+' { //sub 和 topic 内容不匹配了
|
||
spos++
|
||
for { //找到topic的下一个主题分割符
|
||
if tpos < topiclen && topic[tpos] != '/' {
|
||
tpos++
|
||
} else {
|
||
break
|
||
}
|
||
}
|
||
if tpos == topiclen && spos == sublen { //都遍历完了,返回true
|
||
return true
|
||
}
|
||
} else if topicFilter[spos] == '#' {
|
||
multilevelWildcard = true
|
||
return true
|
||
} else {
|
||
/* Check for e.g. foo/bar matching foo/+/# */
|
||
if spos > 0 && spos+2 == sublen && tpos == topiclen && topicFilter[spos-1] == '+' && topicFilter[spos] == '/' && topicFilter[spos+1] == '#' {
|
||
multilevelWildcard = true
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
}
|
||
} else {
|
||
break
|
||
}
|
||
}
|
||
if !multilevelWildcard && (tpos < topiclen || spos < sublen) {
|
||
return false
|
||
}
|
||
return false
|
||
}
|
||
|
||
// TotalBytes returns how many bytes of the packet
|
||
func TotalBytes(p Packet) uint32 {
|
||
var header *FixHeader
|
||
switch pt := p.(type) {
|
||
case *Auth:
|
||
header = pt.FixHeader
|
||
case *Connect:
|
||
header = pt.FixHeader
|
||
case *Connack:
|
||
header = pt.FixHeader
|
||
case *Disconnect:
|
||
header = pt.FixHeader
|
||
case *Pingreq:
|
||
header = pt.FixHeader
|
||
case *Pingresp:
|
||
header = pt.FixHeader
|
||
case *Puback:
|
||
header = pt.FixHeader
|
||
case *Pubcomp:
|
||
header = pt.FixHeader
|
||
case *Publish:
|
||
header = pt.FixHeader
|
||
case *Pubrec:
|
||
header = pt.FixHeader
|
||
case *Pubrel:
|
||
header = pt.FixHeader
|
||
case *Suback:
|
||
header = pt.FixHeader
|
||
case *Subscribe:
|
||
header = pt.FixHeader
|
||
case *Unsuback:
|
||
header = pt.FixHeader
|
||
case *Unsubscribe:
|
||
header = pt.FixHeader
|
||
}
|
||
if header == nil {
|
||
return 0
|
||
}
|
||
var headerLength uint32
|
||
if header.RemainLength <= 127 {
|
||
headerLength = 2
|
||
} else if header.RemainLength <= 16383 {
|
||
headerLength = 3
|
||
} else if header.RemainLength <= 2097151 {
|
||
headerLength = 4
|
||
} else if header.RemainLength <= 268435455 {
|
||
headerLength = 5
|
||
}
|
||
return headerLength + uint32(header.RemainLength)
|
||
|
||
}
|