diff options
Diffstat (limited to 'internal/store/header.go')
| -rw-r--r-- | internal/store/header.go | 86 |
1 files changed, 86 insertions, 0 deletions
diff --git a/internal/store/header.go b/internal/store/header.go new file mode 100644 index 00000000..a6e756f9 --- /dev/null +++ b/internal/store/header.go @@ -0,0 +1,86 @@ +package store + +import ( + "encoding/hex" + "errors" + "io" + "os" + "strconv" + "syscall" + + "hakurei.app/hst" +) + +const ( + // entryHeaderMagic are magic bytes at the beginning of the state entry file. + entryHeaderMagic = "\x00\xff\xca\xfe" + // entryHeaderRevision follows entryHeaderMagic and is incremented for revisions of the format. + entryHeaderRevision = "\x00\x00" + // entryHeaderSize is the fixed size of the header in bytes, including the enablement byte and its complement. + entryHeaderSize = len(entryHeaderMagic+entryHeaderRevision) + 2 +) + +// entryHeaderEncode encodes a state entry header for a [hst.Enablement] byte. +func entryHeaderEncode(et hst.Enablement) *[entryHeaderSize]byte { + data := [entryHeaderSize]byte([]byte( + entryHeaderMagic + entryHeaderRevision + string([]hst.Enablement{et, ^et}), + )) + return &data +} + +// entryHeaderDecode validates a state entry header and returns the [hst.Enablement] byte. +func entryHeaderDecode(data *[entryHeaderSize]byte) (hst.Enablement, error) { + if magic := data[:len(entryHeaderMagic)]; string(magic) != entryHeaderMagic { + return 0, errors.New("invalid header " + hex.EncodeToString(magic)) + } + if revision := data[len(entryHeaderMagic):len(entryHeaderMagic+entryHeaderRevision)]; string(revision) != entryHeaderRevision { + return 0, errors.New("unexpected revision " + hex.EncodeToString(revision)) + } + + et := data[len(entryHeaderMagic+entryHeaderRevision)] + if et != ^data[len(entryHeaderMagic+entryHeaderRevision)+1] { + return 0, errors.New("header enablement value is inconsistent") + } + return hst.Enablement(et), nil +} + +// EntrySizeError is returned for a file too small to hold a state entry header. +type EntrySizeError struct { + Name string + Size int64 +} + +func (e *EntrySizeError) Error() string { + if e.Name == "" { + return "state entry file is too short" + } + return "state entry file " + strconv.Quote(e.Name) + " is too short" +} + +// entryCheckFile checks whether [os.FileInfo] refers to a file that might hold [hst.State]. +func entryCheckFile(fi os.FileInfo) error { + if fi.IsDir() { + return syscall.EISDIR + } + if s := fi.Size(); s <= int64(entryHeaderSize) { + return &EntrySizeError{Name: fi.Name(), Size: s} + } + return nil +} + +// entryReadHeader reads [hst.Enablement] from an [io.Reader]. +func entryReadHeader(r io.Reader) (hst.Enablement, error) { + var data [entryHeaderSize]byte + if n, err := r.Read(data[:]); err != nil { + return 0, err + } else if n != entryHeaderSize { + return 0, &EntrySizeError{Size: int64(n)} + } + return entryHeaderDecode(&data) +} + +// entryWriteHeader writes [hst.Enablement] header to an [io.Writer]. +func entryWriteHeader(w io.Writer, et hst.Enablement) error { + _, err := w.Write(entryHeaderEncode(et)[:]) + return err +} |
