-
Notifications
You must be signed in to change notification settings - Fork 3.3k
/
frame.go
152 lines (139 loc) · 4.43 KB
/
frame.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package derive
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
)
// Frames cannot be larger than 1 MB.
// Data transactions that carry frames are generally not larger than 128 KB due to L1 network conditions,
// but we leave space to grow larger anyway (gas limit allows for more data).
const MaxFrameLen = 1_000_000
// Data Format
//
// frame = channel_id ++ frame_number ++ frame_data_length ++ frame_data ++ is_last
//
// channel_id = bytes16
// frame_number = uint16
// frame_data_length = uint32
// frame_data = bytes
// is_last = bool
type Frame struct {
ID ChannelID
FrameNumber uint16
Data []byte
IsLast bool
}
// MarshalBinary writes the frame to `w`.
// It returns any errors encountered while writing, but
// generally expects the writer very rarely fail.
func (f *Frame) MarshalBinary(w io.Writer) error {
_, err := w.Write(f.ID[:])
if err != nil {
return err
}
if err := binary.Write(w, binary.BigEndian, f.FrameNumber); err != nil {
return err
}
if err := binary.Write(w, binary.BigEndian, uint32(len(f.Data))); err != nil {
return err
}
_, err = w.Write(f.Data)
if err != nil {
return err
}
if f.IsLast {
if _, err = w.Write([]byte{1}); err != nil {
return err
}
} else {
if _, err = w.Write([]byte{0}); err != nil {
return err
}
}
return nil
}
type ByteReader interface {
io.Reader
io.ByteReader
}
// UnmarshalBinary consumes a full frame from the reader.
// If `r` fails a read, it returns the error from the reader
// The reader will be left in a partially read state.
//
// If r doesn't return any bytes, returns io.EOF.
// If r unexpectedly stops returning data half-way, returns io.ErrUnexpectedEOF.
func (f *Frame) UnmarshalBinary(r ByteReader) error {
if _, err := io.ReadFull(r, f.ID[:]); err != nil {
// Forward io.EOF here ok, would mean not a single byte from r.
return fmt.Errorf("reading channel_id: %w", err)
}
if err := binary.Read(r, binary.BigEndian, &f.FrameNumber); err != nil {
return fmt.Errorf("reading frame_number: %w", eofAsUnexpectedMissing(err))
}
var frameLength uint32
if err := binary.Read(r, binary.BigEndian, &frameLength); err != nil {
return fmt.Errorf("reading frame_data_length: %w", eofAsUnexpectedMissing(err))
}
// Cap frame length to MaxFrameLen (currently 1MB)
if frameLength > MaxFrameLen {
return fmt.Errorf("frame_data_length is too large: %d", frameLength)
}
f.Data = make([]byte, int(frameLength))
if _, err := io.ReadFull(r, f.Data); err != nil {
return fmt.Errorf("reading frame_data: %w", eofAsUnexpectedMissing(err))
}
if isLastByte, err := r.ReadByte(); err != nil {
return fmt.Errorf("reading final byte (is_last): %w", eofAsUnexpectedMissing(err))
} else if isLastByte == 0 {
f.IsLast = false
} else if isLastByte == 1 {
f.IsLast = true
} else {
return errors.New("invalid byte as is_last")
}
return nil
}
// eofAsUnexpectedMissing converts an io.EOF in the error chain of err into an
// io.ErrUnexpectedEOF. It should be used to convert intermediate io.EOF errors
// in unmarshaling code to achieve idiomatic error behavior.
// Other errors are passed through unchanged.
func eofAsUnexpectedMissing(err error) error {
if errors.Is(err, io.EOF) {
return fmt.Errorf("fully missing: %w", io.ErrUnexpectedEOF)
}
return err
}
// Frames are stored in L1 transactions with the following format:
// data = DerivationVersion0 ++ Frame(s)
// Where there is one or more frames concatenated together.
// ParseFrames parse the on chain serialization of frame(s) in
// an L1 transaction. Currently only version 0 of the serialization
// format is supported.
// All frames must be parsed without error and there must not be
// any left over data and there must be at least one frame.
func ParseFrames(data []byte) ([]Frame, error) {
if len(data) == 0 {
return nil, errors.New("data array must not be empty")
}
if data[0] != DerivationVersion0 {
return nil, fmt.Errorf("invalid derivation format byte: got %d", data[0])
}
buf := bytes.NewBuffer(data[1:])
var frames []Frame
for buf.Len() > 0 {
var f Frame
if err := f.UnmarshalBinary(buf); err != nil {
return nil, fmt.Errorf("parsing frame %d: %w", len(frames), err)
}
frames = append(frames, f)
}
if buf.Len() != 0 {
return nil, fmt.Errorf("did not fully consume data: have %d frames and %d bytes left", len(frames), buf.Len())
}
if len(frames) == 0 {
return nil, errors.New("was not able to find any frames")
}
return frames, nil
}