manifest_preflight.go RawHistoryFolder

1package snapshot
2
3import (
4	"fmt"
5
6	"github.com/bnema/vev/internal/domain"
7	"github.com/bnema/vev/internal/usecase/layout"
8)
9
10// preflightManifest rejects hostile declarations before UnmarshalManifest
11// allocates tab, pane, string, or reference slices.
12func preflightManifest(body []byte) error {
13	r := payloadReader{b: body}
14	budget := manifestPreflightBudget{}
15	generation, err := r.getUint64()
16	if err != nil {
17		return err
18	}
19	if generation == 0 {
20		return fmt.Errorf("%w: manifest generation", ErrInvalidData)
21	}
22	incarnationBytes, err := r.getBytes(len(domain.IncarnationID{}))
23	if err != nil {
24		return err
25	}
26	var incarnationID domain.IncarnationID
27	copy(incarnationID[:], incarnationBytes)
28	if incarnationID == (domain.IncarnationID{}) {
29		return fmt.Errorf("%w: manifest incarnation", ErrInvalidData)
30	}
31	parentPresent, err := r.getUint8()
32	if err != nil {
33		return err
34	}
35	switch parentPresent {
36	case 0:
37	case 1:
38		parentGeneration, err := r.getUint64()
39		if err != nil {
40			return err
41		}
42		digest, err := r.getBytes(len(SnapshotDigest{}))
43		if err != nil {
44			return err
45		}
46		if parentGeneration == 0 || parentGeneration >= generation || isZeroDigestBytes(digest) {
47			return fmt.Errorf("%w: parent checkpoint", ErrInvalidData)
48		}
49	default:
50		return fmt.Errorf("%w: parent checkpoint presence", ErrInvalidData)
51	}
52	name, err := preflightManifestStringBytes(&r, &budget)
53	if err != nil {
54		return err
55	}
56	if len(name) == 0 {
57		return fmt.Errorf("%w: manifest name", ErrInvalidData)
58	}
59	if _, err := r.getUint64(); err != nil {
60		return err
61	}
62	active, err := r.getUint16()
63	if err != nil {
64		return err
65	}
66	tabs, err := r.getUint16()
67	if err != nil {
68		return err
69	}
70	if err := validateActive(active, int(tabs)); err != nil {
71		return err
72	}
73	if uint64(tabs) > uint64(len(r.b))/2 || !budget.add(uint64(tabs)*128) {
74		return fmt.Errorf("%w: tab allocation", ErrInvalidData)
75	}
76	for range tabs {
77		if err := preflightManifestTab(&r, &budget); err != nil {
78			return err
79		}
80	}
81	return preflightManifestWeights(&r, budget.nodes)
82}
83
84type manifestPreflightBudget struct {
85	allocation uint64
86	nodes      uint32
87}
88
89func (b *manifestPreflightBudget) add(n uint64) bool {
90	if n > maxSnapshotDecodedAllocation-b.allocation {
91		return false
92	}
93	b.allocation += n
94	return true
95}
96
97func preflightManifestStringBytes(r *payloadReader, budget *manifestPreflightBudget) ([]byte, error) {
98	n, err := r.getUint16()
99	if err != nil {
100		return nil, err
101	}
102	if uint64(n) > uint64(len(r.b)) {
103		return nil, ErrShortPayload
104	}
105	if !budget.add(uint64(n) + 16) {
106		return nil, fmt.Errorf("%w: string allocation", ErrInvalidData)
107	}
108	b := r.b[:n]
109	r.b = r.b[n:]
110	return b, nil
111}
112
113// skipManifestString accounts for the decoded string without materializing it.
114func skipManifestString(r *payloadReader, budget *manifestPreflightBudget) error {
115	_, err := preflightManifestStringBytes(r, budget)
116	return err
117}
118
119func preflightManifestString(r *payloadReader, budget *manifestPreflightBudget) (string, error) {
120	b, err := preflightManifestStringBytes(r, budget)
121	if err != nil {
122		return "", err
123	}
124	return string(b), nil
125}
126
127func preflightManifestTab(r *payloadReader, budget *manifestPreflightBudget) error {
128	if err := skipManifestString(r, budget); err != nil {
129		return err
130	}
131	cols, err := r.getUint16()
132	if err != nil {
133		return err
134	}
135	rows, err := r.getUint16()
136	if err != nil {
137		return err
138	}
139	if !safeDimensions(cols, rows) {
140		return fmt.Errorf("%w: dimensions", ErrInvalidData)
141	}
142	if _, err := r.getUint64(); err != nil {
143		return err
144	}
145	focus, err := preflightManifestString(r, budget)
146	if err != nil {
147		return err
148	}
149	references := make([]layout.PaneID, 0, 8)
150	if err := preflightManifestNode(r, 0, true, budget, &references); err != nil {
151		return err
152	}
153	panes, err := r.getUint16()
154	if err != nil {
155		return err
156	}
157	if uint64(panes) > uint64(len(r.b))/2 || !budget.add(uint64(panes)*256) {
158		return fmt.Errorf("%w: pane allocation", ErrInvalidData)
159	}
160	ids := make(map[layout.PaneID]struct{}, panes)
161	for range panes {
162		id, err := preflightManifestPane(r, budget)
163		if err != nil {
164			return err
165		}
166		if id == "" {
167			return fmt.Errorf("%w: empty pane ID", ErrInvalidData)
168		}
169		if _, duplicate := ids[id]; duplicate {
170			return fmt.Errorf("%w: duplicate pane ID", ErrInvalidData)
171		}
172		ids[id] = struct{}{}
173	}
174	if focus != "" {
175		references = append(references, layout.PaneID(focus))
176	}
177	for _, id := range references {
178		if _, ok := ids[id]; !ok {
179			return ErrUnknownPane
180		}
181	}
182	return nil
183}
184
185func preflightManifestNode(r *payloadReader, depth int, root bool, budget *manifestPreflightBudget, refs *[]layout.PaneID) error {
186	if depth > 64 || !budget.add(128) {
187		return fmt.Errorf("%w: tree resource declaration", ErrInvalidData)
188	}
189	kind, err := r.getUint8()
190	if err != nil {
191		return err
192	}
193	switch kind {
194	case manifestNodeLeaf:
195		leaf, err := preflightManifestString(r, budget)
196		if err != nil {
197			return err
198		}
199		budget.nodes++
200		*refs = append(*refs, layout.PaneID(leaf))
201		return nil
202	case manifestNodeSplit:
203		dir, err := r.getUint8()
204		if err != nil {
205			return err
206		}
207		if layout.SplitDir(dir) != layout.Horizontal && layout.SplitDir(dir) != layout.Vertical {
208			return fmt.Errorf("%w: split dir", ErrInvalidData)
209		}
210	case manifestNodeStack:
211		expanded, err := preflightManifestString(r, budget)
212		if err != nil {
213			return err
214		}
215		if expanded != "" {
216			*refs = append(*refs, layout.PaneID(expanded))
217		}
218	case manifestNodeNil:
219		if !root {
220			return fmt.Errorf("%w: nil child node", ErrInvalidData)
221		}
222		return nil
223	default:
224		return fmt.Errorf("%w: node kind", ErrInvalidData)
225	}
226	budget.nodes++
227	n, err := r.getUint16()
228	if err != nil {
229		return err
230	}
231	if uint64(n) > uint64(len(r.b)) {
232		return ErrShortPayload
233	}
234	for range n {
235		if err := preflightManifestNode(r, depth+1, false, budget, refs); err != nil {
236			return err
237		}
238	}
239	return nil
240}
241
242func preflightManifestWeights(r *payloadReader, wantNodes uint32) error {
243	return readManifestWeightExtension(r, uint64(wantNodes), nil)
244}
245
246func preflightManifestPane(r *payloadReader, budget *manifestPreflightBudget) (layout.PaneID, error) {
247	id, err := preflightManifestString(r, budget)
248	if err != nil {
249		return "", err
250	}
251	if err := skipManifestString(r, budget); err != nil {
252		return "", err
253	}
254	if err := skipManifestString(r, budget); err != nil {
255		return "", err
256	}
257	sealed, err := r.getUint16()
258	if err != nil {
259		return "", err
260	}
261	if uint64(sealed) > uint64(len(r.b))/(objectRefDigestSize+objectRefSizeFieldSize+1) || !budget.add(uint64(sealed)*48) {
262		return "", fmt.Errorf("%w: sealed allocation", ErrInvalidData)
263	}
264	for range sealed {
265		if err := preflightObjectRef(r, HistoryChunk); err != nil {
266			return "", err
267		}
268	}
269	if err := preflightObjectRef(r, HistoryTail); err != nil {
270		return "", err
271	}
272	if err := preflightObjectRef(r, RecoveryTranscript); err != nil {
273		return "", err
274	}
275	if err := preflightManifestProcess(r, budget); err != nil {
276		return "", err
277	}
278	return layout.PaneID(id), nil
279}
280
281func preflightObjectRef(r *payloadReader, want ObjectKind) error {
282	kind, digest, size, err := parseObjectRef(r)
283	if err != nil {
284		return err
285	}
286	return validateObjectRefFields(kind, digest, size, want)
287}
288func preflightManifestProcess(r *payloadReader, budget *manifestPreflightBudget) error {
289	present, err := r.getUint8()
290	if err != nil {
291		return err
292	}
293	if present == processAbsent {
294		return nil
295	}
296	if present != processPresent {
297		return fmt.Errorf("%w: process presence", ErrInvalidData)
298	}
299	n, err := r.getUint16()
300	if err != nil {
301		return err
302	}
303	if n == 0 || !budget.add(uint64(n)*16) {
304		return fmt.Errorf("%w: process argv", ErrInvalidData)
305	}
306	for range n {
307		if err := skipManifestString(r, budget); err != nil {
308			return err
309		}
310	}
311	if err := skipManifestString(r, budget); err != nil {
312		return err
313	}
314	return skipManifestString(r, budget)
315}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close