1package snapshot
2
3import (
4 "encoding/binary"
5 "fmt"
6 "hash/crc32"
7 "math"
8
9 "github.com/bnema/vev/internal/usecase/layout"
10)
11
12// marshalTest is a test-only v4 encoder. Production retains only the decoder
13// required by the one-time import.
14func marshalTest(s Session) ([]byte, error) {
15 var w payloadWriter
16 if err := writeSession(&w, s); err != nil {
17 return nil, err
18 }
19 if len(w.b) > maxDecodedBodySize || len(w.b) > math.MaxUint32 {
20 return nil, fmt.Errorf("%w: body too large", ErrInvalidData)
21 }
22 out := make([]byte, 16+len(w.b))
23 copy(out[:4], magic)
24 binary.BigEndian.PutUint16(out[4:6], version)
25 binary.BigEndian.PutUint32(out[8:12], uint32(len(w.b)))
26 binary.BigEndian.PutUint32(out[12:16], crc32.ChecksumIEEE(w.b))
27 copy(out[16:], w.b)
28 return out, nil
29}
30
31func writeSession(w *payloadWriter, s Session) error {
32 if len(s.Tabs) > math.MaxUint16 {
33 return fmt.Errorf("%w: too many tabs", ErrInvalidData)
34 }
35 if err := validateActive(s.Active, len(s.Tabs)); err != nil {
36 return err
37 }
38 if err := w.putString(s.Name); err != nil {
39 return err
40 }
41 w.putUint64(s.CreatedAt)
42 w.putUint16(s.Active)
43 w.putUint16(uint16(len(s.Tabs)))
44 for _, t := range s.Tabs {
45 if err := writeTab(w, t); err != nil {
46 return err
47 }
48 }
49 return nil
50}
51
52func writeTab(w *payloadWriter, t Tab) error {
53 if err := w.putString(t.StableID); err != nil {
54 return err
55 }
56 w.putUint16(t.Cols)
57 w.putUint16(t.Rows)
58 w.putUint64(t.NextPaneID)
59 if err := w.putString(string(t.Focus)); err != nil {
60 return err
61 }
62 var root *layout.Node
63 if t.Tree != nil {
64 root = t.Tree.Root
65 }
66 if err := writeNode(w, root); err != nil {
67 return err
68 }
69 if len(t.Panes) > math.MaxUint16 {
70 return fmt.Errorf("%w: too many panes", ErrInvalidData)
71 }
72 w.putUint16(uint16(len(t.Panes)))
73 for _, p := range t.Panes {
74 if err := writePane(w, p); err != nil {
75 return err
76 }
77 }
78 return nil
79}
80
81func writePane(w *payloadWriter, p Pane) error {
82 if err := w.putString(string(p.ID)); err != nil {
83 return err
84 }
85 if err := w.putString(p.StableID); err != nil {
86 return err
87 }
88 if err := w.putString(p.Cwd); err != nil {
89 return err
90 }
91 if len(p.SealedChunks) > maxSnapshotObjects {
92 return fmt.Errorf("%w: too many sealed chunks", ErrInvalidData)
93 }
94 w.putUint32(uint32(len(p.SealedChunks)))
95 for _, blob := range p.SealedChunks {
96 if len(blob) == 0 || len(blob) > math.MaxUint32 {
97 return fmt.Errorf("%w: sealed chunk length", ErrInvalidData)
98 }
99 w.putUint32(uint32(len(blob)))
100 w.b = append(w.b, blob...)
101 }
102 if len(p.Tail) == 0 || len(p.Tail) > math.MaxUint32 {
103 return fmt.Errorf("%w: tail length", ErrInvalidData)
104 }
105 w.putUint32(uint32(len(p.Tail)))
106 w.b = append(w.b, p.Tail...)
107 if len(p.Transcript) == 0 || len(p.Transcript) > math.MaxUint32 {
108 return fmt.Errorf("%w: transcript length", ErrInvalidData)
109 }
110 w.putUint32(uint32(len(p.Transcript)))
111 w.b = append(w.b, p.Transcript...)
112 return writeProcess(w, p.Process)
113}