Skip to content

Commit a0a1cf8

Browse files
Validate LeaderRequest body length and reserved value on decode
``LeaderRequest.encode_body`` writes a single uint64 reserved word of 0. The decoder was accepting any payload and ignoring the field entirely, so truncated / extended bodies and non-zero reserved values silently produced a valid ``LeaderRequest()``. Assert the length is 8 bytes and the reserved word is 0, raising ``DecodeError`` otherwise — same strict-decode pattern as the Header.reserved and zero-column row marker checks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 8454a4b commit a0a1cf8

2 files changed

Lines changed: 36 additions & 0 deletions

File tree

src/dqlitewire/messages/requests.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ def encode_body(self) -> bytes:
4646

4747
@classmethod
4848
def decode_body(cls, data: bytes, schema: int = 0) -> "LeaderRequest":
49+
# Strict decode symmetric with ``encode_body``: the body is
50+
# exactly one uint64 reserved word, defined as 0 by upstream.
51+
# Reject truncated/extended bodies and non-zero reserved values
52+
# rather than silently accepting them.
53+
if len(data) != 8:
54+
raise DecodeError(f"LeaderRequest body must be 8 bytes, got {len(data)}")
55+
reserved = decode_uint64(data)
56+
if reserved != 0:
57+
raise DecodeError(f"LeaderRequest reserved field must be 0, got {reserved}")
4958
return cls()
5059

5160

tests/test_messages_requests.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,33 @@ def test_roundtrip(self) -> None:
7171
decoded = LeaderRequest.decode_body(encoded[HEADER_SIZE:])
7272
assert isinstance(decoded, LeaderRequest)
7373

74+
def test_decode_rejects_short_body(self) -> None:
75+
import pytest
76+
77+
from dqlitewire.exceptions import DecodeError
78+
79+
with pytest.raises(DecodeError, match="must be 8 bytes"):
80+
LeaderRequest.decode_body(b"")
81+
with pytest.raises(DecodeError, match="must be 8 bytes"):
82+
LeaderRequest.decode_body(b"\x00" * 7)
83+
84+
def test_decode_rejects_extended_body(self) -> None:
85+
import pytest
86+
87+
from dqlitewire.exceptions import DecodeError
88+
89+
with pytest.raises(DecodeError, match="must be 8 bytes"):
90+
LeaderRequest.decode_body(b"\x00" * 16)
91+
92+
def test_decode_rejects_nonzero_reserved(self) -> None:
93+
import pytest
94+
95+
from dqlitewire.exceptions import DecodeError
96+
from dqlitewire.types import encode_uint64
97+
98+
with pytest.raises(DecodeError, match="reserved field must be 0"):
99+
LeaderRequest.decode_body(encode_uint64(1))
100+
74101

75102
class TestClientRequest:
76103
def test_encode(self) -> None:

0 commit comments

Comments
 (0)