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

com.pubnub.internal.crypto.cryptor.HeaderParser.kt Maven / Gradle / Ivy

package com.pubnub.internal.crypto.cryptor

import com.pubnub.api.PubNubError
import com.pubnub.api.PubNubException
import com.pubnub.internal.crypto.readExactlyNBytez
import org.slf4j.LoggerFactory
import java.io.BufferedInputStream
import java.io.InputStream

private val SENTINEL = "PNED".toByteArray()
private const val STARTING_INDEX_OF_ONE_BYTE_CRYPTOR_DATA_SIZE = 10
private const val STARTING_INDEX_OF_THREE_BYTES_CRYPTOR_DATA_SIZE = 12
private const val MINIMAL_SIZE_OF_DATA_HAVING_CRYPTOR_HEADER = 10
private const val THREE_BYTES_SIZE_CRYPTOR_DATA_INDICATOR: UByte = 255U

private const val SENTINEL_STARTING_INDEX = 0
private const val SENTINEL_ENDING_INDEX = 3
private const val VERSION_INDEX = 4
private const val CRYPTOR_ID_STARTING_INDEX = 5
private const val CRYPTOR_ID_ENDING_INDEX = 8
private const val CRYPTOR_DATA_SIZE_STARTING_INDEX = 9
private const val THREE_BYTES_CRYPTOR_DATA_SIZE_STARTING_INDEX = 10
private const val THREE_BYTES_CRYPTOR_DATA_SIZE_ENDING_INDEX = 11
private const val MAX_VALUE_THAT_CAN_BE_STORED_ON_TWO_BYTES = 65535
private const val MINIMAL_SIZE_OF_CRYPTO_HEADER = 10

internal class HeaderParser {
    private val log = LoggerFactory.getLogger(HeaderParser::class.java)

    fun parseDataWithHeader(stream: BufferedInputStream): ParseResult {
        val bufferedInputStream = stream.buffered()
        bufferedInputStream.mark(Int.MAX_VALUE) // TODO Can be calculated from spec
        val possibleInitialHeader = ByteArray(MINIMAL_SIZE_OF_CRYPTO_HEADER)
        val initiallyRead = bufferedInputStream.read(possibleInitialHeader)
        if (!possibleInitialHeader.sliceArray(SENTINEL_STARTING_INDEX..SENTINEL_ENDING_INDEX).contentEquals(SENTINEL)) {
            bufferedInputStream.reset()
            return ParseResult.NoHeader
        }

        if (initiallyRead < MINIMAL_SIZE_OF_DATA_HAVING_CRYPTOR_HEADER) {
            throw PubNubException(
                errorMessage = "Minimal size of Cryptor Data Header is: $MINIMAL_SIZE_OF_DATA_HAVING_CRYPTOR_HEADER",
                pubnubError = PubNubError.CRYPTOR_HEADER_PARSE_ERROR,
            )
        }

        validateCryptorHeaderVersion(possibleInitialHeader)
        val cryptorId = possibleInitialHeader.sliceArray(CRYPTOR_ID_STARTING_INDEX..CRYPTOR_ID_ENDING_INDEX)
        val cryptorDataSizeFirstByte = possibleInitialHeader[CRYPTOR_DATA_SIZE_STARTING_INDEX].toUByte()

        val cryptorData: ByteArray =
            if (cryptorDataSizeFirstByte == THREE_BYTES_SIZE_CRYPTOR_DATA_INDICATOR) {
                val cryptorDataSizeBytes = readExactlyNBytez(bufferedInputStream, 2)
                val cryptorDataSize = convertTwoBytesToIntBigEndian(cryptorDataSizeBytes[0], cryptorDataSizeBytes[1])
                readExactlyNBytez(bufferedInputStream, cryptorDataSize)
            } else {
                if (cryptorDataSizeFirstByte == UByte.MIN_VALUE) {
                    byteArrayOf()
                } else {
                    readExactlyNBytez(bufferedInputStream, cryptorDataSizeFirstByte.toInt())
                }
            }
        return ParseResult.Success(cryptorId, cryptorData, bufferedInputStream)
    }

    private fun readExactlyNBytez(
        bufferedInputStream: BufferedInputStream,
        numberOfBytesToRead: Int,
    ) = bufferedInputStream.readExactlyNBytez(numberOfBytesToRead) { n ->
        throw PubNubException(errorMessage = "Couldn't read $n bytes")
    }

    fun parseDataWithHeader(data: ByteArray): ParseResult {
        if (data.size < SENTINEL.size) {
            return ParseResult.NoHeader
        }
        val sentinel = data.sliceArray(SENTINEL_STARTING_INDEX..SENTINEL_ENDING_INDEX)
        if (!SENTINEL.contentEquals(sentinel)) {
            return ParseResult.NoHeader
        }

        if (data.size < MINIMAL_SIZE_OF_DATA_HAVING_CRYPTOR_HEADER) {
            throw PubNubException(
                errorMessage =
                    "Minimal size of encrypted data having Cryptor Data Header is: $MINIMAL_SIZE_OF_DATA_HAVING_CRYPTOR_HEADER",
                pubnubError = PubNubError.CRYPTOR_DATA_HEADER_SIZE_TO_SMALL,
            )
        }

        validateCryptorHeaderVersion(data)

        val cryptorId = data.sliceArray(CRYPTOR_ID_STARTING_INDEX..CRYPTOR_ID_ENDING_INDEX)
        log.trace("CryptoId: ${String(cryptorId, Charsets.UTF_8)}")

        val cryptorDataSizeFirstByte: Byte = data[CRYPTOR_DATA_SIZE_STARTING_INDEX]
        val (startingIndexOfCryptorData, cryptorDataSize) =
            getCryptorDataSizeAndStartingIndex(
                data,
                cryptorDataSizeFirstByte,
            )

        if (startingIndexOfCryptorData + cryptorDataSize > data.size) {
            throw PubNubException(
                errorMessage = "Input data size: ${data.size} is to small to fit header of size $startingIndexOfCryptorData and cryptorData of size: $cryptorDataSize",
                pubnubError = PubNubError.CRYPTOR_HEADER_PARSE_ERROR,
            )
        }
        val cryptorData =
            data.sliceArray(startingIndexOfCryptorData until (startingIndexOfCryptorData + cryptorDataSize))
        val sizeOfCryptorHeader = startingIndexOfCryptorData + cryptorDataSize
        val encryptedData = data.sliceArray(sizeOfCryptorHeader until data.size)

        return ParseResult.Success(cryptorId, cryptorData, encryptedData)
    }

