599 lines
21 KiB
Go
599 lines
21 KiB
Go
|
package packets
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"reflect"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/winc-link/hummingbird/internal/pkg/codes"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
PropPayloadFormat byte = 0x01
|
||
|
PropMessageExpiry byte = 0x02
|
||
|
PropContentType byte = 0x03
|
||
|
PropResponseTopic byte = 0x08
|
||
|
PropCorrelationData byte = 0x09
|
||
|
PropSubscriptionIdentifier byte = 0x0B
|
||
|
PropSessionExpiryInterval byte = 0x11
|
||
|
PropAssignedClientID byte = 0x12
|
||
|
PropServerKeepAlive byte = 0x13
|
||
|
PropAuthMethod byte = 0x15
|
||
|
PropAuthData byte = 0x16
|
||
|
PropRequestProblemInfo byte = 0x17
|
||
|
PropWillDelayInterval byte = 0x18
|
||
|
PropRequestResponseInfo byte = 0x19
|
||
|
PropResponseInfo byte = 0x1A
|
||
|
PropServerReference byte = 0x1C
|
||
|
PropReasonString byte = 0x1F
|
||
|
PropReceiveMaximum byte = 0x21
|
||
|
PropTopicAliasMaximum byte = 0x22
|
||
|
PropTopicAlias byte = 0x23
|
||
|
PropMaximumQoS byte = 0x24
|
||
|
PropRetainAvailable byte = 0x25
|
||
|
PropUser byte = 0x26
|
||
|
PropMaximumPacketSize byte = 0x27
|
||
|
PropWildcardSubAvailable byte = 0x28
|
||
|
PropSubIDAvailable byte = 0x29
|
||
|
PropSharedSubAvailable byte = 0x2A
|
||
|
)
|
||
|
|
||
|
func errMorethanOnce(property byte) error {
|
||
|
return fmt.Errorf("property %v presents more than once", property)
|
||
|
}
|
||
|
|
||
|
type UserProperty struct {
|
||
|
K []byte
|
||
|
V []byte
|
||
|
}
|
||
|
|
||
|
// Properties is a struct representing the all the described properties
|
||
|
// allowed by the MQTT protocol, determining the validity of a property
|
||
|
// relvative to the packettype it was received in is provided by the
|
||
|
// ValidateID function
|
||
|
type Properties struct {
|
||
|
// PayloadFormat indicates the format of the payload of the message
|
||
|
// 0 is unspecified bytes
|
||
|
// 1 is UTF8 encoded character data
|
||
|
PayloadFormat *byte
|
||
|
// MessageExpiry is the lifetime of the message in seconds
|
||
|
MessageExpiry *uint32
|
||
|
// ContentType is a UTF8 string describing the content of the message
|
||
|
// for example it could be a MIME type
|
||
|
ContentType []byte
|
||
|
// ResponseTopic is a UTF8 string indicating the topic name to which any
|
||
|
// response to this message should be sent
|
||
|
ResponseTopic []byte
|
||
|
// CorrelationData is binary data used to associate future response
|
||
|
// messages with the original request message
|
||
|
CorrelationData []byte
|
||
|
// SubscriptionIdentifier is an identifier of the subscription to which
|
||
|
// the Publish matched
|
||
|
SubscriptionIdentifier []uint32
|
||
|
// SessionExpiryInterval is the time in seconds after a client disconnects
|
||
|
// that the server should retain the session Info (subscriptions etc)
|
||
|
SessionExpiryInterval *uint32
|
||
|
// AssignedClientID is the server assigned client identifier in the case
|
||
|
// that a client connected without specifying a clientID the server
|
||
|
// generates one and returns it in the Connack
|
||
|
AssignedClientID []byte
|
||
|
// ServerKeepAlive allows the server to specify in the Connack packet
|
||
|
// the time in seconds to be used as the keep alive value
|
||
|
ServerKeepAlive *uint16
|
||
|
// AuthMethod is a UTF8 string containing the name of the authentication
|
||
|
// method to be used for extended authentication
|
||
|
AuthMethod []byte
|
||
|
// AuthData is binary data containing authentication data
|
||
|
AuthData []byte
|
||
|
// RequestProblemInfo is used by the Client to indicate to the server to
|
||
|
// include the Reason String and/or User Properties in case of failures
|
||
|
RequestProblemInfo *byte
|
||
|
// WillDelayInterval is the number of seconds the server waits after the
|
||
|
// point at which it would otherwise send the will message before sending
|
||
|
// it. The client reconnecting before that time expires causes the server
|
||
|
// to cancel sending the will
|
||
|
WillDelayInterval *uint32
|
||
|
// RequestResponseInfo is used by the Client to request the Server provide
|
||
|
// Response Info in the Connack
|
||
|
RequestResponseInfo *byte
|
||
|
// ResponseInfo is a UTF8 encoded string that can be used as the basis for
|
||
|
// createing a Response Topic. The way in which the Client creates a
|
||
|
// Response Topic from the Response Info is not defined. A base
|
||
|
// use of this is to pass a globally unique portion of the topic tree which
|
||
|
// is reserved for this Client for at least the lifetime of its Session. This
|
||
|
// often cannot just be a random name as both the requesting Client and the
|
||
|
// responding Client need to be authorized to use it. It is normal to use this
|
||
|
// as the root of a topic tree for a particular Client. For the Server to
|
||
|
// return this Info, it normally needs to be correctly configured.
|
||
|
// Using this mechanism allows this configuration to be done once in the
|
||
|
// Server rather than in each Client
|
||
|
ResponseInfo []byte
|
||
|
// ServerReference is a UTF8 string indicating another server the client
|
||
|
// can use
|
||
|
ServerReference []byte
|
||
|
// ReasonString is a UTF8 string representing the reason associated with
|
||
|
// this response, intended to be human readable for diagnostic purposes
|
||
|
ReasonString []byte
|
||
|
// ReceiveMaximum is the maximum number of QOS1 & 2 messages allowed to be
|
||
|
// 'inflight' (not having received a PUBACK/PUBCOMP response for)
|
||
|
ReceiveMaximum *uint16
|
||
|
// TopicAliasMaximum is the highest value permitted as a Topic Alias
|
||
|
TopicAliasMaximum *uint16
|
||
|
// TopicAlias is used in place of the topic string to reduce the size of
|
||
|
// packets for repeated messages on a topic
|
||
|
TopicAlias *uint16
|
||
|
// MaximumQoS is the highest QOS level permitted for a Publish
|
||
|
MaximumQoS *byte
|
||
|
// RetainAvailable indicates whether the server supports messages with the
|
||
|
// retain flag set
|
||
|
RetainAvailable *byte
|
||
|
// User is a map of user provided properties
|
||
|
User []UserProperty
|
||
|
|
||
|
// MaximumPacketSize allows the client or server to specify the maximum packet
|
||
|
// size in bytes that they support
|
||
|
MaximumPacketSize *uint32
|
||
|
// WildcardSubAvailable indicates whether wildcard subscriptions are permitted
|
||
|
WildcardSubAvailable *byte
|
||
|
// SubIDAvailable indicates whether subscription identifiers are supported
|
||
|
SubIDAvailable *byte
|
||
|
// SharedSubAvailable indicates whether shared subscriptions are supported
|
||
|
SharedSubAvailable *byte
|
||
|
}
|
||
|
|
||
|
func sprintf(name string, v interface{}) string {
|
||
|
if v == nil {
|
||
|
return fmt.Sprintf("%s: %v", name, v)
|
||
|
}
|
||
|
t := reflect.TypeOf(v)
|
||
|
if t.Kind() == reflect.Ptr {
|
||
|
rv := reflect.ValueOf(v)
|
||
|
if rv.IsNil() {
|
||
|
return fmt.Sprintf("%s: nil", name)
|
||
|
}
|
||
|
return fmt.Sprintf("%s: %v", name, reflect.ValueOf(v).Elem())
|
||
|
}
|
||
|
return fmt.Sprintf("%s: %v", name, v)
|
||
|
|
||
|
}
|
||
|
|
||
|
func (p *Properties) String() string {
|
||
|
var str []string
|
||
|
str = append(str, sprintf("PayloadFormat", p.PayloadFormat))
|
||
|
str = append(str, sprintf("MessageExpiry", p.MessageExpiry))
|
||
|
str = append(str, sprintf("ContentType", p.ContentType))
|
||
|
str = append(str, sprintf("ResponseTopic", p.ResponseTopic))
|
||
|
str = append(str, sprintf("CorrelationData", p.CorrelationData))
|
||
|
str = append(str, sprintf("SubscriptionIdentifier", p.SubscriptionIdentifier))
|
||
|
str = append(str, sprintf("SessionExpiryInterval", p.SessionExpiryInterval))
|
||
|
str = append(str, sprintf("AssignedClientID", p.AssignedClientID))
|
||
|
str = append(str, sprintf("ServerKeepAlive", p.ServerKeepAlive))
|
||
|
str = append(str, sprintf("AuthMethod", p.AuthMethod))
|
||
|
str = append(str, sprintf("AuthData", p.AuthData))
|
||
|
str = append(str, sprintf("RequestProblemInfo", p.RequestProblemInfo))
|
||
|
str = append(str, sprintf("WillDelayInterval", p.WillDelayInterval))
|
||
|
str = append(str, sprintf("RequestResponseInfo", p.RequestResponseInfo))
|
||
|
str = append(str, sprintf("ResponseInfo", p.ResponseInfo))
|
||
|
str = append(str, sprintf("ServerReference", p.ServerReference))
|
||
|
str = append(str, sprintf("ReasonString", p.ReasonString))
|
||
|
str = append(str, sprintf("ReceiveMaximum", p.ReceiveMaximum))
|
||
|
str = append(str, sprintf("TopicAliasMaximum", p.TopicAliasMaximum))
|
||
|
str = append(str, sprintf("TopicAlias", p.TopicAlias))
|
||
|
str = append(str, sprintf("MaximumQoS", p.MaximumQoS))
|
||
|
str = append(str, sprintf("RetainAvailable", p.RetainAvailable))
|
||
|
str = append(str, sprintf("User", p.User))
|
||
|
str = append(str, sprintf("MaximumPacketSize", p.MaximumPacketSize))
|
||
|
str = append(str, sprintf("WildcardSubAvailable", p.WildcardSubAvailable))
|
||
|
str = append(str, sprintf("SubIDAvailable", p.SubIDAvailable))
|
||
|
str = append(str, sprintf("SharedSubAvailable", p.SharedSubAvailable))
|
||
|
return strings.Join(str, ", ")
|
||
|
}
|
||
|
|
||
|
func (p *Properties) PackWillProperties(bufw *bytes.Buffer) {
|
||
|
newBufw := &bytes.Buffer{}
|
||
|
defer func() {
|
||
|
b, _ := DecodeRemainLength(newBufw.Len())
|
||
|
bufw.Write(b)
|
||
|
newBufw.WriteTo(bufw)
|
||
|
}()
|
||
|
if p == nil {
|
||
|
return
|
||
|
}
|
||
|
propertyWriteByte(PropPayloadFormat, p.PayloadFormat, newBufw)
|
||
|
propertyWriteUint32(PropMessageExpiry, p.MessageExpiry, newBufw)
|
||
|
propertyWriteString(PropContentType, p.ContentType, newBufw)
|
||
|
propertyWriteString(PropResponseTopic, p.ResponseTopic, newBufw)
|
||
|
propertyWriteString(PropCorrelationData, p.CorrelationData, newBufw)
|
||
|
propertyWriteUint32(PropWillDelayInterval, p.WillDelayInterval, newBufw)
|
||
|
if len(p.User) != 0 {
|
||
|
for _, v := range p.User {
|
||
|
newBufw.WriteByte(PropUser)
|
||
|
writeBinary(newBufw, v.K)
|
||
|
writeBinary(newBufw, v.V)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Pack takes all the defined properties for an Properties and produces
|
||
|
// a slice of bytes representing the wire format for the Info
|
||
|
func (p *Properties) Pack(bufw *bytes.Buffer, packetType byte) {
|
||
|
newBufw := &bytes.Buffer{}
|
||
|
defer func() {
|
||
|
b, _ := DecodeRemainLength(newBufw.Len())
|
||
|
bufw.Write(b)
|
||
|
newBufw.WriteTo(bufw)
|
||
|
}()
|
||
|
if p == nil {
|
||
|
return
|
||
|
}
|
||
|
propertyWriteByte(PropPayloadFormat, p.PayloadFormat, newBufw)
|
||
|
propertyWriteUint32(PropMessageExpiry, p.MessageExpiry, newBufw)
|
||
|
propertyWriteString(PropContentType, p.ContentType, newBufw)
|
||
|
propertyWriteString(PropResponseTopic, p.ResponseTopic, newBufw)
|
||
|
propertyWriteString(PropCorrelationData, p.CorrelationData, newBufw)
|
||
|
|
||
|
if len(p.SubscriptionIdentifier) != 0 {
|
||
|
for _, v := range p.SubscriptionIdentifier {
|
||
|
newBufw.WriteByte(PropSubscriptionIdentifier)
|
||
|
b, _ := DecodeRemainLength(int(v))
|
||
|
newBufw.Write(b)
|
||
|
}
|
||
|
}
|
||
|
propertyWriteUint32(PropSessionExpiryInterval, p.SessionExpiryInterval, newBufw)
|
||
|
propertyWriteString(PropAssignedClientID, p.AssignedClientID, newBufw)
|
||
|
propertyWriteUint16(PropServerKeepAlive, p.ServerKeepAlive, newBufw)
|
||
|
propertyWriteString(PropAuthMethod, p.AuthMethod, newBufw)
|
||
|
propertyWriteString(PropAuthData, p.AuthData, newBufw)
|
||
|
propertyWriteByte(PropRequestProblemInfo, p.RequestProblemInfo, newBufw)
|
||
|
propertyWriteUint32(PropWillDelayInterval, p.WillDelayInterval, newBufw)
|
||
|
propertyWriteByte(PropRequestResponseInfo, p.RequestResponseInfo, newBufw)
|
||
|
propertyWriteString(PropResponseInfo, p.ResponseInfo, newBufw)
|
||
|
propertyWriteString(PropServerReference, p.ServerReference, newBufw)
|
||
|
propertyWriteString(PropReasonString, p.ReasonString, newBufw)
|
||
|
propertyWriteUint16(PropReceiveMaximum, p.ReceiveMaximum, newBufw)
|
||
|
propertyWriteUint16(PropTopicAliasMaximum, p.TopicAliasMaximum, newBufw)
|
||
|
propertyWriteUint16(PropTopicAlias, p.TopicAlias, newBufw)
|
||
|
propertyWriteByte(PropMaximumQoS, p.MaximumQoS, newBufw)
|
||
|
propertyWriteByte(PropRetainAvailable, p.RetainAvailable, newBufw)
|
||
|
|
||
|
if len(p.User) != 0 {
|
||
|
for _, v := range p.User {
|
||
|
newBufw.WriteByte(PropUser)
|
||
|
writeBinary(newBufw, v.K)
|
||
|
writeBinary(newBufw, v.V)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
propertyWriteUint32(PropMaximumPacketSize, p.MaximumPacketSize, newBufw)
|
||
|
propertyWriteByte(PropWildcardSubAvailable, p.WildcardSubAvailable, newBufw)
|
||
|
propertyWriteByte(PropSubIDAvailable, p.SubIDAvailable, newBufw)
|
||
|
propertyWriteByte(PropSharedSubAvailable, p.SharedSubAvailable, newBufw)
|
||
|
}
|
||
|
|
||
|
func (p *Properties) UnpackWillProperties(bufr *bytes.Buffer) error {
|
||
|
var err error
|
||
|
length, err := EncodeRemainLength(bufr)
|
||
|
// 整个buffer最多只能读到length这么长
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if length == 0 {
|
||
|
return nil
|
||
|
}
|
||
|
newBufr := bytes.NewBuffer(bufr.Next(length))
|
||
|
var propType byte
|
||
|
for {
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
propType, err = newBufr.ReadByte()
|
||
|
if err != nil && err != io.EOF {
|
||
|
return err
|
||
|
}
|
||
|
if err == io.EOF {
|
||
|
break
|
||
|
}
|
||
|
switch propType {
|
||
|
case PropWillDelayInterval:
|
||
|
p.WillDelayInterval, err = propertyReadUint32(p.WillDelayInterval, newBufr, propType, nil)
|
||
|
case PropPayloadFormat:
|
||
|
p.PayloadFormat, err = propertyReadBool(p.PayloadFormat, newBufr, propType)
|
||
|
case PropMessageExpiry:
|
||
|
p.MessageExpiry, err = propertyReadUint32(p.MessageExpiry, newBufr, propType, nil)
|
||
|
case PropContentType:
|
||
|
p.ContentType, err = propertyReadUTF8String(p.ContentType, newBufr, propType, nil)
|
||
|
case PropResponseTopic:
|
||
|
p.ResponseTopic, err = propertyReadUTF8String(p.ResponseTopic, newBufr, propType, func(u []byte) bool {
|
||
|
return ValidTopicName(true, u) // [MQTT-3.3.2-14]
|
||
|
})
|
||
|
case PropCorrelationData:
|
||
|
p.CorrelationData, err = propertyReadBinary(p.CorrelationData, newBufr, propType, nil)
|
||
|
case PropUser:
|
||
|
k, err := readUTF8String(true, newBufr)
|
||
|
if err != nil {
|
||
|
return codes.ErrMalformed
|
||
|
}
|
||
|
v, err := readUTF8String(true, newBufr)
|
||
|
if err != nil {
|
||
|
return codes.ErrMalformed
|
||
|
}
|
||
|
p.User = append(p.User, UserProperty{K: k, V: v})
|
||
|
default:
|
||
|
return codes.ErrMalformed
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Unpack takes a buffer of bytes and reads out the defined properties
|
||
|
// filling in the appropriate entries in the struct, it returns the number
|
||
|
// of bytes used to store the Prop data and any error in decoding them
|
||
|
func (p *Properties) Unpack(bufr *bytes.Buffer, packetType byte) error {
|
||
|
var err error
|
||
|
length, err := EncodeRemainLength(bufr)
|
||
|
// 整个buffer最多只能读到length这么长
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if length == 0 {
|
||
|
return nil
|
||
|
}
|
||
|
newBufr := bytes.NewBuffer(bufr.Next(length))
|
||
|
var propType byte
|
||
|
for {
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
propType, err = newBufr.ReadByte()
|
||
|
if err != nil && err != io.EOF {
|
||
|
return err
|
||
|
}
|
||
|
if err == io.EOF {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
if !ValidateID(packetType, propType) {
|
||
|
return codes.ErrProtocol
|
||
|
}
|
||
|
|
||
|
switch propType {
|
||
|
case PropPayloadFormat:
|
||
|
p.PayloadFormat, err = propertyReadBool(p.PayloadFormat, newBufr, propType)
|
||
|
case PropMessageExpiry:
|
||
|
p.MessageExpiry, err = propertyReadUint32(p.MessageExpiry, newBufr, propType, nil)
|
||
|
case PropContentType:
|
||
|
p.ContentType, err = propertyReadUTF8String(p.ContentType, newBufr, propType, nil)
|
||
|
case PropResponseTopic:
|
||
|
p.ResponseTopic, err = propertyReadUTF8String(p.ResponseTopic, newBufr, propType, func(u []byte) bool {
|
||
|
return ValidTopicName(true, u) // [MQTT-3.3.2-14]
|
||
|
})
|
||
|
case PropCorrelationData:
|
||
|
p.CorrelationData, err = propertyReadBinary(p.CorrelationData, newBufr, propType, nil)
|
||
|
case PropSubscriptionIdentifier:
|
||
|
if len(p.SubscriptionIdentifier) != 0 {
|
||
|
return codes.ErrProtocol
|
||
|
}
|
||
|
si, err := EncodeRemainLength(newBufr)
|
||
|
if err != nil {
|
||
|
return codes.ErrMalformed
|
||
|
}
|
||
|
if si == 0 {
|
||
|
return codes.ErrProtocol
|
||
|
}
|
||
|
p.SubscriptionIdentifier = append(p.SubscriptionIdentifier, uint32(si))
|
||
|
case PropSessionExpiryInterval:
|
||
|
p.SessionExpiryInterval, err = propertyReadUint32(p.SessionExpiryInterval, newBufr, propType, nil)
|
||
|
case PropAssignedClientID:
|
||
|
p.AssignedClientID, err = propertyReadUTF8String(p.AssignedClientID, newBufr, propType, nil)
|
||
|
case PropServerKeepAlive:
|
||
|
p.ServerKeepAlive, err = propertyReadUint16(p.ServerKeepAlive, newBufr, propType, nil)
|
||
|
case PropAuthMethod:
|
||
|
p.AuthMethod, err = propertyReadUTF8String(p.AuthMethod, newBufr, propType, nil)
|
||
|
case PropAuthData:
|
||
|
p.AuthData, err = propertyReadUTF8String(p.AuthData, newBufr, propType, nil)
|
||
|
case PropRequestProblemInfo:
|
||
|
p.RequestProblemInfo, err = propertyReadBool(p.RequestProblemInfo, newBufr, propType)
|
||
|
case PropWillDelayInterval:
|
||
|
p.WillDelayInterval, err = propertyReadUint32(p.WillDelayInterval, newBufr, propType, nil)
|
||
|
case PropRequestResponseInfo:
|
||
|
p.RequestResponseInfo, err = propertyReadBool(p.RequestResponseInfo, newBufr, propType)
|
||
|
case PropResponseInfo:
|
||
|
p.ResponseInfo, err = propertyReadUTF8String(p.ResponseInfo, newBufr, propType, nil)
|
||
|
case PropServerReference:
|
||
|
p.ServerReference, err = propertyReadUTF8String(p.ServerReference, newBufr, propType, nil)
|
||
|
case PropReasonString:
|
||
|
p.ReasonString, err = propertyReadUTF8String(p.ReasonString, newBufr, propType, nil)
|
||
|
case PropReceiveMaximum:
|
||
|
p.ReceiveMaximum, err = propertyReadUint16(p.ReceiveMaximum, newBufr, propType, func(u uint16) bool {
|
||
|
return u != 0
|
||
|
})
|
||
|
case PropTopicAliasMaximum:
|
||
|
p.TopicAliasMaximum, err = propertyReadUint16(p.TopicAliasMaximum, newBufr, propType, nil)
|
||
|
case PropTopicAlias:
|
||
|
p.TopicAlias, err = propertyReadUint16(p.TopicAlias, newBufr, propType, func(u uint16) bool {
|
||
|
return u != 0 // [MQTT-3.3.2-8]
|
||
|
})
|
||
|
case PropMaximumQoS:
|
||
|
p.MaximumQoS, err = propertyReadBool(p.MaximumQoS, newBufr, propType)
|
||
|
case PropRetainAvailable:
|
||
|
p.RetainAvailable, err = propertyReadBool(p.RetainAvailable, newBufr, propType)
|
||
|
case PropUser:
|
||
|
k, err := readUTF8String(true, newBufr)
|
||
|
if err != nil {
|
||
|
return codes.ErrMalformed
|
||
|
}
|
||
|
v, err := readUTF8String(true, newBufr)
|
||
|
if err != nil {
|
||
|
return codes.ErrMalformed
|
||
|
}
|
||
|
p.User = append(p.User, UserProperty{K: k, V: v})
|
||
|
case PropMaximumPacketSize:
|
||
|
p.MaximumPacketSize, err = propertyReadUint32(p.MaximumPacketSize, newBufr, propType, func(u uint32) bool {
|
||
|
return u != 0
|
||
|
})
|
||
|
case PropWildcardSubAvailable:
|
||
|
p.WildcardSubAvailable, err = propertyReadBool(p.WildcardSubAvailable, newBufr, propType)
|
||
|
case PropSubIDAvailable:
|
||
|
p.SubIDAvailable, err = propertyReadBool(p.SubIDAvailable, newBufr, propType)
|
||
|
case PropSharedSubAvailable:
|
||
|
p.SharedSubAvailable, err = propertyReadBool(p.SharedSubAvailable, newBufr, propType)
|
||
|
default:
|
||
|
return codes.ErrMalformed
|
||
|
}
|
||
|
}
|
||
|
if p.AuthData != nil && p.AuthMethod == nil {
|
||
|
return codes.ErrMalformed
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// ValidProperties is a map of the various properties and the
|
||
|
// PacketTypes that is valid for server to unpack.
|
||
|
var ValidProperties = map[byte]map[byte]struct{}{
|
||
|
PropPayloadFormat: {CONNECT: {}, PUBLISH: {}},
|
||
|
PropMessageExpiry: {CONNECT: {}, PUBLISH: {}},
|
||
|
PropContentType: {CONNECT: {}, PUBLISH: {}},
|
||
|
PropResponseTopic: {CONNECT: {}, PUBLISH: {}},
|
||
|
PropCorrelationData: {CONNECT: {}, PUBLISH: {}},
|
||
|
PropSubscriptionIdentifier: {SUBSCRIBE: {}},
|
||
|
PropSessionExpiryInterval: {CONNECT: {}, CONNACK: {}, DISCONNECT: {}},
|
||
|
PropAssignedClientID: {CONNACK: {}},
|
||
|
PropServerKeepAlive: {CONNACK: {}},
|
||
|
PropAuthMethod: {CONNECT: {}, CONNACK: {}, AUTH: {}},
|
||
|
PropAuthData: {CONNECT: {}, CONNACK: {}, AUTH: {}},
|
||
|
PropRequestProblemInfo: {CONNECT: {}},
|
||
|
PropWillDelayInterval: {CONNECT: {}},
|
||
|
PropRequestResponseInfo: {CONNECT: {}},
|
||
|
PropResponseInfo: {CONNACK: {}},
|
||
|
PropServerReference: {CONNACK: {}, DISCONNECT: {}},
|
||
|
PropReasonString: {CONNACK: {}, PUBACK: {}, PUBREC: {}, PUBREL: {}, PUBCOMP: {}, SUBACK: {}, UNSUBACK: {}, DISCONNECT: {}, AUTH: {}},
|
||
|
PropReceiveMaximum: {CONNECT: {}, CONNACK: {}},
|
||
|
PropTopicAliasMaximum: {CONNECT: {}, CONNACK: {}},
|
||
|
PropTopicAlias: {PUBLISH: {}},
|
||
|
PropMaximumQoS: {CONNACK: {}},
|
||
|
PropRetainAvailable: {CONNACK: {}},
|
||
|
PropUser: {CONNECT: {}, CONNACK: {}, PUBLISH: {}, PUBACK: {}, PUBREC: {}, PUBREL: {}, PUBCOMP: {}, SUBSCRIBE: {}, UNSUBSCRIBE: {}, SUBACK: {}, UNSUBACK: {}, DISCONNECT: {}, AUTH: {}},
|
||
|
PropMaximumPacketSize: {CONNECT: {}, CONNACK: {}},
|
||
|
PropWildcardSubAvailable: {CONNACK: {}},
|
||
|
PropSubIDAvailable: {CONNACK: {}},
|
||
|
PropSharedSubAvailable: {CONNACK: {}},
|
||
|
}
|
||
|
|
||
|
// ValidateID takes a PacketType and a property name and returns
|
||
|
// a boolean indicating if that property is valid for that
|
||
|
// PacketType
|
||
|
func ValidateID(packetType byte, i byte) bool {
|
||
|
_, ok := ValidProperties[i][packetType]
|
||
|
return ok
|
||
|
}
|
||
|
|
||
|
func ValidateCode(packType byte, code byte) bool {
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
func propertyReadBool(i *byte, r *bytes.Buffer, propType byte) (*byte, error) {
|
||
|
if i != nil {
|
||
|
return nil, codes.ErrProtocol
|
||
|
}
|
||
|
o, err := r.ReadByte()
|
||
|
if err != nil {
|
||
|
return nil, codes.ErrMalformed
|
||
|
}
|
||
|
if o != 0 && o != 1 {
|
||
|
return nil, codes.ErrProtocol
|
||
|
}
|
||
|
return &o, nil
|
||
|
}
|
||
|
|
||
|
func propertyReadUint32(i *uint32, r *bytes.Buffer, propType byte,
|
||
|
validate func(u uint32) bool) (*uint32, error) {
|
||
|
if i != nil {
|
||
|
return nil, errMorethanOnce(propType)
|
||
|
}
|
||
|
o, err := readUint32(r)
|
||
|
if err != nil {
|
||
|
return nil, codes.ErrMalformed
|
||
|
}
|
||
|
if validate != nil {
|
||
|
if !validate(o) {
|
||
|
return nil, codes.ErrProtocol
|
||
|
}
|
||
|
}
|
||
|
return &o, nil
|
||
|
}
|
||
|
|
||
|
func propertyReadUint16(i *uint16, r *bytes.Buffer, propType byte, validate func(u uint16) bool) (*uint16, error) {
|
||
|
if i != nil {
|
||
|
return nil, codes.ErrProtocol
|
||
|
}
|
||
|
o, err := readUint16(r)
|
||
|
if err != nil {
|
||
|
return nil, codes.ErrMalformed
|
||
|
}
|
||
|
if validate != nil {
|
||
|
if !validate(o) {
|
||
|
return nil, codes.ErrProtocol
|
||
|
}
|
||
|
}
|
||
|
return &o, nil
|
||
|
}
|
||
|
func propertyReadUTF8String(i []byte, r *bytes.Buffer, propType byte, validate func(u []byte) bool) (b []byte, err error) {
|
||
|
if i != nil {
|
||
|
return nil, errMorethanOnce(propType)
|
||
|
}
|
||
|
o, err := readUTF8String(true, r)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if validate != nil {
|
||
|
if !validate(o) {
|
||
|
return nil, codes.ErrProtocol
|
||
|
}
|
||
|
}
|
||
|
return o, nil
|
||
|
}
|
||
|
func propertyReadBinary(i []byte, r *bytes.Buffer, propType byte,
|
||
|
validate func(u []byte) bool) (b []byte, err error) {
|
||
|
if i != nil {
|
||
|
return nil, errMorethanOnce(propType)
|
||
|
}
|
||
|
o, err := readUTF8String(false, r)
|
||
|
if err != nil {
|
||
|
return nil, codes.ErrMalformed
|
||
|
}
|
||
|
if validate != nil {
|
||
|
if !validate(o) {
|
||
|
return nil, codes.ErrProtocol
|
||
|
}
|
||
|
}
|
||
|
return o, nil
|
||
|
}
|
||
|
|
||
|
func propertyWriteByte(t byte, i *byte, w *bytes.Buffer) {
|
||
|
if i != nil {
|
||
|
w.Write([]byte{t, *i})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func propertyWriteUint16(t byte, i *uint16, w *bytes.Buffer) {
|
||
|
if i != nil {
|
||
|
w.WriteByte(t)
|
||
|
writeUint16(w, *i)
|
||
|
}
|
||
|
}
|
||
|
func propertyWriteUint32(t byte, i *uint32, w *bytes.Buffer) {
|
||
|
if i != nil {
|
||
|
w.WriteByte(t)
|
||
|
writeUint32(w, *i)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func propertyWriteString(t byte, i []byte, w *bytes.Buffer) {
|
||
|
if i != nil {
|
||
|
w.WriteByte(t)
|
||
|
writeBinary(w, i)
|
||
|
}
|
||
|
}
|