Skip to content

Commit faa72de

Browse files
Document FilesResponse padding as defensive, add aligned content test
The C server asserts file content is always word-aligned (SQLite pages are multiples of 512) and writes no explicit padding. Document this in the docstring and comments, and add a test for realistic aligned content alongside the existing defensive non-aligned test. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 90de862 commit faa72de

2 files changed

Lines changed: 33 additions & 20 deletions

File tree

src/dqlitewire/messages/responses.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,10 @@ class FilesResponse(Message):
282282
"""Database dump files response.
283283
284284
Body: uint64 count, then repeated (text filename, uint64 size, raw bytes content)
285+
286+
Note: the C server asserts content size is always word-aligned (SQLite pages
287+
are multiples of 512), so no padding is written after content. The decoder
288+
handles padding defensively in case of non-aligned sizes.
285289
"""
286290

287291
MSG_TYPE: ClassVar[int] = ResponseType.FILES
@@ -296,8 +300,11 @@ def encode_body(self) -> bytes:
296300
result += encode_text(name)
297301
result += encode_uint64(len(content))
298302
result += content
303+
# The C server only produces word-aligned content (SQLite pages),
304+
# but we pad defensively for correctness with arbitrary content.
299305
padding = pad_to_word(len(content))
300-
result += b"\x00" * padding
306+
if padding:
307+
result += b"\x00" * padding
301308
return result
302309

303310
@classmethod
@@ -314,6 +321,8 @@ def decode_body(cls, data: bytes) -> "FilesResponse":
314321
size = decode_uint64(data[offset:])
315322
offset += 8
316323
content = data[offset : offset + size]
324+
# Content from the C server is always word-aligned, but we
325+
# account for padding defensively.
317326
offset += size + pad_to_word(size)
318327
files[name] = content
319328
return cls(files)

tests/test_messages_responses.py

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -293,12 +293,25 @@ def test_roundtrip_single_file(self) -> None:
293293
decoded = FilesResponse.decode_body(encoded[HEADER_SIZE:])
294294
assert decoded.files["main.db"] == b"\x00\x01\x02\x03"
295295

296+
def test_roundtrip_aligned_content(self) -> None:
297+
"""Real dqlite content is always word-aligned (SQLite pages are multiples of 512)."""
298+
page = b"\x00" * 512 # Realistic SQLite page size
299+
msg = FilesResponse(
300+
files={
301+
"main.db": page,
302+
"wal.db": page + page,
303+
}
304+
)
305+
encoded = msg.encode()
306+
decoded = FilesResponse.decode_body(encoded[HEADER_SIZE:])
307+
assert decoded.files["main.db"] == page
308+
assert decoded.files["wal.db"] == page + page
309+
296310
def test_roundtrip_non_aligned_content(self) -> None:
297-
"""File content not aligned to 8 bytes must still roundtrip correctly.
311+
"""Non-aligned content is handled defensively with padding.
298312
299-
The Go implementation uses blob-style encoding with padding to word
300-
boundary after raw content bytes. This test verifies that non-aligned
301-
content sizes work correctly with multiple files.
313+
The C server asserts content is always word-aligned, but the Python
314+
implementation pads defensively for correctness with arbitrary content.
302315
"""
303316
msg = FilesResponse(
304317
files={
@@ -311,22 +324,13 @@ def test_roundtrip_non_aligned_content(self) -> None:
311324
assert decoded.files["file1.db"] == b"\x01\x02\x03"
312325
assert decoded.files["file2.db"] == b"\x04\x05\x06\x07\x08\x09\x0a"
313326

314-
def test_content_padded_to_word_boundary(self) -> None:
315-
"""Each file's content must be padded so subsequent fields stay aligned."""
316-
from dqlitewire.types import decode_text, decode_uint64
317-
318-
msg = FilesResponse(files={"a": b"\x01\x02\x03"})
327+
def test_aligned_content_has_no_padding(self) -> None:
328+
"""Word-aligned content must not produce any extra padding bytes."""
329+
content = b"\x00" * 16 # exactly 2 words
330+
msg = FilesResponse(files={"a.db": content})
319331
body = msg.encode_body()
320-
offset = 8 # skip count
321-
_name, consumed = decode_text(body[offset:])
322-
offset += consumed
323-
size = decode_uint64(body[offset:])
324-
offset += 8
325-
assert size == 3
326-
# Content (3 bytes) + padding should advance to next word boundary
327-
content_with_padding = offset + size + (8 - size % 8) % 8
328-
# Total body should account for padding
329-
assert len(body) >= content_with_padding
332+
# count(8) + name "a.db\0"(8) + size(8) + content(16) = 40
333+
assert len(body) == 40
330334

331335

332336
class TestServersResponse:

0 commit comments

Comments
 (0)