1package sessionwire
2
3import (
4 "testing"
5
6 "github.com/bnema/vev/internal/domain"
7 "github.com/bnema/vev/internal/protocol"
8 "github.com/bnema/vev/internal/protocol/wire"
9 "github.com/stretchr/testify/require"
10)
11
12func testWireHello(t *testing.T) *wire.Hello {
13 t.Helper()
14 hello, err := helloToWire(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}})
15 require.NoError(t, err)
16 return hello
17}
18
19func testExactWireTarget() *wire.ExactTarget {
20 lifecycle := domain.SessionLifecycleID{1}
21 return &wire.ExactTarget{LifecycleId: &wire.LifecycleID{Value: lifecycle[:]}, SessionName: "work"}
22}
23
24// TestNarrowingConversionsRejectOverflow proves every wire uint32 -> smaller
25// semantic conversion is validated before the cast: hostile high bits can no
26// longer truncate into a valid enum or geometry range.
27func TestNarrowingConversionsRejectOverflow(t *testing.T) {
28 tests := []struct {
29 name string
30 run func(t *testing.T) error
31 wantErr error
32 }{
33 {
34 name: "hello environment policy",
35 run: func(t *testing.T) error {
36 hello := testWireHello(t)
37 hello.EnvironmentPolicy = 256
38 _, err := helloFromWire(hello)
39 return err
40 },
41 wantErr: errProtoConvertRange,
42 },
43 {
44 name: "hello navigation capabilities",
45 run: func(t *testing.T) error {
46 hello := testWireHello(t)
47 hello.NavigationCapabilities = 256
48 _, err := helloFromWire(hello)
49 return err
50 },
51 wantErr: errProtoConvertRange,
52 },
53 {
54 name: "hello output window",
55 run: func(t *testing.T) error {
56 hello := testWireHello(t)
57 hello.MaxOutputInFlight = 256
58 _, err := helloFromWire(hello)
59 return err
60 },
61 wantErr: errProtoConvertRange,
62 },
63 {
64 name: "hello terminal focus range",
65 run: func(t *testing.T) error {
66 hello := testWireHello(t)
67 hello.TerminalFocus = 256
68 _, err := helloFromWire(hello)
69 return err
70 },
71 wantErr: errProtoConvertRange,
72 },
73 {
74 name: "hello terminal focus taxonomy",
75 run: func(t *testing.T) error {
76 hello := testWireHello(t)
77 hello.TerminalFocus = 3
78 _, err := helloFromWire(hello)
79 return err
80 },
81 wantErr: protocol.ErrInvalidHello,
82 },
83 {
84 name: "hello geometry",
85 run: func(t *testing.T) error {
86 hello := testWireHello(t)
87 hello.Cols = 70000
88 _, err := helloFromWire(hello)
89 return err
90 },
91 wantErr: errProtoConvertRange,
92 },
93 {
94 name: "attach target environment policy",
95 run: func(t *testing.T) error {
96 _, err := attachTargetFromWire(&wire.AttachTarget{Session: "work", Intent: uint32(protocol.IntentAttach), EnvironmentPolicy: 256})
97 return err
98 },
99 wantErr: protocol.ErrInvalidAttachTarget,
100 },
101 {
102 name: "attach target intent",
103 run: func(t *testing.T) error {
104 _, err := attachTargetFromWire(&wire.AttachTarget{Session: "work", EnvironmentPolicy: uint32(protocol.EnvironmentPolicyClientOwned), Intent: 256})
105 return err
106 },
107 wantErr: protocol.ErrInvalidAttachTarget,
108 },
109 {
110 name: "kill scope aliasing",
111 run: func(t *testing.T) error { _, err := killFromWire(&wire.Kill{Name: "work", Scope: 257}); return err },
112 wantErr: errProtoConvertRange,
113 },
114 {
115 name: "session state",
116 run: func(t *testing.T) error {
117 _, err := sessionsFromWire(&wire.Sessions{Sessions: []*wire.SessionInfo{{State: 256}}})
118 return err
119 },
120 wantErr: errProtoConvertRange,
121 },
122 {
123 name: "session tab count",
124 run: func(t *testing.T) error {
125 _, err := sessionsFromWire(&wire.Sessions{Sessions: []*wire.SessionInfo{{Tabs: 70000}}})
126 return err
127 },
128 wantErr: errProtoConvertRange,
129 },
130 {
131 name: "ui receipt outcome aliasing",
132 run: func(t *testing.T) error { _, err := uiReceiptFromWire(&wire.UIReceipt{Outcome: 257}); return err },
133 wantErr: errProtoConvertRange,
134 },
135 {
136 name: "recent route kind",
137 run: func(t *testing.T) error {
138 _, err := recentRouteEntryFromWire(&wire.RecentRouteEntry{Target: testExactWireTarget(), Kind: 256})
139 return err
140 },
141 wantErr: errProtoConvertRange,
142 },
143 {
144 name: "recent route reachability",
145 run: func(t *testing.T) error {
146 _, err := recentRouteEntryFromWire(&wire.RecentRouteEntry{Target: testExactWireTarget(), Reachability: 257})
147 return err
148 },
149 wantErr: errProtoConvertRange,
150 },
151 {
152 name: "route failure code",
153 run: func(t *testing.T) error {
154 _, err := navigationFailureFromWire(&wire.RouteNavigationFailure{Code: 256})
155 return err
156 },
157 wantErr: errProtoConvertRange,
158 },
159 {
160 name: "route failure decode identity",
161 run: func(t *testing.T) error {
162 _, err := navigationFailureFromWire(&wire.RouteNavigationFailure{Code: uint32(protocol.RouteFailureUnavailable)})
163 return err
164 },
165 wantErr: protocol.ErrInvalidRouteWire,
166 },
167 {
168 name: "route failure encode identity",
169 run: func(t *testing.T) error {
170 _, err := navigationFailureToWire(protocol.RouteNavigationFailure{Code: protocol.RouteFailureUnavailable})
171 return err
172 },
173 wantErr: protocol.ErrInvalidRouteWire,
174 },
175 {
176 name: "same peer switch failure code",
177 run: func(t *testing.T) error {
178 _, err := samePeerSwitchFailureFromWire(&wire.SamePeerSwitchFailure{Code: 256})
179 return err
180 },
181 wantErr: protocol.ErrInvalidRouteWire,
182 },
183 {
184 name: "session creation failure code",
185 run: func(t *testing.T) error {
186 _, err := sessionCreationFailureFromWire(&wire.SessionCreationFailure{Code: 256})
187 return err
188 },
189 wantErr: protocol.ErrInvalidRouteWire,
190 },
191 {
192 name: "inventory request operation",
193 run: func(t *testing.T) error {
194 _, err := inventoryRequestFromWire(&wire.NavigationInventoryRequest{Operation: 257})
195 return err
196 },
197 wantErr: protocol.ErrInvalidNavigation,
198 },
199 {
200 name: "inventory response status",
201 run: func(t *testing.T) error {
202 _, err := inventoryResponseFromWire(&wire.NavigationInventoryResponse{Operation: 1, Status: 257})
203 return err
204 },
205 wantErr: protocol.ErrInvalidNavigation,
206 },
207 {
208 name: "inventory failure code",
209 run: func(t *testing.T) error {
210 _, err := inventoryFailureFromWire(&wire.NavigationInventoryFailure{Code: 256})
211 return err
212 },
213 wantErr: protocol.ErrInvalidNavigation,
214 },
215 {
216 name: "inventory source status",
217 run: func(t *testing.T) error {
218 _, err := inventoryGroupsFromWire([]*wire.InventorySourceGroup{{Status: 257}})
219 return err
220 },
221 wantErr: errProtoConvertRange,
222 },
223 {
224 name: "picker line kind",
225 run: func(t *testing.T) error { _, err := pickerLineFromWire(&wire.PickerLine{Kind: 256}); return err },
226 wantErr: protocol.ErrInvalidNavigation,
227 },
228 {
229 name: "picker selection action",
230 run: func(t *testing.T) error {
231 _, err := pickerSelectionFromWire(&wire.PickerSelection{Action: 257})
232 return err
233 },
234 wantErr: errProtoConvertRange,
235 },
236 {
237 name: "remote preview status",
238 run: func(t *testing.T) error {
239 _, err := remotePreviewFromWire(&wire.RemotePreview{Status: 257})
240 return err
241 },
242 wantErr: protocol.ErrInvalidRemotePreview,
243 },
244 }
245 for _, tt := range tests {
246 t.Run(tt.name, func(t *testing.T) {
247 require.ErrorIs(t, tt.run(t), tt.wantErr)
248 })
249 }
250}
251
252func testWireRouteEntry(key, generation uint64) *wire.RecentRouteEntry {
253 return &wire.RecentRouteEntry{
254 Key: key,
255 Generation: generation,
256 Target: testExactWireTarget(),
257 Name: "work",
258 Kind: uint32(protocol.RouteKindLocal),
259 Reachability: uint32(protocol.RouteReachabilityReachable),
260 }
261}
262
263func testSemanticRouteEntry(key, generation uint64) protocol.RecentRouteEntry {
264 lifecycle := domain.SessionLifecycleID{1}
265 return protocol.RecentRouteEntry{
266 Key: key,
267 Generation: generation,
268 Target: protocol.ExactSessionTarget{LifecycleID: lifecycle, SessionName: "work"},
269 Name: "work",
270 Kind: protocol.RouteKindLocal,
271 Reachability: protocol.RouteReachabilityReachable,
272 }
273}
274
275// TestRouteSemanticBoundsRestored proves the old strict route codec bounds
276// and invariants are restored on both conversion directions: encode refuses
277// to emit a malformed value, decode refuses to accept one, and valid bounded
278// values still round-trip unchanged.
279func TestRouteSemanticBoundsRestored(t *testing.T) {
280 entry := testSemanticRouteEntry(1, 1)
281 ref := protocol.RouteRef{Key: 1, Generation: 1}
282 validSnapshot := protocol.RecentRouteSnapshot{Generation: 1, Entries: []protocol.RecentRouteEntry{entry}}
283 validSubscription := protocol.RouteAttentionSubscription{Targets: []protocol.RouteAttentionTarget{{Ref: ref, Target: entry.Target}}}
284 validAction := protocol.RouteNavigationAction{SnapshotGeneration: 1, Key: 1, Generation: 1}
285
286 oversized := validSnapshot
287 for len(oversized.Entries) <= protocol.RouteSnapshotMaxEntries {
288 next := uint64(len(oversized.Entries) + 1)
289 oversized.Entries = append(oversized.Entries, testSemanticRouteEntry(next, next))
290 }
291 mismatched := protocol.RecentRouteSnapshot{
292 Generation: 1,
293 Active: protocol.RouteRef{Key: 2, Generation: 2},
294 ActiveEntry: entry,
295 }
296 duplicateSubscription := protocol.RouteAttentionSubscription{Targets: []protocol.RouteAttentionTarget{
297 {Ref: ref, Target: entry.Target},
298 {Ref: ref, Target: entry.Target},
299 }}
300
301 decodedEntries := make([]*wire.RecentRouteEntry, 0, protocol.RouteSnapshotMaxEntries+1)
302 for i := 1; i <= protocol.RouteSnapshotMaxEntries+1; i++ {
303 decodedEntries = append(decodedEntries, testWireRouteEntry(uint64(i), uint64(i)))
304 }
305
306 tests := []struct {
307 name string
308 run func(t *testing.T) error
309 wantErr error
310 }{
311 {
312 name: "encode oversized snapshot",
313 run: func(t *testing.T) error {
314 _, err := recentRouteSnapshotToWire(oversized)
315 return err
316 },
317 wantErr: protocol.ErrInvalidRouteWire,
318 },
319 {
320 name: "encode mismatched active presentation",
321 run: func(t *testing.T) error {
322 _, err := recentRouteSnapshotToWire(mismatched)
323 return err
324 },
325 wantErr: protocol.ErrInvalidRouteWire,
326 },
327 {
328 name: "encode snapshot missing home reference",
329 run: func(t *testing.T) error {
330 broken := validSnapshot
331 broken.Home = protocol.RouteRef{Key: 9, Generation: 9}
332 _, err := recentRouteSnapshotToWire(broken)
333 return err
334 },
335 wantErr: protocol.ErrInvalidRouteWire,
336 },
337 {
338 name: "encode duplicate attention reference",
339 run: func(t *testing.T) error {
340 _, err := attentionSubscriptionToWire(duplicateSubscription)
341 return err
342 },
343 wantErr: protocol.ErrInvalidRouteWire,
344 },
345 {
346 name: "encode zero navigation action",
347 run: func(t *testing.T) error {
348 _, err := routeNavigationActionToWire(protocol.RouteNavigationAction{})
349 return err
350 },
351 wantErr: protocol.ErrInvalidRouteWire,
352 },
353 {
354 name: "decode oversized snapshot",
355 run: func(t *testing.T) error {
356 _, err := recentRouteSnapshotFromWire(&wire.RecentRouteSnapshot{Generation: 1, Entries: decodedEntries})
357 return err
358 },
359 wantErr: protocol.ErrInvalidRouteWire,
360 },
361 {
362 name: "decode mismatched active presentation",
363 run: func(t *testing.T) error {
364 _, err := recentRouteSnapshotFromWire(&wire.RecentRouteSnapshot{
365 Generation: 1,
366 Active: &wire.RouteRef{Key: 2, Generation: 2},
367 ActiveEntry: testWireRouteEntry(1, 1),
368 })
369 return err
370 },
371 wantErr: protocol.ErrInvalidRouteWire,
372 },
373 {
374 name: "decode duplicate attention reference",
375 run: func(t *testing.T) error {
376 _, err := attentionSubscriptionFromWire(&wire.RouteAttentionSubscription{Targets: []*wire.RouteAttentionTarget{
377 {Ref: &wire.RouteRef{Key: 1, Generation: 1}, Target: testExactWireTarget()},
378 {Ref: &wire.RouteRef{Key: 1, Generation: 1}, Target: testExactWireTarget()},
379 }})
380 return err
381 },
382 wantErr: protocol.ErrInvalidRouteWire,
383 },
384 {
385 name: "decode zero navigation action",
386 run: func(t *testing.T) error {
387 _, err := routeNavigationActionFromWire(&wire.RouteNavigationAction{})
388 return err
389 },
390 wantErr: protocol.ErrInvalidRouteWire,
391 },
392 }
393 for _, tt := range tests {
394 t.Run(tt.name, func(t *testing.T) {
395 require.ErrorIs(t, tt.run(t), tt.wantErr)
396 })
397 }
398
399 t.Run("valid snapshot round trip", func(t *testing.T) {
400 converted, err := recentRouteSnapshotToWire(validSnapshot)
401 require.NoError(t, err)
402 decoded, err := recentRouteSnapshotFromWire(converted)
403 require.NoError(t, err)
404 require.Equal(t, validSnapshot, decoded)
405 })
406 t.Run("valid attention subscription round trip", func(t *testing.T) {
407 converted, err := attentionSubscriptionToWire(validSubscription)
408 require.NoError(t, err)
409 decoded, err := attentionSubscriptionFromWire(converted)
410 require.NoError(t, err)
411 require.Equal(t, validSubscription, decoded)
412 })
413 t.Run("valid navigation action round trip", func(t *testing.T) {
414 converted, err := routeNavigationActionToWire(validAction)
415 require.NoError(t, err)
416 decoded, err := routeNavigationActionFromWire(converted)
417 require.NoError(t, err)
418 require.Equal(t, validAction, decoded)
419 })
420}