codec.gno
4.36 Kb · 174 lines
1package vmkit
2
3import "errors"
4
5// ErrTruncated is returned by every Reader method that runs past the end of
6// the buffer. A snapshot is consensus state, so a short read is always a hard
7// error and never a zero value.
8var ErrTruncated = errors.New("vmkit: truncated snapshot")
9
10// ErrBadSnapshot is returned when a snapshot is well-formed but not for this
11// machine: wrong magic, wrong version, or a field outside its legal range.
12var ErrBadSnapshot = errors.New("vmkit: snapshot is not for this machine")
13
14// Writer builds a snapshot. Every integer is fixed-width big-endian and every
15// byte slice is length-prefixed, so the encoding is canonical: the same
16// machine state always produces the same bytes, on every node.
17//
18// Machines are not required to use it, but a machine that invents its own
19// layout owes the zoo an explanation, because the cross-VM snapshot cost
20// comparison only means something when the encodings match.
21type Writer struct {
22 buf []byte
23}
24
25// NewWriter returns a Writer with room for n bytes reserved up front.
26func NewWriter(n int) *Writer {
27 if n < 0 {
28 n = 0
29 }
30 return &Writer{buf: make([]byte, 0, n)}
31}
32
33// Byte appends one byte.
34func (w *Writer) Byte(b byte) { w.buf = append(w.buf, b) }
35
36// Uint32 appends a 4-byte big-endian value.
37func (w *Writer) Uint32(v uint32) {
38 w.buf = append(w.buf, byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
39}
40
41// Uint64 appends an 8-byte big-endian value.
42func (w *Writer) Uint64(v uint64) {
43 w.buf = append(w.buf,
44 byte(v>>56), byte(v>>48), byte(v>>40), byte(v>>32),
45 byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
46}
47
48// Int appends a signed value, zig-zag encoded into a Uint64 so that small
49// negative numbers do not cost eight 0xff bytes.
50func (w *Writer) Int(v int64) {
51 u := uint64(v) << 1
52 if v < 0 {
53 u = ^u
54 }
55 w.Uint64(u)
56}
57
58// Bytes appends a length-prefixed byte slice.
59func (w *Writer) Bytes(p []byte) {
60 w.Uint32(uint32(len(p)))
61 w.buf = append(w.buf, p...)
62}
63
64// String appends a length-prefixed string.
65func (w *Writer) String(s string) { w.Bytes([]byte(s)) }
66
67// Out returns the encoded bytes.
68func (w *Writer) Out() []byte { return w.buf }
69
70// Reader consumes a snapshot written by [Writer].
71//
72// It latches the first error it hits: a caller may decode a whole struct and
73// check [Reader.Err] once at the end, instead of after every field. Every
74// method returns a zero value once the Reader is in error.
75type Reader struct {
76 buf []byte
77 pos int
78 err error
79}
80
81// NewReader returns a Reader over b.
82func NewReader(b []byte) *Reader { return &Reader{buf: b} }
83
84// Err returns the first error hit, or nil.
85func (r *Reader) Err() error { return r.err }
86
87// Remaining reports how many bytes are left unread.
88func (r *Reader) Remaining() int {
89 if r.pos > len(r.buf) {
90 return 0
91 }
92 return len(r.buf) - r.pos
93}
94
95// Fail latches err, so a machine can reject a field its own rules forbid and
96// have it surface through [Reader.Err] like any decoding failure.
97func (r *Reader) Fail(err error) {
98 if r.err == nil {
99 r.err = err
100 }
101}
102
103func (r *Reader) take(n int) []byte {
104 if r.err != nil {
105 return nil
106 }
107 if n < 0 || r.pos+n > len(r.buf) {
108 r.err = ErrTruncated
109 return nil
110 }
111 out := r.buf[r.pos : r.pos+n]
112 r.pos += n
113 return out
114}
115
116// Byte reads one byte.
117func (r *Reader) Byte() byte {
118 p := r.take(1)
119 if p == nil {
120 return 0
121 }
122 return p[0]
123}
124
125// Uint32 reads a 4-byte big-endian value.
126func (r *Reader) Uint32() uint32 {
127 p := r.take(4)
128 if p == nil {
129 return 0
130 }
131 return uint32(p[0])<<24 | uint32(p[1])<<16 | uint32(p[2])<<8 | uint32(p[3])
132}
133
134// Uint64 reads an 8-byte big-endian value.
135func (r *Reader) Uint64() uint64 {
136 p := r.take(8)
137 if p == nil {
138 return 0
139 }
140 return uint64(p[0])<<56 | uint64(p[1])<<48 | uint64(p[2])<<40 | uint64(p[3])<<32 |
141 uint64(p[4])<<24 | uint64(p[5])<<16 | uint64(p[6])<<8 | uint64(p[7])
142}
143
144// Int reads a zig-zag encoded signed value written by [Writer.Int].
145func (r *Reader) Int() int64 {
146 u := r.Uint64()
147 v := int64(u >> 1)
148 if u&1 != 0 {
149 v = ^v
150 }
151 return v
152}
153
154// Bytes reads a length-prefixed byte slice. The result is a copy, so a
155// machine can keep it without aliasing the snapshot it was handed.
156func (r *Reader) Bytes() []byte {
157 n := int(r.Uint32())
158 p := r.take(n)
159 if p == nil {
160 return nil
161 }
162 out := make([]byte, n)
163 copy(out, p)
164 return out
165}
166
167// String reads a length-prefixed string.
168func (r *Reader) String() string {
169 p := r.take(int(r.Uint32()))
170 if p == nil {
171 return ""
172 }
173 return string(p)
174}