v0.8.1 1f54f61

proto_limits.go RawHistoryFolder

1package sessionwire
2
3// Directional ceiling enforcement (P2.2/P3.3, P5.1 limits table).
4//
5// The preamble negotiates one immutable pair of ceilings per connection:
6// maxReceiveEnvelopeBytes bounds every envelope this side accepts, and
7// outputDataLimit bounds the uncompressed terminal bytes of one Output
8// message. Envelope ceilings are category-aware:
9//
10//   - ordinary control envelopes never exceed wire.ControlEnvelopeLimit;
11//   - payload-bearing Input, Output, ImagePush, PickerSnapshot, and
12//     NavigationInventoryResponse variants may use the full negotiated
13//     envelope ceiling, which
14//     the preamble already bounds by wire.AbsoluteEnvelopeLimit.
15//
16// A negotiated ceiling below the applicable category ceiling always wins.
17// Category detection is centralized here and derives from the serialized
18// payload, so every typed send (normal, async, owned-synchronous) and every
19// typed receive shares one classification; unknown or malformed payloads
20// fall back to control, the strictest category. Carriage framing limits
21// stay owned by the concrete transports.
22
23import (
24	"errors"
25
26	"google.golang.org/protobuf/encoding/protowire"
27
28	"github.com/bnema/vev/internal/protocol/wire"
29)
30
31// errOutputDataExceedsLimit reports one Output whose uncompressed terminal
32// data is above the negotiated output-data ceiling.
33var errOutputDataExceedsLimit = errors.New("sessionwire: output exceeds negotiated data limit")
34
35// envelopeCategory selects the serialized-size ceiling for one directional
36// envelope.
37type envelopeCategory uint8
38
39const (
40	// categoryControl covers every envelope that is neither Output nor
41	// ImagePush; it never exceeds wire.ControlEnvelopeLimit.
42	categoryControl envelopeCategory = iota
43	// categoryOutput covers server Output; its serialized envelope rides
44	// the negotiated envelope ceiling and its uncompressed data is bounded
45	// separately by outputDataLimit.
46	categoryOutput
47	// categoryBulk covers bounded application payloads whose semantic limits
48	// exceed the ordinary 1 MiB control ceiling.
49	categoryBulk
50)
51
52// Envelope variant field numbers that select the ceiling-exempt categories;
53// see schema/envelope.proto. Every other variant, known or not, is control.
54const (
55	clientInputField               = 2
56	clientImagePushField           = 10
57	serverOutputField              = 3
58	serverNavigationInventoryField = 19
59	serverPickerSnapshotField      = 23
60)
61
62// envelopeLimitFor returns the serialized-size ceiling for one categorized
63// envelope: control is capped at wire.ControlEnvelopeLimit while bulk
64// payloads may use the full negotiated ceiling. A negotiated
65// ceiling below the category ceiling always wins.
66func envelopeLimitFor(category envelopeCategory, negotiated uint64) uint64 {
67	if category == categoryControl && negotiated > wire.ControlEnvelopeLimit {
68		return wire.ControlEnvelopeLimit
69	}
70	return negotiated
71}
72
73// checkEnvelopeCeiling rejects one serialized envelope above ceiling bytes.
74func checkEnvelopeCeiling(payload []byte, ceiling uint64) error {
75	if uint64(len(payload)) > ceiling {
76		return wire.ErrScanLength
77	}
78	return nil
79}
80
81// checkCategoryCeiling classifies one serialized directional payload and
82// applies its category ceiling. Sends and receives share this single
83// enforcement point.
84func checkCategoryCeiling(payload []byte, category envelopeCategory, negotiated uint64) error {
85	return checkEnvelopeCeiling(payload, envelopeLimitFor(category, negotiated))
86}
87
88// clientEnvelopeCategory classifies one serialized client envelope before
89// decode. Input and ImagePush are exempt from wire.ControlEnvelopeLimit; every
90// other variant, including unknown and malformed payloads, stays control so
91// the strictest ceiling applies.
92func clientEnvelopeCategory(payload []byte) envelopeCategory {
93	switch leadingField(payload) {
94	case clientInputField, clientImagePushField:
95		return categoryBulk
96	}
97	return categoryControl
98}
99
100// serverEnvelopeCategory classifies one serialized server envelope before
101// decode. Output and the semantically 4 MiB-bounded inventory/picker snapshots
102// are exempt from wire.ControlEnvelopeLimit; every other variant, including
103// unknown and malformed payloads, stays control.
104func serverEnvelopeCategory(payload []byte) envelopeCategory {
105	switch leadingField(payload) {
106	case serverOutputField:
107		return categoryOutput
108	case serverNavigationInventoryField, serverPickerSnapshotField:
109		return categoryBulk
110	}
111	return categoryControl
112}
113
114// leadingField returns the field number of payload's leading tag when it is
115// length-delimited, the wire type every envelope variant uses. It returns 0
116// for empty, truncated, or non-length-delimited payloads, which then
117// classify as control.
118func leadingField(payload []byte) protowire.Number {
119	number, wireType, consumed := protowire.ConsumeTag(payload)
120	if consumed <= 0 || wireType != protowire.BytesType {
121		return 0
122	}
123	return number
124}
125
126// checkOutputDataCeiling rejects one serialized Output envelope whose
127// declared uncompressed length is above the negotiated output-data ceiling.
128// It runs before decompression so hostile lengths never allocate.
129func checkOutputDataCeiling(envelope *wire.ServerEnvelope, limit uint64) error {
130	if envelope == nil {
131		return nil
132	}
133	output, ok := envelope.Payload.(*wire.ServerEnvelope_Output)
134	if !ok || output.Output == nil {
135		return nil
136	}
137	if output.Output.GetUncompressedLength() > limit {
138		return errOutputDataExceedsLimit
139	}
140	return nil
141}
142
143// sendEnvelopeWithin marshals one server envelope and enforces the
144// negotiated output-data and category envelope ceilings before any
145// transport call. All server send modes (normal, async, owned synchronous)
146// share it.
147func sendEnvelopeWithin(envelope *wire.ServerEnvelope, ceilings protoCeilings) (wire.Envelope, error) {
148	if err := checkOutputDataCeiling(envelope, ceilings.outputDataLimit); err != nil {
149		return wire.Envelope{}, err
150	}
151	raw, err := marshalServerEnvelope(envelope)
152	if err != nil {
153		return wire.Envelope{}, err
154	}
155	if err := checkCategoryCeiling(raw.Payload, serverEnvelopeCategory(raw.Payload), ceilings.maxReceiveEnvelopeBytes); err != nil {
156		return wire.Envelope{}, err
157	}
158	return raw, nil
159}

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