diff --git a/pkg/headers/headers.go b/pkg/headers/headers.go new file mode 100644 index 0000000..b25ecbe --- /dev/null +++ b/pkg/headers/headers.go @@ -0,0 +1,69 @@ +// Package headers provides headers for the db binary files +package headers + +import ( + "encoding/binary" + "errors" +) + +type Headers struct { + HeaderString []byte // Header string + PageSize uint64 // Page size for chunks + DBVersion uint16 // Version of the db + + // Reserved for future use. + WAL bool // Enable write-ahead-logging + Commit []byte // SHA256 sum of the current DB - Could be switched to ULID/UUID in future. +} + +// constant HeaderString +const HeaderString = "zutto/db/" + +//Commit data length - mapped to be 32 bytes for now, to match SHA256SUM +const CommitDataLength = 32 + +// Static lengths of the headers HeaderString length(uint16, 2 bytes)- Pagesize (uint64, 8 bytes) + DBVersion (int16, 2 bytes) + WAL (int8//bool, 1 byte) + Commit (SHA256 32 bytes) +const StaticHeadersLength = 13 + CommitDataLength + +// Generate generates []byte output from the headers +func (h *Headers) Generate() *[]byte { + var HeaderStringLength int = len(h.HeaderString) + + //Output array + var data []byte = make([]byte, HeaderStringLength+StaticHeadersLength) + + //Headerstring + binary.LittleEndian.PutUint16(data[0:2], uint16(HeaderStringLength)) + copy(data[2:2+HeaderStringLength], h.HeaderString[:]) + + //add the simpler metadata + binary.LittleEndian.PutUint64(data[2+HeaderStringLength:10+HeaderStringLength], h.PageSize) + binary.LittleEndian.PutUint16(data[10+HeaderStringLength:12+HeaderStringLength], h.DBVersion) + + //Convert bool-value to byte with a helper function true = byte(1), false = byte(0) + data[12+HeaderStringLength] = ToSingleByte(h.WAL) + + //commit data, extr + copy(data[13+HeaderStringLength:13+CommitDataLength+HeaderStringLength], h.Commit[0:CommitDataLength]) + + return &data +} + +// Parse parses the headers +// Accepts 2 parameters, Input *[]byte's and Padding +// Returns error in case of errors. +func (h *Headers) Parse(Input *[]byte, Padding int) error { + if Padding+len((*Input)) < StaticHeadersLength { + return errors.New("Input was not correct length") + } + + var HeaderStringLength int = int(binary.LittleEndian.Uint16((*Input)[Padding : Padding+2])) + h.HeaderString = (*Input)[2+Padding : Padding+2+HeaderStringLength] + h.PageSize = binary.LittleEndian.Uint64((*Input)[2+HeaderStringLength+Padding : Padding+10+HeaderStringLength]) + h.DBVersion = binary.LittleEndian.Uint16((*Input)[10+HeaderStringLength+Padding : Padding+12+HeaderStringLength]) + + h.WAL = ByteToBool((*Input)[12+HeaderStringLength]) + h.Commit = ((*Input)[13+HeaderStringLength+Padding : Padding+13+CommitDataLength+HeaderStringLength]) + + return nil +} diff --git a/pkg/headers/headers_test.go b/pkg/headers/headers_test.go new file mode 100644 index 0000000..097b2b1 --- /dev/null +++ b/pkg/headers/headers_test.go @@ -0,0 +1,33 @@ +package headers + +import ( + "crypto/rand" + "reflect" + "testing" +) + +func TestHeaders_Generate(t *testing.T) { + + h := Headers{ + HeaderString: []byte(HeaderString), + PageSize: uint64(256 ^ 2), + DBVersion: uint16(1), + + WAL: true, + Commit: make([]byte, CommitDataLength), + } + + _, err := rand.Read(h.Commit[:CommitDataLength]) + if err != nil { + t.Error("Failed to generate random string") + } + + output := h.Generate() + + h2 := Headers{} + h2.Parse(output, 0) + + if !reflect.DeepEqual(h, h2) { + t.Error("deepequal failed") + } +} diff --git a/pkg/headers/helper.go b/pkg/headers/helper.go new file mode 100644 index 0000000..e378650 --- /dev/null +++ b/pkg/headers/helper.go @@ -0,0 +1,35 @@ +package headers + +// ByteHelper provides helping functions to less-fun-types +type SingleByteType interface { + bool | ~uint8 +} +type BoolType interface { + bool +} + +func ByteToBool(Input byte) bool { + if byte(1) == Input { + return true + } + + return false +} + +// ToSingleByte converts single-byte types, such as int8 and bool to single bytes. +func ToSingleByte[T SingleByteType](input T) byte { + switch input := any(input).(type) { + case uint8: + return byte(input) + case int8: + return byte(input) + case bool: + if input { + return byte(1) + } else { + return byte(0) + } + default: + return byte(-0) + } +}