25300c04创建于 2024年11月4日历史提交
/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2024. All rights reserved.
 */

package httpclient4cj

/**
 * @file The file manage Http2Reader.
 */

/*
 * The class is Http2Reader
 * Reads HTTP/2 transport frames
 *
 * @author luoyukai4
 * @since 0.34.3
 */
class Http2Reader {
    let source: OkBuffer
    let frameDecode = Decoder.newDecode()

    init(source: OkBuffer) {
        this.source = source
    }

    func readConnectionPreface(handler: H2Connection) {
        if (!nextFrame(true, handler)) {
            throw HttpException("Required SETTINGS preface not received")
        }
    }

    func nextFrame(requireSettings: Bool, handler: H2Connection): Bool {
        try {
            let length = readMedium(source)
            if (length < 0 || length > INITIAL_MAX_FRAME_SIZE) {
                throw HttpException("FRAME_SIZE_ERROR: ${length}")
            }
            let frameType: Byte = source.readByte()

            if (requireSettings && frameType != TYPE_SETTINGS) {
                throw HttpException("Expected a SETTINGS frame but was ${frameType}")
            }

            let flags: Byte = source.readByte()
            let streamId: UInt32 = UInt32(readInt32(source) & 0x7fffffff)
            match (frameType) {
                case 0x0 => //DATA
                    readData(handler, length, flags, streamId)
                case 0x1 => //HEADER
                    readHeaders(handler, length, flags, streamId)
                case 0x3 => //TYPE_RST_STREAM
                    readRstStream(handler, length, flags, streamId)
                case 0x4 => //SETTINGS
                    readSettings(handler, length, flags, streamId)
                case 0x6 => //PING
                    readPing(handler, length, flags, streamId)
                case 0x7 => //GOAWAY
                    readGoAway(handler, length, flags, streamId)
                case 0x8 => //TYPE_WINDOW_UPDATE
                    readWindowUpdate(handler, length, flags, streamId)
                case _ => source.skipBuffer(length)
            }
            return true
        } catch (e: EOFException) {
            return false
        } catch (e: SocketException) {
            throw HttpException("SocketException")
        } catch (e: Exception) {
            e.printStackTrace()
            throw e
        }
    }

    private func readInt32(source: OkBuffer): Int64 {
        return Int64(Int32(source.readByte()) << 24 | Int32(source.readByte()) << 16 | Int32(source.readByte()) << 8 |
                Int32(source.readByte()))
    }

    private func readInt16(source: OkBuffer): Int64 {
        return Int64(Int32(source.readByte()) << 8 | Int32(source.readByte()))
    }

    func readMedium(source: OkBuffer): Int64 {
        let arr = ByteBuffer()
        source.read(arr, 3)
        let bytes = readToEnd(arr)
        return Int64(Int32(bytes[0]) << 16 | Int32(bytes[1]) << 8 | Int32(bytes[2]))
    }

    func lengthWithoutPadding(len: Int64, flags: Byte, padding: Int64) {
        var length = len

        if ((flags & FLAG_PADDED) != 0) {
            length--
        }

        if (padding > length) {
            throw HttpException("PROTOCOL_ERROR padding ${padding} > remaining length ${length}")
        }

        return length - padding
    }

    func readPriority(_: UInt32) {
        readInt32(source)
        source.readByte()
    }

    private func readWindowUpdate(handler: H2Connection, length: Int64, _: Byte, streamId: UInt32) {
        if (length != 4) {
            throw HttpException("TYPE_WINDOW_UPDATE length !=4: ${length}")
        }

        let increment = readInt32(source) & 0x7fffffff

        if (increment == 0) {
            throw HttpException("windowSizeIncrement was 0")
        }

        handler.windowUpdate(streamId, increment)
    }

    private func readRstStream(handler: H2Connection, length: Int64, _: Byte, streamId: UInt32) {
        if (length != 4) {
            throw HttpException("TYPE_RST_STREAM length !=4: ${length}")
        }

        if (streamId == 0) {
            throw HttpException("TYPE_RST_STREAM streamId == 0")
        }

        let errorCodeInt = readInt32(source)
        let errorCode = ErrorCode.fromHttp2(errorCodeInt)
        if (!has(errorCode)) {
            throw HttpException("TYPE_RST_STREAM unexpected error code: ${errorCodeInt}")
        }
        handler.rstStream(streamId, errorCode.getOrThrow())
    }

    private func readGoAway(handler: H2Connection, length: Int64, _: Byte, streamId: UInt32) {
        if (length < 8) {
            throw HttpException("TYPE_GOAWAY length < 8: ${length}")
        }

        if (streamId != 0) {
            throw HttpException("TYPE_GOAWAY streamId != 0")
        }

        let lastStreamId = readInt32(source)
        let errorCodeInt = readInt32(source)
        let opaqueDataLength = length - 8
        let errorCode = ErrorCode.fromHttp2(errorCodeInt)

        if (!has(errorCode)) {
            throw HttpException("TYPE_GOAWAY unexpected error code: ${errorCodeInt}")
        }

        if (opaqueDataLength > 0) {
            let arr = ByteBuffer()
            source.read(arr, opaqueDataLength)
            arr.clear()
        }

        handler.goAway(lastStreamId, errorCode.getOrThrow())
    }

