This is an automated email from the ASF dual-hosted git repository. piotr pushed a commit to branch non_string_header_key in repository https://gitbox.apache.org/repos/asf/iggy.git
commit 7e10a96332b3190321681417358b7e2718a77e84 Author: spetz <[email protected]> AuthorDate: Fri Jan 30 13:53:10 2026 +0100 Add boundary checks for node, java, cli, make key/value private --- .../src/cli/binary_message/poll_messages.rs | 4 +- core/cli/src/args/message.rs | 24 ++- core/common/src/types/message/user_headers.rs | 49 ++--- .../tests/cli/message/test_message_poll_command.rs | 2 +- .../tests/cli/message/test_message_send_command.rs | 7 +- .../message-headers/typed-headers/consumer/main.rs | 4 +- .../java/org/apache/iggy/message/HeaderValue.java | 203 ++++++++++++++++++++- .../main/java/org/apache/iggy/message/Message.java | 2 +- .../org/apache/iggy/serde/BytesDeserializer.java | 8 +- .../org/apache/iggy/serde/BytesSerializer.java | 4 +- .../client/blocking/tcp/BytesSerializerTest.java | 9 +- .../apache/iggy/serde/BytesDeserializerTest.java | 4 +- foreign/node/src/wire/message/header.utils.ts | 11 ++ 13 files changed, 284 insertions(+), 47 deletions(-) diff --git a/core/binary_protocol/src/cli/binary_message/poll_messages.rs b/core/binary_protocol/src/cli/binary_message/poll_messages.rs index dd210ddb8..7b6725512 100644 --- a/core/binary_protocol/src/cli/binary_message/poll_messages.rs +++ b/core/binary_protocol/src/cli/binary_message/poll_messages.rs @@ -88,7 +88,7 @@ impl PollMessagesCmd { match HashMap::<HeaderKey, HeaderValue>::from_bytes(user_headers.clone()) { Ok(headers) => headers .iter() - .map(|(k, v)| (k.clone(), v.kind)) + .map(|(k, v)| (k.clone(), v.kind())) .collect::<Vec<_>>(), Err(e) => { tracing::error!("Failed to parse user headers, error: {e}"); @@ -146,7 +146,7 @@ impl PollMessagesCmd { .as_ref() .map(|h| { h.get(key) - .filter(|v| v.kind == *kind) + .filter(|v| v.kind() == *kind) .map(|v| v.to_string_value()) .unwrap_or_default() }) diff --git a/core/cli/src/args/message.rs b/core/cli/src/args/message.rs index 5157722fe..9b70df702 100644 --- a/core/cli/src/args/message.rs +++ b/core/cli/src/args/message.rs @@ -120,15 +120,14 @@ pub(crate) struct SendMessagesArgs { /// Parse Header Key, Kind and Value from the string separated by a ':' fn parse_key_val(s: &str) -> Result<(HeaderKey, HeaderValue), IggyError> { - let lower = s.to_lowercase(); - let parts = lower.split(':').collect::<Vec<_>>(); + let parts = s.splitn(3, ':').collect::<Vec<_>>(); if parts.len() != 3 { return Err(IggyError::InvalidFormat); } let key = HeaderKey::from_str(parts[0])?; - let kind = HeaderKind::from_str(parts[1])?; + let kind = HeaderKind::from_str(&parts[1].to_lowercase())?; let value_str = parts[2]; let value = match kind { @@ -351,4 +350,23 @@ mod tests { let result = parse_key_val("key:uint8:69.42"); assert!(result.is_err()); } + + #[test] + fn parse_key_val_should_preserve_value_case() { + let expected_value = "HelloWorld"; + let result = parse_key_val(&format!("key:string:{expected_value}")); + assert!(result.is_ok()); + let (_, value) = result.unwrap(); + assert_eq!(value.as_str().unwrap(), expected_value); + } + + #[test] + fn parse_key_val_should_preserve_colons_in_value() { + let expected_value = "http://example.com:8080"; + let result = parse_key_val(&format!("url:string:{expected_value}")); + assert!(result.is_ok()); + let (key, value) = result.unwrap(); + assert_eq!(key, HeaderKey::from_str("url").unwrap()); + assert_eq!(value.as_str().unwrap(), expected_value); + } } diff --git a/core/common/src/types/message/user_headers.rs b/core/common/src/types/message/user_headers.rs index 353bbc072..aaec4ffe0 100644 --- a/core/common/src/types/message/user_headers.rs +++ b/core/common/src/types/message/user_headers.rs @@ -146,15 +146,30 @@ pub struct ValueMarker; #[serde_as] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] pub struct HeaderField<T> { - /// The type of value stored in this header field. - pub kind: HeaderKind, - /// The raw bytes of the value, encoded according to `kind`. + kind: HeaderKind, #[serde_as(as = "Base64")] - pub value: Bytes, + value: Bytes, #[serde(skip)] _marker: PhantomData<T>, } +impl<T> HeaderField<T> { + /// Returns the kind of this header field. + pub fn kind(&self) -> HeaderKind { + self.kind + } + + /// Returns a clone of the raw bytes value. + pub fn value(&self) -> Bytes { + self.value.clone() + } + + /// Returns a reference to the raw bytes value. + pub fn as_bytes(&self) -> &[u8] { + &self.value + } +} + /// Indicates the type of value stored in a [`HeaderField`]. /// /// This enum is used to track what type of data is stored in the header's raw bytes, @@ -923,14 +938,14 @@ impl BytesSerializable for HashMap<HeaderKey, HeaderValue> { let mut bytes = BytesMut::new(); for (key, value) in self { - bytes.put_u8(key.kind.as_code()); + bytes.put_u8(key.kind().as_code()); #[allow(clippy::cast_possible_truncation)] - bytes.put_u32_le(key.value.len() as u32); - bytes.put_slice(&key.value); - bytes.put_u8(value.kind.as_code()); + bytes.put_u32_le(key.as_bytes().len() as u32); + bytes.put_slice(key.as_bytes()); + bytes.put_u8(value.kind().as_code()); #[allow(clippy::cast_possible_truncation)] - bytes.put_u32_le(value.value.len() as u32); - bytes.put_slice(&value.value); + bytes.put_u32_le(value.as_bytes().len() as u32); + bytes.put_slice(value.as_bytes()); } bytes.freeze() @@ -1005,16 +1020,8 @@ impl BytesSerializable for HashMap<HeaderKey, HeaderValue> { position += value_length; headers.insert( - HeaderKey { - kind: key_kind, - value: Bytes::from(key_value), - _marker: PhantomData, - }, - HeaderValue { - kind: value_kind, - value: Bytes::from(value_value), - _marker: PhantomData, - }, + HeaderKey::new_unchecked(key_kind, &key_value), + HeaderValue::new_unchecked(value_kind, &value_value), ); } @@ -1026,7 +1033,7 @@ pub fn get_user_headers_size(headers: &Option<HashMap<HeaderKey, HeaderValue>>) let mut size = 0; if let Some(headers) = headers { for (key, value) in headers { - size += 1 + 4 + key.value.len() as u32 + 1 + 4 + value.value.len() as u32; + size += 1 + 4 + key.as_bytes().len() as u32 + 1 + 4 + value.as_bytes().len() as u32; } } Some(size) diff --git a/core/integration/tests/cli/message/test_message_poll_command.rs b/core/integration/tests/cli/message/test_message_poll_command.rs index 867a8aca1..7d7532912 100644 --- a/core/integration/tests/cli/message/test_message_poll_command.rs +++ b/core/integration/tests/cli/message/test_message_poll_command.rs @@ -199,7 +199,7 @@ impl IggyCmdTestCase for TestMessagePollCmd { "Header: {}", self.headers.0.to_string_value() ))) - .stdout(contains(self.headers.1.kind.to_string())) + .stdout(contains(self.headers.1.kind().to_string())) .stdout(contains(self.headers.1.to_string_value()).count(self.message_count)) } diff --git a/core/integration/tests/cli/message/test_message_send_command.rs b/core/integration/tests/cli/message/test_message_send_command.rs index fea3498e5..97ed4fe66 100644 --- a/core/integration/tests/cli/message/test_message_send_command.rs +++ b/core/integration/tests/cli/message/test_message_send_command.rs @@ -110,7 +110,12 @@ impl TestMessageSendCmd { header .iter() .map(|(k, v)| { - format!("{}:{}:{}", k.to_string_value(), v.kind, v.to_string_value()) + format!( + "{}:{}:{}", + k.to_string_value(), + v.kind(), + v.to_string_value() + ) }) .collect::<Vec<_>>() .join(","), diff --git a/examples/rust/src/message-headers/typed-headers/consumer/main.rs b/examples/rust/src/message-headers/typed-headers/consumer/main.rs index ce9a3be76..496f9b09d 100644 --- a/examples/rust/src/message-headers/typed-headers/consumer/main.rs +++ b/examples/rust/src/message-headers/typed-headers/consumer/main.rs @@ -59,9 +59,9 @@ fn handle_message(message: &IggyMessage) -> Result<(), Box<dyn Error>> { for (key, value) in &headers_map { info!( " key: [kind={}, value={}] -> value: [kind={}, value={}]", - key.kind, + key.kind(), key.to_string_value(), - value.kind, + value.kind(), value.to_string_value() ); } diff --git a/foreign/java/java-sdk/src/main/java/org/apache/iggy/message/HeaderValue.java b/foreign/java/java-sdk/src/main/java/org/apache/iggy/message/HeaderValue.java index 90b3bf053..dba7918ef 100644 --- a/foreign/java/java-sdk/src/main/java/org/apache/iggy/message/HeaderValue.java +++ b/foreign/java/java-sdk/src/main/java/org/apache/iggy/message/HeaderValue.java @@ -22,15 +22,212 @@ package org.apache.iggy.message; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.Base64; -public record HeaderValue(HeaderKind kind, String value) { +public record HeaderValue(HeaderKind kind, byte[] value) { @JsonCreator public static HeaderValue fromJson( @JsonProperty("kind") HeaderKind kind, @JsonProperty("value") String base64Value) { byte[] decodedValue = Base64.getDecoder().decode(base64Value); - String stringValue = new String(decodedValue, StandardCharsets.UTF_8); - return new HeaderValue(kind, stringValue); + return new HeaderValue(kind, decodedValue); + } + + public static HeaderValue fromString(String val) { + if (val.isEmpty() || val.length() > 255) { + throw new IllegalArgumentException("Value has incorrect size, must be between 1 and 255"); + } + return new HeaderValue(HeaderKind.String, val.getBytes(StandardCharsets.UTF_8)); + } + + public static HeaderValue fromBool(boolean val) { + return new HeaderValue(HeaderKind.Bool, new byte[] {(byte) (val ? 1 : 0)}); + } + + public static HeaderValue fromInt8(byte val) { + return new HeaderValue(HeaderKind.Int8, new byte[] {val}); + } + + public static HeaderValue fromInt16(short val) { + ByteBuffer buffer = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN); + buffer.putShort(val); + return new HeaderValue(HeaderKind.Int16, buffer.array()); + } + + public static HeaderValue fromInt32(int val) { + ByteBuffer buffer = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(val); + return new HeaderValue(HeaderKind.Int32, buffer.array()); + } + + public static HeaderValue fromInt64(long val) { + ByteBuffer buffer = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN); + buffer.putLong(val); + return new HeaderValue(HeaderKind.Int64, buffer.array()); + } + + public static HeaderValue fromUint8(short val) { + if (val < 0 || val > 255) { + throw new IllegalArgumentException("Value must be between 0 and 255"); + } + return new HeaderValue(HeaderKind.Uint8, new byte[] {(byte) val}); + } + + public static HeaderValue fromUint16(int val) { + if (val < 0 || val > 65535) { + throw new IllegalArgumentException("Value must be between 0 and 65535"); + } + ByteBuffer buffer = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN); + buffer.putShort((short) val); + return new HeaderValue(HeaderKind.Uint16, buffer.array()); + } + + public static HeaderValue fromUint32(long val) { + if (val < 0 || val > 4294967295L) { + throw new IllegalArgumentException("Value must be between 0 and 4294967295"); + } + ByteBuffer buffer = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt((int) val); + return new HeaderValue(HeaderKind.Uint32, buffer.array()); + } + + public static HeaderValue fromFloat32(float val) { + ByteBuffer buffer = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); + buffer.putFloat(val); + return new HeaderValue(HeaderKind.Float32, buffer.array()); + } + + public static HeaderValue fromFloat64(double val) { + ByteBuffer buffer = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN); + buffer.putDouble(val); + return new HeaderValue(HeaderKind.Float64, buffer.array()); + } + + public static HeaderValue fromRaw(byte[] val) { + if (val.length == 0 || val.length > 255) { + throw new IllegalArgumentException("Value has incorrect size, must be between 1 and 255"); + } + return new HeaderValue(HeaderKind.Raw, val); + } + + public String asString() { + if (kind != HeaderKind.String) { + throw new IllegalStateException("Header value is not a string, kind: " + kind); + } + return new String(value, StandardCharsets.UTF_8); + } + + public boolean asBool() { + if (kind != HeaderKind.Bool) { + throw new IllegalStateException("Header value is not a bool, kind: " + kind); + } + return value[0] == 1; + } + + public byte asInt8() { + if (kind != HeaderKind.Int8) { + throw new IllegalStateException("Header value is not an int8, kind: " + kind); + } + return value[0]; + } + + public short asInt16() { + if (kind != HeaderKind.Int16) { + throw new IllegalStateException("Header value is not an int16, kind: " + kind); + } + return ByteBuffer.wrap(value).order(ByteOrder.LITTLE_ENDIAN).getShort(); + } + + public int asInt32() { + if (kind != HeaderKind.Int32) { + throw new IllegalStateException("Header value is not an int32, kind: " + kind); + } + return ByteBuffer.wrap(value).order(ByteOrder.LITTLE_ENDIAN).getInt(); + } + + public long asInt64() { + if (kind != HeaderKind.Int64) { + throw new IllegalStateException("Header value is not an int64, kind: " + kind); + } + return ByteBuffer.wrap(value).order(ByteOrder.LITTLE_ENDIAN).getLong(); + } + + public short asUint8() { + if (kind != HeaderKind.Uint8) { + throw new IllegalStateException("Header value is not a uint8, kind: " + kind); + } + return (short) (value[0] & 0xFF); + } + + public int asUint16() { + if (kind != HeaderKind.Uint16) { + throw new IllegalStateException("Header value is not a uint16, kind: " + kind); + } + return (ByteBuffer.wrap(value).order(ByteOrder.LITTLE_ENDIAN).getShort() & 0xFFFF); + } + + public long asUint32() { + if (kind != HeaderKind.Uint32) { + throw new IllegalStateException("Header value is not a uint32, kind: " + kind); + } + return (ByteBuffer.wrap(value).order(ByteOrder.LITTLE_ENDIAN).getInt() & 0xFFFFFFFFL); + } + + public float asFloat32() { + if (kind != HeaderKind.Float32) { + throw new IllegalStateException("Header value is not a float32, kind: " + kind); + } + return ByteBuffer.wrap(value).order(ByteOrder.LITTLE_ENDIAN).getFloat(); + } + + public double asFloat64() { + if (kind != HeaderKind.Float64) { + throw new IllegalStateException("Header value is not a float64, kind: " + kind); + } + return ByteBuffer.wrap(value).order(ByteOrder.LITTLE_ENDIAN).getDouble(); + } + + public byte[] asRaw() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + HeaderValue that = (HeaderValue) o; + return kind == that.kind && Arrays.equals(value, that.value); + } + + @Override + public int hashCode() { + int result = kind.hashCode(); + result = 31 * result + Arrays.hashCode(value); + return result; + } + + @Override + public String toString() { + return switch (kind) { + case String -> asString(); + case Bool -> String.valueOf(asBool()); + case Int8 -> String.valueOf(asInt8()); + case Int16 -> String.valueOf(asInt16()); + case Int32 -> String.valueOf(asInt32()); + case Int64 -> String.valueOf(asInt64()); + case Uint8 -> String.valueOf(asUint8()); + case Uint16 -> String.valueOf(asUint16()); + case Uint32 -> String.valueOf(asUint32()); + case Float32 -> String.valueOf(asFloat32()); + case Float64 -> String.valueOf(asFloat64()); + case Raw, Int128, Uint64, Uint128 -> Base64.getEncoder().encodeToString(value); + }; } } diff --git a/foreign/java/java-sdk/src/main/java/org/apache/iggy/message/Message.java b/foreign/java/java-sdk/src/main/java/org/apache/iggy/message/Message.java index 8e4d4bc21..144103d10 100644 --- a/foreign/java/java-sdk/src/main/java/org/apache/iggy/message/Message.java +++ b/foreign/java/java-sdk/src/main/java/org/apache/iggy/message/Message.java @@ -113,7 +113,7 @@ public record Message( long size = 0L; for (Map.Entry<HeaderKey, HeaderValue> entry : userHeaders.entrySet()) { byte[] keyBytes = entry.getKey().value(); - byte[] valueBytes = entry.getValue().value().getBytes(); + byte[] valueBytes = entry.getValue().value(); size += 1L + 4L + keyBytes.length + 1L + 4L + valueBytes.length; } return size; diff --git a/foreign/java/java-sdk/src/main/java/org/apache/iggy/serde/BytesDeserializer.java b/foreign/java/java-sdk/src/main/java/org/apache/iggy/serde/BytesDeserializer.java index ef94e1590..fcd16ca55 100644 --- a/foreign/java/java-sdk/src/main/java/org/apache/iggy/serde/BytesDeserializer.java +++ b/foreign/java/java-sdk/src/main/java/org/apache/iggy/serde/BytesDeserializer.java @@ -210,11 +210,11 @@ public final class BytesDeserializer { var userHeaderValueKindCode = userHeadersBuffer.readUnsignedByte(); var userHeaderValueLength = userHeadersBuffer.readUnsignedIntLE(); - String userHeaderValue = userHeadersBuffer - .readCharSequence(toInt(userHeaderValueLength), StandardCharsets.UTF_8) - .toString(); + byte[] userHeaderValueBytes = new byte[toInt(userHeaderValueLength)]; + userHeadersBuffer.readBytes(userHeaderValueBytes); headers.put( - userHeaderKey, new HeaderValue(HeaderKind.fromCode(userHeaderValueKindCode), userHeaderValue)); + userHeaderKey, + new HeaderValue(HeaderKind.fromCode(userHeaderValueKindCode), userHeaderValueBytes)); } userHeaders = headers; } diff --git a/foreign/java/java-sdk/src/main/java/org/apache/iggy/serde/BytesSerializer.java b/foreign/java/java-sdk/src/main/java/org/apache/iggy/serde/BytesSerializer.java index cc6f6bd11..20301ecdb 100644 --- a/foreign/java/java-sdk/src/main/java/org/apache/iggy/serde/BytesSerializer.java +++ b/foreign/java/java-sdk/src/main/java/org/apache/iggy/serde/BytesSerializer.java @@ -134,8 +134,8 @@ public final class BytesSerializer { HeaderValue value = entry.getValue(); buffer.writeByte(value.kind().asCode()); - buffer.writeIntLE(value.value().length()); - buffer.writeBytes(value.value().getBytes()); + buffer.writeIntLE(value.value().length); + buffer.writeBytes(value.value()); } return buffer; } diff --git a/foreign/java/java-sdk/src/test/java/org/apache/iggy/client/blocking/tcp/BytesSerializerTest.java b/foreign/java/java-sdk/src/test/java/org/apache/iggy/client/blocking/tcp/BytesSerializerTest.java index 99514dc4d..3453f1863 100644 --- a/foreign/java/java-sdk/src/test/java/org/apache/iggy/client/blocking/tcp/BytesSerializerTest.java +++ b/foreign/java/java-sdk/src/test/java/org/apache/iggy/client/blocking/tcp/BytesSerializerTest.java @@ -27,7 +27,6 @@ import org.apache.iggy.identifier.ConsumerId; import org.apache.iggy.identifier.StreamId; import org.apache.iggy.message.BytesMessageId; import org.apache.iggy.message.HeaderKey; -import org.apache.iggy.message.HeaderKind; import org.apache.iggy.message.HeaderValue; import org.apache.iggy.message.Message; import org.apache.iggy.message.MessageHeader; @@ -459,7 +458,7 @@ class BytesSerializerTest { void shouldSerializeSingleHeader() { // given Map<HeaderKey, HeaderValue> headers = new HashMap<>(); - headers.put(HeaderKey.fromString("key1"), new HeaderValue(HeaderKind.Raw, "value1")); + headers.put(HeaderKey.fromString("key1"), HeaderValue.fromRaw("value1".getBytes())); // when ByteBuf result = BytesSerializer.toBytes(headers); @@ -481,8 +480,8 @@ class BytesSerializerTest { void shouldSerializeMultipleHeaders() { // given Map<HeaderKey, HeaderValue> headers = new HashMap<>(); - headers.put(HeaderKey.fromString("k1"), new HeaderValue(HeaderKind.Raw, "v1")); - headers.put(HeaderKey.fromString("k2"), new HeaderValue(HeaderKind.String, "v2")); + headers.put(HeaderKey.fromString("k1"), HeaderValue.fromRaw("v1".getBytes())); + headers.put(HeaderKey.fromString("k2"), HeaderValue.fromString("v2")); // when ByteBuf result = BytesSerializer.toBytes(headers); @@ -523,7 +522,7 @@ class BytesSerializerTest { // given var messageId = new BytesMessageId(new byte[16]); Map<HeaderKey, HeaderValue> userHeaders = new HashMap<>(); - userHeaders.put(HeaderKey.fromString("key"), new HeaderValue(HeaderKind.Raw, "val")); + userHeaders.put(HeaderKey.fromString("key"), HeaderValue.fromRaw("val".getBytes())); // Calculate user headers size ByteBuf headersBuf = BytesSerializer.toBytes(userHeaders); diff --git a/foreign/java/java-sdk/src/test/java/org/apache/iggy/serde/BytesDeserializerTest.java b/foreign/java/java-sdk/src/test/java/org/apache/iggy/serde/BytesDeserializerTest.java index 001cfcdbe..36a6d5ea3 100644 --- a/foreign/java/java-sdk/src/test/java/org/apache/iggy/serde/BytesDeserializerTest.java +++ b/foreign/java/java-sdk/src/test/java/org/apache/iggy/serde/BytesDeserializerTest.java @@ -382,8 +382,8 @@ class BytesDeserializerTest { // then assertThat(message.userHeaders()).hasSize(1); - assertThat(message.userHeaders().get(HeaderKey.fromString("key")).value()) - .isEqualTo("val"); + assertThat(message.userHeaders().get(HeaderKey.fromString("key")).asRaw()) + .isEqualTo("val".getBytes()); } @Test diff --git a/foreign/node/src/wire/message/header.utils.ts b/foreign/node/src/wire/message/header.utils.ts index 2b2be17a5..34b1486f7 100644 --- a/foreign/node/src/wire/message/header.utils.ts +++ b/foreign/node/src/wire/message/header.utils.ts @@ -278,6 +278,7 @@ export const deserializeHeaderValue = ( * @param p - Buffer containing serialized headers * @param pos - Starting position in the buffer * @returns Object with bytes read and deserialized header data + * @throws Error if header key or value length is invalid (must be 1-255) */ export const deserializeHeader = ( p: Buffer, @@ -285,11 +286,21 @@ export const deserializeHeader = ( ): ParsedHeaderDeserialized => { const _keyKind = p.readUInt8(pos); const keyLength = p.readUInt32LE(pos + 1); + if (keyLength < 1 || keyLength > 255) { + throw new Error( + `Invalid header key length: ${keyLength}, must be between 1 and 255`, + ); + } const key = p.subarray(pos + 5, pos + 5 + keyLength).toString(); pos += 5 + keyLength; const valueKind = p.readUInt8(pos); const valueLength = p.readUInt32LE(pos + 1); + if (valueLength < 1 || valueLength > 255) { + throw new Error( + `Invalid header value length: ${valueLength}, must be between 1 and 255`, + ); + } const value = deserializeHeaderValue( valueKind, p.subarray(pos + 5, pos + 5 + valueLength),