    fun createCryptorHeader(
        cryptorId: ByteArray,
        cryptorData: ByteArray?,
    ): ByteArray {
        val sentinel: ByteArray = SENTINEL
        val cryptorHeaderVersion: Byte = getCurrentCryptoHeaderVersion()
        val cryptorDataSize: Int = cryptorData?.size ?: 0
        val finalCryptorDataSize: ByteArray =
            if (cryptorDataSize < THREE_BYTES_SIZE_CRYPTOR_DATA_INDICATOR.toInt()) {
                byteArrayOf(cryptorDataSize.toByte()) // cryptorDataSize will be stored on 1 byte
            } else if (cryptorDataSize < MAX_VALUE_THAT_CAN_BE_STORED_ON_TWO_BYTES) {
                // cryptorDataSize will be stored on 3 byte
                byteArrayOf(cryptorDataSize.toByte()) + writeNumberOnTwoBytes(cryptorDataSize)
            } else {
                throw PubNubException(
                    errorMessage =
                        "Cryptor Data Size is: $cryptorDataSize whereas " +
                            "max cryptor data size is: $MAX_VALUE_THAT_CAN_BE_STORED_ON_TWO_BYTES",
                    pubnubError = PubNubError.CRYPTOR_HEADER_PARSE_ERROR,
                )
            }

        val cryptorHeader =
            CryptorHeader(sentinel, cryptorHeaderVersion, cryptorId, finalCryptorDataSize, cryptorData ?: byteArrayOf())
        return cryptorHeader.toByteArray()
    }

    private fun getCurrentCryptoHeaderVersion(): Byte {
        return CryptorHeaderVersion.One.value.toByte()
    }

    private fun getCryptorDataSizeAndStartingIndex(
        data: ByteArray,
        cryptorDataSizeFirstByte: Byte,
    ): Pair {
        val startingIndexOfCryptorData: Int
        val cryptorDataSize: Int
        val cryptoDataFirstByteAsUByte: UByte = cryptorDataSizeFirstByte.toUByte()

        if (cryptoDataFirstByteAsUByte == THREE_BYTES_SIZE_CRYPTOR_DATA_INDICATOR) {
            startingIndexOfCryptorData = STARTING_INDEX_OF_THREE_BYTES_CRYPTOR_DATA_SIZE
            log.trace("\"Cryptor data size\" first byte's value is 255 that mean that size is stored on two next bytes")
            val cryptorDataSizeSecondByte = data[THREE_BYTES_CRYPTOR_DATA_SIZE_STARTING_INDEX]
            val cryptorDataSizeThirdByte = data[THREE_BYTES_CRYPTOR_DATA_SIZE_ENDING_INDEX]
            cryptorDataSize = convertTwoBytesToIntBigEndian(cryptorDataSizeSecondByte, cryptorDataSizeThirdByte)
        } else {
            startingIndexOfCryptorData = STARTING_INDEX_OF_ONE_BYTE_CRYPTOR_DATA_SIZE
            cryptorDataSize = cryptoDataFirstByteAsUByte.toInt()
            log.trace("\"Cryptor data size\" is 1 byte long and its value is: $cryptorDataSize")
        }
        return Pair(startingIndexOfCryptorData, cryptorDataSize)
    }

    private fun validateCryptorHeaderVersion(data: ByteArray) {
        val version: UByte = data[VERSION_INDEX].toUByte() // 5th byte
        val versionAsInt = version.toInt()
        log.trace("Cryptor header version is: $versionAsInt")
        // check if version exist in this SDK version
        CryptorHeaderVersion.fromValue(versionAsInt)
            ?: throw PubNubException(
                errorMessage = "Cryptor header version unknown. Please, update SDK",
                pubnubError = PubNubError.CRYPTOR_HEADER_VERSION_UNKNOWN,
            )
    }

    private fun convertTwoBytesToIntBigEndian(
        byte1: Byte,
        byte2: Byte,
    ): Int {
        return ((byte1.toInt() and 0xFF) shl 8) or (byte2.toInt() and 0xFF)
    }

    private fun writeNumberOnTwoBytes(number: Int): ByteArray {
        val result = ByteArray(2)

        result[0] = (number shr 8).toByte()
        result[1] = number.toByte()

        return result
    }
}

internal sealed class ParseResult {
    data class Success(val cryptoId: ByteArray, val cryptorData: ByteArray, val encryptedData: T) :
        ParseResult()

    object NoHeader : ParseResult()
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy