tursodatabase/turso · error · anyhow::Error
buffer too short for u32 at offset
Error message
buffer too short for u32 at offset {offset} What it means
read_u32_le is the parser's bounds-checked little-endian u32 reader; it errors when fewer than 4 bytes remain at the requested offset. In the sync server's own flow the callers pre-check lengths (e.g. scan_mvcc_log requires 56 bytes before validate_mvcc_log_header), so hitting this error means a length precondition was violated upstream or the helpers were reused with an unchecked buffer.
Solutions
- Route parsing through scan_mvcc_log, which enforces log.len() >= MVCC_LOG_HEADER_SIZE first
- Check buffer length before reading: require buf.len() >= offset + 4
- On error, log buffer length and offset to find which caller skipped its precondition
Example fix
// before
let salt = u64::from_le_bytes(log[SALT_START..SALT_END].try_into().unwrap()); // panics or reads short buffer
// after
let bytes = log
.get(SALT_START..SALT_END)
.ok_or_else(|| anyhow!("buffer too short for u64 at offset {SALT_START}"))?;
let salt = u64::from_le_bytes(bytes.try_into().expect("fixed-size salt slice")); Defensive patterns
Strategy: validation
Validate before calling
fn has_u32_at(buf: &[u8], offset: usize) -> bool {
buf.len() >= offset.saturating_add(4)
} Type guard
fn can_read_u32(buf: &[u8], offset: usize) -> bool {
offset.checked_add(4).is_some_and(|end| end <= buf.len())
} Try / catch
match read_u32_le(&log, offset) {
Ok(v) => v,
Err(err) if err.to_string().starts_with("buffer too short for u32") => {
// caller precondition failed: log len/offset and fix the missing length check
return Err(err.context(format!("len={} offset={}", log.len(), offset)));
}
Err(err) => return Err(err),
} Prevention
- Always route log parsing through scan_mvcc_log so header-length pre-checks run
- Prefer bounds-checked readers (buf.get) over direct indexing in parsers
- Add tests with buffers truncated at every field boundary
When it happens
Trigger: Calling read_u32_le (directly or via validate_mvcc_log_header / initial_mvcc_log_crc / read_mvcc_frame_boundary) with offset > buf.len() - 4, e.g. a log shorter than the fixed header passed straight to the header validator.
Common situations: Embedding or reusing these parsing helpers on truncated buffers; tests feeding short fixtures; refactors that skip the scan_mvcc_log length pre-checks.
Related errors
- buffer too short for u64 at offset
- invalid MVCC logical log frame end magic at offset
- invalid MVCC logical log frame magic at offset
- invalid MVCC logical log header flags
- invalid MVCC logical log header length
AI-assisted analysis of tursodatabase/turso@492c4a71cd (2026-08-20).
Data as JSON: /api/errors/41a0118c497dd7cc.
Report an issue: GitHub.
Appendix: source
Thrown at cli/sync_server.rs:1221
let stored_crc = read_u32_le(log, trailer_start)?;
if stored_crc != expected_crc {
return Err(anyhow!(
"MVCC logical log frame checksum mismatch at offset {offset}"
));
}
let end_magic = read_u32_le(log, trailer_start + 4)?;
if end_magic != MVCC_TX_END_MAGIC {
return Err(anyhow!(
"invalid MVCC logical log frame end magic at offset {offset}"
));
}
Ok(Some((frame_end, stored_crc)))
}
fn read_u32_le(buf: &[u8], offset: usize) -> Result<u32> {
let bytes = buf
.get(offset..offset + 4)
.ok_or_else(|| anyhow!("buffer too short for u32 at offset {offset}"))?;
Ok(u32::from_le_bytes(bytes.try_into().unwrap()))
}
fn read_u64_le(buf: &[u8], offset: usize) -> Result<u64> {
let bytes = buf
.get(offset..offset + 8)
.ok_or_else(|| anyhow!("buffer too short for u64 at offset {offset}"))?;
Ok(u64::from_le_bytes(bytes.try_into().unwrap()))
}
fn current_db_size_pages(conn: &Connection, max_frame: u64) -> Result<u64> {
if max_frame > 0 {
let frame_size = WAL_FRAME_HEADER_SIZE + PAGE_SIZE;
let mut last_frame = vec![0u8; frame_size];
let last_info = conn.wal_get_frame(max_frame, &mut last_frame)?;
Ok(last_info.db_size as u64)
} else {
Ok(0)View on GitHub (pinned to 492c4a71cd)