feat(protocol): add parked route messages
- commit
- 47e144b1c7518882ec86437858edd528681565cf
- author
- brice · · 2026-08-25 13:34 UTC
- parent
- f571e9b
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
6 files · +292 -46
M internal/ports/frame.go +57 -3
| @@ -12,7 +12,7 @@ import ( | |||
| 12 | 12 | const HandshakeTimeout = 15 * time.Second | |
| 13 | 13 | ||
| 14 | 14 | // ProtocolVersion is the current vev IPC wire protocol version. | |
| 15 | - | const ProtocolVersion uint16 = 33 | |
| 15 | + | const ProtocolVersion uint16 = 34 | |
| 16 | 16 | ||
| 17 | 17 | // MaxFrameLen is the largest permitted frame length, including the type byte | |
| 18 | 18 | // and excluding the four-byte length prefix. | |
| @@ -22,8 +22,8 @@ const MaxFrameLen = 16 << 20 | |||
| 22 | 22 | type MsgType uint8 | |
| 23 | 23 | ||
| 24 | 24 | // Frame message types use non-contiguous allocations: the legacy client range | |
| 25 | - | // is 1–13 and 15, and newer client controls use 32–33. Server-originated | |
| 26 | - | // messages occupy 16–23, 25–31, and 34. Values 14 and 24 remain reserved for | |
| 25 | + | // is 1–13 and 15, and newer client controls use 32–33 and 35. Server-originated | |
| 26 | + | // messages occupy 16–23, 25–31, 34, and 36. Values 14 and 24 remain reserved for | |
| 27 | 27 | // future extensions. | |
| 28 | 28 | const ( | |
| 29 | 29 | MsgHello MsgType = 1 | |
| @@ -42,6 +42,7 @@ const ( | |||
| 42 | 42 | MsgRemotePreviewRequest MsgType = 15 | |
| 43 | 43 | MsgRouteAttentionSubscription MsgType = 32 | |
| 44 | 44 | MsgSamePeerSwitchRequest MsgType = 33 | |
| 45 | + | MsgParkedRouteRequest MsgType = 35 | |
| 45 | 46 | ||
| 46 | 47 | MsgWelcome MsgType = 16 | |
| 47 | 48 | MsgError MsgType = 17 | |
| @@ -61,6 +62,7 @@ const ( | |||
| 61 | 62 | MsgRouteNavigationFailure MsgType = 30 | |
| 62 | 63 | MsgRoutePosition MsgType = 31 | |
| 63 | 64 | MsgSamePeerSwitchFailure MsgType = 34 | |
| 65 | + | MsgParkedRouteResponse MsgType = 36 | |
| 64 | 66 | ) | |
| 65 | 67 | ||
| 66 | 68 | // Frame is the unit of exchange over a Transport: a typed, length-delimited | |
| @@ -97,6 +99,58 @@ const ( | |||
| 97 | 99 | NavigationBack NavigationAction = 2 | |
| 98 | 100 | ) | |
| 99 | 101 | ||
| 102 | + | // ParkedRouteLeaseID is an opaque capability minted by the attached daemon for | |
| 103 | + | // one home-picker excursion. It is bound server-side to the exact connection | |
| 104 | + | // generation and transport that received the navigation directive. | |
| 105 | + | type ParkedRouteLeaseID [16]byte | |
| 106 | + | ||
| 107 | + | func (id ParkedRouteLeaseID) IsZero() bool { return id == ParkedRouteLeaseID{} } | |
| 108 | + | ||
| 109 | + | // NavigationDirective requests one client-owned route transition. Opening the | |
| 110 | + | // home picker always carries a fresh lease; Back is sent by the transient home | |
| 111 | + | // route and therefore carries no lease. | |
| 112 | + | type NavigationDirective struct { | |
| 113 | + | Action NavigationAction | |
| 114 | + | LeaseID ParkedRouteLeaseID | |
| 115 | + | } | |
| 116 | + | ||
| 117 | + | // ParkedRouteAction advances one retained route through its one-shot lease. | |
| 118 | + | type ParkedRouteAction uint8 | |
| 119 | + | ||
| 120 | + | const ( | |
| 121 | + | ParkedRoutePrepare ParkedRouteAction = iota + 1 | |
| 122 | + | ParkedRouteResume | |
| 123 | + | ParkedRouteSwitch | |
| 124 | + | ) | |
| 125 | + | ||
| 126 | + | // ParkedRouteRequest prepares, resumes, or switches the retained authenticated | |
| 127 | + | // route. Target is present only for Switch and retains exact lifecycle/tab | |
| 128 | + | // authority from the home picker's remote catalogue row. | |
| 129 | + | type ParkedRouteRequest struct { | |
| 130 | + | RequestID uint64 | |
| 131 | + | LeaseID ParkedRouteLeaseID | |
| 132 | + | Action ParkedRouteAction | |
| 133 | + | Target *domain.RemoteSessionTarget | |
| 134 | + | } | |
| 135 | + | ||
| 136 | + | // ParkedRouteStatus is the explicit outcome for a retained-route request. | |
| 137 | + | type ParkedRouteStatus uint8 | |
| 138 | + | ||
| 139 | + | const ( | |
| 140 | + | ParkedRouteReady ParkedRouteStatus = iota + 1 | |
| 141 | + | ParkedRouteResumed | |
| 142 | + | ParkedRouteSwitched | |
| 143 | + | ParkedRouteRejected | |
| 144 | + | ParkedRouteExpired | |
| 145 | + | ParkedRouteStaleTarget | |
| 146 | + | ) | |
| 147 | + | ||
| 148 | + | // ParkedRouteResponse correlates one retained-route request with its outcome. | |
| 149 | + | type ParkedRouteResponse struct { | |
| 150 | + | RequestID uint64 | |
| 151 | + | Status ParkedRouteStatus | |
| 152 | + | } | |
| 153 | + | ||
| 100 | 154 | // RemotePreviewSchemaVersion is independent from the attachment IPC version. | |
| 101 | 155 | const RemotePreviewSchemaVersion uint16 = 1 | |
| 102 | 156 | ||
| @@ -12,7 +12,7 @@ import ( | |||||
| 12 | const HandshakeTimeout = 15 * time.Second | 12 | const HandshakeTimeout = 15 * time.Second | ||
| 13 | 13 | ||||
| 14 | // ProtocolVersion is the current vev IPC wire protocol version. | 14 | // ProtocolVersion is the current vev IPC wire protocol version. | ||
| 15 | - | const ProtocolVersion uint16 = 33 | 15 | + | const ProtocolVersion uint16 = 34 |
| 16 | 16 | ||||
| 17 | // MaxFrameLen is the largest permitted frame length, including the type byte | 17 | // MaxFrameLen is the largest permitted frame length, including the type byte | ||
| 18 | // and excluding the four-byte length prefix. | 18 | // and excluding the four-byte length prefix. | ||
| @@ -22,8 +22,8 @@ const MaxFrameLen = 16 << 20 | |||||
| 22 | type MsgType uint8 | 22 | type MsgType uint8 | ||
| 23 | 23 | ||||
| 24 | // Frame message types use non-contiguous allocations: the legacy client range | 24 | // Frame message types use non-contiguous allocations: the legacy client range | ||
| 25 | - | // is 1–13 and 15, and newer client controls use 32–33. Server-originated | 25 | + | // is 1–13 and 15, and newer client controls use 32–33 and 35. Server-originated |
| 26 | - | // messages occupy 16–23, 25–31, and 34. Values 14 and 24 remain reserved for | 26 | + | // messages occupy 16–23, 25–31, 34, and 36. Values 14 and 24 remain reserved for |
| 27 | // future extensions. | 27 | // future extensions. | ||
| 28 | const ( | 28 | const ( | ||
| 29 | MsgHello MsgType = 1 | 29 | MsgHello MsgType = 1 | ||
| @@ -42,6 +42,7 @@ const ( | |||||
| 42 | MsgRemotePreviewRequest MsgType = 15 | 42 | MsgRemotePreviewRequest MsgType = 15 | ||
| 43 | MsgRouteAttentionSubscription MsgType = 32 | 43 | MsgRouteAttentionSubscription MsgType = 32 | ||
| 44 | MsgSamePeerSwitchRequest MsgType = 33 | 44 | MsgSamePeerSwitchRequest MsgType = 33 | ||
| 45 | + | MsgParkedRouteRequest MsgType = 35 | |||
| 45 | 46 | ||||
| 46 | MsgWelcome MsgType = 16 | 47 | MsgWelcome MsgType = 16 | ||
| 47 | MsgError MsgType = 17 | 48 | MsgError MsgType = 17 | ||
| @@ -61,6 +62,7 @@ const ( | |||||
| 61 | MsgRouteNavigationFailure MsgType = 30 | 62 | MsgRouteNavigationFailure MsgType = 30 | ||
| 62 | MsgRoutePosition MsgType = 31 | 63 | MsgRoutePosition MsgType = 31 | ||
| 63 | MsgSamePeerSwitchFailure MsgType = 34 | 64 | MsgSamePeerSwitchFailure MsgType = 34 | ||
| 65 | + | MsgParkedRouteResponse MsgType = 36 | |||
| 64 | ) | 66 | ) | ||
| 65 | 67 | ||||
| 66 | // Frame is the unit of exchange over a Transport: a typed, length-delimited | 68 | // Frame is the unit of exchange over a Transport: a typed, length-delimited | ||
| @@ -97,6 +99,58 @@ const ( | |||||
| 97 | NavigationBack NavigationAction = 2 | 99 | NavigationBack NavigationAction = 2 | ||
| 98 | ) | 100 | ) | ||
| 99 | 101 | ||||
| 102 | + | // ParkedRouteLeaseID is an opaque capability minted by the attached daemon for | |||
| 103 | + | // one home-picker excursion. It is bound server-side to the exact connection | |||
| 104 | + | // generation and transport that received the navigation directive. | |||
| 105 | + | type ParkedRouteLeaseID [16]byte | |||
| 106 | + | ||||
| 107 | + | func (id ParkedRouteLeaseID) IsZero() bool { return id == ParkedRouteLeaseID{} } | |||
| 108 | + | ||||
| 109 | + | // NavigationDirective requests one client-owned route transition. Opening the | |||
| 110 | + | // home picker always carries a fresh lease; Back is sent by the transient home | |||
| 111 | + | // route and therefore carries no lease. | |||
| 112 | + | type NavigationDirective struct { | |||
| 113 | + | Action NavigationAction | |||
| 114 | + | LeaseID ParkedRouteLeaseID | |||
| 115 | + | } | |||
| 116 | + | ||||
| 117 | + | // ParkedRouteAction advances one retained route through its one-shot lease. | |||
| 118 | + | type ParkedRouteAction uint8 | |||
| 119 | + | ||||
| 120 | + | const ( | |||
| 121 | + | ParkedRoutePrepare ParkedRouteAction = iota + 1 | |||
| 122 | + | ParkedRouteResume | |||
| 123 | + | ParkedRouteSwitch | |||
| 124 | + | ) | |||
| 125 | + | ||||
| 126 | + | // ParkedRouteRequest prepares, resumes, or switches the retained authenticated | |||
| 127 | + | // route. Target is present only for Switch and retains exact lifecycle/tab | |||
| 128 | + | // authority from the home picker's remote catalogue row. | |||
| 129 | + | type ParkedRouteRequest struct { | |||
| 130 | + | RequestID uint64 | |||
| 131 | + | LeaseID ParkedRouteLeaseID | |||
| 132 | + | Action ParkedRouteAction | |||
| 133 | + | Target *domain.RemoteSessionTarget | |||
| 134 | + | } | |||
| 135 | + | ||||
| 136 | + | // ParkedRouteStatus is the explicit outcome for a retained-route request. | |||
| 137 | + | type ParkedRouteStatus uint8 | |||
| 138 | + | ||||
| 139 | + | const ( | |||
| 140 | + | ParkedRouteReady ParkedRouteStatus = iota + 1 | |||
| 141 | + | ParkedRouteResumed | |||
| 142 | + | ParkedRouteSwitched | |||
| 143 | + | ParkedRouteRejected | |||
| 144 | + | ParkedRouteExpired | |||
| 145 | + | ParkedRouteStaleTarget | |||
| 146 | + | ) | |||
| 147 | + | ||||
| 148 | + | // ParkedRouteResponse correlates one retained-route request with its outcome. | |||
| 149 | + | type ParkedRouteResponse struct { | |||
| 150 | + | RequestID uint64 | |||
| 151 | + | Status ParkedRouteStatus | |||
| 152 | + | } | |||
| 153 | + | ||||
| 100 | // RemotePreviewSchemaVersion is independent from the attachment IPC version. | 154 | // RemotePreviewSchemaVersion is independent from the attachment IPC version. | ||
| 101 | const RemotePreviewSchemaVersion uint16 = 1 | 155 | const RemotePreviewSchemaVersion uint16 = 1 | ||
| 102 | 156 | ||||
M internal/ports/frame_test.go +5 -4
| @@ -3,8 +3,8 @@ package ports | |||
| 3 | 3 | import "testing" | |
| 4 | 4 | ||
| 5 | 5 | func TestProtocolVersion(t *testing.T) { | |
| 6 | - | if ProtocolVersion != 33 { | |
| 7 | - | t.Fatalf("ProtocolVersion = %d, want 33", ProtocolVersion) | |
| 6 | + | if ProtocolVersion != 34 { | |
| 7 | + | t.Fatalf("ProtocolVersion = %d, want 34", ProtocolVersion) | |
| 8 | 8 | } | |
| 9 | 9 | } | |
| 10 | 10 | ||
| @@ -32,14 +32,15 @@ func TestMsgTypeUnique(t *testing.T) { | |||
| 32 | 32 | {"MsgDetach", MsgDetach}, {"MsgPing", MsgPing}, {"MsgList", MsgList}, | |
| 33 | 33 | {"MsgKill", MsgKill}, {"MsgTheme", MsgTheme}, {"MsgAck", MsgAck}, | |
| 34 | 34 | {"MsgImagePush", MsgImagePush}, {"MsgClientNotice", MsgClientNotice}, | |
| 35 | - | {"MsgCommand", MsgCommand}, {"MsgOutputResetRequest", MsgOutputResetRequest}, {"MsgRemotePreviewRequest", MsgRemotePreviewRequest}, {"MsgRouteAttentionSubscription", MsgRouteAttentionSubscription}, | |
| 35 | + | {"MsgCommand", MsgCommand}, {"MsgOutputResetRequest", MsgOutputResetRequest}, {"MsgRemotePreviewRequest", MsgRemotePreviewRequest}, | |
| 36 | + | {"MsgRouteAttentionSubscription", MsgRouteAttentionSubscription}, {"MsgSamePeerSwitchRequest", MsgSamePeerSwitchRequest}, {"MsgParkedRouteRequest", MsgParkedRouteRequest}, | |
| 36 | 37 | {"MsgWelcome", MsgWelcome}, {"MsgError", MsgError}, {"MsgOutput", MsgOutput}, | |
| 37 | 38 | {"MsgDetached", MsgDetached}, {"MsgPong", MsgPong}, {"MsgSessions", MsgSessions}, | |
| 38 | 39 | {"MsgCommandResult", MsgCommandResult}, | |
| 39 | 40 | {"MsgRemotePreviewResponse", MsgRemotePreviewResponse}, {"MsgAttachTarget", MsgAttachTarget}, {"MsgNavigationAction", MsgNavigationAction}, | |
| 40 | 41 | {"MsgCommittedRouteIdentity", MsgCommittedRouteIdentity}, {"MsgRecentRouteSnapshot", MsgRecentRouteSnapshot}, | |
| 41 | 42 | {"MsgNavigateRecentRoute", MsgNavigateRecentRoute}, {"MsgRouteNavigationFailure", MsgRouteNavigationFailure}, | |
| 42 | - | {"MsgRoutePosition", MsgRoutePosition}, | |
| 43 | + | {"MsgRoutePosition", MsgRoutePosition}, {"MsgSamePeerSwitchFailure", MsgSamePeerSwitchFailure}, {"MsgParkedRouteResponse", MsgParkedRouteResponse}, | |
| 43 | 44 | } | |
| 44 | 45 | ||
| 45 | 46 | seen := make(map[MsgType]string, len(tests)) | |
| @@ -3,8 +3,8 @@ package ports | |||||
| 3 | import "testing" | 3 | import "testing" | ||
| 4 | 4 | ||||
| 5 | func TestProtocolVersion(t *testing.T) { | 5 | func TestProtocolVersion(t *testing.T) { | ||
| 6 | - | if ProtocolVersion != 33 { | 6 | + | if ProtocolVersion != 34 { |
| 7 | - | t.Fatalf("ProtocolVersion = %d, want 33", ProtocolVersion) | 7 | + | t.Fatalf("ProtocolVersion = %d, want 34", ProtocolVersion) |
| 8 | } | 8 | } | ||
| 9 | } | 9 | } | ||
| 10 | 10 | ||||
| @@ -32,14 +32,15 @@ func TestMsgTypeUnique(t *testing.T) { | |||||
| 32 | {"MsgDetach", MsgDetach}, {"MsgPing", MsgPing}, {"MsgList", MsgList}, | 32 | {"MsgDetach", MsgDetach}, {"MsgPing", MsgPing}, {"MsgList", MsgList}, | ||
| 33 | {"MsgKill", MsgKill}, {"MsgTheme", MsgTheme}, {"MsgAck", MsgAck}, | 33 | {"MsgKill", MsgKill}, {"MsgTheme", MsgTheme}, {"MsgAck", MsgAck}, | ||
| 34 | {"MsgImagePush", MsgImagePush}, {"MsgClientNotice", MsgClientNotice}, | 34 | {"MsgImagePush", MsgImagePush}, {"MsgClientNotice", MsgClientNotice}, | ||
| 35 | - | {"MsgCommand", MsgCommand}, {"MsgOutputResetRequest", MsgOutputResetRequest}, {"MsgRemotePreviewRequest", MsgRemotePreviewRequest}, {"MsgRouteAttentionSubscription", MsgRouteAttentionSubscription}, | 35 | + | {"MsgCommand", MsgCommand}, {"MsgOutputResetRequest", MsgOutputResetRequest}, {"MsgRemotePreviewRequest", MsgRemotePreviewRequest}, |
| 36 | + | {"MsgRouteAttentionSubscription", MsgRouteAttentionSubscription}, {"MsgSamePeerSwitchRequest", MsgSamePeerSwitchRequest}, {"MsgParkedRouteRequest", MsgParkedRouteRequest}, | |||
| 36 | {"MsgWelcome", MsgWelcome}, {"MsgError", MsgError}, {"MsgOutput", MsgOutput}, | 37 | {"MsgWelcome", MsgWelcome}, {"MsgError", MsgError}, {"MsgOutput", MsgOutput}, | ||
| 37 | {"MsgDetached", MsgDetached}, {"MsgPong", MsgPong}, {"MsgSessions", MsgSessions}, | 38 | {"MsgDetached", MsgDetached}, {"MsgPong", MsgPong}, {"MsgSessions", MsgSessions}, | ||
| 38 | {"MsgCommandResult", MsgCommandResult}, | 39 | {"MsgCommandResult", MsgCommandResult}, | ||
| 39 | {"MsgRemotePreviewResponse", MsgRemotePreviewResponse}, {"MsgAttachTarget", MsgAttachTarget}, {"MsgNavigationAction", MsgNavigationAction}, | 40 | {"MsgRemotePreviewResponse", MsgRemotePreviewResponse}, {"MsgAttachTarget", MsgAttachTarget}, {"MsgNavigationAction", MsgNavigationAction}, | ||
| 40 | {"MsgCommittedRouteIdentity", MsgCommittedRouteIdentity}, {"MsgRecentRouteSnapshot", MsgRecentRouteSnapshot}, | 41 | {"MsgCommittedRouteIdentity", MsgCommittedRouteIdentity}, {"MsgRecentRouteSnapshot", MsgRecentRouteSnapshot}, | ||
| 41 | {"MsgNavigateRecentRoute", MsgNavigateRecentRoute}, {"MsgRouteNavigationFailure", MsgRouteNavigationFailure}, | 42 | {"MsgNavigateRecentRoute", MsgNavigateRecentRoute}, {"MsgRouteNavigationFailure", MsgRouteNavigationFailure}, | ||
| 42 | - | {"MsgRoutePosition", MsgRoutePosition}, | 43 | + | {"MsgRoutePosition", MsgRoutePosition}, {"MsgSamePeerSwitchFailure", MsgSamePeerSwitchFailure}, {"MsgParkedRouteResponse", MsgParkedRouteResponse}, |
| 43 | } | 44 | } | ||
| 44 | 45 | ||||
| 45 | seen := make(map[MsgType]string, len(tests)) | 46 | seen := make(map[MsgType]string, len(tests)) | ||
M internal/ports/navigation_test.go +108 -18
| @@ -8,39 +8,129 @@ import ( | |||
| 8 | 8 | "github.com/stretchr/testify/require" | |
| 9 | 9 | ) | |
| 10 | 10 | ||
| 11 | - | func TestNavigationActionWireTable(t *testing.T) { | |
| 11 | + | func TestNavigationDirectiveWireTable(t *testing.T) { | |
| 12 | + | lease := ParkedRouteLeaseID{1, 2, 3} | |
| 12 | 13 | tests := []struct { | |
| 13 | - | name string | |
| 14 | - | action NavigationAction | |
| 15 | - | payload []byte | |
| 16 | - | valid bool | |
| 14 | + | name string | |
| 15 | + | directive NavigationDirective | |
| 16 | + | valid bool | |
| 17 | 17 | }{ | |
| 18 | - | {name: "open home", action: NavigationOpenHomePicker, payload: []byte{1}, valid: true}, | |
| 19 | - | {name: "back", action: NavigationBack, payload: []byte{2}, valid: true}, | |
| 20 | - | {name: "zero", action: 0, payload: []byte{0}, valid: false}, | |
| 21 | - | {name: "unknown", action: 3, payload: []byte{3}, valid: false}, | |
| 18 | + | {name: "open home", directive: NavigationDirective{Action: NavigationOpenHomePicker, LeaseID: lease}, valid: true}, | |
| 19 | + | {name: "back", directive: NavigationDirective{Action: NavigationBack}, valid: true}, | |
| 20 | + | {name: "open home without lease", directive: NavigationDirective{Action: NavigationOpenHomePicker}}, | |
| 21 | + | {name: "back with lease", directive: NavigationDirective{Action: NavigationBack, LeaseID: lease}}, | |
| 22 | + | {name: "zero", directive: NavigationDirective{}}, | |
| 23 | + | {name: "unknown", directive: NavigationDirective{Action: 3}}, | |
| 22 | 24 | } | |
| 23 | 25 | ||
| 24 | 26 | for _, tt := range tests { | |
| 25 | 27 | t.Run(tt.name, func(t *testing.T) { | |
| 26 | - | got := MarshalNavigationAction(tt.action) | |
| 28 | + | got := MarshalNavigationDirective(tt.directive) | |
| 27 | 29 | if !tt.valid { | |
| 28 | 30 | require.Nil(t, got) | |
| 29 | - | _, err := UnmarshalNavigationAction(tt.payload) | |
| 30 | - | require.ErrorIs(t, err, ErrInvalidNavigation) | |
| 31 | 31 | return | |
| 32 | 32 | } | |
| 33 | - | require.Equal(t, tt.payload, got) | |
| 34 | - | decoded, err := UnmarshalNavigationAction(got) | |
| 33 | + | decoded, err := UnmarshalNavigationDirective(got) | |
| 35 | 34 | require.NoError(t, err) | |
| 36 | - | require.Equal(t, tt.action, decoded) | |
| 37 | - | assertAllPrefixesFail(t, got, UnmarshalNavigationAction) | |
| 38 | - | _, err = UnmarshalNavigationAction(append(append([]byte(nil), got...), 0)) | |
| 35 | + | require.Equal(t, tt.directive, decoded) | |
| 36 | + | assertAllPrefixesFail(t, got, UnmarshalNavigationDirective) | |
| 37 | + | _, err = UnmarshalNavigationDirective(append(append([]byte(nil), got...), 0)) | |
| 39 | 38 | require.ErrorIs(t, err, ErrInvalidNavigation) | |
| 40 | 39 | }) | |
| 41 | 40 | } | |
| 42 | 41 | } | |
| 43 | 42 | ||
| 43 | + | func TestParkedRouteWireRoundTripsStrictly(t *testing.T) { | |
| 44 | + | lease := ParkedRouteLeaseID{1, 2, 3} | |
| 45 | + | target := &domain.RemoteSessionTarget{ | |
| 46 | + | Endpoint: "remote", DisplayOrigin: "remote", LifecycleID: domain.SessionLifecycleID{4}, | |
| 47 | + | SessionName: "work", LiveTabID: "tab-1", | |
| 48 | + | } | |
| 49 | + | requests := []struct { | |
| 50 | + | name string | |
| 51 | + | value ParkedRouteRequest | |
| 52 | + | golden string | |
| 53 | + | }{ | |
| 54 | + | {name: "prepare", value: ParkedRouteRequest{RequestID: 1, LeaseID: lease, Action: ParkedRoutePrepare}, golden: "0000000000000001" + "01020300000000000000000000000000" + "010001"}, | |
| 55 | + | {name: "resume", value: ParkedRouteRequest{RequestID: 2, LeaseID: lease, Action: ParkedRouteResume}}, | |
| 56 | + | {name: "switch", value: ParkedRouteRequest{RequestID: 3, LeaseID: lease, Action: ParkedRouteSwitch, Target: target}}, | |
| 57 | + | } | |
| 58 | + | for _, tt := range requests { | |
| 59 | + | t.Run("request "+tt.name, func(t *testing.T) { | |
| 60 | + | payload := MarshalParkedRouteRequest(tt.value) | |
| 61 | + | require.NotNil(t, payload) | |
| 62 | + | if tt.golden != "" { | |
| 63 | + | golden, err := hex.DecodeString(tt.golden) | |
| 64 | + | require.NoError(t, err) | |
| 65 | + | require.Equal(t, golden, payload) | |
| 66 | + | } | |
| 67 | + | decoded, err := UnmarshalParkedRouteRequest(payload) | |
| 68 | + | require.NoError(t, err) | |
| 69 | + | require.Equal(t, tt.value, decoded) | |
| 70 | + | assertAllPrefixesFail(t, payload, UnmarshalParkedRouteRequest) | |
| 71 | + | assertTrailingGarbageFails(t, payload, UnmarshalParkedRouteRequest) | |
| 72 | + | }) | |
| 73 | + | } | |
| 74 | + | ||
| 75 | + | responses := []struct { | |
| 76 | + | name string | |
| 77 | + | value ParkedRouteResponse | |
| 78 | + | golden string | |
| 79 | + | }{ | |
| 80 | + | {name: "ready", value: ParkedRouteResponse{RequestID: 1, Status: ParkedRouteReady}, golden: "000000000000000101"}, | |
| 81 | + | {name: "resumed", value: ParkedRouteResponse{RequestID: 2, Status: ParkedRouteResumed}}, | |
| 82 | + | {name: "switched", value: ParkedRouteResponse{RequestID: 3, Status: ParkedRouteSwitched}}, | |
| 83 | + | {name: "rejected", value: ParkedRouteResponse{RequestID: 4, Status: ParkedRouteRejected}}, | |
| 84 | + | {name: "expired", value: ParkedRouteResponse{RequestID: 5, Status: ParkedRouteExpired}}, | |
| 85 | + | {name: "stale target", value: ParkedRouteResponse{RequestID: 6, Status: ParkedRouteStaleTarget}}, | |
| 86 | + | } | |
| 87 | + | for _, tt := range responses { | |
| 88 | + | t.Run("response "+tt.name, func(t *testing.T) { | |
| 89 | + | payload := MarshalParkedRouteResponse(tt.value) | |
| 90 | + | if tt.golden != "" { | |
| 91 | + | golden, err := hex.DecodeString(tt.golden) | |
| 92 | + | require.NoError(t, err) | |
| 93 | + | require.Equal(t, golden, payload) | |
| 94 | + | } | |
| 95 | + | decoded, err := UnmarshalParkedRouteResponse(payload) | |
| 96 | + | require.NoError(t, err) | |
| 97 | + | require.Equal(t, tt.value, decoded) | |
| 98 | + | assertAllPrefixesFail(t, payload, UnmarshalParkedRouteResponse) | |
| 99 | + | assertTrailingGarbageFails(t, payload, UnmarshalParkedRouteResponse) | |
| 100 | + | }) | |
| 101 | + | } | |
| 102 | + | ||
| 103 | + | invalidRequests := []struct { | |
| 104 | + | name string | |
| 105 | + | value ParkedRouteRequest | |
| 106 | + | }{ | |
| 107 | + | {name: "zero request ID", value: ParkedRouteRequest{LeaseID: lease, Action: ParkedRoutePrepare}}, | |
| 108 | + | {name: "zero lease", value: ParkedRouteRequest{RequestID: 1, Action: ParkedRoutePrepare}}, | |
| 109 | + | {name: "unknown action", value: ParkedRouteRequest{RequestID: 1, LeaseID: lease, Action: 99}}, | |
| 110 | + | {name: "prepare target", value: ParkedRouteRequest{RequestID: 1, LeaseID: lease, Action: ParkedRoutePrepare, Target: target}}, | |
| 111 | + | {name: "resume target", value: ParkedRouteRequest{RequestID: 1, LeaseID: lease, Action: ParkedRouteResume, Target: target}}, | |
| 112 | + | {name: "missing switch target", value: ParkedRouteRequest{RequestID: 1, LeaseID: lease, Action: ParkedRouteSwitch}}, | |
| 113 | + | {name: "invalid switch target", value: ParkedRouteRequest{RequestID: 1, LeaseID: lease, Action: ParkedRouteSwitch, Target: &domain.RemoteSessionTarget{}}}, | |
| 114 | + | } | |
| 115 | + | for _, tt := range invalidRequests { | |
| 116 | + | t.Run("invalid request "+tt.name, func(t *testing.T) { | |
| 117 | + | require.Nil(t, MarshalParkedRouteRequest(tt.value)) | |
| 118 | + | }) | |
| 119 | + | } | |
| 120 | + | for _, tt := range []struct { | |
| 121 | + | name string | |
| 122 | + | value ParkedRouteResponse | |
| 123 | + | }{ | |
| 124 | + | {name: "zero request ID", value: ParkedRouteResponse{Status: ParkedRouteReady}}, | |
| 125 | + | {name: "zero status", value: ParkedRouteResponse{RequestID: 1}}, | |
| 126 | + | {name: "unknown status", value: ParkedRouteResponse{RequestID: 1, Status: 99}}, | |
| 127 | + | } { | |
| 128 | + | t.Run("invalid response "+tt.name, func(t *testing.T) { | |
| 129 | + | require.Nil(t, MarshalParkedRouteResponse(tt.value)) | |
| 130 | + | }) | |
| 131 | + | } | |
| 132 | + | } | |
| 133 | + | ||
| 44 | 134 | func TestHelloNavigationValidationTable(t *testing.T) { | |
| 45 | 135 | base := Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}} | |
| 46 | 136 | remoteTarget := &domain.RemoteSessionTarget{ | |
| @@ -62,7 +152,7 @@ func TestHelloNavigationValidationTable(t *testing.T) { | |||
| 62 | 152 | {name: "client-owned home picker rejected", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityHomePicker}, valid: false}, | |
| 63 | 153 | {name: "daemon-owned back picker rejected", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker, EnvironmentPolicy: EnvironmentPolicyDaemonOwned}, valid: false}, | |
| 64 | 154 | {name: "remote-target back picker rejected", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Name: "work", Size: domain.Size{Cols: 80, Rows: 24}, RemoteTarget: remoteTarget, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker, EnvironmentPolicy: EnvironmentPolicyDaemonOwned}, valid: false}, | |
| 65 | - | {name: "back with startup picker", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker}, valid: true, capability: NavigationCapabilityBack, overlay: StartupOverlaySessionPicker, wantPayload: "002102" + "00000000000000000000000000000000" + "0000000000000000" + "0000" + "00500018" + "0000" + "0000" + "00" + "00" + "00000000" + "00" + "00" + "00" + "0000" + "0201"}, | |
| 155 | + | {name: "back with startup picker", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker}, valid: true, capability: NavigationCapabilityBack, overlay: StartupOverlaySessionPicker, wantPayload: "002202" + "00000000000000000000000000000000" + "0000000000000000" + "0000" + "00500018" + "0000" + "0000" + "00" + "00" + "00000000" + "00" + "00" + "00" + "0000" + "0201"}, | |
| 66 | 156 | {name: "new rejects navigation", hello: Hello{Version: ProtocolVersion, Intent: IntentNew, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker}, valid: false}, | |
| 67 | 157 | {name: "unknown capability", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: 4}, valid: false}, | |
| 68 | 158 | {name: "back without picker", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack}, valid: false}, | |
| @@ -8,39 +8,129 @@ import ( | |||||
| 8 | "github.com/stretchr/testify/require" | 8 | "github.com/stretchr/testify/require" | ||
| 9 | ) | 9 | ) | ||
| 10 | 10 | ||||
| 11 | - | func TestNavigationActionWireTable(t *testing.T) { | 11 | + | func TestNavigationDirectiveWireTable(t *testing.T) { |
| 12 | + | lease := ParkedRouteLeaseID{1, 2, 3} | |||
| 12 | tests := []struct { | 13 | tests := []struct { | ||
| 13 | - | name string | 14 | + | name string |
| 14 | - | action NavigationAction | 15 | + | directive NavigationDirective |
| 15 | - | payload []byte | 16 | + | valid bool |
| 16 | - | valid bool | |||
| 17 | }{ | 17 | }{ | ||
| 18 | - | {name: "open home", action: NavigationOpenHomePicker, payload: []byte{1}, valid: true}, | 18 | + | {name: "open home", directive: NavigationDirective{Action: NavigationOpenHomePicker, LeaseID: lease}, valid: true}, |
| 19 | - | {name: "back", action: NavigationBack, payload: []byte{2}, valid: true}, | 19 | + | {name: "back", directive: NavigationDirective{Action: NavigationBack}, valid: true}, |
| 20 | - | {name: "zero", action: 0, payload: []byte{0}, valid: false}, | 20 | + | {name: "open home without lease", directive: NavigationDirective{Action: NavigationOpenHomePicker}}, |
| 21 | - | {name: "unknown", action: 3, payload: []byte{3}, valid: false}, | 21 | + | {name: "back with lease", directive: NavigationDirective{Action: NavigationBack, LeaseID: lease}}, |
| 22 | + | {name: "zero", directive: NavigationDirective{}}, | |||
| 23 | + | {name: "unknown", directive: NavigationDirective{Action: 3}}, | |||
| 22 | } | 24 | } | ||
| 23 | 25 | ||||
| 24 | for _, tt := range tests { | 26 | for _, tt := range tests { | ||
| 25 | t.Run(tt.name, func(t *testing.T) { | 27 | t.Run(tt.name, func(t *testing.T) { | ||
| 26 | - | got := MarshalNavigationAction(tt.action) | 28 | + | got := MarshalNavigationDirective(tt.directive) |
| 27 | if !tt.valid { | 29 | if !tt.valid { | ||
| 28 | require.Nil(t, got) | 30 | require.Nil(t, got) | ||
| 29 | - | _, err := UnmarshalNavigationAction(tt.payload) | |||
| 30 | - | require.ErrorIs(t, err, ErrInvalidNavigation) | |||
| 31 | return | 31 | return | ||
| 32 | } | 32 | } | ||
| 33 | - | require.Equal(t, tt.payload, got) | 33 | + | decoded, err := UnmarshalNavigationDirective(got) |
| 34 | - | decoded, err := UnmarshalNavigationAction(got) | |||
| 35 | require.NoError(t, err) | 34 | require.NoError(t, err) | ||
| 36 | - | require.Equal(t, tt.action, decoded) | 35 | + | require.Equal(t, tt.directive, decoded) |
| 37 | - | assertAllPrefixesFail(t, got, UnmarshalNavigationAction) | 36 | + | assertAllPrefixesFail(t, got, UnmarshalNavigationDirective) |
| 38 | - | _, err = UnmarshalNavigationAction(append(append([]byte(nil), got...), 0)) | 37 | + | _, err = UnmarshalNavigationDirective(append(append([]byte(nil), got...), 0)) |
| 39 | require.ErrorIs(t, err, ErrInvalidNavigation) | 38 | require.ErrorIs(t, err, ErrInvalidNavigation) | ||
| 40 | }) | 39 | }) | ||
| 41 | } | 40 | } | ||
| 42 | } | 41 | } | ||
| 43 | 42 | ||||
| 43 | + | func TestParkedRouteWireRoundTripsStrictly(t *testing.T) { | |||
| 44 | + | lease := ParkedRouteLeaseID{1, 2, 3} | |||
| 45 | + | target := &domain.RemoteSessionTarget{ | |||
| 46 | + | Endpoint: "remote", DisplayOrigin: "remote", LifecycleID: domain.SessionLifecycleID{4}, | |||
| 47 | + | SessionName: "work", LiveTabID: "tab-1", | |||
| 48 | + | } | |||
| 49 | + | requests := []struct { | |||
| 50 | + | name string | |||
| 51 | + | value ParkedRouteRequest | |||
| 52 | + | golden string | |||
| 53 | + | }{ | |||
| 54 | + | {name: "prepare", value: ParkedRouteRequest{RequestID: 1, LeaseID: lease, Action: ParkedRoutePrepare}, golden: "0000000000000001" + "01020300000000000000000000000000" + "010001"}, | |||
| 55 | + | {name: "resume", value: ParkedRouteRequest{RequestID: 2, LeaseID: lease, Action: ParkedRouteResume}}, | |||
| 56 | + | {name: "switch", value: ParkedRouteRequest{RequestID: 3, LeaseID: lease, Action: ParkedRouteSwitch, Target: target}}, | |||
| 57 | + | } | |||
| 58 | + | for _, tt := range requests { | |||
| 59 | + | t.Run("request "+tt.name, func(t *testing.T) { | |||
| 60 | + | payload := MarshalParkedRouteRequest(tt.value) | |||
| 61 | + | require.NotNil(t, payload) | |||
| 62 | + | if tt.golden != "" { | |||
| 63 | + | golden, err := hex.DecodeString(tt.golden) | |||
| 64 | + | require.NoError(t, err) | |||
| 65 | + | require.Equal(t, golden, payload) | |||
| 66 | + | } | |||
| 67 | + | decoded, err := UnmarshalParkedRouteRequest(payload) | |||
| 68 | + | require.NoError(t, err) | |||
| 69 | + | require.Equal(t, tt.value, decoded) | |||
| 70 | + | assertAllPrefixesFail(t, payload, UnmarshalParkedRouteRequest) | |||
| 71 | + | assertTrailingGarbageFails(t, payload, UnmarshalParkedRouteRequest) | |||
| 72 | + | }) | |||
| 73 | + | } | |||
| 74 | + | ||||
| 75 | + | responses := []struct { | |||
| 76 | + | name string | |||
| 77 | + | value ParkedRouteResponse | |||
| 78 | + | golden string | |||
| 79 | + | }{ | |||
| 80 | + | {name: "ready", value: ParkedRouteResponse{RequestID: 1, Status: ParkedRouteReady}, golden: "000000000000000101"}, | |||
| 81 | + | {name: "resumed", value: ParkedRouteResponse{RequestID: 2, Status: ParkedRouteResumed}}, | |||
| 82 | + | {name: "switched", value: ParkedRouteResponse{RequestID: 3, Status: ParkedRouteSwitched}}, | |||
| 83 | + | {name: "rejected", value: ParkedRouteResponse{RequestID: 4, Status: ParkedRouteRejected}}, | |||
| 84 | + | {name: "expired", value: ParkedRouteResponse{RequestID: 5, Status: ParkedRouteExpired}}, | |||
| 85 | + | {name: "stale target", value: ParkedRouteResponse{RequestID: 6, Status: ParkedRouteStaleTarget}}, | |||
| 86 | + | } | |||
| 87 | + | for _, tt := range responses { | |||
| 88 | + | t.Run("response "+tt.name, func(t *testing.T) { | |||
| 89 | + | payload := MarshalParkedRouteResponse(tt.value) | |||
| 90 | + | if tt.golden != "" { | |||
| 91 | + | golden, err := hex.DecodeString(tt.golden) | |||
| 92 | + | require.NoError(t, err) | |||
| 93 | + | require.Equal(t, golden, payload) | |||
| 94 | + | } | |||
| 95 | + | decoded, err := UnmarshalParkedRouteResponse(payload) | |||
| 96 | + | require.NoError(t, err) | |||
| 97 | + | require.Equal(t, tt.value, decoded) | |||
| 98 | + | assertAllPrefixesFail(t, payload, UnmarshalParkedRouteResponse) | |||
| 99 | + | assertTrailingGarbageFails(t, payload, UnmarshalParkedRouteResponse) | |||
| 100 | + | }) | |||
| 101 | + | } | |||
| 102 | + | ||||
| 103 | + | invalidRequests := []struct { | |||
| 104 | + | name string | |||
| 105 | + | value ParkedRouteRequest | |||
| 106 | + | }{ | |||
| 107 | + | {name: "zero request ID", value: ParkedRouteRequest{LeaseID: lease, Action: ParkedRoutePrepare}}, | |||
| 108 | + | {name: "zero lease", value: ParkedRouteRequest{RequestID: 1, Action: ParkedRoutePrepare}}, | |||
| 109 | + | {name: "unknown action", value: ParkedRouteRequest{RequestID: 1, LeaseID: lease, Action: 99}}, | |||
| 110 | + | {name: "prepare target", value: ParkedRouteRequest{RequestID: 1, LeaseID: lease, Action: ParkedRoutePrepare, Target: target}}, | |||
| 111 | + | {name: "resume target", value: ParkedRouteRequest{RequestID: 1, LeaseID: lease, Action: ParkedRouteResume, Target: target}}, | |||
| 112 | + | {name: "missing switch target", value: ParkedRouteRequest{RequestID: 1, LeaseID: lease, Action: ParkedRouteSwitch}}, | |||
| 113 | + | {name: "invalid switch target", value: ParkedRouteRequest{RequestID: 1, LeaseID: lease, Action: ParkedRouteSwitch, Target: &domain.RemoteSessionTarget{}}}, | |||
| 114 | + | } | |||
| 115 | + | for _, tt := range invalidRequests { | |||
| 116 | + | t.Run("invalid request "+tt.name, func(t *testing.T) { | |||
| 117 | + | require.Nil(t, MarshalParkedRouteRequest(tt.value)) | |||
| 118 | + | }) | |||
| 119 | + | } | |||
| 120 | + | for _, tt := range []struct { | |||
| 121 | + | name string | |||
| 122 | + | value ParkedRouteResponse | |||
| 123 | + | }{ | |||
| 124 | + | {name: "zero request ID", value: ParkedRouteResponse{Status: ParkedRouteReady}}, | |||
| 125 | + | {name: "zero status", value: ParkedRouteResponse{RequestID: 1}}, | |||
| 126 | + | {name: "unknown status", value: ParkedRouteResponse{RequestID: 1, Status: 99}}, | |||
| 127 | + | } { | |||
| 128 | + | t.Run("invalid response "+tt.name, func(t *testing.T) { | |||
| 129 | + | require.Nil(t, MarshalParkedRouteResponse(tt.value)) | |||
| 130 | + | }) | |||
| 131 | + | } | |||
| 132 | + | } | |||
| 133 | + | ||||
| 44 | func TestHelloNavigationValidationTable(t *testing.T) { | 134 | func TestHelloNavigationValidationTable(t *testing.T) { | ||
| 45 | base := Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}} | 135 | base := Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}} | ||
| 46 | remoteTarget := &domain.RemoteSessionTarget{ | 136 | remoteTarget := &domain.RemoteSessionTarget{ | ||
| @@ -62,7 +152,7 @@ func TestHelloNavigationValidationTable(t *testing.T) { | |||||
| 62 | {name: "client-owned home picker rejected", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityHomePicker}, valid: false}, | 152 | {name: "client-owned home picker rejected", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityHomePicker}, valid: false}, | ||
| 63 | {name: "daemon-owned back picker rejected", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker, EnvironmentPolicy: EnvironmentPolicyDaemonOwned}, valid: false}, | 153 | {name: "daemon-owned back picker rejected", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker, EnvironmentPolicy: EnvironmentPolicyDaemonOwned}, valid: false}, | ||
| 64 | {name: "remote-target back picker rejected", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Name: "work", Size: domain.Size{Cols: 80, Rows: 24}, RemoteTarget: remoteTarget, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker, EnvironmentPolicy: EnvironmentPolicyDaemonOwned}, valid: false}, | 154 | {name: "remote-target back picker rejected", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Name: "work", Size: domain.Size{Cols: 80, Rows: 24}, RemoteTarget: remoteTarget, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker, EnvironmentPolicy: EnvironmentPolicyDaemonOwned}, valid: false}, | ||
| 65 | - | {name: "back with startup picker", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker}, valid: true, capability: NavigationCapabilityBack, overlay: StartupOverlaySessionPicker, wantPayload: "002102" + "00000000000000000000000000000000" + "0000000000000000" + "0000" + "00500018" + "0000" + "0000" + "00" + "00" + "00000000" + "00" + "00" + "00" + "0000" + "0201"}, | 155 | + | {name: "back with startup picker", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker}, valid: true, capability: NavigationCapabilityBack, overlay: StartupOverlaySessionPicker, wantPayload: "002202" + "00000000000000000000000000000000" + "0000000000000000" + "0000" + "00500018" + "0000" + "0000" + "00" + "00" + "00000000" + "00" + "00" + "00" + "0000" + "0201"}, |
| 66 | {name: "new rejects navigation", hello: Hello{Version: ProtocolVersion, Intent: IntentNew, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker}, valid: false}, | 156 | {name: "new rejects navigation", hello: Hello{Version: ProtocolVersion, Intent: IntentNew, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker}, valid: false}, | ||
| 67 | {name: "unknown capability", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: 4}, valid: false}, | 157 | {name: "unknown capability", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: 4}, valid: false}, | ||
| 68 | {name: "back without picker", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack}, valid: false}, | 158 | {name: "back without picker", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack}, valid: false}, | ||
M internal/ports/wire.go +113 -12
| @@ -2066,27 +2066,128 @@ func UnmarshalRemotePreview(data []byte) (RemotePreview, error) { | |||
| 2066 | 2066 | return p, nil | |
| 2067 | 2067 | } | |
| 2068 | 2068 | ||
| 2069 | - | // MarshalNavigationAction encodes one bounded navigation action. | |
| 2070 | - | func MarshalNavigationAction(action NavigationAction) []byte { | |
| 2071 | - | if action != NavigationOpenHomePicker && action != NavigationBack { | |
| 2069 | + | // MarshalNavigationDirective encodes one bounded navigation directive. | |
| 2070 | + | func MarshalNavigationDirective(directive NavigationDirective) []byte { | |
| 2071 | + | if directive.Action != NavigationOpenHomePicker && directive.Action != NavigationBack { | |
| 2072 | 2072 | return nil | |
| 2073 | 2073 | } | |
| 2074 | - | return []byte{byte(action)} | |
| 2074 | + | if directive.Action == NavigationOpenHomePicker && directive.LeaseID.IsZero() || directive.Action == NavigationBack && !directive.LeaseID.IsZero() { | |
| 2075 | + | return nil | |
| 2076 | + | } | |
| 2077 | + | w := payloadWriter{} | |
| 2078 | + | w.putUint8(uint8(directive.Action)) | |
| 2079 | + | w.putBytes(directive.LeaseID[:]) | |
| 2080 | + | return w.b | |
| 2075 | 2081 | } | |
| 2076 | 2082 | ||
| 2077 | - | // UnmarshalNavigationAction decodes one strict navigation action payload. | |
| 2078 | - | func UnmarshalNavigationAction(b []byte) (NavigationAction, error) { | |
| 2083 | + | // UnmarshalNavigationDirective decodes one strict navigation directive. | |
| 2084 | + | func UnmarshalNavigationDirective(b []byte) (NavigationDirective, error) { | |
| 2079 | 2085 | r := payloadReader{b: b} | |
| 2080 | 2086 | value, err := r.getUint8() | |
| 2081 | 2087 | if err != nil { | |
| 2082 | - | return 0, ErrInvalidNavigation | |
| 2088 | + | return NavigationDirective{}, ErrInvalidNavigation | |
| 2089 | + | } | |
| 2090 | + | lease, err := r.getBytes(len(ParkedRouteLeaseID{})) | |
| 2091 | + | if err != nil { | |
| 2092 | + | return NavigationDirective{}, ErrInvalidNavigation | |
| 2083 | 2093 | } | |
| 2084 | 2094 | if err := r.done(); err != nil { | |
| 2085 | - | return 0, ErrInvalidNavigation | |
| 2095 | + | return NavigationDirective{}, ErrInvalidNavigation | |
| 2096 | + | } | |
| 2097 | + | directive := NavigationDirective{Action: NavigationAction(value)} | |
| 2098 | + | copy(directive.LeaseID[:], lease) | |
| 2099 | + | if MarshalNavigationDirective(directive) == nil { | |
| 2100 | + | return NavigationDirective{}, ErrInvalidNavigation | |
| 2101 | + | } | |
| 2102 | + | return directive, nil | |
| 2103 | + | } | |
| 2104 | + | ||
| 2105 | + | // ValidateParkedRouteRequest enforces the closed action/target shape before a | |
| 2106 | + | // request reaches either side's route state machine. | |
| 2107 | + | func ValidateParkedRouteRequest(request ParkedRouteRequest) error { | |
| 2108 | + | if request.RequestID == 0 || request.LeaseID.IsZero() { | |
| 2109 | + | return ErrInvalidNavigation | |
| 2110 | + | } | |
| 2111 | + | switch request.Action { | |
| 2112 | + | case ParkedRoutePrepare, ParkedRouteResume: | |
| 2113 | + | if request.Target != nil { | |
| 2114 | + | return ErrInvalidNavigation | |
| 2115 | + | } | |
| 2116 | + | case ParkedRouteSwitch: | |
| 2117 | + | if request.Target == nil || validateWireRemoteTarget(*request.Target) != nil { | |
| 2118 | + | return ErrInvalidNavigation | |
| 2119 | + | } | |
| 2120 | + | default: | |
| 2121 | + | return ErrInvalidNavigation | |
| 2122 | + | } | |
| 2123 | + | return nil | |
| 2124 | + | } | |
| 2125 | + | ||
| 2126 | + | // MarshalParkedRouteRequest encodes one retained-route operation. | |
| 2127 | + | func MarshalParkedRouteRequest(request ParkedRouteRequest) []byte { | |
| 2128 | + | if ValidateParkedRouteRequest(request) != nil { | |
| 2129 | + | return nil | |
| 2130 | + | } | |
| 2131 | + | w := payloadWriter{} | |
| 2132 | + | w.putUint64(request.RequestID) | |
| 2133 | + | w.putBytes(request.LeaseID[:]) | |
| 2134 | + | w.putUint8(uint8(request.Action)) | |
| 2135 | + | marshalRemoteTargetSection(&w, request.Target, EnvironmentPolicyDaemonOwned) | |
| 2136 | + | return w.b | |
| 2137 | + | } | |
| 2138 | + | ||
| 2139 | + | // UnmarshalParkedRouteRequest decodes one strict retained-route operation. | |
| 2140 | + | func UnmarshalParkedRouteRequest(b []byte) (ParkedRouteRequest, error) { | |
| 2141 | + | r := payloadReader{b: b} | |
| 2142 | + | requestID, err := r.getUint64() | |
| 2143 | + | if err != nil { | |
| 2144 | + | return ParkedRouteRequest{}, ErrInvalidNavigation | |
| 2145 | + | } | |
| 2146 | + | lease, err := r.getBytes(len(ParkedRouteLeaseID{})) | |
| 2147 | + | if err != nil { | |
| 2148 | + | return ParkedRouteRequest{}, ErrInvalidNavigation | |
| 2149 | + | } | |
| 2150 | + | action, err := r.getUint8() | |
| 2151 | + | if err != nil { | |
| 2152 | + | return ParkedRouteRequest{}, ErrInvalidNavigation | |
| 2153 | + | } | |
| 2154 | + | target, policy, err := unmarshalRemoteTargetSection(&r) | |
| 2155 | + | if err != nil || policy != EnvironmentPolicyDaemonOwned || r.done() != nil { | |
| 2156 | + | return ParkedRouteRequest{}, ErrInvalidNavigation | |
| 2157 | + | } | |
| 2158 | + | request := ParkedRouteRequest{RequestID: requestID, Action: ParkedRouteAction(action), Target: target} | |
| 2159 | + | copy(request.LeaseID[:], lease) | |
| 2160 | + | if ValidateParkedRouteRequest(request) != nil { | |
| 2161 | + | return ParkedRouteRequest{}, ErrInvalidNavigation | |
| 2162 | + | } | |
| 2163 | + | return request, nil | |
| 2164 | + | } | |
| 2165 | + | ||
| 2166 | + | // MarshalParkedRouteResponse encodes one correlated retained-route outcome. | |
| 2167 | + | func MarshalParkedRouteResponse(response ParkedRouteResponse) []byte { | |
| 2168 | + | if response.RequestID == 0 || response.Status < ParkedRouteReady || response.Status > ParkedRouteStaleTarget { | |
| 2169 | + | return nil | |
| 2170 | + | } | |
| 2171 | + | w := payloadWriter{} | |
| 2172 | + | w.putUint64(response.RequestID) | |
| 2173 | + | w.putUint8(uint8(response.Status)) | |
| 2174 | + | return w.b | |
| 2175 | + | } | |
| 2176 | + | ||
| 2177 | + | // UnmarshalParkedRouteResponse decodes one strict retained-route outcome. | |
| 2178 | + | func UnmarshalParkedRouteResponse(b []byte) (ParkedRouteResponse, error) { | |
| 2179 | + | r := payloadReader{b: b} | |
| 2180 | + | requestID, err := r.getUint64() | |
| 2181 | + | if err != nil { | |
| 2182 | + | return ParkedRouteResponse{}, ErrInvalidNavigation | |
| 2183 | + | } | |
| 2184 | + | status, err := r.getUint8() | |
| 2185 | + | if err != nil || r.done() != nil { | |
| 2186 | + | return ParkedRouteResponse{}, ErrInvalidNavigation | |
| 2086 | 2187 | } | |
| 2087 | - | action := NavigationAction(value) | |
| 2088 | - | if action != NavigationOpenHomePicker && action != NavigationBack { | |
| 2089 | - | return 0, ErrInvalidNavigation | |
| 2188 | + | response := ParkedRouteResponse{RequestID: requestID, Status: ParkedRouteStatus(status)} | |
| 2189 | + | if MarshalParkedRouteResponse(response) == nil { | |
| 2190 | + | return ParkedRouteResponse{}, ErrInvalidNavigation | |
| 2090 | 2191 | } | |
| 2091 | - | return action, nil | |
| 2192 | + | return response, nil | |
| 2092 | 2193 | } | |
| @@ -2066,27 +2066,128 @@ func UnmarshalRemotePreview(data []byte) (RemotePreview, error) { | |||||
| 2066 | return p, nil | 2066 | return p, nil | ||
| 2067 | } | 2067 | } | ||
| 2068 | 2068 | ||||
| 2069 | - | // MarshalNavigationAction encodes one bounded navigation action. | 2069 | + | // MarshalNavigationDirective encodes one bounded navigation directive. |
| 2070 | - | func MarshalNavigationAction(action NavigationAction) []byte { | 2070 | + | func MarshalNavigationDirective(directive NavigationDirective) []byte { |
| 2071 | - | if action != NavigationOpenHomePicker && action != NavigationBack { | 2071 | + | if directive.Action != NavigationOpenHomePicker && directive.Action != NavigationBack { |
| 2072 | return nil | 2072 | return nil | ||
| 2073 | } | 2073 | } | ||
| 2074 | - | return []byte{byte(action)} | 2074 | + | if directive.Action == NavigationOpenHomePicker && directive.LeaseID.IsZero() || directive.Action == NavigationBack && !directive.LeaseID.IsZero() { |
| 2075 | + | return nil | |||
| 2076 | + | } | |||
| 2077 | + | w := payloadWriter{} | |||
| 2078 | + | w.putUint8(uint8(directive.Action)) | |||
| 2079 | + | w.putBytes(directive.LeaseID[:]) | |||
| 2080 | + | return w.b | |||
| 2075 | } | 2081 | } | ||
| 2076 | 2082 | ||||
| 2077 | - | // UnmarshalNavigationAction decodes one strict navigation action payload. | 2083 | + | // UnmarshalNavigationDirective decodes one strict navigation directive. |
| 2078 | - | func UnmarshalNavigationAction(b []byte) (NavigationAction, error) { | 2084 | + | func UnmarshalNavigationDirective(b []byte) (NavigationDirective, error) { |
| 2079 | r := payloadReader{b: b} | 2085 | r := payloadReader{b: b} | ||
| 2080 | value, err := r.getUint8() | 2086 | value, err := r.getUint8() | ||
| 2081 | if err != nil { | 2087 | if err != nil { | ||
| 2082 | - | return 0, ErrInvalidNavigation | 2088 | + | return NavigationDirective{}, ErrInvalidNavigation |
| 2089 | + | } | |||
| 2090 | + | lease, err := r.getBytes(len(ParkedRouteLeaseID{})) | |||
| 2091 | + | if err != nil { | |||
| 2092 | + | return NavigationDirective{}, ErrInvalidNavigation | |||
| 2083 | } | 2093 | } | ||
| 2084 | if err := r.done(); err != nil { | 2094 | if err := r.done(); err != nil { | ||
| 2085 | - | return 0, ErrInvalidNavigation | 2095 | + | return NavigationDirective{}, ErrInvalidNavigation |
| 2096 | + | } | |||
| 2097 | + | directive := NavigationDirective{Action: NavigationAction(value)} | |||
| 2098 | + | copy(directive.LeaseID[:], lease) | |||
| 2099 | + | if MarshalNavigationDirective(directive) == nil { | |||
| 2100 | + | return NavigationDirective{}, ErrInvalidNavigation | |||
| 2101 | + | } | |||
| 2102 | + | return directive, nil | |||
| 2103 | + | } | |||
| 2104 | + | ||||
| 2105 | + | // ValidateParkedRouteRequest enforces the closed action/target shape before a | |||
| 2106 | + | // request reaches either side's route state machine. | |||
| 2107 | + | func ValidateParkedRouteRequest(request ParkedRouteRequest) error { | |||
| 2108 | + | if request.RequestID == 0 || request.LeaseID.IsZero() { | |||
| 2109 | + | return ErrInvalidNavigation | |||
| 2110 | + | } | |||
| 2111 | + | switch request.Action { | |||
| 2112 | + | case ParkedRoutePrepare, ParkedRouteResume: | |||
| 2113 | + | if request.Target != nil { | |||
| 2114 | + | return ErrInvalidNavigation | |||
| 2115 | + | } | |||
| 2116 | + | case ParkedRouteSwitch: | |||
| 2117 | + | if request.Target == nil || validateWireRemoteTarget(*request.Target) != nil { | |||
| 2118 | + | return ErrInvalidNavigation | |||
| 2119 | + | } | |||
| 2120 | + | default: | |||
| 2121 | + | return ErrInvalidNavigation | |||
| 2122 | + | } | |||
| 2123 | + | return nil | |||
| 2124 | + | } | |||
| 2125 | + | ||||
| 2126 | + | // MarshalParkedRouteRequest encodes one retained-route operation. | |||
| 2127 | + | func MarshalParkedRouteRequest(request ParkedRouteRequest) []byte { | |||
| 2128 | + | if ValidateParkedRouteRequest(request) != nil { | |||
| 2129 | + | return nil | |||
| 2130 | + | } | |||
| 2131 | + | w := payloadWriter{} | |||
| 2132 | + | w.putUint64(request.RequestID) | |||
| 2133 | + | w.putBytes(request.LeaseID[:]) | |||
| 2134 | + | w.putUint8(uint8(request.Action)) | |||
| 2135 | + | marshalRemoteTargetSection(&w, request.Target, EnvironmentPolicyDaemonOwned) | |||
| 2136 | + | return w.b | |||
| 2137 | + | } | |||
| 2138 | + | ||||
| 2139 | + | // UnmarshalParkedRouteRequest decodes one strict retained-route operation. | |||
| 2140 | + | func UnmarshalParkedRouteRequest(b []byte) (ParkedRouteRequest, error) { | |||
| 2141 | + | r := payloadReader{b: b} | |||
| 2142 | + | requestID, err := r.getUint64() | |||
| 2143 | + | if err != nil { | |||
| 2144 | + | return ParkedRouteRequest{}, ErrInvalidNavigation | |||
| 2145 | + | } | |||
| 2146 | + | lease, err := r.getBytes(len(ParkedRouteLeaseID{})) | |||
| 2147 | + | if err != nil { | |||
| 2148 | + | return ParkedRouteRequest{}, ErrInvalidNavigation | |||
| 2149 | + | } | |||
| 2150 | + | action, err := r.getUint8() | |||
| 2151 | + | if err != nil { | |||
| 2152 | + | return ParkedRouteRequest{}, ErrInvalidNavigation | |||
| 2153 | + | } | |||
| 2154 | + | target, policy, err := unmarshalRemoteTargetSection(&r) | |||
| 2155 | + | if err != nil || policy != EnvironmentPolicyDaemonOwned || r.done() != nil { | |||
| 2156 | + | return ParkedRouteRequest{}, ErrInvalidNavigation | |||
| 2157 | + | } | |||
| 2158 | + | request := ParkedRouteRequest{RequestID: requestID, Action: ParkedRouteAction(action), Target: target} | |||
| 2159 | + | copy(request.LeaseID[:], lease) | |||
| 2160 | + | if ValidateParkedRouteRequest(request) != nil { | |||
| 2161 | + | return ParkedRouteRequest{}, ErrInvalidNavigation | |||
| 2162 | + | } | |||
| 2163 | + | return request, nil | |||
| 2164 | + | } | |||
| 2165 | + | ||||
| 2166 | + | // MarshalParkedRouteResponse encodes one correlated retained-route outcome. | |||
| 2167 | + | func MarshalParkedRouteResponse(response ParkedRouteResponse) []byte { | |||
| 2168 | + | if response.RequestID == 0 || response.Status < ParkedRouteReady || response.Status > ParkedRouteStaleTarget { | |||
| 2169 | + | return nil | |||
| 2170 | + | } | |||
| 2171 | + | w := payloadWriter{} | |||
| 2172 | + | w.putUint64(response.RequestID) | |||
| 2173 | + | w.putUint8(uint8(response.Status)) | |||
| 2174 | + | return w.b | |||
| 2175 | + | } | |||
| 2176 | + | ||||
| 2177 | + | // UnmarshalParkedRouteResponse decodes one strict retained-route outcome. | |||
| 2178 | + | func UnmarshalParkedRouteResponse(b []byte) (ParkedRouteResponse, error) { | |||
| 2179 | + | r := payloadReader{b: b} | |||
| 2180 | + | requestID, err := r.getUint64() | |||
| 2181 | + | if err != nil { | |||
| 2182 | + | return ParkedRouteResponse{}, ErrInvalidNavigation | |||
| 2183 | + | } | |||
| 2184 | + | status, err := r.getUint8() | |||
| 2185 | + | if err != nil || r.done() != nil { | |||
| 2186 | + | return ParkedRouteResponse{}, ErrInvalidNavigation | |||
| 2086 | } | 2187 | } | ||
| 2087 | - | action := NavigationAction(value) | 2188 | + | response := ParkedRouteResponse{RequestID: requestID, Status: ParkedRouteStatus(status)} |
| 2088 | - | if action != NavigationOpenHomePicker && action != NavigationBack { | 2189 | + | if MarshalParkedRouteResponse(response) == nil { |
| 2089 | - | return 0, ErrInvalidNavigation | 2190 | + | return ParkedRouteResponse{}, ErrInvalidNavigation |
| 2090 | } | 2191 | } | ||
| 2091 | - | return action, nil | 2192 | + | return response, nil |
| 2092 | } | 2193 | } | ||
M internal/ports/wire_final_test.go +3 -3
| @@ -11,8 +11,8 @@ import ( | |||
| 11 | 11 | ) | |
| 12 | 12 | ||
| 13 | 13 | func TestFinalProtocolVersionAndNoV21Hello(t *testing.T) { | |
| 14 | - | if ProtocolVersion != 33 { | |
| 15 | - | t.Fatalf("ProtocolVersion = %d, want 33", ProtocolVersion) | |
| 14 | + | if ProtocolVersion != 34 { | |
| 15 | + | t.Fatalf("ProtocolVersion = %d, want 34", ProtocolVersion) | |
| 16 | 16 | } | |
| 17 | 17 | payload := MarshalHello(Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}}) | |
| 18 | 18 | if len(payload) < 2 { | |
| @@ -26,7 +26,7 @@ func TestFinalProtocolVersionAndNoV21Hello(t *testing.T) { | |||
| 26 | 26 | ||
| 27 | 27 | func TestFinalHelloGoldenStrict(t *testing.T) { | |
| 28 | 28 | msg := Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}} | |
| 29 | - | want := append([]byte{0, 33, 2}, make([]byte, 16+8)...) | |
| 29 | + | want := append([]byte{0, 34, 2}, make([]byte, 16+8)...) | |
| 30 | 30 | want = append(want, 0, 0, 0, 80, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) | |
| 31 | 31 | got := MarshalHello(msg) | |
| 32 | 32 | if !bytes.Equal(got, want) { | |
| @@ -11,8 +11,8 @@ import ( | |||||
| 11 | ) | 11 | ) | ||
| 12 | 12 | ||||
| 13 | func TestFinalProtocolVersionAndNoV21Hello(t *testing.T) { | 13 | func TestFinalProtocolVersionAndNoV21Hello(t *testing.T) { | ||
| 14 | - | if ProtocolVersion != 33 { | 14 | + | if ProtocolVersion != 34 { |
| 15 | - | t.Fatalf("ProtocolVersion = %d, want 33", ProtocolVersion) | 15 | + | t.Fatalf("ProtocolVersion = %d, want 34", ProtocolVersion) |
| 16 | } | 16 | } | ||
| 17 | payload := MarshalHello(Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}}) | 17 | payload := MarshalHello(Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}}) | ||
| 18 | if len(payload) < 2 { | 18 | if len(payload) < 2 { | ||
| @@ -26,7 +26,7 @@ func TestFinalProtocolVersionAndNoV21Hello(t *testing.T) { | |||||
| 26 | 26 | ||||
| 27 | func TestFinalHelloGoldenStrict(t *testing.T) { | 27 | func TestFinalHelloGoldenStrict(t *testing.T) { | ||
| 28 | msg := Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}} | 28 | msg := Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}} | ||
| 29 | - | want := append([]byte{0, 33, 2}, make([]byte, 16+8)...) | 29 | + | want := append([]byte{0, 34, 2}, make([]byte, 16+8)...) |
| 30 | want = append(want, 0, 0, 0, 80, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) | 30 | want = append(want, 0, 0, 0, 80, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) | ||
| 31 | got := MarshalHello(msg) | 31 | got := MarshalHello(msg) | ||
| 32 | if !bytes.Equal(got, want) { | 32 | if !bytes.Equal(got, want) { | ||
M internal/ports/wire_test.go +6 -6
| @@ -131,7 +131,7 @@ func TestHelloEnvironmentCodec(t *testing.T) { | |||
| 131 | 131 | } | |
| 132 | 132 | got := MarshalHello(msg) | |
| 133 | 133 | want := []byte{ | |
| 134 | - | 0x00, 0x21, 0x00, // version, intent, first client ID byte | |
| 134 | + | 0x00, 0x22, 0x00, // version, intent, first client ID byte | |
| 135 | 135 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 136 | 136 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // client ID | |
| 137 | 137 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // resume token | |
| @@ -361,7 +361,7 @@ func TestThemeGenerationClearedWireGoldenPreservesProtocolVersion(t *testing.T) | |||
| 361 | 361 | } | |
| 362 | 362 | want := append([]byte{0x0f, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00, 0x00}, make([]byte, 48)...) | |
| 363 | 363 | require.Equal(t, want, MarshalTheme(cleared)) | |
| 364 | - | require.Equal(t, uint16(33), ProtocolVersion) | |
| 364 | + | require.Equal(t, uint16(34), ProtocolVersion) | |
| 365 | 365 | } | |
| 366 | 366 | ||
| 367 | 367 | func TestResizeGoldenAndRoundTrip(t *testing.T) { | |
| @@ -522,7 +522,7 @@ func TestCommandRequestGoldenAndRoundTrip(t *testing.T) { | |||
| 522 | 522 | name: "minimal", | |
| 523 | 523 | msg: CommandRequest{Version: ProtocolVersion, Slug: "split-right"}, | |
| 524 | 524 | want: []byte{ | |
| 525 | - | 0x00, 0x21, | |
| 525 | + | 0x00, 0x22, | |
| 526 | 526 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // request ID | |
| 527 | 527 | 0x00, // attached | |
| 528 | 528 | 0x00, // self | |
| @@ -545,7 +545,7 @@ func TestCommandRequestGoldenAndRoundTrip(t *testing.T) { | |||
| 545 | 545 | JSON: true, | |
| 546 | 546 | }, | |
| 547 | 547 | want: []byte{ | |
| 548 | - | 0x00, 0x21, | |
| 548 | + | 0x00, 0x22, | |
| 549 | 549 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // request ID | |
| 550 | 550 | 0x00, // attached | |
| 551 | 551 | 0x01, // self | |
| @@ -974,7 +974,7 @@ func TestCommandCorrelationGoldenAndStrict(t *testing.T) { | |||
| 974 | 974 | name: "attached", | |
| 975 | 975 | msg: CommandRequest{Version: ProtocolVersion, RequestID: 0x0102030405060708, Attached: true, Self: true, Slug: "split-right", Args: []string{"--vertical"}}, | |
| 976 | 976 | want: []byte{ | |
| 977 | - | 0x00, 0x21, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x01, | |
| 977 | + | 0x00, 0x22, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x01, | |
| 978 | 978 | 0x00, 0x0b, 's', 'p', 'l', 'i', 't', '-', 'r', 'i', 'g', 'h', 't', | |
| 979 | 979 | 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, '-', '-', 'v', 'e', 'r', 't', 'i', 'c', 'a', 'l', | |
| 980 | 980 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| @@ -984,7 +984,7 @@ func TestCommandCorrelationGoldenAndStrict(t *testing.T) { | |||
| 984 | 984 | name: "control", | |
| 985 | 985 | msg: CommandRequest{Version: ProtocolVersion, Slug: "ls"}, | |
| 986 | 986 | want: []byte{ | |
| 987 | - | 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 987 | + | 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 988 | 988 | 0x00, 0x02, 'l', 's', 0x00, 0x00, | |
| 989 | 989 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 990 | 990 | }, | |
| @@ -131,7 +131,7 @@ func TestHelloEnvironmentCodec(t *testing.T) { | |||||
| 131 | } | 131 | } | ||
| 132 | got := MarshalHello(msg) | 132 | got := MarshalHello(msg) | ||
| 133 | want := []byte{ | 133 | want := []byte{ | ||
| 134 | - | 0x00, 0x21, 0x00, // version, intent, first client ID byte | 134 | + | 0x00, 0x22, 0x00, // version, intent, first client ID byte |
| 135 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | 135 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
| 136 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // client ID | 136 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // client ID | ||
| 137 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // resume token | 137 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // resume token | ||
| @@ -361,7 +361,7 @@ func TestThemeGenerationClearedWireGoldenPreservesProtocolVersion(t *testing.T) | |||||
| 361 | } | 361 | } | ||
| 362 | want := append([]byte{0x0f, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00, 0x00}, make([]byte, 48)...) | 362 | want := append([]byte{0x0f, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00, 0x00}, make([]byte, 48)...) | ||
| 363 | require.Equal(t, want, MarshalTheme(cleared)) | 363 | require.Equal(t, want, MarshalTheme(cleared)) | ||
| 364 | - | require.Equal(t, uint16(33), ProtocolVersion) | 364 | + | require.Equal(t, uint16(34), ProtocolVersion) |
| 365 | } | 365 | } | ||
| 366 | 366 | ||||
| 367 | func TestResizeGoldenAndRoundTrip(t *testing.T) { | 367 | func TestResizeGoldenAndRoundTrip(t *testing.T) { | ||
| @@ -522,7 +522,7 @@ func TestCommandRequestGoldenAndRoundTrip(t *testing.T) { | |||||
| 522 | name: "minimal", | 522 | name: "minimal", | ||
| 523 | msg: CommandRequest{Version: ProtocolVersion, Slug: "split-right"}, | 523 | msg: CommandRequest{Version: ProtocolVersion, Slug: "split-right"}, | ||
| 524 | want: []byte{ | 524 | want: []byte{ | ||
| 525 | - | 0x00, 0x21, | 525 | + | 0x00, 0x22, |
| 526 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // request ID | 526 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // request ID | ||
| 527 | 0x00, // attached | 527 | 0x00, // attached | ||
| 528 | 0x00, // self | 528 | 0x00, // self | ||
| @@ -545,7 +545,7 @@ func TestCommandRequestGoldenAndRoundTrip(t *testing.T) { | |||||
| 545 | JSON: true, | 545 | JSON: true, | ||
| 546 | }, | 546 | }, | ||
| 547 | want: []byte{ | 547 | want: []byte{ | ||
| 548 | - | 0x00, 0x21, | 548 | + | 0x00, 0x22, |
| 549 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // request ID | 549 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // request ID | ||
| 550 | 0x00, // attached | 550 | 0x00, // attached | ||
| 551 | 0x01, // self | 551 | 0x01, // self | ||
| @@ -974,7 +974,7 @@ func TestCommandCorrelationGoldenAndStrict(t *testing.T) { | |||||
| 974 | name: "attached", | 974 | name: "attached", | ||
| 975 | msg: CommandRequest{Version: ProtocolVersion, RequestID: 0x0102030405060708, Attached: true, Self: true, Slug: "split-right", Args: []string{"--vertical"}}, | 975 | msg: CommandRequest{Version: ProtocolVersion, RequestID: 0x0102030405060708, Attached: true, Self: true, Slug: "split-right", Args: []string{"--vertical"}}, | ||
| 976 | want: []byte{ | 976 | want: []byte{ | ||
| 977 | - | 0x00, 0x21, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x01, | 977 | + | 0x00, 0x22, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x01, |
| 978 | 0x00, 0x0b, 's', 'p', 'l', 'i', 't', '-', 'r', 'i', 'g', 'h', 't', | 978 | 0x00, 0x0b, 's', 'p', 'l', 'i', 't', '-', 'r', 'i', 'g', 'h', 't', | ||
| 979 | 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, '-', '-', 'v', 'e', 'r', 't', 'i', 'c', 'a', 'l', | 979 | 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, '-', '-', 'v', 'e', 'r', 't', 'i', 'c', 'a', 'l', | ||
| 980 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | 980 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
| @@ -984,7 +984,7 @@ func TestCommandCorrelationGoldenAndStrict(t *testing.T) { | |||||
| 984 | name: "control", | 984 | name: "control", | ||
| 985 | msg: CommandRequest{Version: ProtocolVersion, Slug: "ls"}, | 985 | msg: CommandRequest{Version: ProtocolVersion, Slug: "ls"}, | ||
| 986 | want: []byte{ | 986 | want: []byte{ | ||
| 987 | - | 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | 987 | + | 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 988 | 0x00, 0x02, 'l', 's', 0x00, 0x00, | 988 | 0x00, 0x02, 'l', 's', 0x00, 0x00, | ||
| 989 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | 989 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
| 990 | }, | 990 | }, | ||