All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.com.ditchoom.mqtt5.controlpacket.PublishMessage.kt Maven / Gradle / Ivy

There is a newer version: 1.2.0
Show newest version
@file:Suppress("EXPERIMENTAL_API_USAGE", "KDocUnresolvedReference", "EXPERIMENTAL_UNSIGNED_LITERALS", "DuplicatedCode",
    "EXPERIMENTAL_OVERRIDE"
)

package com.ditchoom.mqtt5.controlpacket

import com.ditchoom.buffer.*
import com.ditchoom.mqtt.MalformedPacketException
import com.ditchoom.mqtt.ProtocolError
import com.ditchoom.mqtt.controlpacket.ControlPacket.Companion.readMqttUtf8StringNotValidatedSized
import com.ditchoom.mqtt.controlpacket.ControlPacket.Companion.variableByteSize
import com.ditchoom.mqtt.controlpacket.ControlPacket.Companion.writeMqttUtf8String
import com.ditchoom.mqtt.controlpacket.ControlPacket.Companion.writeVariableByteInteger
import com.ditchoom.mqtt.controlpacket.IPublishMessage
import com.ditchoom.mqtt.controlpacket.QualityOfService
import com.ditchoom.mqtt.controlpacket.QualityOfService.AT_LEAST_ONCE
import com.ditchoom.mqtt.controlpacket.QualityOfService.AT_MOST_ONCE
import com.ditchoom.mqtt.controlpacket.format.fixed.DirectionOfFlow
import com.ditchoom.mqtt.controlpacket.utf8Length
import com.ditchoom.mqtt5.controlpacket.properties.*

/**
 * Creates an MQTT PUBLISH
 *
 */
data class PublishMessage(
    val fixed: FixedHeader = FixedHeader(),
    val variable: VariableHeader,
    val payload: PlatformBuffer? = null
) :
    ControlPacketV5(IPublishMessage.controlPacketValue, DirectionOfFlow.BIDIRECTIONAL, fixed.flags), IPublishMessage {
    init {
        if (fixed.qos == AT_MOST_ONCE && variable.packetIdentifier != null) {
            throw IllegalArgumentException("Cannot allocate a publish message with a QoS of 0 with a packet identifier")
        } else if (fixed.qos.isGreaterThan(AT_MOST_ONCE) && variable.packetIdentifier == null) {
            throw IllegalArgumentException("Cannot allocate a publish message with a QoS >0 and no packet identifier")
        }
    }


    constructor(
        topic: CharSequence, qos: QualityOfService,
        packetIdentifier: UShort,
        dup: Boolean = false,
        retain: Boolean = false
    ) : this(FixedHeader(dup, qos, retain), VariableHeader(topic, packetIdentifier = packetIdentifier.toInt()))

    override val qualityOfService: QualityOfService = fixed.qos
    override fun variableHeader(writeBuffer: WriteBuffer) = variable.serialize(writeBuffer)
    override fun payload(writeBuffer: WriteBuffer) {
        if (payload != null) {
            writeBuffer.write(payload)
        }
    }

    override fun remainingLength(): UInt {
        var size = variable.size()
        if (payload != null) {
            size += payload.remaining()
        }
        return size
    }

    override val topic = variable.topicName

    override fun expectedResponse() = when (fixed.qos) {
        AT_LEAST_ONCE -> {
            PublishAcknowledgment(variable.packetIdentifier!!.toUShort())
        }
        QualityOfService.EXACTLY_ONCE -> {
            PublishRelease(variable.packetIdentifier!!.toUShort())
        }
        else -> null
    }

    data class FixedHeader(
        /**
         * 3.3.1.1 DUP
         *
         * Position: byte 1, bit 3.
         *
         * If the DUP flag is set to 0, it indicates that this is the first occasion that the Client or Server
         * has attempted to send this PUBLISH packet. If the DUP flag is set to 1, it indicates that this might
         * be re-delivery of an earlier attempt to send the packet.
         *
         * The DUP flag MUST be set to 1 by the Client or Server when it attempts to re-deliver a PUBLISH packet
         * [MQTT-3.3.1-1]. The DUP flag MUST be set to 0 for all QoS 0 messages [MQTT-3.3.1-2].
         *
         * The value of the DUP flag from an incoming PUBLISH packet is not propagated when the PUBLISH packet is
         * sent to subscribers by the Server. The DUP flag in the outgoing PUBLISH packet is set independently to
         * the incoming PUBLISH packet, its value MUST be determined solely by whether the outgoing PUBLISH packet
         * is a retransmission [MQTT-3.3.1-3].
         *
         * Non-normative comment
         *
         * The receiver of an MQTT Control Packet that contains the DUP flag set to 1 cannot assume that it has
         * seen an earlier copy of this packet.
         *
         * Non-normative comment
         *
         * It is important to note that the DUP flag refers to the MQTT Control Packet itself and not to the
         * Application Message that it contains. When using QoS 1, it is possible for a Client to receive a
         * PUBLISH packet with DUP flag set to 0 that contains a repetition of an Application Message that it
         * received earlier, but with a different Packet Identifier. Section 2.2.1 provides more information
         * about Packet Identifiers.
         */
        val dup: Boolean = false,
        /**
         * 3.3.1.2 QoS
         *
         * Position: byte 1, bits 2-1.
         *
         * This field indicates the level of assurance for delivery of an Application Message. The QoS levels are
         * shown below.
         *
         * If the Server included a Maximum QoS in its CONNACK response to a Client and it receives a PUBLISH packet
         * with a QoS greater than this, then it uses DISCONNECT with Reason Code 0x9B (QoS not supported) as
         * described in section 4.13 Handling errors.
         *
         * A PUBLISH Packet MUST NOT have both QoS bits set to 1 [MQTT-3.3.1-4]. If a Server or Client receives a
         * PUBLISH packet which has both QoS bits set to 1 it is a Malformed Packet. Use DISCONNECT with Reason Code
         * 0x81 (Malformed Packet) as described in section 4.13.
         */
        val qos: QualityOfService = AT_MOST_ONCE,
        /**
         * 3.3.1.3 RETAIN
         *
         * Position: byte 1, bit 0.
         *
         * If the RETAIN flag is set to 1 in a PUBLISH packet sent by a Client to a Server, the Server MUST replace
         * any existing retained message for this topic and store the Application Message [MQTT-3.3.1-5], so that it
         * can be delivered to future subscribers whose subscriptions match its Topic Name. If the Payload contains
         * zero bytes it is processed normally by the Server but any retained message with the same topic name MUST
         * be removed and any future subscribers for the topic will not receive a retained message [MQTT-3.3.1-6]. A
         * retained message with a Payload containing zero bytes MUST NOT be stored as a retained message on the
         * Server [MQTT-3.3.1-7].
         *
         * If the RETAIN flag is 0 in a PUBLISH packet sent by a Client to a Server, the Server MUST NOT store the
         * message as a retained message and MUST NOT remove or replace any existing retained message [MQTT-3.3.1-8].
         *
         * If the Server included Retain Available in its CONNACK response to a Client with its value set to 0 and it
         * receives a PUBLISH packet with the RETAIN flag is set to 1, then it uses the DISCONNECT Reason Code of
         * 0x9A (Retain not supported) as described in section 4.13.
         *
         * When a new Non‑shared Subscription is made, the last retained message, if any, on each matching topic name
         * is sent to the Client as directed by the Retain Handling Subscription Option. These messages are sent with
         * the RETAIN flag set to 1. Which retained messages are sent is controlled by the Retain Handling Subscription
         * Option. At the time of the Subscription:
         *
         * ·         If Retain Handling is set to 0 the Server MUST send the retained messages matching the Topic
         * Filter of the subscription to the Client [MQTT-3.3.1-9].
         *
         * ·         If Retain Handling is set to 1 then if the subscription did not already exist, the Server MUST
         * send all retained message matching the Topic Filter of the subscription to the Client, and if the
         * subscription did exist the Server MUST NOT send the retained messages. [MQTT-3.3.1-10].
         *
         * ·         If Retain Handling is set to 2, the Server MUST NOT send the retained messages [MQTT-3.3.1-11].
         *
         * Refer to section 3.8.3.1 for a definition of the Subscription Options.
         *
         * If the Server receives a PUBLISH packet with the RETAIN flag set to 1, and QoS 0 it SHOULD store the new
         * QoS 0 message as the new retained message for that topic, but MAY choose to discard it at any time. If
         * this happens there will be no retained message for that topic.
         *
         * If the current retained message for a Topic expires, it is discarded and there will be no retained message
         * for that topic.
         *
         * The setting of the RETAIN flag in an Application Message forwarded by the Server from an established
         * connection is controlled by the Retain As Published subscription option. Refer to section 3.8.3.1 for
         * a definition of the Subscription Options.
         *
         * ·         If the value of Retain As Published subscription option is set to 0, the Server MUST set the
         * RETAIN flag to 0 when forwarding an Application Message regardless of how the RETAIN flag was set in the
         * received PUBLISH packet [MQTT-3.3.1-12].
         *
         * ·         If the value of Retain As Published subscription option is set to 1, the Server MUST set the
         * RETAIN flag equal to the RETAIN flag in the received PUBLISH packet [MQTT-3.3.1-13].
         *
         * Non-normative comment
         *
         * Retained messages are useful where publishers send state messages on an irregular basis. A new non-shared
         * subscriber will receive the most recent state.
         */
        val retain: Boolean = false
    ) {
        val flags by lazy {
            val dupInt = if (dup) 0b1000 else 0b0
            val qosInt = qos.integerValue.toInt().shl(1)
            val retainInt = if (retain) 0b1 else 0b0
            (dupInt or qosInt or retainInt).toByte()
        }

        companion object {
            fun fromByte(byte1: UByte): FixedHeader {
                val byte1Int = byte1.toInt()
                val dup = byte1Int.shl(4).toUByte().toInt().shr(7) == 1
                val qosBit2 = byte1Int.shl(5).toUByte().toInt().shr(7) == 1
                val qosBit1 = byte1Int.shl(6).toUByte().toInt().shr(7) == 1
                if (qosBit2 && qosBit1) {
                    throw MalformedPacketException(
                        "A PUBLISH Packet MUST NOT have both QoS bits set to 1 [MQTT-3.3.1-4]." +
                                " If a Server or Client receives a PUBLISH packet which has both QoS bits set to 1 it is a " +
                                "Malformed Packet. Use DISCONNECT with Reason Code 0x81 (Malformed Packet) as described in" +
                                " section 4.13."
                    )
                }
                val qos = QualityOfService.fromBooleans(qosBit2, qosBit1)
                val retain = byte1Int.shl(7).toUByte().toInt().shr(7) == 1
                return FixedHeader(dup, qos, retain)
            }
        }
    }

    /**
     * 3.3.2 PUBLISH Variable Header
     *
     * The Variable Header of the PUBLISH Packet contains the following fields in the order: Topic Name, Packet
     * Identifier, and Properties. The rules for encoding Properties are described in section 2.2.2.
     */
    data class VariableHeader(
        val topicName: CharSequence,
        val packetIdentifier: Int? = null,
        val properties: Properties = Properties()
    ) {

        init {
            if (properties.topicAlias == 0) {
                throw ProtocolError(
                    "Topic Alias not permitted to be set to 0:" +
                            "https://docs.oasis-open.org/mqtt/mqtt/v5.0/cos02/mqtt-v5.0-cos02.html#_Toc1477413"
                )
            }
        }

        override fun equals(other: Any?): Boolean {
            if (other !is VariableHeader) return false
            return when {
                topicName.toString() != other.topicName.toString() -> {
                    false
                }
                packetIdentifier != other.packetIdentifier -> {
                    false
                }
                properties != other.properties -> {
                    false
                }
                else -> true
            }
        }

        fun serialize(buffer: WriteBuffer) {
            buffer.writeMqttUtf8String(topicName)
            if (packetIdentifier != null) {
                buffer.write(packetIdentifier.toUShort())
            }
            properties.serialize(buffer)
        }

        fun size(): UInt {
            var size = (UShort.SIZE_BYTES + topicName.utf8Length()).toUInt()
            if (packetIdentifier != null) {
                size += UShort.SIZE_BYTES.toUInt()
            }
            val propsSize = properties.size()
            size += variableByteSize(propsSize) + propsSize
            return size
        }

        override fun hashCode(): Int {
            var result = topicName.toString().hashCode()
            result = 31 * result + (packetIdentifier ?: 0)
            result = 31 * result + properties.hashCode()
            return result
        }

        data class Properties(
            /**
             * 3.3.2.3.2 Payload Format Indicator
             *
             * 1 (0x01) Byte, Identifier of the Payload Format Indicator.
             *
             * Followed by the value of the Payload Format Indicator, either of:
             *
             * ·         0 (0x00) Byte Indicates that the Payload is unspecified bytes, which
             * is equivalent to not sending a Payload Format Indicator.
             *
             * ·         1 (0x01) Byte Indicates that the Payload is UTF-8 Encoded Character
             * Data. The UTF-8 data in the Payload MUST be well-formed UTF-8 as defined by the
             * Unicode specification [Unicode] and restated in RFC 3629 [RFC3629].
             *
             * A Server MUST send the Payload Format Indicator unaltered to all subscribers
             * receiving the Application Message [MQTT-3.3.2-4]. The receiver MAY validate
             * that the Payload is of the format indicated, and if it is not send a PUBACK,
             * PUBREC, or DISCONNECT with Reason Code of 0x99 (Payload format invalid) as
             * described in section 4.13.  Refer to section 5.4.9 for information about
             * security issues in validating the payload format.
             */
            val payloadFormatIndicator: Boolean = false,
            /**
             * 3.3.2.3.3 Message Expiry Interval`
             *
             * 2 (0x02) Byte, Identifier of the Message Expiry Interval.
             *
             * Followed by the Four Byte Integer representing the Message Expiry Interval.
             *
             * If present, the Four Byte value is the lifetime of the Application Message in
             * seconds. If the Message Expiry Interval has passed and the Server has not
             * managed to start onward delivery to a matching subscriber, then it MUST delete
             * the copy of the message for that subscriber [MQTT-3.3.2-5].
             *
             * If absent, the Application Message does not expire.
             *
             * The PUBLISH packet sent to a Client by the Server MUST contain a Message Expiry
             * Interval set to the received value minus the time that the Application Message
             * has been waiting in the Server [MQTT-3.3.2-6]. Refer to section 4.1 for details
             * and limitations of stored state.
             */
            val messageExpiryInterval: Long? = null,
            /**
             * 3.3.2.3.4 Topic Alias
             *
             * 35 (0x23) Byte, Identifier of the Topic Alias.
             *
             * Followed by the Two Byte integer representing the Topic Alias value. It is a Protocol Error to
             * include the Topic Alias value more than once.
             *
             * A Topic Alias is an integer value that is used to identify the Topic instead of using the Topic
             * Name. This reduces the size of the PUBLISH packet, and is useful when the Topic Names are long
             * and the same Topic Names are used repetitively within a Network Connection.
             *
             * The sender decides whether to use a Topic Alias and chooses the value. It sets a Topic Alias mapping
             * by including a non-zero length Topic Name and a Topic Alias in the PUBLISH packet. The receiver
             * processes the PUBLISH as normal but also sets the specified Topic Alias mapping to this Topic Name.
             *
             * If a Topic Alias mapping has been set at the receiver, a sender can send a PUBLISH packet that
             * contains that Topic Alias and a zero length Topic Name. The receiver then treats the incoming
             * PUBLISH as if it had contained the Topic Name of the Topic Alias.
             *
             * A sender can modify the Topic Alias mapping by sending another PUBLISH in the same Network
             * Connection with the same Topic Alias value and a different non-zero length Topic Name.
             *
             * Topic Alias mappings exist only within a Network Connection and last only for the lifetime of that
             * Network Connection. A receiver MUST NOT carry forward any Topic Alias mappings from one Network
             * Connection to another [MQTT-3.3.2-7].
             *
             * A Topic Alias of 0 is not permitted. A sender MUST NOT send a PUBLISH packet containing a Topic
             * Alias which has the value 0 [MQTT-3.3.2-8].
             *
             * A Client MUST NOT send a PUBLISH packet with a Topic Alias greater than the Topic Alias Maximum
             * value returned by the Server in the CONNACK packet [MQTT-3.3.2-9]. A Client MUST accept all Topic
             * Alias values greater than 0 and less than or equal to the Topic Alias Maximum value that it sent
             * in the CONNECT packet [MQTT-3.3.2-10].
             *
             * A Server MUST NOT send a PUBLISH packet with a Topic Alias greater than the Topic Alias Maximum
             * value sent by the Client in the CONNECT packet [MQTT-3.3.2-11]. A Server MUST accept all Topic
             * Alias values greater than 0 and less than or equal to the Topic Alias Maximum value that it
             * returned in the CONNACK packet [MQTT-3.3.2-12].
             *
             * The Topic Alias mappings used by the Client and Server are independent from each other. Thus, when
             * a Client sends a PUBLISH containing a Topic Alias value of 1 to a Server and the Server sends a
             * PUBLISH with a Topic Alias value of 1 to that Client they will in general be referring to
             * different Topics.
             */
            val topicAlias: Int? = null,
            /**
             * 3.3.2.3.5 Response Topic
             *
             * 8 (0x08) Byte, Identifier of the Response Topic.
             *
             * Followed by a UTF-8 Encoded String which is used as the Topic Name for a response message. The
             * Response Topic MUST be a UTF-8 Encoded String as defined in section 1.5.4 [MQTT-3.3.2-13]. The
             * Response Topic MUST NOT contain wildcard characters [MQTT-3.3.2-14]. It is a Protocol Error to
             * include the Response Topic more than once. The presence of a Response Topic identifies the Message
             * as a Request.
             *
             * Refer to section 4.10 for more information about Request / Response.
             *
             * The Server MUST send the Response Topic unaltered to all subscribers receiving the Application
             * Message [MQTT-3.3.2-15].
             *
             * Non-normative comment:
             *
             * The receiver of an Application Message with a Response Topic sends a response by using the
             * Response Topic as the Topic Name of a PUBLISH. If the Request Message contains a Correlation
             * Data, the receiver of the Request Message should also include this Correlation Data as a
             * property in the PUBLISH packet of the Response Message.
             */
            val responseTopic: CharSequence? = null,
            /**
             * 3.3.2.3.6 Correlation Data
             *
             * 9 (0x09) Byte, Identifier of the Correlation Data.
             *
             * Followed by Binary Data. The Correlation Data is used by the sender of the Request Message to
             * identify which request the Response Message is for when it is received. It is a Protocol Error
             * to include Correlation Data more than once. If the Correlation Data is not present, the Requester
             * does not require any correlation data.
             *
             * The Server MUST send the Correlation Data unaltered to all subscribers receiving the Application
             * Message [MQTT-3.3.2-16]. The value of the Correlation Data only has meaning to the sender of the
             * Request Message and receiver of the Response Message.
             *
             * Non-normative comment
             *
             * The receiver of an Application Message which contains both a Response Topic and a Correlation Data
             * sends a response by using the Response Topic as the Topic Name of a PUBLISH. The Client should also
             * send the Correlation Data unaltered as part of the PUBLISH of the responses.
             *
             * Non-normative comment
             *
             * If the Correlation Data contains information which can cause application failures if modified by
             * the Client responding to the request, it should be encrypted and/or hashed to allow any alteration
             * to be detected.
             *
             * Refer to section 4.10 for more information about Request / Response
             */
            val correlationData: PlatformBuffer? = null,
            /**
             * 3.3.2.3.7 User Property
             *
             * 38 (0x26) Byte, Identifier of the User Property.
             *
             * Followed by a UTF-8 String Pair. The User Property is allowed to appear multiple times to
             * represent multiple name, value pairs. The same name is allowed to appear more than once.
             *
             * The Server MUST send all User Properties unaltered in a PUBLISH packet when forwarding the
             * Application Message to a Client [MQTT-3.3.2-17]. The Server MUST maintain the order of User
             * Properties when forwarding the Application Message [MQTT-3.3.2-18].
             *
             * Non-normative comment
             *
             * This property is intended to provide a means of transferring application layer name-value tags
             * whose meaning and interpretation are known only by the application programs responsible for
             * sending and receiving them.
             */
            val userProperty: List> = emptyList(),
            /**
             * 3.3.2.3.8 Subscription Identifier
             *
             * 11 (0x0B), Identifier of the Subscription Identifier.
             *
             * Followed by a Variable Byte Integer representing the identifier of the subscription.
             *
             * The Subscription Identifier can have the value of 1 to 268,435,455. It is a Protocol Error if the
             * Subscription Identifier has a value of 0. Multiple Subscription Identifiers will be included if
             * the publication is the result of a match to more than one subscription, in this case their order
             * is not significant.
             */
            val subscriptionIdentifier: Set = emptySet(),
            /**
             * 3.3.2.3.9 Content Type
             *
             * 3 (0x03) Identifier of the Content Type.
             *
             * Followed by a UTF-8 Encoded String describing the content of the Application Message. The Content
             * Type MUST be a UTF-8 Encoded String as defined in section 1.5.4 [MQTT-3.3.2-19].
             *
             * It is a Protocol Error to include the Content Type more than once. The value of the Content Type is
             * defined by the sending and receiving application.
             *
             * A Server MUST send the Content Type unaltered to all subscribers receiving the Application Message
             * [MQTT-3.3.2-20].
             *
             * Non-normative comment
             *
             * The UTF-8 Encoded String may use a MIME content type string to describe the contents of the
             * Application message. However, since the sending and receiving applications are responsible for
             * the definition and interpretation of the string, MQTT performs no validation of the string except
             * to insure it is a valid UTF-8 Encoded String.
             *
             * Non-normative example
             *
             * Figure 3-9 shows an example of a PUBLISH packet with the Topic Name set to “a/b”, the Packet
             * Identifier set to 10, and having no properties.
             */
            val contentType: CharSequence? = null
        ) {

            init {
                if (topicAlias == 0) {
                    throw ProtocolError(
                        "Topic Alias not permitted to be set to 0:" +
                                "https://docs.oasis-open.org/mqtt/mqtt/v5.0/cos02/mqtt-v5.0-cos02.html#_Toc1477413"
                    )
                }
            }

            val props by lazy {
                val list = ArrayList(7 + userProperty.count())
                if (payloadFormatIndicator) {
                    list += PayloadFormatIndicator(payloadFormatIndicator)
                }
                if (messageExpiryInterval != null) {
                    list += MessageExpiryInterval(messageExpiryInterval)
                }
                if (topicAlias != null) {
                    list += TopicAlias(topicAlias)
                }
                if (responseTopic != null) {
                    list += ResponseTopic(responseTopic)
                }
                if (correlationData != null) {
                    list += CorrelationData(correlationData)
                }
                if (userProperty.isNotEmpty()) {
                    for (keyValueProperty in userProperty) {
                        val key = keyValueProperty.first
                        val value = keyValueProperty.second
                        list += UserProperty(key, value)
                    }
                }
                if (subscriptionIdentifier.isNotEmpty()) {
                    for (sub in subscriptionIdentifier) {
                        list += SubscriptionIdentifier(sub)
                    }
                }
                if (contentType != null) {
                    list += ContentType(contentType)
                }
                list
            }

            fun size(): UInt {
                var size = 0u
                props.forEach { size += it.size() }
                return size
            }

            fun serialize(buffer: WriteBuffer) {
                val size = size()
                buffer.writeVariableByteInteger(size)
                props.forEach { it.write(buffer) }
            }

            companion object {
                fun from(keyValuePairs: Collection?): Properties {
                    var payloadFormatIndicator: Boolean? = null
                    var messageExpiryInterval: Long? = null
                    var topicAlias: Int? = null
                    var responseTopic: CharSequence? = null
                    var correlationData: PlatformBuffer? = null
                    val userProperty = mutableListOf>()
                    val subscriptionIdentifier = LinkedHashSet()
                    var contentType: CharSequence? = null
                    keyValuePairs?.forEach {
                        when (it) {
                            is PayloadFormatIndicator -> {
                                if (payloadFormatIndicator != null) {
                                    throw ProtocolError("Payload Indicator Format found twice")
                                }
                                payloadFormatIndicator = it.willMessageIsUtf8
                            }
                            is MessageExpiryInterval -> {
                                if (messageExpiryInterval != null) {
                                    throw ProtocolError("Message Expiry Interval found twice")
                                }
                                messageExpiryInterval = it.seconds
                            }
                            is TopicAlias -> {
                                if (topicAlias != null) {
                                    throw ProtocolError(
                                        "Topic Alias found twice see:" +
                                                "https://docs.oasis-open.org/mqtt/mqtt/v5.0/cos02/mqtt-v5.0-cos02.html#_Toc1477413"
                                    )
                                }
                                if (it.value == 0) {
                                    throw ProtocolError(
                                        "Topic Alias not permitted to be set to 0:" +
                                                "https://docs.oasis-open.org/mqtt/mqtt/v5.0/cos02/mqtt-v5.0-cos02.html#_Toc1477413"
                                    )
                                }
                                topicAlias = it.value
                            }
                            is ResponseTopic -> {
                                if (responseTopic != null) {
                                    throw ProtocolError(
                                        "Response Topic found twice see:" +
                                                "https://docs.oasis-open.org/mqtt/mqtt/v5.0/cos02/mqtt-v5.0-cos02.html#_Toc1477414"
                                    )
                                }
                                responseTopic = it.value
                            }
                            is CorrelationData -> {
                                if (correlationData != null) {
                                    throw ProtocolError(
                                        "Correlation Data found twice see:" +
                                                "https://docs.oasis-open.org/mqtt/mqtt/v5.0/cos02/mqtt-v5.0-cos02.html#_Toc1477415"
                                    )
                                }
                                correlationData = it.data
                            }
                            is UserProperty -> userProperty += Pair(it.key, it.value)
                            is SubscriptionIdentifier -> {
                                if (it.value == 0L) {
                                    throw ProtocolError(
                                        "Subscription Identifier not permitted to be set to 0:" +
                                                "https://docs.oasis-open.org/mqtt/mqtt/v5.0/cos02/mqtt-v5.0-cos02.html#_Toc1477417"
                                    )
                                }
                                subscriptionIdentifier.add(it.value)
                            }
                            is ContentType -> {
                                if (contentType != null) {
                                    throw ProtocolError(
                                        "Content Type found twice see:" +
                                                "https://docs.oasis-open.org/mqtt/mqtt/v5.0/cos02/mqtt-v5.0-cos02.html#_Toc1477417"
                                    )
                                }
                                contentType = it.value
                            }
                            else -> throw MalformedPacketException("Invalid property type found in MQTT properties $it")
                        }
                    }
                    return Properties(
                        payloadFormatIndicator ?: false,
                        messageExpiryInterval, topicAlias, responseTopic, correlationData, userProperty,
                        subscriptionIdentifier, contentType
                    )
                }
            }
        }

        companion object {

            fun from(buffer: ReadBuffer, isQos0: Boolean): Pair {
                val result = buffer.readMqttUtf8StringNotValidatedSized()
                var size = result.first + UShort.SIZE_BYTES.toUInt()
                val topicName = result.second
                val packetIdentifier = if (isQos0) {
                    null
                } else {
                    size += 2u
                    buffer.readUnsignedShort().toInt()
                }
                val propertiesSized = buffer.readPropertiesSized()
                size += 1u
                size += propertiesSized.first
                val props = Properties.from(propertiesSized.second)
                return Pair(size, VariableHeader(topicName, packetIdentifier, props))
            }
        }
    }

    companion object {
        fun from(buffer: ReadBuffer, byte1: UByte, remainingLength: UInt): PublishMessage {
            val fixedHeader = FixedHeader.fromByte(byte1)
            val variableHeaderSized = VariableHeader.from(buffer, fixedHeader.qos == AT_MOST_ONCE)
            val variableHeader = variableHeaderSized.second
            val variableSize = variableHeaderSized.first
            val size = remainingLength - variableSize
            val payloadBuffer = if (size > 0u) {
                val payloadBuffer = allocateNewBuffer(size)
                payloadBuffer.write(buffer.readByteArray(size))
                payloadBuffer
            } else {
                null
            }
            return PublishMessage(fixedHeader, variableHeader, payloadBuffer)
        }

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy