Skip to content

Commit 5c858e3

Browse files
drpcwire: add KindWindowUpdate flow-control frame
Define the KindWindowUpdate control frame that carries a flow-control credit grant: a varint byte delta in the body, for the stream named by the frame's stream id. Add WindowUpdateFrame/ParseWindowUpdate helpers to build and read it. Stream id 0 is reserved for a possible future connection-level window and is unused in v1. The control bit marks the frame as out-of-band signaling, so it is emitted without blocking on data backpressure. It is not relied on for backward compatibility: an unknown control frame is only dropped after packet assembly, so it is not safely ignorable on an active stream. Instead, grants are only ever sent once flow control is active cluster-wide, so a peer that predates flow control never receives one. ParseWindowUpdate enforces the whole v1 wire contract in one place, since these frames are intercepted before packet assembly and its message-id checks: it accepts only a self-contained control frame (Control and Done set) naming a real stream (id != 0) with a positive delta and no trailing bytes. A non-conforming frame yields ok=false and is dropped by the caller, so a reserved stream-0 update from a future peer is ignored rather than mishandled. This is dormant scaffolding for the flow-control work: nothing emits or acts on the frame yet. Co-Authored-By: roachdev-claude <roachdev-claude-bot@cockroachlabs.com>
1 parent cbabd21 commit 5c858e3

3 files changed

Lines changed: 95 additions & 2 deletions

File tree

drpcwire/packet.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ const (
3636

3737
// KindInvokeMetadata includes metadata about the next Invoke packet.
3838
KindInvokeMetadata Kind = 7
39+
40+
// KindWindowUpdate carries a flow-control credit grant: a varint byte delta
41+
// for the frame's stream id. Stream id 0 is reserved for a future
42+
// connection-level window and is unused in v1.
43+
KindWindowUpdate Kind = 8
3944
)
4045

4146
//
@@ -143,6 +148,35 @@ func AppendFrame(buf []byte, fr Frame) []byte {
143148
return out
144149
}
145150

151+
// WindowUpdateFrame builds a KindWindowUpdate grant frame. Callers must pass a
152+
// real stream id (>0; 0 is reserved) and a positive delta, the conditions
153+
// ParseWindowUpdate enforces.
154+
func WindowUpdateFrame(streamID, delta uint64) Frame {
155+
return Frame{
156+
Data: AppendVarint(nil, delta),
157+
ID: ID{Stream: streamID},
158+
Kind: KindWindowUpdate,
159+
Done: true,
160+
Control: true,
161+
}
162+
}
163+
164+
// ParseWindowUpdate returns a grant frame's stream id and delta. ok is false
165+
// unless the frame conforms to the wire contract, so the caller drops
166+
// non-conforming frames rather than acting on them.
167+
func ParseWindowUpdate(fr Frame) (streamID, delta uint64, ok bool) {
168+
// A self-contained control frame for a real stream; the message id is
169+
// unchecked since grants are intercepted before packet assembly.
170+
if fr.Kind != KindWindowUpdate || !fr.Control || !fr.Done || fr.ID.Stream == 0 {
171+
return 0, 0, false
172+
}
173+
rem, d, parsed, err := ReadVarint(fr.Data)
174+
if !parsed || err != nil || len(rem) != 0 || d == 0 { // positive delta, no trailing bytes
175+
return 0, 0, false
176+
}
177+
return fr.ID.Stream, d, true
178+
}
179+
146180
//
147181
// packet
148182
//

drpcwire/packet_string.go

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

drpcwire/packet_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,61 @@ func TestAppendParse(t *testing.T) {
1919
assert.DeepEqual(t, got, exp)
2020
}
2121
}
22+
23+
func TestKindWindowUpdateString(t *testing.T) {
24+
assert.Equal(t, KindWindowUpdate.String(), "WindowUpdate")
25+
}
26+
27+
func TestWindowUpdateFrameRoundTrip(t *testing.T) {
28+
for _, tc := range []struct {
29+
stream uint64
30+
delta uint64
31+
}{
32+
{stream: 7, delta: 128 << 10}, // per-stream
33+
{stream: 123456, delta: 1 << 40}, // large delta
34+
} {
35+
fr := WindowUpdateFrame(tc.stream, tc.delta)
36+
assert.That(t, fr.Control)
37+
assert.That(t, fr.Done)
38+
assert.Equal(t, fr.Kind, KindWindowUpdate)
39+
40+
rem, got, ok, err := ParseFrame(AppendFrame(nil, fr))
41+
assert.NoError(t, err)
42+
assert.That(t, ok)
43+
assert.Equal(t, len(rem), 0)
44+
45+
sid, delta, ok := ParseWindowUpdate(got)
46+
assert.That(t, ok)
47+
assert.Equal(t, sid, tc.stream)
48+
assert.Equal(t, delta, tc.delta)
49+
}
50+
}
51+
52+
func TestParseWindowUpdateRejectsNonconforming(t *testing.T) {
53+
if _, _, ok := ParseWindowUpdate(WindowUpdateFrame(7, 128)); !ok {
54+
t.Fatal("conforming window update did not parse")
55+
}
56+
57+
notControl := WindowUpdateFrame(7, 128)
58+
notControl.Control = false
59+
notDone := WindowUpdateFrame(7, 128)
60+
notDone.Done = false
61+
trailing := WindowUpdateFrame(7, 128)
62+
trailing.Data = append(append([]byte(nil), trailing.Data...), 0xff)
63+
empty := WindowUpdateFrame(7, 128)
64+
empty.Data = nil
65+
66+
for name, fr := range map[string]Frame{
67+
"wrong kind": {Kind: KindMessage, Control: true, Done: true, ID: ID{Stream: 7}, Data: AppendVarint(nil, 1)},
68+
"not control": notControl,
69+
"not done": notDone,
70+
"stream zero": WindowUpdateFrame(0, 1),
71+
"zero delta": WindowUpdateFrame(7, 0),
72+
"trailing bytes": trailing,
73+
"empty payload": empty,
74+
} {
75+
if _, _, ok := ParseWindowUpdate(fr); ok {
76+
t.Fatalf("expected %q to be rejected", name)
77+
}
78+
}
79+
}

0 commit comments

Comments
 (0)