168 lines
4.1 KiB
Go
168 lines
4.1 KiB
Go
// Copyright (c) HashiCorp, Inc
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package wal
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"io"
|
|
"time"
|
|
|
|
"github.com/hashicorp/raft"
|
|
)
|
|
|
|
const (
|
|
// FirstExternalCodecID is the lowest value an external code may use to
|
|
// identify their codec. Values lower than this are reserved for future
|
|
// internal use.
|
|
FirstExternalCodecID = 1 << 16
|
|
|
|
// Codec* constants identify internally-defined codec identifiers.
|
|
CodecBinaryV1 uint64 = iota
|
|
)
|
|
|
|
// Codec is the interface required for encoding/decoding log entries. Callers
|
|
// can pass a custom one to manage their own serialization, or to add additional
|
|
// layers like encryption or compression of records. Each codec
|
|
type Codec interface {
|
|
// ID returns the globally unique identifier for this codec version. This is
|
|
// encoded into segment file headers and must remain consistent over the life
|
|
// of the log. Values up to FirstExternalCodecID are reserved and will error
|
|
// if specified externally.
|
|
ID() uint64
|
|
|
|
// Encode the log into the io.Writer. We pass a writer to allow the caller to
|
|
// manage buffer allocation and re-use.
|
|
Encode(l *raft.Log, w io.Writer) error
|
|
|
|
// Decode a log from the passed byte slice into the log entry pointed to. This
|
|
// allows the caller to manage allocation and re-use of the bytes and log
|
|
// entry. The resulting raft.Log MUST NOT reference data in the input byte
|
|
// slice since the input byte slice may be returned to a pool and re-used.
|
|
Decode([]byte, *raft.Log) error
|
|
}
|
|
|
|
// BinaryCodec is a Codec that encodes raft.Log with a simple binary format. We
|
|
// test that all fields are captured using reflection.
|
|
//
|
|
// For now we assume raft.Log is not likely to change too much. If it does we'll
|
|
// use a new Codec ID for the later version and have to support decoding either.
|
|
type BinaryCodec struct{}
|
|
|
|
// ID returns the globally unique identifier for this codec version. This is
|
|
// encoded into segment file headers and must remain consistent over the life
|
|
// of the log. Values up to FirstExternalCodecID are reserved and will error
|
|
// if specified externally.
|
|
func (c *BinaryCodec) ID() uint64 {
|
|
return CodecBinaryV1
|
|
}
|
|
|
|
// Encode the log into the io.Writer. We pass a writer to allow the caller to
|
|
// manage buffer allocation and re-use.
|
|
func (c *BinaryCodec) Encode(l *raft.Log, w io.Writer) error {
|
|
enc := encoder{w: w}
|
|
enc.varint(l.Index)
|
|
enc.varint(l.Term)
|
|
enc.varint(uint64(l.Type))
|
|
enc.bytes(l.Data)
|
|
enc.bytes(l.Extensions)
|
|
enc.time(l.AppendedAt)
|
|
return enc.err
|
|
}
|
|
|
|
// Decode a log from the passed byte slice into the log entry pointed to. This
|
|
// allows the caller to manage allocation and re-use of the bytes and log
|
|
// entry.
|
|
func (c *BinaryCodec) Decode(bs []byte, l *raft.Log) error {
|
|
dec := decoder{buf: bs}
|
|
l.Index = dec.varint()
|
|
l.Term = dec.varint()
|
|
l.Type = raft.LogType(dec.varint())
|
|
l.Data = dec.bytes()
|
|
l.Extensions = dec.bytes()
|
|
l.AppendedAt = dec.time()
|
|
return dec.err
|
|
}
|
|
|
|
type encoder struct {
|
|
w io.Writer
|
|
err error
|
|
scratch [10]byte
|
|
}
|
|
|
|
func (e *encoder) varint(v uint64) {
|
|
if e.err != nil {
|
|
return
|
|
}
|
|
|
|
// Varint encoding might use up to 9 bytes for a uint64
|
|
n := binary.PutUvarint(e.scratch[:], v)
|
|
_, e.err = e.w.Write(e.scratch[:n])
|
|
}
|
|
|
|
func (e *encoder) bytes(bs []byte) {
|
|
// Put a length prefix
|
|
e.varint(uint64(len(bs)))
|
|
if e.err != nil {
|
|
return
|
|
}
|
|
// Copy the bytes to the writer
|
|
_, e.err = e.w.Write(bs)
|
|
}
|
|
|
|
func (e *encoder) time(t time.Time) {
|
|
if e.err != nil {
|
|
return
|
|
}
|
|
bs, err := t.MarshalBinary()
|
|
if err != nil {
|
|
e.err = err
|
|
return
|
|
}
|
|
_, e.err = e.w.Write(bs)
|
|
}
|
|
|
|
type decoder struct {
|
|
buf []byte
|
|
err error
|
|
}
|
|
|
|
func (d *decoder) varint() uint64 {
|
|
if d.err != nil {
|
|
return 0
|
|
}
|
|
v, n := binary.Uvarint(d.buf)
|
|
d.buf = d.buf[n:]
|
|
return v
|
|
}
|
|
|
|
func (d *decoder) bytes() []byte {
|
|
// Get length prefix
|
|
n := d.varint()
|
|
if d.err != nil {
|
|
return nil
|
|
}
|
|
if n == 0 {
|
|
return nil
|
|
}
|
|
if n > uint64(len(d.buf)) {
|
|
d.err = io.ErrShortBuffer
|
|
return nil
|
|
}
|
|
bs := make([]byte, n)
|
|
copy(bs, d.buf[:n])
|
|
d.buf = d.buf[n:]
|
|
return bs
|
|
}
|
|
|
|
func (d *decoder) time() time.Time {
|
|
var t time.Time
|
|
if d.err != nil {
|
|
return t
|
|
}
|
|
// Note that Unmarshal Binary updates d.buf to remove the bytes it read
|
|
// already.
|
|
d.err = t.UnmarshalBinary(d.buf)
|
|
return t
|
|
}
|