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
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user