    private func readHeaders(handler: H2Connection, len: Int64, flags: Byte, streamId: UInt32) {
        if (streamId == 0) {
            throw HttpException("TYPE_HEADERS streamId == 0")
        }

        var length = len
        var endStream: Bool = (flags & FLAG_END_STREAM) != 0
        var padding = 0

        if ((flags & FLAG_PADDED) != 0) {
            padding = Int64(source.readByte())
        }

        if ((flags & FLAG_PRIORITY) != 0) {
            readPriority(streamId)
            length -= 5
            // account for above read.
        }

        length = lengthWithoutPadding(length, flags, padding)
        let arr = ByteBuffer()
        source.read(arr, length)
        source.skipBuffer(padding)

        while (endStream) {
            endStream = readContinuationHeader(arr, streamId)
        }

        let headerBlock: Array<HeaderField> = frameDecode.decode(readToEnd(arr))
        handler.headers(endStream, streamId, -1, headerBlock)
    }

    func readContinuationHeader(arr: ByteBuffer, previousStreamId: UInt32) {
        let length = readMedium(source)
        let frameType: Byte = source.readByte()
        let flags: Byte = source.readByte()
        let streamId: UInt32 = UInt32(readInt32(source) & 0x7fffffff)

        if (frameType != TYPE_CONTINUATION) {
            throw HttpException("${frameType} != TYPE_CONTINUATION")
        }

        if (previousStreamId != streamId) {
            throw HttpException("TYPE_CONTINUATION streamId changed")
        }

        source.read(arr, length)
        return (flags & FLAG_END_STREAM) != 0
    }

    private func readData(handler: H2Connection, len: Int64, flags: Byte, streamId: UInt32) {
        if (streamId == 0) {
            throw HttpException("TYPE_DATA streamId == 0")
        }

        let inFinished: Bool = (flags & FLAG_END_STREAM) != 0
        let gzipped: Bool = (flags & FLAG_COMPRESSED) != 0

        if (gzipped) {
            throw HttpException("PROTOCOL_ERROR: FLAG_COMPRESSED without SETTINGS_COMPRESS_DATA")
        }

        var padding = 0
        if ((flags & FLAG_PADDED) != 0) {
            padding = Int64(source.readByte())
        }

        let length = lengthWithoutPadding(len, flags, padding)
        handler.data(inFinished, streamId, source, length)
        source.skipBuffer(padding)
    }

    private func readPing(handler: H2Connection, length: Int64, flags: Byte, streamId: UInt32) {
        if (length != 8) {
            throw HttpException("TYPE_PING length != 8: ${length}")
        }

        if (streamId != 0) {
            throw HttpException("TYPE_PING streamId != 0")
        }

        let payload1 = readInt32(source)
        let payload2 = readInt32(source)
        let ack: Bool = (flags & FLAG_ACK) != 0
        handler.ping(ack, payload1, payload2)
    }

    private func readSettings(handler: H2Connection, length: Int64, flags: Byte, streamId: UInt32) {
        if (streamId != 0) {
            throw HttpException("TYPE_SETTINGS streamId != 0")
        }

        if ((flags & FLAG_ACK) != 0) {
            if (length != 0) {
                throw HttpException("FRAME_SIZE_ERROR ack frame should be empty!")
            }
            return
        }

        if (length % 6 != 0) {
            throw HttpException("TYPE_SETTINGS length %% 6 != 0: ${length}")
        }

        let settings: Settings = Settings()

        for (_ in 0..length : 6) {
            var id = readInt16(source) & 0xFFFF
            let value = Int32(readInt32(source))
            match (id) {
                case 1 => // SETTINGS_HEADER_TABLE_SIZE
                ()
                case 2 =>
                        // SETTINGS_ENABLE_PUSH
                        if (value != 0 && value != 1) {
                    throw HttpException("PROTOCOL_ERROR SETTINGS_ENABLE_PUSH != 0 or 1")
                }
                case 3 => // SETTINGS_MAX_CONCURRENT_STREAMS
                id = 4 // Renumbered in draft 10
                case 4 =>
                    // SETTINGS_INITIAL_WINDOW_SIZE
                    id = 7 // Renumbered in draft 10
                    if (value < 0) {
                        throw HttpException("PROTOCOL_ERROR SETTINGS_INITIAL_WINDOW_SIZE should < 2^31 - 1")
                    }
                case 5 => if (Int64(value) < INITIAL_MAX_FRAME_SIZE || value > 16777215) {
                    throw HttpException("PROTOCOL_ERROR SETTINGS_MAX_FRAME_SIZE: ${value}")
                }
                case 6 => // SETTINGS_MAX_HEADER_LIST_SIZE
                ()
                case _ => // Must ignore setting with unknown id
                ()
            }
            settings.setting(id, Int64(value))
        }
        handler.settings(settings)
    }
}