- 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
99 lines
2.5 KiB
Go
99 lines
2.5 KiB
Go
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 0–27 (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 0–27 (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
|
||
}
|