Skip to content

Commit cd75b15

Browse files
authored
Add Checksum type (#54)
1 parent c17fcd2 commit cd75b15

9 files changed

+202
-79
lines changed

cmd/ltx/apply.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ Arguments:
4646
}
4747

4848
// Open database file. Create if it doesn't exist.
49-
dbFile, err := os.OpenFile(*dbPath, os.O_RDWR|os.O_CREATE, 0666)
49+
dbFile, err := os.OpenFile(*dbPath, os.O_RDWR|os.O_CREATE, 0o666)
5050
if err != nil {
5151
return err
5252
}
@@ -87,7 +87,7 @@ func (c *ApplyCommand) applyLTXFile(ctx context.Context, dbFile *os.File, filena
8787
if err != nil {
8888
return fmt.Errorf("compute pre-apply checksum: %w", err)
8989
} else if preApplyChecksum != dec.Header().PreApplyChecksum {
90-
return fmt.Errorf("pre-apply checksum mismatch: %016x <> %016x", preApplyChecksum, dec.Header().PreApplyChecksum)
90+
return fmt.Errorf("pre-apply checksum mismatch: %s <> %s", preApplyChecksum, dec.Header().PreApplyChecksum)
9191
}
9292

9393
// Apply each page to the database.
@@ -119,7 +119,7 @@ func (c *ApplyCommand) applyLTXFile(ctx context.Context, dbFile *os.File, filena
119119
if err != nil {
120120
return fmt.Errorf("compute post-apply checksum: %w", err)
121121
} else if postApplyChecksum != dec.Trailer().PostApplyChecksum {
122-
return fmt.Errorf("post-apply checksum mismatch: %016x <> %016x", postApplyChecksum, dec.Trailer().PostApplyChecksum)
122+
return fmt.Errorf("post-apply checksum mismatch: %s <> %s", postApplyChecksum, dec.Trailer().PostApplyChecksum)
123123
}
124124

125125
return nil

cmd/ltx/checksum.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,6 @@ Usage:
6666
return err
6767
}
6868

69-
fmt.Printf("%016x\n", chksum)
69+
fmt.Println(chksum)
7070
return nil
7171
}

cmd/ltx/dump.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ Arguments:
6262
fmt.Printf("Min TXID: %s (%d)\n", hdr.MinTXID.String(), hdr.MinTXID)
6363
fmt.Printf("Max TXID: %s (%d)\n", hdr.MaxTXID.String(), hdr.MaxTXID)
6464
fmt.Printf("Timestamp: %s (%d)\n", time.UnixMilli(int64(hdr.Timestamp)).UTC().Format(time.RFC3339Nano), hdr.Timestamp)
65-
fmt.Printf("Pre-apply: %016x\n", hdr.PreApplyChecksum)
65+
fmt.Printf("Pre-apply: %s\n", hdr.PreApplyChecksum)
6666
fmt.Printf("WAL offset: %d\n", hdr.WALOffset)
6767
fmt.Printf("WAL size: %d\n", hdr.WALSize)
6868
fmt.Printf("WAL salt: %08x %08x\n", hdr.WALSalt1, hdr.WALSalt2)
@@ -90,8 +90,8 @@ Arguments:
9090
trailer := dec.Trailer()
9191

9292
fmt.Printf("# TRAILER\n")
93-
fmt.Printf("Post-apply: %016x\n", trailer.PostApplyChecksum)
94-
fmt.Printf("File Checksum: %016x\n", trailer.FileChecksum)
93+
fmt.Printf("Post-apply: %s\n", trailer.PostApplyChecksum)
94+
fmt.Printf("File Checksum: %s\n", trailer.FileChecksum)
9595
fmt.Printf("\n")
9696
if err != nil {
9797
return err

cmd/ltx/encode_db.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ Arguments:
7272
}
7373

7474
var flags uint32
75-
var postApplyChecksum uint64
75+
var postApplyChecksum ltx.Checksum
7676
if *compressed {
7777
flags |= ltx.HeaderFlagCompressLZ4
7878
}

cmd/ltx/list.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func (c *ListCommand) printFile(w io.Writer, filename string) error {
7979
timestamp = ""
8080
}
8181

82-
_, _ = fmt.Fprintf(w, "%s\t%s\t%d\t%d\t%016x\t%016x\t%s\t%d\t%d\t%08x %08x\n",
82+
_, _ = fmt.Fprintf(w, "%s\t%s\t%d\t%d\t%s\t%s\t%s\t%d\t%d\t%08x %08x\n",
8383
dec.Header().MinTXID.String(),
8484
dec.Header().MaxTXID.String(),
8585
dec.Header().Commit,

decoder.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ type Decoder struct {
1818
trailer Trailer
1919
state string
2020

21-
chksum uint64
21+
chksum Checksum
2222
hash hash.Hash64
2323
pageN int // pages read
2424
n int64 // bytes read
@@ -77,14 +77,14 @@ func (dec *Decoder) Close() error {
7777
dec.writeToHash(b[:TrailerChecksumOffset])
7878

7979
// Compare checksum with checksum in trailer.
80-
if chksum := ChecksumFlag | dec.hash.Sum64(); chksum != dec.trailer.FileChecksum {
80+
if chksum := ChecksumFlag | Checksum(dec.hash.Sum64()); chksum != dec.trailer.FileChecksum {
8181
return ErrChecksumMismatch
8282
}
8383

8484
// Verify post-apply checksum for snapshot files.
8585
if dec.header.IsSnapshot() {
8686
if dec.trailer.PostApplyChecksum != dec.chksum {
87-
return fmt.Errorf("post-apply checksum in trailer (%016x) does not match calculated checksum (%016x)", dec.trailer.PostApplyChecksum, dec.chksum)
87+
return fmt.Errorf("post-apply checksum in trailer (%s) does not match calculated checksum (%s)", dec.trailer.PostApplyChecksum, dec.chksum)
8888
}
8989
}
9090

encoder.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func (enc *Encoder) PostApplyPos() Pos {
5454

5555
// SetPostApplyChecksum sets the post-apply checksum of the database.
5656
// Must call before Close().
57-
func (enc *Encoder) SetPostApplyChecksum(chksum uint64) {
57+
func (enc *Encoder) SetPostApplyChecksum(chksum Checksum) {
5858
enc.trailer.PostApplyChecksum = chksum
5959
}
6060

@@ -90,7 +90,7 @@ func (enc *Encoder) Close() error {
9090
return fmt.Errorf("marshal trailer: %w", err)
9191
}
9292
enc.writeToHash(b1[:TrailerChecksumOffset])
93-
enc.trailer.FileChecksum = ChecksumFlag | enc.hash.Sum64()
93+
enc.trailer.FileChecksum = ChecksumFlag | Checksum(enc.hash.Sum64())
9494

9595
// Validate trailer now that we have the file checksum.
9696
if err := enc.trailer.Validate(); err != nil {

ltx.go

+77-61
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ var (
5252
)
5353

5454
// ChecksumFlag is a flag on the checksum to ensure it is non-zero.
55-
const ChecksumFlag uint64 = 1 << 63
55+
const ChecksumFlag Checksum = 1 << 63
5656

5757
// internal reader/writer states
5858
const (
@@ -65,11 +65,11 @@ const (
6565
// Pos represents the transactional position of a database.
6666
type Pos struct {
6767
TXID TXID
68-
PostApplyChecksum uint64
68+
PostApplyChecksum Checksum
6969
}
7070

7171
// NewPos returns a new instance of Pos.
72-
func NewPos(txID TXID, postApplyChecksum uint64) Pos {
72+
func NewPos(txID TXID, postApplyChecksum Checksum) Pos {
7373
return Pos{
7474
TXID: txID,
7575
PostApplyChecksum: postApplyChecksum,
@@ -87,9 +87,9 @@ func ParsePos(s string) (Pos, error) {
8787
return Pos{}, err
8888
}
8989

90-
checksum, err := strconv.ParseUint(s[17:], 16, 64)
90+
checksum, err := ParseChecksum(s[17:])
9191
if err != nil {
92-
return Pos{}, fmt.Errorf("invalid checksum format: %q", s[17:])
92+
return Pos{}, err
9393
}
9494

9595
return Pos{
@@ -100,43 +100,14 @@ func ParsePos(s string) (Pos, error) {
100100

101101
// String returns a string representation of the position.
102102
func (p Pos) String() string {
103-
return fmt.Sprintf("%s/%016x", p.TXID, p.PostApplyChecksum)
103+
return fmt.Sprintf("%s/%s", p.TXID, p.PostApplyChecksum)
104104
}
105105

106106
// IsZero returns true if the position is empty.
107107
func (p Pos) IsZero() bool {
108108
return p == (Pos{})
109109
}
110110

111-
// Marshal serializes the position into JSON.
112-
func (p Pos) MarshalJSON() ([]byte, error) {
113-
var v posJSON
114-
v.TXID = p.TXID.String()
115-
v.PostApplyChecksum = fmt.Sprintf("%016x", p.PostApplyChecksum)
116-
return json.Marshal(v)
117-
}
118-
119-
// Unmarshal deserializes the position from JSON.
120-
func (p *Pos) UnmarshalJSON(data []byte) (err error) {
121-
var v posJSON
122-
if err := json.Unmarshal(data, &v); err != nil {
123-
return err
124-
}
125-
126-
if p.TXID, err = ParseTXID(v.TXID); err != nil {
127-
return fmt.Errorf("cannot parse txid: %q", v.TXID)
128-
}
129-
if p.PostApplyChecksum, err = strconv.ParseUint(v.PostApplyChecksum, 16, 64); err != nil {
130-
return fmt.Errorf("cannot parse post-apply checksum: %q", v.PostApplyChecksum)
131-
}
132-
return nil
133-
}
134-
135-
type posJSON struct {
136-
TXID string `json:"txid"`
137-
PostApplyChecksum string `json:"postApplyChecksum"`
138-
}
139-
140111
// PosMismatchError is returned when an LTX file is not contiguous with the current position.
141112
type PosMismatchError struct {
142113
Pos Pos `json:"pos"`
@@ -197,6 +168,51 @@ func (t *TXID) UnmarshalJSON(data []byte) (err error) {
197168
return nil
198169
}
199170

171+
// Checksum represents an LTX checksum.
172+
type Checksum uint64
173+
174+
// ParseChecksum parses a 16-character hex string into a checksum.
175+
func ParseChecksum(s string) (Checksum, error) {
176+
if len(s) != 16 {
177+
return 0, fmt.Errorf("invalid formatted checksum length: %q", s)
178+
}
179+
v, err := strconv.ParseUint(s, 16, 64)
180+
if err != nil {
181+
return 0, fmt.Errorf("invalid checksum format: %q", s)
182+
}
183+
return Checksum(v), nil
184+
}
185+
186+
// String returns c formatted as a fixed-width hex number.
187+
func (c Checksum) String() string {
188+
return fmt.Sprintf("%016x", uint64(c))
189+
}
190+
191+
func (c Checksum) MarshalJSON() ([]byte, error) {
192+
return []byte(`"` + c.String() + `"`), nil
193+
}
194+
195+
func (c *Checksum) UnmarshalJSON(data []byte) (err error) {
196+
var s *string
197+
if err := json.Unmarshal(data, &s); err != nil {
198+
return fmt.Errorf("cannot unmarshal checksum from JSON value")
199+
}
200+
201+
// Set to zero if value is nil.
202+
if s == nil {
203+
*c = 0
204+
return nil
205+
}
206+
207+
chksum, err := ParseChecksum(*s)
208+
if err != nil {
209+
return fmt.Errorf("cannot parse checksum from JSON string: %q", *s)
210+
}
211+
*c = Checksum(chksum)
212+
213+
return nil
214+
}
215+
200216
// Header flags.
201217
const (
202218
HeaderFlagMask = uint32(0x00000001)
@@ -206,19 +222,19 @@ const (
206222

207223
// Header represents the header frame of an LTX file.
208224
type Header struct {
209-
Version int // based on magic
210-
Flags uint32 // reserved flags
211-
PageSize uint32 // page size, in bytes
212-
Commit uint32 // db size after transaction, in pages
213-
MinTXID TXID // minimum transaction ID
214-
MaxTXID TXID // maximum transaction ID
215-
Timestamp int64 // milliseconds since unix epoch
216-
PreApplyChecksum uint64 // rolling checksum of database before applying this LTX file
217-
WALOffset int64 // file offset from original WAL; zero if journal
218-
WALSize int64 // size of original WAL segment; zero if journal
219-
WALSalt1 uint32 // header salt-1 from original WAL; zero if journal or compaction
220-
WALSalt2 uint32 // header salt-2 from original WAL; zero if journal or compaction
221-
NodeID uint64 // node id where the LTX file was created, zero if unset
225+
Version int // based on magic
226+
Flags uint32 // reserved flags
227+
PageSize uint32 // page size, in bytes
228+
Commit uint32 // db size after transaction, in pages
229+
MinTXID TXID // minimum transaction ID
230+
MaxTXID TXID // maximum transaction ID
231+
Timestamp int64 // milliseconds since unix epoch
232+
PreApplyChecksum Checksum // rolling checksum of database before applying this LTX file
233+
WALOffset int64 // file offset from original WAL; zero if journal
234+
WALSize int64 // size of original WAL segment; zero if journal
235+
WALSalt1 uint32 // header salt-1 from original WAL; zero if journal or compaction
236+
WALSalt2 uint32 // header salt-2 from original WAL; zero if journal or compaction
237+
NodeID uint64 // node id where the LTX file was created, zero if unset
222238
}
223239

224240
// IsSnapshot returns true if header represents a complete database snapshot.
@@ -313,7 +329,7 @@ func (h *Header) MarshalBinary() ([]byte, error) {
313329
binary.BigEndian.PutUint64(b[16:], uint64(h.MinTXID))
314330
binary.BigEndian.PutUint64(b[24:], uint64(h.MaxTXID))
315331
binary.BigEndian.PutUint64(b[32:], uint64(h.Timestamp))
316-
binary.BigEndian.PutUint64(b[40:], h.PreApplyChecksum)
332+
binary.BigEndian.PutUint64(b[40:], uint64(h.PreApplyChecksum))
317333
binary.BigEndian.PutUint64(b[48:], uint64(h.WALOffset))
318334
binary.BigEndian.PutUint64(b[56:], uint64(h.WALSize))
319335
binary.BigEndian.PutUint32(b[64:], h.WALSalt1)
@@ -334,7 +350,7 @@ func (h *Header) UnmarshalBinary(b []byte) error {
334350
h.MinTXID = TXID(binary.BigEndian.Uint64(b[16:]))
335351
h.MaxTXID = TXID(binary.BigEndian.Uint64(b[24:]))
336352
h.Timestamp = int64(binary.BigEndian.Uint64(b[32:]))
337-
h.PreApplyChecksum = binary.BigEndian.Uint64(b[40:])
353+
h.PreApplyChecksum = Checksum(binary.BigEndian.Uint64(b[40:]))
338354
h.WALOffset = int64(binary.BigEndian.Uint64(b[48:]))
339355
h.WALSize = int64(binary.BigEndian.Uint64(b[56:]))
340356
h.WALSalt1 = binary.BigEndian.Uint32(b[64:])
@@ -371,8 +387,8 @@ func IsValidHeaderFlags(flags uint32) bool {
371387

372388
// Trailer represents the ending frame of an LTX file.
373389
type Trailer struct {
374-
PostApplyChecksum uint64 // rolling checksum of database after this LTX file is applied
375-
FileChecksum uint64 // crc64 checksum of entire file
390+
PostApplyChecksum Checksum // rolling checksum of database after this LTX file is applied
391+
FileChecksum Checksum // crc64 checksum of entire file
376392
}
377393

378394
// Validate returns an error if t is invalid.
@@ -394,8 +410,8 @@ func (t *Trailer) Validate() error {
394410
// MarshalBinary encodes h to a byte slice.
395411
func (t *Trailer) MarshalBinary() ([]byte, error) {
396412
b := make([]byte, TrailerSize)
397-
binary.BigEndian.PutUint64(b[0:], t.PostApplyChecksum)
398-
binary.BigEndian.PutUint64(b[8:], t.FileChecksum)
413+
binary.BigEndian.PutUint64(b[0:], uint64(t.PostApplyChecksum))
414+
binary.BigEndian.PutUint64(b[8:], uint64(t.FileChecksum))
399415
return b, nil
400416
}
401417

@@ -405,8 +421,8 @@ func (t *Trailer) UnmarshalBinary(b []byte) error {
405421
return io.ErrShortBuffer
406422
}
407423

408-
t.PostApplyChecksum = binary.BigEndian.Uint64(b[0:])
409-
t.FileChecksum = binary.BigEndian.Uint64(b[8:])
424+
t.PostApplyChecksum = Checksum(binary.BigEndian.Uint64(b[0:]))
425+
t.FileChecksum = Checksum(binary.BigEndian.Uint64(b[8:]))
410426
return nil
411427
}
412428

@@ -464,23 +480,23 @@ func NewHasher() hash.Hash64 {
464480
}
465481

466482
// ChecksumPage returns a CRC64 checksum that combines the page number & page data.
467-
func ChecksumPage(pgno uint32, data []byte) uint64 {
483+
func ChecksumPage(pgno uint32, data []byte) Checksum {
468484
return ChecksumPageWithHasher(NewHasher(), pgno, data)
469485
}
470486

471487
// ChecksumPageWithHasher returns a CRC64 checksum that combines the page number & page data.
472-
func ChecksumPageWithHasher(h hash.Hash64, pgno uint32, data []byte) uint64 {
488+
func ChecksumPageWithHasher(h hash.Hash64, pgno uint32, data []byte) Checksum {
473489
h.Reset()
474490
_ = binary.Write(h, binary.BigEndian, pgno)
475491
_, _ = h.Write(data)
476-
return ChecksumFlag | h.Sum64()
492+
return ChecksumFlag | Checksum(h.Sum64())
477493
}
478494

479495
// ChecksumReader reads an entire database file from r and computes its rolling checksum.
480-
func ChecksumReader(r io.Reader, pageSize int) (uint64, error) {
496+
func ChecksumReader(r io.Reader, pageSize int) (Checksum, error) {
481497
data := make([]byte, pageSize)
482498

483-
var chksum uint64
499+
var chksum Checksum
484500
for pgno := uint32(1); ; pgno++ {
485501
if _, err := io.ReadFull(r, data); err == io.EOF {
486502
break

0 commit comments

Comments
 (0)