mirror of
https://github.com/mochi-mqtt/server.git
synced 2025-09-26 20:21:12 +08:00
478 lines
17 KiB
Go
478 lines
17 KiB
Go
// SPDX-License-Identifier: MIT
|
|
// SPDX-FileCopyrightText: 2022 mochi-co
|
|
// SPDX-FileContributor: mochi-co
|
|
|
|
package packets
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
PropPayloadFormat byte = 1
|
|
PropMessageExpiryInterval byte = 2
|
|
PropContentType byte = 3
|
|
PropResponseTopic byte = 8
|
|
PropCorrelationData byte = 9
|
|
PropSubscriptionIdentifier byte = 11
|
|
PropSessionExpiryInterval byte = 17
|
|
PropAssignedClientID byte = 18
|
|
PropServerKeepAlive byte = 19
|
|
PropAuthenticationMethod byte = 21
|
|
PropAuthenticationData byte = 22
|
|
PropRequestProblemInfo byte = 23
|
|
PropWillDelayInterval byte = 24
|
|
PropRequestResponseInfo byte = 25
|
|
PropResponseInfo byte = 26
|
|
PropServerReference byte = 28
|
|
PropReasonString byte = 31
|
|
PropReceiveMaximum byte = 33
|
|
PropTopicAliasMaximum byte = 34
|
|
PropTopicAlias byte = 35
|
|
PropMaximumQos byte = 36
|
|
PropRetainAvailable byte = 37
|
|
PropUser byte = 38
|
|
PropMaximumPacketSize byte = 39
|
|
PropWildcardSubAvailable byte = 40
|
|
PropSubIDAvailable byte = 41
|
|
PropSharedSubAvailable byte = 42
|
|
)
|
|
|
|
// validPacketProperties indicates which properties are valid for which packet types.
|
|
var validPacketProperties = map[byte]map[byte]byte{
|
|
PropPayloadFormat: {Publish: 1, WillProperties: 1},
|
|
PropMessageExpiryInterval: {Publish: 1, WillProperties: 1},
|
|
PropContentType: {Publish: 1, WillProperties: 1},
|
|
PropResponseTopic: {Publish: 1, WillProperties: 1},
|
|
PropCorrelationData: {Publish: 1, WillProperties: 1},
|
|
PropSubscriptionIdentifier: {Publish: 1, Subscribe: 1},
|
|
PropSessionExpiryInterval: {Connect: 1, Connack: 1, Disconnect: 1},
|
|
PropAssignedClientID: {Connack: 1},
|
|
PropServerKeepAlive: {Connack: 1},
|
|
PropAuthenticationMethod: {Connect: 1, Connack: 1, Auth: 1},
|
|
PropAuthenticationData: {Connect: 1, Connack: 1, Auth: 1},
|
|
PropRequestProblemInfo: {Connect: 1},
|
|
PropWillDelayInterval: {WillProperties: 1},
|
|
PropRequestResponseInfo: {Connect: 1},
|
|
PropResponseInfo: {Connack: 1},
|
|
PropServerReference: {Connack: 1, Disconnect: 1},
|
|
PropReasonString: {Connack: 1, Puback: 1, Pubrec: 1, Pubrel: 1, Pubcomp: 1, Suback: 1, Unsuback: 1, Disconnect: 1, Auth: 1},
|
|
PropReceiveMaximum: {Connect: 1, Connack: 1},
|
|
PropTopicAliasMaximum: {Connect: 1, Connack: 1},
|
|
PropTopicAlias: {Publish: 1},
|
|
PropMaximumQos: {Connack: 1},
|
|
PropRetainAvailable: {Connack: 1},
|
|
PropUser: {Connect: 1, Connack: 1, Publish: 1, Puback: 1, Pubrec: 1, Pubrel: 1, Pubcomp: 1, Subscribe: 1, Suback: 1, Unsubscribe: 1, Unsuback: 1, Disconnect: 1, Auth: 1, WillProperties: 1},
|
|
PropMaximumPacketSize: {Connect: 1, Connack: 1},
|
|
PropWildcardSubAvailable: {Connack: 1},
|
|
PropSubIDAvailable: {Connack: 1},
|
|
PropSharedSubAvailable: {Connack: 1},
|
|
}
|
|
|
|
// UserProperty is an arbitrary key-value pair for a packet user properties array.
|
|
type UserProperty struct { // [MQTT-1.5.7-1]
|
|
Key string `json:"k"`
|
|
Val string `json:"v"`
|
|
}
|
|
|
|
// Properties contains all of the mqtt v5 properties available for a packet.
|
|
// Some properties have valid values of 0 or not-present. In this case, we opt for
|
|
// property flags to indicate the usage of property.
|
|
// Refer to mqtt v5 2.2.2.2 Property spec for more information.
|
|
type Properties struct {
|
|
CorrelationData []byte `json:"cd"`
|
|
SubscriptionIdentifier []int `json:"si"`
|
|
AuthenticationData []byte `json:"ad"`
|
|
User []UserProperty `json:"user"`
|
|
ContentType string `json:"ct"`
|
|
ResponseTopic string `json:"rt"`
|
|
AssignedClientID string `json:"aci"`
|
|
AuthenticationMethod string `json:"am"`
|
|
ResponseInfo string `json:"ri"`
|
|
ServerReference string `json:"sr"`
|
|
ReasonString string `json:"rs"`
|
|
MessageExpiryInterval uint32 `json:"me"`
|
|
SessionExpiryInterval uint32 `json:"sei"`
|
|
WillDelayInterval uint32 `json:"wdi"`
|
|
MaximumPacketSize uint32 `json:"mps"`
|
|
ServerKeepAlive uint16 `json:"ska"`
|
|
ReceiveMaximum uint16 `json:"rm"`
|
|
TopicAliasMaximum uint16 `json:"tam"`
|
|
TopicAlias uint16 `json:"ta"`
|
|
PayloadFormat byte `json:"pf"`
|
|
PayloadFormatFlag bool `json:"fpf"`
|
|
SessionExpiryIntervalFlag bool `json:"fsei"`
|
|
ServerKeepAliveFlag bool `json:"fska"`
|
|
RequestProblemInfo byte `json:"rpi"`
|
|
RequestProblemInfoFlag bool `json:"frpi"`
|
|
RequestResponseInfo byte `json:"rri"`
|
|
TopicAliasFlag bool `json:"fta"`
|
|
MaximumQos byte `json:"mqos"`
|
|
MaximumQosFlag bool `json:"fmqos"`
|
|
RetainAvailable byte `json:"ra"`
|
|
RetainAvailableFlag bool `json:"fra"`
|
|
WildcardSubAvailable byte `json:"wsa"`
|
|
WildcardSubAvailableFlag bool `json:"fwsa"`
|
|
SubIDAvailable byte `json:"sida"`
|
|
SubIDAvailableFlag bool `json:"fsida"`
|
|
SharedSubAvailable byte `json:"ssa"`
|
|
SharedSubAvailableFlag bool `json:"fssa"`
|
|
}
|
|
|
|
// Copy creates a new Properties struct with copies of the values.
|
|
func (p *Properties) Copy(allowTransfer bool) Properties {
|
|
pr := Properties{
|
|
PayloadFormat: p.PayloadFormat, // [MQTT-3.3.2-4]
|
|
PayloadFormatFlag: p.PayloadFormatFlag,
|
|
MessageExpiryInterval: p.MessageExpiryInterval,
|
|
ContentType: p.ContentType, // [MQTT-3.3.2-20]
|
|
ResponseTopic: p.ResponseTopic, // [MQTT-3.3.2-15]
|
|
SessionExpiryInterval: p.SessionExpiryInterval,
|
|
SessionExpiryIntervalFlag: p.SessionExpiryIntervalFlag,
|
|
AssignedClientID: p.AssignedClientID,
|
|
ServerKeepAlive: p.ServerKeepAlive,
|
|
ServerKeepAliveFlag: p.ServerKeepAliveFlag,
|
|
AuthenticationMethod: p.AuthenticationMethod,
|
|
RequestProblemInfo: p.RequestProblemInfo,
|
|
RequestProblemInfoFlag: p.RequestProblemInfoFlag,
|
|
WillDelayInterval: p.WillDelayInterval,
|
|
RequestResponseInfo: p.RequestResponseInfo,
|
|
ResponseInfo: p.ResponseInfo,
|
|
ServerReference: p.ServerReference,
|
|
ReasonString: p.ReasonString,
|
|
ReceiveMaximum: p.ReceiveMaximum,
|
|
TopicAliasMaximum: p.TopicAliasMaximum,
|
|
TopicAlias: 0, // NB; do not copy topic alias [MQTT-3.3.2-7] + we do not send to clients (currently) [MQTT-3.1.2-26] [MQTT-3.1.2-27]
|
|
MaximumQos: p.MaximumQos,
|
|
MaximumQosFlag: p.MaximumQosFlag,
|
|
RetainAvailable: p.RetainAvailable,
|
|
RetainAvailableFlag: p.RetainAvailableFlag,
|
|
MaximumPacketSize: p.MaximumPacketSize,
|
|
WildcardSubAvailable: p.WildcardSubAvailable,
|
|
WildcardSubAvailableFlag: p.WildcardSubAvailableFlag,
|
|
SubIDAvailable: p.SubIDAvailable,
|
|
SubIDAvailableFlag: p.SubIDAvailableFlag,
|
|
SharedSubAvailable: p.SharedSubAvailable,
|
|
SharedSubAvailableFlag: p.SharedSubAvailableFlag,
|
|
}
|
|
|
|
if allowTransfer {
|
|
pr.TopicAlias = p.TopicAlias
|
|
pr.TopicAliasFlag = p.TopicAliasFlag
|
|
}
|
|
|
|
if len(p.CorrelationData) > 0 {
|
|
pr.CorrelationData = append([]byte{}, p.CorrelationData...) // [MQTT-3.3.2-16]
|
|
}
|
|
|
|
if len(p.SubscriptionIdentifier) > 0 {
|
|
pr.SubscriptionIdentifier = append([]int{}, p.SubscriptionIdentifier...)
|
|
}
|
|
|
|
if len(p.AuthenticationData) > 0 {
|
|
pr.AuthenticationData = append([]byte{}, p.AuthenticationData...)
|
|
}
|
|
|
|
if len(p.User) > 0 {
|
|
pr.User = []UserProperty{}
|
|
for _, v := range p.User {
|
|
pr.User = append(pr.User, UserProperty{ // [MQTT-3.3.2-17]
|
|
Key: v.Key,
|
|
Val: v.Val,
|
|
})
|
|
}
|
|
}
|
|
|
|
return pr
|
|
}
|
|
|
|
// canEncode returns true if the property type is valid for the packet type.
|
|
func (p *Properties) canEncode(pkt byte, k byte) bool {
|
|
return validPacketProperties[k][pkt] == 1
|
|
}
|
|
|
|
// Encode encodes properties into a bytes buffer.
|
|
func (p *Properties) Encode(pkt byte, mods Mods, b *bytes.Buffer, n int) {
|
|
if p == nil {
|
|
return
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if p.canEncode(pkt, PropPayloadFormat) && p.PayloadFormatFlag {
|
|
buf.WriteByte(PropPayloadFormat)
|
|
buf.WriteByte(p.PayloadFormat)
|
|
}
|
|
|
|
if p.canEncode(pkt, PropMessageExpiryInterval) && p.MessageExpiryInterval > 0 {
|
|
buf.WriteByte(PropMessageExpiryInterval)
|
|
buf.Write(encodeUint32(p.MessageExpiryInterval))
|
|
}
|
|
|
|
if p.canEncode(pkt, PropContentType) && p.ContentType != "" {
|
|
buf.WriteByte(PropContentType)
|
|
buf.Write(encodeString(p.ContentType)) // [MQTT-3.3.2-19]
|
|
}
|
|
|
|
if mods.AllowResponseInfo && p.canEncode(pkt, PropResponseTopic) && // [MQTT-3.3.2-14]
|
|
p.ResponseTopic != "" && !strings.ContainsAny(p.ResponseTopic, "+#") { // [MQTT-3.1.2-28]
|
|
buf.WriteByte(PropResponseTopic)
|
|
buf.Write(encodeString(p.ResponseTopic)) // [MQTT-3.3.2-13]
|
|
}
|
|
|
|
if mods.AllowResponseInfo && p.canEncode(pkt, PropCorrelationData) && len(p.CorrelationData) > 0 { // [MQTT-3.1.2-28]
|
|
buf.WriteByte(PropCorrelationData)
|
|
buf.Write(encodeBytes(p.CorrelationData))
|
|
}
|
|
|
|
if p.canEncode(pkt, PropSubscriptionIdentifier) && len(p.SubscriptionIdentifier) > 0 {
|
|
for _, v := range p.SubscriptionIdentifier {
|
|
if v > 0 {
|
|
buf.WriteByte(PropSubscriptionIdentifier)
|
|
encodeLength(&buf, int64(v))
|
|
}
|
|
}
|
|
}
|
|
|
|
if p.canEncode(pkt, PropSessionExpiryInterval) && p.SessionExpiryIntervalFlag { // [MQTT-3.14.2-2]
|
|
buf.WriteByte(PropSessionExpiryInterval)
|
|
buf.Write(encodeUint32(p.SessionExpiryInterval))
|
|
}
|
|
|
|
if p.canEncode(pkt, PropAssignedClientID) && p.AssignedClientID != "" {
|
|
buf.WriteByte(PropAssignedClientID)
|
|
buf.Write(encodeString(p.AssignedClientID))
|
|
}
|
|
|
|
if p.canEncode(pkt, PropServerKeepAlive) && p.ServerKeepAliveFlag {
|
|
buf.WriteByte(PropServerKeepAlive)
|
|
buf.Write(encodeUint16(p.ServerKeepAlive))
|
|
}
|
|
|
|
if p.canEncode(pkt, PropAuthenticationMethod) && p.AuthenticationMethod != "" {
|
|
buf.WriteByte(PropAuthenticationMethod)
|
|
buf.Write(encodeString(p.AuthenticationMethod))
|
|
}
|
|
|
|
if p.canEncode(pkt, PropAuthenticationData) && len(p.AuthenticationData) > 0 {
|
|
buf.WriteByte(PropAuthenticationData)
|
|
buf.Write(encodeBytes(p.AuthenticationData))
|
|
}
|
|
|
|
if p.canEncode(pkt, PropRequestProblemInfo) && p.RequestProblemInfoFlag {
|
|
buf.WriteByte(PropRequestProblemInfo)
|
|
buf.WriteByte(p.RequestProblemInfo)
|
|
}
|
|
|
|
if p.canEncode(pkt, PropWillDelayInterval) && p.WillDelayInterval > 0 {
|
|
buf.WriteByte(PropWillDelayInterval)
|
|
buf.Write(encodeUint32(p.WillDelayInterval))
|
|
}
|
|
|
|
if p.canEncode(pkt, PropRequestResponseInfo) && p.RequestResponseInfo > 0 {
|
|
buf.WriteByte(PropRequestResponseInfo)
|
|
buf.WriteByte(p.RequestResponseInfo)
|
|
}
|
|
|
|
if mods.AllowResponseInfo && p.canEncode(pkt, PropResponseInfo) && len(p.ResponseInfo) > 0 { // [MQTT-3.1.2-28]
|
|
buf.WriteByte(PropResponseInfo)
|
|
buf.Write(encodeString(p.ResponseInfo))
|
|
}
|
|
|
|
if p.canEncode(pkt, PropServerReference) && len(p.ServerReference) > 0 {
|
|
buf.WriteByte(PropServerReference)
|
|
buf.Write(encodeString(p.ServerReference))
|
|
}
|
|
|
|
// [MQTT-3.2.2-19] [MQTT-3.14.2-3] [MQTT-3.4.2-2] [MQTT-3.5.2-2]
|
|
// [MQTT-3.6.2-2] [MQTT-3.9.2-1] [MQTT-3.11.2-1] [MQTT-3.15.2-2]
|
|
if !mods.DisallowProblemInfo && p.canEncode(pkt, PropReasonString) && p.ReasonString != "" {
|
|
b := encodeString(p.ReasonString)
|
|
if mods.MaxSize == 0 || uint32(n+len(b)+1) < mods.MaxSize {
|
|
buf.WriteByte(PropReasonString)
|
|
buf.Write(b)
|
|
}
|
|
}
|
|
|
|
if p.canEncode(pkt, PropReceiveMaximum) && p.ReceiveMaximum > 0 {
|
|
buf.WriteByte(PropReceiveMaximum)
|
|
buf.Write(encodeUint16(p.ReceiveMaximum))
|
|
}
|
|
|
|
if p.canEncode(pkt, PropTopicAliasMaximum) && p.TopicAliasMaximum > 0 {
|
|
buf.WriteByte(PropTopicAliasMaximum)
|
|
buf.Write(encodeUint16(p.TopicAliasMaximum))
|
|
}
|
|
|
|
if p.canEncode(pkt, PropTopicAlias) && p.TopicAliasFlag && p.TopicAlias > 0 { // [MQTT-3.3.2-8]
|
|
buf.WriteByte(PropTopicAlias)
|
|
buf.Write(encodeUint16(p.TopicAlias))
|
|
}
|
|
|
|
if p.canEncode(pkt, PropMaximumQos) && p.MaximumQosFlag && p.MaximumQos < 2 {
|
|
buf.WriteByte(PropMaximumQos)
|
|
buf.WriteByte(p.MaximumQos)
|
|
}
|
|
|
|
if p.canEncode(pkt, PropRetainAvailable) && p.RetainAvailableFlag {
|
|
buf.WriteByte(PropRetainAvailable)
|
|
buf.WriteByte(p.RetainAvailable)
|
|
}
|
|
|
|
if !mods.DisallowProblemInfo && p.canEncode(pkt, PropUser) {
|
|
pb := bytes.NewBuffer([]byte{})
|
|
for _, v := range p.User {
|
|
pb.WriteByte(PropUser)
|
|
pb.Write(encodeString(v.Key))
|
|
pb.Write(encodeString(v.Val))
|
|
}
|
|
// [MQTT-3.2.2-20] [MQTT-3.14.2-4] [MQTT-3.4.2-3] [MQTT-3.5.2-3]
|
|
// [MQTT-3.6.2-3] [MQTT-3.9.2-2] [MQTT-3.11.2-2] [MQTT-3.15.2-3]
|
|
if mods.MaxSize == 0 || uint32(n+pb.Len()+1) < mods.MaxSize {
|
|
buf.Write(pb.Bytes())
|
|
}
|
|
}
|
|
|
|
if p.canEncode(pkt, PropMaximumPacketSize) && p.MaximumPacketSize > 0 {
|
|
buf.WriteByte(PropMaximumPacketSize)
|
|
buf.Write(encodeUint32(p.MaximumPacketSize))
|
|
}
|
|
|
|
if p.canEncode(pkt, PropWildcardSubAvailable) && p.WildcardSubAvailableFlag {
|
|
buf.WriteByte(PropWildcardSubAvailable)
|
|
buf.WriteByte(p.WildcardSubAvailable)
|
|
}
|
|
|
|
if p.canEncode(pkt, PropSubIDAvailable) && p.SubIDAvailableFlag {
|
|
buf.WriteByte(PropSubIDAvailable)
|
|
buf.WriteByte(p.SubIDAvailable)
|
|
}
|
|
|
|
if p.canEncode(pkt, PropSharedSubAvailable) && p.SharedSubAvailableFlag {
|
|
buf.WriteByte(PropSharedSubAvailable)
|
|
buf.WriteByte(p.SharedSubAvailable)
|
|
}
|
|
|
|
encodeLength(b, int64(buf.Len()))
|
|
buf.WriteTo(b) // [MQTT-3.1.3-10]
|
|
}
|
|
|
|
// Decode decodes property bytes into a properties struct.
|
|
func (p *Properties) Decode(pkt byte, b *bytes.Buffer) (n int, err error) {
|
|
if p == nil {
|
|
return 0, nil
|
|
}
|
|
|
|
var bu int
|
|
n, bu, err = DecodeLength(b)
|
|
if err != nil {
|
|
return n + bu, err
|
|
}
|
|
|
|
if n == 0 {
|
|
return n + bu, nil
|
|
}
|
|
|
|
bt := b.Bytes()
|
|
var k byte
|
|
for offset := 0; offset < n; {
|
|
k, offset, err = decodeByte(bt, offset)
|
|
if err != nil {
|
|
return n + bu, err
|
|
}
|
|
|
|
if _, ok := validPacketProperties[k][pkt]; !ok {
|
|
return n + bu, fmt.Errorf("property type %v not valid for packet type %v: %w", k, pkt, ErrProtocolViolationUnsupportedProperty)
|
|
}
|
|
|
|
switch k {
|
|
case PropPayloadFormat:
|
|
p.PayloadFormat, offset, err = decodeByte(bt, offset)
|
|
p.PayloadFormatFlag = true
|
|
case PropMessageExpiryInterval:
|
|
p.MessageExpiryInterval, offset, err = decodeUint32(bt, offset)
|
|
case PropContentType:
|
|
p.ContentType, offset, err = decodeString(bt, offset)
|
|
case PropResponseTopic:
|
|
p.ResponseTopic, offset, err = decodeString(bt, offset)
|
|
case PropCorrelationData:
|
|
p.CorrelationData, offset, err = decodeBytes(bt, offset)
|
|
case PropSubscriptionIdentifier:
|
|
if p.SubscriptionIdentifier == nil {
|
|
p.SubscriptionIdentifier = []int{}
|
|
}
|
|
|
|
n, bu, err := DecodeLength(bytes.NewBuffer(bt[offset:]))
|
|
if err != nil {
|
|
return n + bu, err
|
|
}
|
|
p.SubscriptionIdentifier = append(p.SubscriptionIdentifier, n)
|
|
offset += bu
|
|
case PropSessionExpiryInterval:
|
|
p.SessionExpiryInterval, offset, err = decodeUint32(bt, offset)
|
|
p.SessionExpiryIntervalFlag = true
|
|
case PropAssignedClientID:
|
|
p.AssignedClientID, offset, err = decodeString(bt, offset)
|
|
case PropServerKeepAlive:
|
|
p.ServerKeepAlive, offset, err = decodeUint16(bt, offset)
|
|
p.ServerKeepAliveFlag = true
|
|
case PropAuthenticationMethod:
|
|
p.AuthenticationMethod, offset, err = decodeString(bt, offset)
|
|
case PropAuthenticationData:
|
|
p.AuthenticationData, offset, err = decodeBytes(bt, offset)
|
|
case PropRequestProblemInfo:
|
|
p.RequestProblemInfo, offset, err = decodeByte(bt, offset)
|
|
p.RequestProblemInfoFlag = true
|
|
case PropWillDelayInterval:
|
|
p.WillDelayInterval, offset, err = decodeUint32(bt, offset)
|
|
case PropRequestResponseInfo:
|
|
p.RequestResponseInfo, offset, err = decodeByte(bt, offset)
|
|
case PropResponseInfo:
|
|
p.ResponseInfo, offset, err = decodeString(bt, offset)
|
|
case PropServerReference:
|
|
p.ServerReference, offset, err = decodeString(bt, offset)
|
|
case PropReasonString:
|
|
p.ReasonString, offset, err = decodeString(bt, offset)
|
|
case PropReceiveMaximum:
|
|
p.ReceiveMaximum, offset, err = decodeUint16(bt, offset)
|
|
case PropTopicAliasMaximum:
|
|
p.TopicAliasMaximum, offset, err = decodeUint16(bt, offset)
|
|
case PropTopicAlias:
|
|
p.TopicAlias, offset, err = decodeUint16(bt, offset)
|
|
p.TopicAliasFlag = true
|
|
case PropMaximumQos:
|
|
p.MaximumQos, offset, err = decodeByte(bt, offset)
|
|
p.MaximumQosFlag = true
|
|
case PropRetainAvailable:
|
|
p.RetainAvailable, offset, err = decodeByte(bt, offset)
|
|
p.RetainAvailableFlag = true
|
|
case PropUser:
|
|
var k, v string
|
|
k, offset, err = decodeString(bt, offset)
|
|
if err != nil {
|
|
return n + bu, err
|
|
}
|
|
v, offset, err = decodeString(bt, offset)
|
|
p.User = append(p.User, UserProperty{Key: k, Val: v})
|
|
case PropMaximumPacketSize:
|
|
p.MaximumPacketSize, offset, err = decodeUint32(bt, offset)
|
|
case PropWildcardSubAvailable:
|
|
p.WildcardSubAvailable, offset, err = decodeByte(bt, offset)
|
|
p.WildcardSubAvailableFlag = true
|
|
case PropSubIDAvailable:
|
|
p.SubIDAvailable, offset, err = decodeByte(bt, offset)
|
|
p.SubIDAvailableFlag = true
|
|
case PropSharedSubAvailable:
|
|
p.SharedSubAvailable, offset, err = decodeByte(bt, offset)
|
|
p.SharedSubAvailableFlag = true
|
|
}
|
|
|
|
if err != nil {
|
|
return n + bu, err
|
|
}
|
|
}
|
|
|
|
return n + bu, nil
|
|
}
|