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