/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
* This source file is part of the Cangjie project, licensed under Apache-2.0
* with Runtime Library Exception.
*
* See https://cangjie-lang.cn/pages/LICENSE for license information.
*/
/**
* @file
*
* This is a library for Decoder class.
*/
package stdx.net.http
import stdx.log.*
// cjlint-ignore -start !G.OTH.03
/**
* HPACK Decoder
* A decoder decode HPACK-encoded data frame to HTTP header list.
*
* https://www.rfc-editor.org/rfc/rfc7541#section-3
*/
// cjlint-ignore -end
class Decoder {
private let headerTable: HeaderTable
/**
* The value of Server.headerTableSize.
* The value should be send to peer as SETTINGS_HEADER_TABLE_SIZE.
* The value will be used to validate the new maximum table size while decoding 'Dynamic Table Size Update'.
*/
private var headerTableSizeLimit: Int64 = 4096
/**
* For decoder, this value will set by user by using the SETTINGS_MAX_HEADER_LIST_SIZE.
* The initial value of this setting is unlimited.
*/
var maxHeaderListSize = -1
/**
* Constructor
*/
Decoder(let encoder: Encoder, let name!: String = "unknown", var _logger!: Logger = mutexLogger()) {
headerTable = HeaderTable("${name}.decoder", _logger)
}
/**
* Logger
*/
mut prop logger: Logger {
get() {
return _logger
}
set(v) {
_logger = v
headerTable.logger = v
}
}
// cjlint-ignore -start !G.OTH.03
/**
* Decode HPACK-encoded data frame to HTTP header list.
*
* https://www.rfc-editor.org/rfc/rfc7541#section-3.2
*
* @param headerBlock HPACK-encoded data frame.
* @return FieldsList HTTP header list.
*
* @throws HpackException, if index not exists.
*/
// cjlint-ignore -end
func decode(headerBlock: Array<UInt8>): FieldsList {
let headerList = FieldsList()
let iterator = ByteIterator(headerBlock.iterator())
var totalHeaderListSize: Int64 = 0 // headerSize = name.size + value.size + 32
var isFirstByte = true
while (let Some(b) <- iterator.next()) {
let field: HeaderField = match {
// cjlint-ignore -start !G.OTH.03
/*
* Indexed Header Field Representation
* https://www.rfc-editor.org/rfc/rfc7541#section-6.1
*
* 0 1 2 3 4 5 6 7
* +---+---+---+---+---+---+---+---+
* | 1 | Index (7+) |
* +---+---------------------------+
*/
// cjlint-ignore -end
case (b & 0x80) == 0x80 =>
let index = decodeInt(b, 7, iterator)
let field = headerTable.get(index) // Will throw exception while index not exists
if (logger.enabled(LogLevel.TRACE)) {
httpLogTrace(logger,
"[${this.name}.Decoder#decode] index7: ${index}, header:(${field[0]}, ${field[1]})")
}
field
// cjlint-ignore -start !G.OTH.03
/*
* Literal Header Field with Incremental Indexing
* https://www.rfc-editor.org/rfc/rfc7541#section-6.2.1
*
* 0 1 2 3 4 5 6 7
* +---+---+---+---+---+---+---+---+
* | 0 | 1 | Index (6+) |
* +---+---+-----------------------+
* | H | Value Length (7+) |
* +---+---------------------------+
* | Value String (Length octets) |
* +-------------------------------+
*
* or
*
* 0 1 2 3 4 5 6 7
* +---+---+---+---+---+---+---+---+
* | 0 | 1 | 0 |
* +---+---+-----------------------+
* | H | Name Length (7+) |
* +---+---------------------------+
* | Name String (Length octets) |
* +---+---------------------------+
* | H | Value Length (7+) |
* +---+---------------------------+
* | Value String (Length octets) |
* +-------------------------------+
*/
// cjlint-ignore -end
case (b & 0x40) == 0x40 =>
let field = parseLiteral(b, 6, iterator)
headerTable.insert(field)
field
// cjlint-ignore -start !G.OTH.03
/*
* Dynamic Table Size Update
* https://www.rfc-editor.org/rfc/rfc7541#section-6.3
*
* 0 1 2 3 4 5 6 7
* +---+---+---+---+---+---+---+---+
* | 0 | 0 | 1 | Max size (5+) |
* +---+---------------------------+
*/
// cjlint-ignore -end
case (b & 0x20) == 0x20 =>
if (!isFirstByte) {
throw HpackException(
"Dynamic table size update MUST occur at the beginning of the first header block.")
}
let size = decodeInt(b, 5, iterator)
updateDynamicTableSize(size)
if (logger.enabled(LogLevel.TRACE)) {
httpLogTrace(logger, "[${this.name}.Decoder#decode] Max size: ${size}")
}
continue
// cjlint-ignore -start !G.OTH.03
/*
* Literal Header Field Never Indexed
* https://www.rfc-editor.org/rfc/rfc7541#section-6.2.3
*
* 0 1 2 3 4 5 6 7
* +---+---+---+---+---+---+---+---+
* | 0 | 0 | 0 | 1 | Index (4+) |
* +---+---+-----------------------+
* | H | Value Length (7+) |
* +---+---------------------------+
* | Value String (Length octets) |
* +-------------------------------+
*
* or
*
* 0 1 2 3 4 5 6 7
* +---+---+---+---+---+---+---+---+
* | 0 | 0 | 0 | 1 | 0 |
* +---+---+-----------------------+
* | H | Name Length (7+) |
* +---+---------------------------+
* | Name String (Length octets) |
* +---+---------------------------+
* | H | Value Length (7+) |
* +---+---------------------------+
* | Value String (Length octets) |
* +-------------------------------+
*/
// cjlint-ignore -end
case (b & 0x10) == 0x10 =>
let field = parseLiteral(b, 4, iterator)
// Intermediaries MUST use the same representation for encoding this header field.
encoder.setSensitive(field)
field
// cjlint-ignore -start !G.OTH.03
/*
* Literal Header Field without Indexing
* https://www.rfc-editor.org/rfc/rfc7541#section-6.2.2
*
* 0 1 2 3 4 5 6 7
* +---+---+---+---+---+---+---+---+
* | 0 | 0 | 0 | 0 | Index (4+) |
* +---+---+-----------------------+
* | H | Value Length (7+) |
* +---+---------------------------+
* | Value String (Length octets) |
* +-------------------------------+
*
* or
*
* 0 1 2 3 4 5 6 7
* +---+---+---+---+---+---+---+---+
* | 0 | 0 | 0 | 0 | 0 |
* +---+---+-----------------------+
* | H | Name Length (7+) |
* +---+---------------------------+
* | Name String (Length octets) |
* +---+---------------------------+
* | H | Value Length (7+) |
* +---+---------------------------+
* | Value String (Length octets) |
* +-------------------------------+
*/
// cjlint-ignore -end
case _ => parseLiteral(b, 4, iterator)
}
isFirstByte = false // used by check 0x20
// check total header list size
if (maxHeaderListSize != -1) {
totalHeaderListSize += fieldSize(field)
if (totalHeaderListSize > maxHeaderListSize) {
throw HpackException(
"Total size:${totalHeaderListSize} out of SettingsMaxHeaderListSize :${maxHeaderListSize}.")
}
}
// append to header list
headerList.add(field)
}
return headerList
}
private func parseLiteral(b: Byte, n: Int64, nextBytes: ByteIterator): HeaderField {
let index = decodeInt(b, n, nextBytes)
let name: String
if (index == 0) {
name = decodeString(nextBytes.nextByte(), nextBytes)
} else {
let field = headerTable.get(index) // Will throw exception while index not exists
name = field[0]
}
let value = decodeString(nextBytes.nextByte(), nextBytes)
if (logger.enabled(LogLevel.TRACE)) {
httpLogTrace(logger, "[${this.name}.Decoder#parseLiteral] index${n}: ${index}, header:(${name}, ${value})")
}
return (name, value)
}
func setHeaderTableSizeLimit(limit: Int64): Unit {
this.headerTableSizeLimit = limit
headerTable.headerTableSize = limit
}
func updateDynamicTableSize(newMaxSize: Int64): Unit {
if (newMaxSize > this.headerTableSizeLimit) {
throw HpackException(
"Failed to update dynamic table size, new size: ${newMaxSize} exceeds the limit: ${this.headerTableSizeLimit}."
)
}
headerTable.headerTableSize = newMaxSize
}
// cjlint-ignore -start !G.OTH.03
/**
* String Literal Representation
* https://www.rfc-editor.org/rfc/rfc7541#section-5.2
*
* 0 1 2 3 4 5 6 7
* +---+---+---+---+---+---+---+---+
* | H | String Length (7+) |
* +---+---------------------------+
* | String Data (Length octets) |
* +-------------------------------+
*/
// cjlint-ignore -end
func decodeString(b: Byte, nextBytes: ByteIterator): String {
let len = decodeInt(b, 7, nextBytes)
// check header size
if (maxHeaderListSize != -1 && maxHeaderListSize < len) {
throw HpackException(
"Failed to decode String, header string size:${len} out of SettingsMaxHeaderListSize :${maxHeaderListSize}."
)
}
var bytes = Array<Byte>(len, {_ => nextBytes.nextByte()})
let isHuff = (Int64(b) & 0x80) == 0x80
if (isHuff) {
bytes = QuickHuffmanDecoder.decode(bytes)
}
try {
return String.fromUtf8(bytes)
} catch (e: Exception) {
throw HpackException("Failed to decode string: ${e.message}.")
}
}
}
// cjlint-ignore -start !G.OTH.03
/**
* Integer Representation
* https://www.rfc-editor.org/rfc/rfc7541#section-5.1
*
* 0 1 2 3 4 5 6 7
* +---+---+---+---+---+---+---+---+
* | ? | ? | ? | Value |
* +---+---+---+-------------------+
*
* or
*
* 0 1 2 3 4 5 6 7
* +---+---+---+---+---+---+---+---+
* | ? | ? | ? | 1 1 1 1 1 |
* +---+---+---+-------------------+
* | 1 | Value-(2^N-1) LSB |
* +---+---------------------------+
* ...
* +---+---------------------------+
* | 0 | Value-(2^N-1) MSB |
* +---+---------------------------+
*/
// cjlint-ignore -end
func decodeInt(b: Byte, n: Int64, nextBytes: ByteIterator): Int64 {
let maskN = (1 << n) - 1
var value = Int64(b) & maskN
if (value < maskN) {
return value
}
let mask7 = (1 << 7) - 1
var m = 1
var t: Int64
do {
t = Int64(nextBytes.nextByte())
value += (t & mask7) * m
m <<= 7
if (m < 0) {
throw HpackException("Failed to decode integer, number too large.")
}
} while ((t & 0x80) == 0x80)
return value
}
class ByteIterator {
ByteIterator(let it: Iterator<Byte>) {}
func nextByte(): Byte {
return it.next() ?? throw HpackException("Failed to decode data, next byte required but not found.") // cjlint-ignore !G.EXP.03
}
func next(): Option<Byte> {
return it.next()
}
}