Files
go-kv/wal/header.go
T
dailz cf913b1d52 feat: initialize project structure and error types
- go.mod with github.com/dailz/go-kv, Go 1.26.3, testify
- config/config.go with WalConfig, Validate() with checked arithmetic
- errors.go with sentinel errors (ErrCommitUnknown, ErrWriteStopped, etc.)
- wal/constants.go with all WAL format constants and enums
- wal/header.go with WAL File Header encode/decode (CRC32 IEEE)
- wal/record.go with Physical Record codec, block boundary, SplitIntoRecords
- wal/entry.go with WAL Entry codec (varint keys/values, OpType, ValueKind)
- wal/sequence.go with SequenceManager (atomic, CAS, overflow-safe)
- manifest/manifest.go with MANIFEST stub (Load/Save atomic)
- manifest/current.go with CURRENT file (WriteCurrent/ReadCurrent)
- Comprehensive tests for all modules
- .golangci.yml configuration
2026-06-12 13:23:27 +08:00

99 lines
2.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package wal
import (
"encoding/binary"
"errors"
"hash/crc32"
)
const (
walFileHeaderSize = 32
walMagic = 0x57414C4B // "WALK" in ASCII
walFormatVersion = 1
)
// WalFileHeader is the 32-byte header written at the start of every WAL segment file.
type WalFileHeader struct {
Magic uint32
FormatVersion uint16
HeaderSize uint16
BlockSize uint32
SegmentID uint64
StartSequence uint64
HeaderCRC uint32
}
// EncodeWalHeader serializes h into a fixed-size 32-byte array using little-endian byte order.
// The HeaderCRC field is computed over bytes 027 (everything except the CRC itself).
func EncodeWalHeader(h *WalFileHeader) [walFileHeaderSize]byte {
h.HeaderSize = walFileHeaderSize
h.Magic = walMagic
h.FormatVersion = walFormatVersion
var buf [walFileHeaderSize]byte
le := binary.LittleEndian
le.PutUint32(buf[0:4], h.Magic)
le.PutUint16(buf[4:6], h.FormatVersion)
le.PutUint16(buf[6:8], h.HeaderSize)
le.PutUint32(buf[8:12], h.BlockSize)
le.PutUint64(buf[12:20], h.SegmentID)
le.PutUint64(buf[20:28], h.StartSequence)
// CRC32 IEEE over bytes 027 (excludes the CRC field itself)
h.HeaderCRC = crc32.ChecksumIEEE(buf[0:28])
le.PutUint32(buf[28:32], h.HeaderCRC)
return buf
}
var (
errBadMagic = errors.New("wal: bad magic number")
errBadVersion = errors.New("wal: unsupported format version")
errBadHeaderSize = errors.New("wal: bad header size")
errCRCMismatch = errors.New("wal: header CRC mismatch")
errHeaderTooShort = errors.New("wal: header data too short")
)
// DecodeWalHeader parses a 32-byte little-endian header and validates magic,
// format version, header size, and CRC.
func DecodeWalHeader(data []byte) (*WalFileHeader, error) {
if len(data) < walFileHeaderSize {
return nil, errHeaderTooShort
}
le := binary.LittleEndian
magic := le.Uint32(data[0:4])
if magic != walMagic {
return nil, errBadMagic
}
version := le.Uint16(data[4:6])
if version != walFormatVersion {
return nil, errBadVersion
}
hdrSize := le.Uint16(data[6:8])
if hdrSize != walFileHeaderSize {
return nil, errBadHeaderSize
}
// Verify CRC before trusting any other fields
gotCRC := crc32.ChecksumIEEE(data[0:28])
storedCRC := le.Uint32(data[28:32])
if gotCRC != storedCRC {
return nil, errCRCMismatch
}
return &WalFileHeader{
Magic: magic,
FormatVersion: version,
HeaderSize: hdrSize,
BlockSize: le.Uint32(data[8:12]),
SegmentID: le.Uint64(data[12:20]),
StartSequence: le.Uint64(data[20:28]),
HeaderCRC: storedCRC,
}, nil
}