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

com.hp.jipp.encoding.IppInputStream.kt Maven / Gradle / Ivy

// Copyright 2017 - 2021 HP Development Company, L.P.
// SPDX-License-Identifier: MIT

package com.hp.jipp.encoding

import com.hp.jipp.model.EnumTypes
import com.hp.jipp.model.KeyValuesTypes
import com.hp.jipp.util.ParseError
import java.io.BufferedInputStream
import java.io.DataInputStream
import java.io.IOException
import java.io.InputStream

/** An [InputStream] which can read IPP packet data. */
@Suppress("TooManyFunctions") // This utility class must handle many different data types.
class IppInputStream(inputStream: InputStream) : DataInputStream(BufferedInputStream(inputStream)) {

    /** Reads a complete packet from this stream. */
    @Throws(IOException::class)
    fun readPacket() =
        IppPacket(
            readShort().toInt(), // Version
            readShort().toInt(), // Code / Status
            readInt(), // ID
            generateSequence { readGroup() }.toList()
        )

    /**
     * Returns the next [AttributeGroup] from the stream or null if there are no more groups.
     */
    fun readGroup(): AttributeGroup? =
        readTag()?.takeIf { it != Tag.endOfAttributes }?.let { tag ->
            if (tag !is DelimiterTag) throw ParseError("Illegal delimiter $tag")
            readAttributeGroup(tag)
        } // Note: a null tag means no endOfAttributes tag (which is not valid) but we ignore it.

    /** Read and return the next [Tag] in the input if possible. */
    private fun readTag(): Tag? = read().takeIf { it >= 0 }?.let { Tag.fromInt(it) }

    /**
     * Read an entire attribute group if available in the input stream.
     */
    internal fun readAttributeGroup(tag: DelimiterTag) =
        AttributeGroup.groupOf(tag, generateSequence { readNextAttribute() }.toList())

    /** Read the next attribute if present. */
    private fun readNextAttribute(): Attribute<*>? {
        mark(1)
        return readTag()?.let { tag ->
            if (tag.isDelimiter) {
                reset()
                null
            } else {
                readAnyAttribute(tag)
            }
        }
    }

    /** Read and return an attribute with all of its values. */
    private fun readAnyAttribute(initTag: Tag): Attribute<*> =
        readAnyAttribute(readString(), initTag)

    /** Read the next string (including length) from the stream. */
    internal fun readString() = String(readValueBytes())

    /** Read and return an attribute with all of its values, having its attribute name already. */
    private fun readAnyAttribute(attributeName: String, initTag: Tag): Attribute<*> =
        when (initTag) {
            is OutOfBandTag -> {
                readValueBytes()
                EmptyAttribute(attributeName, initTag)
            }
            is ValueTag ->
                IppStreams.codecs.find { it.handlesTag(initTag) }?.let {
                    UnknownAttribute(
                        attributeName,
                        listOf(readValue(it, initTag, attributeName)) +
                            generateSequence { readNextValue(attributeName) }
                    )
                } ?: throw ParseError("No codec found for tag $initTag")
            else -> throw ParseError("invalid attribute tag $initTag")
        }

    @Suppress("ReturnCount")
    private fun  readValue(codec: Codec, tag: ValueTag, attributeName: String): Any {
        // Apply a special case for enum values which we can match with all known [EnumTypes]
        if (tag == Tag.enumValue) {
            EnumTypes.all[attributeName]?.also {
                // Note: !! is safe because we know EnumTypes can handle Int input
                return it.coerce(readIntValue())!!
            }
        } else if (tag == Tag.octetString) {
            KeyValuesTypes.all[attributeName]?.also {
                return KeyValues.codec.readValue(this, tag)
            }
        }
        return codec.readValue(this, tag)
    }

    /** Return an encoded int value. */
    internal fun readIntValue(): Int {
        takeLength(IppStreams.INT_LENGTH)
        return readInt()
    }

    /** Read a length of an expected amount, throwing if something else is found. */
    internal fun takeLength(length: Int) {
        val readLength = readShort().toInt()
        if (readLength != length) {
            throw ParseError("Bad attribute length: expected $length, got $readLength")
        }
    }

    /** Read a length-value pair, returning it as a [ByteArray]. */
    internal fun readValueBytes(): ByteArray {
        val valueLength = readShort().toInt()
        val valueBytes = ByteArray(valueLength)
        if (valueLength > 0) {
            readFully(valueBytes)
        }
        return valueBytes
    }

    /** Read the next value for the given attributeName. */
    @Suppress("ReturnCount") // Best way to handle errors in this case
    private fun readNextValue(attributeName: String): Any? {
        mark(IppStreams.TAG_LEN + IppStreams.LENGTH_LENGTH)
        return readTag()?.let { tag ->
            if (tag.isEndOfValueStream() || readShort().toInt() != 0) {
                // Non-value tag or non-empty name means its a completely different attribute.
                reset()
                null
            } else if (tag is ValueTag) {
                val codec = IppStreams.tagToCodec[tag] // Fast lookup
                    ?: IppStreams.codecs.firstOrNull { it.handlesTag(tag) } // Slower, more thorough lookup
                    ?: throw ParseError("No codec found for tag $tag")
                readValue(codec, tag, attributeName)
            } else null
        }
    }

    internal fun readCollectionAttributes(): List> {
        val attributes = mutableListOf>()
        while (true) {
            when (val tag = readTag()) {
                Tag.endCollection -> {
                    skipValueBytes()
                    skipValueBytes()
                    return attributes
                }
                Tag.memberAttributeName -> {
                    skipValueBytes()
                    val memberName = readString()
                    val memberTag = readTag() ?: throw ParseError("Missing member tag in $tag")
                    // Read and throw away the (blank) attribute value
                    readValueBytes()
                    attributes.add(readAnyAttribute(memberName, memberTag))
                }
                else -> throw ParseError("Bad tag in collection: $tag")
            }
        }
    }

    /** Read and discard a length-value pair. */
    internal fun skipValueBytes() {
        val valueLength = readShort().toLong()
        if (valueLength != skip(valueLength)) throw ParseError("Value too short")
    }

    internal fun readByteValue(): Byte {
        takeLength(IppStreams.BYTE_LENGTH)
        return readByte()
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy