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

commonMain.com.ditchoom.mqtt.controlpacket.String.kt Maven / Gradle / Ivy

@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_FEATURE_WARNING")

package com.ditchoom.mqtt.controlpacket

import com.ditchoom.mqtt.MalformedPacketException

fun String.validateMqttUTF8String(): Boolean {
    if (length > 65_535) {
        return false
    }
    for (c in this) {
        if (c in controlCharactersRange) return false
        if (c in shouldNotIncludeCharRange1) return false
        if (c in shouldNotIncludeCharRange2) return false
        if (c in privateUseCharRange) return false
    }
    return true
}

class InvalidMqttUtf8StringMalformedPacketException(
    msg: CharSequence,
    indexOfError: Int,
    originalString: CharSequence
) :
    MalformedPacketException("Fails to match MQTT Spec for a UTF-8 String. Error:($msg) at index $indexOfError of $originalString")

private val controlCharactersRange by lazy { '\uD800'..'\uDFFF' }
private val shouldNotIncludeCharRange1 by lazy { '\u0001'..'\u001F' }
private val shouldNotIncludeCharRange2 by lazy { '\u007F'..'\u009F' }

/**
 * Cannot add planes 15 or 16 as it does not compile into a 'char' in kotlin
 * http://www.unicode.org/faq/private_use.html#pua2
 */
private val privateUseCharRange by lazy { '\uE000'..'\uF8FF' }

data class MqttUtf8String(val value: CharSequence) {
    fun getValueOrThrow(includeWarnings: Boolean = true): CharSequence {
        val ex = exception
        if (ex != null) {
            throw ex
        }
        val w = warning
        if (w != null && includeWarnings) {
            throw w
        }
        return value
    }

    override fun equals(other: Any?): Boolean {
        return value.toString() == ((other as? MqttUtf8String)?.value).toString()
    }

    private val exception: InvalidMqttUtf8StringMalformedPacketException?
        get() {
            if (value.length > 65_535) {
                return InvalidMqttUtf8StringMalformedPacketException(
                    "MQTT UTF-8 String too large",
                    65_535,
                    value.substring(0, 65_535)
                )
            }
            value.forEachIndexed { index, c ->
                if (c == '\u0000')
                    return InvalidMqttUtf8StringMalformedPacketException(
                        "Invalid Control Character null \\u0000",
                        index,
                        value
                    )
                if (c in controlCharactersRange)
                    return InvalidMqttUtf8StringMalformedPacketException(
                        "Invalid Control Character (\\uD800..\\uDFFF)",
                        index,
                        value
                    )
            }
            return null
        }

    private val warning: InvalidMqttUtf8StringMalformedPacketException?
        get() {
            value.forEachIndexed { index, c ->
                if (c in shouldNotIncludeCharRange1)
                    return InvalidMqttUtf8StringMalformedPacketException(
                        "Invalid Character in range (\\u0001..\\u001F)",
                        index,
                        value
                    )
                if (c in shouldNotIncludeCharRange2)
                    return InvalidMqttUtf8StringMalformedPacketException(
                        "Invalid Character in range (\\u007F..\\u009F)",
                        index,
                        value
                    )
                if (c in privateUseCharRange)
                    return InvalidMqttUtf8StringMalformedPacketException(
                        "Invalid Character in range (\\uE000..\\uF8FF)",
                        index,
                        value
                    )
            }
            return null
        }
}

fun CharSequence.utf8Length(): Int {
    var count = 0
    var i = 0
    val len = length
    while (i < len) {
        val ch = get(i)
        when {
            ch.code <= 0x7F -> count++
            ch.code <= 0x7FF -> count += 2
            ch >= Char.MIN_HIGH_SURROGATE && ch.code < Char.MAX_HIGH_SURROGATE.code + 1 -> {
                count += 4
                ++i
            }
            else -> count += 3
        }
        i++
    }
    return count
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy