package packet import ( "errors" "github.com/yosssi/gmq/mqtt" ) // Maximum Remaining Length const maxRemainingLength = 268435455 // Minimum length of the fixed header of the PUBLISH Packet const minLenPUBLISHFixedHeader = 2 // Minimum length of the variable header of the PUBLISH Packet const minLenPUBLISHVariableHeader = 2 // Error value var ErrInvalidPacketID = errors.New("invalid Packet Identifier") // PUBLISH represents a PUBLISH Packet. type PUBLISH struct { base // dup is the DUP flag of the fixed header. DUP bool // qos is the QoS of the fixed header. QoS byte // retain is the Retain of the fixed header. retain bool // topicName is the Topic Name of the varible header. TopicName []byte // packetID is the Packet Identifier of the variable header. PacketID uint16 // message is the Application Message of the payload. Message []byte } // setFixedHeader sets the fixed header to the Packet. func (p *PUBLISH) setFixedHeader() { // Define the first byte of the fixed header. b := TypePUBLISH << 4 // Set 1 to the Bit 3 if the DUP flag is true. if p.DUP { b |= 0x08 } // Set the value of the Will QoS to the Bit 2 and 1. b |= p.QoS << 1 // Set 1 to the Bit 0 if the Retain is true. if p.retain { b |= 0x01 } // Append the first byte to the fixed header. p.fixedHeader = append(p.fixedHeader, b) // Append the Remaining Length to the fixed header. p.appendRemainingLength() } // setVariableHeader sets the variable header to the Packet. func (p *PUBLISH) setVariableHeader() { // Append the Topic Name to the variable header. p.variableHeader = appendLenStr(p.variableHeader, p.TopicName) if p.QoS != mqtt.QoS0 { // Append the Packet Identifier to the variable header. p.variableHeader = append(p.variableHeader, encodeUint16(p.PacketID)...) } } // setPayload sets the payload to the Packet. func (p *PUBLISH) setPayload() { p.payload = p.Message } // NewPUBLISH creates and returns a PUBLISH Packet. func NewPUBLISH(opts *PUBLISHOptions) (Packet, error) { // Initialize the options. if opts == nil { opts = &PUBLISHOptions{} } // Validate the options. if err := opts.validate(); err != nil { return nil, err } // Create a PUBLISH Packet. p := &PUBLISH{ DUP: opts.DUP, QoS: opts.QoS, retain: opts.Retain, TopicName: opts.TopicName, PacketID: opts.PacketID, Message: opts.Message, } // Set the variable header to the Packet. p.setVariableHeader() // Set the payload to the Packet. p.setPayload() // Set the Fixed header to the Packet. p.setFixedHeader() // Return the Packet. return p, nil } // NewPUBLISHFromBytes creates the PUBLISH Packet // from the byte data and returns it. func NewPUBLISHFromBytes(fixedHeader FixedHeader, remaining []byte) (Packet, error) { // Validate the byte data. if err := validatePUBLISHBytes(fixedHeader, remaining); err != nil { return nil, err } // Get the first byte from the fixedHeader. b := fixedHeader[0] // Create a PUBLISH Packet. p := &PUBLISH{ DUP: b&0x08 == 0x08, QoS: b & 0x06 >> 1, retain: b&0x01 == 0x01, } // Set the fixed header to the Packet. p.fixedHeader = fixedHeader // Extract the length of the Topic Name. lenTopicName, _ := decodeUint16(remaining[0:2]) // Calculate the length of the variable header. var lenVariableHeader int if p.QoS == mqtt.QoS0 { lenVariableHeader = 2 + int(lenTopicName) } else { lenVariableHeader = 2 + int(lenTopicName) + 2 } // Set the variable header to the Packet. p.variableHeader = remaining[:lenVariableHeader] // Set the payload to the Packet. p.payload = remaining[lenVariableHeader:] // Set the Topic Name to the Packet. p.TopicName = remaining[2 : 2+lenTopicName] // Extract the Packet Identifier. var packetID uint16 if p.QoS != mqtt.QoS0 { packetID, _ = decodeUint16(remaining[2+lenTopicName : 2+lenTopicName+2]) } // Set the Packet Identifier to the Packet. p.PacketID = packetID // Set the Application Message to the Packet. p.Message = p.payload // Return the Packet. return p, nil } // validatePUBLISHBytes validates the fixed header and the variable header. func validatePUBLISHBytes(fixedHeader FixedHeader, remaining []byte) error { // Extract the MQTT Control Packet type. ptype, err := fixedHeader.ptype() if err != nil { return err } // Check the length of the fixed header. if len(fixedHeader) < minLenPUBLISHFixedHeader { return ErrInvalidFixedHeaderLen } // Check the MQTT Control Packet type. if ptype != TypePUBLISH { return ErrInvalidPacketType } // Get the QoS. qos := (fixedHeader[0] & 0x06) >> 1 // Check the QoS. if !mqtt.ValidQoS(qos) { return ErrInvalidQoS } // Check the length of the remaining. if l := len(remaining); l < minLenPUBLISHVariableHeader || l > maxRemainingLength { return ErrInvalidRemainingLen } // Extract the length of the Topic Name. lenTopicName, _ := decodeUint16(remaining[0:2]) // Calculate the length of the variable header. var lenVariableHeader int if qos == mqtt.QoS0 { lenVariableHeader = 2 + int(lenTopicName) } else { lenVariableHeader = 2 + int(lenTopicName) + 2 } // Check the length of the remaining. if len(remaining) < lenVariableHeader { return ErrInvalidRemainingLength } // End the validation if the QoS equals to QoS 0. if qos == mqtt.QoS0 { return nil } // Extract the Packet Identifier. packetID, _ := decodeUint16(remaining[2+int(lenTopicName) : 2+int(lenTopicName)+2]) // Check the Packet Identifier. if packetID == 0 { return ErrInvalidPacketID } return nil }