detached f791584

refactor(protocol): isolate binary wire encoding (#228)

* refactor(protocol): isolate binary wire encoding

* docs(architecture): record wire module ownership

* docs(architecture): correct wire layer map
commit
f791584fd55cd239e4484d7ada11e6b587ec64ec
author
bnema · · 2026-08-28 13:03 UTC
committer
GitHub · · 2026-08-28 13:03 UTC
parent
b21386d
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
137 files · +1868 -1790
M AGENTS.md +4 -2
@@ -37,7 +37,9 @@ Hexagonal boundaries are enforced by `boundary_test.go`.
37 Layer map:37 Layer map:
38 38
39 - `main.go` → `internal/app`: CLI parsing, wiring, daemon startup, hidden subcommands.39 - `main.go` → `internal/app`: CLI parsing, wiring, daemon startup, hidden subcommands.
40-- `internal/ports`: interfaces and wire protocol.40+- `internal/ports`: application and raw carriage interfaces.
41+- `internal/protocol`: typed transport-neutral messages and negotiated session policy.
42+- `internal/protocol/wire`: message IDs, frames, strict binary codecs, compression, and encoded bounds.
41 - `internal/usecase/daemon`: sessions, tabs, panes, VT screens, renderer shadows, daemon features.43 - `internal/usecase/daemon`: sessions, tabs, panes, VT screens, renderer shadows, daemon features.
42 - `internal/usecase/client`: raw-mode thin client; writes output bytes verbatim and interprets nothing.44 - `internal/usecase/client`: raw-mode thin client; writes output bytes verbatim and interprets nothing.
43 - `internal/adapters`: IPC, SSH stdio, PTY, terminal, and clock implementations.45 - `internal/adapters`: IPC, SSH stdio, PTY, terminal, and clock implementations.
@@ -61,7 +63,7 @@ Before touching daemon teardown paths, read the lock-ordering notes at the top o
61 63
62 ## Wire protocol64 ## Wire protocol
63 65
64-Semantic presentation, route, and session-control values live in `internal/protocol`. Frame IDs and codecs live in `internal/ports/frame.go`, `internal/ports/wire.go`, `internal/ports/routes_wire.go`, and `internal/ports/wire_command.go`. Connection framing lives in `internal/adapters/ipc/transport.go`.66+Typed messages and negotiated version live in `internal/protocol`. Message IDs, frames, strict codecs, compression, and encoded bounds live in `internal/protocol/wire`; raw carriage interfaces remain in `internal/ports`. Connection framing lives in concrete carriage adapters such as `internal/adapters/ipc`.
65 67
66 - IPC frames on a connection are 4-byte big-endian length, 1 type byte, then payload.68 - IPC frames on a connection are 4-byte big-endian length, 1 type byte, then payload.
67 - Client message types occupy `1–13`, `15`, `32–33`, and `35` (`MsgParkedRouteRequest`); server types occupy `16–23`, `25–31`, `34`, and `36` (`MsgParkedRouteResponse`). Types `14` and `24` remain reserved.69 - Client message types occupy `1–13`, `15`, `32–33`, and `35` (`MsgParkedRouteRequest`); server types occupy `16–23`, `25–31`, `34`, and `36` (`MsgParkedRouteResponse`). Types `14` and `24` remain reserved.
M internal/adapters/dgram/control_plane_test.go +5 -4
@@ -10,6 +10,7 @@ import (
10 "time"10 "time"
11 11
12 "github.com/bnema/vev/internal/ports"12 "github.com/bnema/vev/internal/ports"
13+ "github.com/bnema/vev/internal/protocol/wire"
13 pdgram "github.com/bnema/vev/pkg/dgram"14 pdgram "github.com/bnema/vev/pkg/dgram"
14 )15 )
15 16
@@ -81,7 +82,7 @@ func TestReadLoopDoesNotWriteACKSynchronously(t *testing.T) {
81 a.diagnosticCh = make(chan Diagnostic, 1)82 a.diagnosticCh = make(chan Diagnostic, 1)
82 a.mu.Unlock()83 a.mu.Unlock()
83 84
84- if err := b.Send(ports.Frame{Type: ports.MsgInput, Payload: []byte("first")}); err != nil {85+ if err := b.Send(wire.Frame{Type: wire.MsgInput, Payload: []byte("first")}); err != nil {
85 t.Fatal(err)86 t.Fatal(err)
86 }87 }
87 awaitSignal(t, contacts, "first authenticated record")88 awaitSignal(t, contacts, "first authenticated record")
@@ -92,7 +93,7 @@ func TestReadLoopDoesNotWriteACKSynchronously(t *testing.T) {
92 awaitSignal(t, ackStarted, "blocked ACK")93 awaitSignal(t, ackStarted, "blocked ACK")
93 94
94 clk.advance(time.Second)95 clk.advance(time.Second)
95- if err := b.Send(ports.Frame{Type: ports.MsgInput, Payload: []byte("second")}); err != nil {96+ if err := b.Send(wire.Frame{Type: wire.MsgInput, Payload: []byte("second")}); err != nil {
96 t.Fatal(err)97 t.Fatal(err)
97 }98 }
98 awaitSignal(t, contacts, "second authenticated record while ACK write is blocked")99 awaitSignal(t, contacts, "second authenticated record while ACK write is blocked")
@@ -471,7 +472,7 @@ func TestHeartbeatContinuesDuringRetransmitStorm(t *testing.T) {
471 a.mu.Lock()472 a.mu.Lock()
472 for seq := uint64(1); seq <= 64; seq++ {473 for seq := uint64(1); seq <= 64; seq++ {
473 a.pending[seq] = &pending{474 a.pending[seq] = &pending{
474- frame: ports.Frame{Type: ports.MsgInput, Payload: make([]byte, 24*a.mtu)},475+ frame: wire.Frame{Type: wire.MsgInput, Payload: make([]byte, 24*a.mtu)},
475 enqueued: now.Add(-time.Second), first: now.Add(-time.Second), last: now.Add(-time.Second),476 enqueued: now.Add(-time.Second), first: now.Add(-time.Second), last: now.Add(-time.Second),
476 }477 }
477 }478 }
@@ -515,7 +516,7 @@ func TestHealthChecksContinueDuringRetransmitStorm(t *testing.T) {
515 now := clk.Now()516 now := clk.Now()
516 a.mu.Lock()517 a.mu.Lock()
517 a.pending[1] = &pending{518 a.pending[1] = &pending{
518- frame: ports.Frame{Type: ports.MsgInput, Payload: make([]byte, 24*a.mtu)},519+ frame: wire.Frame{Type: wire.MsgInput, Payload: make([]byte, 24*a.mtu)},
519 enqueued: now.Add(-time.Second), first: now.Add(-time.Second), last: now.Add(-time.Second),520 enqueued: now.Add(-time.Second), first: now.Add(-time.Second), last: now.Add(-time.Second),
520 }521 }
521 a.mu.Unlock()522 a.mu.Unlock()
M internal/adapters/dgram/diagnostics_test.go +3 -2
@@ -6,6 +6,7 @@ import (
6 "time"6 "time"
7 7
8 "github.com/bnema/vev/internal/ports"8 "github.com/bnema/vev/internal/ports"
9+ "github.com/bnema/vev/internal/protocol/wire"
9 )10 )
10 11
11 func TestDiagnosticSnapshotSeparatesProgressAges(t *testing.T) {12 func TestDiagnosticSnapshotSeparatesProgressAges(t *testing.T) {
@@ -20,8 +21,8 @@ func TestDiagnosticSnapshotSeparatesProgressAges(t *testing.T) {
20 retransmits: 64,21 retransmits: 64,
21 reassemblyInflight: 2,22 reassemblyInflight: 2,
22 pending: map[uint64]*pending{23 pending: map[uint64]*pending{
23- 1: {frame: ports.Frame{Payload: make([]byte, 7)}},24+ 1: {frame: wire.Frame{Payload: make([]byte, 7)}},
24- 2: {frame: ports.Frame{Payload: make([]byte, 11)}},25+ 2: {frame: wire.Frame{Payload: make([]byte, 11)}},
25 },26 },
26 diagnosticCh: make(chan Diagnostic, diagnosticBufferSize),27 diagnosticCh: make(chan Diagnostic, diagnosticBufferSize),
27 done: make(chan struct{}),28 done: make(chan struct{}),
M internal/adapters/dgram/network_sim_test.go +7 -6
@@ -12,6 +12,7 @@ import (
12 "github.com/bnema/vev/internal/domain"12 "github.com/bnema/vev/internal/domain"
13 "github.com/bnema/vev/internal/ports"13 "github.com/bnema/vev/internal/ports"
14 "github.com/bnema/vev/internal/protocol"14 "github.com/bnema/vev/internal/protocol"
15+ "github.com/bnema/vev/internal/protocol/wire"
15 pdgram "github.com/bnema/vev/pkg/dgram"16 pdgram "github.com/bnema/vev/pkg/dgram"
16 )17 )
17 18
@@ -454,7 +455,7 @@ func TestTransportFloodClassification(t *testing.T) {
454 }455 }
455 b.mu.Unlock()456 b.mu.Unlock()
456 primer := mustMarshalOutput(protocol.Output{Epoch: 1, Base: 0, New: 1, Size: domain.Size{Cols: 1, Rows: 1}, Full: true, Data: []byte("primer")})457 primer := mustMarshalOutput(protocol.Output{Epoch: 1, Base: 0, New: 1, Size: domain.Size{Cols: 1, Rows: 1}, Full: true, Data: []byte("primer")})
457- if err := a.SendAsync(ports.Frame{Type: ports.MsgOutput, Payload: primer}); err != nil {458+ if err := a.SendAsync(wire.Frame{Type: wire.MsgOutput, Payload: primer}); err != nil {
458 t.Fatal(err)459 t.Fatal(err)
459 }460 }
460 awaitSignal(t, primerWritten, "primer write")461 awaitSignal(t, primerWritten, "primer write")
@@ -537,7 +538,7 @@ func TestTransportFloodClassification(t *testing.T) {
537 Size: domain.Size{Cols: 1, Rows: 1},538 Size: domain.Size{Cols: 1, Rows: 1},
538 Data: bytes.Repeat([]byte{byte(state + 1)}, outputDataBytes),539 Data: bytes.Repeat([]byte{byte(state + 1)}, outputDataBytes),
539 })540 })
540- if err := a.SendAsync(ports.Frame{Type: ports.MsgOutput, Payload: payload}); err != nil {541+ if err := a.SendAsync(wire.Frame{Type: wire.MsgOutput, Payload: payload}); err != nil {
541 t.Fatal(err)542 t.Fatal(err)
542 }543 }
543 }544 }
@@ -631,7 +632,7 @@ func TestTransportFloodClassification(t *testing.T) {
631 for {632 for {
632 select {633 select {
633 case frame := <-b.in:634 case frame := <-b.in:
634- output, err := ports.UnmarshalOutput(frame.Payload)635+ output, err := wire.UnmarshalOutput(frame.Payload)
635 if err != nil {636 if err != nil {
636 t.Fatal(err)637 t.Fatal(err)
637 }638 }
@@ -795,7 +796,7 @@ func TestTransportFloodClassification(t *testing.T) {
795 // A fresh record queues behind the sole data sender while control traffic796 // A fresh record queues behind the sole data sender while control traffic
796 // remains independent of congestion tokens and the blocked data write.797 // remains independent of congestion tokens and the blocked data write.
797 go func() {798 go func() {
798- freshReturned <- a.Send(ports.Frame{Type: ports.MsgOutput, Payload: floodOutputPayload(floodRecordCount, 128)})799+ freshReturned <- a.Send(wire.Frame{Type: wire.MsgOutput, Payload: floodOutputPayload(floodRecordCount, 128)})
799 }()800 }()
800 select {801 select {
801 case err := <-freshReturned:802 case err := <-freshReturned:
@@ -869,7 +870,7 @@ func TestFloodOutputPayloadIsIncrementalStateBearingOutput(t *testing.T) {
869 const mtu = 128870 const mtu = 128
870 for state := range floodRecordCount {871 for state := range floodRecordCount {
871 payload := floodOutputPayload(state, mtu)872 payload := floodOutputPayload(state, mtu)
872- output, err := ports.UnmarshalOutput(payload)873+ output, err := wire.UnmarshalOutput(payload)
873 if err != nil {874 if err != nil {
874 t.Fatalf("state %d: %v", state, err)875 t.Fatalf("state %d: %v", state, err)
875 }876 }
@@ -889,7 +890,7 @@ func TestFloodOutputPayloadIsIncrementalStateBearingOutput(t *testing.T) {
889 func sendFloodOutputs(t *testing.T, sender *Transport, count, mtu int) {890 func sendFloodOutputs(t *testing.T, sender *Transport, count, mtu int) {
890 t.Helper()891 t.Helper()
891 for state := range count {892 for state := range count {
892- if err := sender.SendAsync(ports.Frame{Type: ports.MsgOutput, Payload: floodOutputPayload(state, mtu)}); err != nil {893+ if err := sender.SendAsync(wire.Frame{Type: wire.MsgOutput, Payload: floodOutputPayload(state, mtu)}); err != nil {
893 t.Fatal(err)894 t.Fatal(err)
894 }895 }
895 }896 }
M internal/adapters/dgram/output_pacer.go +4 -4
@@ -1,11 +1,11 @@
1 package dgram1 package dgram
2 2
3-import "github.com/bnema/vev/internal/ports"3+import "github.com/bnema/vev/internal/protocol/wire"
4 4
5 type dataSendJob struct {5 type dataSendJob struct {
6 seq uint646 seq uint64
7 reliable bool7 reliable bool
8- frame ports.Frame8+ frame wire.Frame
9 done chan error9 done chan error
10 }10 }
11 11
@@ -52,8 +52,8 @@ func (t *Transport) notifyOutputPacerLocked() {
52 t.outputWake = make(chan struct{})52 t.outputWake = make(chan struct{})
53 }53 }
54 54
55-func shouldPaceOutput(f ports.Frame) bool {55+func shouldPaceOutput(f wire.Frame) bool {
56- return f.Type == ports.MsgOutput56+ return f.Type == wire.MsgOutput
57 }57 }
58 58
59 func (t *Transport) markPendingSent(seq uint64, reliable bool) {59 func (t *Transport) markPendingSent(seq uint64, reliable bool) {
M internal/adapters/dgram/proxy.go +6 -5
@@ -8,6 +8,7 @@ import (
8 "time"8 "time"
9 9
10 "github.com/bnema/vev/internal/ports"10 "github.com/bnema/vev/internal/ports"
11+ "github.com/bnema/vev/internal/protocol/wire"
11 )12 )
12 13
13 const (14 const (
@@ -50,7 +51,7 @@ type proxyCopyDirection struct {
50 dst ports.Transport51 dst ports.Transport
51 recvKind proxyCopyErrKind52 recvKind proxyCopyErrKind
52 sendKind proxyCopyErrKind53 sendKind proxyCopyErrKind
53- transform func(ports.Frame) ports.Frame54+ transform func(wire.Frame) wire.Frame
54 retryRecoverable bool55 retryRecoverable bool
55 }56 }
56 57
@@ -211,15 +212,15 @@ func recoverableClientLink(t ports.Transport, err error) bool {
211 // clampDatagramHelloOutputWindow enforces the datagram-safe state window at212 // clampDatagramHelloOutputWindow enforces the datagram-safe state window at
212 // the adapter boundary. Invalid Hello payloads pass through unchanged so the213 // the adapter boundary. Invalid Hello payloads pass through unchanged so the
213 // daemon remains the single authority for strict decoding and version checks.214 // daemon remains the single authority for strict decoding and version checks.
214-func clampDatagramHelloOutputWindow(f ports.Frame) ports.Frame {215+func clampDatagramHelloOutputWindow(f wire.Frame) wire.Frame {
215- if f.Type != ports.MsgHello {216+ if f.Type != wire.MsgHello {
216 return f217 return f
217 }218 }
218- hello, err := ports.UnmarshalHello(f.Payload)219+ hello, err := wire.UnmarshalHello(f.Payload)
219 if err != nil {220 if err != nil {
220 return f221 return f
221 }222 }
222 hello.MaxOutputInFlight = 1223 hello.MaxOutputInFlight = 1
223- f.Payload = ports.MarshalHello(hello)224+ f.Payload = wire.MarshalHello(hello)
224 return f225 return f
225 }226 }
M internal/adapters/dgram/proxy_test.go +28 -27
@@ -13,11 +13,12 @@ import (
13 "github.com/bnema/vev/internal/domain"13 "github.com/bnema/vev/internal/domain"
14 "github.com/bnema/vev/internal/ports"14 "github.com/bnema/vev/internal/ports"
15 "github.com/bnema/vev/internal/protocol"15 "github.com/bnema/vev/internal/protocol"
16+ "github.com/bnema/vev/internal/protocol/wire"
16 )17 )
17 18
18 type fakeTransport struct {19 type fakeTransport struct {
19 recv chan recvResult20 recv chan recvResult
20- sent chan ports.Frame21+ sent chan wire.Frame
21 sendErr chan error22 sendErr chan error
22 linkState ports.LinkState23 linkState ports.LinkState
23 linkEvents chan ports.LinkEvent24 linkEvents chan ports.LinkEvent
@@ -26,14 +27,14 @@ type fakeTransport struct {
26 }27 }
27 28
28 type recvResult struct {29 type recvResult struct {
29- frame ports.Frame30+ frame wire.Frame
30 err error31 err error
31 }32 }
32 33
33 func newFakeTransport() *fakeTransport {34 func newFakeTransport() *fakeTransport {
34- return &fakeTransport{recv: make(chan recvResult, 4), sent: make(chan ports.Frame, 4), sendErr: make(chan error, 4), linkEvents: make(chan ports.LinkEvent, 4), linkState: ports.LinkStateConnected, closed: make(chan struct{})}35+ return &fakeTransport{recv: make(chan recvResult, 4), sent: make(chan wire.Frame, 4), sendErr: make(chan error, 4), linkEvents: make(chan ports.LinkEvent, 4), linkState: ports.LinkStateConnected, closed: make(chan struct{})}
35 }36 }
36-func (f *fakeTransport) Send(fr ports.Frame) error {37+func (f *fakeTransport) Send(fr wire.Frame) error {
37 select {38 select {
38 case err := <-f.sendErr:39 case err := <-f.sendErr:
39 return err40 return err
@@ -42,12 +43,12 @@ func (f *fakeTransport) Send(fr ports.Frame) error {
42 f.sent <- fr43 f.sent <- fr
43 return nil44 return nil
44 }45 }
45-func (f *fakeTransport) Recv() (ports.Frame, error) {46+func (f *fakeTransport) Recv() (wire.Frame, error) {
46 select {47 select {
47 case r := <-f.recv:48 case r := <-f.recv:
48 return r.frame, r.err49 return r.frame, r.err
49 case <-f.closed:50 case <-f.closed:
50- return ports.Frame{}, io.EOF51+ return wire.Frame{}, io.EOF
51 }52 }
52 }53 }
53 func (f *fakeTransport) Close() error {54 func (f *fakeTransport) Close() error {
@@ -62,10 +63,10 @@ func TestProxyRuntimeCopiesFramesAndDaemonEOFIsTerminal(t *testing.T) {
62 daemon := newFakeTransport()63 daemon := newFakeTransport()
63 errCh := make(chan error, 1)64 errCh := make(chan error, 1)
64 go func() { errCh <- ProxyRuntime{Client: client, Daemon: daemon, IdleTTL: time.Hour}.Run(t.Context()) }()65 go func() { errCh <- ProxyRuntime{Client: client, Daemon: daemon, IdleTTL: time.Hour}.Run(t.Context()) }()
65- client.recv <- recvResult{frame: ports.Frame{Type: ports.MsgInput, Payload: []byte("x")}}66+ client.recv <- recvResult{frame: wire.Frame{Type: wire.MsgInput, Payload: []byte("x")}}
66 select {67 select {
67 case got := <-daemon.sent:68 case got := <-daemon.sent:
68- if got.Type != ports.MsgInput {69+ if got.Type != wire.MsgInput {
69 t.Fatalf("daemon got type %v", got.Type)70 t.Fatalf("daemon got type %v", got.Type)
70 }71 }
71 case <-time.After(time.Second):72 case <-time.After(time.Second):
@@ -90,11 +91,11 @@ func TestProxyRuntimeClampsDatagramHelloOutputWindowAndPreservesControl(t *testi
90 errCh := make(chan error, 1)91 errCh := make(chan error, 1)
91 go func() { errCh <- ProxyRuntime{Client: client, Daemon: daemon, IdleTTL: time.Hour}.Run(t.Context()) }()92 go func() { errCh <- ProxyRuntime{Client: client, Daemon: daemon, IdleTTL: time.Hour}.Run(t.Context()) }()
92 hello := protocol.Hello{Version: protocol.Version, Intent: protocol.IntentAttach, Name: "work", Size: domain.Size{Cols: 80, Rows: 24}, MaxOutputInFlight: requested}93 hello := protocol.Hello{Version: protocol.Version, Intent: protocol.IntentAttach, Name: "work", Size: domain.Size{Cols: 80, Rows: 24}, MaxOutputInFlight: requested}
93- client.recv <- recvResult{frame: ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(hello)}}94+ client.recv <- recvResult{frame: wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(hello)}}
94 95
95 select {96 select {
96 case frame := <-daemon.sent:97 case frame := <-daemon.sent:
97- got, err := ports.UnmarshalHello(frame.Payload)98+ got, err := wire.UnmarshalHello(frame.Payload)
98 if err != nil {99 if err != nil {
99 t.Fatal(err)100 t.Fatal(err)
100 }101 }
@@ -105,11 +106,11 @@ func TestProxyRuntimeClampsDatagramHelloOutputWindowAndPreservesControl(t *testi
105 t.Fatal("timeout waiting for proxied Hello")106 t.Fatal("timeout waiting for proxied Hello")
106 }107 }
107 108
108- input := ports.Frame{Type: ports.MsgInput, Payload: []byte("still flowing")}109+ input := wire.Frame{Type: wire.MsgInput, Payload: []byte("still flowing")}
109- ack := ports.Frame{Type: ports.MsgAck, Payload: mustMarshalAck(protocol.Ack{Epoch: 1, State: 7})}110+ ack := wire.Frame{Type: wire.MsgAck, Payload: mustMarshalAck(protocol.Ack{Epoch: 1, State: 7})}
110 client.recv <- recvResult{frame: input}111 client.recv <- recvResult{frame: input}
111 client.recv <- recvResult{frame: ack}112 client.recv <- recvResult{frame: ack}
112- for _, want := range []ports.Frame{input, ack} {113+ for _, want := range []wire.Frame{input, ack} {
113 select {114 select {
114 case got := <-daemon.sent:115 case got := <-daemon.sent:
115 if !reflect.DeepEqual(got, want) {116 if !reflect.DeepEqual(got, want) {
@@ -149,10 +150,10 @@ func TestProxyRuntimeRetriesRecoverableClientSendWithoutDroppingFrame(t *testing
149 errCh <- ProxyRuntime{Client: client, Daemon: daemon, IdleTTL: time.Hour, RetryBackoff: time.Nanosecond}.Run(t.Context())150 errCh <- ProxyRuntime{Client: client, Daemon: daemon, IdleTTL: time.Hour, RetryBackoff: time.Nanosecond}.Run(t.Context())
150 }()151 }()
151 client.sendErr <- tt.err152 client.sendErr <- tt.err
152- daemon.recv <- recvResult{frame: ports.Frame{Type: ports.MsgOutput, Payload: []byte("preserved during outage")}}153+ daemon.recv <- recvResult{frame: wire.Frame{Type: wire.MsgOutput, Payload: []byte("preserved during outage")}}
153 select {154 select {
154 case got := <-client.sent:155 case got := <-client.sent:
155- if got.Type != ports.MsgOutput || string(got.Payload) != "preserved during outage" {156+ if got.Type != wire.MsgOutput || string(got.Payload) != "preserved during outage" {
156 t.Fatalf("client got %+v, want retried output", got)157 t.Fatalf("client got %+v, want retried output", got)
157 }158 }
158 case <-time.After(time.Second):159 case <-time.After(time.Second):
@@ -187,10 +188,10 @@ func TestProxyRuntimeSurvivesPendingFullAndPreservesOrder(t *testing.T) {
187 const frames = 4188 const frames = 4
188 for i := range frames {189 for i := range frames {
189 client.sendErr <- ErrPendingFull190 client.sendErr <- ErrPendingFull
190- daemon.recv <- recvResult{frame: ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(i)}}}191+ daemon.recv <- recvResult{frame: wire.Frame{Type: wire.MsgOutput, Payload: []byte{byte(i)}}}
191 select {192 select {
192 case got := <-client.sent:193 case got := <-client.sent:
193- if got.Type != ports.MsgOutput || len(got.Payload) != 1 || got.Payload[0] != byte(i) {194+ if got.Type != wire.MsgOutput || len(got.Payload) != 1 || got.Payload[0] != byte(i) {
194 t.Fatalf("frame %d delivered out of order or dropped: %+v", i, got)195 t.Fatalf("frame %d delivered out of order or dropped: %+v", i, got)
195 }196 }
196 case <-time.After(time.Second):197 case <-time.After(time.Second):
@@ -378,7 +379,7 @@ func TestProxyRuntimeClientSendDeadWaitsForIdleTTL(t *testing.T) {
378 }()379 }()
379 380
380 client.sendErr <- ErrLinkDead381 client.sendErr <- ErrLinkDead
381- daemon.recv <- recvResult{frame: ports.Frame{Type: ports.MsgOutput, Payload: []byte("client is gone")}}382+ daemon.recv <- recvResult{frame: wire.Frame{Type: wire.MsgOutput, Payload: []byte("client is gone")}}
382 waitForProxyManualTimers(t, clk, 1)383 waitForProxyManualTimers(t, clk, 1)
383 select {384 select {
384 case err := <-errCh:385 case err := <-errCh:
@@ -443,14 +444,14 @@ func newRetryingOfflineTransport() *retryingOfflineTransport {
443 return &retryingOfflineTransport{closed: make(chan struct{}), firstSend: make(chan struct{})}444 return &retryingOfflineTransport{closed: make(chan struct{}), firstSend: make(chan struct{})}
444 }445 }
445 446
446-func (t *retryingOfflineTransport) Send(ports.Frame) error {447+func (t *retryingOfflineTransport) Send(wire.Frame) error {
447 t.firstSendOnce.Do(func() { close(t.firstSend) })448 t.firstSendOnce.Do(func() { close(t.firstSend) })
448 return errors.New("offline")449 return errors.New("offline")
449 }450 }
450 451
451-func (t *retryingOfflineTransport) Recv() (ports.Frame, error) {452+func (t *retryingOfflineTransport) Recv() (wire.Frame, error) {
452 <-t.closed453 <-t.closed
453- return ports.Frame{}, io.EOF454+ return wire.Frame{}, io.EOF
454 }455 }
455 456
456 func (t *retryingOfflineTransport) Close() error {457 func (t *retryingOfflineTransport) Close() error {
@@ -474,7 +475,7 @@ func TestProxyCopierStopsRetryingWhenContextCanceled(t *testing.T) {
474 close(done)475 close(done)
475 }()476 }()
476 477
477- daemon.recv <- recvResult{frame: ports.Frame{Type: ports.MsgOutput, Payload: []byte("retry until cancel")}}478+ daemon.recv <- recvResult{frame: wire.Frame{Type: wire.MsgOutput, Payload: []byte("retry until cancel")}}
478 select {479 select {
479 case <-client.firstSend:480 case <-client.firstSend:
480 case <-time.After(time.Second):481 case <-time.After(time.Second):
@@ -490,23 +491,23 @@ func TestProxyCopierStopsRetryingWhenContextCanceled(t *testing.T) {
490 491
491 func TestClampDatagramHelloOutputWindowPreservesProtocolValidation(t *testing.T) {492 func TestClampDatagramHelloOutputWindowPreservesProtocolValidation(t *testing.T) {
492 t.Run("malformed payload remains malformed", func(t *testing.T) {493 t.Run("malformed payload remains malformed", func(t *testing.T) {
493- payload := append(ports.MarshalHello(protocol.Hello{Version: protocol.Version, Size: domain.Size{Cols: 80, Rows: 24}, MaxOutputInFlight: 8}), 0xff)494+ payload := append(wire.MarshalHello(protocol.Hello{Version: protocol.Version, Size: domain.Size{Cols: 80, Rows: 24}, MaxOutputInFlight: 8}), 0xff)
494- frame := ports.Frame{Type: ports.MsgHello, Payload: payload}495+ frame := wire.Frame{Type: wire.MsgHello, Payload: payload}
495 got := clampDatagramHelloOutputWindow(frame)496 got := clampDatagramHelloOutputWindow(frame)
496 if !reflect.DeepEqual(got, frame) {497 if !reflect.DeepEqual(got, frame) {
497 t.Fatalf("malformed Hello changed: got %#v, want %#v", got, frame)498 t.Fatalf("malformed Hello changed: got %#v, want %#v", got, frame)
498 }499 }
499- if _, err := ports.UnmarshalHello(got.Payload); err == nil {500+ if _, err := wire.UnmarshalHello(got.Payload); err == nil {
500 t.Fatal("malformed Hello became decodable")501 t.Fatal("malformed Hello became decodable")
501 }502 }
502 })503 })
503 504
504 t.Run("mismatched version remains mismatched", func(t *testing.T) {505 t.Run("mismatched version remains mismatched", func(t *testing.T) {
505 wantVersion := protocol.Version + 1506 wantVersion := protocol.Version + 1
506- frame := ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(protocol.Hello{507+ frame := wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(protocol.Hello{
507 Version: wantVersion, Size: domain.Size{Cols: 80, Rows: 24}, MaxOutputInFlight: 8,508 Version: wantVersion, Size: domain.Size{Cols: 80, Rows: 24}, MaxOutputInFlight: 8,
508 })}509 })}
509- got, err := ports.UnmarshalHello(clampDatagramHelloOutputWindow(frame).Payload)510+ got, err := wire.UnmarshalHello(clampDatagramHelloOutputWindow(frame).Payload)
510 if err != nil {511 if err != nil {
511 t.Fatal(err)512 t.Fatal(err)
512 }513 }
M internal/adapters/dgram/transport.go +5 -4
@@ -9,6 +9,7 @@ import (
9 "time"9 "time"
10 10
11 "github.com/bnema/vev/internal/ports"11 "github.com/bnema/vev/internal/ports"
12+ "github.com/bnema/vev/internal/protocol/wire"
12 pdgram "github.com/bnema/vev/pkg/dgram"13 pdgram "github.com/bnema/vev/pkg/dgram"
13 )14 )
14 15
@@ -221,7 +222,7 @@ type Transport struct {
221 recvMu sync.Mutex222 recvMu sync.Mutex
222 replay *pdgram.ReplayWindow223 replay *pdgram.ReplayWindow
223 reasm *pdgram.Reassembler224 reasm *pdgram.Reassembler
224- in chan ports.Frame225+ in chan wire.Frame
225 done chan struct{}226 done chan struct{}
226 227
227 // Connection replacement takes writeMu, controlConnMu, then mu. Data writes228 // Connection replacement takes writeMu, controlConnMu, then mu. Data writes
@@ -257,11 +258,11 @@ type Transport struct {
257 deliverMu sync.Mutex258 deliverMu sync.Mutex
258 deliverCond *sync.Cond259 deliverCond *sync.Cond
259 nextRecvSeq uint64260 nextRecvSeq uint64
260- recvBuf map[uint64]ports.Frame261+ recvBuf map[uint64]wire.Frame
261 }262 }
262 263
263 type pending struct {264 type pending struct {
264- frame ports.Frame265+ frame wire.Frame
265 enqueued time.Time266 enqueued time.Time
266 first time.Time267 first time.Time
267 last time.Time268 last time.Time
@@ -273,7 +274,7 @@ type pending struct {
273 274
274 type retransmitRecord struct {275 type retransmitRecord struct {
275 seq uint64276 seq uint64
276- f ports.Frame277+ f wire.Frame
277 }278 }
278 279
279 type writeDeadlineState struct {280 type writeDeadlineState struct {
M internal/adapters/dgram/transport_health_test.go +11 -10
@@ -10,6 +10,7 @@ import (
10 "time"10 "time"
11 11
12 "github.com/bnema/vev/internal/ports"12 "github.com/bnema/vev/internal/ports"
13+ "github.com/bnema/vev/internal/protocol/wire"
13 pdgram "github.com/bnema/vev/pkg/dgram"14 pdgram "github.com/bnema/vev/pkg/dgram"
14 )15 )
15 16
@@ -32,7 +33,7 @@ func TestPortHopPreservesPendingReliableMessages(t *testing.T) {
32 defer func() { _ = a.Close() }()33 defer func() { _ = a.Close() }()
33 defer func() { _ = b.Close() }()34 defer func() { _ = b.Close() }()
34 35
35- if err := a.Send(ports.Frame{Type: ports.MsgInput, Payload: []byte("survive")}); err != nil {36+ if err := a.Send(wire.Frame{Type: wire.MsgInput, Payload: []byte("survive")}); err != nil {
36 t.Fatal(err)37 t.Fatal(err)
37 }38 }
38 a.mu.Lock()39 a.mu.Lock()
@@ -44,7 +45,7 @@ func TestPortHopPreservesPendingReliableMessages(t *testing.T) {
44 }45 }
45 aPC.drop = nil46 aPC.drop = nil
46 got := recvWithin(t, b, time.Second)47 got := recvWithin(t, b, time.Second)
47- if got.Type != ports.MsgInput || string(got.Payload) != "survive" {48+ if got.Type != wire.MsgInput || string(got.Payload) != "survive" {
48 t.Fatalf("got=%+v, want pending reliable message", got)49 t.Fatalf("got=%+v, want pending reliable message", got)
49 }50 }
50 select {51 select {
@@ -151,8 +152,8 @@ func TestRecvUnblocksWhenPeerDeadAfter(t *testing.T) {
151 func TestPendingAttemptsResetWhenLinkRecovers(t *testing.T) {152 func TestPendingAttemptsResetWhenLinkRecovers(t *testing.T) {
152 tr, _ := newResendTestTransport(t, 10)153 tr, _ := newResendTestTransport(t, 10)
153 tr.linkState = ports.LinkStateDegraded154 tr.linkState = ports.LinkStateDegraded
154- tr.pending[1] = &pending{frame: ports.Frame{Type: ports.MsgOutput, Payload: []byte("stale")}, attempts: 4}155+ tr.pending[1] = &pending{frame: wire.Frame{Type: wire.MsgOutput, Payload: []byte("stale")}, attempts: 4}
155- tr.pending[2] = &pending{frame: ports.Frame{Type: ports.MsgOutput, Payload: []byte("stale")}, attempts: 7}156+ tr.pending[2] = &pending{frame: wire.Frame{Type: wire.MsgOutput, Payload: []byte("stale")}, attempts: 7}
156 157
157 tr.setLinkState(ports.LinkStateConnected, nil)158 tr.setLinkState(ports.LinkStateConnected, nil)
158 159
@@ -200,12 +201,12 @@ func TestPendingReliableQueueReturnsWhenLinkDegrades(t *testing.T) {
200 defer func() { _ = a.Close() }()201 defer func() { _ = a.Close() }()
201 defer func() { _ = b.Close() }()202 defer func() { _ = b.Close() }()
202 203
203- if err := a.Send(ports.Frame{Type: ports.MsgInput, Payload: []byte("first")}); err != nil {204+ if err := a.Send(wire.Frame{Type: wire.MsgInput, Payload: []byte("first")}); err != nil {
204 t.Fatal(err)205 t.Fatal(err)
205 }206 }
206 done := make(chan error, 2)207 done := make(chan error, 2)
207- go func() { done <- a.Send(ports.Frame{Type: ports.MsgInput, Payload: []byte("second")}) }()208+ go func() { done <- a.Send(wire.Frame{Type: wire.MsgInput, Payload: []byte("second")}) }()
208- go func() { done <- a.Send(ports.Frame{Type: ports.MsgInput, Payload: []byte("third")}) }()209+ go func() { done <- a.Send(wire.Frame{Type: wire.MsgInput, Payload: []byte("third")}) }()
209 waitForManualTimers(t, clk, 2)210 waitForManualTimers(t, clk, 2)
210 a.setLinkState(ports.LinkStateDegraded, nil)211 a.setLinkState(ports.LinkStateDegraded, nil)
211 for i := range 2 {212 for i := range 2 {
@@ -313,7 +314,7 @@ func TestLinkHealthPendingStallWithFreshContactProbesOnceWithoutDying(t *testing
313 now := time.Now()314 now := time.Now()
314 a.mu.Lock()315 a.mu.Lock()
315 a.health = healthTracker{lastPacket: now, lastRecord: now.Add(-time.Minute), lastProgress: now.Add(-time.Minute)}316 a.health = healthTracker{lastPacket: now, lastRecord: now.Add(-time.Minute), lastProgress: now.Add(-time.Minute)}
316- a.pending[1] = &pending{frame: ports.Frame{Type: ports.MsgOutput}, enqueued: now.Add(-time.Minute), attempts: 3}317+ a.pending[1] = &pending{frame: wire.Frame{Type: wire.MsgOutput}, enqueued: now.Add(-time.Minute), attempts: 3}
317 a.mu.Unlock()318 a.mu.Unlock()
318 319
319 a.checkSilence()320 a.checkSilence()
@@ -347,7 +348,7 @@ func TestLinkHealthHeartbeatControlCannotMaskPendingACKStall(t *testing.T) {
347 }348 }
348 defer func() { _ = tr.Close() }()349 defer func() { _ = tr.Close() }()
349 defer func() { _ = bPC.Close() }()350 defer func() { _ = bPC.Close() }()
350- if err := tr.Send(ports.Frame{Type: ports.MsgInput, Payload: []byte("unacked")}); err != nil {351+ if err := tr.Send(wire.Frame{Type: wire.MsgInput, Payload: []byte("unacked")}); err != nil {
351 t.Fatal(err)352 t.Fatal(err)
352 }353 }
353 354
@@ -417,7 +418,7 @@ func TestLinkHealthOnlyAckProgressThatRemovesPendingRestoresConnected(t *testing
417 now := tr.clock.Now()418 now := tr.clock.Now()
418 tr.linkState = ports.LinkStateDegraded419 tr.linkState = ports.LinkStateDegraded
419 tr.health = healthTracker{lastPacket: now, lastRecord: now.Add(-time.Minute), lastProgress: now.Add(-time.Minute)}420 tr.health = healthTracker{lastPacket: now, lastRecord: now.Add(-time.Minute), lastProgress: now.Add(-time.Minute)}
420- tr.pending[1] = &pending{frame: ports.Frame{Type: ports.MsgOutput}, first: now, last: now}421+ tr.pending[1] = &pending{frame: wire.Frame{Type: wire.MsgOutput}, first: now, last: now}
421 422
422 tr.handleRecord(ackRecord(1))423 tr.handleRecord(ackRecord(1))
423 if got := tr.LinkState(); got != ports.LinkStateConnected {424 if got := tr.LinkState(); got != ports.LinkStateConnected {
M internal/adapters/dgram/transport_lifecycle.go +6 -5
@@ -6,6 +6,7 @@ import (
6 "sync"6 "sync"
7 7
8 "github.com/bnema/vev/internal/ports"8 "github.com/bnema/vev/internal/ports"
9+ "github.com/bnema/vev/internal/protocol/wire"
9 pdgram "github.com/bnema/vev/pkg/dgram"10 pdgram "github.com/bnema/vev/pkg/dgram"
10 )11 )
11 12
@@ -29,7 +30,7 @@ func NewTransportWithOptions(pc net.PacketConn, peer net.Addr, key []byte, sendD
29 pending: make(map[uint64]*pending),30 pending: make(map[uint64]*pending),
30 replay: pdgram.NewReplayWindow(),31 replay: pdgram.NewReplayWindow(),
31 reasm: pdgram.NewReassembler(),32 reasm: pdgram.NewReassembler(),
32- in: make(chan ports.Frame, 32),33+ in: make(chan wire.Frame, 32),
33 done: make(chan struct{}),34 done: make(chan struct{}),
34 health: newHealthTracker(opts.Clock.Now()),35 health: newHealthTracker(opts.Clock.Now()),
35 lastAuthenticatedPacket: opts.Clock.Now(),36 lastAuthenticatedPacket: opts.Clock.Now(),
@@ -56,7 +57,7 @@ func NewTransportWithOptions(pc net.PacketConn, peer net.Addr, key []byte, sendD
56 probeWait: make(map[uint64]chan struct{}),57 probeWait: make(map[uint64]chan struct{}),
57 rebind: opts.RebindPacketConn,58 rebind: opts.RebindPacketConn,
58 nextRecvSeq: 1,59 nextRecvSeq: 1,
59- recvBuf: make(map[uint64]ports.Frame),60+ recvBuf: make(map[uint64]wire.Frame),
60 }61 }
61 for _, opt := range runtimeOpts {62 for _, opt := range runtimeOpts {
62 if opt != nil {63 if opt != nil {
@@ -90,7 +91,7 @@ func NewTransportWithOptions(pc net.PacketConn, peer net.Addr, key []byte, sendD
90 return t, nil91 return t, nil
91 }92 }
92 93
93-func (t *Transport) Recv() (ports.Frame, error) {94+func (t *Transport) Recv() (wire.Frame, error) {
94 end := t.beginRuntimeOperation(ports.RuntimeAdapterReceiveStart, 0)95 end := t.beginRuntimeOperation(ports.RuntimeAdapterReceiveStart, 0)
95 select {96 select {
96 case f := <-t.in:97 case f := <-t.in:
@@ -102,11 +103,11 @@ func (t *Transport) Recv() (ports.Frame, error) {
102 t.mu.Unlock()103 t.mu.Unlock()
103 if err != nil {104 if err != nil {
104 end(false)105 end(false)
105- return ports.Frame{}, err106+ return wire.Frame{}, err
106 }107 }
107 err = errors.New("dgram: closed")108 err = errors.New("dgram: closed")
108 end(false)109 end(false)
109- return ports.Frame{}, err110+ return wire.Frame{}, err
110 }111 }
111 }112 }
112 113
M internal/adapters/dgram/transport_observability_test.go +4 -3
@@ -13,6 +13,7 @@ import (
13 13
14 "github.com/bnema/vev/internal/adapters/observability"14 "github.com/bnema/vev/internal/adapters/observability"
15 "github.com/bnema/vev/internal/ports"15 "github.com/bnema/vev/internal/ports"
16+ "github.com/bnema/vev/internal/protocol/wire"
16 )17 )
17 18
18 func TestTransportObservabilityDgramFailedCloseEndsReceiveAndSend(t *testing.T) {19 func TestTransportObservabilityDgramFailedCloseEndsReceiveAndSend(t *testing.T) {
@@ -33,7 +34,7 @@ func TestTransportObservabilityDgramFailedCloseEndsReceiveAndSend(t *testing.T)
33 if err != nil {34 if err != nil {
34 t.Fatalf("NewTransportWithOptions() error = %v", err)35 t.Fatalf("NewTransportWithOptions() error = %v", err)
35 }36 }
36- if err := transport.Send(ports.Frame{Type: ports.MsgOutput, Payload: []byte("write failure")}); err == nil {37+ if err := transport.Send(wire.Frame{Type: wire.MsgOutput, Payload: []byte("write failure")}); err == nil {
37 t.Fatal("Send() error = nil on packet write failure")38 t.Fatal("Send() error = nil on packet write failure")
38 }39 }
39 recvDone := make(chan error, 1)40 recvDone := make(chan error, 1)
@@ -155,11 +156,11 @@ func TestTransportObservabilityDgramKeepsBehaviorClockSeparate(t *testing.T) {
155 }156 }
156 defer func() { _ = b.Close() }()157 defer func() { _ = b.Close() }()
157 158
158- want := ports.Frame{Type: ports.MsgOutput, Payload: []byte("datagram carriage remains exact")}159+ want := wire.Frame{Type: wire.MsgOutput, Payload: []byte("datagram carriage remains exact")}
159 if err := a.Send(want); err != nil {160 if err := a.Send(want); err != nil {
160 t.Fatalf("Send() error = %v", err)161 t.Fatalf("Send() error = %v", err)
161 }162 }
162- gotCh := make(chan ports.Frame, 1)163+ gotCh := make(chan wire.Frame, 1)
163 errCh := make(chan error, 1)164 errCh := make(chan error, 1)
164 go func() {165 go func() {
165 frame, recvErr := b.Recv()166 frame, recvErr := b.Recv()
M internal/adapters/dgram/transport_receive.go +8 -7
@@ -5,6 +5,7 @@ import (
5 "net"5 "net"
6 6
7 "github.com/bnema/vev/internal/ports"7 "github.com/bnema/vev/internal/ports"
8+ "github.com/bnema/vev/internal/protocol/wire"
8 pdgram "github.com/bnema/vev/pkg/dgram"9 pdgram "github.com/bnema/vev/pkg/dgram"
9 )10 )
10 11
@@ -176,7 +177,7 @@ func (t *Transport) handleRecord(p []byte) {
176 }177 }
177 }178 }
178 179
179-func (t *Transport) enqueueReliable(seq uint64, f ports.Frame) (ackSeq uint64, ack bool, queued bool) {180+func (t *Transport) enqueueReliable(seq uint64, f wire.Frame) (ackSeq uint64, ack bool, queued bool) {
180 t.deliverMu.Lock()181 t.deliverMu.Lock()
181 defer t.deliverMu.Unlock()182 defer t.deliverMu.Unlock()
182 if seq < t.nextRecvSeq {183 if seq < t.nextRecvSeq {
@@ -235,14 +236,14 @@ func (t *Transport) isClosed() bool {
235 return t.closed236 return t.closed
236 }237 }
237 238
238-func (t *Transport) deliver(f ports.Frame) {239+func (t *Transport) deliver(f wire.Frame) {
239 select {240 select {
240 case t.in <- f:241 case t.in <- f:
241 case <-t.done:242 case <-t.done:
242 }243 }
243 }244 }
244 245
245-func encodeData(seq uint64, reliable bool, f ports.Frame) []byte {246+func encodeData(seq uint64, reliable bool, f wire.Frame) []byte {
246 b := make([]byte, dataRecordHeaderSize+len(f.Payload))247 b := make([]byte, dataRecordHeaderSize+len(f.Payload))
247 b[0] = recData248 b[0] = recData
248 binary.BigEndian.PutUint64(b[1:9], seq)249 binary.BigEndian.PutUint64(b[1:9], seq)
@@ -253,12 +254,12 @@ func encodeData(seq uint64, reliable bool, f ports.Frame) []byte {
253 copy(b[dataRecordHeaderSize:], f.Payload)254 copy(b[dataRecordHeaderSize:], f.Payload)
254 return b255 return b
255 }256 }
256-func decodeData(b []byte) (uint64, bool, ports.Frame, bool) {257+func decodeData(b []byte) (uint64, bool, wire.Frame, bool) {
257 if len(b) < dataRecordHeaderSize || b[0] != recData {258 if len(b) < dataRecordHeaderSize || b[0] != recData {
258- return 0, false, ports.Frame{}, false259+ return 0, false, wire.Frame{}, false
259 }260 }
260 if b[11] != 0 {261 if b[11] != 0 {
261- return 0, false, ports.Frame{}, false262+ return 0, false, wire.Frame{}, false
262 }263 }
263- return binary.BigEndian.Uint64(b[1:9]), b[9] == 1, ports.Frame{Type: ports.MsgType(b[10]), Payload: append([]byte(nil), b[dataRecordHeaderSize:]...)}, true264+ return binary.BigEndian.Uint64(b[1:9]), b[9] == 1, wire.Frame{Type: wire.MsgType(b[10]), Payload: append([]byte(nil), b[dataRecordHeaderSize:]...)}, true
264 }265 }
M internal/adapters/dgram/transport_receive_test.go +18 -17
@@ -8,6 +8,7 @@ import (
8 "time"8 "time"
9 9
10 "github.com/bnema/vev/internal/ports"10 "github.com/bnema/vev/internal/ports"
11+ "github.com/bnema/vev/internal/protocol/wire"
11 pdgram "github.com/bnema/vev/pkg/dgram"12 pdgram "github.com/bnema/vev/pkg/dgram"
12 )13 )
13 14
@@ -35,11 +36,11 @@ func TestReliableDuplicateAckedButNotDeliveredTwice(t *testing.T) {
35 t.Errorf("close b: %v", err)36 t.Errorf("close b: %v", err)
36 }37 }
37 }()38 }()
38- if err := a.Send(ports.Frame{Type: ports.MsgInput, Payload: []byte("typed")}); err != nil {39+ if err := a.Send(wire.Frame{Type: wire.MsgInput, Payload: []byte("typed")}); err != nil {
39 t.Fatal(err)40 t.Fatal(err)
40 }41 }
41 got := recvWithin(t, b, time.Second)42 got := recvWithin(t, b, time.Second)
42- if got.Type != ports.MsgInput || string(got.Payload) != "typed" {43+ if got.Type != wire.MsgInput || string(got.Payload) != "typed" {
43 t.Fatalf("got=%+v", got)44 t.Fatalf("got=%+v", got)
44 }45 }
45 eventually(t, time.Second, droppedAck.Load)46 eventually(t, time.Second, droppedAck.Load)
@@ -70,7 +71,7 @@ func TestAuthenticatedPeerChangeUpdatesPeerAndEmitsConnected(t *testing.T) {
70 t.Fatalf("unauthenticated packet changed peer to %v", a.Peer())71 t.Fatalf("unauthenticated packet changed peer to %v", a.Peer())
71 }72 }
72 c, _ := pdgram.NewCodec(key())73 c, _ := pdgram.NewCodec(key())
73- rec := encodeData(7, true, ports.Frame{Type: ports.MsgPing})74+ rec := encodeData(7, true, wire.Frame{Type: wire.MsgPing})
74 frags, _ := pdgram.FragmentPayload(9, rec, pdgram.DefaultMTU)75 frags, _ := pdgram.FragmentPayload(9, rec, pdgram.DefaultMTU)
75 raw, _ := pdgram.MarshalFragment(frags[0])76 raw, _ := pdgram.MarshalFragment(frags[0])
76 aPC.in <- packet{c.Seal(2, 99, raw, nil), testAddr("evil")}77 aPC.in <- packet{c.Seal(2, 99, raw, nil), testAddr("evil")}
@@ -214,12 +215,12 @@ func TestAuthenticatedFragmentSeparatesContactProgressAndPeerEligibility(t *test
214 }215 }
215 216
216 func TestReliableReceiveBufferDoesNotDropAckedFutureFrame(t *testing.T) {217 func TestReliableReceiveBufferDoesNotDropAckedFutureFrame(t *testing.T) {
217- tr := &Transport{maxRecvBuffer: 1, nextRecvSeq: 1, recvBuf: make(map[uint64]ports.Frame)}218+ tr := &Transport{maxRecvBuffer: 1, nextRecvSeq: 1, recvBuf: make(map[uint64]wire.Frame)}
218- _, ack, queued := tr.enqueueReliable(2, ports.Frame{Type: ports.MsgOutput, Payload: []byte("future")})219+ _, ack, queued := tr.enqueueReliable(2, wire.Frame{Type: wire.MsgOutput, Payload: []byte("future")})
219 if !ack || !queued {220 if !ack || !queued {
220 t.Fatalf("future enqueue ack=%v queued=%v, want true/true", ack, queued)221 t.Fatalf("future enqueue ack=%v queued=%v, want true/true", ack, queued)
221 }222 }
222- _, ack, queued = tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")})223+ _, ack, queued = tr.enqueueReliable(1, wire.Frame{Type: wire.MsgOutput, Payload: []byte("next")})
223 if !ack || !queued {224 if !ack || !queued {
224 t.Fatalf("next enqueue ack=%v queued=%v, want true/true", ack, queued)225 t.Fatalf("next enqueue ack=%v queued=%v, want true/true", ack, queued)
225 }226 }
@@ -229,19 +230,19 @@ func TestReliableReceiveBufferDoesNotDropAckedFutureFrame(t *testing.T) {
229 }230 }
230 231
231 func TestDataRecordWireEncoding(t *testing.T) {232 func TestDataRecordWireEncoding(t *testing.T) {
232- got := encodeData(5, true, ports.Frame{Type: ports.MsgOutput, Payload: []byte("x")})233+ got := encodeData(5, true, wire.Frame{Type: wire.MsgOutput, Payload: []byte("x")})
233- want := []byte{recData, 0, 0, 0, 0, 0, 0, 0, 5, 1, byte(ports.MsgOutput), 0, 'x'}234+ want := []byte{recData, 0, 0, 0, 0, 0, 0, 0, 5, 1, byte(wire.MsgOutput), 0, 'x'}
234 if !bytes.Equal(got, want) {235 if !bytes.Equal(got, want) {
235 t.Fatalf("encoded data=%v, want %v", got, want)236 t.Fatalf("encoded data=%v, want %v", got, want)
236 }237 }
237 seq, reliable, frame, ok := decodeData(got)238 seq, reliable, frame, ok := decodeData(got)
238- if !ok || seq != 5 || !reliable || frame.Type != ports.MsgOutput || string(frame.Payload) != "x" {239+ if !ok || seq != 5 || !reliable || frame.Type != wire.MsgOutput || string(frame.Payload) != "x" {
239 t.Fatalf("decoded seq=%d reliable=%v frame=%+v ok=%v", seq, reliable, frame, ok)240 t.Fatalf("decoded seq=%d reliable=%v frame=%+v ok=%v", seq, reliable, frame, ok)
240 }241 }
241 }242 }
242 243
243 func TestDataRecordWireDecodeRejectsUnknownFlags(t *testing.T) {244 func TestDataRecordWireDecodeRejectsUnknownFlags(t *testing.T) {
244- withTrailingGarbage := []byte{recData, 0, 0, 0, 0, 0, 0, 0, 5, 1, byte(ports.MsgOutput), 1, 'x'}245+ withTrailingGarbage := []byte{recData, 0, 0, 0, 0, 0, 0, 0, 5, 1, byte(wire.MsgOutput), 1, 'x'}
245 if _, _, _, ok := decodeData(withTrailingGarbage); ok {246 if _, _, _, ok := decodeData(withTrailingGarbage); ok {
246 t.Fatal("decodeData accepted unknown data-record flags with trailing garbage")247 t.Fatal("decodeData accepted unknown data-record flags with trailing garbage")
247 }248 }
@@ -255,13 +256,13 @@ func TestReliableDeliveryPreservesInputSequenceOrder(t *testing.T) {
255 defer func() { _ = b.Close() }()256 defer func() { _ = b.Close() }()
256 257
257 for i := range 100 {258 for i := range 100 {
258- if err := a.Send(ports.Frame{Type: ports.MsgInput, Payload: []byte{byte(i)}}); err != nil {259+ if err := a.Send(wire.Frame{Type: wire.MsgInput, Payload: []byte{byte(i)}}); err != nil {
259 t.Fatal(err)260 t.Fatal(err)
260 }261 }
261 }262 }
262 for i := range 100 {263 for i := range 100 {
263 got := recvWithin(t, b, time.Second)264 got := recvWithin(t, b, time.Second)
264- if got.Type != ports.MsgInput || len(got.Payload) != 1 || got.Payload[0] != byte(i) {265+ if got.Type != wire.MsgInput || len(got.Payload) != 1 || got.Payload[0] != byte(i) {
265 t.Fatalf("frame %d delivered out of order: %+v", i, got)266 t.Fatalf("frame %d delivered out of order: %+v", i, got)
266 }267 }
267 }268 }
@@ -275,7 +276,7 @@ func TestAckProcessingContinuesWhenConsumerBackpressured(t *testing.T) {
275 defer func() { _ = b.Close() }()276 defer func() { _ = b.Close() }()
276 277
277 for i := range cap(b.in) + 20 {278 for i := range cap(b.in) + 20 {
278- if err := a.SendAsync(ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(i)}}); err != nil {279+ if err := a.SendAsync(wire.Frame{Type: wire.MsgOutput, Payload: []byte{byte(i)}}); err != nil {
279 t.Fatal(err)280 t.Fatal(err)
280 }281 }
281 }282 }
@@ -296,9 +297,9 @@ func TestAckProcessingContinuesWhenConsumerBackpressured(t *testing.T) {
296 }297 }
297 298
298 func TestReliableReceiverAcksOnlyHighestContiguousSequence(t *testing.T) {299 func TestReliableReceiverAcksOnlyHighestContiguousSequence(t *testing.T) {
299- tr := &Transport{maxRecvBuffer: maxRecvBuffer, nextRecvSeq: 1, recvBuf: make(map[uint64]ports.Frame)}300+ tr := &Transport{maxRecvBuffer: maxRecvBuffer, nextRecvSeq: 1, recvBuf: make(map[uint64]wire.Frame)}
300 301
301- ackSeq, ack, queued := tr.enqueueReliable(3, ports.Frame{Type: ports.MsgOutput, Payload: []byte("third")})302+ ackSeq, ack, queued := tr.enqueueReliable(3, wire.Frame{Type: wire.MsgOutput, Payload: []byte("third")})
302 if !ack || !queued {303 if !ack || !queued {
303 t.Fatalf("out-of-order enqueue ack=%v queued=%v, want true/true", ack, queued)304 t.Fatalf("out-of-order enqueue ack=%v queued=%v, want true/true", ack, queued)
304 }305 }
@@ -306,7 +307,7 @@ func TestReliableReceiverAcksOnlyHighestContiguousSequence(t *testing.T) {
306 t.Fatalf("out-of-order ack=%d, want highest contiguous %d", got, want)307 t.Fatalf("out-of-order ack=%d, want highest contiguous %d", got, want)
307 }308 }
308 309
309- ackSeq, ack, queued = tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("first")})310+ ackSeq, ack, queued = tr.enqueueReliable(1, wire.Frame{Type: wire.MsgOutput, Payload: []byte("first")})
310 if !ack || !queued {311 if !ack || !queued {
311 t.Fatalf("first enqueue ack=%v queued=%v, want true/true", ack, queued)312 t.Fatalf("first enqueue ack=%v queued=%v, want true/true", ack, queued)
312 }313 }
@@ -314,7 +315,7 @@ func TestReliableReceiverAcksOnlyHighestContiguousSequence(t *testing.T) {
314 t.Fatalf("first contiguous ack=%d, want %d", got, want)315 t.Fatalf("first contiguous ack=%d, want %d", got, want)
315 }316 }
316 317
317- ackSeq, ack, queued = tr.enqueueReliable(2, ports.Frame{Type: ports.MsgOutput, Payload: []byte("second")})318+ ackSeq, ack, queued = tr.enqueueReliable(2, wire.Frame{Type: wire.MsgOutput, Payload: []byte("second")})
318 if !ack || !queued {319 if !ack || !queued {
319 t.Fatalf("gap fill enqueue ack=%v queued=%v, want true/true", ack, queued)320 t.Fatalf("gap fill enqueue ack=%v queued=%v, want true/true", ack, queued)
320 }321 }
M internal/adapters/dgram/transport_replay_test.go +3 -3
@@ -3,13 +3,13 @@ package dgram
3 import (3 import (
4 "testing"4 "testing"
5 5
6- "github.com/bnema/vev/internal/ports"6+ "github.com/bnema/vev/internal/protocol/wire"
7 "github.com/bnema/vev/internal/testutil/replaytest"7 "github.com/bnema/vev/internal/testutil/replaytest"
8 "github.com/stretchr/testify/require"8 "github.com/stretchr/testify/require"
9 )9 )
10 10
11 func TestTransportReplay(t *testing.T) {11 func TestTransportReplay(t *testing.T) {
12- replaytest.Run(t, func(t *testing.T, frames []ports.Frame) []ports.Frame {12+ replaytest.Run(t, func(t *testing.T, frames []wire.Frame) []wire.Frame {
13 link := newSimulatedLink(fixedClock{}, packetPolicy{})13 link := newSimulatedLink(fixedClock{}, packetPolicy{})
14 leftPC, rightPC := newPairWithCapacity(link, 32)14 leftPC, rightPC := newPairWithCapacity(link, 32)
15 left, err := NewTransport(leftPC, testAddr("b"), key(), 1, 2)15 left, err := NewTransport(leftPC, testAddr("b"), key(), 1, 2)
@@ -18,7 +18,7 @@ func TestTransportReplay(t *testing.T) {
18 require.NoError(t, err)18 require.NoError(t, err)
19 t.Cleanup(func() { _ = left.Close(); _ = right.Close() })19 t.Cleanup(func() { _ = left.Close(); _ = right.Close() })
20 20
21- got := make([]ports.Frame, 0, len(frames))21+ got := make([]wire.Frame, 0, len(frames))
22 for _, frame := range frames {22 for _, frame := range frames {
23 require.NoError(t, left.Send(frame))23 require.NoError(t, left.Send(frame))
24 link.flush(rightPC)24 link.flush(rightPC)
M internal/adapters/dgram/transport_retransmit_test.go +21 -20
@@ -7,11 +7,12 @@ import (
7 "time"7 "time"
8 8
9 "github.com/bnema/vev/internal/ports"9 "github.com/bnema/vev/internal/ports"
10+ "github.com/bnema/vev/internal/protocol/wire"
10 )11 )
11 12
12 func TestResendSkipsQueuedOutputBeforeFirstWireAttempt(t *testing.T) {13 func TestResendSkipsQueuedOutputBeforeFirstWireAttempt(t *testing.T) {
13 tr, writes := newResendTestTransport(t, 10)14 tr, writes := newResendTestTransport(t, 10)
14- tr.pending[1] = &pending{frame: ports.Frame{Type: ports.MsgOutput, Payload: []byte("queued")}}15+ tr.pending[1] = &pending{frame: wire.Frame{Type: wire.MsgOutput, Payload: []byte("queued")}}
15 16
16 tr.resendPending()17 tr.resendPending()
17 18
@@ -27,7 +28,7 @@ func TestResendSkipsOutputDuringFirstWireAttempt(t *testing.T) {
27 tr, writes := newResendTestTransport(t, 10)28 tr, writes := newResendTestTransport(t, 10)
28 now := tr.clock.Now()29 now := tr.clock.Now()
29 tr.pending[1] = &pending{30 tr.pending[1] = &pending{
30- frame: ports.Frame{Type: ports.MsgOutput, Payload: []byte("sending")},31+ frame: wire.Frame{Type: wire.MsgOutput, Payload: []byte("sending")},
31 first: now.Add(-time.Second),32 first: now.Add(-time.Second),
32 last: now.Add(-time.Second),33 last: now.Add(-time.Second),
33 initialInFlight: true,34 initialInFlight: true,
@@ -68,11 +69,11 @@ func TestReliableInputRetransmitsUntilAck(t *testing.T) {
68 t.Errorf("close b: %v", err)69 t.Errorf("close b: %v", err)
69 }70 }
70 }()71 }()
71- if err := a.Send(ports.Frame{Type: ports.MsgInput, Payload: []byte("typed")}); err != nil {72+ if err := a.Send(wire.Frame{Type: wire.MsgInput, Payload: []byte("typed")}); err != nil {
72 t.Fatal(err)73 t.Fatal(err)
73 }74 }
74 got := recvWithin(t, b, time.Second)75 got := recvWithin(t, b, time.Second)
75- if got.Type != ports.MsgInput || string(got.Payload) != "typed" || !dropped.Load() {76+ if got.Type != wire.MsgInput || string(got.Payload) != "typed" || !dropped.Load() {
76 t.Fatalf("got=%+v dropped=%v", got, dropped.Load())77 t.Fatalf("got=%+v dropped=%v", got, dropped.Load())
77 }78 }
78 }79 }
@@ -95,11 +96,11 @@ func TestOutputRetransmitsUntilAck(t *testing.T) {
95 t.Errorf("close b: %v", err)96 t.Errorf("close b: %v", err)
96 }97 }
97 }()98 }()
98- if err := a.SendAsync(ports.Frame{Type: ports.MsgOutput, Payload: []byte("state1")}); err != nil {99+ if err := a.SendAsync(wire.Frame{Type: wire.MsgOutput, Payload: []byte("state1")}); err != nil {
99 t.Fatal(err)100 t.Fatal(err)
100 }101 }
101 got := recvWithin(t, b, time.Second)102 got := recvWithin(t, b, time.Second)
102- if got.Type != ports.MsgOutput || string(got.Payload) != "state1" || !dropped.Load() {103+ if got.Type != wire.MsgOutput || string(got.Payload) != "state1" || !dropped.Load() {
103 t.Fatalf("got=%+v dropped=%v", got, dropped.Load())104 t.Fatalf("got=%+v dropped=%v", got, dropped.Load())
104 }105 }
105 }106 }
@@ -124,7 +125,7 @@ func TestImmediateLoopbackAckSamplesStampedFinalWrite(t *testing.T) {
124 t.Error("ACK did not retire pending frame before WriteTo returned")125 t.Error("ACK did not retire pending frame before WriteTo returned")
125 }126 }
126 127
127- if err := a.SendAsync(ports.Frame{Type: ports.MsgOutput, Payload: []byte("state")}); err != nil {128+ if err := a.SendAsync(wire.Frame{Type: wire.MsgOutput, Payload: []byte("state")}); err != nil {
128 t.Fatal(err)129 t.Fatal(err)
129 }130 }
130 eventually(t, time.Second, func() bool {131 eventually(t, time.Second, func() bool {
@@ -155,14 +156,14 @@ func TestOutputDependencyChainRetransmitsDroppedPredecessors(t *testing.T) {
155 defer func() { _ = b.Close() }()156 defer func() { _ = b.Close() }()
156 157
157 for _, payload := range []string{"state1", "state2", "state3"} {158 for _, payload := range []string{"state1", "state2", "state3"} {
158- if err := a.SendAsync(ports.Frame{Type: ports.MsgOutput, Payload: []byte(payload)}); err != nil {159+ if err := a.SendAsync(wire.Frame{Type: wire.MsgOutput, Payload: []byte(payload)}); err != nil {
159 t.Fatal(err)160 t.Fatal(err)
160 }161 }
161 }162 }
162 163
163 for _, want := range []string{"state1", "state2", "state3"} {164 for _, want := range []string{"state1", "state2", "state3"} {
164 got := recvWithin(t, b, time.Second)165 got := recvWithin(t, b, time.Second)
165- if got.Type != ports.MsgOutput || string(got.Payload) != want {166+ if got.Type != wire.MsgOutput || string(got.Payload) != want {
166 t.Fatalf("got=%+v, want ordered prerequisite output %q", got, want)167 t.Fatalf("got=%+v, want ordered prerequisite output %q", got, want)
167 }168 }
168 }169 }
@@ -175,7 +176,7 @@ func TestResendPendingCapsBurst(t *testing.T) {
175 tr, writes := newResendTestTransport(t, 2)176 tr, writes := newResendTestTransport(t, 2)
176 now := tr.clock.Now()177 now := tr.clock.Now()
177 for i := range 5 {178 for i := range 5 {
178- tr.pending[uint64(i+1)] = &pending{frame: ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(i)}}, last: now.Add(-time.Second)}179+ tr.pending[uint64(i+1)] = &pending{frame: wire.Frame{Type: wire.MsgOutput, Payload: []byte{byte(i)}}, last: now.Add(-time.Second)}
179 }180 }
180 181
181 tr.resendPending()182 tr.resendPending()
@@ -189,7 +190,7 @@ func TestRetransmitBatchReducesCongestionWindowOnce(t *testing.T) {
189 now := tr.clock.Now()190 now := tr.clock.Now()
190 for seq := uint64(1); seq <= 3; seq++ {191 for seq := uint64(1); seq <= 3; seq++ {
191 tr.pending[seq] = &pending{192 tr.pending[seq] = &pending{
192- frame: ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(seq)}},193+ frame: wire.Frame{Type: wire.MsgOutput, Payload: []byte{byte(seq)}},
193 last: now.Add(-time.Second),194 last: now.Add(-time.Second),
194 }195 }
195 }196 }
@@ -209,7 +210,7 @@ func TestACKBeforeQueuedRetransmitDoesNotReduceCongestionWindow(t *testing.T) {
209 tr, writes := newResendTestTransport(t, 1)210 tr, writes := newResendTestTransport(t, 1)
210 now := tr.clock.Now()211 now := tr.clock.Now()
211 tr.pending[1] = &pending{212 tr.pending[1] = &pending{
212- frame: ports.Frame{Type: ports.MsgOutput, Payload: []byte("acked")},213+ frame: wire.Frame{Type: wire.MsgOutput, Payload: []byte("acked")},
213 first: now.Add(-time.Second),214 first: now.Add(-time.Second),
214 last: now.Add(-time.Second),215 last: now.Add(-time.Second),
215 wireBytes: tr.mtu,216 wireBytes: tr.mtu,
@@ -244,7 +245,7 @@ func TestOversizedRetransmitHonorsPacketBudget(t *testing.T) {
244 tr.clock = clk245 tr.clock = clk
245 tr.done = make(chan struct{})246 tr.done = make(chan struct{})
246 tr.pending[1] = &pending{247 tr.pending[1] = &pending{
247- frame: ports.Frame{Type: ports.MsgOutput, Payload: make([]byte, 24*tr.mtu)},248+ frame: wire.Frame{Type: wire.MsgOutput, Payload: make([]byte, 24*tr.mtu)},
248 last: clk.now.Add(-time.Second),249 last: clk.now.Add(-time.Second),
249 }250 }
250 done := make(chan struct{})251 done := make(chan struct{})
@@ -274,9 +275,9 @@ func TestOversizedRetransmitHonorsPacketBudget(t *testing.T) {
274 func TestResendPendingUsesExponentialBackoffAndMaxDelay(t *testing.T) {275 func TestResendPendingUsesExponentialBackoffAndMaxDelay(t *testing.T) {
275 tr, writes := newResendTestTransport(t, 10)276 tr, writes := newResendTestTransport(t, 10)
276 now := tr.clock.Now()277 now := tr.clock.Now()
277- tr.pending[1] = &pending{frame: ports.Frame{Type: ports.MsgOutput, Payload: []byte("too-soon")}, last: now.Add(-150 * time.Millisecond), attempts: 1}278+ tr.pending[1] = &pending{frame: wire.Frame{Type: wire.MsgOutput, Payload: []byte("too-soon")}, last: now.Add(-150 * time.Millisecond), attempts: 1}
278- tr.pending[2] = &pending{frame: ports.Frame{Type: ports.MsgOutput, Payload: []byte("ready")}, last: now.Add(-250 * time.Millisecond), attempts: 1}279+ tr.pending[2] = &pending{frame: wire.Frame{Type: wire.MsgOutput, Payload: []byte("ready")}, last: now.Add(-250 * time.Millisecond), attempts: 1}
279- tr.pending[3] = &pending{frame: ports.Frame{Type: ports.MsgOutput, Payload: []byte("capped")}, last: now.Add(-time.Second), attempts: 10}280+ tr.pending[3] = &pending{frame: wire.Frame{Type: wire.MsgOutput, Payload: []byte("capped")}, last: now.Add(-time.Second), attempts: 10}
280 281
281 tr.resendPending()282 tr.resendPending()
282 if got := writes.Load(); got != 2 {283 if got := writes.Load(); got != 2 {
@@ -297,7 +298,7 @@ func TestCumulativeAckReleasesAllEarlierPendingFrames(t *testing.T) {
297 tr, _ := newResendTestTransport(t, 10)298 tr, _ := newResendTestTransport(t, 10)
298 now := tr.clock.Now()299 now := tr.clock.Now()
299 for seq := uint64(1); seq <= 3; seq++ {300 for seq := uint64(1); seq <= 3; seq++ {
300- tr.pending[seq] = &pending{frame: ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(seq)}}, first: now, last: now}301+ tr.pending[seq] = &pending{frame: wire.Frame{Type: wire.MsgOutput, Payload: []byte{byte(seq)}}, first: now, last: now}
301 }302 }
302 303
303 tr.handleRecord(ackRecord(3))304 tr.handleRecord(ackRecord(3))
@@ -314,14 +315,14 @@ func TestRTTEstimatorUpdatesRTOFromAckSample(t *testing.T) {
314 tr.resendAfter = 100 * time.Millisecond315 tr.resendAfter = 100 * time.Millisecond
315 tr.maxResendAfter = time.Second316 tr.maxResendAfter = time.Second
316 tr.rto = tr.resendAfter317 tr.rto = tr.resendAfter
317- tr.pending[1] = &pending{frame: ports.Frame{Type: ports.MsgOutput}, first: clk.now.Add(-200 * time.Millisecond), last: clk.now.Add(-200 * time.Millisecond)}318+ tr.pending[1] = &pending{frame: wire.Frame{Type: wire.MsgOutput}, first: clk.now.Add(-200 * time.Millisecond), last: clk.now.Add(-200 * time.Millisecond)}
318 319
319 tr.handleRecord(ackRecord(1))320 tr.handleRecord(ackRecord(1))
320 321
321 if tr.rto <= tr.resendAfter {322 if tr.rto <= tr.resendAfter {
322 t.Fatalf("rto=%v, want above initial resend after RTT sample", tr.rto)323 t.Fatalf("rto=%v, want above initial resend after RTT sample", tr.rto)
323 }324 }
324- tr.pending[2] = &pending{frame: ports.Frame{Type: ports.MsgOutput}, first: clk.now, last: clk.now.Add(-(tr.rto - time.Millisecond))}325+ tr.pending[2] = &pending{frame: wire.Frame{Type: wire.MsgOutput}, first: clk.now, last: clk.now.Add(-(tr.rto - time.Millisecond))}
325 tr.resendPending()326 tr.resendPending()
326 if got := writes.Load(); got != 0 {327 if got := writes.Load(); got != 0 {
327 t.Fatalf("writes before rto=%d, want 0", got)328 t.Fatalf("writes before rto=%d, want 0", got)
@@ -340,7 +341,7 @@ func TestRTTEstimatorIgnoresRetransmittedFramesAfterRecovery(t *testing.T) {
340 tr.resendAfter = 100 * time.Millisecond341 tr.resendAfter = 100 * time.Millisecond
341 tr.maxResendAfter = time.Second342 tr.maxResendAfter = time.Second
342 tr.rto = tr.resendAfter343 tr.rto = tr.resendAfter
343- tr.pending[1] = &pending{frame: ports.Frame{Type: ports.MsgOutput}, first: clk.now.Add(-200 * time.Millisecond), last: clk.now.Add(-tr.resendAfter)}344+ tr.pending[1] = &pending{frame: wire.Frame{Type: wire.MsgOutput}, first: clk.now.Add(-200 * time.Millisecond), last: clk.now.Add(-tr.resendAfter)}
344 tr.resendPending()345 tr.resendPending()
345 tr.linkState = ports.LinkStateOffline346 tr.linkState = ports.LinkStateOffline
346 347
M internal/adapters/dgram/transport_send.go +5 -4
@@ -6,16 +6,17 @@ import (
6 "time"6 "time"
7 7
8 "github.com/bnema/vev/internal/ports"8 "github.com/bnema/vev/internal/ports"
9+ "github.com/bnema/vev/internal/protocol/wire"
9 )10 )
10 11
11-func (t *Transport) Send(f ports.Frame) error {12+func (t *Transport) Send(f wire.Frame) error {
12 end := t.beginRuntimeOperation(ports.RuntimeAdapterSendStart, uint64(len(f.Payload)))13 end := t.beginRuntimeOperation(ports.RuntimeAdapterSendStart, uint64(len(f.Payload)))
13 err := t.send(f, false)14 err := t.send(f, false)
14 end(err == nil)15 end(err == nil)
15 return err16 return err
16 }17 }
17 18
18-func (t *Transport) SendAsync(f ports.Frame) error {19+func (t *Transport) SendAsync(f wire.Frame) error {
19 end := t.beginRuntimeOperation(ports.RuntimeAdapterSendStart, uint64(len(f.Payload)))20 end := t.beginRuntimeOperation(ports.RuntimeAdapterSendStart, uint64(len(f.Payload)))
20 err := t.send(f, true)21 err := t.send(f, true)
21 end(err == nil)22 end(err == nil)
@@ -24,9 +25,9 @@ func (t *Transport) SendAsync(f ports.Frame) error {
24 25
25 // SendSynchronous owns queue reservation, fragment pacing, per-write deadlines,26 // SendSynchronous owns queue reservation, fragment pacing, per-write deadlines,
26 // and close cancellation. Callers must not layer a shorter preflight timer over it.27 // and close cancellation. Callers must not layer a shorter preflight timer over it.
27-func (t *Transport) SendSynchronous(f ports.Frame) error { return t.Send(f) }28+func (t *Transport) SendSynchronous(f wire.Frame) error { return t.Send(f) }
28 29
29-func (t *Transport) send(f ports.Frame, async bool) error {30+func (t *Transport) send(f wire.Frame, async bool) error {
30 reliable := true31 reliable := true
31 if err := t.lockOutboundSlot(reliable); err != nil {32 if err := t.lockOutboundSlot(reliable); err != nil {
32 return err33 return err
M internal/adapters/dgram/transport_send_test.go +31 -31
@@ -8,8 +8,8 @@ import (
8 "time"8 "time"
9 9
10 "github.com/bnema/vev/internal/domain"10 "github.com/bnema/vev/internal/domain"
11- "github.com/bnema/vev/internal/ports"
12 "github.com/bnema/vev/internal/protocol"11 "github.com/bnema/vev/internal/protocol"
12+ "github.com/bnema/vev/internal/protocol/wire"
13 pdgram "github.com/bnema/vev/pkg/dgram"13 pdgram "github.com/bnema/vev/pkg/dgram"
14 "github.com/stretchr/testify/require"14 "github.com/stretchr/testify/require"
15 )15 )
@@ -26,7 +26,7 @@ func TestPendingWireBytesMatchSealedDatagrams(t *testing.T) {
26 defer func() { _ = a.Close() }()26 defer func() { _ = a.Close() }()
27 27
28 maxFragmentData := mtu - pdgram.HeaderSize - a.codec.Overhead() - 1628 maxFragmentData := mtu - pdgram.HeaderSize - a.codec.Overhead() - 16
29- frame := ports.Frame{Type: ports.MsgOutput, Payload: make([]byte, maxFragmentData-dataRecordHeaderSize+1)}29+ frame := wire.Frame{Type: wire.MsgOutput, Payload: make([]byte, maxFragmentData-dataRecordHeaderSize+1)}
30 if err := a.Send(frame); err != nil {30 if err := a.Send(frame); err != nil {
31 t.Fatal(err)31 t.Fatal(err)
32 }32 }
@@ -69,7 +69,7 @@ func TestSendAsyncTransfersFragmentedRecordToBytePacedSender(t *testing.T) {
69 defer func() { _ = a.Close() }()69 defer func() { _ = a.Close() }()
70 70
71 maxFragmentData := mtu - pdgram.HeaderSize - a.codec.Overhead()71 maxFragmentData := mtu - pdgram.HeaderSize - a.codec.Overhead()
72- frame := ports.Frame{Type: ports.MsgOutput, Payload: make([]byte, 2*maxFragmentData)}72+ frame := wire.Frame{Type: wire.MsgOutput, Payload: make([]byte, 2*maxFragmentData)}
73 if err := a.SendAsync(frame); err != nil {73 if err := a.SendAsync(frame); err != nil {
74 t.Fatal(err)74 t.Fatal(err)
75 }75 }
@@ -92,12 +92,12 @@ func TestSmallOutputsShareInitialByteBurst(t *testing.T) {
92 defer func() { _ = a.Close() }()92 defer func() { _ = a.Close() }()
93 93
94 for i := range 3 {94 for i := range 3 {
95- if err := a.SendAsync(ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(i)}}); err != nil {95+ if err := a.SendAsync(wire.Frame{Type: wire.MsgOutput, Payload: []byte{byte(i)}}); err != nil {
96 t.Fatal(err)96 t.Fatal(err)
97 }97 }
98 }98 }
99 eventuallyPacketCount(t, bPC, 3)99 eventuallyPacketCount(t, bPC, 3)
100- if err := a.SendAsync(ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")}); err != nil {100+ if err := a.SendAsync(wire.Frame{Type: wire.MsgOutput, Payload: []byte("next")}); err != nil {
101 t.Fatal(err)101 t.Fatal(err)
102 }102 }
103 eventuallyPacketCount(t, bPC, 4)103 eventuallyPacketCount(t, bPC, 4)
@@ -112,12 +112,12 @@ func TestOutputPacingSendsAtMostOneOversizedFramePerTick(t *testing.T) {
112 }112 }
113 defer func() { _ = a.Close() }()113 defer func() { _ = a.Close() }()
114 114
115- if err := a.SendAsync(ports.Frame{Type: ports.MsgOutput, Payload: []byte("prime")}); err != nil {115+ if err := a.SendAsync(wire.Frame{Type: wire.MsgOutput, Payload: []byte("prime")}); err != nil {
116 t.Fatal(err)116 t.Fatal(err)
117 }117 }
118 large := mustMarshalOutput(protocol.Output{Epoch: 1, Base: 1, New: 2, Size: domain.Size{Cols: 1, Rows: 1}, Data: make([]byte, 24*a.mtu)})118 large := mustMarshalOutput(protocol.Output{Epoch: 1, Base: 1, New: 2, Size: domain.Size{Cols: 1, Rows: 1}, Data: make([]byte, 24*a.mtu)})
119 for range 2 {119 for range 2 {
120- if err := a.SendAsync(ports.Frame{Type: ports.MsgOutput, Payload: large}); err != nil {120+ if err := a.SendAsync(wire.Frame{Type: wire.MsgOutput, Payload: large}); err != nil {
121 t.Fatal(err)121 t.Fatal(err)
122 }122 }
123 }123 }
@@ -152,7 +152,7 @@ func TestPacedInitialSendTimestampsFinalFragmentCompletion(t *testing.T) {
152 t.Fatal(err)152 t.Fatal(err)
153 }153 }
154 defer func() { _ = a.Close() }()154 defer func() { _ = a.Close() }()
155- large := ports.Frame{Type: ports.MsgOutput, Payload: make([]byte, 24*a.mtu)}155+ large := wire.Frame{Type: wire.MsgOutput, Payload: make([]byte, 24*a.mtu)}
156 done := make(chan error, 1)156 done := make(chan error, 1)
157 go func() { done <- a.Send(large) }()157 go func() { done <- a.Send(large) }()
158 start := clk.Now()158 start := clk.Now()
@@ -209,7 +209,7 @@ func TestOwnedSynchronousMaxSideEffectCompletesThroughConcurrentPacedWork(t *tes
209 a.srtt = 500 * time.Millisecond209 a.srtt = 500 * time.Millisecond
210 a.mu.Unlock()210 a.mu.Unlock()
211 for i := range 8 {211 for i := range 8 {
212- if err := a.SendAsync(ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(i)}}); err != nil {212+ if err := a.SendAsync(wire.Frame{Type: wire.MsgOutput, Payload: []byte{byte(i)}}); err != nil {
213 t.Fatal(err)213 t.Fatal(err)
214 }214 }
215 }215 }
@@ -217,13 +217,13 @@ func TestOwnedSynchronousMaxSideEffectCompletesThroughConcurrentPacedWork(t *tes
217 a.mu.Lock()217 a.mu.Lock()
218 for _, seq := range []uint64{100, 101} {218 for _, seq := range []uint64{100, 101} {
219 a.pending[seq] = &pending{219 a.pending[seq] = &pending{
220- frame: ports.Frame{Type: ports.MsgOutput, Payload: make([]byte, 20*a.mtu)},220+ frame: wire.Frame{Type: wire.MsgOutput, Payload: make([]byte, 20*a.mtu)},
221 enqueued: now.Add(-time.Second), first: now.Add(-time.Second), last: now.Add(-time.Second),221 enqueued: now.Add(-time.Second), first: now.Add(-time.Second), last: now.Add(-time.Second),
222 }222 }
223 }223 }
224 a.mu.Unlock()224 a.mu.Unlock()
225 go a.resendPending()225 go a.resendPending()
226- sideEffect := ports.Frame{Type: ports.MsgOutput, Payload: mustMarshalOutput(protocol.Output{Epoch: 1, Size: domain.Size{Cols: 1, Rows: 1}, Data: make([]byte, 100*1024)})}226+ sideEffect := wire.Frame{Type: wire.MsgOutput, Payload: mustMarshalOutput(protocol.Output{Epoch: 1, Size: domain.Size{Cols: 1, Rows: 1}, Data: make([]byte, 100*1024)})}
227 done := make(chan error, 1)227 done := make(chan error, 1)
228 go func() { done <- a.SendSynchronous(sideEffect) }()228 go func() { done <- a.SendSynchronous(sideEffect) }()
229 229
@@ -256,12 +256,12 @@ func TestInputWaitsForDequeuedOutputBatch(t *testing.T) {
256 256
257 a.writeMu.Lock()257 a.writeMu.Lock()
258 for _, payload := range []string{"first", "queued"} {258 for _, payload := range []string{"first", "queued"} {
259- if err := a.SendAsync(ports.Frame{Type: ports.MsgOutput, Payload: []byte(payload)}); err != nil {259+ if err := a.SendAsync(wire.Frame{Type: wire.MsgOutput, Payload: []byte(payload)}); err != nil {
260 t.Fatal(err)260 t.Fatal(err)
261 }261 }
262 }262 }
263 done := make(chan error, 1)263 done := make(chan error, 1)
264- go func() { done <- a.Send(ports.Frame{Type: ports.MsgInput, Payload: []byte("typed")}) }()264+ go func() { done <- a.Send(wire.Frame{Type: wire.MsgInput, Payload: []byte("typed")}) }()
265 select {265 select {
266 case err := <-done:266 case err := <-done:
267 a.writeMu.Unlock()267 a.writeMu.Unlock()
@@ -295,7 +295,7 @@ func TestPacedControlQueueFailureClosesTransport(t *testing.T) {
295 t.Fatal(err)295 t.Fatal(err)
296 }296 }
297 inputDone := make(chan error, 1)297 inputDone := make(chan error, 1)
298- go func() { inputDone <- a.Send(ports.Frame{Type: ports.MsgInput, Payload: []byte("typed")}) }()298+ go func() { inputDone <- a.Send(wire.Frame{Type: wire.MsgInput, Payload: []byte("typed")}) }()
299 if err := <-inputDone; err == nil {299 if err := <-inputDone; err == nil {
300 t.Fatal("data send succeeded after paced wire failure")300 t.Fatal("data send succeeded after paced wire failure")
301 }301 }
@@ -328,10 +328,10 @@ func TestRemoveQueuedPendingResolvesSynchronousWaiters(t *testing.T) {
328 func TestCloseUnblocksQueuedSynchronousFrames(t *testing.T) {328 func TestCloseUnblocksQueuedSynchronousFrames(t *testing.T) {
329 frames := []struct {329 frames := []struct {
330 name string330 name string
331- frame ports.Frame331+ frame wire.Frame
332 }{332 }{
333- {name: "control", frame: ports.Frame{Type: ports.MsgPing}},333+ {name: "control", frame: wire.Frame{Type: wire.MsgPing}},
334- {name: "terminal side effect", frame: ports.Frame{Type: ports.MsgOutput, Payload: mustMarshalOutput(protocol.Output{Epoch: 1, Size: domain.Size{Cols: 1, Rows: 1}, Data: []byte("osc")})}},334+ {name: "terminal side effect", frame: wire.Frame{Type: wire.MsgOutput, Payload: mustMarshalOutput(protocol.Output{Epoch: 1, Size: domain.Size{Cols: 1, Rows: 1}, Data: []byte("osc")})}},
335 }335 }
336 for _, tt := range frames {336 for _, tt := range frames {
337 t.Run(tt.name, func(t *testing.T) {337 t.Run(tt.name, func(t *testing.T) {
@@ -341,7 +341,7 @@ func TestCloseUnblocksQueuedSynchronousFrames(t *testing.T) {
341 if err != nil {341 if err != nil {
342 t.Fatal(err)342 t.Fatal(err)
343 }343 }
344- if err := a.SendAsync(ports.Frame{Type: ports.MsgOutput, Payload: make([]byte, 3*a.mtu)}); err != nil {344+ if err := a.SendAsync(wire.Frame{Type: wire.MsgOutput, Payload: make([]byte, 3*a.mtu)}); err != nil {
345 t.Fatal(err)345 t.Fatal(err)
346 }346 }
347 done := make(chan error, 1)347 done := make(chan error, 1)
@@ -371,7 +371,7 @@ func TestOutputPaceFailureClosesTransportWithWriteError(t *testing.T) {
371 if err := bPC.Close(); err != nil {371 if err := bPC.Close(); err != nil {
372 t.Fatal(err)372 t.Fatal(err)
373 }373 }
374- if err := a.SendAsync(ports.Frame{Type: ports.MsgOutput, Payload: []byte("fails")}); err != nil {374+ if err := a.SendAsync(wire.Frame{Type: wire.MsgOutput, Payload: []byte("fails")}); err != nil {
375 t.Fatal(err)375 t.Fatal(err)
376 }376 }
377 377
@@ -380,7 +380,7 @@ func TestOutputPaceFailureClosesTransportWithWriteError(t *testing.T) {
380 case <-time.After(time.Second):380 case <-time.After(time.Second):
381 t.Fatal("transport remained open after paced output write failed")381 t.Fatal("transport remained open after paced output write failed")
382 }382 }
383- if err := a.Send(ports.Frame{Type: ports.MsgInput}); err == nil || err.Error() != "peer closed" {383+ if err := a.Send(wire.Frame{Type: wire.MsgInput}); err == nil || err.Error() != "peer closed" {
384 t.Fatalf("send after paced write failure err=%v, want peer closed", err)384 t.Fatalf("send after paced write failure err=%v, want peer closed", err)
385 }385 }
386 }386 }
@@ -394,24 +394,24 @@ func TestInputAndControlRemainOrderedInBoundedPacedQueue(t *testing.T) {
394 }394 }
395 defer func() { _ = a.Close() }()395 defer func() { _ = a.Close() }()
396 396
397- if err := a.SendAsync(ports.Frame{Type: ports.MsgOutput, Payload: []byte("output")}); err != nil {397+ if err := a.SendAsync(wire.Frame{Type: wire.MsgOutput, Payload: []byte("output")}); err != nil {
398 t.Fatal(err)398 t.Fatal(err)
399 }399 }
400- if err := a.SendAsync(ports.Frame{Type: ports.MsgOutput, Payload: []byte("queued")}); err != nil {400+ if err := a.SendAsync(wire.Frame{Type: wire.MsgOutput, Payload: []byte("queued")}); err != nil {
401 t.Fatal(err)401 t.Fatal(err)
402 }402 }
403 eventuallyPacketCount(t, bPC, 2)403 eventuallyPacketCount(t, bPC, 2)
404 inputDone := make(chan error, 1)404 inputDone := make(chan error, 1)
405- go func() { inputDone <- a.Send(ports.Frame{Type: ports.MsgInput, Payload: []byte("typed")}) }()405+ go func() { inputDone <- a.Send(wire.Frame{Type: wire.MsgInput, Payload: []byte("typed")}) }()
406 eventuallyPacketCount(t, bPC, 3)406 eventuallyPacketCount(t, bPC, 3)
407 if err := <-inputDone; err != nil {407 if err := <-inputDone; err != nil {
408 t.Fatal(err)408 t.Fatal(err)
409 }409 }
410- if err := a.SendAsync(ports.Frame{Type: ports.MsgOutput, Payload: []byte("queued-before-control")}); err != nil {410+ if err := a.SendAsync(wire.Frame{Type: wire.MsgOutput, Payload: []byte("queued-before-control")}); err != nil {
411 t.Fatal(err)411 t.Fatal(err)
412 }412 }
413 controlDone := make(chan error, 1)413 controlDone := make(chan error, 1)
414- go func() { controlDone <- a.Send(ports.Frame{Type: ports.MsgPing}) }()414+ go func() { controlDone <- a.Send(wire.Frame{Type: wire.MsgPing}) }()
415 eventuallyPacketCount(t, bPC, 5)415 eventuallyPacketCount(t, bPC, 5)
416 if err := <-controlDone; err != nil {416 if err := <-controlDone; err != nil {
417 t.Fatal(err)417 t.Fatal(err)
@@ -431,7 +431,7 @@ func TestSendWriteTimeoutBoundsBlockedPacketConn(t *testing.T) {
431 defer func() { _ = tr.Close() }()431 defer func() { _ = tr.Close() }()
432 432
433 done := make(chan error, 1)433 done := make(chan error, 1)
434- go func() { done <- tr.Send(ports.Frame{Type: ports.MsgInput, Payload: []byte("blocked")}) }()434+ go func() { done <- tr.Send(wire.Frame{Type: wire.MsgInput, Payload: []byte("blocked")}) }()
435 select {435 select {
436 case err := <-done:436 case err := <-done:
437 if err == nil {437 if err == nil {
@@ -460,11 +460,11 @@ func TestPendingReliableQueueBackpressuresUntilAck(t *testing.T) {
460 defer func() { _ = a.Close() }()460 defer func() { _ = a.Close() }()
461 defer func() { _ = b.Close() }()461 defer func() { _ = b.Close() }()
462 462
463- if err := a.Send(ports.Frame{Type: ports.MsgInput, Payload: []byte("first")}); err != nil {463+ if err := a.Send(wire.Frame{Type: wire.MsgInput, Payload: []byte("first")}); err != nil {
464 t.Fatal(err)464 t.Fatal(err)
465 }465 }
466 done := make(chan error, 1)466 done := make(chan error, 1)
467- go func() { done <- a.Send(ports.Frame{Type: ports.MsgInput, Payload: []byte("second")}) }()467+ go func() { done <- a.Send(wire.Frame{Type: wire.MsgInput, Payload: []byte("second")}) }()
468 468
469 select {469 select {
470 case err := <-done:470 case err := <-done:
@@ -498,11 +498,11 @@ func TestPendingReliableQueueReturnsErrPendingFullWhileConnected(t *testing.T) {
498 defer func() { _ = a.Close() }()498 defer func() { _ = a.Close() }()
499 defer func() { _ = b.Close() }()499 defer func() { _ = b.Close() }()
500 500
501- if err := a.Send(ports.Frame{Type: ports.MsgInput, Payload: []byte("first")}); err != nil {501+ if err := a.Send(wire.Frame{Type: wire.MsgInput, Payload: []byte("first")}); err != nil {
502 t.Fatal(err)502 t.Fatal(err)
503 }503 }
504 done := make(chan error, 1)504 done := make(chan error, 1)
505- go func() { done <- a.Send(ports.Frame{Type: ports.MsgInput, Payload: []byte("second")}) }()505+ go func() { done <- a.Send(wire.Frame{Type: wire.MsgInput, Payload: []byte("second")}) }()
506 waitForManualTimers(t, clk, 4)506 waitForManualTimers(t, clk, 4)
507 507
508 clk.advance(50 * time.Millisecond)508 clk.advance(50 * time.Millisecond)
@@ -530,7 +530,7 @@ func TestWritePacketDeadlineUsesTransportClock(t *testing.T) {
530 }530 }
531 defer func() { _ = tr.Close() }()531 defer func() { _ = tr.Close() }()
532 532
533- if err := tr.Send(ports.Frame{Type: ports.MsgInput, Payload: []byte("deadline")}); err != nil {533+ if err := tr.Send(wire.Frame{Type: wire.MsgInput, Payload: []byte("deadline")}); err != nil {
534 t.Fatal(err)534 t.Fatal(err)
535 }535 }
536 got, _ := pc.deadline.Load().(time.Time)536 got, _ := pc.deadline.Load().(time.Time)
M internal/adapters/dgram/transport_test_helpers_test.go +19 -18
@@ -10,6 +10,7 @@ import (
10 "time"10 "time"
11 11
12 "github.com/bnema/vev/internal/ports"12 "github.com/bnema/vev/internal/ports"
13+ "github.com/bnema/vev/internal/protocol/wire"
13 pdgram "github.com/bnema/vev/pkg/dgram"14 pdgram "github.com/bnema/vev/pkg/dgram"
14 )15 )
15 16
@@ -309,15 +310,15 @@ func probeRecord(kind byte, id uint64) []byte {
309 }310 }
310 311
311 func TestReliableRecvBufferBoundedForFarFutureSequences(t *testing.T) {312 func TestReliableRecvBufferBoundedForFarFutureSequences(t *testing.T) {
312- tr := &Transport{nextRecvSeq: 1, recvBuf: make(map[uint64]ports.Frame), maxRecvBuffer: maxRecvBuffer, done: make(chan struct{})}313+ tr := &Transport{nextRecvSeq: 1, recvBuf: make(map[uint64]wire.Frame), maxRecvBuffer: maxRecvBuffer, done: make(chan struct{})}
313 tr.deliverCond = sync.NewCond(&tr.deliverMu)314 tr.deliverCond = sync.NewCond(&tr.deliverMu)
314 for i := range maxRecvBuffer + 100 {315 for i := range maxRecvBuffer + 100 {
315- tr.enqueueReliable(uint64(1000+i), ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(i)}})316+ tr.enqueueReliable(uint64(1000+i), wire.Frame{Type: wire.MsgOutput, Payload: []byte{byte(i)}})
316 }317 }
317 if got := len(tr.recvBuf); got != maxRecvBuffer {318 if got := len(tr.recvBuf); got != maxRecvBuffer {
318 t.Fatalf("recvBuf len=%d, want %d", got, maxRecvBuffer)319 t.Fatalf("recvBuf len=%d, want %d", got, maxRecvBuffer)
319 }320 }
320- tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")})321+ tr.enqueueReliable(1, wire.Frame{Type: wire.MsgOutput, Payload: []byte("next")})
321 if _, ok := tr.recvBuf[1]; !ok {322 if _, ok := tr.recvBuf[1]; !ok {
322 t.Fatalf("next expected sequence was dropped when far-future buffer was full")323 t.Fatalf("next expected sequence was dropped when far-future buffer was full")
323 }324 }
@@ -329,13 +330,13 @@ func TestReliableRecvBufferBoundedForFarFutureSequences(t *testing.T) {
329 func TestOutputPrerequisitesRemainInFullReceiveBuffer(t *testing.T) {330 func TestOutputPrerequisitesRemainInFullReceiveBuffer(t *testing.T) {
330 tr := &Transport{331 tr := &Transport{
331 nextRecvSeq: 1,332 nextRecvSeq: 1,
332- recvBuf: map[uint64]ports.Frame{2: {Type: ports.MsgOutput, Payload: []byte("stale")}},333+ recvBuf: map[uint64]wire.Frame{2: {Type: wire.MsgOutput, Payload: []byte("stale")}},
333 maxRecvBuffer: 1,334 maxRecvBuffer: 1,
334 done: make(chan struct{}),335 done: make(chan struct{}),
335 }336 }
336 tr.deliverCond = sync.NewCond(&tr.deliverMu)337 tr.deliverCond = sync.NewCond(&tr.deliverMu)
337 338
338- ackSeq, ack, queued := tr.enqueueReliable(3, ports.Frame{Type: ports.MsgOutput, Payload: []byte("replacement")})339+ ackSeq, ack, queued := tr.enqueueReliable(3, wire.Frame{Type: wire.MsgOutput, Payload: []byte("replacement")})
339 if ack || queued || ackSeq != 0 {340 if ack || queued || ackSeq != 0 {
340 t.Fatalf("replacement ackSeq=%d ack=%v queued=%v, want output rejected while prerequisite buffer is full", ackSeq, ack, queued)341 t.Fatalf("replacement ackSeq=%d ack=%v queued=%v, want output rejected while prerequisite buffer is full", ackSeq, ack, queued)
341 }342 }
@@ -353,22 +354,22 @@ func TestReliableFullRecvBufferDoesNotAckDroppedFarFutureFrame(t *testing.T) {
353 if err != nil {354 if err != nil {
354 t.Fatal(err)355 t.Fatal(err)
355 }356 }
356- tr := &Transport{pc: aPC, peer: testAddr("b"), codec: codec, sendDir: 1, mtu: pdgram.DefaultMTU, nextRecvSeq: 1, recvBuf: make(map[uint64]ports.Frame), maxRecvBuffer: maxRecvBuffer, clock: realClock{}, done: make(chan struct{})}357+ tr := &Transport{pc: aPC, peer: testAddr("b"), codec: codec, sendDir: 1, mtu: pdgram.DefaultMTU, nextRecvSeq: 1, recvBuf: make(map[uint64]wire.Frame), maxRecvBuffer: maxRecvBuffer, clock: realClock{}, done: make(chan struct{})}
357 tr.deliverCond = sync.NewCond(&tr.deliverMu)358 tr.deliverCond = sync.NewCond(&tr.deliverMu)
358 defer func() { _ = aPC.Close() }()359 defer func() { _ = aPC.Close() }()
359 defer func() { _ = bPC.Close() }()360 defer func() { _ = bPC.Close() }()
360 361
361 for i := range maxRecvBuffer {362 for i := range maxRecvBuffer {
362- tr.recvBuf[uint64(1000+i)] = ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(i)}}363+ tr.recvBuf[uint64(1000+i)] = wire.Frame{Type: wire.MsgOutput, Payload: []byte{byte(i)}}
363 }364 }
364- tr.handleRecord(encodeData(9999, true, ports.Frame{Type: ports.MsgOutput, Payload: []byte("dropped")}))365+ tr.handleRecord(encodeData(9999, true, wire.Frame{Type: wire.MsgOutput, Payload: []byte("dropped")}))
365 select {366 select {
366 case pkt := <-bPC.in:367 case pkt := <-bPC.in:
367 t.Fatalf("unexpected ACK packet for dropped frame: %x", pkt.b)368 t.Fatalf("unexpected ACK packet for dropped frame: %x", pkt.b)
368 case <-time.After(25 * time.Millisecond):369 case <-time.After(25 * time.Millisecond):
369 }370 }
370 371
371- tr.handleRecord(encodeData(1, true, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")}))372+ tr.handleRecord(encodeData(1, true, wire.Frame{Type: wire.MsgOutput, Payload: []byte("next")}))
372 select {373 select {
373 case <-bPC.in:374 case <-bPC.in:
374 case <-time.After(time.Second):375 case <-time.After(time.Second):
@@ -376,27 +377,27 @@ func TestReliableFullRecvBufferDoesNotAckDroppedFarFutureFrame(t *testing.T) {
376 }377 }
377 }378 }
378 379
379-func recvMaybe(tr *Transport, d time.Duration) (ports.Frame, bool) {380+func recvMaybe(tr *Transport, d time.Duration) (wire.Frame, bool) {
380- ch := make(chan ports.Frame, 1)381+ ch := make(chan wire.Frame, 1)
381 go func() { f, _ := tr.Recv(); ch <- f }()382 go func() { f, _ := tr.Recv(); ch <- f }()
382 select {383 select {
383 case f := <-ch:384 case f := <-ch:
384 return f, true385 return f, true
385 case <-time.After(d):386 case <-time.After(d):
386- return ports.Frame{}, false387+ return wire.Frame{}, false
387 }388 }
388 }389 }
389 390
390-func recvWithin(t *testing.T, tr *Transport, d time.Duration) ports.Frame {391+func recvWithin(t *testing.T, tr *Transport, d time.Duration) wire.Frame {
391 t.Helper()392 t.Helper()
392- ch := make(chan ports.Frame, 1)393+ ch := make(chan wire.Frame, 1)
393 go func() { f, _ := tr.Recv(); ch <- f }()394 go func() { f, _ := tr.Recv(); ch <- f }()
394 select {395 select {
395 case f := <-ch:396 case f := <-ch:
396 return f397 return f
397 case <-time.After(d):398 case <-time.After(d):
398 t.Fatal("timeout")399 t.Fatal("timeout")
399- return ports.Frame{}400+ return wire.Frame{}
400 }401 }
401 }402 }
402 403
@@ -412,10 +413,10 @@ func waitPeer(t *testing.T, tr *Transport, want string) {
412 t.Fatalf("peer=%v, want %s", tr.Peer(), want)413 t.Fatalf("peer=%v, want %s", tr.Peer(), want)
413 }414 }
414 415
415-func recvErrWithin(t *testing.T, tr *Transport, d time.Duration) (ports.Frame, error) {416+func recvErrWithin(t *testing.T, tr *Transport, d time.Duration) (wire.Frame, error) {
416 t.Helper()417 t.Helper()
417 type result struct {418 type result struct {
418- f ports.Frame419+ f wire.Frame
419 err error420 err error
420 }421 }
421 ch := make(chan result, 1)422 ch := make(chan result, 1)
@@ -428,6 +429,6 @@ func recvErrWithin(t *testing.T, tr *Transport, d time.Duration) (ports.Frame, e
428 return r.f, r.err429 return r.f, r.err
429 case <-time.After(d):430 case <-time.After(d):
430 t.Fatal("timeout")431 t.Fatal("timeout")
431- return ports.Frame{}, nil432+ return wire.Frame{}, nil
432 }433 }
433 }434 }
M internal/adapters/dgram/wire_test_helpers_test.go +3 -3
@@ -1,12 +1,12 @@
1 package dgram1 package dgram
2 2
3 import (3 import (
4- "github.com/bnema/vev/internal/ports"
5 "github.com/bnema/vev/internal/protocol"4 "github.com/bnema/vev/internal/protocol"
5+ "github.com/bnema/vev/internal/protocol/wire"
6 )6 )
7 7
8 func mustMarshalOutput(m protocol.Output) []byte {8 func mustMarshalOutput(m protocol.Output) []byte {
9- payload, err := ports.MarshalOutput(m)9+ payload, err := wire.MarshalOutput(m)
10 if err != nil {10 if err != nil {
11 panic(err)11 panic(err)
12 }12 }
@@ -14,7 +14,7 @@ func mustMarshalOutput(m protocol.Output) []byte {
14 }14 }
15 15
16 func mustMarshalAck(m protocol.Ack) []byte {16 func mustMarshalAck(m protocol.Ack) []byte {
17- payload, err := ports.MarshalAck(m)17+ payload, err := wire.MarshalAck(m)
18 if err != nil {18 if err != nil {
19 panic(err)19 panic(err)
20 }20 }
M internal/adapters/ipc/listener_test.go +3 -3
@@ -9,7 +9,7 @@ import (
9 "strings"9 "strings"
10 "testing"10 "testing"
11 11
12- "github.com/bnema/vev/internal/ports"12+ "github.com/bnema/vev/internal/protocol/wire"
13 )13 )
14 14
15 func TestListenAcceptRoundTrip(t *testing.T) {15 func TestListenAcceptRoundTrip(t *testing.T) {
@@ -43,7 +43,7 @@ func TestListenAcceptRoundTrip(t *testing.T) {
43 }43 }
44 defer func() { _ = conn.Close() }()44 defer func() { _ = conn.Close() }()
45 client := NewTransport(conn)45 client := NewTransport(conn)
46- errCh <- client.Send(ports.Frame{Type: ports.MsgPing})46+ errCh <- client.Send(wire.Frame{Type: wire.MsgPing})
47 }()47 }()
48 48
49 transport, err := ln.Accept()49 transport, err := ln.Accept()
@@ -56,7 +56,7 @@ func TestListenAcceptRoundTrip(t *testing.T) {
56 if err != nil {56 if err != nil {
57 t.Fatalf("Recv() error = %v", err)57 t.Fatalf("Recv() error = %v", err)
58 }58 }
59- if got.Type != ports.MsgPing {59+ if got.Type != wire.MsgPing {
60 t.Fatalf("Recv() type = %v, want MsgPing", got.Type)60 t.Fatalf("Recv() type = %v, want MsgPing", got.Type)
61 }61 }
62 if err := <-errCh; err != nil {62 if err := <-errCh; err != nil {
M internal/adapters/ipc/transport.go +13 -12
@@ -8,6 +8,7 @@ import (
8 "sync"8 "sync"
9 9
10 "github.com/bnema/vev/internal/ports"10 "github.com/bnema/vev/internal/ports"
11+ "github.com/bnema/vev/internal/protocol/wire"
11 )12 )
12 13
13 // frameHeaderLen is the size, in bytes, of the length prefix that precedes14 // frameHeaderLen is the size, in bytes, of the length prefix that precedes
@@ -19,7 +20,7 @@ const frameHeaderLen = 4
19 var ErrZeroLengthFrame = errors.New("ipc: zero-length frame")20 var ErrZeroLengthFrame = errors.New("ipc: zero-length frame")
20 21
21 // ErrFrameTooLarge is returned by Recv when a frame's length field exceeds22 // ErrFrameTooLarge is returned by Recv when a frame's length field exceeds
22-// ports.MaxFrameLen, and by Send when a payload is too large to encode.23+// wire.MaxFrameLen, and by Send when a payload is too large to encode.
23 var ErrFrameTooLarge = errors.New("ipc: frame exceeds maximum length")24 var ErrFrameTooLarge = errors.New("ipc: frame exceeds maximum length")
24 25
25 // ErrBackpressure means the bounded IPC egress queue is full. Callers using26 // ErrBackpressure means the bounded IPC egress queue is full. Callers using
@@ -103,7 +104,7 @@ type sendRequest struct {
103 }104 }
104 105
105 // Send queues f for the sole writer and waits for its wire attempt.106 // Send queues f for the sole writer and waits for its wire attempt.
106-func (t *unixTransport) Send(f ports.Frame) error {107+func (t *unixTransport) Send(f wire.Frame) error {
107 data, err := marshalFrame(f)108 data, err := marshalFrame(f)
108 if err != nil {109 if err != nil {
109 return err110 return err
@@ -124,7 +125,7 @@ func (t *unixTransport) Send(f ports.Frame) error {
124 125
125 // SendAsync accepts f for ordered background transmission without waiting for126 // SendAsync accepts f for ordered background transmission without waiting for
126 // a socket write. The queue is bounded and ownership includes a payload copy.127 // a socket write. The queue is bounded and ownership includes a payload copy.
127-func (t *unixTransport) SendAsync(f ports.Frame) error {128+func (t *unixTransport) SendAsync(f wire.Frame) error {
128 data, err := marshalFrame(f)129 data, err := marshalFrame(f)
129 if err != nil {130 if err != nil {
130 return err131 return err
@@ -137,9 +138,9 @@ func (t *unixTransport) SendAsync(f ports.Frame) error {
137 return nil138 return nil
138 }139 }
139 140
140-func marshalFrame(f ports.Frame) ([]byte, error) {141+func marshalFrame(f wire.Frame) ([]byte, error) {
141 n := 1 + len(f.Payload) // type + payload142 n := 1 + len(f.Payload) // type + payload
142- if n > ports.MaxFrameLen {143+ if n > wire.MaxFrameLen {
143 return nil, ErrFrameTooLarge144 return nil, ErrFrameTooLarge
144 }145 }
145 buf := make([]byte, frameHeaderLen+n)146 buf := make([]byte, frameHeaderLen+n)
@@ -256,22 +257,22 @@ func writeAll(w io.Writer, data []byte) error {
256 // Recv reads one frame: a 4-byte length, then a body of that many bytes257 // Recv reads one frame: a 4-byte length, then a body of that many bytes
257 // (type byte + payload). It blocks until a full frame arrives, the258 // (type byte + payload). It blocks until a full frame arrives, the
258 // connection is closed (io.EOF), or an error occurs.259 // connection is closed (io.EOF), or an error occurs.
259-func (t *unixTransport) Recv() (ports.Frame, error) {260+func (t *unixTransport) Recv() (wire.Frame, error) {
260 end := t.beginOperation(ports.RuntimeAdapterReceiveStart, 0)261 end := t.beginOperation(ports.RuntimeAdapterReceiveStart, 0)
261 var hdr [frameHeaderLen]byte262 var hdr [frameHeaderLen]byte
262 if _, err := io.ReadFull(t.conn, hdr[:]); err != nil {263 if _, err := io.ReadFull(t.conn, hdr[:]); err != nil {
263 end(false)264 end(false)
264- return ports.Frame{}, err265+ return wire.Frame{}, err
265 }266 }
266 267
267 n := binary.BigEndian.Uint32(hdr[:])268 n := binary.BigEndian.Uint32(hdr[:])
268 if n == 0 {269 if n == 0 {
269 end(false)270 end(false)
270- return ports.Frame{}, ErrZeroLengthFrame271+ return wire.Frame{}, ErrZeroLengthFrame
271 }272 }
272- if n > ports.MaxFrameLen {273+ if n > wire.MaxFrameLen {
273 end(false)274 end(false)
274- return ports.Frame{}, ErrFrameTooLarge275+ return wire.Frame{}, ErrFrameTooLarge
275 }276 }
276 277
277 if cap(t.readBuf) < int(n) {278 if cap(t.readBuf) < int(n) {
@@ -281,7 +282,7 @@ func (t *unixTransport) Recv() (ports.Frame, error) {
281 }282 }
282 if _, err := io.ReadFull(t.conn, t.readBuf); err != nil {283 if _, err := io.ReadFull(t.conn, t.readBuf); err != nil {
283 end(false)284 end(false)
284- return ports.Frame{}, err285+ return wire.Frame{}, err
285 }286 }
286 287
287 var payload []byte288 var payload []byte
@@ -290,7 +291,7 @@ func (t *unixTransport) Recv() (ports.Frame, error) {
290 copy(payload, t.readBuf[1:])291 copy(payload, t.readBuf[1:])
291 }292 }
292 end(true)293 end(true)
293- return ports.Frame{Type: ports.MsgType(t.readBuf[0]), Payload: payload}, nil294+ return wire.Frame{Type: wire.MsgType(t.readBuf[0]), Payload: payload}, nil
294 }295 }
295 296
296 func (t *unixTransport) beginOperation(start ports.RuntimeMarkKind, bytes uint64) func(bool) {297 func (t *unixTransport) beginOperation(start ports.RuntimeMarkKind, bytes uint64) func(bool) {
M internal/adapters/ipc/transport_benchmark_test.go +4 -4
@@ -7,14 +7,14 @@ import (
7 "testing"7 "testing"
8 "time"8 "time"
9 9
10- "github.com/bnema/vev/internal/ports"10+ "github.com/bnema/vev/internal/protocol/wire"
11 )11 )
12 12
13 func BenchmarkTransportSend(b *testing.B) {13 func BenchmarkTransportSend(b *testing.B) {
14 conn := discardConn{}14 conn := discardConn{}
15 tr := NewTransport(conn)15 tr := NewTransport(conn)
16 payload := []byte("payload payload payload")16 payload := []byte("payload payload payload")
17- frame := ports.Frame{Type: ports.MsgOutput, Payload: payload}17+ frame := wire.Frame{Type: wire.MsgOutput, Payload: payload}
18 18
19 b.ReportAllocs()19 b.ReportAllocs()
20 for b.Loop() {20 for b.Loop() {
@@ -26,7 +26,7 @@ func BenchmarkTransportSend(b *testing.B) {
26 26
27 func BenchmarkTransportRecvReuse(b *testing.B) {27 func BenchmarkTransportRecvReuse(b *testing.B) {
28 payload := []byte("payload payload payload")28 payload := []byte("payload payload payload")
29- encoded := encodeBenchmarkFrame(ports.Frame{Type: ports.MsgOutput, Payload: payload})29+ encoded := encodeBenchmarkFrame(wire.Frame{Type: wire.MsgOutput, Payload: payload})
30 recv := &unixTransport{conn: &loopingReaderConn{data: encoded}}30 recv := &unixTransport{conn: &loopingReaderConn{data: encoded}}
31 31
32 b.ReportAllocs()32 b.ReportAllocs()
@@ -37,7 +37,7 @@ func BenchmarkTransportRecvReuse(b *testing.B) {
37 }37 }
38 }38 }
39 39
40-func encodeBenchmarkFrame(f ports.Frame) []byte {40+func encodeBenchmarkFrame(f wire.Frame) []byte {
41 n := 1 + len(f.Payload)41 n := 1 + len(f.Payload)
42 buf := make([]byte, frameHeaderLen+n)42 buf := make([]byte, frameHeaderLen+n)
43 binary.BigEndian.PutUint32(buf[:frameHeaderLen], uint32(n))43 binary.BigEndian.PutUint32(buf[:frameHeaderLen], uint32(n))
M internal/adapters/ipc/transport_observability_test.go +3 -2
@@ -12,6 +12,7 @@ import (
12 12
13 "github.com/bnema/vev/internal/adapters/observability"13 "github.com/bnema/vev/internal/adapters/observability"
14 "github.com/bnema/vev/internal/ports"14 "github.com/bnema/vev/internal/ports"
15+ "github.com/bnema/vev/internal/protocol/wire"
15 )16 )
16 17
17 func TestTransportObservabilityIPCEOFAndCloseEndFailedSpans(t *testing.T) {18 func TestTransportObservabilityIPCEOFAndCloseEndFailedSpans(t *testing.T) {
@@ -55,7 +56,7 @@ func TestTransportObservabilityIPCEOFAndCloseEndFailedSpans(t *testing.T) {
55 left, right := net.Pipe()56 left, right := net.Pipe()
56 transport := NewTransport(left, WithRuntimeObserver(reporter))57 transport := NewTransport(left, WithRuntimeObserver(reporter))
57 _ = right.Close()58 _ = right.Close()
58- if err := transport.Send(ports.Frame{Type: ports.MsgOutput, Payload: []byte("failed")}); err == nil {59+ if err := transport.Send(wire.Frame{Type: wire.MsgOutput, Payload: []byte("failed")}); err == nil {
59 t.Fatal("Send() error = nil after peer close")60 t.Fatal("Send() error = nil after peer close")
60 }61 }
61 _ = transport.Close()62 _ = transport.Close()
@@ -152,7 +153,7 @@ func TestTransportObservabilityIPCMarksCarriageWithoutChangingBytes(t *testing.T
152 defer func() { _ = right.Close() }()153 defer func() { _ = right.Close() }()
153 client := NewTransport(left, WithRuntimeObserver(reporter))154 client := NewTransport(left, WithRuntimeObserver(reporter))
154 server := NewTransport(right, WithRuntimeObserver(reporter))155 server := NewTransport(right, WithRuntimeObserver(reporter))
155- want := ports.Frame{Type: ports.MsgOutput, Payload: []byte("wire bytes stay exact")}156+ want := wire.Frame{Type: wire.MsgOutput, Payload: []byte("wire bytes stay exact")}
156 157
157 var wg sync.WaitGroup158 var wg sync.WaitGroup
158 wg.Go(func() {159 wg.Go(func() {
M internal/adapters/ipc/transport_replay_test.go +3 -3
@@ -5,12 +5,12 @@ import (
5 "sync"5 "sync"
6 "testing"6 "testing"
7 7
8- "github.com/bnema/vev/internal/ports"8+ "github.com/bnema/vev/internal/protocol/wire"
9 "github.com/bnema/vev/internal/testutil/replaytest"9 "github.com/bnema/vev/internal/testutil/replaytest"
10 )10 )
11 11
12 func TestTransportReplay(t *testing.T) {12 func TestTransportReplay(t *testing.T) {
13- replaytest.Run(t, func(t *testing.T, frames []ports.Frame) []ports.Frame {13+ replaytest.Run(t, func(t *testing.T, frames []wire.Frame) []wire.Frame {
14 left, right := net.Pipe()14 left, right := net.Pipe()
15 t.Cleanup(func() { _ = left.Close(); _ = right.Close() })15 t.Cleanup(func() { _ = left.Close(); _ = right.Close() })
16 sender, receiver := NewTransport(left), NewTransport(right)16 sender, receiver := NewTransport(left), NewTransport(right)
@@ -24,7 +24,7 @@ func TestTransportReplay(t *testing.T) {
24 }24 }
25 }25 }
26 })26 })
27- got := make([]ports.Frame, 0, len(frames))27+ got := make([]wire.Frame, 0, len(frames))
28 for range frames {28 for range frames {
29 frame, err := receiver.Recv()29 frame, err := receiver.Recv()
30 if err != nil {30 if err != nil {
M internal/adapters/ipc/transport_test.go +27 -26
@@ -14,6 +14,7 @@ import (
14 "github.com/bnema/vev/internal/domain"14 "github.com/bnema/vev/internal/domain"
15 "github.com/bnema/vev/internal/ports"15 "github.com/bnema/vev/internal/ports"
16 "github.com/bnema/vev/internal/protocol"16 "github.com/bnema/vev/internal/protocol"
17+ "github.com/bnema/vev/internal/protocol/wire"
17 )18 )
18 19
19 func TestTransportSendRecvBothDirections(t *testing.T) {20 func TestTransportSendRecvBothDirections(t *testing.T) {
@@ -24,8 +25,8 @@ func TestTransportSendRecvBothDirections(t *testing.T) {
24 client := NewTransport(c1)25 client := NewTransport(c1)
25 server := NewTransport(c2)26 server := NewTransport(c2)
26 27
27- clientToServer := ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(protocol.Hello{Version: 1, Intent: protocol.IntentNew, Name: "w0"})}28+ clientToServer := wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(protocol.Hello{Version: 1, Intent: protocol.IntentNew, Name: "w0"})}
28- serverToClient := ports.Frame{Type: ports.MsgWelcome, Payload: ports.MarshalWelcome(protocol.Welcome{SessionID: "s1", SessionName: "main"})}29+ serverToClient := wire.Frame{Type: wire.MsgWelcome, Payload: wire.MarshalWelcome(protocol.Welcome{SessionID: "s1", SessionName: "main"})}
29 30
30 var wg sync.WaitGroup31 var wg sync.WaitGroup
31 wg.Go(func() {32 wg.Go(func() {
@@ -97,12 +98,12 @@ func TestTransportLargeFrameNearCap(t *testing.T) {
97 server := NewTransport(c2)98 server := NewTransport(c2)
98 99
99 // Largest payload whose frame length (1 + len(payload)) still fits100 // Largest payload whose frame length (1 + len(payload)) still fits
100- // exactly at ports.MaxFrameLen.101+ // exactly at wire.MaxFrameLen.
101- payload := make([]byte, ports.MaxFrameLen-1)102+ payload := make([]byte, wire.MaxFrameLen-1)
102 for i := range payload {103 for i := range payload {
103 payload[i] = byte(i)104 payload[i] = byte(i)
104 }105 }
105- want := ports.Frame{Type: ports.MsgOutput, Payload: payload}106+ want := wire.Frame{Type: wire.MsgOutput, Payload: payload}
106 107
107 var wg sync.WaitGroup108 var wg sync.WaitGroup
108 wg.Go(func() {109 wg.Go(func() {
@@ -135,13 +136,13 @@ func TestTransportManySmallFramesBackToBack(t *testing.T) {
135 server := NewTransport(c2)136 server := NewTransport(c2)
136 137
137 const count = 200138 const count = 200
138- frames := make([]ports.Frame, count)139+ frames := make([]wire.Frame, count)
139 for i := range frames {140 for i := range frames {
140- frames[i] = ports.Frame{Type: ports.MsgPing, Payload: nil}141+ frames[i] = wire.Frame{Type: wire.MsgPing, Payload: nil}
141 }142 }
142 // Vary a couple to prove ordering and content are both preserved.143 // Vary a couple to prove ordering and content are both preserved.
143- frames[7] = ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(protocol.Input{Data: []byte("hop")})}144+ frames[7] = wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(protocol.Input{Data: []byte("hop")})}
144- frames[150] = ports.Frame{Type: ports.MsgResize, Payload: mustMarshalResize(protocol.Resize{Size: domain.Size{Cols: 5, Rows: 6}})}145+ frames[150] = wire.Frame{Type: wire.MsgResize, Payload: mustMarshalResize(protocol.Resize{Size: domain.Size{Cols: 5, Rows: 6}})}
145 146
146 var wg sync.WaitGroup147 var wg sync.WaitGroup
147 wg.Go(func() {148 wg.Go(func() {
@@ -178,7 +179,7 @@ func TestTransportRecvOversizeFrame(t *testing.T) {
178 179
179 go func() {180 go func() {
180 var hdr [4]byte181 var hdr [4]byte
181- binary.BigEndian.PutUint32(hdr[:], ports.MaxFrameLen+1)182+ binary.BigEndian.PutUint32(hdr[:], wire.MaxFrameLen+1)
182 _, _ = c1.Write(hdr[:])183 _, _ = c1.Write(hdr[:])
183 }()184 }()
184 185
@@ -228,8 +229,8 @@ func TestTransportSendOversizePayloadRejected(t *testing.T) {
228 defer func() { _ = c2.Close() }()229 defer func() { _ = c2.Close() }()
229 230
230 client := NewTransport(c1)231 client := NewTransport(c1)
231- payload := make([]byte, ports.MaxFrameLen) // +1 byte type => exceeds max232+ payload := make([]byte, wire.MaxFrameLen) // +1 byte type => exceeds max
232- err := client.Send(ports.Frame{Type: ports.MsgOutput, Payload: payload})233+ err := client.Send(wire.Frame{Type: wire.MsgOutput, Payload: payload})
233 if !errors.Is(err, ErrFrameTooLarge) {234 if !errors.Is(err, ErrFrameTooLarge) {
234 t.Fatalf("client.Send() error = %v, want ErrFrameTooLarge", err)235 t.Fatalf("client.Send() error = %v, want ErrFrameTooLarge", err)
235 }236 }
@@ -257,7 +258,7 @@ func TestTransportAsyncEgressPreservesWelcomeBeforeOutput(t *testing.T) {
257 t.Fatal("IPC transport does not implement AsyncTransport")258 t.Fatal("IPC transport does not implement AsyncTransport")
258 }259 }
259 260
260- welcome := ports.Frame{Type: ports.MsgWelcome, Payload: []byte("welcome")}261+ welcome := wire.Frame{Type: wire.MsgWelcome, Payload: []byte("welcome")}
261 sent := make(chan error, 1)262 sent := make(chan error, 1)
262 go func() { sent <- server.Send(welcome) }()263 go func() { sent <- server.Send(welcome) }()
263 got, err := client.Recv()264 got, err := client.Recv()
@@ -277,16 +278,16 @@ func TestTransportAsyncEgressPreservesWelcomeBeforeOutput(t *testing.T) {
277 }278 }
278 279
279 payload := []byte("first")280 payload := []byte("first")
280- if err := async.SendAsync(ports.Frame{Type: ports.MsgOutput, Payload: payload}); err != nil {281+ if err := async.SendAsync(wire.Frame{Type: wire.MsgOutput, Payload: payload}); err != nil {
281 t.Fatalf("SendAsync first output: %v", err)282 t.Fatalf("SendAsync first output: %v", err)
282 }283 }
283 payload[0] = 'X' // async ownership must not retain caller memory.284 payload[0] = 'X' // async ownership must not retain caller memory.
284- if err := async.SendAsync(ports.Frame{Type: ports.MsgOutput, Payload: []byte("second")}); err != nil {285+ if err := async.SendAsync(wire.Frame{Type: wire.MsgOutput, Payload: []byte("second")}); err != nil {
285 t.Fatalf("SendAsync second output: %v", err)286 t.Fatalf("SendAsync second output: %v", err)
286 }287 }
287- for _, want := range []ports.Frame{288+ for _, want := range []wire.Frame{
288- {Type: ports.MsgOutput, Payload: []byte("first")},289+ {Type: wire.MsgOutput, Payload: []byte("first")},
289- {Type: ports.MsgOutput, Payload: []byte("second")},290+ {Type: wire.MsgOutput, Payload: []byte("second")},
290 } {291 } {
291 got, err := client.Recv()292 got, err := client.Recv()
292 if err != nil {293 if err != nil {
@@ -322,7 +323,7 @@ func TestTransportSendWaitsForEgressCapacity(t *testing.T) {
322 }323 }
323 324
324 queued := fillAsyncEgress(t, transport, async, timeout)325 queued := fillAsyncEgress(t, transport, async, timeout)
325- want := ports.Frame{Type: ports.MsgOutput, Payload: []byte("synchronous")}326+ want := wire.Frame{Type: wire.MsgOutput, Payload: []byte("synchronous")}
326 sent := make(chan error, 1)327 sent := make(chan error, 1)
327 go func() { sent <- server.Send(want) }()328 go func() { sent <- server.Send(want) }()
328 waitForEgressSender(t, transport, timeout)329 waitForEgressSender(t, transport, timeout)
@@ -371,7 +372,7 @@ func TestTransportCloseInterruptsSendWaitingForEgressCapacity(t *testing.T) {
371 fillAsyncEgress(t, transport, async, timeout)372 fillAsyncEgress(t, transport, async, timeout)
372 373
373 sent := make(chan error, 1)374 sent := make(chan error, 1)
374- go func() { sent <- server.Send(ports.Frame{Type: ports.MsgOutput, Payload: []byte("synchronous")}) }()375+ go func() { sent <- server.Send(wire.Frame{Type: wire.MsgOutput, Payload: []byte("synchronous")}) }()
375 waitForEgressSender(t, transport, timeout)376 waitForEgressSender(t, transport, timeout)
376 377
377 closeTransport(t, server)378 closeTransport(t, server)
@@ -421,18 +422,18 @@ func closeTransport(t *testing.T, transport ports.Transport) {
421 }422 }
422 }423 }
423 424
424-func fillAsyncEgress(t *testing.T, transport *unixTransport, async ports.AsyncTransport, timeout time.Duration) []ports.Frame {425+func fillAsyncEgress(t *testing.T, transport *unixTransport, async ports.AsyncTransport, timeout time.Duration) []wire.Frame {
425 t.Helper()426 t.Helper()
426 427
427- first := ports.Frame{Type: ports.MsgOutput, Payload: []byte{0}}428+ first := wire.Frame{Type: wire.MsgOutput, Payload: []byte{0}}
428 if err := async.SendAsync(first); err != nil {429 if err := async.SendAsync(first); err != nil {
429 t.Fatalf("SendAsync blocked frame: %v", err)430 t.Fatalf("SendAsync blocked frame: %v", err)
430 }431 }
431 waitForEgressDequeue(t, transport, timeout)432 waitForEgressDequeue(t, transport, timeout)
432 433
433- queued := []ports.Frame{first}434+ queued := []wire.Frame{first}
434 for i := 1; ; i++ {435 for i := 1; ; i++ {
435- frame := ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(i)}}436+ frame := wire.Frame{Type: wire.MsgOutput, Payload: []byte{byte(i)}}
436 err := async.SendAsync(frame)437 err := async.SendAsync(frame)
437 switch {438 switch {
438 case err == nil:439 case err == nil:
@@ -479,12 +480,12 @@ func TestTransportAsyncEgressIsBoundedAndCloseInterruptsWorkers(t *testing.T) {
479 }480 }
480 481
481 // The first write blocks in net.Pipe because the peer never drains it.482 // The first write blocks in net.Pipe because the peer never drains it.
482- if err := async.SendAsync(ports.Frame{Type: ports.MsgOutput, Payload: []byte("blocked")}); err != nil {483+ if err := async.SendAsync(wire.Frame{Type: wire.MsgOutput, Payload: []byte("blocked")}); err != nil {
483 t.Fatalf("SendAsync blocked frame: %v", err)484 t.Fatalf("SendAsync blocked frame: %v", err)
484 }485 }
485 var backpressure error486 var backpressure error
486 for range sendQueueCapacity + 2 {487 for range sendQueueCapacity + 2 {
487- err := async.SendAsync(ports.Frame{Type: ports.MsgOutput, Payload: []byte("queued")})488+ err := async.SendAsync(wire.Frame{Type: wire.MsgOutput, Payload: []byte("queued")})
488 if errors.Is(err, ErrBackpressure) {489 if errors.Is(err, ErrBackpressure) {
489 backpressure = err490 backpressure = err
490 break491 break
M internal/adapters/ipc/wire_test_helpers_test.go +2 -2
@@ -1,12 +1,12 @@
1 package ipc1 package ipc
2 2
3 import (3 import (
4- "github.com/bnema/vev/internal/ports"
5 "github.com/bnema/vev/internal/protocol"4 "github.com/bnema/vev/internal/protocol"
5+ "github.com/bnema/vev/internal/protocol/wire"
6 )6 )
7 7
8 func mustMarshalResize(m protocol.Resize) []byte {8 func mustMarshalResize(m protocol.Resize) []byte {
9- payload, err := ports.MarshalResize(m)9+ payload, err := wire.MarshalResize(m)
10 if err != nil {10 if err != nil {
11 panic(err)11 panic(err)
12 }12 }
M internal/adapters/remote/preview.go +3 -2
@@ -12,6 +12,7 @@ import (
12 "github.com/bnema/vev/internal/domain"12 "github.com/bnema/vev/internal/domain"
13 "github.com/bnema/vev/internal/ports"13 "github.com/bnema/vev/internal/ports"
14 "github.com/bnema/vev/internal/protocol"14 "github.com/bnema/vev/internal/protocol"
15+ "github.com/bnema/vev/internal/protocol/wire"
15 )16 )
16 17
17 const (18 const (
@@ -42,7 +43,7 @@ func NewPreviewClient() ports.RemotePreviewClient {
42 43
43 func (c *PreviewClient) Preview(ctx context.Context, target domain.RemoteSessionTarget, width, height uint16) (protocol.RemotePreview, error) {44 func (c *PreviewClient) Preview(ctx context.Context, target domain.RemoteSessionTarget, width, height uint16) (protocol.RemotePreview, error) {
44 request := protocol.RemotePreviewRequest{Version: protocol.RemotePreviewSchemaVersion, Target: target, Width: width, Height: height}45 request := protocol.RemotePreviewRequest{Version: protocol.RemotePreviewSchemaVersion, Target: target, Width: width, Height: height}
45- payload := ports.MarshalRemotePreviewRequest(request)46+ payload := wire.MarshalRemotePreviewRequest(request)
46 if payload == nil {47 if payload == nil {
47 return protocol.RemotePreview{}, protocol.ErrInvalidRemotePreviewRequest48 return protocol.RemotePreview{}, protocol.ErrInvalidRemotePreviewRequest
48 }49 }
@@ -78,7 +79,7 @@ func (c *PreviewClient) Preview(ctx context.Context, target domain.RemoteSession
78 if stdout.overflow || stderr.overflow {79 if stdout.overflow || stderr.overflow {
79 return protocol.RemotePreview{}, errRemotePreviewTooLarge80 return protocol.RemotePreview{}, errRemotePreviewTooLarge
80 }81 }
81- preview, err := ports.UnmarshalRemotePreview(stdout.Bytes())82+ preview, err := wire.UnmarshalRemotePreview(stdout.Bytes())
82 if err != nil {83 if err != nil {
83 return protocol.RemotePreview{}, err84 return protocol.RemotePreview{}, err
84 }85 }
M internal/adapters/remote/preview_test.go +4 -4
@@ -15,8 +15,8 @@ import (
15 renderer "github.com/bnema/vev-vt"15 renderer "github.com/bnema/vev-vt"
16 "github.com/bnema/vev/internal/adapters/sshstdio"16 "github.com/bnema/vev/internal/adapters/sshstdio"
17 "github.com/bnema/vev/internal/domain"17 "github.com/bnema/vev/internal/domain"
18- "github.com/bnema/vev/internal/ports"
19 "github.com/bnema/vev/internal/protocol"18 "github.com/bnema/vev/internal/protocol"
19+ "github.com/bnema/vev/internal/protocol/wire"
20 )20 )
21 21
22 func previewClientTargetForTest() domain.RemoteSessionTarget {22 func previewClientTargetForTest() domain.RemoteSessionTarget {
@@ -37,7 +37,7 @@ func TestRemotePreviewClientBuildsExactSSHCommandAndDecodesResponse(t *testing.T
37 LifecycleID: lifecycle, TabID: target.LiveTabID, Revision: 7, Width: 1, Height: 1,37 LifecycleID: lifecycle, TabID: target.LiveTabID, Revision: 7, Width: 1, Height: 1,
38 Cells: []renderer.Cell{{Rune: 'x', Style: renderer.DefaultStyle()}},38 Cells: []renderer.Cell{{Rune: 'x', Style: renderer.DefaultStyle()}},
39 }39 }
40- payload := ports.MarshalRemotePreview(want)40+ payload := wire.MarshalRemotePreview(want)
41 require.NotNil(t, payload)41 require.NotNil(t, payload)
42 42
43 var gotPath string43 var gotPath string
@@ -57,7 +57,7 @@ func TestRemotePreviewClientBuildsExactSSHCommandAndDecodesResponse(t *testing.T
57 if err != nil {57 if err != nil {
58 t.Fatalf("decode request: %v", err)58 t.Fatalf("decode request: %v", err)
59 }59 }
60- request, err := ports.UnmarshalRemotePreviewRequest(requestPayload)60+ request, err := wire.UnmarshalRemotePreviewRequest(requestPayload)
61 if err != nil {61 if err != nil {
62 t.Fatalf("decode request payload: %v", err)62 t.Fatalf("decode request payload: %v", err)
63 }63 }
@@ -96,7 +96,7 @@ func TestRemotePreviewClientRejectsMismatchedResponseIdentity(t *testing.T) {
96 t.Run(tt.name, func(t *testing.T) {96 t.Run(tt.name, func(t *testing.T) {
97 preview := valid97 preview := valid
98 tt.mutate(&preview)98 tt.mutate(&preview)
99- payload := ports.MarshalRemotePreview(preview)99+ payload := wire.MarshalRemotePreview(preview)
100 require.NotNil(t, payload)100 require.NotNil(t, payload)
101 101
102 client := &PreviewClient{command: func(ctx context.Context, _ string, _ ...string) *exec.Cmd {102 client := &PreviewClient{command: func(ctx context.Context, _ string, _ ...string) *exec.Cmd {
M internal/adapters/sshstdio/transport.go +9 -8
@@ -19,10 +19,11 @@ import (
19 "time"19 "time"
20 20
21 "github.com/bnema/vev/internal/ports"21 "github.com/bnema/vev/internal/ports"
22+ "github.com/bnema/vev/internal/protocol/wire"
22 )23 )
23 24
24 const (25 const (
25- maxFrameLen = ports.MaxFrameLen26+ maxFrameLen = wire.MaxFrameLen
26 frameHeaderLen = 427 frameHeaderLen = 4
27 sshCloseTimeout = 3 * time.Second28 sshCloseTimeout = 3 * time.Second
28 )29 )
@@ -260,7 +261,7 @@ func (r processStdinReader) Read(dst []byte) (int, error) {
260 return r.pump.read(r.done, dst)261 return r.pump.read(r.done, dst)
261 }262 }
262 263
263-func (t *transport) Send(f ports.Frame) error {264+func (t *transport) Send(f wire.Frame) error {
264 end := t.beginOperation(ports.RuntimeAdapterSendStart, uint64(len(f.Payload)))265 end := t.beginOperation(ports.RuntimeAdapterSendStart, uint64(len(f.Payload)))
265 n := 1 + len(f.Payload)266 n := 1 + len(f.Payload)
266 if n > maxFrameLen {267 if n > maxFrameLen {
@@ -280,23 +281,23 @@ func (t *transport) Send(f ports.Frame) error {
280 return err281 return err
281 }282 }
282 283
283-func (t *transport) Recv() (ports.Frame, error) {284+func (t *transport) Recv() (wire.Frame, error) {
284 end := t.beginOperation(ports.RuntimeAdapterReceiveStart, 0)285 end := t.beginOperation(ports.RuntimeAdapterReceiveStart, 0)
285 var hdr [frameHeaderLen]byte286 var hdr [frameHeaderLen]byte
286 if _, err := io.ReadFull(t.r, hdr[:]); err != nil {287 if _, err := io.ReadFull(t.r, hdr[:]); err != nil {
287 err = t.mapEOFError(err)288 err = t.mapEOFError(err)
288 end(false)289 end(false)
289- return ports.Frame{}, err290+ return wire.Frame{}, err
290 }291 }
291 292
292 n := binary.BigEndian.Uint32(hdr[:])293 n := binary.BigEndian.Uint32(hdr[:])
293 if n == 0 {294 if n == 0 {
294 end(false)295 end(false)
295- return ports.Frame{}, ErrZeroLengthFrame296+ return wire.Frame{}, ErrZeroLengthFrame
296 }297 }
297 if n > maxFrameLen {298 if n > maxFrameLen {
298 end(false)299 end(false)
299- return ports.Frame{}, ErrFrameTooLarge300+ return wire.Frame{}, ErrFrameTooLarge
300 }301 }
301 302
302 if cap(t.readBuf) < int(n) {303 if cap(t.readBuf) < int(n) {
@@ -307,12 +308,12 @@ func (t *transport) Recv() (ports.Frame, error) {
307 if _, err := io.ReadFull(t.r, t.readBuf); err != nil {308 if _, err := io.ReadFull(t.r, t.readBuf); err != nil {
308 err = t.mapEOFError(err)309 err = t.mapEOFError(err)
309 end(false)310 end(false)
310- return ports.Frame{}, err311+ return wire.Frame{}, err
311 }312 }
312 313
313 payload := append([]byte(nil), t.readBuf[1:]...)314 payload := append([]byte(nil), t.readBuf[1:]...)
314 end(true)315 end(true)
315- return ports.Frame{Type: ports.MsgType(t.readBuf[0]), Payload: payload}, nil316+ return wire.Frame{Type: wire.MsgType(t.readBuf[0]), Payload: payload}, nil
316 }317 }
317 318
318 func (t *transport) beginOperation(start ports.RuntimeMarkKind, bytes uint64) func(bool) {319 func (t *transport) beginOperation(start ports.RuntimeMarkKind, bytes uint64) func(bool) {
M internal/adapters/sshstdio/transport_observability_test.go +2 -1
@@ -13,6 +13,7 @@ import (
13 13
14 "github.com/bnema/vev/internal/adapters/observability"14 "github.com/bnema/vev/internal/adapters/observability"
15 "github.com/bnema/vev/internal/ports"15 "github.com/bnema/vev/internal/ports"
16+ "github.com/bnema/vev/internal/protocol/wire"
16 )17 )
17 18
18 func TestTransportObservabilitySSHStdioEOFEndsReceive(t *testing.T) {19 func TestTransportObservabilitySSHStdioEOFEndsReceive(t *testing.T) {
@@ -212,7 +213,7 @@ func TestTransportObservabilitySSHStdioPreservesCarriage(t *testing.T) {
212 reporter := ports.NewSerializedRuntimeObserver(observer, 64)213 reporter := ports.NewSerializedRuntimeObserver(observer, 64)
213 defer reporter.Close()214 defer reporter.Close()
214 215
215- frame := ports.Frame{Type: ports.MsgOutput, Payload: []byte("stdio bytes stay exact")}216+ frame := wire.Frame{Type: wire.MsgOutput, Payload: []byte("stdio bytes stay exact")}
216 var observed, baseline bytes.Buffer217 var observed, baseline bytes.Buffer
217 if err := NewTransport(bytes.NewReader(nil), &observed, nil, WithRuntimeObserver(reporter)).Send(frame); err != nil {218 if err := NewTransport(bytes.NewReader(nil), &observed, nil, WithRuntimeObserver(reporter)).Send(frame); err != nil {
218 t.Fatalf("observed Send() error = %v", err)219 t.Fatalf("observed Send() error = %v", err)
M internal/adapters/sshstdio/transport_replay_test.go +3 -3
@@ -5,12 +5,12 @@ import (
5 "sync"5 "sync"
6 "testing"6 "testing"
7 7
8- "github.com/bnema/vev/internal/ports"8+ "github.com/bnema/vev/internal/protocol/wire"
9 "github.com/bnema/vev/internal/testutil/replaytest"9 "github.com/bnema/vev/internal/testutil/replaytest"
10 )10 )
11 11
12 func TestTransportReplay(t *testing.T) {12 func TestTransportReplay(t *testing.T) {
13- replaytest.Run(t, func(t *testing.T, frames []ports.Frame) []ports.Frame {13+ replaytest.Run(t, func(t *testing.T, frames []wire.Frame) []wire.Frame {
14 clientRead, serverWrite := io.Pipe()14 clientRead, serverWrite := io.Pipe()
15 serverRead, clientWrite := io.Pipe()15 serverRead, clientWrite := io.Pipe()
16 sender := NewTransport(clientRead, clientWrite, func() error { return clientWrite.Close() })16 sender := NewTransport(clientRead, clientWrite, func() error { return clientWrite.Close() })
@@ -26,7 +26,7 @@ func TestTransportReplay(t *testing.T) {
26 }26 }
27 }27 }
28 })28 })
29- got := make([]ports.Frame, 0, len(frames))29+ got := make([]wire.Frame, 0, len(frames))
30 for range frames {30 for range frames {
31 frame, err := receiver.Recv()31 frame, err := receiver.Recv()
32 if err != nil {32 if err != nil {
M internal/adapters/sshstdio/transport_test.go +15 -15
@@ -14,8 +14,8 @@ import (
14 "time"14 "time"
15 15
16 "github.com/bnema/vev/internal/domain"16 "github.com/bnema/vev/internal/domain"
17- "github.com/bnema/vev/internal/ports"
18 "github.com/bnema/vev/internal/protocol"17 "github.com/bnema/vev/internal/protocol"
18+ "github.com/bnema/vev/internal/protocol/wire"
19 )19 )
20 20
21 func TestBuildCommandUsesExecArgs(t *testing.T) {21 func TestBuildCommandUsesExecArgs(t *testing.T) {
@@ -114,7 +114,7 @@ func TestCloseInterruptsBlockedSend(t *testing.T) {
114 transport := NewTransport(nil, writer, nil)114 transport := NewTransport(nil, writer, nil)
115 115
116 sendDone := make(chan error, 1)116 sendDone := make(chan error, 1)
117- go func() { sendDone <- transport.Send(ports.Frame{Type: ports.MsgOutput, Payload: []byte("blocked")}) }()117+ go func() { sendDone <- transport.Send(wire.Frame{Type: wire.MsgOutput, Payload: []byte("blocked")}) }()
118 <-writer.started118 <-writer.started
119 if err := transport.Close(); err != nil {119 if err := transport.Close(); err != nil {
120 t.Fatalf("Close() error = %v", err)120 t.Fatalf("Close() error = %v", err)
@@ -166,7 +166,7 @@ func TestTransportRoundTripAndVersionMismatchFrame(t *testing.T) {
166 t.Errorf("server Recv: %v", err)166 t.Errorf("server Recv: %v", err)
167 return167 return
168 }168 }
169- got, err := ports.UnmarshalHello(f.Payload)169+ got, err := wire.UnmarshalHello(f.Payload)
170 if err != nil {170 if err != nil {
171 t.Errorf("UnmarshalHello: %v", err)171 t.Errorf("UnmarshalHello: %v", err)
172 return172 return
@@ -174,20 +174,20 @@ func TestTransportRoundTripAndVersionMismatchFrame(t *testing.T) {
174 if got.Version == protocol.Version {174 if got.Version == protocol.Version {
175 t.Errorf("test did not send a mismatched version")175 t.Errorf("test did not send a mismatched version")
176 }176 }
177- _ = server.Send(ports.Frame{Type: ports.MsgError, Payload: ports.MarshalErrorMsg(protocol.ErrorMsg{Code: protocol.ErrVersionMismatch, Text: "protocol version mismatch"})})177+ _ = server.Send(wire.Frame{Type: wire.MsgError, Payload: wire.MarshalErrorMsg(protocol.ErrorMsg{Code: protocol.ErrVersionMismatch, Text: "protocol version mismatch"})})
178 }()178 }()
179 179
180- if err := client.Send(ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(hello)}); err != nil {180+ if err := client.Send(wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(hello)}); err != nil {
181 t.Fatalf("client Send: %v", err)181 t.Fatalf("client Send: %v", err)
182 }182 }
183 reply, err := client.Recv()183 reply, err := client.Recv()
184 if err != nil {184 if err != nil {
185 t.Fatalf("client Recv: %v", err)185 t.Fatalf("client Recv: %v", err)
186 }186 }
187- if reply.Type != ports.MsgError {187+ if reply.Type != wire.MsgError {
188 t.Fatalf("reply type = %d, want MsgError", reply.Type)188 t.Fatalf("reply type = %d, want MsgError", reply.Type)
189 }189 }
190- em, err := ports.UnmarshalErrorMsg(reply.Payload)190+ em, err := wire.UnmarshalErrorMsg(reply.Payload)
191 if err != nil {191 if err != nil {
192 t.Fatalf("UnmarshalErrorMsg: %v", err)192 t.Fatalf("UnmarshalErrorMsg: %v", err)
193 }193 }
@@ -205,35 +205,35 @@ func TestTransportRejectsZeroLengthFrame(t *testing.T) {
205 }205 }
206 206
207 func TestTransportUsesCanonicalFrameMaximum(t *testing.T) {207 func TestTransportUsesCanonicalFrameMaximum(t *testing.T) {
208- boundaryPayload := make([]byte, ports.MaxFrameLen-1)208+ boundaryPayload := make([]byte, wire.MaxFrameLen-1)
209 boundaryWire := &bytes.Buffer{}209 boundaryWire := &bytes.Buffer{}
210 boundarySend := NewTransport(nil, boundaryWire, nil)210 boundarySend := NewTransport(nil, boundaryWire, nil)
211- if err := boundarySend.Send(ports.Frame{Type: ports.MsgOutput, Payload: boundaryPayload}); err != nil {211+ if err := boundarySend.Send(wire.Frame{Type: wire.MsgOutput, Payload: boundaryPayload}); err != nil {
212 t.Fatalf("boundary Send error = %v", err)212 t.Fatalf("boundary Send error = %v", err)
213 }213 }
214- if got := binary.BigEndian.Uint32(boundaryWire.Bytes()[:frameHeaderLen]); got != ports.MaxFrameLen {214+ if got := binary.BigEndian.Uint32(boundaryWire.Bytes()[:frameHeaderLen]); got != wire.MaxFrameLen {
215- t.Fatalf("boundary frame length = %d, want %d", got, ports.MaxFrameLen)215+ t.Fatalf("boundary frame length = %d, want %d", got, wire.MaxFrameLen)
216 }216 }
217 boundaryRecv := NewTransport(bytes.NewReader(boundaryWire.Bytes()), io.Discard, nil)217 boundaryRecv := NewTransport(bytes.NewReader(boundaryWire.Bytes()), io.Discard, nil)
218 boundaryFrame, err := boundaryRecv.Recv()218 boundaryFrame, err := boundaryRecv.Recv()
219 if err != nil {219 if err != nil {
220 t.Fatalf("boundary Recv error = %v", err)220 t.Fatalf("boundary Recv error = %v", err)
221 }221 }
222- if boundaryFrame.Type != ports.MsgOutput || len(boundaryFrame.Payload) != len(boundaryPayload) {222+ if boundaryFrame.Type != wire.MsgOutput || len(boundaryFrame.Payload) != len(boundaryPayload) {
223- t.Fatalf("boundary frame = type %d, payload %d bytes; want type %d, payload %d bytes", boundaryFrame.Type, len(boundaryFrame.Payload), ports.MsgOutput, len(boundaryPayload))223+ t.Fatalf("boundary frame = type %d, payload %d bytes; want type %d, payload %d bytes", boundaryFrame.Type, len(boundaryFrame.Payload), wire.MsgOutput, len(boundaryPayload))
224 }224 }
225 if !bytes.Equal(boundaryFrame.Payload, boundaryPayload) {225 if !bytes.Equal(boundaryFrame.Payload, boundaryPayload) {
226 t.Fatal("boundary payload was corrupted")226 t.Fatal("boundary payload was corrupted")
227 }227 }
228 228
229 send := NewTransport(nil, io.Discard, nil)229 send := NewTransport(nil, io.Discard, nil)
230- err = send.Send(ports.Frame{Type: ports.MsgOutput, Payload: make([]byte, ports.MaxFrameLen)})230+ err = send.Send(wire.Frame{Type: wire.MsgOutput, Payload: make([]byte, wire.MaxFrameLen)})
231 if !errors.Is(err, ErrFrameTooLarge) {231 if !errors.Is(err, ErrFrameTooLarge) {
232 t.Fatalf("Send error = %v, want ErrFrameTooLarge", err)232 t.Fatalf("Send error = %v, want ErrFrameTooLarge", err)
233 }233 }
234 234
235 var header [frameHeaderLen]byte235 var header [frameHeaderLen]byte
236- binary.BigEndian.PutUint32(header[:], ports.MaxFrameLen+1)236+ binary.BigEndian.PutUint32(header[:], wire.MaxFrameLen+1)
237 recv := NewTransport(bytes.NewReader(header[:]), io.Discard, nil)237 recv := NewTransport(bytes.NewReader(header[:]), io.Discard, nil)
238 _, err = recv.Recv()238 _, err = recv.Recv()
239 if !errors.Is(err, ErrFrameTooLarge) {239 if !errors.Is(err, ErrFrameTooLarge) {
M internal/app/cmd.go +6 -5
@@ -14,6 +14,7 @@ import (
14 "github.com/bnema/vev/internal/adapters/ipc"14 "github.com/bnema/vev/internal/adapters/ipc"
15 "github.com/bnema/vev/internal/ports"15 "github.com/bnema/vev/internal/ports"
16 "github.com/bnema/vev/internal/protocol"16 "github.com/bnema/vev/internal/protocol"
17+ "github.com/bnema/vev/internal/protocol/wire"
17 commandusecase "github.com/bnema/vev/internal/usecase/command"18 commandusecase "github.com/bnema/vev/internal/usecase/command"
18 "github.com/bnema/vev/internal/usecase/daemon"19 "github.com/bnema/vev/internal/usecase/daemon"
19 )20 )
@@ -170,7 +171,7 @@ func runCmdWithDeps(ctx context.Context, invocation cmdInvocation, deps cmdDeps)
170 return usagef("--self requires running inside a vev pane")171 return usagef("--self requires running inside a vev pane")
171 }172 }
172 173
173- if _, err := ports.MarshalCommandRequest(request); errors.Is(err, ports.ErrTooManyCommandArgs) {174+ if _, err := wire.MarshalCommandRequest(request); errors.Is(err, wire.ErrTooManyCommandArgs) {
174 return usagef("too many command arguments")175 return usagef("too many command arguments")
175 } else if err != nil {176 } else if err != nil {
176 return fmt.Errorf("encoding command: %w", err)177 return fmt.Errorf("encoding command: %w", err)
@@ -190,12 +191,12 @@ func runCmdWithDeps(ctx context.Context, invocation cmdInvocation, deps cmdDeps)
190 const generation = uint64(1)191 const generation = uint64(1)
191 requestID, outcome := tracker.Publish(generation)192 requestID, outcome := tracker.Publish(generation)
192 request.RequestID = requestID193 request.RequestID = requestID
193- payload, err := ports.MarshalCommandRequest(request)194+ payload, err := wire.MarshalCommandRequest(request)
194 if err != nil {195 if err != nil {
195 tracker.Remove(requestID, generation)196 tracker.Remove(requestID, generation)
196 return fmt.Errorf("encoding command: %w", err)197 return fmt.Errorf("encoding command: %w", err)
197 }198 }
198- if err := transport.Send(ports.Frame{Type: ports.MsgCommand, Payload: payload}); err != nil {199+ if err := transport.Send(wire.Frame{Type: wire.MsgCommand, Payload: payload}); err != nil {
199 tracker.Remove(requestID, generation)200 tracker.Remove(requestID, generation)
200 return &exitCoded{code: 3, err: fmt.Errorf("sending command: %w", err)}201 return &exitCoded{code: 3, err: fmt.Errorf("sending command: %w", err)}
201 }202 }
@@ -205,11 +206,11 @@ func runCmdWithDeps(ctx context.Context, invocation cmdInvocation, deps cmdDeps)
205 tracker.Fail(requestID, generation, fmt.Errorf("reading command reply: %w", recvErr))206 tracker.Fail(requestID, generation, fmt.Errorf("reading command reply: %w", recvErr))
206 return207 return
207 }208 }
208- if reply.Type != ports.MsgCommandResult {209+ if reply.Type != wire.MsgCommandResult {
209 tracker.Fail(requestID, generation, fmt.Errorf("unexpected command reply type %d", reply.Type))210 tracker.Fail(requestID, generation, fmt.Errorf("unexpected command reply type %d", reply.Type))
210 return211 return
211 }212 }
212- result, decodeErr := ports.UnmarshalCommandResult(reply.Payload)213+ result, decodeErr := wire.UnmarshalCommandResult(reply.Payload)
213 if decodeErr != nil {214 if decodeErr != nil {
214 tracker.Fail(requestID, generation, fmt.Errorf("decoding command reply: %w", decodeErr))215 tracker.Fail(requestID, generation, fmt.Errorf("decoding command reply: %w", decodeErr))
215 return216 return
M internal/app/cmd_test.go +21 -20
@@ -14,6 +14,7 @@ import (
14 "github.com/bnema/vev/internal/ports"14 "github.com/bnema/vev/internal/ports"
15 portsmocks "github.com/bnema/vev/internal/ports/mocks"15 portsmocks "github.com/bnema/vev/internal/ports/mocks"
16 "github.com/bnema/vev/internal/protocol"16 "github.com/bnema/vev/internal/protocol"
17+ "github.com/bnema/vev/internal/protocol/wire"
17 "github.com/bnema/vev/internal/usecase/daemon"18 "github.com/bnema/vev/internal/usecase/daemon"
18 "github.com/stretchr/testify/mock"19 "github.com/stretchr/testify/mock"
19 "github.com/stretchr/testify/require"20 "github.com/stretchr/testify/require"
@@ -141,14 +142,14 @@ func TestMoveCmdPreservesPositionalArguments(t *testing.T) {
141 t.Run(tt.slug, func(t *testing.T) {142 t.Run(tt.slug, func(t *testing.T) {
142 parsed, err := parseArgs(append([]string{"cmd", tt.slug}, tt.args...))143 parsed, err := parseArgs(append([]string{"cmd", tt.slug}, tt.args...))
143 require.NoError(t, err)144 require.NoError(t, err)
144- transport := &cmdTestTransport{recv: ports.Frame{Type: ports.MsgCommandResult, Payload: ports.MarshalCommandResult(protocol.CommandResult{OK: true})}}145+ transport := &cmdTestTransport{recv: wire.Frame{Type: wire.MsgCommandResult, Payload: wire.MarshalCommandResult(protocol.CommandResult{OK: true})}}
145 require.NoError(t, runCmdWithDeps(context.Background(), parsed.cmd, cmdDeps{146 require.NoError(t, runCmdWithDeps(context.Background(), parsed.cmd, cmdDeps{
146 stdout: io.Discard,147 stdout: io.Discard,
147 getenv: func(string) string { return "" },148 getenv: func(string) string { return "" },
148 dial: func(context.Context, string) (ports.Transport, error) { return transport, nil },149 dial: func(context.Context, string) (ports.Transport, error) { return transport, nil },
149 }))150 }))
150 require.Len(t, transport.sent, 1)151 require.Len(t, transport.sent, 1)
151- request, err := ports.UnmarshalCommandRequest(transport.sent[0].Payload)152+ request, err := wire.UnmarshalCommandRequest(transport.sent[0].Payload)
152 require.NoError(t, err)153 require.NoError(t, err)
153 require.Equal(t, tt.slug, request.Slug)154 require.Equal(t, tt.slug, request.Slug)
154 require.Equal(t, tt.args, request.Args)155 require.Equal(t, tt.args, request.Args)
@@ -158,7 +159,7 @@ func TestMoveCmdPreservesPositionalArguments(t *testing.T) {
158 }159 }
159 160
160 func TestRemoteCatalogCommandEnsuresDaemon(t *testing.T) {161 func TestRemoteCatalogCommandEnsuresDaemon(t *testing.T) {
161- transport := &cmdTestTransport{recv: ports.Frame{Type: ports.MsgCommandResult, Payload: ports.MarshalCommandResult(protocol.CommandResult{OK: true})}}162+ transport := &cmdTestTransport{recv: wire.Frame{Type: wire.MsgCommandResult, Payload: wire.MarshalCommandResult(protocol.CommandResult{OK: true})}}
162 ensureCalls := 0163 ensureCalls := 0
163 err := runCmdWithDeps(context.Background(), cmdInvocation{slug: "remote-catalog", jsonOut: true}, cmdDeps{164 err := runCmdWithDeps(context.Background(), cmdInvocation{slug: "remote-catalog", jsonOut: true}, cmdDeps{
164 stdout: io.Discard,165 stdout: io.Discard,
@@ -184,7 +185,7 @@ func TestMoveCmdInvalidArgumentResultExitsTwo(t *testing.T) {
184 {slug: "move-tab", args: []string{"work", "extra"}},185 {slug: "move-tab", args: []string{"work", "extra"}},
185 } {186 } {
186 t.Run(invocation.slug+"/"+strconv.Itoa(len(invocation.args)), func(t *testing.T) {187 t.Run(invocation.slug+"/"+strconv.Itoa(len(invocation.args)), func(t *testing.T) {
187- transport := &cmdTestTransport{recv: ports.Frame{Type: ports.MsgCommandResult, Payload: ports.MarshalCommandResult(protocol.CommandResult{188+ transport := &cmdTestTransport{recv: wire.Frame{Type: wire.MsgCommandResult, Payload: wire.MarshalCommandResult(protocol.CommandResult{
188 Code: protocol.ErrInvalidCommandArgs, Text: "invalid command arguments",189 Code: protocol.ErrInvalidCommandArgs, Text: "invalid command arguments",
189 })}}190 })}}
190 err := runCmdWithDeps(context.Background(), invocation, cmdDeps{191 err := runCmdWithDeps(context.Background(), invocation, cmdDeps{
@@ -207,14 +208,14 @@ func TestTargetPaneCmdsParseAndUseSelfTarget(t *testing.T) {
207 t.Run(slug, func(t *testing.T) {208 t.Run(slug, func(t *testing.T) {
208 invocation, err := parseArgs([]string{"cmd", "--self", slug})209 invocation, err := parseArgs([]string{"cmd", "--self", slug})
209 require.NoError(t, err)210 require.NoError(t, err)
210- transport := &cmdTestTransport{recv: ports.Frame{Type: ports.MsgCommandResult, Payload: ports.MarshalCommandResult(protocol.CommandResult{OK: true})}}211+ transport := &cmdTestTransport{recv: wire.Frame{Type: wire.MsgCommandResult, Payload: wire.MarshalCommandResult(protocol.CommandResult{OK: true})}}
211 require.NoError(t, runCmdWithDeps(context.Background(), invocation.cmd, cmdDeps{212 require.NoError(t, runCmdWithDeps(context.Background(), invocation.cmd, cmdDeps{
212 stdout: io.Discard,213 stdout: io.Discard,
213 getenv: func(string) string { return "session=work,tab=t_abc,pane=p_def" },214 getenv: func(string) string { return "session=work,tab=t_abc,pane=p_def" },
214 dial: func(context.Context, string) (ports.Transport, error) { return transport, nil },215 dial: func(context.Context, string) (ports.Transport, error) { return transport, nil },
215 }))216 }))
216 require.Len(t, transport.sent, 1)217 require.Len(t, transport.sent, 1)
217- request, err := ports.UnmarshalCommandRequest(transport.sent[0].Payload)218+ request, err := wire.UnmarshalCommandRequest(transport.sent[0].Payload)
218 require.NoError(t, err)219 require.NoError(t, err)
219 require.Equal(t, slug, request.Slug)220 require.Equal(t, slug, request.Slug)
220 require.True(t, request.Self)221 require.True(t, request.Self)
@@ -226,7 +227,7 @@ func TestTargetPaneCmdsParseAndUseSelfTarget(t *testing.T) {
226 }227 }
227 228
228 func TestRunCmdBuildsOneShotTargetedRequest(t *testing.T) {229 func TestRunCmdBuildsOneShotTargetedRequest(t *testing.T) {
229- transport := &cmdTestTransport{recv: ports.Frame{Type: ports.MsgCommandResult, Payload: ports.MarshalCommandResult(protocol.CommandResult{OK: true, Output: "done"})}}230+ transport := &cmdTestTransport{recv: wire.Frame{Type: wire.MsgCommandResult, Payload: wire.MarshalCommandResult(protocol.CommandResult{OK: true, Output: "done"})}}
230 out := new(strings.Builder)231 out := new(strings.Builder)
231 err := runCmdWithDeps(context.Background(), cmdInvocation{slug: "split-right", self: true}, cmdDeps{232 err := runCmdWithDeps(context.Background(), cmdInvocation{slug: "split-right", self: true}, cmdDeps{
232 stdout: out,233 stdout: out,
@@ -239,11 +240,11 @@ func TestRunCmdBuildsOneShotTargetedRequest(t *testing.T) {
239 if len(transport.sent) != 1 || transport.recvCalls != 1 || transport.closeCalls != 1 {240 if len(transport.sent) != 1 || transport.recvCalls != 1 || transport.closeCalls != 1 {
240 t.Fatalf("transport calls: sent=%d recv=%d close=%d", len(transport.sent), transport.recvCalls, transport.closeCalls)241 t.Fatalf("transport calls: sent=%d recv=%d close=%d", len(transport.sent), transport.recvCalls, transport.closeCalls)
241 }242 }
242- req, err := ports.UnmarshalCommandRequest(transport.sent[0].Payload)243+ req, err := wire.UnmarshalCommandRequest(transport.sent[0].Payload)
243 if err != nil {244 if err != nil {
244 t.Fatalf("decode request: %v", err)245 t.Fatalf("decode request: %v", err)
245 }246 }
246- if transport.sent[0].Type != ports.MsgCommand || req.Slug != "split-right" || !req.Self || req.TargetSession != "old" || req.TargetTab != "t_abc" || req.TargetPane != "p_def" {247+ if transport.sent[0].Type != wire.MsgCommand || req.Slug != "split-right" || !req.Self || req.TargetSession != "old" || req.TargetTab != "t_abc" || req.TargetPane != "p_def" {
247 t.Fatalf("request = type %d %+v", transport.sent[0].Type, req)248 t.Fatalf("request = type %d %+v", transport.sent[0].Type, req)
248 }249 }
249 if out.String() != "done\n" {250 if out.String() != "done\n" {
@@ -252,7 +253,7 @@ func TestRunCmdBuildsOneShotTargetedRequest(t *testing.T) {
252 }253 }
253 254
254 func TestRunCmdVEVWithoutSelfUsesIDsOnlyAsSessionLocator(t *testing.T) {255 func TestRunCmdVEVWithoutSelfUsesIDsOnlyAsSessionLocator(t *testing.T) {
255- transport := &cmdTestTransport{recv: ports.Frame{Type: ports.MsgCommandResult, Payload: ports.MarshalCommandResult(protocol.CommandResult{OK: true})}}256+ transport := &cmdTestTransport{recv: wire.Frame{Type: wire.MsgCommandResult, Payload: wire.MarshalCommandResult(protocol.CommandResult{OK: true})}}
256 err := runCmdWithDeps(context.Background(), cmdInvocation{slug: "split-right"}, cmdDeps{257 err := runCmdWithDeps(context.Background(), cmdInvocation{slug: "split-right"}, cmdDeps{
257 stdout: io.Discard,258 stdout: io.Discard,
258 getenv: func(string) string { return "session=old,tab=t_abc,pane=p_def" },259 getenv: func(string) string { return "session=old,tab=t_abc,pane=p_def" },
@@ -261,7 +262,7 @@ func TestRunCmdVEVWithoutSelfUsesIDsOnlyAsSessionLocator(t *testing.T) {
261 if err != nil {262 if err != nil {
262 t.Fatalf("runCmdWithDeps: %v", err)263 t.Fatalf("runCmdWithDeps: %v", err)
263 }264 }
264- req, err := ports.UnmarshalCommandRequest(transport.sent[0].Payload)265+ req, err := wire.UnmarshalCommandRequest(transport.sent[0].Payload)
265 if err != nil {266 if err != nil {
266 t.Fatalf("decode request: %v", err)267 t.Fatalf("decode request: %v", err)
267 }268 }
@@ -271,7 +272,7 @@ func TestRunCmdVEVWithoutSelfUsesIDsOnlyAsSessionLocator(t *testing.T) {
271 }272 }
272 273
273 func TestRunCmdExplicitSessionDoesNotUseEnvIDsWithoutSelf(t *testing.T) {274 func TestRunCmdExplicitSessionDoesNotUseEnvIDsWithoutSelf(t *testing.T) {
274- transport := &cmdTestTransport{recv: ports.Frame{Type: ports.MsgCommandResult, Payload: ports.MarshalCommandResult(protocol.CommandResult{OK: true})}}275+ transport := &cmdTestTransport{recv: wire.Frame{Type: wire.MsgCommandResult, Payload: wire.MarshalCommandResult(protocol.CommandResult{OK: true})}}
275 err := runCmdWithDeps(context.Background(), cmdInvocation{slug: "new-tab", session: "current"}, cmdDeps{276 err := runCmdWithDeps(context.Background(), cmdInvocation{slug: "new-tab", session: "current"}, cmdDeps{
276 stdout: io.Discard,277 stdout: io.Discard,
277 getenv: func(string) string { return "session=old,tab=t_abc,pane=p_def" },278 getenv: func(string) string { return "session=old,tab=t_abc,pane=p_def" },
@@ -280,7 +281,7 @@ func TestRunCmdExplicitSessionDoesNotUseEnvIDsWithoutSelf(t *testing.T) {
280 if err != nil {281 if err != nil {
281 t.Fatalf("runCmdWithDeps: %v", err)282 t.Fatalf("runCmdWithDeps: %v", err)
282 }283 }
283- req, err := ports.UnmarshalCommandRequest(transport.sent[0].Payload)284+ req, err := wire.UnmarshalCommandRequest(transport.sent[0].Payload)
284 if err != nil {285 if err != nil {
285 t.Fatalf("decode request: %v", err)286 t.Fatalf("decode request: %v", err)
286 }287 }
@@ -343,7 +344,7 @@ func TestRunCmdTimesOutPendingRequest(t *testing.T) {
343 if ExitCode(err) != 3 || !errors.Is(err, daemon.ErrCommandRequestTimeout) {344 if ExitCode(err) != 3 || !errors.Is(err, daemon.ErrCommandRequestTimeout) {
344 t.Fatalf("timeout error=%v code=%d", err, ExitCode(err))345 t.Fatalf("timeout error=%v code=%d", err, ExitCode(err))
345 }346 }
346- request, decodeErr := ports.UnmarshalCommandRequest(transport.sent[0].Payload)347+ request, decodeErr := wire.UnmarshalCommandRequest(transport.sent[0].Payload)
347 if decodeErr != nil || request.RequestID == 0 {348 if decodeErr != nil || request.RequestID == 0 {
348 t.Fatalf("request=%+v decode=%v, want non-zero request ID", request, decodeErr)349 t.Fatalf("request=%+v decode=%v, want non-zero request ID", request, decodeErr)
349 }350 }
@@ -351,7 +352,7 @@ func TestRunCmdTimesOutPendingRequest(t *testing.T) {
351 352
352 func TestRunCmdZeroRequestIDReplyReturnsDaemonError(t *testing.T) {353 func TestRunCmdZeroRequestIDReplyReturnsDaemonError(t *testing.T) {
353 const want = "daemon error"354 const want = "daemon error"
354- transport := &cmdTestTransport{recv: ports.Frame{Type: ports.MsgCommandResult, Payload: ports.MarshalCommandResult(protocol.CommandResult{355+ transport := &cmdTestTransport{recv: wire.Frame{Type: wire.MsgCommandResult, Payload: wire.MarshalCommandResult(protocol.CommandResult{
355 Code: protocol.ErrInternal, Text: want,356 Code: protocol.ErrInternal, Text: want,
356 })}}357 })}}
357 err := runCmdWithDeps(context.Background(), cmdInvocation{slug: "split-right"}, cmdDeps{358 err := runCmdWithDeps(context.Background(), cmdInvocation{slug: "split-right"}, cmdDeps{
@@ -415,7 +416,7 @@ func TestRunCmdClassifiesDaemonCommandErrors(t *testing.T) {
415 }416 }
416 for _, tt := range tests {417 for _, tt := range tests {
417 t.Run(tt.name, func(t *testing.T) {418 t.Run(tt.name, func(t *testing.T) {
418- transport := &cmdTestTransport{recv: ports.Frame{Type: ports.MsgCommandResult, Payload: ports.MarshalCommandResult(tt.result)}}419+ transport := &cmdTestTransport{recv: wire.Frame{Type: wire.MsgCommandResult, Payload: wire.MarshalCommandResult(tt.result)}}
419 err := runCmdWithDeps(context.Background(), cmdInvocation{slug: "split-right"}, cmdDeps{420 err := runCmdWithDeps(context.Background(), cmdInvocation{slug: "split-right"}, cmdDeps{
420 stdout: io.Discard,421 stdout: io.Discard,
421 getenv: func(string) string { return "" },422 getenv: func(string) string { return "" },
@@ -429,8 +430,8 @@ func TestRunCmdClassifiesDaemonCommandErrors(t *testing.T) {
429 }430 }
430 431
431 type cmdTestTransport struct {432 type cmdTestTransport struct {
432- sent []ports.Frame433+ sent []wire.Frame
433- recv ports.Frame434+ recv wire.Frame
434 recvErr error435 recvErr error
435 recvCalls int436 recvCalls int
436 closeCalls int437 closeCalls int
@@ -438,12 +439,12 @@ type cmdTestTransport struct {
438 recvGate <-chan struct{}439 recvGate <-chan struct{}
439 }440 }
440 441
441-func (t *cmdTestTransport) Send(frame ports.Frame) error {442+func (t *cmdTestTransport) Send(frame wire.Frame) error {
442 t.sent = append(t.sent, frame)443 t.sent = append(t.sent, frame)
443 return nil444 return nil
444 }445 }
445 446
446-func (t *cmdTestTransport) Recv() (ports.Frame, error) {447+func (t *cmdTestTransport) Recv() (wire.Frame, error) {
447 t.recvCalls++448 t.recvCalls++
448 if t.recvStarted != nil {449 if t.recvStarted != nil {
449 close(t.recvStarted)450 close(t.recvStarted)
M internal/app/daemon_integration_test.go +38 -37
@@ -29,6 +29,7 @@ import (
29 "github.com/bnema/vev/internal/persist"29 "github.com/bnema/vev/internal/persist"
30 "github.com/bnema/vev/internal/ports"30 "github.com/bnema/vev/internal/ports"
31 "github.com/bnema/vev/internal/protocol"31 "github.com/bnema/vev/internal/protocol"
32+ "github.com/bnema/vev/internal/protocol/wire"
32 "github.com/bnema/vev/internal/usecase/daemon"33 "github.com/bnema/vev/internal/usecase/daemon"
33 "github.com/bnema/vev/internal/usecase/recovery"34 "github.com/bnema/vev/internal/usecase/recovery"
34 )35 )
@@ -64,10 +65,10 @@ func startDaemonInDir(t *testing.T, dir string, opts ...daemon.Option) (string,
64 65
65 // pump wires a background Recv pump to a transport so tests can await frames66 // pump wires a background Recv pump to a transport so tests can await frames
66 // with a timeout (ports.Transport has no read deadline).67 // with a timeout (ports.Transport has no read deadline).
67-type pump struct{ ch chan ports.Frame }68+type pump struct{ ch chan wire.Frame }
68 69
69 func recvPump(tr ports.Transport) *pump {70 func recvPump(tr ports.Transport) *pump {
70- p := &pump{ch: make(chan ports.Frame, 128)}71+ p := &pump{ch: make(chan wire.Frame, 128)}
71 go func() {72 go func() {
72 for {73 for {
73 f, err := tr.Recv()74 f, err := tr.Recv()
@@ -92,12 +93,12 @@ func attachWithEnvironment(t *testing.T, dir string, intent uint8, name string,
92 tr, err := ipc.DialContext(context.Background(), dir)93 tr, err := ipc.DialContext(context.Background(), dir)
93 require.NoError(t, err)94 require.NoError(t, err)
94 hello := protocol.Hello{Version: protocol.Version, Intent: intent, Name: name, Size: sz, TermEnv: "xterm-256color", TrueColor: true, Env: env}95 hello := protocol.Hello{Version: protocol.Version, Intent: intent, Name: name, Size: sz, TermEnv: "xterm-256color", TrueColor: true, Env: env}
95- require.NoError(t, tr.Send(ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(hello)}))96+ require.NoError(t, tr.Send(wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(hello)}))
96 p := recvPump(tr)97 p := recvPump(tr)
97 select {98 select {
98 case f, ok := <-p.ch:99 case f, ok := <-p.ch:
99 require.True(t, ok, "connection closed before welcome")100 require.True(t, ok, "connection closed before welcome")
100- require.Equal(t, ports.MsgWelcome, f.Type)101+ require.Equal(t, wire.MsgWelcome, f.Type)
101 case <-time.After(3 * time.Second):102 case <-time.After(3 * time.Second):
102 t.Fatal("timed out waiting for welcome")103 t.Fatal("timed out waiting for welcome")
103 }104 }
@@ -109,11 +110,11 @@ func listRemoteSessions(t *testing.T, dir string) protocol.Sessions {
109 tr, err := ipc.DialContext(context.Background(), dir)110 tr, err := ipc.DialContext(context.Background(), dir)
110 require.NoError(t, err)111 require.NoError(t, err)
111 defer func() { _ = tr.Close() }()112 defer func() { _ = tr.Close() }()
112- require.NoError(t, tr.Send(ports.Frame{Type: ports.MsgList, Payload: ports.MarshalList(protocol.List{})}))113+ require.NoError(t, tr.Send(wire.Frame{Type: wire.MsgList, Payload: wire.MarshalList(protocol.List{})}))
113 f, err := tr.Recv()114 f, err := tr.Recv()
114 require.NoError(t, err)115 require.NoError(t, err)
115- require.Equal(t, ports.MsgSessions, f.Type)116+ require.Equal(t, wire.MsgSessions, f.Type)
116- sessions, err := ports.UnmarshalSessions(f.Payload)117+ sessions, err := wire.UnmarshalSessions(f.Payload)
117 require.NoError(t, err)118 require.NoError(t, err)
118 return sessions119 return sessions
119 }120 }
@@ -124,7 +125,7 @@ func killAll(dir string) error {
124 return err125 return err
125 }126 }
126 defer func() { _ = tr.Close() }()127 defer func() { _ = tr.Close() }()
127- if err := tr.Send(ports.Frame{Type: ports.MsgKill, Payload: ports.MarshalKill(protocol.Kill{All: true})}); err != nil {128+ if err := tr.Send(wire.Frame{Type: wire.MsgKill, Payload: wire.MarshalKill(protocol.Kill{All: true})}); err != nil {
128 return err129 return err
129 }130 }
130 _, err = tr.Recv()131 _, err = tr.Recv()
@@ -153,8 +154,8 @@ func awaitScreenText(t *testing.T, p *pump, sz domain.Size, want string) string
153 if !ok {154 if !ok {
154 t.Fatalf("connection closed before %q appeared; screen=%q", want, screenText(screen))155 t.Fatalf("connection closed before %q appeared; screen=%q", want, screenText(screen))
155 }156 }
156- if f.Type == ports.MsgOutput {157+ if f.Type == wire.MsgOutput {
157- o, err := ports.UnmarshalOutput(f.Payload)158+ o, err := wire.UnmarshalOutput(f.Payload)
158 require.NoError(t, err)159 require.NoError(t, err)
159 screen.Write(o.Data)160 screen.Write(o.Data)
160 text := screenText(screen)161 text := screenText(screen)
@@ -178,8 +179,8 @@ func assertNoTextAfterInput(t *testing.T, p *pump, sz domain.Size, absent string
178 if !ok {179 if !ok {
179 return180 return
180 }181 }
181- if f.Type == ports.MsgOutput {182+ if f.Type == wire.MsgOutput {
182- o, err := ports.UnmarshalOutput(f.Payload)183+ o, err := wire.UnmarshalOutput(f.Payload)
183 require.NoError(t, err)184 require.NoError(t, err)
184 screen.Write(o.Data)185 screen.Write(o.Data)
185 require.NotContains(t, screenText(screen), absent)186 require.NotContains(t, screenText(screen), absent)
@@ -214,7 +215,7 @@ func shellFixture(t *testing.T, label string) string {
214 func assertChildEnvironment(t *testing.T, tr ports.Transport, p *pump, sz domain.Size, wantTestEnv, wantShell, wantRuntimeDir, wantWayland string) {215 func assertChildEnvironment(t *testing.T, tr ports.Transport, p *pump, sz domain.Size, wantTestEnv, wantShell, wantRuntimeDir, wantWayland string) {
215 t.Helper()216 t.Helper()
216 command := "printf '\\033[2J\\033[H'; printf 'VEV_TEST_ENV=%s SHELL=%s XDG_RUNTIME_DIR=%s WAYLAND_DISPLAY=%s TERM=%s COLORTERM=%s TERM_PROGRAM=%s VEV_PREFIX=%.24s\\n' \"$VEV_TEST_ENV\" \"${SHELL##*/}\" \"$XDG_RUNTIME_DIR\" \"$WAYLAND_DISPLAY\" \"$TERM\" \"$COLORTERM\" \"$TERM_PROGRAM\" \"$VEV\"\n"217 command := "printf '\\033[2J\\033[H'; printf 'VEV_TEST_ENV=%s SHELL=%s XDG_RUNTIME_DIR=%s WAYLAND_DISPLAY=%s TERM=%s COLORTERM=%s TERM_PROGRAM=%s VEV_PREFIX=%.24s\\n' \"$VEV_TEST_ENV\" \"${SHELL##*/}\" \"$XDG_RUNTIME_DIR\" \"$WAYLAND_DISPLAY\" \"$TERM\" \"$COLORTERM\" \"$TERM_PROGRAM\" \"$VEV\"\n"
217- require.NoError(t, tr.Send(ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(protocol.Input{Data: []byte(command)})}))218+ require.NoError(t, tr.Send(wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(protocol.Input{Data: []byte(command)})}))
218 text := awaitScreenText(t, p, sz, "TERM_PROGRAM=vev")219 text := awaitScreenText(t, p, sz, "TERM_PROGRAM=vev")
219 for _, want := range []string{220 for _, want := range []string{
220 "VEV_TEST_ENV=" + wantTestEnv,221 "VEV_TEST_ENV=" + wantTestEnv,
@@ -255,9 +256,9 @@ func TestIntegration_AttachEnvironmentRefreshesFuturePTYChildren(t *testing.T) {
255 // The first shell was already running, so it retains its original environment.256 // The first shell was already running, so it retains its original environment.
256 assertChildEnvironment(t, tr2, p2, sz, "first", firstShell, "/run/first", "wayland-first")257 assertChildEnvironment(t, tr2, p2, sz, "first", firstShell, "/run/first", "wayland-first")
257 258
258- require.NoError(t, tr2.Send(ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(protocol.Input{Data: []byte("\x1b ")})}))259+ require.NoError(t, tr2.Send(wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(protocol.Input{Data: []byte("\x1b ")})}))
259 awaitText(t, p2, sz, "Commands")260 awaitText(t, p2, sz, "Commands")
260- require.NoError(t, tr2.Send(ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(protocol.Input{Data: []byte("CNT\r")})}))261+ require.NoError(t, tr2.Send(wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(protocol.Input{Data: []byte("CNT\r")})}))
261 awaitText(t, p2, sz, "SHELL_COMMAND=second")262 awaitText(t, p2, sz, "SHELL_COMMAND=second")
262 assertChildEnvironment(t, tr2, p2, sz, "second", secondShell, "/run/second", "wayland-second")263 assertChildEnvironment(t, tr2, p2, sz, "second", secondShell, "/run/second", "wayland-second")
263 }264 }
@@ -288,17 +289,17 @@ func TestIntegration_TwoAttachmentsReceiveSharedMutationPTYOutput(t *testing.T)
288 289
289 // The second attachment mutates shared tab state. Its newly opened PTY must290 // The second attachment mutates shared tab state. Its newly opened PTY must
290 // also be rendered to that attachment, not only to the coordinator primary.291 // also be rendered to that attachment, not only to the coordinator primary.
291- require.NoError(t, tr2.Send(ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(protocol.Input{Data: []byte("\x1b ")})}))292+ require.NoError(t, tr2.Send(wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(protocol.Input{Data: []byte("\x1b ")})}))
292 awaitText(t, p2, sz, "Commands")293 awaitText(t, p2, sz, "Commands")
293- require.NoError(t, tr2.Send(ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(protocol.Input{Data: []byte("CNT\r")})}))294+ require.NoError(t, tr2.Send(wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(protocol.Input{Data: []byte("CNT\r")})}))
294 awaitText(t, p2, sz, "SHELL_COMMAND=second")295 awaitText(t, p2, sz, "SHELL_COMMAND=second")
295 296
296 // Each attachment owns its selected tab. Move the first attachment to the297 // Each attachment owns its selected tab. Move the first attachment to the
297 // new PTY before releasing its gate, so the fresh PTY output must reach both298 // new PTY before releasing its gate, so the fresh PTY output must reach both
298 // views rather than only being recovered by a later first paint.299 // views rather than only being recovered by a later first paint.
299- require.NoError(t, tr1.Send(ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(protocol.Input{Data: []byte("\x1b2")})}))300+ require.NoError(t, tr1.Send(wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(protocol.Input{Data: []byte("\x1b2")})}))
300 awaitText(t, p1, sz, "SHELL_COMMAND=second")301 awaitText(t, p1, sz, "SHELL_COMMAND=second")
301- require.NoError(t, tr1.Send(ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(protocol.Input{Data: []byte("go\n")})}))302+ require.NoError(t, tr1.Send(wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(protocol.Input{Data: []byte("go\n")})}))
302 awaitText(t, p2, sz, sharedOutput)303 awaitText(t, p2, sz, sharedOutput)
303 awaitText(t, p1, sz, sharedOutput)304 awaitText(t, p1, sz, sharedOutput)
304 }305 }
@@ -320,7 +321,7 @@ func TestIntegration_InputRoundtrip(t *testing.T) {
320 tr, p := attach(t, dir, protocol.IntentEphemeral, "", sz)321 tr, p := attach(t, dir, protocol.IntentEphemeral, "", sz)
321 defer func() { _ = tr.Close() }()322 defer func() { _ = tr.Close() }()
322 323
323- require.NoError(t, tr.Send(ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(protocol.Input{Data: []byte("PINGPONG\n")})}))324+ require.NoError(t, tr.Send(wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(protocol.Input{Data: []byte("PINGPONG\n")})}))
324 awaitText(t, p, sz, "PINGPONG")325 awaitText(t, p, sz, "PINGPONG")
325 }326 }
326 327
@@ -332,10 +333,10 @@ func TestIntegration_CommandPaletteCreatesTab(t *testing.T) {
332 defer func() { _ = tr.Close() }()333 defer func() { _ = tr.Close() }()
333 awaitText(t, p, sz, "READY")334 awaitText(t, p, sz, "READY")
334 335
335- require.NoError(t, tr.Send(ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(protocol.Input{Data: []byte("\x1b ")})}))336+ require.NoError(t, tr.Send(wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(protocol.Input{Data: []byte("\x1b ")})}))
336 awaitText(t, p, sz, "Commands")337 awaitText(t, p, sz, "Commands")
337 338
338- require.NoError(t, tr.Send(ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(protocol.Input{Data: []byte("CNT\r")})}))339+ require.NoError(t, tr.Send(wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(protocol.Input{Data: []byte("CNT\r")})}))
339 // Tab labels are enriched with the focused pane's title; with no process340 // Tab labels are enriched with the focused pane's title; with no process
340 // inspector wired in this test, that title falls back to the shell's341 // inspector wired in this test, that title falls back to the shell's
341 // basename ("sh").342 // basename ("sh").
@@ -352,24 +353,24 @@ func TestIntegration_CommandPaletteRenamesEphemeralSession(t *testing.T) {
352 text := awaitScreenText(t, p, sz, "READY")353 text := awaitScreenText(t, p, sz, "READY")
353 require.Contains(t, text, " 0* ")354 require.Contains(t, text, " 0* ")
354 355
355- require.NoError(t, tr.Send(ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(protocol.Input{Data: []byte("\x1b ")})}))356+ require.NoError(t, tr.Send(wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(protocol.Input{Data: []byte("\x1b ")})}))
356 awaitText(t, p, sz, "Commands")357 awaitText(t, p, sz, "Commands")
357 358
358- require.NoError(t, tr.Send(ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(protocol.Input{Data: []byte("RNS\r")})}))359+ require.NoError(t, tr.Send(wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(protocol.Input{Data: []byte("RNS\r")})}))
359 awaitText(t, p, sz, "Rename session")360 awaitText(t, p, sz, "Rename session")
360 361
361- require.NoError(t, tr.Send(ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(protocol.Input{Data: []byte("\x7fwork\r")})}))362+ require.NoError(t, tr.Send(wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(protocol.Input{Data: []byte("\x7fwork\r")})}))
362 text = awaitScreenText(t, p, sz, " work ")363 text = awaitScreenText(t, p, sz, " work ")
363 require.NotContains(t, text, "work*")364 require.NotContains(t, text, "work*")
364 365
365 listTr, err := ipc.DialContext(context.Background(), dir)366 listTr, err := ipc.DialContext(context.Background(), dir)
366 require.NoError(t, err)367 require.NoError(t, err)
367 defer func() { _ = listTr.Close() }()368 defer func() { _ = listTr.Close() }()
368- require.NoError(t, listTr.Send(ports.Frame{Type: ports.MsgList, Payload: ports.MarshalList(protocol.List{})}))369+ require.NoError(t, listTr.Send(wire.Frame{Type: wire.MsgList, Payload: wire.MarshalList(protocol.List{})}))
369 f, err := listTr.Recv()370 f, err := listTr.Recv()
370 require.NoError(t, err)371 require.NoError(t, err)
371- require.Equal(t, ports.MsgSessions, f.Type)372+ require.Equal(t, wire.MsgSessions, f.Type)
372- sessions, err := ports.UnmarshalSessions(f.Payload)373+ sessions, err := wire.UnmarshalSessions(f.Payload)
373 require.NoError(t, err)374 require.NoError(t, err)
374 require.NotEmpty(t, sessions.Sessions)375 require.NotEmpty(t, sessions.Sessions)
375 require.Equal(t, "work", sessions.Sessions[0].Name)376 require.Equal(t, "work", sessions.Sessions[0].Name)
@@ -385,7 +386,7 @@ func TestIntegration_AltCWithoutPaletteDoesNotCreateTab(t *testing.T) {
385 text := awaitScreenText(t, p, sz, "READY")386 text := awaitScreenText(t, p, sz, "READY")
386 require.NotContains(t, text, " 1 2 ")387 require.NotContains(t, text, " 1 2 ")
387 388
388- require.NoError(t, tr.Send(ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(protocol.Input{Data: []byte("\x1bc")})}))389+ require.NoError(t, tr.Send(wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(protocol.Input{Data: []byte("\x1bc")})}))
389 assertNoTextAfterInput(t, p, sz, " 1 2 ")390 assertNoTextAfterInput(t, p, sz, " 1 2 ")
390 }391 }
391 392
@@ -802,7 +803,7 @@ func publishRestorableCheckpoint(t *testing.T, stateDir string, repository *snap
802 go func() { served <- d.Serve(ctx, listener) }()803 go func() { served <- d.Serve(ctx, listener) }()
803 804
804 tr, _ := attach(t, runtimeDir, protocol.IntentNew, name, domain.Size{Cols: 80, Rows: 24})805 tr, _ := attach(t, runtimeDir, protocol.IntentNew, name, domain.Size{Cols: 80, Rows: 24})
805- require.NoError(t, tr.Send(ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(protocol.Input{Data: []byte("checkpoint me\n")})}))806+ require.NoError(t, tr.Send(wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(protocol.Input{Data: []byte("checkpoint me\n")})}))
806 require.Eventually(t, func() bool {807 require.Eventually(t, func() bool {
807 record, ok, _ := opened.Catalogue.Record(name)808 record, ok, _ := opened.Catalogue.Record(name)
808 return ok && record.Committed != nil && record.DegradedReason == ""809 return ok && record.Committed != nil && record.DegradedReason == ""
@@ -888,7 +889,7 @@ func runBlockedSnapshotWriterShutdown(t *testing.T) blockedSnapshotWriterShutdow
888 889
889 awaitLifecycleStage(t, listenerReady, "daemon listener")890 awaitLifecycleStage(t, listenerReady, "daemon listener")
890 tr, _ := attach(t, runtimeDir, protocol.IntentNew, "work", domain.Size{Cols: 80, Rows: 24})891 tr, _ := attach(t, runtimeDir, protocol.IntentNew, "work", domain.Size{Cols: 80, Rows: 24})
891- require.NoError(t, tr.Send(ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(protocol.Input{Data: []byte("dirty state\n")})}))892+ require.NoError(t, tr.Send(wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(protocol.Input{Data: []byte("dirty state\n")})}))
892 awaitLifecycleStage(t, repository.entered, "snapshot publication")893 awaitLifecycleStage(t, repository.entered, "snapshot publication")
893 require.NoError(t, tr.Close())894 require.NoError(t, tr.Close())
894 require.Eventually(t, func() bool {895 require.Eventually(t, func() bool {
@@ -1240,11 +1241,11 @@ func (c *lifecycleControlledCatalogue) Close() error {
1240 1241
1241 type integrationTransport struct{}1242 type integrationTransport struct{}
1242 1243
1243-func (*integrationTransport) Send(ports.Frame) error { return nil }1244+func (*integrationTransport) Send(wire.Frame) error { return nil }
1244-func (*integrationTransport) Recv() (ports.Frame, error) { return ports.Frame{}, io.EOF }1245+func (*integrationTransport) Recv() (wire.Frame, error) { return wire.Frame{}, io.EOF }
1245-func (*integrationTransport) Close() error { return nil }1246+func (*integrationTransport) Close() error { return nil }
1246-func (*integrationTransport) LocalAddr() net.Addr { return nil }1247+func (*integrationTransport) LocalAddr() net.Addr { return nil }
1247-func (*integrationTransport) RemoteAddr() net.Addr { return nil }1248+func (*integrationTransport) RemoteAddr() net.Addr { return nil }
1248 1249
1249 func TestIntegration_KillAllShutsDownDaemon(t *testing.T) {1250 func TestIntegration_KillAllShutsDownDaemon(t *testing.T) {
1250 sz := domain.Size{Cols: 80, Rows: 24}1251 sz := domain.Size{Cols: 80, Rows: 24}
@@ -1258,7 +1259,7 @@ func TestIntegration_KillAllShutsDownDaemon(t *testing.T) {
1258 killTr, err := ipc.DialContext(context.Background(), dir)1259 killTr, err := ipc.DialContext(context.Background(), dir)
1259 require.NoError(t, err)1260 require.NoError(t, err)
1260 defer func() { _ = killTr.Close() }()1261 defer func() { _ = killTr.Close() }()
1261- require.NoError(t, killTr.Send(ports.Frame{Type: ports.MsgKill, Payload: ports.MarshalKill(protocol.Kill{All: true})}))1262+ require.NoError(t, killTr.Send(wire.Frame{Type: wire.MsgKill, Payload: wire.MarshalKill(protocol.Kill{All: true})}))
1262 1263
1263 _, err = killTr.Recv()1264 _, err = killTr.Recv()
1264 require.ErrorIs(t, err, io.EOF)1265 require.ErrorIs(t, err, io.EOF)
M internal/app/final_acceptance_test.go +17 -16
@@ -9,16 +9,17 @@ import (
9 "github.com/bnema/vev/internal/domain"9 "github.com/bnema/vev/internal/domain"
10 "github.com/bnema/vev/internal/ports"10 "github.com/bnema/vev/internal/ports"
11 "github.com/bnema/vev/internal/protocol"11 "github.com/bnema/vev/internal/protocol"
12+ "github.com/bnema/vev/internal/protocol/wire"
12 "github.com/bnema/vev/internal/usecase/daemon"13 "github.com/bnema/vev/internal/usecase/daemon"
13 "github.com/stretchr/testify/require"14 "github.com/stretchr/testify/require"
14 )15 )
15 16
16 func sendAcceptanceInput(t *testing.T, tr ports.Transport, data string) {17 func sendAcceptanceInput(t *testing.T, tr ports.Transport, data string) {
17 t.Helper()18 t.Helper()
18- require.NoError(t, tr.Send(ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(protocol.Input{Data: []byte(data)})}))19+ require.NoError(t, tr.Send(wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(protocol.Input{Data: []byte(data)})}))
19 }20 }
20 21
21-func awaitAcceptanceFrame(t *testing.T, p *pump, typ ports.MsgType, predicate func(ports.Frame) bool) ports.Frame {22+func awaitAcceptanceFrame(t *testing.T, p *pump, typ wire.MsgType, predicate func(wire.Frame) bool) wire.Frame {
22 t.Helper()23 t.Helper()
23 deadline := time.NewTimer(5 * time.Second)24 deadline := time.NewTimer(5 * time.Second)
24 defer deadline.Stop()25 defer deadline.Stop()
@@ -37,35 +38,35 @@ func awaitAcceptanceFrame(t *testing.T, p *pump, typ ports.MsgType, predicate fu
37 38
38 func awaitAcceptanceCommand(t *testing.T, p *pump, requestID uint64) protocol.CommandResult {39 func awaitAcceptanceCommand(t *testing.T, p *pump, requestID uint64) protocol.CommandResult {
39 t.Helper()40 t.Helper()
40- frame := awaitAcceptanceFrame(t, p, ports.MsgCommandResult, func(frame ports.Frame) bool {41+ frame := awaitAcceptanceFrame(t, p, wire.MsgCommandResult, func(frame wire.Frame) bool {
41- result, err := ports.UnmarshalCommandResult(frame.Payload)42+ result, err := wire.UnmarshalCommandResult(frame.Payload)
42 require.NoError(t, err)43 require.NoError(t, err)
43 return result.RequestID == requestID44 return result.RequestID == requestID
44 })45 })
45- result, err := ports.UnmarshalCommandResult(frame.Payload)46+ result, err := wire.UnmarshalCommandResult(frame.Payload)
46 require.NoError(t, err)47 require.NoError(t, err)
47 return result48 return result
48 }49 }
49 50
50 func awaitAcceptanceOutput(t *testing.T, p *pump, predicate func(protocol.Output) bool) protocol.Output {51 func awaitAcceptanceOutput(t *testing.T, p *pump, predicate func(protocol.Output) bool) protocol.Output {
51 t.Helper()52 t.Helper()
52- frame := awaitAcceptanceFrame(t, p, ports.MsgOutput, func(frame ports.Frame) bool {53+ frame := awaitAcceptanceFrame(t, p, wire.MsgOutput, func(frame wire.Frame) bool {
53- output, err := ports.UnmarshalOutput(frame.Payload)54+ output, err := wire.UnmarshalOutput(frame.Payload)
54 require.NoError(t, err)55 require.NoError(t, err)
55 return predicate(output)56 return predicate(output)
56 })57 })
57- output, err := ports.UnmarshalOutput(frame.Payload)58+ output, err := wire.UnmarshalOutput(frame.Payload)
58 require.NoError(t, err)59 require.NoError(t, err)
59 return output60 return output
60 }61 }
61 62
62 func awaitAcceptanceCommandResult(t *testing.T, tr ports.Transport, p *pump, requestID uint64, slug string) protocol.CommandResult {63 func awaitAcceptanceCommandResult(t *testing.T, tr ports.Transport, p *pump, requestID uint64, slug string) protocol.CommandResult {
63 t.Helper()64 t.Helper()
64- payload, err := ports.MarshalCommandRequest(protocol.CommandRequest{65+ payload, err := wire.MarshalCommandRequest(protocol.CommandRequest{
65 Version: protocol.Version, RequestID: requestID, Attached: true, Slug: slug,66 Version: protocol.Version, RequestID: requestID, Attached: true, Slug: slug,
66 })67 })
67 require.NoError(t, err)68 require.NoError(t, err)
68- require.NoError(t, tr.Send(ports.Frame{Type: ports.MsgCommand, Payload: payload}))69+ require.NoError(t, tr.Send(wire.Frame{Type: wire.MsgCommand, Payload: payload}))
69 return awaitAcceptanceCommand(t, p, requestID)70 return awaitAcceptanceCommand(t, p, requestID)
70 }71 }
71 72
@@ -114,25 +115,25 @@ func TestAcceptanceTwoLocalAttachmentsKeepViewsOverTransports(t *testing.T) {
114 // The resize keeps its attachment-local output stream while recalculating115 // The resize keeps its attachment-local output stream while recalculating
115 // shared PTY/content geometry from the latest valid attachment claim; the116 // shared PTY/content geometry from the latest valid attachment claim; the
116 // peer stream remains independent.117 // peer stream remains independent.
117- resizePayload, err := ports.MarshalResize(protocol.Resize{Size: domain.Size{Cols: 100, Rows: 30}})118+ resizePayload, err := wire.MarshalResize(protocol.Resize{Size: domain.Size{Cols: 100, Rows: 30}})
118 require.NoError(t, err)119 require.NoError(t, err)
119- require.NoError(t, first.Send(ports.Frame{Type: ports.MsgResize, Payload: resizePayload}))120+ require.NoError(t, first.Send(wire.Frame{Type: wire.MsgResize, Payload: resizePayload}))
120 resized := awaitAcceptanceOutput(t, firstPump, func(output protocol.Output) bool { return output.Size == (domain.Size{Cols: 100, Rows: 30}) })121 resized := awaitAcceptanceOutput(t, firstPump, func(output protocol.Output) bool { return output.Size == (domain.Size{Cols: 100, Rows: 30}) })
121 require.Equal(t, domain.Size{Cols: 100, Rows: 30}, resized.Size)122 require.Equal(t, domain.Size{Cols: 100, Rows: 30}, resized.Size)
122 assertNoTextAfterInput(t, secondPump, size, "FIRST_PANE_INPUT")123 assertNoTextAfterInput(t, secondPump, size, "FIRST_PANE_INPUT")
123 124
124- require.NoError(t, first.Send(ports.Frame{Type: ports.MsgOutputResetRequest, Payload: ports.MarshalOutputResetRequest(protocol.OutputResetRequest{})}))125+ require.NoError(t, first.Send(wire.Frame{Type: wire.MsgOutputResetRequest, Payload: wire.MarshalOutputResetRequest(protocol.OutputResetRequest{})}))
125 reset := awaitAcceptanceOutput(t, firstPump, func(output protocol.Output) bool { return output.Base == 0 && output.Full })126 reset := awaitAcceptanceOutput(t, firstPump, func(output protocol.Output) bool { return output.Base == 0 && output.Full })
126 require.True(t, reset.Full)127 require.True(t, reset.Full)
127 assertNoTextAfterInput(t, secondPump, size, "FIRST_PANE_INPUT")128 assertNoTextAfterInput(t, secondPump, size, "FIRST_PANE_INPUT")
128 129
129- require.NoError(t, first.Send(ports.Frame{Type: ports.MsgDetach, Payload: ports.MarshalDetach(protocol.Detach{})}))130+ require.NoError(t, first.Send(wire.Frame{Type: wire.MsgDetach, Payload: wire.MarshalDetach(protocol.Detach{})}))
130 deadline := time.NewTimer(5 * time.Second)131 deadline := time.NewTimer(5 * time.Second)
131 defer deadline.Stop()132 defer deadline.Stop()
132 for {133 for {
133 select {134 select {
134 case frame := <-firstPump.ch:135 case frame := <-firstPump.ch:
135- if frame.Type == ports.MsgDetached {136+ if frame.Type == wire.MsgDetached {
136 goto detached137 goto detached
137 }138 }
138 case <-deadline.C:139 case <-deadline.C:
@@ -173,7 +174,7 @@ func TestAcceptanceAttachedCommandUsesItsConnectionOnly(t *testing.T) {
173 }174 }
174 175
175 drained:176 drained:
176- require.NoError(t, first.Send(ports.Frame{Type: ports.MsgOutputResetRequest, Payload: ports.MarshalOutputResetRequest(protocol.OutputResetRequest{})}))177+ require.NoError(t, first.Send(wire.Frame{Type: wire.MsgOutputResetRequest, Payload: wire.MarshalOutputResetRequest(protocol.OutputResetRequest{})}))
177 awaitAcceptanceOutput(t, firstPump, func(output protocol.Output) bool { return output.Full && output.Base == 0 })178 awaitAcceptanceOutput(t, firstPump, func(output protocol.Output) bool { return output.Full && output.Base == 0 })
178 assertNoTextAfterInput(t, secondPump, size, "Commands")179 assertNoTextAfterInput(t, secondPump, size, "Commands")
179 }180 }
M internal/app/remote_final_acceptance_test.go +12 -11
@@ -16,6 +16,7 @@ import (
16 "github.com/bnema/vev/internal/ports"16 "github.com/bnema/vev/internal/ports"
17 portsmocks "github.com/bnema/vev/internal/ports/mocks"17 portsmocks "github.com/bnema/vev/internal/ports/mocks"
18 "github.com/bnema/vev/internal/protocol"18 "github.com/bnema/vev/internal/protocol"
19+ "github.com/bnema/vev/internal/protocol/wire"
19 "github.com/bnema/vev/internal/usecase/client"20 "github.com/bnema/vev/internal/usecase/client"
20 "github.com/stretchr/testify/require"21 "github.com/stretchr/testify/require"
21 )22 )
@@ -58,17 +59,17 @@ func serveAcceptanceRemote(tr ports.Transport, target string, handoff protocol.A
58 return59 return
59 }60 }
60 if !welcomed {61 if !welcomed {
61- if frame.Type != ports.MsgHello {62+ if frame.Type != wire.MsgHello {
62 continue63 continue
63 }64 }
64- if err := tr.Send(ports.Frame{Type: ports.MsgWelcome, Payload: ports.MarshalWelcome(protocol.Welcome{SessionID: target})}); err != nil {65+ if err := tr.Send(wire.Frame{Type: wire.MsgWelcome, Payload: wire.MarshalWelcome(protocol.Welcome{SessionID: target})}); err != nil {
65 return66 return
66 }67 }
67 welcomed = true68 welcomed = true
68 continue69 continue
69 }70 }
70 if handoff.Endpoint != "" {71 if handoff.Endpoint != "" {
71- _ = tr.Send(ports.Frame{Type: ports.MsgAttachTarget, Payload: ports.MarshalAttachTarget(handoff)})72+ _ = tr.Send(wire.Frame{Type: wire.MsgAttachTarget, Payload: wire.MarshalAttachTarget(handoff)})
72 for {73 for {
73 if _, err := tr.Recv(); err != nil {74 if _, err := tr.Recv(); err != nil {
74 return75 return
@@ -79,16 +80,16 @@ func serveAcceptanceRemote(tr ports.Transport, target string, handoff protocol.A
79 if outputs != nil {80 if outputs != nil {
80 outputs <- string(data)81 outputs <- string(data)
81 }82 }
82- payload, err := ports.MarshalOutput(protocol.Output{83+ payload, err := wire.MarshalOutput(protocol.Output{
83 Epoch: 1, New: 1, Full: true, Size: domain.Size{Cols: 80, Rows: 24}, Data: data,84 Epoch: 1, New: 1, Full: true, Size: domain.Size{Cols: 80, Rows: 24}, Data: data,
84 })85 })
85 if err != nil {86 if err != nil {
86 return87 return
87 }88 }
88- if err := tr.Send(ports.Frame{Type: ports.MsgOutput, Payload: payload}); err != nil {89+ if err := tr.Send(wire.Frame{Type: wire.MsgOutput, Payload: payload}); err != nil {
89 return90 return
90 }91 }
91- _ = tr.Send(ports.Frame{Type: ports.MsgDetached, Payload: ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach})})92+ _ = tr.Send(wire.Frame{Type: wire.MsgDetached, Payload: wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach})})
92 for {93 for {
93 if _, err := tr.Recv(); err != nil {94 if _, err := tr.Recv(); err != nil {
94 return95 return
@@ -150,18 +151,18 @@ func TestAcceptanceRemoteDirectAndPickerUseOnlyRemoteTransports(t *testing.T) {
150 handoff: protocol.AttachTarget{Endpoint: "picked.example", Session: "picked", Intent: protocol.IntentAttach},151 handoff: protocol.AttachTarget{Endpoint: "picked.example", Session: "picked", Intent: protocol.IntentAttach},
151 }).Dial(context.Background())152 }).Dial(context.Background())
152 require.NoError(t, err)153 require.NoError(t, err)
153- require.NoError(t, wireTransport.Send(ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(protocol.Hello{154+ require.NoError(t, wireTransport.Send(wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(protocol.Hello{
154 Version: protocol.Version, Intent: protocol.IntentAttach, Name: "work", Size: domain.Size{Cols: 80, Rows: 24},155 Version: protocol.Version, Intent: protocol.IntentAttach, Name: "work", Size: domain.Size{Cols: 80, Rows: 24},
155 })}))156 })}))
156 welcome, err := wireTransport.Recv()157 welcome, err := wireTransport.Recv()
157 require.NoError(t, err)158 require.NoError(t, err)
158- require.Equal(t, ports.MsgWelcome, welcome.Type)159+ require.Equal(t, wire.MsgWelcome, welcome.Type)
159- require.NoError(t, wireTransport.Send(ports.Frame{Type: ports.MsgTheme, Payload: ports.MarshalTheme(protocol.Theme{})}))160+ require.NoError(t, wireTransport.Send(wire.Frame{Type: wire.MsgTheme, Payload: wire.MarshalTheme(protocol.Theme{})}))
160 attachTarget, err := wireTransport.Recv()161 attachTarget, err := wireTransport.Recv()
161 require.NoError(t, err)162 require.NoError(t, err)
162- target, err := ports.UnmarshalAttachTarget(attachTarget.Payload)163+ target, err := wire.UnmarshalAttachTarget(attachTarget.Payload)
163 require.NoError(t, err)164 require.NoError(t, err)
164- require.Equal(t, ports.MsgAttachTarget, attachTarget.Type)165+ require.Equal(t, wire.MsgAttachTarget, attachTarget.Type)
165 require.Equal(t, "picked.example", target.Endpoint)166 require.Equal(t, "picked.example", target.Endpoint)
166 require.NoError(t, wireTransport.Close())167 require.NoError(t, wireTransport.Close())
167 168
M internal/app/remote_preview.go +8 -8
@@ -8,8 +8,8 @@ import (
8 "os"8 "os"
9 9
10 "github.com/bnema/vev/internal/adapters/ipc"10 "github.com/bnema/vev/internal/adapters/ipc"
11- "github.com/bnema/vev/internal/ports"
12 "github.com/bnema/vev/internal/protocol"11 "github.com/bnema/vev/internal/protocol"
12+ "github.com/bnema/vev/internal/protocol/wire"
13 )13 )
14 14
15 // runRemotePreview is the hidden SSH-side carriage. The request is encoded in15 // runRemotePreview is the hidden SSH-side carriage. The request is encoded in
@@ -20,7 +20,7 @@ func runRemotePreview(ctx context.Context, encoded string) error {
20 if err != nil {20 if err != nil {
21 return fmt.Errorf("vev: invalid remote preview request: %w", err)21 return fmt.Errorf("vev: invalid remote preview request: %w", err)
22 }22 }
23- if _, err := ports.UnmarshalRemotePreviewRequest(request); err != nil {23+ if _, err := wire.UnmarshalRemotePreviewRequest(request); err != nil {
24 return fmt.Errorf("vev: invalid remote preview request: %w", err)24 return fmt.Errorf("vev: invalid remote preview request: %w", err)
25 }25 }
26 transport, err := ipc.DialContext(ctx, ipc.SocketDir())26 transport, err := ipc.DialContext(ctx, ipc.SocketDir())
@@ -31,17 +31,17 @@ func runRemotePreview(ctx context.Context, encoded string) error {
31 return writeRemotePreviewStatus(protocol.RemotePreviewUnavailable)31 return writeRemotePreviewStatus(protocol.RemotePreviewUnavailable)
32 }32 }
33 defer func() { _ = transport.Close() }()33 defer func() { _ = transport.Close() }()
34- if err := transport.Send(ports.Frame{Type: ports.MsgRemotePreviewRequest, Payload: request}); err != nil {34+ if err := transport.Send(wire.Frame{Type: wire.MsgRemotePreviewRequest, Payload: request}); err != nil {
35 return fmt.Errorf("vev: sending remote preview request: %w", err)35 return fmt.Errorf("vev: sending remote preview request: %w", err)
36 }36 }
37 frameCh := make(chan struct {37 frameCh := make(chan struct {
38- frame ports.Frame38+ frame wire.Frame
39 err error39 err error
40 }, 1)40 }, 1)
41 go func() {41 go func() {
42 frame, err := transport.Recv()42 frame, err := transport.Recv()
43 frameCh <- struct {43 frameCh <- struct {
44- frame ports.Frame44+ frame wire.Frame
45 err error45 err error
46 }{frame: frame, err: err}46 }{frame: frame, err: err}
47 }()47 }()
@@ -53,10 +53,10 @@ func runRemotePreview(ctx context.Context, encoded string) error {
53 if result.err != nil {53 if result.err != nil {
54 return fmt.Errorf("vev: receiving remote preview: %w", result.err)54 return fmt.Errorf("vev: receiving remote preview: %w", result.err)
55 }55 }
56- if result.frame.Type != ports.MsgRemotePreviewResponse {56+ if result.frame.Type != wire.MsgRemotePreviewResponse {
57 return fmt.Errorf("vev: unexpected remote preview response %d", result.frame.Type)57 return fmt.Errorf("vev: unexpected remote preview response %d", result.frame.Type)
58 }58 }
59- if _, err := ports.UnmarshalRemotePreview(result.frame.Payload); err != nil {59+ if _, err := wire.UnmarshalRemotePreview(result.frame.Payload); err != nil {
60 return fmt.Errorf("vev: malformed remote preview response: %w", err)60 return fmt.Errorf("vev: malformed remote preview response: %w", err)
61 }61 }
62 _, err := os.Stdout.Write(result.frame.Payload)62 _, err := os.Stdout.Write(result.frame.Payload)
@@ -65,7 +65,7 @@ func runRemotePreview(ctx context.Context, encoded string) error {
65 }65 }
66 66
67 func writeRemotePreviewStatus(status protocol.RemotePreviewStatus) error {67 func writeRemotePreviewStatus(status protocol.RemotePreviewStatus) error {
68- payload := ports.MarshalRemotePreview(protocol.RemotePreview{Version: protocol.RemotePreviewSchemaVersion, Status: status})68+ payload := wire.MarshalRemotePreview(protocol.RemotePreview{Version: protocol.RemotePreviewSchemaVersion, Status: status})
69 if payload == nil {69 if payload == nil {
70 return errors.New("vev: failed to encode remote preview status")70 return errors.New("vev: failed to encode remote preview status")
71 }71 }
M internal/app/remote_test.go +5 -4
@@ -12,6 +12,7 @@ import (
12 "github.com/bnema/vev/internal/ports"12 "github.com/bnema/vev/internal/ports"
13 portsmocks "github.com/bnema/vev/internal/ports/mocks"13 portsmocks "github.com/bnema/vev/internal/ports/mocks"
14 "github.com/bnema/vev/internal/protocol"14 "github.com/bnema/vev/internal/protocol"
15+ "github.com/bnema/vev/internal/protocol/wire"
15 "github.com/bnema/vev/internal/usecase/client"16 "github.com/bnema/vev/internal/usecase/client"
16 "github.com/stretchr/testify/mock"17 "github.com/stretchr/testify/mock"
17 "github.com/stretchr/testify/require"18 "github.com/stretchr/testify/require"
@@ -56,15 +57,15 @@ func TestRemoteHostDepsDefaultsStore(t *testing.T) {
56 57
57 func TestDecodeSessionListErrorReply(t *testing.T) {58 func TestDecodeSessionListErrorReply(t *testing.T) {
58 t.Run("valid", func(t *testing.T) {59 t.Run("valid", func(t *testing.T) {
59- _, err := decodeSessionListReply(ports.Frame{60+ _, err := decodeSessionListReply(wire.Frame{
60- Type: ports.MsgError,61+ Type: wire.MsgError,
61- Payload: ports.MarshalErrorMsg(protocol.ErrorMsg{Text: "denied"}),62+ Payload: wire.MarshalErrorMsg(protocol.ErrorMsg{Text: "denied"}),
62 })63 })
63 require.EqualError(t, err, "vev: denied")64 require.EqualError(t, err, "vev: denied")
64 })65 })
65 66
66 t.Run("malformed", func(t *testing.T) {67 t.Run("malformed", func(t *testing.T) {
67- _, err := decodeSessionListReply(ports.Frame{Type: ports.MsgError, Payload: []byte{0}})68+ _, err := decodeSessionListReply(wire.Frame{Type: wire.MsgError, Payload: []byte{0}})
68 require.ErrorContains(t, err, "vev: decoding error reply")69 require.ErrorContains(t, err, "vev: decoding error reply")
69 })70 })
70 }71 }
M internal/app/run.go +16 -15
@@ -47,6 +47,7 @@ import (
47 "github.com/bnema/vev/internal/platform"47 "github.com/bnema/vev/internal/platform"
48 "github.com/bnema/vev/internal/ports"48 "github.com/bnema/vev/internal/ports"
49 "github.com/bnema/vev/internal/protocol"49 "github.com/bnema/vev/internal/protocol"
50+ "github.com/bnema/vev/internal/protocol/wire"
50 "github.com/bnema/vev/internal/usecase/client"51 "github.com/bnema/vev/internal/usecase/client"
51 "github.com/bnema/vev/internal/usecase/daemon"52 "github.com/bnema/vev/internal/usecase/daemon"
52 "github.com/bnema/vev/internal/usecase/recovery"53 "github.com/bnema/vev/internal/usecase/recovery"
@@ -1077,12 +1078,12 @@ func createDetachedLocalSession(ctx context.Context, name string) error {
1077 cwd = ""1078 cwd = ""
1078 }1079 }
1079 hello := detachedLocalHello(name, cwd)1080 hello := detachedLocalHello(name, cwd)
1080- if err := transport.Send(ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(hello)}); err != nil {1081+ if err := transport.Send(wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(hello)}); err != nil {
1081 return fmt.Errorf("vev: creating detached session: %w", err)1082 return fmt.Errorf("vev: creating detached session: %w", err)
1082 }1083 }
1083 1084
1084 type detachedReply struct {1085 type detachedReply struct {
1085- frame ports.Frame1086+ frame wire.Frame
1086 err error1087 err error
1087 }1088 }
1088 replyCh := make(chan detachedReply, 1)1089 replyCh := make(chan detachedReply, 1)
@@ -1100,10 +1101,10 @@ func createDetachedLocalSession(ctx context.Context, name string) error {
1100 return fmt.Errorf("vev: awaiting detached session creation: %w", reply.err)1101 return fmt.Errorf("vev: awaiting detached session creation: %w", reply.err)
1101 }1102 }
1102 switch reply.frame.Type {1103 switch reply.frame.Type {
1103- case ports.MsgWelcome:1104+ case wire.MsgWelcome:
1104 return nil1105 return nil
1105- case ports.MsgError:1106+ case wire.MsgError:
1106- em, derr := ports.UnmarshalErrorMsg(reply.frame.Payload)1107+ em, derr := wire.UnmarshalErrorMsg(reply.frame.Payload)
1107 if derr != nil {1108 if derr != nil {
1108 return fmt.Errorf("vev: decoding error reply: %w", derr)1109 return fmt.Errorf("vev: decoding error reply: %w", derr)
1109 }1110 }
@@ -1126,7 +1127,7 @@ func requestDaemonStop(ctx context.Context) error {
1126 return errors.Join(errors.New("vev: no daemon running"), owner.Release())1127 return errors.Join(errors.New("vev: no daemon running"), owner.Release())
1127 }1128 }
1128 defer func() { _ = transport.Close() }()1129 defer func() { _ = transport.Close() }()
1129- if err := transport.Send(ports.Frame{Type: ports.MsgKill, Payload: ports.MarshalKill(protocol.Kill{All: true})}); err != nil {1130+ if err := transport.Send(wire.Frame{Type: wire.MsgKill, Payload: wire.MarshalKill(protocol.Kill{All: true})}); err != nil {
1130 return fmt.Errorf("vev: requesting daemon stop: %w", err)1131 return fmt.Errorf("vev: requesting daemon stop: %w", err)
1131 }1132 }
1132 if _, err := transport.Recv(); err != nil && !errors.Is(err, io.EOF) {1133 if _, err := transport.Recv(); err != nil && !errors.Is(err, io.EOF) {
@@ -1482,7 +1483,7 @@ func listLocalSessions(ctx context.Context) (_ []protocol.SessionInfo, retErr er
1482 }1483 }
1483 defer func() { _ = transport.Close() }()1484 defer func() { _ = transport.Close() }()
1484 1485
1485- if err := transport.Send(ports.Frame{Type: ports.MsgList, Payload: ports.MarshalList(protocol.List{})}); err != nil {1486+ if err := transport.Send(wire.Frame{Type: wire.MsgList, Payload: wire.MarshalList(protocol.List{})}); err != nil {
1486 return nil, fmt.Errorf("vev: requesting session list: %w", err)1487 return nil, fmt.Errorf("vev: requesting session list: %w", err)
1487 }1488 }
1488 reply, err := transport.Recv()1489 reply, err := transport.Recv()
@@ -1492,18 +1493,18 @@ func listLocalSessions(ctx context.Context) (_ []protocol.SessionInfo, retErr er
1492 return decodeSessionListReply(reply)1493 return decodeSessionListReply(reply)
1493 }1494 }
1494 1495
1495-func decodeSessionListReply(reply ports.Frame) ([]protocol.SessionInfo, error) {1496+func decodeSessionListReply(reply wire.Frame) ([]protocol.SessionInfo, error) {
1496- if reply.Type == ports.MsgError {1497+ if reply.Type == wire.MsgError {
1497- em, err := ports.UnmarshalErrorMsg(reply.Payload)1498+ em, err := wire.UnmarshalErrorMsg(reply.Payload)
1498 if err != nil {1499 if err != nil {
1499 return nil, fmt.Errorf("vev: decoding error reply: %w", err)1500 return nil, fmt.Errorf("vev: decoding error reply: %w", err)
1500 }1501 }
1501 return nil, fmt.Errorf("vev: %s", em.Text)1502 return nil, fmt.Errorf("vev: %s", em.Text)
1502 }1503 }
1503- if reply.Type != ports.MsgSessions {1504+ if reply.Type != wire.MsgSessions {
1504 return nil, fmt.Errorf("vev: unexpected reply type %d to list", reply.Type)1505 return nil, fmt.Errorf("vev: unexpected reply type %d to list", reply.Type)
1505 }1506 }
1506- sessions, err := ports.UnmarshalSessions(reply.Payload)1507+ sessions, err := wire.UnmarshalSessions(reply.Payload)
1507 if err != nil {1508 if err != nil {
1508 return nil, fmt.Errorf("vev: decoding session list: %w", err)1509 return nil, fmt.Errorf("vev: decoding session list: %w", err)
1509 }1510 }
@@ -1596,7 +1597,7 @@ func runKill(ctx context.Context, name string, all, daemon bool) (retErr error)
1596 }1597 }
1597 defer func() { _ = transport.Close() }()1598 defer func() { _ = transport.Close() }()
1598 1599
1599- if err := transport.Send(ports.Frame{Type: ports.MsgKill, Payload: ports.MarshalKill(protocol.Kill{Name: name, All: all || daemon})}); err != nil {1600+ if err := transport.Send(wire.Frame{Type: wire.MsgKill, Payload: wire.MarshalKill(protocol.Kill{Name: name, All: all || daemon})}); err != nil {
1600 return fmt.Errorf("vev: requesting kill: %w", err)1601 return fmt.Errorf("vev: requesting kill: %w", err)
1601 }1602 }
1602 reply, err := transport.Recv()1603 reply, err := transport.Recv()
@@ -1609,8 +1610,8 @@ func runKill(ctx context.Context, name string, all, daemon bool) (retErr error)
1609 }1610 }
1610 return fmt.Errorf("vev: reading kill reply: %w", err)1611 return fmt.Errorf("vev: reading kill reply: %w", err)
1611 }1612 }
1612- if reply.Type == ports.MsgError {1613+ if reply.Type == wire.MsgError {
1613- em, _ := ports.UnmarshalErrorMsg(reply.Payload)1614+ em, _ := wire.UnmarshalErrorMsg(reply.Payload)
1614 return fmt.Errorf("vev: %s", em.Text)1615 return fmt.Errorf("vev: %s", em.Text)
1615 }1616 }
1616 printKillSuccess(name, all, daemon)1617 printKillSuccess(name, all, daemon)
M internal/ports/mocks/transport_mock.go +43 -42
@@ -8,6 +8,7 @@ import (
8 "context"8 "context"
9 9
10 "github.com/bnema/vev/internal/ports"10 "github.com/bnema/vev/internal/ports"
11+ "github.com/bnema/vev/internal/protocol/wire"
11 mock "github.com/stretchr/testify/mock"12 mock "github.com/stretchr/testify/mock"
12 )13 )
13 14
@@ -83,22 +84,22 @@ func (_c *MockTransport_Close_Call) RunAndReturn(run func() error) *MockTranspor
83 }84 }
84 85
85 // Recv provides a mock function for the type MockTransport86 // Recv provides a mock function for the type MockTransport
86-func (_mock *MockTransport) Recv() (ports.Frame, error) {87+func (_mock *MockTransport) Recv() (wire.Frame, error) {
87 ret := _mock.Called()88 ret := _mock.Called()
88 89
89 if len(ret) == 0 {90 if len(ret) == 0 {
90 panic("no return value specified for Recv")91 panic("no return value specified for Recv")
91 }92 }
92 93
93- var r0 ports.Frame94+ var r0 wire.Frame
94 var r1 error95 var r1 error
95- if returnFunc, ok := ret.Get(0).(func() (ports.Frame, error)); ok {96+ if returnFunc, ok := ret.Get(0).(func() (wire.Frame, error)); ok {
96 return returnFunc()97 return returnFunc()
97 }98 }
98- if returnFunc, ok := ret.Get(0).(func() ports.Frame); ok {99+ if returnFunc, ok := ret.Get(0).(func() wire.Frame); ok {
99 r0 = returnFunc()100 r0 = returnFunc()
100 } else {101 } else {
101- r0 = ret.Get(0).(ports.Frame)102+ r0 = ret.Get(0).(wire.Frame)
102 }103 }
103 if returnFunc, ok := ret.Get(1).(func() error); ok {104 if returnFunc, ok := ret.Get(1).(func() error); ok {
104 r1 = returnFunc()105 r1 = returnFunc()
@@ -125,18 +126,18 @@ func (_c *MockTransport_Recv_Call) Run(run func()) *MockTransport_Recv_Call {
125 return _c126 return _c
126 }127 }
127 128
128-func (_c *MockTransport_Recv_Call) Return(frame ports.Frame, err error) *MockTransport_Recv_Call {129+func (_c *MockTransport_Recv_Call) Return(frame wire.Frame, err error) *MockTransport_Recv_Call {
129 _c.Call.Return(frame, err)130 _c.Call.Return(frame, err)
130 return _c131 return _c
131 }132 }
132 133
133-func (_c *MockTransport_Recv_Call) RunAndReturn(run func() (ports.Frame, error)) *MockTransport_Recv_Call {134+func (_c *MockTransport_Recv_Call) RunAndReturn(run func() (wire.Frame, error)) *MockTransport_Recv_Call {
134 _c.Call.Return(run)135 _c.Call.Return(run)
135 return _c136 return _c
136 }137 }
137 138
138 // Send provides a mock function for the type MockTransport139 // Send provides a mock function for the type MockTransport
139-func (_mock *MockTransport) Send(frame ports.Frame) error {140+func (_mock *MockTransport) Send(frame wire.Frame) error {
140 ret := _mock.Called(frame)141 ret := _mock.Called(frame)
141 142
142 if len(ret) == 0 {143 if len(ret) == 0 {
@@ -144,7 +145,7 @@ func (_mock *MockTransport) Send(frame ports.Frame) error {
144 }145 }
145 146
146 var r0 error147 var r0 error
147- if returnFunc, ok := ret.Get(0).(func(ports.Frame) error); ok {148+ if returnFunc, ok := ret.Get(0).(func(wire.Frame) error); ok {
148 r0 = returnFunc(frame)149 r0 = returnFunc(frame)
149 } else {150 } else {
150 r0 = ret.Error(0)151 r0 = ret.Error(0)
@@ -158,16 +159,16 @@ type MockTransport_Send_Call struct {
158 }159 }
159 160
160 // Send is a helper method to define mock.On call161 // Send is a helper method to define mock.On call
161-// - frame ports.Frame162+// - frame wire.Frame
162 func (_e *MockTransport_Expecter) Send(frame any) *MockTransport_Send_Call {163 func (_e *MockTransport_Expecter) Send(frame any) *MockTransport_Send_Call {
163 return &MockTransport_Send_Call{Call: _e.mock.On("Send", frame)}164 return &MockTransport_Send_Call{Call: _e.mock.On("Send", frame)}
164 }165 }
165 166
166-func (_c *MockTransport_Send_Call) Run(run func(frame ports.Frame)) *MockTransport_Send_Call {167+func (_c *MockTransport_Send_Call) Run(run func(frame wire.Frame)) *MockTransport_Send_Call {
167 _c.Call.Run(func(args mock.Arguments) {168 _c.Call.Run(func(args mock.Arguments) {
168- var arg0 ports.Frame169+ var arg0 wire.Frame
169 if args[0] != nil {170 if args[0] != nil {
170- arg0 = args[0].(ports.Frame)171+ arg0 = args[0].(wire.Frame)
171 }172 }
172 run(173 run(
173 arg0,174 arg0,
@@ -181,7 +182,7 @@ func (_c *MockTransport_Send_Call) Return(err error) *MockTransport_Send_Call {
181 return _c182 return _c
182 }183 }
183 184
184-func (_c *MockTransport_Send_Call) RunAndReturn(run func(frame ports.Frame) error) *MockTransport_Send_Call {185+func (_c *MockTransport_Send_Call) RunAndReturn(run func(frame wire.Frame) error) *MockTransport_Send_Call {
185 _c.Call.Return(run)186 _c.Call.Return(run)
186 return _c187 return _c
187 }188 }
@@ -291,22 +292,22 @@ func (_c *MockDatagramTransport_DatagramTransport_Call) RunAndReturn(run func())
291 }292 }
292 293
293 // Recv provides a mock function for the type MockDatagramTransport294 // Recv provides a mock function for the type MockDatagramTransport
294-func (_mock *MockDatagramTransport) Recv() (ports.Frame, error) {295+func (_mock *MockDatagramTransport) Recv() (wire.Frame, error) {
295 ret := _mock.Called()296 ret := _mock.Called()
296 297
297 if len(ret) == 0 {298 if len(ret) == 0 {
298 panic("no return value specified for Recv")299 panic("no return value specified for Recv")
299 }300 }
300 301
301- var r0 ports.Frame302+ var r0 wire.Frame
302 var r1 error303 var r1 error
303- if returnFunc, ok := ret.Get(0).(func() (ports.Frame, error)); ok {304+ if returnFunc, ok := ret.Get(0).(func() (wire.Frame, error)); ok {
304 return returnFunc()305 return returnFunc()
305 }306 }
306- if returnFunc, ok := ret.Get(0).(func() ports.Frame); ok {307+ if returnFunc, ok := ret.Get(0).(func() wire.Frame); ok {
307 r0 = returnFunc()308 r0 = returnFunc()
308 } else {309 } else {
309- r0 = ret.Get(0).(ports.Frame)310+ r0 = ret.Get(0).(wire.Frame)
310 }311 }
311 if returnFunc, ok := ret.Get(1).(func() error); ok {312 if returnFunc, ok := ret.Get(1).(func() error); ok {
312 r1 = returnFunc()313 r1 = returnFunc()
@@ -333,18 +334,18 @@ func (_c *MockDatagramTransport_Recv_Call) Run(run func()) *MockDatagramTranspor
333 return _c334 return _c
334 }335 }
335 336
336-func (_c *MockDatagramTransport_Recv_Call) Return(frame ports.Frame, err error) *MockDatagramTransport_Recv_Call {337+func (_c *MockDatagramTransport_Recv_Call) Return(frame wire.Frame, err error) *MockDatagramTransport_Recv_Call {
337 _c.Call.Return(frame, err)338 _c.Call.Return(frame, err)
338 return _c339 return _c
339 }340 }
340 341
341-func (_c *MockDatagramTransport_Recv_Call) RunAndReturn(run func() (ports.Frame, error)) *MockDatagramTransport_Recv_Call {342+func (_c *MockDatagramTransport_Recv_Call) RunAndReturn(run func() (wire.Frame, error)) *MockDatagramTransport_Recv_Call {
342 _c.Call.Return(run)343 _c.Call.Return(run)
343 return _c344 return _c
344 }345 }
345 346
346 // Send provides a mock function for the type MockDatagramTransport347 // Send provides a mock function for the type MockDatagramTransport
347-func (_mock *MockDatagramTransport) Send(frame ports.Frame) error {348+func (_mock *MockDatagramTransport) Send(frame wire.Frame) error {
348 ret := _mock.Called(frame)349 ret := _mock.Called(frame)
349 350
350 if len(ret) == 0 {351 if len(ret) == 0 {
@@ -352,7 +353,7 @@ func (_mock *MockDatagramTransport) Send(frame ports.Frame) error {
352 }353 }
353 354
354 var r0 error355 var r0 error
355- if returnFunc, ok := ret.Get(0).(func(ports.Frame) error); ok {356+ if returnFunc, ok := ret.Get(0).(func(wire.Frame) error); ok {
356 r0 = returnFunc(frame)357 r0 = returnFunc(frame)
357 } else {358 } else {
358 r0 = ret.Error(0)359 r0 = ret.Error(0)
@@ -366,16 +367,16 @@ type MockDatagramTransport_Send_Call struct {
366 }367 }
367 368
368 // Send is a helper method to define mock.On call369 // Send is a helper method to define mock.On call
369-// - frame ports.Frame370+// - frame wire.Frame
370 func (_e *MockDatagramTransport_Expecter) Send(frame any) *MockDatagramTransport_Send_Call {371 func (_e *MockDatagramTransport_Expecter) Send(frame any) *MockDatagramTransport_Send_Call {
371 return &MockDatagramTransport_Send_Call{Call: _e.mock.On("Send", frame)}372 return &MockDatagramTransport_Send_Call{Call: _e.mock.On("Send", frame)}
372 }373 }
373 374
374-func (_c *MockDatagramTransport_Send_Call) Run(run func(frame ports.Frame)) *MockDatagramTransport_Send_Call {375+func (_c *MockDatagramTransport_Send_Call) Run(run func(frame wire.Frame)) *MockDatagramTransport_Send_Call {
375 _c.Call.Run(func(args mock.Arguments) {376 _c.Call.Run(func(args mock.Arguments) {
376- var arg0 ports.Frame377+ var arg0 wire.Frame
377 if args[0] != nil {378 if args[0] != nil {
378- arg0 = args[0].(ports.Frame)379+ arg0 = args[0].(wire.Frame)
379 }380 }
380 run(381 run(
381 arg0,382 arg0,
@@ -389,7 +390,7 @@ func (_c *MockDatagramTransport_Send_Call) Return(err error) *MockDatagramTransp
389 return _c390 return _c
390 }391 }
391 392
392-func (_c *MockDatagramTransport_Send_Call) RunAndReturn(run func(frame ports.Frame) error) *MockDatagramTransport_Send_Call {393+func (_c *MockDatagramTransport_Send_Call) RunAndReturn(run func(frame wire.Frame) error) *MockDatagramTransport_Send_Call {
393 _c.Call.Return(run)394 _c.Call.Return(run)
394 return _c395 return _c
395 }396 }
@@ -422,7 +423,7 @@ func (_m *MockAsyncTransport) EXPECT() *MockAsyncTransport_Expecter {
422 }423 }
423 424
424 // SendAsync provides a mock function for the type MockAsyncTransport425 // SendAsync provides a mock function for the type MockAsyncTransport
425-func (_mock *MockAsyncTransport) SendAsync(frame ports.Frame) error {426+func (_mock *MockAsyncTransport) SendAsync(frame wire.Frame) error {
426 ret := _mock.Called(frame)427 ret := _mock.Called(frame)
427 428
428 if len(ret) == 0 {429 if len(ret) == 0 {
@@ -430,7 +431,7 @@ func (_mock *MockAsyncTransport) SendAsync(frame ports.Frame) error {
430 }431 }
431 432
432 var r0 error433 var r0 error
433- if returnFunc, ok := ret.Get(0).(func(ports.Frame) error); ok {434+ if returnFunc, ok := ret.Get(0).(func(wire.Frame) error); ok {
434 r0 = returnFunc(frame)435 r0 = returnFunc(frame)
435 } else {436 } else {
436 r0 = ret.Error(0)437 r0 = ret.Error(0)
@@ -444,16 +445,16 @@ type MockAsyncTransport_SendAsync_Call struct {
444 }445 }
445 446
446 // SendAsync is a helper method to define mock.On call447 // SendAsync is a helper method to define mock.On call
447-// - frame ports.Frame448+// - frame wire.Frame
448 func (_e *MockAsyncTransport_Expecter) SendAsync(frame any) *MockAsyncTransport_SendAsync_Call {449 func (_e *MockAsyncTransport_Expecter) SendAsync(frame any) *MockAsyncTransport_SendAsync_Call {
449 return &MockAsyncTransport_SendAsync_Call{Call: _e.mock.On("SendAsync", frame)}450 return &MockAsyncTransport_SendAsync_Call{Call: _e.mock.On("SendAsync", frame)}
450 }451 }
451 452
452-func (_c *MockAsyncTransport_SendAsync_Call) Run(run func(frame ports.Frame)) *MockAsyncTransport_SendAsync_Call {453+func (_c *MockAsyncTransport_SendAsync_Call) Run(run func(frame wire.Frame)) *MockAsyncTransport_SendAsync_Call {
453 _c.Call.Run(func(args mock.Arguments) {454 _c.Call.Run(func(args mock.Arguments) {
454- var arg0 ports.Frame455+ var arg0 wire.Frame
455 if args[0] != nil {456 if args[0] != nil {
456- arg0 = args[0].(ports.Frame)457+ arg0 = args[0].(wire.Frame)
457 }458 }
458 run(459 run(
459 arg0,460 arg0,
@@ -467,7 +468,7 @@ func (_c *MockAsyncTransport_SendAsync_Call) Return(err error) *MockAsyncTranspo
467 return _c468 return _c
468 }469 }
469 470
470-func (_c *MockAsyncTransport_SendAsync_Call) RunAndReturn(run func(frame ports.Frame) error) *MockAsyncTransport_SendAsync_Call {471+func (_c *MockAsyncTransport_SendAsync_Call) RunAndReturn(run func(frame wire.Frame) error) *MockAsyncTransport_SendAsync_Call {
471 _c.Call.Return(run)472 _c.Call.Return(run)
472 return _c473 return _c
473 }474 }
@@ -500,7 +501,7 @@ func (_m *MockOwnedSynchronousTransport) EXPECT() *MockOwnedSynchronousTransport
500 }501 }
501 502
502 // SendSynchronous provides a mock function for the type MockOwnedSynchronousTransport503 // SendSynchronous provides a mock function for the type MockOwnedSynchronousTransport
503-func (_mock *MockOwnedSynchronousTransport) SendSynchronous(frame ports.Frame) error {504+func (_mock *MockOwnedSynchronousTransport) SendSynchronous(frame wire.Frame) error {
504 ret := _mock.Called(frame)505 ret := _mock.Called(frame)
505 506
506 if len(ret) == 0 {507 if len(ret) == 0 {
@@ -508,7 +509,7 @@ func (_mock *MockOwnedSynchronousTransport) SendSynchronous(frame ports.Frame) e
508 }509 }
509 510
510 var r0 error511 var r0 error
511- if returnFunc, ok := ret.Get(0).(func(ports.Frame) error); ok {512+ if returnFunc, ok := ret.Get(0).(func(wire.Frame) error); ok {
512 r0 = returnFunc(frame)513 r0 = returnFunc(frame)
513 } else {514 } else {
514 r0 = ret.Error(0)515 r0 = ret.Error(0)
@@ -522,16 +523,16 @@ type MockOwnedSynchronousTransport_SendSynchronous_Call struct {
522 }523 }
523 524
524 // SendSynchronous is a helper method to define mock.On call525 // SendSynchronous is a helper method to define mock.On call
525-// - frame ports.Frame526+// - frame wire.Frame
526 func (_e *MockOwnedSynchronousTransport_Expecter) SendSynchronous(frame any) *MockOwnedSynchronousTransport_SendSynchronous_Call {527 func (_e *MockOwnedSynchronousTransport_Expecter) SendSynchronous(frame any) *MockOwnedSynchronousTransport_SendSynchronous_Call {
527 return &MockOwnedSynchronousTransport_SendSynchronous_Call{Call: _e.mock.On("SendSynchronous", frame)}528 return &MockOwnedSynchronousTransport_SendSynchronous_Call{Call: _e.mock.On("SendSynchronous", frame)}
528 }529 }
529 530
530-func (_c *MockOwnedSynchronousTransport_SendSynchronous_Call) Run(run func(frame ports.Frame)) *MockOwnedSynchronousTransport_SendSynchronous_Call {531+func (_c *MockOwnedSynchronousTransport_SendSynchronous_Call) Run(run func(frame wire.Frame)) *MockOwnedSynchronousTransport_SendSynchronous_Call {
531 _c.Call.Run(func(args mock.Arguments) {532 _c.Call.Run(func(args mock.Arguments) {
532- var arg0 ports.Frame533+ var arg0 wire.Frame
533 if args[0] != nil {534 if args[0] != nil {
534- arg0 = args[0].(ports.Frame)535+ arg0 = args[0].(wire.Frame)
535 }536 }
536 run(537 run(
537 arg0,538 arg0,
@@ -545,7 +546,7 @@ func (_c *MockOwnedSynchronousTransport_SendSynchronous_Call) Return(err error)
545 return _c546 return _c
546 }547 }
547 548
548-func (_c *MockOwnedSynchronousTransport_SendSynchronous_Call) RunAndReturn(run func(frame ports.Frame) error) *MockOwnedSynchronousTransport_SendSynchronous_Call {549+func (_c *MockOwnedSynchronousTransport_SendSynchronous_Call) RunAndReturn(run func(frame wire.Frame) error) *MockOwnedSynchronousTransport_SendSynchronous_Call {
549 _c.Call.Return(run)550 _c.Call.Return(run)
550 return _c551 return _c
551 }552 }
M internal/ports/transport.go +9 -5
@@ -1,13 +1,17 @@
1 package ports1 package ports
2 2
3-import "context"3+import (
4+ "context"
5+
6+ "github.com/bnema/vev/internal/protocol/wire"
7+)
4 8
5 // Transport is a framed message channel over a single connection. Close must9 // Transport is a framed message channel over a single connection. Close must
6 // be safe to call concurrently with Send and Recv, and must unblock active10 // be safe to call concurrently with Send and Recv, and must unblock active
7 // Send and Recv calls.11 // Send and Recv calls.
8 type Transport interface {12 type Transport interface {
9- Send(Frame) error13+ Send(wire.Frame) error
10- Recv() (Frame, error) // blocking; io.EOF on close14+ Recv() (wire.Frame, error) // blocking; io.EOF on close
11 Close() error15 Close() error
12 }16 }
13 17
@@ -22,13 +26,13 @@ type DatagramTransport interface {
22 // returns once the adapter owns the frame; Send retains its synchronous wire-26 // returns once the adapter owns the frame; Send retains its synchronous wire-
23 // attempt contract. Daemon paint output may use this capability to pipeline.27 // attempt contract. Daemon paint output may use this capability to pipeline.
24 type AsyncTransport interface {28 type AsyncTransport interface {
25- SendAsync(Frame) error29+ SendAsync(wire.Frame) error
26 }30 }
27 31
28 // OwnedSynchronousTransport owns the complete bounded synchronous operation,32 // OwnedSynchronousTransport owns the complete bounded synchronous operation,
29 // including adapter queues, pacing, write deadlines, and close cancellation.33 // including adapter queues, pacing, write deadlines, and close cancellation.
30 type OwnedSynchronousTransport interface {34 type OwnedSynchronousTransport interface {
31- SendSynchronous(Frame) error35+ SendSynchronous(wire.Frame) error
32 }36 }
33 37
34 // Dialer establishes outbound Transport connections.38 // Dialer establishes outbound Transport connections.
R internal/ports/frame.go → internal/protocol/wire/frame.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package ports1+package wire
2 2
3 // MaxFrameLen is the largest permitted frame length, including the type byte3 // MaxFrameLen is the largest permitted frame length, including the type byte
4 // and excluding the four-byte length prefix.4 // and excluding the four-byte length prefix.
R internal/ports/frame_test.go → internal/protocol/wire/frame_test.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package ports1+package wire
2 2
3 import (3 import (
4 "testing"4 "testing"
R internal/ports/navigation_test.go → internal/protocol/wire/navigation_test.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package ports1+package wire
2 2
3 import (3 import (
4 "encoding/hex"4 "encoding/hex"
R internal/ports/output_compression_test.go → internal/protocol/wire/output_compression_test.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package ports1+package wire
2 2
3 import (3 import (
4 "bytes"4 "bytes"
R internal/ports/remote_preview_hardening_test.go → internal/protocol/wire/remote_preview_hardening_test.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package ports1+package wire
2 2
3 import (3 import (
4 "bytes"4 "bytes"
R internal/ports/remote_preview_test.go → internal/protocol/wire/remote_preview_test.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package ports1+package wire
2 2
3 import (3 import (
4 "testing"4 "testing"
R internal/ports/remote_target_wire_test.go → internal/protocol/wire/remote_target_wire_test.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package ports1+package wire
2 2
3 import (3 import (
4 "bytes"4 "bytes"
R internal/ports/routes_test.go → internal/protocol/wire/routes_test.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package ports1+package wire
2 2
3 import (3 import (
4 "encoding/hex"4 "encoding/hex"
R internal/ports/routes_wire.go → internal/protocol/wire/routes_wire.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package ports1+package wire
2 2
3 import (3 import (
4 "fmt"4 "fmt"
R internal/ports/same_peer_switch_test.go → internal/protocol/wire/same_peer_switch_test.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package ports1+package wire
2 2
3 import (3 import (
4 "bytes"4 "bytes"
R internal/ports/wire.go → internal/protocol/wire/wire.go +3 -6 renamed, 99% similar
@@ -1,10 +1,7 @@
1-// Wire codecs for vev's IPC message payloads. They live in ports (alongside1+// Package wire owns vev's binary frame representation and strict payload
2-// Frame and the MsgType constants) because wire encoding is protocol2+// codecs. It performs no I/O; carriage adapters transport Frame values.
3-// surface, not I/O: everything here is pure, stdlib-only byte marshalling.
4-// Adapters keep what actually performs I/O (framing over a connection,
5-// listening on a socket) and import these types for the payloads they carry.
6 3
7-package ports4+package wire
8 5
9 import (6 import (
10 "bytes"7 "bytes"
R internal/ports/wire_command.go → internal/protocol/wire/wire_command.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package ports1+package wire
2 2
3 import (3 import (
4 "encoding/binary"4 "encoding/binary"
R internal/ports/wire_final_test.go → internal/protocol/wire/wire_final_test.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package ports1+package wire
2 2
3 import (3 import (
4 "bytes"4 "bytes"
R internal/ports/wire_test.go → internal/protocol/wire/wire_test.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package ports1+package wire
2 2
3 import (3 import (
4 "bytes"4 "bytes"
M internal/testutil/replaytest/replay.go +12 -12
@@ -8,31 +8,31 @@ import (
8 "testing"8 "testing"
9 9
10 "github.com/bnema/vev/internal/domain"10 "github.com/bnema/vev/internal/domain"
11- "github.com/bnema/vev/internal/ports"
12 "github.com/bnema/vev/internal/protocol"11 "github.com/bnema/vev/internal/protocol"
12+ "github.com/bnema/vev/internal/protocol/wire"
13 )13 )
14 14
15 func mustMarshalOutput(m protocol.Output) []byte {15 func mustMarshalOutput(m protocol.Output) []byte {
16- payload, err := ports.MarshalOutput(m)16+ payload, err := wire.MarshalOutput(m)
17 if err != nil {17 if err != nil {
18 panic(err)18 panic(err)
19 }19 }
20 return payload20 return payload
21 }21 }
22 22
23-var transcript = []ports.Frame{23+var transcript = []wire.Frame{
24- {Type: ports.MsgOutput, Payload: mustMarshalOutput(protocol.Output{Epoch: 1, Base: 0, New: 1, Size: domain.Size{Cols: 80, Rows: 24}, Full: true, Data: []byte("\x1b[2J\x1b[Hone\r\ntwo")})},24+ {Type: wire.MsgOutput, Payload: mustMarshalOutput(protocol.Output{Epoch: 1, Base: 0, New: 1, Size: domain.Size{Cols: 80, Rows: 24}, Full: true, Data: []byte("\x1b[2J\x1b[Hone\r\ntwo")})},
25- {Type: ports.MsgOutput, Payload: mustMarshalOutput(protocol.Output{Epoch: 1, Base: 1, New: 2, Echo: 7, Size: domain.Size{Cols: 80, Rows: 24}, Data: []byte("\x1b[2;1HTWO")})},25+ {Type: wire.MsgOutput, Payload: mustMarshalOutput(protocol.Output{Epoch: 1, Base: 1, New: 2, Echo: 7, Size: domain.Size{Cols: 80, Rows: 24}, Data: []byte("\x1b[2;1HTWO")})},
26 }26 }
27 27
28 // Transcript returns a deep copy of the canonical replay transcript so an28 // Transcript returns a deep copy of the canonical replay transcript so an
29 // adapter cannot mutate the fixture observed by another adapter.29 // adapter cannot mutate the fixture observed by another adapter.
30-func Transcript() []ports.Frame {30+func Transcript() []wire.Frame {
31 return cloneFrames(transcript)31 return cloneFrames(transcript)
32 }32 }
33 33
34 // Exchange carries already-composed frames through one transport adapter.34 // Exchange carries already-composed frames through one transport adapter.
35-type Exchange func(t *testing.T, frames []ports.Frame) []ports.Frame35+type Exchange func(t *testing.T, frames []wire.Frame) []wire.Frame
36 36
37 // Run verifies that an adapter preserves the canonical transcript exactly.37 // Run verifies that an adapter preserves the canonical transcript exactly.
38 func Run(t *testing.T, exchange Exchange) {38 func Run(t *testing.T, exchange Exchange) {
@@ -50,12 +50,12 @@ func Run(t *testing.T, exchange Exchange) {
50 t.Errorf("frame %d payload = %x, want byte-for-byte %x", i, got[i].Payload, want[i].Payload)50 t.Errorf("frame %d payload = %x, want byte-for-byte %x", i, got[i].Payload, want[i].Payload)
51 continue51 continue
52 }52 }
53- gotOutput, err := ports.UnmarshalOutput(got[i].Payload)53+ gotOutput, err := wire.UnmarshalOutput(got[i].Payload)
54 if err != nil {54 if err != nil {
55 t.Errorf("frame %d output decode: %v", i, err)55 t.Errorf("frame %d output decode: %v", i, err)
56 continue56 continue
57 }57 }
58- wantOutput, err := ports.UnmarshalOutput(want[i].Payload)58+ wantOutput, err := wire.UnmarshalOutput(want[i].Payload)
59 if err != nil {59 if err != nil {
60 t.Fatalf("canonical frame %d output decode: %v", i, err)60 t.Fatalf("canonical frame %d output decode: %v", i, err)
61 }61 }
@@ -65,10 +65,10 @@ func Run(t *testing.T, exchange Exchange) {
65 }65 }
66 }66 }
67 67
68-func cloneFrames(frames []ports.Frame) []ports.Frame {68+func cloneFrames(frames []wire.Frame) []wire.Frame {
69- cloned := make([]ports.Frame, len(frames))69+ cloned := make([]wire.Frame, len(frames))
70 for i, frame := range frames {70 for i, frame := range frames {
71- cloned[i] = ports.Frame{Type: frame.Type, Payload: append([]byte(nil), frame.Payload...)}71+ cloned[i] = wire.Frame{Type: frame.Type, Payload: append([]byte(nil), frame.Payload...)}
72 }72 }
73 return cloned73 return cloned
74 }74 }
M internal/usecase/client/ack_queue_test.go +26 -26
@@ -7,20 +7,20 @@ import (
7 "testing"7 "testing"
8 "time"8 "time"
9 9
10- "github.com/bnema/vev/internal/ports"10+ "github.com/bnema/vev/internal/protocol/wire"
11 "github.com/stretchr/testify/require"11 "github.com/stretchr/testify/require"
12 )12 )
13 13
14-type ackRecordingTransport struct{ sent chan ports.Frame }14+type ackRecordingTransport struct{ sent chan wire.Frame }
15 15
16-func (t *ackRecordingTransport) Send(f ports.Frame) error { t.sent <- f; return nil }16+func (t *ackRecordingTransport) Send(f wire.Frame) error { t.sent <- f; return nil }
17-func (t *ackRecordingTransport) Recv() (ports.Frame, error) { return ports.Frame{}, io.EOF }17+func (t *ackRecordingTransport) Recv() (wire.Frame, error) { return wire.Frame{}, io.EOF }
18-func (t *ackRecordingTransport) Close() error { return nil }18+func (t *ackRecordingTransport) Close() error { return nil }
19 19
20 func TestCumulativeAckBypassesFullNormalSendQueue(t *testing.T) {20 func TestCumulativeAckBypassesFullNormalSendQueue(t *testing.T) {
21- normal := make(chan ports.Frame, sendQueueDepth)21+ normal := make(chan wire.Frame, sendQueueDepth)
22 for range cap(normal) {22 for range cap(normal) {
23- normal <- ports.Frame{Type: ports.MsgInput}23+ normal <- wire.Frame{Type: wire.MsgInput}
24 }24 }
25 const epoch = 725 const epoch = 7
26 acks := newCumulativeAckQueue()26 acks := newCumulativeAckQueue()
@@ -30,13 +30,13 @@ func TestCumulativeAckBypassesFullNormalSendQueue(t *testing.T) {
30 30
31 ctx, cancel := context.WithCancel(context.Background())31 ctx, cancel := context.WithCancel(context.Background())
32 defer cancel()32 defer cancel()
33- tr := &ackRecordingTransport{sent: make(chan ports.Frame, sendQueueDepth+1)}33+ tr := &ackRecordingTransport{sent: make(chan wire.Frame, sendQueueDepth+1)}
34 errCh := make(chan error, 1)34 errCh := make(chan error, 1)
35 go runSender(ctx, cancel, tr, nil, nil, normal, nil, acks, errCh, slog.Default())35 go runSender(ctx, cancel, tr, nil, nil, normal, nil, acks, errCh, slog.Default())
36 36
37 first := <-tr.sent37 first := <-tr.sent
38- require.Equal(t, ports.MsgAck, first.Type)38+ require.Equal(t, wire.MsgAck, first.Type)
39- ack, err := ports.UnmarshalAck(first.Payload)39+ ack, err := wire.UnmarshalAck(first.Payload)
40 require.NoError(t, err)40 require.NoError(t, err)
41 require.Equal(t, uint64(epoch), ack.Epoch)41 require.Equal(t, uint64(epoch), ack.Epoch)
42 require.Equal(t, uint64(maxUnackedOutputStatesForTest), ack.State)42 require.Equal(t, uint64(maxUnackedOutputStatesForTest), ack.State)
@@ -45,21 +45,21 @@ func TestCumulativeAckBypassesFullNormalSendQueue(t *testing.T) {
45 func TestSamePeerSwitchControlBypassesHeldInput(t *testing.T) {45 func TestSamePeerSwitchControlBypassesHeldInput(t *testing.T) {
46 ctx, cancel := context.WithCancel(context.Background())46 ctx, cancel := context.WithCancel(context.Background())
47 defer cancel()47 defer cancel()
48- normal := make(chan ports.Frame, 1)48+ normal := make(chan wire.Frame, 1)
49- control := make(chan ports.Frame, 1)49+ control := make(chan wire.Frame, 1)
50 gate := newSamePeerInputGate()50 gate := newSamePeerInputGate()
51 gate.setPaused(true)51 gate.setPaused(true)
52 held := make(chan struct{})52 held := make(chan struct{})
53 gate.afterInputHeld = func() { close(held) }53 gate.afterInputHeld = func() { close(held) }
54- transport := &ackRecordingTransport{sent: make(chan ports.Frame, 2)}54+ transport := &ackRecordingTransport{sent: make(chan wire.Frame, 2)}
55 acks := newCumulativeAckQueue()55 acks := newCumulativeAckQueue()
56 errs := make(chan error, 1)56 errs := make(chan error, 1)
57 go runSender(ctx, cancel, transport, control, nil, normal, gate, acks, errs, slog.Default())57 go runSender(ctx, cancel, transport, control, nil, normal, gate, acks, errs, slog.Default())
58 58
59- normal <- ports.Frame{Type: ports.MsgInput, Payload: []byte("held")}59+ normal <- wire.Frame{Type: wire.MsgInput, Payload: []byte("held")}
60 awaitSenderSignal(t, held)60 awaitSenderSignal(t, held)
61- control <- ports.Frame{Type: ports.MsgSamePeerSwitchRequest, Payload: []byte("switch")}61+ control <- wire.Frame{Type: wire.MsgSamePeerSwitchRequest, Payload: []byte("switch")}
62- if got := awaitSenderFrame(t, transport.sent); got.Type != ports.MsgSamePeerSwitchRequest {62+ if got := awaitSenderFrame(t, transport.sent); got.Type != wire.MsgSamePeerSwitchRequest {
63 t.Fatalf("first frame = %d, want same-peer switch request", got.Type)63 t.Fatalf("first frame = %d, want same-peer switch request", got.Type)
64 }64 }
65 select {65 select {
@@ -68,7 +68,7 @@ func TestSamePeerSwitchControlBypassesHeldInput(t *testing.T) {
68 default:68 default:
69 }69 }
70 gate.setPaused(false)70 gate.setPaused(false)
71- if got := awaitSenderFrame(t, transport.sent); got.Type != ports.MsgInput {71+ if got := awaitSenderFrame(t, transport.sent); got.Type != wire.MsgInput {
72 t.Fatalf("released frame = %d, want input", got.Type)72 t.Fatalf("released frame = %d, want input", got.Type)
73 }73 }
74 }74 }
@@ -76,14 +76,14 @@ func TestSamePeerSwitchControlBypassesHeldInput(t *testing.T) {
76 func TestSenderBarrierLeavesPausedInputQueued(t *testing.T) {76 func TestSenderBarrierLeavesPausedInputQueued(t *testing.T) {
77 ctx, cancel := context.WithCancel(context.Background())77 ctx, cancel := context.WithCancel(context.Background())
78 defer cancel()78 defer cancel()
79- normal := make(chan ports.Frame, 1)79+ normal := make(chan wire.Frame, 1)
80 barriers := make(chan chan struct{})80 barriers := make(chan chan struct{})
81 gate := newSamePeerInputGate()81 gate := newSamePeerInputGate()
82 gate.setPaused(true)82 gate.setPaused(true)
83- transport := &ackRecordingTransport{sent: make(chan ports.Frame, 1)}83+ transport := &ackRecordingTransport{sent: make(chan wire.Frame, 1)}
84 go runSender(ctx, cancel, transport, nil, barriers, normal, gate, newCumulativeAckQueue(), make(chan error, 1), slog.Default())84 go runSender(ctx, cancel, transport, nil, barriers, normal, gate, newCumulativeAckQueue(), make(chan error, 1), slog.Default())
85 85
86- normal <- ports.Frame{Type: ports.MsgInput, Payload: []byte("held")}86+ normal <- wire.Frame{Type: wire.MsgInput, Payload: []byte("held")}
87 barrierDone := make(chan struct{})87 barrierDone := make(chan struct{})
88 barriers <- barrierDone88 barriers <- barrierDone
89 awaitSenderSignal(t, barrierDone)89 awaitSenderSignal(t, barrierDone)
@@ -94,7 +94,7 @@ func TestSenderBarrierLeavesPausedInputQueued(t *testing.T) {
94 }94 }
95 95
96 gate.setPaused(false)96 gate.setPaused(false)
97- if got := awaitSenderFrame(t, transport.sent); got.Type != ports.MsgInput {97+ if got := awaitSenderFrame(t, transport.sent); got.Type != wire.MsgInput {
98 t.Fatalf("released frame = %d, want input", got.Type)98 t.Fatalf("released frame = %d, want input", got.Type)
99 }99 }
100 }100 }
@@ -104,14 +104,14 @@ func TestSenderBarrierFlushesPendingAck(t *testing.T) {
104 defer cancel()104 defer cancel()
105 acks := newCumulativeAckQueue()105 acks := newCumulativeAckQueue()
106 acks.offer(3, 9)106 acks.offer(3, 9)
107- transport := &ackRecordingTransport{sent: make(chan ports.Frame, 1)}107+ transport := &ackRecordingTransport{sent: make(chan wire.Frame, 1)}
108 barriers := make(chan chan struct{})108 barriers := make(chan chan struct{})
109- go runSender(ctx, cancel, transport, nil, barriers, make(chan ports.Frame, 1), newSamePeerInputGate(), acks, make(chan error, 1), slog.Default())109+ go runSender(ctx, cancel, transport, nil, barriers, make(chan wire.Frame, 1), newSamePeerInputGate(), acks, make(chan error, 1), slog.Default())
110 110
111 done := make(chan struct{})111 done := make(chan struct{})
112 barriers <- done112 barriers <- done
113 frame := awaitSenderFrame(t, transport.sent)113 frame := awaitSenderFrame(t, transport.sent)
114- require.Equal(t, ports.MsgAck, frame.Type)114+ require.Equal(t, wire.MsgAck, frame.Type)
115 awaitSenderSignal(t, done)115 awaitSenderSignal(t, done)
116 }116 }
117 117
@@ -124,14 +124,14 @@ func awaitSenderSignal(t *testing.T, signal <-chan struct{}) {
124 }124 }
125 }125 }
126 126
127-func awaitSenderFrame(t *testing.T, frames <-chan ports.Frame) ports.Frame {127+func awaitSenderFrame(t *testing.T, frames <-chan wire.Frame) wire.Frame {
128 t.Helper()128 t.Helper()
129 select {129 select {
130 case frame := <-frames:130 case frame := <-frames:
131 return frame131 return frame
132 case <-time.After(time.Second):132 case <-time.After(time.Second):
133 t.Fatal("sender did not emit frame")133 t.Fatal("sender did not emit frame")
134- return ports.Frame{}134+ return wire.Frame{}
135 }135 }
136 }136 }
137 137
M internal/usecase/client/client.go +74 -73
@@ -24,6 +24,7 @@ import (
24 "github.com/bnema/vev/internal/domain"24 "github.com/bnema/vev/internal/domain"
25 "github.com/bnema/vev/internal/ports"25 "github.com/bnema/vev/internal/ports"
26 "github.com/bnema/vev/internal/protocol"26 "github.com/bnema/vev/internal/protocol"
27+ "github.com/bnema/vev/internal/protocol/wire"
27 "github.com/bnema/vev/internal/usecase/theme"28 "github.com/bnema/vev/internal/usecase/theme"
28 )29 )
29 30
@@ -1243,14 +1244,14 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
1243 Remote: remote,1244 Remote: remote,
1244 }1245 }
1245 if err := sendHandshake(func() error {1246 if err := sendHandshake(func() error {
1246- return transport.Send(ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(hello)})1247+ return transport.Send(wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(hello)})
1247 }); err != nil {1248 }); err != nil {
1248 return handshakeFailure("sending hello", err)1249 return handshakeFailure("sending hello", err)
1249 }1250 }
1250 ms.helloSent = true1251 ms.helloSent = true
1251 1252
1252 // 2. Await Welcome or a typed rejection.1253 // 2. Await Welcome or a typed rejection.
1253- var reply ports.Frame1254+ var reply wire.Frame
1254 if err := boundedHandshakeOperationWithTransition(handshakeCtx, transport, func() error {1255 if err := boundedHandshakeOperationWithTransition(handshakeCtx, transport, func() error {
1255 var recvErr error1256 var recvErr error
1256 reply, recvErr = transport.Recv()1257 reply, recvErr = transport.Recv()
@@ -1271,8 +1272,8 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
1271 }1272 }
1272 }1273 }
1273 switch reply.Type {1274 switch reply.Type {
1274- case ports.MsgWelcome:1275+ case wire.MsgWelcome:
1275- welcome, derr := ports.UnmarshalWelcome(reply.Payload)1276+ welcome, derr := wire.UnmarshalWelcome(reply.Payload)
1276 if derr != nil {1277 if derr != nil {
1277 return result(false, fmt.Errorf("vev: decoding welcome: %w", derr))1278 return result(false, fmt.Errorf("vev: decoding welcome: %w", derr))
1278 }1279 }
@@ -1286,8 +1287,8 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
1286 remember()1287 remember()
1287 }1288 }
1288 }1289 }
1289- case ports.MsgError:1290+ case wire.MsgError:
1290- em, derr := ports.UnmarshalErrorMsg(reply.Payload)1291+ em, derr := wire.UnmarshalErrorMsg(reply.Payload)
1291 if derr != nil {1292 if derr != nil {
1292 return result(false, fmt.Errorf("vev: decoding error reply: %w", derr))1293 return result(false, fmt.Errorf("vev: decoding error reply: %w", derr))
1293 }1294 }
@@ -1319,30 +1320,30 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
1319 return welcomedResult(fmt.Errorf("vev: committing route identity: %w", commitErr))1320 return welcomedResult(fmt.Errorf("vev: committing route identity: %w", commitErr))
1320 }1321 }
1321 }1322 }
1322- snapshotPayload, err := ports.MarshalRecentRouteSnapshot(a.runner.ledger.snapshot())1323+ snapshotPayload, err := wire.MarshalRecentRouteSnapshot(a.runner.ledger.snapshot())
1323 if err != nil {1324 if err != nil {
1324 return welcomedResult(fmt.Errorf("vev: encoding route snapshot: %w", err))1325 return welcomedResult(fmt.Errorf("vev: encoding route snapshot: %w", err))
1325 }1326 }
1326- attentionPayload, err := ports.MarshalRouteAttentionSubscription(a.runner.ledger.attentionSubscription())1327+ attentionPayload, err := wire.MarshalRouteAttentionSubscription(a.runner.ledger.attentionSubscription())
1327 if err != nil {1328 if err != nil {
1328 return welcomedResult(fmt.Errorf("vev: encoding route attention subscription: %w", err))1329 return welcomedResult(fmt.Errorf("vev: encoding route attention subscription: %w", err))
1329 }1330 }
1330 if err := sendHandshake(func() error {1331 if err := sendHandshake(func() error {
1331- if err := transport.Send(ports.Frame{Type: ports.MsgRecentRouteSnapshot, Payload: snapshotPayload}); err != nil {1332+ if err := transport.Send(wire.Frame{Type: wire.MsgRecentRouteSnapshot, Payload: snapshotPayload}); err != nil {
1332 return err1333 return err
1333 }1334 }
1334- return transport.Send(ports.Frame{Type: ports.MsgRouteAttentionSubscription, Payload: attentionPayload})1335+ return transport.Send(wire.Frame{Type: wire.MsgRouteAttentionSubscription, Payload: attentionPayload})
1335 }); err != nil {1336 }); err != nil {
1336 return welcomedResult(fmt.Errorf("vev: publishing route snapshot: %w", err))1337 return welcomedResult(fmt.Errorf("vev: publishing route snapshot: %w", err))
1337 }1338 }
1338 }1339 }
1339 if a.runner.routeFailure != nil {1340 if a.runner.routeFailure != nil {
1340- payload, err := ports.MarshalRouteNavigationFailure(*a.runner.routeFailure)1341+ payload, err := wire.MarshalRouteNavigationFailure(*a.runner.routeFailure)
1341 if err != nil {1342 if err != nil {
1342 return welcomedResult(fmt.Errorf("vev: encoding route navigation failure: %w", err))1343 return welcomedResult(fmt.Errorf("vev: encoding route navigation failure: %w", err))
1343 }1344 }
1344 if err := sendHandshake(func() error {1345 if err := sendHandshake(func() error {
1345- return transport.Send(ports.Frame{Type: ports.MsgRouteNavigationFailure, Payload: payload})1346+ return transport.Send(wire.Frame{Type: wire.MsgRouteNavigationFailure, Payload: payload})
1346 }); err != nil {1347 }); err != nil {
1347 return welcomedResult(fmt.Errorf("vev: publishing route navigation failure: %w", err))1348 return welcomedResult(fmt.Errorf("vev: publishing route navigation failure: %w", err))
1348 }1349 }
@@ -1369,8 +1370,8 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
1369 input.start()1370 input.start()
1370 defer input.stop()1371 defer input.stop()
1371 }1372 }
1372- sendCh := make(chan ports.Frame, sendQueueDepth)1373+ sendCh := make(chan wire.Frame, sendQueueDepth)
1373- controlCh := make(chan ports.Frame, 1)1374+ controlCh := make(chan wire.Frame, 1)
1374 barrierCh := make(chan chan struct{})1375 barrierCh := make(chan chan struct{})
1375 inputGate := newSamePeerInputGate()1376 inputGate := newSamePeerInputGate()
1376 ackQueue := newCumulativeAckQueue()1377 ackQueue := newCumulativeAckQueue()
@@ -1393,7 +1394,7 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
1393 }1394 }
1394 }1395 }
1395 publish := func(snapshot protocol.Theme) error {1396 publish := func(snapshot protocol.Theme) error {
1396- frame := ports.Frame{Type: ports.MsgTheme, Payload: ports.MarshalTheme(snapshot)}1397+ frame := wire.Frame{Type: wire.MsgTheme, Payload: wire.MarshalTheme(snapshot)}
1397 // The initial cleared snapshot is sent before the sender exists. This1398 // The initial cleared snapshot is sent before the sender exists. This
1398 // keeps the generation publication ordered before the first query and1399 // keeps the generation publication ordered before the first query and
1399 // avoids leaving a queued Theme behind an immediate detach.1400 // avoids leaving a queued Theme behind an immediate detach.
@@ -1503,11 +1504,11 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
1503 // Before the asynchronous sender starts, preserve transport ordering by1504 // Before the asynchronous sender starts, preserve transport ordering by
1504 // requesting the reset synchronously after the initial Theme publication.1505 // requesting the reset synchronously after the initial Theme publication.
1505 resetErr := sendHandshake(func() error {1506 resetErr := sendHandshake(func() error {
1506- payload, err := ports.MarshalResize(protocol.Resize{Size: geometry.Size, PixelWidth: geometry.PixelWidth, PixelHeight: geometry.PixelHeight})1507+ payload, err := wire.MarshalResize(protocol.Resize{Size: geometry.Size, PixelWidth: geometry.PixelWidth, PixelHeight: geometry.PixelHeight})
1507 if err != nil {1508 if err != nil {
1508 return err1509 return err
1509 }1510 }
1510- return transport.Send(ports.Frame{Type: ports.MsgResize, Payload: payload})1511+ return transport.Send(wire.Frame{Type: wire.MsgResize, Payload: payload})
1511 })1512 })
1512 if resetErr != nil {1513 if resetErr != nil {
1513 resetErr = handshakeContextError(ctx, handshakeTimedOut, resetErr)1514 resetErr = handshakeContextError(ctx, handshakeTimedOut, resetErr)
@@ -1629,12 +1630,12 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
1629 return fmt.Errorf("reading terminal geometry for parked route: %w", err)1630 return fmt.Errorf("reading terminal geometry for parked route: %w", err)
1630 }1631 }
1631 geometry = geometry.NormalizePixels()1632 geometry = geometry.NormalizePixels()
1632- payload, err := ports.MarshalResize(protocol.Resize{Size: geometry.Size, PixelWidth: geometry.PixelWidth, PixelHeight: geometry.PixelHeight})1633+ payload, err := wire.MarshalResize(protocol.Resize{Size: geometry.Size, PixelWidth: geometry.PixelWidth, PixelHeight: geometry.PixelHeight})
1633 if err != nil {1634 if err != nil {
1634 return fmt.Errorf("encoding terminal size for parked route: %w", err)1635 return fmt.Errorf("encoding terminal size for parked route: %w", err)
1635 }1636 }
1636 select {1637 select {
1637- case controlCh <- ports.Frame{Type: ports.MsgResize, Payload: payload}:1638+ case controlCh <- wire.Frame{Type: wire.MsgResize, Payload: payload}:
1638 return nil1639 return nil
1639 case <-loopCtx.Done():1640 case <-loopCtx.Done():
1640 return context.Canceled1641 return context.Canceled
@@ -1646,12 +1647,12 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
1646 }1647 }
1647 nextParkedRequestID++1648 nextParkedRequestID++
1648 request := protocol.ParkedRouteRequest{RequestID: nextParkedRequestID, LeaseID: leaseID, Action: action, Target: target}1649 request := protocol.ParkedRouteRequest{RequestID: nextParkedRequestID, LeaseID: leaseID, Action: action, Target: target}
1649- payload := ports.MarshalParkedRouteRequest(request)1650+ payload := wire.MarshalParkedRouteRequest(request)
1650 if payload == nil {1651 if payload == nil {
1651 return errors.New("vev: invalid parked-route request")1652 return errors.New("vev: invalid parked-route request")
1652 }1653 }
1653 select {1654 select {
1654- case controlCh <- ports.Frame{Type: ports.MsgParkedRouteRequest, Payload: payload}:1655+ case controlCh <- wire.Frame{Type: wire.MsgParkedRouteRequest, Payload: payload}:
1655 parkedPending = &parkedRoutePending{1656 parkedPending = &parkedRoutePending{
1656 requestID: request.RequestID, action: action, leaseID: leaseID,1657 requestID: request.RequestID, action: action, leaseID: leaseID,
1657 timer: clk.NewTimer(protocol.HandshakeTimeout),1658 timer: clk.NewTimer(protocol.HandshakeTimeout),
@@ -1726,12 +1727,12 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
1726 return fmt.Errorf("reading terminal geometry for reconnect reconciliation: %w", serr)1727 return fmt.Errorf("reading terminal geometry for reconnect reconciliation: %w", serr)
1727 }1728 }
1728 geometry = geometry.NormalizePixels()1729 geometry = geometry.NormalizePixels()
1729- payload, err := ports.MarshalResize(protocol.Resize{Size: geometry.Size, PixelWidth: geometry.PixelWidth, PixelHeight: geometry.PixelHeight})1730+ payload, err := wire.MarshalResize(protocol.Resize{Size: geometry.Size, PixelWidth: geometry.PixelWidth, PixelHeight: geometry.PixelHeight})
1730 if err != nil {1731 if err != nil {
1731 return fmt.Errorf("encoding terminal size for reconnect reconciliation: %w", err)1732 return fmt.Errorf("encoding terminal size for reconnect reconciliation: %w", err)
1732 }1733 }
1733 select {1734 select {
1734- case sendCh <- ports.Frame{Type: ports.MsgResize, Payload: payload}:1735+ case sendCh <- wire.Frame{Type: wire.MsgResize, Payload: payload}:
1735 awaitingReconnectReset = true1736 awaitingReconnectReset = true
1736 return nil1737 return nil
1737 case <-loopCtx.Done():1738 case <-loopCtx.Done():
@@ -1742,17 +1743,17 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
1742 if a.runner.ledger == nil {1743 if a.runner.ledger == nil {
1743 return errors.New("vev: route ledger unavailable")1744 return errors.New("vev: route ledger unavailable")
1744 }1745 }
1745- snapshotPayload, err := ports.MarshalRecentRouteSnapshot(a.runner.ledger.snapshot())1746+ snapshotPayload, err := wire.MarshalRecentRouteSnapshot(a.runner.ledger.snapshot())
1746 if err != nil {1747 if err != nil {
1747 return err1748 return err
1748 }1749 }
1749- attentionPayload, err := ports.MarshalRouteAttentionSubscription(a.runner.ledger.attentionSubscription())1750+ attentionPayload, err := wire.MarshalRouteAttentionSubscription(a.runner.ledger.attentionSubscription())
1750 if err != nil {1751 if err != nil {
1751 return err1752 return err
1752 }1753 }
1753- for _, frame := range []ports.Frame{1754+ for _, frame := range []wire.Frame{
1754- {Type: ports.MsgRecentRouteSnapshot, Payload: snapshotPayload},1755+ {Type: wire.MsgRecentRouteSnapshot, Payload: snapshotPayload},
1755- {Type: ports.MsgRouteAttentionSubscription, Payload: attentionPayload},1756+ {Type: wire.MsgRouteAttentionSubscription, Payload: attentionPayload},
1756 } {1757 } {
1757 select {1758 select {
1758 case sendCh <- frame:1759 case sendCh <- frame:
@@ -1763,7 +1764,7 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
1763 return nil1764 return nil
1764 }1765 }
1765 sendRouteFailure := func(action protocol.RouteNavigationAction, code protocol.RouteFailureCode) error {1766 sendRouteFailure := func(action protocol.RouteNavigationAction, code protocol.RouteFailureCode) error {
1766- payload, err := ports.MarshalRouteNavigationFailure(protocol.RouteNavigationFailure{1767+ payload, err := wire.MarshalRouteNavigationFailure(protocol.RouteNavigationFailure{
1767 Key: action.Key,1768 Key: action.Key,
1768 Generation: action.Generation,1769 Generation: action.Generation,
1769 Code: code,1770 Code: code,
@@ -1772,7 +1773,7 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
1772 return err1773 return err
1773 }1774 }
1774 select {1775 select {
1775- case sendCh <- ports.Frame{Type: ports.MsgRouteNavigationFailure, Payload: payload}:1776+ case sendCh <- wire.Frame{Type: wire.MsgRouteNavigationFailure, Payload: payload}:
1776 return nil1777 return nil
1777 case <-loopCtx.Done():1778 case <-loopCtx.Done():
1778 return context.Canceled1779 return context.Canceled
@@ -1827,7 +1828,7 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
1827 return welcomedResult(fmt.Errorf("dismissing reconnect toast: %w", err))1828 return welcomedResult(fmt.Errorf("dismissing reconnect toast: %w", err))
1828 }1829 }
1829 select {1830 select {
1830- case sendCh <- ports.Frame{Type: ports.MsgClientNotice, Payload: ports.MarshalClientNotice(protocol.ClientNotice{Action: protocol.ClientNoticeLinkConnected})}:1831+ case sendCh <- wire.Frame{Type: wire.MsgClientNotice, Payload: wire.MarshalClientNotice(protocol.ClientNotice{Action: protocol.ClientNoticeLinkConnected})}:
1831 case <-loopCtx.Done():1832 case <-loopCtx.Done():
1832 return welcomedResult(nil)1833 return welcomedResult(nil)
1833 }1834 }
@@ -1836,7 +1837,7 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
1836 if ev.State == ports.LinkStateDegraded {1837 if ev.State == ports.LinkStateDegraded {
1837 log.Warn("UDP link degraded")1838 log.Warn("UDP link degraded")
1838 select {1839 select {
1839- case sendCh <- ports.Frame{Type: ports.MsgClientNotice, Payload: ports.MarshalClientNotice(protocol.ClientNotice{Action: protocol.ClientNoticeLinkDegraded})}:1840+ case sendCh <- wire.Frame{Type: wire.MsgClientNotice, Payload: wire.MarshalClientNotice(protocol.ClientNotice{Action: protocol.ClientNoticeLinkDegraded})}:
1840 case <-loopCtx.Done():1841 case <-loopCtx.Done():
1841 return welcomedResult(nil)1842 return welcomedResult(nil)
1842 }1843 }
@@ -1884,8 +1885,8 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
1884 return welcomedResult(fmt.Errorf("vev: receiving from daemon: %w", r.err))1885 return welcomedResult(fmt.Errorf("vev: receiving from daemon: %w", r.err))
1885 }1886 }
1886 switch r.frame.Type {1887 switch r.frame.Type {
1887- case ports.MsgOutput:1888+ case wire.MsgOutput:
1888- o, derr := ports.UnmarshalOutput(r.frame.Payload)1889+ o, derr := wire.UnmarshalOutput(r.frame.Payload)
1889 if derr != nil {1890 if derr != nil {
1890 return welcomedResult(fmt.Errorf("vev: decoding output: %w", derr))1891 return welcomedResult(fmt.Errorf("vev: decoding output: %w", derr))
1891 }1892 }
@@ -1896,7 +1897,7 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
1896 // repeated gaps while that reset is in flight.1897 // repeated gaps while that reset is in flight.
1897 if !outputResetRequested {1898 if !outputResetRequested {
1898 select {1899 select {
1899- case sendCh <- ports.Frame{Type: ports.MsgOutputResetRequest, Payload: ports.MarshalOutputResetRequest(protocol.OutputResetRequest{})}:1900+ case sendCh <- wire.Frame{Type: wire.MsgOutputResetRequest, Payload: wire.MarshalOutputResetRequest(protocol.OutputResetRequest{})}:
1900 outputResetRequested = true1901 outputResetRequested = true
1901 case <-loopCtx.Done():1902 case <-loopCtx.Done():
1902 return loopCanceledResult()1903 return loopCanceledResult()
@@ -1946,8 +1947,8 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
1946 log.Debug("received first output")1947 log.Debug("received first output")
1947 endHandshake()1948 endHandshake()
1948 }1949 }
1949- case ports.MsgAttachTarget:1950+ case wire.MsgAttachTarget:
1950- target, derr := ports.UnmarshalAttachTarget(r.frame.Payload)1951+ target, derr := wire.UnmarshalAttachTarget(r.frame.Payload)
1951 if derr != nil {1952 if derr != nil {
1952 return welcomedResult(fmt.Errorf("vev: decoding attach target: %w", derr))1953 return welcomedResult(fmt.Errorf("vev: decoding attach target: %w", derr))
1953 }1954 }
@@ -1966,7 +1967,7 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
1966 }1967 }
1967 nextSamePeerRequestID++1968 nextSamePeerRequestID++
1968 request := a.runner.ledger.samePeerHandoff(request, target)1969 request := a.runner.ledger.samePeerHandoff(request, target)
1969- payload, err := ports.MarshalSamePeerSwitchRequest(protocol.SamePeerSwitchRequest{1970+ payload, err := wire.MarshalSamePeerSwitchRequest(protocol.SamePeerSwitchRequest{
1970 RequestID: nextSamePeerRequestID,1971 RequestID: nextSamePeerRequestID,
1971 Target: *target.ExactTarget,1972 Target: *target.ExactTarget,
1972 PreferredTabID: request.PreferredTabID,1973 PreferredTabID: request.PreferredTabID,
@@ -1977,7 +1978,7 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
1977 samePeerSwitch = &samePeerSwitchPending{requestID: nextSamePeerRequestID, target: *target.ExactTarget}1978 samePeerSwitch = &samePeerSwitchPending{requestID: nextSamePeerRequestID, target: *target.ExactTarget}
1978 inputGate.setPaused(true)1979 inputGate.setPaused(true)
1979 select {1980 select {
1980- case controlCh <- ports.Frame{Type: ports.MsgSamePeerSwitchRequest, Payload: payload}:1981+ case controlCh <- wire.Frame{Type: wire.MsgSamePeerSwitchRequest, Payload: payload}:
1981 continue1982 continue
1982 case <-loopCtx.Done():1983 case <-loopCtx.Done():
1983 return loopCanceledResult()1984 return loopCanceledResult()
@@ -1986,8 +1987,8 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
1986 handoff := welcomedResult(nil)1987 handoff := welcomedResult(nil)
1987 handoff.target = &target1988 handoff.target = &target
1988 return handoff1989 return handoff
1989- case ports.MsgParkedRouteResponse:1990+ case wire.MsgParkedRouteResponse:
1990- response, derr := ports.UnmarshalParkedRouteResponse(r.frame.Payload)1991+ response, derr := wire.UnmarshalParkedRouteResponse(r.frame.Payload)
1991 if derr != nil {1992 if derr != nil {
1992 return welcomedResult(fmt.Errorf("vev: decoding parked-route response: %w", derr))1993 return welcomedResult(fmt.Errorf("vev: decoding parked-route response: %w", derr))
1993 }1994 }
@@ -2033,8 +2034,8 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
2033 return fallback2034 return fallback
2034 }2035 }
2035 }2036 }
2036- case ports.MsgNavigationAction:2037+ case wire.MsgNavigationAction:
2037- directive, derr := ports.UnmarshalNavigationDirective(r.frame.Payload)2038+ directive, derr := wire.UnmarshalNavigationDirective(r.frame.Payload)
2038 if derr != nil {2039 if derr != nil {
2039 return welcomedResult(fmt.Errorf("vev: decoding navigation action: %w", derr))2040 return welcomedResult(fmt.Errorf("vev: decoding navigation action: %w", derr))
2040 }2041 }
@@ -2052,8 +2053,8 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
2052 navigation := welcomedResult(nil)2053 navigation := welcomedResult(nil)
2053 navigation.action = directive.Action2054 navigation.action = directive.Action
2054 return navigation2055 return navigation
2055- case ports.MsgNavigateRecentRoute:2056+ case wire.MsgNavigateRecentRoute:
2056- action, derr := ports.UnmarshalRouteNavigationAction(r.frame.Payload)2057+ action, derr := wire.UnmarshalRouteNavigationAction(r.frame.Payload)
2057 if derr != nil {2058 if derr != nil {
2058 return welcomedResult(fmt.Errorf("vev: decoding route navigation action: %w", derr))2059 return welcomedResult(fmt.Errorf("vev: decoding route navigation action: %w", derr))
2059 }2060 }
@@ -2076,8 +2077,8 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
2076 navigation := welcomedResult(nil)2077 navigation := welcomedResult(nil)
2077 navigation.routeAction = &action2078 navigation.routeAction = &action
2078 return navigation2079 return navigation
2079- case ports.MsgCommittedRouteIdentity:2080+ case wire.MsgCommittedRouteIdentity:
2080- identity, derr := ports.UnmarshalCommittedRouteIdentity(r.frame.Payload)2081+ identity, derr := wire.UnmarshalCommittedRouteIdentity(r.frame.Payload)
2081 if derr != nil {2082 if derr != nil {
2082 return welcomedResult(fmt.Errorf("vev: decoding committed route identity: %w", derr))2083 return welcomedResult(fmt.Errorf("vev: decoding committed route identity: %w", derr))
2083 }2084 }
@@ -2099,8 +2100,8 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
2099 samePeerSwitch = nil2100 samePeerSwitch = nil
2100 inputGate.setPaused(false)2101 inputGate.setPaused(false)
2101 }2102 }
2102- case ports.MsgSamePeerSwitchFailure:2103+ case wire.MsgSamePeerSwitchFailure:
2103- failure, derr := ports.UnmarshalSamePeerSwitchFailure(r.frame.Payload)2104+ failure, derr := wire.UnmarshalSamePeerSwitchFailure(r.frame.Payload)
2104 if derr != nil {2105 if derr != nil {
2105 return welcomedResult(fmt.Errorf("vev: decoding same-peer switch failure: %w", derr))2106 return welcomedResult(fmt.Errorf("vev: decoding same-peer switch failure: %w", derr))
2106 }2107 }
@@ -2111,7 +2112,7 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
2111 transitionWaitingFull = true2112 transitionWaitingFull = true
2112 if !outputResetRequested {2113 if !outputResetRequested {
2113 select {2114 select {
2114- case sendCh <- ports.Frame{Type: ports.MsgOutputResetRequest, Payload: ports.MarshalOutputResetRequest(protocol.OutputResetRequest{})}:2115+ case sendCh <- wire.Frame{Type: wire.MsgOutputResetRequest, Payload: wire.MarshalOutputResetRequest(protocol.OutputResetRequest{})}:
2115 outputResetRequested = true2116 outputResetRequested = true
2116 case <-loopCtx.Done():2117 case <-loopCtx.Done():
2117 return loopCanceledResult()2118 return loopCanceledResult()
@@ -2121,8 +2122,8 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
2121 transition.stop()2122 transition.stop()
2122 }2123 }
2123 }2124 }
2124- case ports.MsgRoutePosition:2125+ case wire.MsgRoutePosition:
2125- position, derr := ports.UnmarshalRoutePosition(r.frame.Payload)2126+ position, derr := wire.UnmarshalRoutePosition(r.frame.Payload)
2126 if derr != nil {2127 if derr != nil {
2127 return welcomedResult(fmt.Errorf("vev: decoding route position: %w", derr))2128 return welcomedResult(fmt.Errorf("vev: decoding route position: %w", derr))
2128 }2129 }
@@ -2133,14 +2134,14 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
2133 return welcomedResult(fmt.Errorf("vev: remembering route position: %w", derr))2134 return welcomedResult(fmt.Errorf("vev: remembering route position: %w", derr))
2134 }2135 }
2135 routePosition = cloneRoutePosition(&position)2136 routePosition = cloneRoutePosition(&position)
2136- case ports.MsgRouteNavigationFailure:2137+ case wire.MsgRouteNavigationFailure:
2137- failure, derr := ports.UnmarshalRouteNavigationFailure(r.frame.Payload)2138+ failure, derr := wire.UnmarshalRouteNavigationFailure(r.frame.Payload)
2138 if derr != nil {2139 if derr != nil {
2139 return welcomedResult(fmt.Errorf("vev: decoding route navigation failure: %w", derr))2140 return welcomedResult(fmt.Errorf("vev: decoding route navigation failure: %w", derr))
2140 }2141 }
2141 log.Warn("route navigation rejected", "key", failure.Key, "generation", failure.Generation, "code", failure.Code)2142 log.Warn("route navigation rejected", "key", failure.Key, "generation", failure.Generation, "code", failure.Code)
2142- case ports.MsgDetached:2143+ case wire.MsgDetached:
2143- d, derr := ports.UnmarshalDetached(r.frame.Payload)2144+ d, derr := wire.UnmarshalDetached(r.frame.Payload)
2144 if derr != nil {2145 if derr != nil {
2145 return welcomedResult(fmt.Errorf("vev: decoding detached: %w", derr))2146 return welcomedResult(fmt.Errorf("vev: decoding detached: %w", derr))
2146 }2147 }
@@ -2151,7 +2152,7 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
2151 log.Warn("detached by daemon", "reason", d.Reason)2152 log.Warn("detached by daemon", "reason", d.Reason)
2152 }2153 }
2153 return welcomedResult(detachedResult(d.Reason))2154 return welcomedResult(detachedResult(d.Reason))
2154- case ports.MsgPong:2155+ case wire.MsgPong:
2155 // Liveness reply; nothing to do.2156 // Liveness reply; nothing to do.
2156 default:2157 default:
2157 // Unknown/out-of-band message types are ignored so a newer2158 // Unknown/out-of-band message types are ignored so a newer
@@ -2164,7 +2165,7 @@ func (a *attachAttempt) run(ctx context.Context) attachResult {
2164 // recvResult carries one framed message (or a read error) from the receive2165 // recvResult carries one framed message (or a read error) from the receive
2165 // pump to the main loop.2166 // pump to the main loop.
2166 type recvResult struct {2167 type recvResult struct {
2167- frame ports.Frame2168+ frame wire.Frame
2168 err error2169 err error
2169 }2170 }
2170 2171
@@ -2227,9 +2228,9 @@ func (q *cumulativeAckQueue) take() (uint64, uint64) {
2227 2228
2228 // runSender preserves normal-frame order while allowing switch control and2229 // runSender preserves normal-frame order while allowing switch control and
2229 // output ACKs to make progress when raw input is held for a same-peer switch.2230 // output ACKs to make progress when raw input is held for a same-peer switch.
2230-func runSender(ctx context.Context, cancel context.CancelFunc, transport ports.Transport, control <-chan ports.Frame, barriers <-chan chan struct{}, in <-chan ports.Frame, inputGate *samePeerInputGate, acks *cumulativeAckQueue, errCh chan<- error, log *slog.Logger) {2231+func runSender(ctx context.Context, cancel context.CancelFunc, transport ports.Transport, control <-chan wire.Frame, barriers <-chan chan struct{}, in <-chan wire.Frame, inputGate *samePeerInputGate, acks *cumulativeAckQueue, errCh chan<- error, log *slog.Logger) {
2231 defer log.Debug("sender pump exited")2232 defer log.Debug("sender pump exited")
2232- send := func(f ports.Frame) bool {2233+ send := func(f wire.Frame) bool {
2233 if err := transport.Send(f); err != nil {2234 if err := transport.Send(f); err != nil {
2234 select {2235 select {
2235 case errCh <- err:2236 case errCh <- err:
@@ -2245,7 +2246,7 @@ func runSender(ctx context.Context, cancel context.CancelFunc, transport ports.T
2245 if state == 0 {2246 if state == 0 {
2246 return true2247 return true
2247 }2248 }
2248- payload, err := ports.MarshalAck(protocol.Ack{Epoch: epoch, State: state})2249+ payload, err := wire.MarshalAck(protocol.Ack{Epoch: epoch, State: state})
2249 if err != nil {2250 if err != nil {
2250 select {2251 select {
2251 case errCh <- fmt.Errorf("encoding output ACK: %w", err):2252 case errCh <- fmt.Errorf("encoding output ACK: %w", err):
@@ -2254,7 +2255,7 @@ func runSender(ctx context.Context, cancel context.CancelFunc, transport ports.T
2254 cancel()2255 cancel()
2255 return false2256 return false
2256 }2257 }
2257- return send(ports.Frame{Type: ports.MsgAck, Payload: payload})2258+ return send(wire.Frame{Type: wire.MsgAck, Payload: payload})
2258 }2259 }
2259 flushPending := func() bool {2260 flushPending := func() bool {
2260 for {2261 for {
@@ -2286,7 +2287,7 @@ func runSender(ctx context.Context, cancel context.CancelFunc, transport ports.T
2286 close(done)2287 close(done)
2287 return true2288 return true
2288 }2289 }
2289- var heldInput *ports.Frame2290+ var heldInput *wire.Frame
2290 for {2291 for {
2291 if heldInput != nil {2292 if heldInput != nil {
2292 paused, changed := inputGate.snapshot()2293 paused, changed := inputGate.snapshot()
@@ -2353,7 +2354,7 @@ func runSender(ctx context.Context, cancel context.CancelFunc, transport ports.T
2353 return2354 return
2354 default:2355 default:
2355 }2356 }
2356- if frame.Type == ports.MsgInput || frame.Type == ports.MsgImagePush {2357+ if frame.Type == wire.MsgInput || frame.Type == wire.MsgImagePush {
2357 if paused, _ := inputGate.snapshot(); paused {2358 if paused, _ := inputGate.snapshot(); paused {
2358 held := frame2359 held := frame
2359 heldInput = &held2360 heldInput = &held
@@ -2725,7 +2726,7 @@ type stdinPump struct {
2725 in io.Reader // compatibility for isolated scanner tests2726 in io.Reader // compatibility for isolated scanner tests
2726 input *terminalInputPump2727 input *terminalInputPump
2727 consumer uint642728 consumer uint64
2728- out chan<- ports.Frame2729+ out chan<- wire.Frame
2729 clock ports.Clock2730 clock ports.Clock
2730 clipboard ports.ClipboardReader2731 clipboard ports.ClipboardReader
2731 logger *slog.Logger2732 logger *slog.Logger
@@ -2746,7 +2747,7 @@ func (p *stdinPump) run() {
2746 var undeliveredMu sync.Mutex2747 var undeliveredMu sync.Mutex
2747 var undelivered []byte2748 var undelivered []byte
2748 sendOK.Store(true)2749 sendOK.Store(true)
2749- send := func(frame ports.Frame) bool {2750+ send := func(frame wire.Frame) bool {
2750 if !sendOK.Load() {2751 if !sendOK.Load() {
2751 return false2752 return false
2752 }2753 }
@@ -2785,7 +2786,7 @@ func (p *stdinPump) run() {
2785 return2786 return
2786 }2787 }
2787 inputSeq++2788 inputSeq++
2788- if !send(ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(protocol.Input{InputSeq: inputSeq, Data: append([]byte(nil), data...)})}) {2789+ if !send(wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(protocol.Input{InputSeq: inputSeq, Data: append([]byte(nil), data...)})}) {
2789 undeliveredMu.Lock()2790 undeliveredMu.Lock()
2790 undelivered = append(undelivered, data...)2791 undelivered = append(undelivered, data...)
2791 undeliveredMu.Unlock()2792 undeliveredMu.Unlock()
@@ -2799,11 +2800,11 @@ func (p *stdinPump) run() {
2799 reader: p.clipboard,2800 reader: p.clipboard,
2800 log: p.logger,2801 log: p.logger,
2801 sendNotice: func(action uint8) {2802 sendNotice: func(action uint8) {
2802- send(ports.Frame{Type: ports.MsgClientNotice, Payload: ports.MarshalClientNotice(protocol.ClientNotice{Action: action})})2803+ send(wire.Frame{Type: wire.MsgClientNotice, Payload: wire.MarshalClientNotice(protocol.ClientNotice{Action: action})})
2803 },2804 },
2804 sendImage: func(mime string, data []byte) {2805 sendImage: func(mime string, data []byte) {
2805 inputSeq++2806 inputSeq++
2806- send(ports.Frame{Type: ports.MsgImagePush, Payload: ports.MarshalImagePush(protocol.ImagePush{InputSeq: inputSeq, Mime: mime, Data: data})})2807+ send(wire.Frame{Type: wire.MsgImagePush, Payload: wire.MarshalImagePush(protocol.ImagePush{InputSeq: inputSeq, Mime: mime, Data: data})})
2807 },2808 },
2808 next: coalescer.Scan,2809 next: coalescer.Scan,
2809 }2810 }
@@ -2945,7 +2946,7 @@ func (p *stdinPump) run() {
2945 disarmMarkerDeadline()2946 disarmMarkerDeadline()
2946 markers.flush(sink)2947 markers.flush(sink)
2947 select {2948 select {
2948- case p.out <- ports.Frame{Type: ports.MsgDetach, Payload: ports.MarshalDetach(protocol.Detach{})}:2949+ case p.out <- wire.Frame{Type: wire.MsgDetach, Payload: wire.MarshalDetach(protocol.Detach{})}:
2949 case <-p.ctx.Done():2950 case <-p.ctx.Done():
2950 }2951 }
2951 p.cancel()2952 p.cancel()
@@ -2966,7 +2967,7 @@ func (p *stdinPump) run() {
2966 // runResize forwards coalesced terminal resize events to the daemon. It2967 // runResize forwards coalesced terminal resize events to the daemon. It
2967 // tolerates an already-closed resize channel (which the terminal adapter2968 // tolerates an already-closed resize channel (which the terminal adapter
2968 // hands back when restore ran before ResizeEvents was first called).2969 // hands back when restore ran before ResizeEvents was first called).
2969-func runResize(ctx context.Context, events <-chan domain.Geometry, out chan<- ports.Frame, sendLease *foregroundSendLease, log *slog.Logger) {2970+func runResize(ctx context.Context, events <-chan domain.Geometry, out chan<- wire.Frame, sendLease *foregroundSendLease, log *slog.Logger) {
2970 defer log.Debug("resize pump exited")2971 defer log.Debug("resize pump exited")
2971 for {2972 for {
2972 select {2973 select {
@@ -2975,12 +2976,12 @@ func runResize(ctx context.Context, events <-chan domain.Geometry, out chan<- po
2975 return2976 return
2976 }2977 }
2977 geometry = geometry.NormalizePixels()2978 geometry = geometry.NormalizePixels()
2978- payload, err := ports.MarshalResize(protocol.Resize{Size: geometry.Size, PixelWidth: geometry.PixelWidth, PixelHeight: geometry.PixelHeight})2979+ payload, err := wire.MarshalResize(protocol.Resize{Size: geometry.Size, PixelWidth: geometry.PixelWidth, PixelHeight: geometry.PixelHeight})
2979 if err != nil {2980 if err != nil {
2980 log.Error("encoding terminal resize", "error", err)2981 log.Error("encoding terminal resize", "error", err)
2981 continue2982 continue
2982 }2983 }
2983- frame := ports.Frame{Type: ports.MsgResize, Payload: payload}2984+ frame := wire.Frame{Type: wire.MsgResize, Payload: payload}
2984 if !sendLease.send(func() bool {2985 if !sendLease.send(func() bool {
2985 select {2986 select {
2986 case out <- frame:2987 case out <- frame:
M internal/usecase/client/client_test.go +198 -197
@@ -19,6 +19,7 @@ import (
19 "github.com/bnema/vev/internal/ports"19 "github.com/bnema/vev/internal/ports"
20 portsmocks "github.com/bnema/vev/internal/ports/mocks"20 portsmocks "github.com/bnema/vev/internal/ports/mocks"
21 "github.com/bnema/vev/internal/protocol"21 "github.com/bnema/vev/internal/protocol"
22+ "github.com/bnema/vev/internal/protocol/wire"
22 "github.com/bnema/vev/internal/usecase/client"23 "github.com/bnema/vev/internal/usecase/client"
23 )24 )
24 25
@@ -117,7 +118,7 @@ func (t *reconnectTestTimer) Stop() bool {
117 118
118 // recvItem is one scripted Recv result.119 // recvItem is one scripted Recv result.
119 type recvItem struct {120 type recvItem struct {
120- f ports.Frame121+ f wire.Frame
121 err error122 err error
122 wait <-chan struct{}123 wait <-chan struct{}
123 }124 }
@@ -135,26 +136,26 @@ func scriptRecv(tr *portsmocks.MockTransport, items ...recvItem) func() {
135 ch <- it136 ch <- it
136 }137 }
137 done := make(chan struct{})138 done := make(chan struct{})
138- tr.EXPECT().Recv().RunAndReturn(func() (ports.Frame, error) {139+ tr.EXPECT().Recv().RunAndReturn(func() (wire.Frame, error) {
139 select {140 select {
140 case it := <-ch:141 case it := <-ch:
141 if it.wait != nil {142 if it.wait != nil {
142 select {143 select {
143 case <-it.wait:144 case <-it.wait:
144 case <-done:145 case <-done:
145- return ports.Frame{}, io.EOF146+ return wire.Frame{}, io.EOF
146 }147 }
147 }148 }
148 return it.f, it.err149 return it.f, it.err
149 case <-done:150 case <-done:
150- return ports.Frame{}, io.EOF151+ return wire.Frame{}, io.EOF
151 }152 }
152 }).Maybe()153 }).Maybe()
153 return func() { close(done) }154 return func() { close(done) }
154 }155 }
155 156
156-func frameOf(t ports.MsgType, payload []byte) ports.Frame {157+func frameOf(t wire.MsgType, payload []byte) wire.Frame {
157- return ports.Frame{Type: t, Payload: payload}158+ return wire.Frame{Type: t, Payload: payload}
158 }159 }
159 160
160 // blockingReader blocks on Read until closed, then returns EOF. Stands in161 // blockingReader blocks on Read until closed, then returns EOF. Stands in
@@ -234,8 +235,8 @@ func newHappyTerminal(t *testing.T, out *bytes.Buffer, restoreCount *atomic.Int3
234 return tm, in235 return tm, in
235 }236 }
236 237
237-func isType(typ ports.MsgType) any {238+func isType(typ wire.MsgType) any {
238- return mock.MatchedBy(func(f ports.Frame) bool { return f.Type == typ })239+ return mock.MatchedBy(func(f wire.Frame) bool { return f.Type == typ })
239 }240 }
240 241
241 // transportDialer adapts one already-open test transport to the Runner API.242 // transportDialer adapts one already-open test transport to the Runner API.
@@ -339,13 +340,13 @@ func TestRunBoundsPreWelcomeOperations(t *testing.T) {
339 return io.ErrClosedPipe340 return io.ErrClosedPipe
340 }341 }
341 if tt.phase == "send" {342 if tt.phase == "send" {
342- tr.EXPECT().Send(isType(ports.MsgHello)).RunAndReturn(func(ports.Frame) error {343+ tr.EXPECT().Send(isType(wire.MsgHello)).RunAndReturn(func(wire.Frame) error {
343 return block()344 return block()
344 }).Once()345 }).Once()
345 } else {346 } else {
346- tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once()347+ tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once()
347- tr.EXPECT().Recv().RunAndReturn(func() (ports.Frame, error) {348+ tr.EXPECT().Recv().RunAndReturn(func() (wire.Frame, error) {
348- return ports.Frame{}, block()349+ return wire.Frame{}, block()
349 }).Once()350 }).Once()
350 }351 }
351 dialer := portsmocks.NewMockDialer(t)352 dialer := portsmocks.NewMockDialer(t)
@@ -384,7 +385,7 @@ func TestRunBoundsPreWelcomeOperations(t *testing.T) {
384 case <-time.After(time.Second):385 case <-time.After(time.Second):
385 t.Fatal("handshake operation leaked after Run returned")386 t.Fatal("handshake operation leaked after Run returned")
386 }387 }
387- tr.AssertNotCalled(t, "Send", isType(ports.MsgTheme))388+ tr.AssertNotCalled(t, "Send", isType(wire.MsgTheme))
388 })389 })
389 }390 }
390 }391 }
@@ -401,17 +402,17 @@ func TestAttachHelloIncludesTrueColor(t *testing.T) {
401 402
402 gotHello := make(chan protocol.Hello, 1)403 gotHello := make(chan protocol.Hello, 1)
403 tr := portsmocks.NewMockTransport(t)404 tr := portsmocks.NewMockTransport(t)
404- tr.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()405+ tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
405- tr.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()406+ tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
406- tr.EXPECT().Send(isType(ports.MsgHello)).RunAndReturn(func(f ports.Frame) error {407+ tr.EXPECT().Send(isType(wire.MsgHello)).RunAndReturn(func(f wire.Frame) error {
407- hello, err := ports.UnmarshalHello(f.Payload)408+ hello, err := wire.UnmarshalHello(f.Payload)
408 require.NoError(t, err)409 require.NoError(t, err)
409 gotHello <- hello410 gotHello <- hello
410 return nil411 return nil
411 }).Once()412 }).Once()
412 unblock := scriptRecv(tr,413 unblock := scriptRecv(tr,
413- recvItem{f: frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},414+ recvItem{f: frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},
414- recvItem{f: frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},415+ recvItem{f: frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},
415 )416 )
416 defer unblock()417 defer unblock()
417 tr.EXPECT().Close().Return(nil).Once()418 tr.EXPECT().Close().Return(nil).Once()
@@ -434,14 +435,14 @@ func TestAttachPublishesCommittedRouteSnapshot(t *testing.T) {
434 defer term.in.unblock()435 defer term.in.unblock()
435 lifecycle := domain.SessionLifecycleID{9, 8, 7}436 lifecycle := domain.SessionLifecycleID{9, 8, 7}
436 tr := &recordingTransport{recvs: []recvItem{437 tr := &recordingTransport{recvs: []recvItem{
437- {f: frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{438+ {f: frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{
438 SessionID: "s1",439 SessionID: "s1",
439 SessionName: "main",440 SessionName: "main",
440 CommittedIdentity: &protocol.CommittedRouteIdentity{441 CommittedIdentity: &protocol.CommittedRouteIdentity{
441 Target: protocol.ExactSessionTarget{LifecycleID: lifecycle, SessionName: "main"},442 Target: protocol.ExactSessionTarget{LifecycleID: lifecycle, SessionName: "main"},
442 },443 },
443 }))},444 }))},
444- {f: frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},445+ {f: frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},
445 }}446 }}
446 447
447 err := runTestClient(context.Background(), attachTestDependencies(tr, term, realClock{}), client.AttachRequest{448 err := runTestClient(context.Background(), attachTestDependencies(tr, term, realClock{}), client.AttachRequest{
@@ -458,14 +459,14 @@ func TestAttachPublishesCommittedRouteSnapshot(t *testing.T) {
458 foundSubscription := false459 foundSubscription := false
459 for _, frame := range tr.Sends() {460 for _, frame := range tr.Sends() {
460 switch frame.Type {461 switch frame.Type {
461- case ports.MsgRecentRouteSnapshot:462+ case wire.MsgRecentRouteSnapshot:
462 var err error463 var err error
463- snapshot, err = ports.UnmarshalRecentRouteSnapshot(frame.Payload)464+ snapshot, err = wire.UnmarshalRecentRouteSnapshot(frame.Payload)
464 require.NoError(t, err)465 require.NoError(t, err)
465 foundSnapshot = true466 foundSnapshot = true
466- case ports.MsgRouteAttentionSubscription:467+ case wire.MsgRouteAttentionSubscription:
467 var err error468 var err error
468- subscription, err = ports.UnmarshalRouteAttentionSubscription(frame.Payload)469+ subscription, err = wire.UnmarshalRouteAttentionSubscription(frame.Payload)
469 require.NoError(t, err)470 require.NoError(t, err)
470 foundSubscription = true471 foundSubscription = true
471 }472 }
@@ -484,8 +485,8 @@ func TestLocalOnlyHandoffBetweenLocalSessionsKeepsClientRunning(t *testing.T) {
484 485
485 firstLifecycle := domain.SessionLifecycleID{1}486 firstLifecycle := domain.SessionLifecycleID{1}
486 secondLifecycle := domain.SessionLifecycleID{2}487 secondLifecycle := domain.SessionLifecycleID{2}
487- welcome := func(name string, lifecycle domain.SessionLifecycleID) ports.Frame {488+ welcome := func(name string, lifecycle domain.SessionLifecycleID) wire.Frame {
488- return frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{489+ return frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{
489 SessionID: name, SessionName: name, ResumeToken: 1, Capabilities: protocol.CapabilityResume,490 SessionID: name, SessionName: name, ResumeToken: 1, Capabilities: protocol.CapabilityResume,
490 CommittedIdentity: &protocol.CommittedRouteIdentity{491 CommittedIdentity: &protocol.CommittedRouteIdentity{
491 Target: protocol.ExactSessionTarget{LifecycleID: lifecycle, SessionName: name},492 Target: protocol.ExactSessionTarget{LifecycleID: lifecycle, SessionName: name},
@@ -495,12 +496,12 @@ func TestLocalOnlyHandoffBetweenLocalSessionsKeepsClientRunning(t *testing.T) {
495 secondTarget := protocol.ExactSessionTarget{LifecycleID: secondLifecycle, SessionName: "second"}496 secondTarget := protocol.ExactSessionTarget{LifecycleID: secondLifecycle, SessionName: "second"}
496 first := &recordingTransport{recvs: []recvItem{497 first := &recordingTransport{recvs: []recvItem{
497 {f: welcome("first", firstLifecycle)},498 {f: welcome("first", firstLifecycle)},
498- {f: frameOf(ports.MsgAttachTarget, ports.MarshalAttachTarget(protocol.AttachTarget{499+ {f: frameOf(wire.MsgAttachTarget, wire.MarshalAttachTarget(protocol.AttachTarget{
499 Session: "second", Intent: protocol.IntentAttach, ExactTarget: &secondTarget,500 Session: "second", Intent: protocol.IntentAttach, ExactTarget: &secondTarget,
500 EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned, SamePeer: true,501 EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned, SamePeer: true,
501 }))},502 }))},
502- {f: frameOf(ports.MsgCommittedRouteIdentity, mustMarshalCommittedIdentity(protocol.CommittedRouteIdentity{Target: secondTarget}))},503+ {f: frameOf(wire.MsgCommittedRouteIdentity, mustMarshalCommittedIdentity(protocol.CommittedRouteIdentity{Target: secondTarget}))},
503- {f: frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},504+ {f: frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},
504 }}505 }}
505 dialer := &sequenceDialer{trs: []ports.Transport{first}}506 dialer := &sequenceDialer{trs: []ports.Transport{first}}
506 507
@@ -530,7 +531,7 @@ func TestAttachHelloPreservesCompleteAttachRequest(t *testing.T) {
530 }531 }
531 tr := &recordingTransport{recvs: []recvItem{532 tr := &recordingTransport{recvs: []recvItem{
532 {f: welcomeFrame(0)},533 {f: welcomeFrame(0)},
533- {f: frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},534+ {f: frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},
534 }}535 }}
535 536
536 require.NoError(t, runTestClient(context.Background(), attachTestDependencies(tr, term, realClock{}), request))537 require.NoError(t, runTestClient(context.Background(), attachTestDependencies(tr, term, realClock{}), request))
@@ -550,8 +551,8 @@ func TestStoppedLocalHandoffDialsReplacementTransport(t *testing.T) {
550 sourceLifecycle := domain.SessionLifecycleID{1}551 sourceLifecycle := domain.SessionLifecycleID{1}
551 targetLifecycle := domain.SessionLifecycleID{2}552 targetLifecycle := domain.SessionLifecycleID{2}
552 target := protocol.ExactSessionTarget{LifecycleID: targetLifecycle, SessionName: "stopped"}553 target := protocol.ExactSessionTarget{LifecycleID: targetLifecycle, SessionName: "stopped"}
553- welcome := func(name string, lifecycle domain.SessionLifecycleID) ports.Frame {554+ welcome := func(name string, lifecycle domain.SessionLifecycleID) wire.Frame {
554- return frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{555+ return frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{
555 SessionID: name, SessionName: name, ResumeToken: 1, Capabilities: protocol.CapabilityResume,556 SessionID: name, SessionName: name, ResumeToken: 1, Capabilities: protocol.CapabilityResume,
556 CommittedIdentity: &protocol.CommittedRouteIdentity{557 CommittedIdentity: &protocol.CommittedRouteIdentity{
557 Target: protocol.ExactSessionTarget{LifecycleID: lifecycle, SessionName: name},558 Target: protocol.ExactSessionTarget{LifecycleID: lifecycle, SessionName: name},
@@ -560,14 +561,14 @@ func TestStoppedLocalHandoffDialsReplacementTransport(t *testing.T) {
560 }561 }
561 first := &recordingTransport{recvs: []recvItem{562 first := &recordingTransport{recvs: []recvItem{
562 {f: welcome("source", sourceLifecycle)},563 {f: welcome("source", sourceLifecycle)},
563- {f: frameOf(ports.MsgAttachTarget, ports.MarshalAttachTarget(protocol.AttachTarget{564+ {f: frameOf(wire.MsgAttachTarget, wire.MarshalAttachTarget(protocol.AttachTarget{
564 Session: "stopped", Intent: protocol.IntentAttach, ExactTarget: &target,565 Session: "stopped", Intent: protocol.IntentAttach, ExactTarget: &target,
565 EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned,566 EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned,
566 }))},567 }))},
567 }}568 }}
568 second := &recordingTransport{recvs: []recvItem{569 second := &recordingTransport{recvs: []recvItem{
569 {f: welcome("stopped", targetLifecycle)},570 {f: welcome("stopped", targetLifecycle)},
570- {f: frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},571+ {f: frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},
571 }}572 }}
572 dialer := &sequenceDialer{trs: []ports.Transport{first, second}}573 dialer := &sequenceDialer{trs: []ports.Transport{first, second}}
573 574
@@ -587,8 +588,8 @@ func TestAcceptedHomeActionUsesCapturedRouteAfterRequestMetadataRebase(t *testin
587 defer term.in.unblock()588 defer term.in.unblock()
588 localLifecycle := domain.SessionLifecycleID{1}589 localLifecycle := domain.SessionLifecycleID{1}
589 remoteLifecycle := domain.SessionLifecycleID{2}590 remoteLifecycle := domain.SessionLifecycleID{2}
590- welcome := func(name string, lifecycle domain.SessionLifecycleID) ports.Frame {591+ welcome := func(name string, lifecycle domain.SessionLifecycleID) wire.Frame {
591- return frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{592+ return frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{
592 SessionID: name, SessionName: name, ResumeToken: 1, Capabilities: protocol.CapabilityResume,593 SessionID: name, SessionName: name, ResumeToken: 1, Capabilities: protocol.CapabilityResume,
593 CommittedIdentity: &protocol.CommittedRouteIdentity{Target: protocol.ExactSessionTarget{LifecycleID: lifecycle, SessionName: name}},594 CommittedIdentity: &protocol.CommittedRouteIdentity{Target: protocol.ExactSessionTarget{LifecycleID: lifecycle, SessionName: name}},
594 }))595 }))
@@ -599,7 +600,7 @@ func TestAcceptedHomeActionUsesCapturedRouteAfterRequestMetadataRebase(t *testin
599 }600 }
600 local1 := &recordingTransport{recvs: []recvItem{601 local1 := &recordingTransport{recvs: []recvItem{
601 {f: welcome("local", localLifecycle)},602 {f: welcome("local", localLifecycle)},
602- {f: frameOf(ports.MsgAttachTarget, ports.MarshalAttachTarget(protocol.AttachTarget{603+ {f: frameOf(wire.MsgAttachTarget, wire.MarshalAttachTarget(protocol.AttachTarget{
603 Endpoint: "remote", Session: "work", Intent: protocol.IntentAttach,604 Endpoint: "remote", Session: "work", Intent: protocol.IntentAttach,
604 RemoteTarget: &remoteTarget, EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned,605 RemoteTarget: &remoteTarget, EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned,
605 }))},606 }))},
@@ -610,7 +611,7 @@ func TestAcceptedHomeActionUsesCapturedRouteAfterRequestMetadataRebase(t *testin
610 }}611 }}
611 local2 := &recordingTransport{recvs: []recvItem{612 local2 := &recordingTransport{recvs: []recvItem{
612 {f: welcome("local", localLifecycle)},613 {f: welcome("local", localLifecycle)},
613- {f: frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},614+ {f: frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},
614 }}615 }}
615 localDialer := &sequenceDialer{trs: []ports.Transport{local1, local2}}616 localDialer := &sequenceDialer{trs: []ports.Transport{local1, local2}}
616 remoteDialer := &sequenceDialer{trs: []ports.Transport{remote}}617 remoteDialer := &sequenceDialer{trs: []ports.Transport{remote}}
@@ -632,8 +633,8 @@ func TestAcceptedHomeActionUsesCapturedRouteAfterRequestMetadataRebase(t *testin
632 require.Equal(t, protocol.StartupOverlaySessionPicker, helloFromSend(t, local2).StartupOverlay)633 require.Equal(t, protocol.StartupOverlaySessionPicker, helloFromSend(t, local2).StartupOverlay)
633 }634 }
634 635
635-func hybridWelcomeFrame(name string, lifecycle domain.SessionLifecycleID) ports.Frame {636+func hybridWelcomeFrame(name string, lifecycle domain.SessionLifecycleID) wire.Frame {
636- return frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{637+ return frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{
637 SessionID: name, SessionName: name, ResumeToken: 1, Capabilities: protocol.CapabilityResume,638 SessionID: name, SessionName: name, ResumeToken: 1, Capabilities: protocol.CapabilityResume,
638 CommittedIdentity: &protocol.CommittedRouteIdentity{Target: protocol.ExactSessionTarget{LifecycleID: lifecycle, SessionName: name}},639 CommittedIdentity: &protocol.CommittedRouteIdentity{Target: protocol.ExactSessionTarget{LifecycleID: lifecycle, SessionName: name}},
639 }))640 }))
@@ -642,7 +643,7 @@ func hybridWelcomeFrame(name string, lifecycle domain.SessionLifecycleID) ports.
642 func hybridLocalBootstrap(lifecycle domain.SessionLifecycleID, target domain.RemoteSessionTarget) *recordingTransport {643 func hybridLocalBootstrap(lifecycle domain.SessionLifecycleID, target domain.RemoteSessionTarget) *recordingTransport {
643 return &recordingTransport{recvs: []recvItem{644 return &recordingTransport{recvs: []recvItem{
644 {f: hybridWelcomeFrame("local", lifecycle)},645 {f: hybridWelcomeFrame("local", lifecycle)},
645- {f: frameOf(ports.MsgAttachTarget, ports.MarshalAttachTarget(protocol.AttachTarget{646+ {f: frameOf(wire.MsgAttachTarget, wire.MarshalAttachTarget(protocol.AttachTarget{
646 Endpoint: target.Endpoint, Session: target.SessionName, Intent: protocol.IntentAttach,647 Endpoint: target.Endpoint, Session: target.SessionName, Intent: protocol.IntentAttach,
647 RemoteTarget: &target, EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned,648 RemoteTarget: &target, EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned,
648 }))},649 }))},
@@ -669,16 +670,16 @@ func hybridPickerDependencies(localDialer ports.Dialer, term ports.Terminal, clo
669 return deps670 return deps
670 }671 }
671 672
672-func hybridParkedRequestHandler(requests chan<- protocol.ParkedRouteRequest, requestErrors chan<- error, signals map[protocol.ParkedRouteAction]chan struct{}) func(ports.Frame) {673+func hybridParkedRequestHandler(requests chan<- protocol.ParkedRouteRequest, requestErrors chan<- error, signals map[protocol.ParkedRouteAction]chan struct{}) func(wire.Frame) {
673 once := make(map[protocol.ParkedRouteAction]*sync.Once, len(signals))674 once := make(map[protocol.ParkedRouteAction]*sync.Once, len(signals))
674 for action := range signals {675 for action := range signals {
675 once[action] = &sync.Once{}676 once[action] = &sync.Once{}
676 }677 }
677- return func(frame ports.Frame) {678+ return func(frame wire.Frame) {
678- if frame.Type != ports.MsgParkedRouteRequest {679+ if frame.Type != wire.MsgParkedRouteRequest {
679 return680 return
680 }681 }
681- request, err := ports.UnmarshalParkedRouteRequest(frame.Payload)682+ request, err := wire.UnmarshalParkedRouteRequest(frame.Payload)
682 if err != nil {683 if err != nil {
683 if requestErrors != nil {684 if requestErrors != nil {
684 requestErrors <- err685 requestErrors <- err
@@ -717,11 +718,11 @@ func TestHybridPickerSameHostSwitchReusesRemoteTransport(t *testing.T) {
717 remote := &recordingTransport{recvs: []recvItem{718 remote := &recordingTransport{recvs: []recvItem{
718 {f: hybridWelcomeFrame("source", remoteSourceLifecycle)},719 {f: hybridWelcomeFrame("source", remoteSourceLifecycle)},
719 {f: navigationDirectiveFrame(protocol.NavigationOpenHomePicker)},720 {f: navigationDirectiveFrame(protocol.NavigationOpenHomePicker)},
720- {f: frameOf(ports.MsgParkedRouteResponse, ports.MarshalParkedRouteResponse(protocol.ParkedRouteResponse{RequestID: 1, Status: protocol.ParkedRouteReady})), wait: parkSent},721+ {f: frameOf(wire.MsgParkedRouteResponse, wire.MarshalParkedRouteResponse(protocol.ParkedRouteResponse{RequestID: 1, Status: protocol.ParkedRouteReady})), wait: parkSent},
721- {f: frameOf(ports.MsgCommittedRouteIdentity, mustMarshalCommittedIdentity(protocol.CommittedRouteIdentity{Target: protocol.ExactSessionTarget{LifecycleID: remoteTargetLifecycle, SessionName: "target"}})), wait: switchSent},722+ {f: frameOf(wire.MsgCommittedRouteIdentity, mustMarshalCommittedIdentity(protocol.CommittedRouteIdentity{Target: protocol.ExactSessionTarget{LifecycleID: remoteTargetLifecycle, SessionName: "target"}})), wait: switchSent},
722- {f: frameOf(ports.MsgParkedRouteResponse, ports.MarshalParkedRouteResponse(protocol.ParkedRouteResponse{RequestID: 2, Status: protocol.ParkedRouteSwitched}))},723+ {f: frameOf(wire.MsgParkedRouteResponse, wire.MarshalParkedRouteResponse(protocol.ParkedRouteResponse{RequestID: 2, Status: protocol.ParkedRouteSwitched}))},
723- {f: frameOf(ports.MsgOutput, mustMarshalOutput(protocol.Output{Epoch: 2, New: 1, Size: domain.Size{Cols: 80, Rows: 24}, Full: true, Data: []byte("target frame")}))},724+ {f: frameOf(wire.MsgOutput, mustMarshalOutput(protocol.Output{Epoch: 2, New: 1, Size: domain.Size{Cols: 80, Rows: 24}, Full: true, Data: []byte("target frame")}))},
724- {f: frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},725+ {f: frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},
725 }}726 }}
726 remote.onSend = hybridParkedRequestHandler(parkedRequests, parkedRequestErrors, map[protocol.ParkedRouteAction]chan struct{}{727 remote.onSend = hybridParkedRequestHandler(parkedRequests, parkedRequestErrors, map[protocol.ParkedRouteAction]chan struct{}{
727 protocol.ParkedRoutePrepare: parkSent,728 protocol.ParkedRoutePrepare: parkSent,
@@ -729,14 +730,14 @@ func TestHybridPickerSameHostSwitchReusesRemoteTransport(t *testing.T) {
729 })730 })
730 localPicker := &recordingTransport{recvs: []recvItem{731 localPicker := &recordingTransport{recvs: []recvItem{
731 {f: hybridWelcomeFrame("local", localLifecycle)},732 {f: hybridWelcomeFrame("local", localLifecycle)},
732- {f: frameOf(ports.MsgAttachTarget, ports.MarshalAttachTarget(protocol.AttachTarget{733+ {f: frameOf(wire.MsgAttachTarget, wire.MarshalAttachTarget(protocol.AttachTarget{
733 Endpoint: "remote", Session: "target", Intent: protocol.IntentAttach,734 Endpoint: "remote", Session: "target", Intent: protocol.IntentAttach,
734 RemoteTarget: &targetTarget, EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned,735 RemoteTarget: &targetTarget, EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned,
735 }))},736 }))},
736 }}737 }}
737 var resizeOnce sync.Once738 var resizeOnce sync.Once
738- localPicker.onSend = func(frame ports.Frame) {739+ localPicker.onSend = func(frame wire.Frame) {
739- if frame.Type == ports.MsgHello {740+ if frame.Type == wire.MsgHello {
740 resizeOnce.Do(func() { term.setSize(domain.Size{Cols: 100, Rows: 40}) })741 resizeOnce.Do(func() { term.setSize(domain.Size{Cols: 100, Rows: 40}) })
741 }742 }
742 }743 }
@@ -763,10 +764,10 @@ func TestHybridPickerSameHostSwitchReusesRemoteTransport(t *testing.T) {
763 frames := remote.Sends()764 frames := remote.Sends()
764 switchIndex := -1765 switchIndex := -1
765 for i, frame := range frames {766 for i, frame := range frames {
766- if frame.Type != ports.MsgParkedRouteRequest {767+ if frame.Type != wire.MsgParkedRouteRequest {
767 continue768 continue
768 }769 }
769- request, requestErr := ports.UnmarshalParkedRouteRequest(frame.Payload)770+ request, requestErr := wire.UnmarshalParkedRouteRequest(frame.Payload)
770 require.NoError(t, requestErr)771 require.NoError(t, requestErr)
771 if request.Action == protocol.ParkedRouteSwitch {772 if request.Action == protocol.ParkedRouteSwitch {
772 switchIndex = i773 switchIndex = i
@@ -774,8 +775,8 @@ func TestHybridPickerSameHostSwitchReusesRemoteTransport(t *testing.T) {
774 }775 }
775 }776 }
776 require.Positive(t, switchIndex)777 require.Positive(t, switchIndex)
777- require.Equal(t, ports.MsgResize, frames[switchIndex-1].Type, "current size must precede the parked switch")778+ require.Equal(t, wire.MsgResize, frames[switchIndex-1].Type, "current size must precede the parked switch")
778- resize, resizeErr := ports.UnmarshalResize(frames[switchIndex-1].Payload)779+ resize, resizeErr := wire.UnmarshalResize(frames[switchIndex-1].Payload)
779 require.NoError(t, resizeErr)780 require.NoError(t, resizeErr)
780 require.Equal(t, domain.Size{Cols: 100, Rows: 40}, resize.Size)781 require.Equal(t, domain.Size{Cols: 100, Rows: 40}, resize.Size)
781 require.Equal(t, int32(1), remoteDialer.calls.Load(), "same-host picker switch must retain the authenticated remote transport")782 require.Equal(t, int32(1), remoteDialer.calls.Load(), "same-host picker switch must retain the authenticated remote transport")
@@ -803,8 +804,8 @@ func TestHybridPickerExpiredSwitchFallsBackToNewDial(t *testing.T) {
803 sourceRemote := &recordingTransport{recvs: []recvItem{804 sourceRemote := &recordingTransport{recvs: []recvItem{
804 {f: hybridWelcomeFrame("source", sourceLifecycle)},805 {f: hybridWelcomeFrame("source", sourceLifecycle)},
805 {f: navigationDirectiveFrame(protocol.NavigationOpenHomePicker)},806 {f: navigationDirectiveFrame(protocol.NavigationOpenHomePicker)},
806- {f: frameOf(ports.MsgParkedRouteResponse, ports.MarshalParkedRouteResponse(protocol.ParkedRouteResponse{RequestID: 1, Status: protocol.ParkedRouteReady})), wait: prepareSent},807+ {f: frameOf(wire.MsgParkedRouteResponse, wire.MarshalParkedRouteResponse(protocol.ParkedRouteResponse{RequestID: 1, Status: protocol.ParkedRouteReady})), wait: prepareSent},
807- {f: frameOf(ports.MsgParkedRouteResponse, ports.MarshalParkedRouteResponse(protocol.ParkedRouteResponse{RequestID: 2, Status: protocol.ParkedRouteExpired})), wait: switchSent},808+ {f: frameOf(wire.MsgParkedRouteResponse, wire.MarshalParkedRouteResponse(protocol.ParkedRouteResponse{RequestID: 2, Status: protocol.ParkedRouteExpired})), wait: switchSent},
808 }}809 }}
809 sourceRemote.onSend = hybridParkedRequestHandler(nil, parkedRequestErrors, map[protocol.ParkedRouteAction]chan struct{}{810 sourceRemote.onSend = hybridParkedRequestHandler(nil, parkedRequestErrors, map[protocol.ParkedRouteAction]chan struct{}{
810 protocol.ParkedRoutePrepare: prepareSent,811 protocol.ParkedRoutePrepare: prepareSent,
@@ -812,14 +813,14 @@ func TestHybridPickerExpiredSwitchFallsBackToNewDial(t *testing.T) {
812 })813 })
813 localPicker := &recordingTransport{recvs: []recvItem{814 localPicker := &recordingTransport{recvs: []recvItem{
814 {f: hybridWelcomeFrame("local", localLifecycle)},815 {f: hybridWelcomeFrame("local", localLifecycle)},
815- {f: frameOf(ports.MsgAttachTarget, ports.MarshalAttachTarget(protocol.AttachTarget{816+ {f: frameOf(wire.MsgAttachTarget, wire.MarshalAttachTarget(protocol.AttachTarget{
816 Endpoint: "remote", Session: "target", Intent: protocol.IntentAttach,817 Endpoint: "remote", Session: "target", Intent: protocol.IntentAttach,
817 RemoteTarget: &targetTarget, EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned,818 RemoteTarget: &targetTarget, EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned,
818 }))},819 }))},
819 }}820 }}
820 targetRemote := &recordingTransport{recvs: []recvItem{821 targetRemote := &recordingTransport{recvs: []recvItem{
821 {f: hybridWelcomeFrame("target", targetLifecycle)},822 {f: hybridWelcomeFrame("target", targetLifecycle)},
822- {f: frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},823+ {f: frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},
823 }}824 }}
824 localDialer := &sequenceDialer{trs: []ports.Transport{localInitial, localPicker}}825 localDialer := &sequenceDialer{trs: []ports.Transport{localInitial, localPicker}}
825 remoteDialer := &sequenceDialer{trs: []ports.Transport{826 remoteDialer := &sequenceDialer{trs: []ports.Transport{
@@ -951,10 +952,10 @@ func TestHybridPickerBackResumesRetainedRemoteTransport(t *testing.T) {
951 remote := &recordingTransport{recvs: []recvItem{952 remote := &recordingTransport{recvs: []recvItem{
952 {f: hybridWelcomeFrame("work", remoteLifecycle)},953 {f: hybridWelcomeFrame("work", remoteLifecycle)},
953 {f: navigationDirectiveFrame(protocol.NavigationOpenHomePicker)},954 {f: navigationDirectiveFrame(protocol.NavigationOpenHomePicker)},
954- {f: frameOf(ports.MsgParkedRouteResponse, ports.MarshalParkedRouteResponse(protocol.ParkedRouteResponse{RequestID: 1, Status: protocol.ParkedRouteReady})), wait: parkSent},955+ {f: frameOf(wire.MsgParkedRouteResponse, wire.MarshalParkedRouteResponse(protocol.ParkedRouteResponse{RequestID: 1, Status: protocol.ParkedRouteReady})), wait: parkSent},
955- {f: frameOf(ports.MsgParkedRouteResponse, ports.MarshalParkedRouteResponse(protocol.ParkedRouteResponse{RequestID: 2, Status: protocol.ParkedRouteResumed})), wait: resumeSent},956+ {f: frameOf(wire.MsgParkedRouteResponse, wire.MarshalParkedRouteResponse(protocol.ParkedRouteResponse{RequestID: 2, Status: protocol.ParkedRouteResumed})), wait: resumeSent},
956- {f: frameOf(ports.MsgOutput, mustMarshalOutput(protocol.Output{Epoch: 1, New: 1, Size: domain.Size{Cols: 80, Rows: 24}, Full: true, Data: []byte("source frame")}))},957+ {f: frameOf(wire.MsgOutput, mustMarshalOutput(protocol.Output{Epoch: 1, New: 1, Size: domain.Size{Cols: 80, Rows: 24}, Full: true, Data: []byte("source frame")}))},
957- {f: frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},958+ {f: frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},
958 }}959 }}
959 remote.onSend = hybridParkedRequestHandler(nil, parkedRequestErrors, map[protocol.ParkedRouteAction]chan struct{}{960 remote.onSend = hybridParkedRequestHandler(nil, parkedRequestErrors, map[protocol.ParkedRouteAction]chan struct{}{
960 protocol.ParkedRoutePrepare: parkSent,961 protocol.ParkedRoutePrepare: parkSent,
@@ -1002,7 +1003,7 @@ func TestHybridPickerDifferentHostFallsBackToNewRemoteDial(t *testing.T) {
1002 sourceRemote := &recordingTransport{recvs: []recvItem{1003 sourceRemote := &recordingTransport{recvs: []recvItem{
1003 {f: hybridWelcomeFrame("source", sourceLifecycle)},1004 {f: hybridWelcomeFrame("source", sourceLifecycle)},
1004 {f: navigationDirectiveFrame(protocol.NavigationOpenHomePicker)},1005 {f: navigationDirectiveFrame(protocol.NavigationOpenHomePicker)},
1005- {f: frameOf(ports.MsgParkedRouteResponse, ports.MarshalParkedRouteResponse(protocol.ParkedRouteResponse{RequestID: 1, Status: protocol.ParkedRouteReady})), wait: parkSent},1006+ {f: frameOf(wire.MsgParkedRouteResponse, wire.MarshalParkedRouteResponse(protocol.ParkedRouteResponse{RequestID: 1, Status: protocol.ParkedRouteReady})), wait: parkSent},
1006 {err: io.EOF, wait: keepSourceOpen},1007 {err: io.EOF, wait: keepSourceOpen},
1007 }}1008 }}
1008 sourceRemote.onSend = hybridParkedRequestHandler(nil, parkedRequestErrors, map[protocol.ParkedRouteAction]chan struct{}{1009 sourceRemote.onSend = hybridParkedRequestHandler(nil, parkedRequestErrors, map[protocol.ParkedRouteAction]chan struct{}{
@@ -1010,14 +1011,14 @@ func TestHybridPickerDifferentHostFallsBackToNewRemoteDial(t *testing.T) {
1010 })1011 })
1011 localPicker := &recordingTransport{recvs: []recvItem{1012 localPicker := &recordingTransport{recvs: []recvItem{
1012 {f: hybridWelcomeFrame("local", localLifecycle)},1013 {f: hybridWelcomeFrame("local", localLifecycle)},
1013- {f: frameOf(ports.MsgAttachTarget, ports.MarshalAttachTarget(protocol.AttachTarget{1014+ {f: frameOf(wire.MsgAttachTarget, wire.MarshalAttachTarget(protocol.AttachTarget{
1014 Endpoint: targetTarget.Endpoint, Session: targetTarget.SessionName, Intent: protocol.IntentAttach,1015 Endpoint: targetTarget.Endpoint, Session: targetTarget.SessionName, Intent: protocol.IntentAttach,
1015 RemoteTarget: &targetTarget, EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned,1016 RemoteTarget: &targetTarget, EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned,
1016 }))},1017 }))},
1017 }}1018 }}
1018 targetRemote := &recordingTransport{recvs: []recvItem{1019 targetRemote := &recordingTransport{recvs: []recvItem{
1019 {f: hybridWelcomeFrame("target", targetLifecycle)},1020 {f: hybridWelcomeFrame("target", targetLifecycle)},
1020- {f: frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},1021+ {f: frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},
1021 }}1022 }}
1022 localDialer := &sequenceDialer{trs: []ports.Transport{localInitial, localPicker}}1023 localDialer := &sequenceDialer{trs: []ports.Transport{localInitial, localPicker}}
1023 sourceDialer := &sequenceDialer{trs: []ports.Transport{markedDatagramTransport{Transport: sourceRemote}}}1024 sourceDialer := &sequenceDialer{trs: []ports.Transport{markedDatagramTransport{Transport: sourceRemote}}}
@@ -1048,8 +1049,8 @@ func TestRouteNavigationPreservesRemoteHomePickerAcrossLocalReturn(t *testing.T)
1048 remoteLifecycle := domain.SessionLifecycleID{2, 2, 2}1049 remoteLifecycle := domain.SessionLifecycleID{2, 2, 2}
1049 newLifecycle := domain.SessionLifecycleID{3, 3, 3}1050 newLifecycle := domain.SessionLifecycleID{3, 3, 3}
1050 remoteNewLifecycle := domain.SessionLifecycleID{4, 4, 4}1051 remoteNewLifecycle := domain.SessionLifecycleID{4, 4, 4}
1051- welcome := func(name string, lifecycle domain.SessionLifecycleID, token uint64) ports.Frame {1052+ welcome := func(name string, lifecycle domain.SessionLifecycleID, token uint64) wire.Frame {
1052- return frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{1053+ return frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{
1053 SessionID: name + "-id",1054 SessionID: name + "-id",
1054 SessionName: name,1055 SessionName: name,
1055 ResumeToken: token,1056 ResumeToken: token,
@@ -1073,14 +1074,14 @@ func TestRouteNavigationPreservesRemoteHomePickerAcrossLocalReturn(t *testing.T)
1073 1074
1074 local1 := &recordingTransport{recvs: []recvItem{1075 local1 := &recordingTransport{recvs: []recvItem{
1075 {f: welcome("test", localLifecycle, 11)},1076 {f: welcome("test", localLifecycle, 11)},
1076- {f: frameOf(ports.MsgAttachTarget, ports.MarshalAttachTarget(remoteHandoff))},1077+ {f: frameOf(wire.MsgAttachTarget, wire.MarshalAttachTarget(remoteHandoff))},
1077 }}1078 }}
1078 local2 := &recordingTransport{recvs: []recvItem{1079 local2 := &recordingTransport{recvs: []recvItem{
1079 {f: welcome("test", localLifecycle, 11)},1080 {f: welcome("test", localLifecycle, 11)},
1080- {f: frameOf(ports.MsgCommittedRouteIdentity, mustMarshalCommittedIdentity(protocol.CommittedRouteIdentity{1081+ {f: frameOf(wire.MsgCommittedRouteIdentity, mustMarshalCommittedIdentity(protocol.CommittedRouteIdentity{
1081 Target: protocol.ExactSessionTarget{LifecycleID: newLifecycle, SessionName: "new"},1082 Target: protocol.ExactSessionTarget{LifecycleID: newLifecycle, SessionName: "new"},
1082 }))},1083 }))},
1083- {f: frameOf(ports.MsgNavigateRecentRoute, mustMarshalRouteAction(protocol.RouteNavigationAction{SnapshotGeneration: 4, Key: 2, Generation: 2}))},1084+ {f: frameOf(wire.MsgNavigateRecentRoute, mustMarshalRouteAction(protocol.RouteNavigationAction{SnapshotGeneration: 4, Key: 2, Generation: 2}))},
1084 }}1085 }}
1085 local3 := &recordingTransport{recvs: []recvItem{1086 local3 := &recordingTransport{recvs: []recvItem{
1086 {f: welcome("test", localLifecycle, 11)},1087 {f: welcome("test", localLifecycle, 11)},
@@ -1088,21 +1089,21 @@ func TestRouteNavigationPreservesRemoteHomePickerAcrossLocalReturn(t *testing.T)
1088 }}1089 }}
1089 remote1 := &recordingTransport{recvs: []recvItem{1090 remote1 := &recordingTransport{recvs: []recvItem{
1090 {f: welcome("remote-manual", remoteLifecycle, 22)},1091 {f: welcome("remote-manual", remoteLifecycle, 22)},
1091- {f: frameOf(ports.MsgNavigateRecentRoute, mustMarshalRouteAction(protocol.RouteNavigationAction{SnapshotGeneration: 2, Key: 1, Generation: 1}))},1092+ {f: frameOf(wire.MsgNavigateRecentRoute, mustMarshalRouteAction(protocol.RouteNavigationAction{SnapshotGeneration: 2, Key: 1, Generation: 1}))},
1092 }}1093 }}
1093 remote2 := &recordingTransport{recvs: []recvItem{1094 remote2 := &recordingTransport{recvs: []recvItem{
1094 {f: welcome("remote-manual", remoteLifecycle, 22)},1095 {f: welcome("remote-manual", remoteLifecycle, 22)},
1095- {f: frameOf(ports.MsgCommittedRouteIdentity, mustMarshalCommittedIdentity(protocol.CommittedRouteIdentity{1096+ {f: frameOf(wire.MsgCommittedRouteIdentity, mustMarshalCommittedIdentity(protocol.CommittedRouteIdentity{
1096 Target: protocol.ExactSessionTarget{LifecycleID: remoteNewLifecycle, SessionName: "remote-new"},1097 Target: protocol.ExactSessionTarget{LifecycleID: remoteNewLifecycle, SessionName: "remote-new"},
1097 }))},1098 }))},
1098- {f: frameOf(ports.MsgRoutePosition, mustMarshalRoutePosition(protocol.RoutePosition{1099+ {f: frameOf(wire.MsgRoutePosition, mustMarshalRoutePosition(protocol.RoutePosition{
1099 Target: protocol.ExactSessionTarget{LifecycleID: remoteNewLifecycle, SessionName: "remote-new"}, ActiveTabID: "tab-2",1100 Target: protocol.ExactSessionTarget{LifecycleID: remoteNewLifecycle, SessionName: "remote-new"}, ActiveTabID: "tab-2",
1100 }))},1101 }))},
1101 {f: navigationDirectiveFrame(protocol.NavigationOpenHomePicker)},1102 {f: navigationDirectiveFrame(protocol.NavigationOpenHomePicker)},
1102 }}1103 }}
1103 remote3 := &recordingTransport{recvs: []recvItem{1104 remote3 := &recordingTransport{recvs: []recvItem{
1104 {f: welcome("remote-new", remoteNewLifecycle, 22)},1105 {f: welcome("remote-new", remoteNewLifecycle, 22)},
1105- {f: frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},1106+ {f: frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},
1106 }}1107 }}
1107 localDialer := &sequenceDialer{trs: []ports.Transport{local1, local2, local3}}1108 localDialer := &sequenceDialer{trs: []ports.Transport{local1, local2, local3}}
1108 remoteDialer := &sequenceDialer{trs: []ports.Transport{remote1, remote2, remote3}}1109 remoteDialer := &sequenceDialer{trs: []ports.Transport{remote1, remote2, remote3}}
@@ -1129,16 +1130,16 @@ func TestRouteNavigationPreservesRemoteHomePickerAcrossLocalReturn(t *testing.T)
1129 require.Equal(t, &protocol.ExactSessionTarget{LifecycleID: remoteNewLifecycle, SessionName: "remote-new"}, remoteNewHello.ExactTarget)1130 require.Equal(t, &protocol.ExactSessionTarget{LifecycleID: remoteNewLifecycle, SessionName: "remote-new"}, remoteNewHello.ExactTarget)
1130 }1131 }
1131 1132
1132-func navigationDirectiveFrame(action protocol.NavigationAction) ports.Frame {1133+func navigationDirectiveFrame(action protocol.NavigationAction) wire.Frame {
1133 directive := protocol.NavigationDirective{Action: action}1134 directive := protocol.NavigationDirective{Action: action}
1134 if action == protocol.NavigationOpenHomePicker {1135 if action == protocol.NavigationOpenHomePicker {
1135 directive.LeaseID = protocol.ParkedRouteLeaseID{1}1136 directive.LeaseID = protocol.ParkedRouteLeaseID{1}
1136 }1137 }
1137- return frameOf(ports.MsgNavigationAction, ports.MarshalNavigationDirective(directive))1138+ return frameOf(wire.MsgNavigationAction, wire.MarshalNavigationDirective(directive))
1138 }1139 }
1139 1140
1140 func mustMarshalCommittedIdentity(identity protocol.CommittedRouteIdentity) []byte {1141 func mustMarshalCommittedIdentity(identity protocol.CommittedRouteIdentity) []byte {
1141- payload, err := ports.MarshalCommittedRouteIdentity(identity)1142+ payload, err := wire.MarshalCommittedRouteIdentity(identity)
1142 if err != nil {1143 if err != nil {
1143 panic(err)1144 panic(err)
1144 }1145 }
@@ -1146,7 +1147,7 @@ func mustMarshalCommittedIdentity(identity protocol.CommittedRouteIdentity) []by
1146 }1147 }
1147 1148
1148 func mustMarshalRouteAction(action protocol.RouteNavigationAction) []byte {1149 func mustMarshalRouteAction(action protocol.RouteNavigationAction) []byte {
1149- payload, err := ports.MarshalRouteNavigationAction(action)1150+ payload, err := wire.MarshalRouteNavigationAction(action)
1150 if err != nil {1151 if err != nil {
1151 panic(err)1152 panic(err)
1152 }1153 }
@@ -1154,7 +1155,7 @@ func mustMarshalRouteAction(action protocol.RouteNavigationAction) []byte {
1154 }1155 }
1155 1156
1156 func mustMarshalRoutePosition(position protocol.RoutePosition) []byte {1157 func mustMarshalRoutePosition(position protocol.RoutePosition) []byte {
1157- payload, err := ports.MarshalRoutePosition(position)1158+ payload, err := wire.MarshalRoutePosition(position)
1158 if err != nil {1159 if err != nil {
1159 panic(err)1160 panic(err)
1160 }1161 }
@@ -1170,11 +1171,11 @@ func TestAttachTargetHandoffReturnsValidatedTargetAndClosesTransport(t *testing.
1170 1171
1171 target := protocol.AttachTarget{Endpoint: "remote.example", Session: "work", Intent: protocol.IntentAttach}1172 target := protocol.AttachTarget{Endpoint: "remote.example", Session: "work", Intent: protocol.IntentAttach}
1172 tr := portsmocks.NewMockTransport(t)1173 tr := portsmocks.NewMockTransport(t)
1173- tr.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()1174+ tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
1174- tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once()1175+ tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once()
1175 unblock := scriptRecv(tr,1176 unblock := scriptRecv(tr,
1176- recvItem{f: frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{SessionID: "local"}))},1177+ recvItem{f: frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{SessionID: "local"}))},
1177- recvItem{f: frameOf(ports.MsgAttachTarget, ports.MarshalAttachTarget(target))},1178+ recvItem{f: frameOf(wire.MsgAttachTarget, wire.MarshalAttachTarget(target))},
1178 )1179 )
1179 defer unblock()1180 defer unblock()
1180 tr.EXPECT().Close().Return(nil).Once()1181 tr.EXPECT().Close().Return(nil).Once()
@@ -1197,10 +1198,10 @@ func TestAttachHelloIncludesCompleteLocalEnvironment(t *testing.T) {
1197 1198
1198 gotHello := make(chan protocol.Hello, 1)1199 gotHello := make(chan protocol.Hello, 1)
1199 tr := portsmocks.NewMockTransport(t)1200 tr := portsmocks.NewMockTransport(t)
1200- tr.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()1201+ tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
1201- tr.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()1202+ tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
1202- tr.EXPECT().Send(isType(ports.MsgHello)).RunAndReturn(func(f ports.Frame) error {1203+ tr.EXPECT().Send(isType(wire.MsgHello)).RunAndReturn(func(f wire.Frame) error {
1203- hello, err := ports.UnmarshalHello(f.Payload)1204+ hello, err := wire.UnmarshalHello(f.Payload)
1204 if err != nil {1205 if err != nil {
1205 return err1206 return err
1206 }1207 }
@@ -1208,8 +1209,8 @@ func TestAttachHelloIncludesCompleteLocalEnvironment(t *testing.T) {
1208 return nil1209 return nil
1209 }).Once()1210 }).Once()
1210 unblock := scriptRecv(tr,1211 unblock := scriptRecv(tr,
1211- recvItem{f: frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},1212+ recvItem{f: frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},
1212- recvItem{f: frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},1213+ recvItem{f: frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},
1213 )1214 )
1214 defer unblock()1215 defer unblock()
1215 tr.EXPECT().Close().Return(nil).Once()1216 tr.EXPECT().Close().Return(nil).Once()
@@ -1226,17 +1227,17 @@ func TestAttachHelloRequestsSingleOutputForDatagramTransport(t *testing.T) {
1226 defer in.unblock()1227 defer in.unblock()
1227 1228
1228 tr := portsmocks.NewMockTransport(t)1229 tr := portsmocks.NewMockTransport(t)
1229- tr.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()1230+ tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
1230- tr.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()1231+ tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
1231- tr.EXPECT().Send(isType(ports.MsgHello)).RunAndReturn(func(f ports.Frame) error {1232+ tr.EXPECT().Send(isType(wire.MsgHello)).RunAndReturn(func(f wire.Frame) error {
1232- hello, err := ports.UnmarshalHello(f.Payload)1233+ hello, err := wire.UnmarshalHello(f.Payload)
1233 require.NoError(t, err)1234 require.NoError(t, err)
1234 require.Equal(t, uint8(1), hello.MaxOutputInFlight)1235 require.Equal(t, uint8(1), hello.MaxOutputInFlight)
1235 return nil1236 return nil
1236 }).Once()1237 }).Once()
1237 unblock := scriptRecv(tr,1238 unblock := scriptRecv(tr,
1238- recvItem{f: frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},1239+ recvItem{f: frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},
1239- recvItem{f: frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},1240+ recvItem{f: frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},
1240 )1241 )
1241 defer unblock()1242 defer unblock()
1242 tr.EXPECT().Close().Return(nil).Once()1243 tr.EXPECT().Close().Return(nil).Once()
@@ -1252,13 +1253,13 @@ func TestAttachHappyPath(t *testing.T) {
1252 defer in.unblock()1253 defer in.unblock()
1253 1254
1254 tr := portsmocks.NewMockTransport(t)1255 tr := portsmocks.NewMockTransport(t)
1255- tr.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()1256+ tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
1256- tr.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()1257+ tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
1257- tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once()1258+ tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once()
1258 unblock := scriptRecv(tr,1259 unblock := scriptRecv(tr,
1259- recvItem{f: frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{SessionID: "s1", SessionName: "main"}))},1260+ recvItem{f: frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{SessionID: "s1", SessionName: "main"}))},
1260- recvItem{f: frameOf(ports.MsgOutput, mustMarshalOutput(protocol.Output{Epoch: 1, Size: domain.Size{Cols: 1, Rows: 1}, Data: []byte("hello world")}))},1261+ recvItem{f: frameOf(wire.MsgOutput, mustMarshalOutput(protocol.Output{Epoch: 1, Size: domain.Size{Cols: 1, Rows: 1}, Data: []byte("hello world")}))},
1261- recvItem{f: frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},1262+ recvItem{f: frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},
1262 )1263 )
1263 defer unblock()1264 defer unblock()
1264 tr.EXPECT().Close().Return(nil).Once()1265 tr.EXPECT().Close().Return(nil).Once()
@@ -1275,9 +1276,9 @@ func TestAttachVersionMismatch(t *testing.T) {
1275 // EnterRaw must NOT be called on the error-before-welcome path.1276 // EnterRaw must NOT be called on the error-before-welcome path.
1276 1277
1277 tr := portsmocks.NewMockTransport(t)1278 tr := portsmocks.NewMockTransport(t)
1278- tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once()1279+ tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once()
1279 tr.EXPECT().Recv().Return(1280 tr.EXPECT().Recv().Return(
1280- frameOf(ports.MsgError, ports.MarshalErrorMsg(protocol.ErrorMsg{Code: protocol.ErrVersionMismatch, Text: "version mismatch"})),1281+ frameOf(wire.MsgError, wire.MarshalErrorMsg(protocol.ErrorMsg{Code: protocol.ErrVersionMismatch, Text: "version mismatch"})),
1281 nil,1282 nil,
1282 ).Once()1283 ).Once()
1283 tr.EXPECT().Close().Return(nil).Once()1284 tr.EXPECT().Close().Return(nil).Once()
@@ -1287,7 +1288,7 @@ func TestAttachVersionMismatch(t *testing.T) {
1287 var pe *client.ProtocolError1288 var pe *client.ProtocolError
1288 require.True(t, errors.As(err, &pe), "want *client.ProtocolError, got %T", err)1289 require.True(t, errors.As(err, &pe), "want *client.ProtocolError, got %T", err)
1289 require.Equal(t, protocol.ErrVersionMismatch, pe.Code)1290 require.Equal(t, protocol.ErrVersionMismatch, pe.Code)
1290- tr.AssertNotCalled(t, "Send", isType(ports.MsgTheme))1291+ tr.AssertNotCalled(t, "Send", isType(wire.MsgTheme))
1291 }1292 }
1292 1293
1293 func TestAttachRestoredOnRecvErrorMidStream(t *testing.T) {1294 func TestAttachRestoredOnRecvErrorMidStream(t *testing.T) {
@@ -1298,12 +1299,12 @@ func TestAttachRestoredOnRecvErrorMidStream(t *testing.T) {
1298 defer in.unblock()1299 defer in.unblock()
1299 1300
1300 tr := portsmocks.NewMockTransport(t)1301 tr := portsmocks.NewMockTransport(t)
1301- tr.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()1302+ tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
1302- tr.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()1303+ tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
1303- tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once()1304+ tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once()
1304 boom := errors.New("connection reset")1305 boom := errors.New("connection reset")
1305 unblock := scriptRecv(tr,1306 unblock := scriptRecv(tr,
1306- recvItem{f: frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},1307+ recvItem{f: frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},
1307 recvItem{err: boom},1308 recvItem{err: boom},
1308 )1309 )
1309 defer unblock()1310 defer unblock()
@@ -1322,11 +1323,11 @@ func TestAttachDaemonVanishedOnEOF(t *testing.T) {
1322 defer in.unblock()1323 defer in.unblock()
1323 1324
1324 tr := portsmocks.NewMockTransport(t)1325 tr := portsmocks.NewMockTransport(t)
1325- tr.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()1326+ tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
1326- tr.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()1327+ tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
1327- tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once()1328+ tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once()
1328 unblock := scriptRecv(tr,1329 unblock := scriptRecv(tr,
1329- recvItem{f: frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},1330+ recvItem{f: frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},
1330 recvItem{err: io.EOF},1331 recvItem{err: io.EOF},
1331 )1332 )
1332 defer unblock()1333 defer unblock()
@@ -1356,35 +1357,35 @@ func TestAttachStdinForwardsSGRMouseReportAsSingleFrame(t *testing.T) {
1356 gotInput := make(chan []byte, 2)1357 gotInput := make(chan []byte, 2)
1357 allowDetach := make(chan struct{})1358 allowDetach := make(chan struct{})
1358 tr := portsmocks.NewMockTransport(t)1359 tr := portsmocks.NewMockTransport(t)
1359- tr.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()1360+ tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
1360- tr.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()1361+ tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
1361- tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once()1362+ tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once()
1362- tr.EXPECT().Send(isType(ports.MsgInput)).RunAndReturn(func(f ports.Frame) error {1363+ tr.EXPECT().Send(isType(wire.MsgInput)).RunAndReturn(func(f wire.Frame) error {
1363- in, err := ports.UnmarshalInput(f.Payload)1364+ in, err := wire.UnmarshalInput(f.Payload)
1364 require.NoError(t, err)1365 require.NoError(t, err)
1365 gotInput <- in.Data1366 gotInput <- in.Data
1366 close(allowDetach)1367 close(allowDetach)
1367 return nil1368 return nil
1368 }).Once()1369 }).Once()
1369- welcome := frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))1370+ welcome := frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))
1370- detached := frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))1371+ detached := frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))
1371 recvCh := make(chan recvItem, 1)1372 recvCh := make(chan recvItem, 1)
1372 recvCh <- recvItem{f: welcome}1373 recvCh <- recvItem{f: welcome}
1373 closed := make(chan struct{})1374 closed := make(chan struct{})
1374- tr.EXPECT().Recv().RunAndReturn(func() (ports.Frame, error) {1375+ tr.EXPECT().Recv().RunAndReturn(func() (wire.Frame, error) {
1375 select {1376 select {
1376 case it := <-recvCh:1377 case it := <-recvCh:
1377 return it.f, it.err1378 return it.f, it.err
1378 case <-allowDetach:1379 case <-allowDetach:
1379 select {1380 select {
1380 case <-closed:1381 case <-closed:
1381- return ports.Frame{}, io.EOF1382+ return wire.Frame{}, io.EOF
1382 default:1383 default:
1383 close(closed)1384 close(closed)
1384 return detached, nil1385 return detached, nil
1385 }1386 }
1386 case <-closed:1387 case <-closed:
1387- return ports.Frame{}, io.EOF1388+ return wire.Frame{}, io.EOF
1388 }1389 }
1389 }).Maybe()1390 }).Maybe()
1390 tr.EXPECT().Close().Return(nil).Once()1391 tr.EXPECT().Close().Return(nil).Once()
@@ -1418,35 +1419,35 @@ func TestAttachStdinCoalescesSplitBracketedPaste(t *testing.T) {
1418 gotInput := make(chan []byte, 1)1419 gotInput := make(chan []byte, 1)
1419 allowDetach := make(chan struct{})1420 allowDetach := make(chan struct{})
1420 tr := portsmocks.NewMockTransport(t)1421 tr := portsmocks.NewMockTransport(t)
1421- tr.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()1422+ tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
1422- tr.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()1423+ tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
1423- tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once()1424+ tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once()
1424- tr.EXPECT().Send(isType(ports.MsgInput)).RunAndReturn(func(f ports.Frame) error {1425+ tr.EXPECT().Send(isType(wire.MsgInput)).RunAndReturn(func(f wire.Frame) error {
1425- in, err := ports.UnmarshalInput(f.Payload)1426+ in, err := wire.UnmarshalInput(f.Payload)
1426 require.NoError(t, err)1427 require.NoError(t, err)
1427 gotInput <- in.Data1428 gotInput <- in.Data
1428 close(allowDetach)1429 close(allowDetach)
1429 return nil1430 return nil
1430 }).Once()1431 }).Once()
1431- welcome := frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))1432+ welcome := frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))
1432- detached := frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))1433+ detached := frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))
1433 recvCh := make(chan recvItem, 1)1434 recvCh := make(chan recvItem, 1)
1434 recvCh <- recvItem{f: welcome}1435 recvCh <- recvItem{f: welcome}
1435 closed := make(chan struct{})1436 closed := make(chan struct{})
1436- tr.EXPECT().Recv().RunAndReturn(func() (ports.Frame, error) {1437+ tr.EXPECT().Recv().RunAndReturn(func() (wire.Frame, error) {
1437 select {1438 select {
1438 case it := <-recvCh:1439 case it := <-recvCh:
1439 return it.f, it.err1440 return it.f, it.err
1440 case <-allowDetach:1441 case <-allowDetach:
1441 select {1442 select {
1442 case <-closed:1443 case <-closed:
1443- return ports.Frame{}, io.EOF1444+ return wire.Frame{}, io.EOF
1444 default:1445 default:
1445 close(closed)1446 close(closed)
1446 return detached, nil1447 return detached, nil
1447 }1448 }
1448 case <-closed:1449 case <-closed:
1449- return ports.Frame{}, io.EOF1450+ return wire.Frame{}, io.EOF
1450 }1451 }
1451 }).Maybe()1452 }).Maybe()
1452 tr.EXPECT().Close().Return(nil).Once()1453 tr.EXPECT().Close().Return(nil).Once()
@@ -1473,24 +1474,24 @@ func TestAttachForwardsResize(t *testing.T) {
1473 var firstRecvOnce sync.Once1474 var firstRecvOnce sync.Once
1474 1475
1475 tr := portsmocks.NewMockTransport(t)1476 tr := portsmocks.NewMockTransport(t)
1476- tr.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()1477+ tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
1477- tr.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()1478+ tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
1478- tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once()1479+ tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once()
1479 // The resize frame is forwarded via the sender goroutine.1480 // The resize frame is forwarded via the sender goroutine.
1480 gotResize := make(chan protocol.Resize, 1)1481 gotResize := make(chan protocol.Resize, 1)
1481- tr.EXPECT().Send(isType(ports.MsgResize)).RunAndReturn(func(f ports.Frame) error {1482+ tr.EXPECT().Send(isType(wire.MsgResize)).RunAndReturn(func(f wire.Frame) error {
1482- r, _ := ports.UnmarshalResize(f.Payload)1483+ r, _ := wire.UnmarshalResize(f.Payload)
1483 gotResize <- r1484 gotResize <- r
1484 close(detachAfterResize)1485 close(detachAfterResize)
1485 return nil1486 return nil
1486 }).Once()1487 }).Once()
1487 1488
1488- welcome := frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))1489+ welcome := frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))
1489- detached := frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))1490+ detached := frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))
1490 recvCh := make(chan recvItem, 2)1491 recvCh := make(chan recvItem, 2)
1491 recvCh <- recvItem{f: welcome}1492 recvCh <- recvItem{f: welcome}
1492 done := make(chan struct{})1493 done := make(chan struct{})
1493- tr.EXPECT().Recv().RunAndReturn(func() (ports.Frame, error) {1494+ tr.EXPECT().Recv().RunAndReturn(func() (wire.Frame, error) {
1494 firstRecvOnce.Do(func() { close(firstRecv) })1495 firstRecvOnce.Do(func() { close(firstRecv) })
1495 select {1496 select {
1496 case it := <-recvCh:1497 case it := <-recvCh:
@@ -1499,13 +1500,13 @@ func TestAttachForwardsResize(t *testing.T) {
1499 // Deliver the detach once the resize has been observed.1500 // Deliver the detach once the resize has been observed.
1500 select {1501 select {
1501 case <-done:1502 case <-done:
1502- return ports.Frame{}, io.EOF1503+ return wire.Frame{}, io.EOF
1503 default:1504 default:
1504 close(done)1505 close(done)
1505 return detached, nil1506 return detached, nil
1506 }1507 }
1507 case <-done:1508 case <-done:
1508- return ports.Frame{}, io.EOF1509+ return wire.Frame{}, io.EOF
1509 }1510 }
1510 }).Maybe()1511 }).Maybe()
1511 tr.EXPECT().Close().Return(nil).Once()1512 tr.EXPECT().Close().Return(nil).Once()
@@ -1534,12 +1535,12 @@ func TestRunRestoresRawModeAfterAttachError(t *testing.T) {
1534 defer in.unblock()1535 defer in.unblock()
1535 1536
1536 tr := portsmocks.NewMockTransport(t)1537 tr := portsmocks.NewMockTransport(t)
1537- tr.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()1538+ tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
1538- tr.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()1539+ tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
1539- tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once()1540+ tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once()
1540 boom := errors.New("connection reset")1541 boom := errors.New("connection reset")
1541 unblock := scriptRecv(tr,1542 unblock := scriptRecv(tr,
1542- recvItem{f: frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},1543+ recvItem{f: frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},
1543 recvItem{err: boom},1544 recvItem{err: boom},
1544 )1545 )
1545 defer unblock()1546 defer unblock()
@@ -1558,9 +1559,9 @@ func TestRunDoesNotEnterRawBeforePreWelcomeError(t *testing.T) {
1558 // EnterRaw must NOT be called when the daemon rejects Hello before Welcome.1559 // EnterRaw must NOT be called when the daemon rejects Hello before Welcome.
1559 1560
1560 tr := portsmocks.NewMockTransport(t)1561 tr := portsmocks.NewMockTransport(t)
1561- tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once()1562+ tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once()
1562 tr.EXPECT().Recv().Return(1563 tr.EXPECT().Recv().Return(
1563- frameOf(ports.MsgError, ports.MarshalErrorMsg(protocol.ErrorMsg{Code: protocol.ErrVersionMismatch, Text: "version mismatch"})),1564+ frameOf(wire.MsgError, wire.MarshalErrorMsg(protocol.ErrorMsg{Code: protocol.ErrVersionMismatch, Text: "version mismatch"})),
1564 nil,1565 nil,
1565 ).Once()1566 ).Once()
1566 tr.EXPECT().Close().Return(nil).Once()1567 tr.EXPECT().Close().Return(nil).Once()
@@ -1571,7 +1572,7 @@ func TestRunDoesNotEnterRawBeforePreWelcomeError(t *testing.T) {
1571 require.Error(t, err)1572 require.Error(t, err)
1572 var pe *client.ProtocolError1573 var pe *client.ProtocolError
1573 require.True(t, errors.As(err, &pe), "want *client.ProtocolError, got %T", err)1574 require.True(t, errors.As(err, &pe), "want *client.ProtocolError, got %T", err)
1574- tr.AssertNotCalled(t, "Send", isType(ports.MsgTheme))1575+ tr.AssertNotCalled(t, "Send", isType(wire.MsgTheme))
1575 }1576 }
1576 1577
1577 func TestRunPhaseASingleAttempt(t *testing.T) {1578 func TestRunPhaseASingleAttempt(t *testing.T) {
@@ -1604,14 +1605,14 @@ func (d *sequenceDialer) Dial(context.Context) (ports.Transport, error) {
1604 type recordingTransport struct {1605 type recordingTransport struct {
1605 mu sync.Mutex1606 mu sync.Mutex
1606 recvs []recvItem1607 recvs []recvItem
1607- sends []ports.Frame1608+ sends []wire.Frame
1608 closed atomic.Int321609 closed atomic.Int32
1609 stall <-chan struct{}1610 stall <-chan struct{}
1610- onSend func(ports.Frame)1611+ onSend func(wire.Frame)
1611 onClose func()1612 onClose func()
1612 }1613 }
1613 1614
1614-func (t *recordingTransport) Send(f ports.Frame) error {1615+func (t *recordingTransport) Send(f wire.Frame) error {
1615 t.mu.Lock()1616 t.mu.Lock()
1616 t.sends = append(t.sends, f)1617 t.sends = append(t.sends, f)
1617 onSend := t.onSend1618 onSend := t.onSend
@@ -1622,18 +1623,18 @@ func (t *recordingTransport) Send(f ports.Frame) error {
1622 return nil1623 return nil
1623 }1624 }
1624 1625
1625-func (t *recordingTransport) Sends() []ports.Frame {1626+func (t *recordingTransport) Sends() []wire.Frame {
1626 t.mu.Lock()1627 t.mu.Lock()
1627 defer t.mu.Unlock()1628 defer t.mu.Unlock()
1628- return append([]ports.Frame(nil), t.sends...)1629+ return append([]wire.Frame(nil), t.sends...)
1629 }1630 }
1630 1631
1631-func (t *recordingTransport) Recv() (ports.Frame, error) {1632+func (t *recordingTransport) Recv() (wire.Frame, error) {
1632 if len(t.recvs) == 0 {1633 if len(t.recvs) == 0 {
1633 if t.stall != nil {1634 if t.stall != nil {
1634 <-t.stall1635 <-t.stall
1635 }1636 }
1636- return ports.Frame{}, io.EOF1637+ return wire.Frame{}, io.EOF
1637 }1638 }
1638 it := t.recvs[0]1639 it := t.recvs[0]
1639 t.recvs = t.recvs[1:]1640 t.recvs = t.recvs[1:]
@@ -1692,14 +1693,14 @@ func helloFromSend(t *testing.T, tr *recordingTransport) protocol.Hello {
1692 t.Helper()1693 t.Helper()
1693 sends := tr.Sends()1694 sends := tr.Sends()
1694 require.NotEmpty(t, sends)1695 require.NotEmpty(t, sends)
1695- require.Equal(t, ports.MsgHello, sends[0].Type)1696+ require.Equal(t, wire.MsgHello, sends[0].Type)
1696- h, err := ports.UnmarshalHello(sends[0].Payload)1697+ h, err := wire.UnmarshalHello(sends[0].Payload)
1697 require.NoError(t, err)1698 require.NoError(t, err)
1698 return h1699 return h
1699 }1700 }
1700 1701
1701-func welcomeFrame(token uint64) ports.Frame {1702+func welcomeFrame(token uint64) wire.Frame {
1702- return frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{SessionID: "s1", SessionName: "main", ResumeToken: token, Capabilities: protocol.CapabilityResume}))1703+ return frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{SessionID: "s1", SessionName: "main", ResumeToken: token, Capabilities: protocol.CapabilityResume}))
1703 }1704 }
1704 1705
1705 func TestRunReconnectsWithRotatedTokenAndSameClientID(t *testing.T) {1706 func TestRunReconnectsWithRotatedTokenAndSameClientID(t *testing.T) {
@@ -1707,7 +1708,7 @@ func TestRunReconnectsWithRotatedTokenAndSameClientID(t *testing.T) {
1707 defer term.in.unblock()1708 defer term.in.unblock()
1708 tr1 := &recordingTransport{recvs: []recvItem{{f: welcomeFrame(11)}, {err: io.EOF}}}1709 tr1 := &recordingTransport{recvs: []recvItem{{f: welcomeFrame(11)}, {err: io.EOF}}}
1709 tr2 := &recordingTransport{recvs: []recvItem{{f: welcomeFrame(22)}, {err: io.EOF}}}1710 tr2 := &recordingTransport{recvs: []recvItem{{f: welcomeFrame(22)}, {err: io.EOF}}}
1710- tr3 := &recordingTransport{recvs: []recvItem{{f: welcomeFrame(33)}, {f: frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))}}}1711+ tr3 := &recordingTransport{recvs: []recvItem{{f: welcomeFrame(33)}, {f: frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))}}}
1711 d := &sequenceDialer{trs: []ports.Transport{tr1, tr2, tr3}}1712 d := &sequenceDialer{trs: []ports.Transport{tr1, tr2, tr3}}
1712 clock := &reconnectTestClock{}1713 clock := &reconnectTestClock{}
1713 result := make(chan error, 1)1714 result := make(chan error, 1)
@@ -1741,20 +1742,20 @@ func TestReconnectRestoresLastPublishedRouteTab(t *testing.T) {
1741 defer term.in.unblock()1742 defer term.in.unblock()
1742 lifecycle := domain.SessionLifecycleID{7, 8, 9}1743 lifecycle := domain.SessionLifecycleID{7, 8, 9}
1743 target := protocol.ExactSessionTarget{LifecycleID: lifecycle, SessionName: "work"}1744 target := protocol.ExactSessionTarget{LifecycleID: lifecycle, SessionName: "work"}
1744- welcome := func(token uint64) ports.Frame {1745+ welcome := func(token uint64) wire.Frame {
1745- return frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{1746+ return frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{
1746 SessionID: "work", SessionName: "work", ResumeToken: token, Capabilities: protocol.CapabilityResume,1747 SessionID: "work", SessionName: "work", ResumeToken: token, Capabilities: protocol.CapabilityResume,
1747 CommittedIdentity: &protocol.CommittedRouteIdentity{Target: target},1748 CommittedIdentity: &protocol.CommittedRouteIdentity{Target: target},
1748 }))1749 }))
1749 }1750 }
1750 tr1 := &recordingTransport{recvs: []recvItem{1751 tr1 := &recordingTransport{recvs: []recvItem{
1751 {f: welcome(11)},1752 {f: welcome(11)},
1752- {f: frameOf(ports.MsgRoutePosition, mustMarshalRoutePosition(protocol.RoutePosition{Target: target, ActiveTabID: "tab-2"}))},1753+ {f: frameOf(wire.MsgRoutePosition, mustMarshalRoutePosition(protocol.RoutePosition{Target: target, ActiveTabID: "tab-2"}))},
1753 {err: io.EOF},1754 {err: io.EOF},
1754 }}1755 }}
1755 tr2 := &recordingTransport{recvs: []recvItem{1756 tr2 := &recordingTransport{recvs: []recvItem{
1756 {f: welcome(22)},1757 {f: welcome(22)},
1757- {f: frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},1758+ {f: frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},
1758 }}1759 }}
1759 dialer := &sequenceDialer{trs: []ports.Transport{tr1, tr2}}1760 dialer := &sequenceDialer{trs: []ports.Transport{tr1, tr2}}
1760 clock := &reconnectTestClock{}1761 clock := &reconnectTestClock{}
@@ -1780,8 +1781,8 @@ func TestRemoteReconnectDropsStalePickerTargetAfterDaemonSessionSwitch(t *testin
1780 Endpoint: "remote", DisplayOrigin: "remote", LifecycleID: oldLifecycle,1781 Endpoint: "remote", DisplayOrigin: "remote", LifecycleID: oldLifecycle,
1781 SessionName: "old", LiveTabID: "tab-1",1782 SessionName: "old", LiveTabID: "tab-1",
1782 }1783 }
1783- welcome := func(name string, lifecycle domain.SessionLifecycleID, token uint64) ports.Frame {1784+ welcome := func(name string, lifecycle domain.SessionLifecycleID, token uint64) wire.Frame {
1784- return frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{1785+ return frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{
1785 SessionID: name, SessionName: name, ResumeToken: token, Capabilities: protocol.CapabilityResume,1786 SessionID: name, SessionName: name, ResumeToken: token, Capabilities: protocol.CapabilityResume,
1786 CommittedIdentity: &protocol.CommittedRouteIdentity{1787 CommittedIdentity: &protocol.CommittedRouteIdentity{
1787 Target: protocol.ExactSessionTarget{LifecycleID: lifecycle, SessionName: name},1788 Target: protocol.ExactSessionTarget{LifecycleID: lifecycle, SessionName: name},
@@ -1790,14 +1791,14 @@ func TestRemoteReconnectDropsStalePickerTargetAfterDaemonSessionSwitch(t *testin
1790 }1791 }
1791 tr1 := &recordingTransport{recvs: []recvItem{1792 tr1 := &recordingTransport{recvs: []recvItem{
1792 {f: welcome("old", oldLifecycle, 11)},1793 {f: welcome("old", oldLifecycle, 11)},
1793- {f: frameOf(ports.MsgCommittedRouteIdentity, mustMarshalCommittedIdentity(protocol.CommittedRouteIdentity{1794+ {f: frameOf(wire.MsgCommittedRouteIdentity, mustMarshalCommittedIdentity(protocol.CommittedRouteIdentity{
1794 Target: protocol.ExactSessionTarget{LifecycleID: newLifecycle, SessionName: "new"},1795 Target: protocol.ExactSessionTarget{LifecycleID: newLifecycle, SessionName: "new"},
1795 }))},1796 }))},
1796 {err: io.EOF},1797 {err: io.EOF},
1797 }}1798 }}
1798 tr2 := &recordingTransport{recvs: []recvItem{1799 tr2 := &recordingTransport{recvs: []recvItem{
1799 {f: welcome("new", newLifecycle, 22)},1800 {f: welcome("new", newLifecycle, 22)},
1800- {f: frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},1801+ {f: frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},
1801 }}1802 }}
1802 dialer := &sequenceDialer{trs: []ports.Transport{tr1, tr2}}1803 dialer := &sequenceDialer{trs: []ports.Transport{tr1, tr2}}
1803 clock := &reconnectTestClock{}1804 clock := &reconnectTestClock{}
@@ -1838,8 +1839,8 @@ func TestAttachRememberRemoteHost(t *testing.T) {
1838 },1839 },
1839 remember: func() error { return nil },1840 remember: func() error { return nil },
1840 recv: []recvItem{1841 recv: []recvItem{
1841- {f: frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},1842+ {f: frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},
1842- {f: frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},1843+ {f: frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},
1843 },1844 },
1844 wantCalled: true,1845 wantCalled: true,
1845 },1846 },
@@ -1852,8 +1853,8 @@ func TestAttachRememberRemoteHost(t *testing.T) {
1852 },1853 },
1853 remember: func() error { return errors.New("disk full") },1854 remember: func() error { return errors.New("disk full") },
1854 recv: []recvItem{1855 recv: []recvItem{
1855- {f: frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},1856+ {f: frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},
1856- {f: frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},1857+ {f: frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},
1857 },1858 },
1858 wantCalled: true,1859 wantCalled: true,
1859 },1860 },
@@ -1869,8 +1870,8 @@ func TestAttachRememberRemoteHost(t *testing.T) {
1869 return nil1870 return nil
1870 },1871 },
1871 recv: []recvItem{1872 recv: []recvItem{
1872- {f: frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},1873+ {f: frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},
1873- {f: frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},1874+ {f: frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},
1874 },1875 },
1875 },1876 },
1876 {1877 {
@@ -1885,7 +1886,7 @@ func TestAttachRememberRemoteHost(t *testing.T) {
1885 return nil1886 return nil
1886 },1887 },
1887 recv: []recvItem{1888 recv: []recvItem{
1888- {f: frameOf(ports.MsgError, ports.MarshalErrorMsg(protocol.ErrorMsg{Code: protocol.ErrVersionMismatch, Text: "version mismatch"}))},1889+ {f: frameOf(wire.MsgError, wire.MarshalErrorMsg(protocol.ErrorMsg{Code: protocol.ErrVersionMismatch, Text: "version mismatch"}))},
1889 },1890 },
1890 preWelcomeReject: true,1891 preWelcomeReject: true,
1891 wantErr: true,1892 wantErr: true,
@@ -1899,7 +1900,7 @@ func TestAttachRememberRemoteHost(t *testing.T) {
1899 },1900 },
1900 remember: func() error { return nil },1901 remember: func() error { return nil },
1901 recv: []recvItem{1902 recv: []recvItem{
1902- {f: frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},1903+ {f: frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},
1903 {err: errors.New("connection reset")},1904 {err: errors.New("connection reset")},
1904 },1905 },
1905 wantCalled: true,1906 wantCalled: true,
@@ -1940,9 +1941,9 @@ func TestAttachRememberRemoteHost(t *testing.T) {
1940 1941
1941 tr := portsmocks.NewMockTransport(t)1942 tr := portsmocks.NewMockTransport(t)
1942 if !tt.preWelcomeReject {1943 if !tt.preWelcomeReject {
1943- tr.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()1944+ tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
1944 }1945 }
1945- tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once()1946+ tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once()
1946 if tt.preWelcomeReject {1947 if tt.preWelcomeReject {
1947 tr.EXPECT().Recv().Return(tt.recv[0].f, tt.recv[0].err).Once()1948 tr.EXPECT().Recv().Return(tt.recv[0].f, tt.recv[0].err).Once()
1948 } else {1949 } else {
@@ -1984,7 +1985,7 @@ func TestRunRememberRemoteHostAtMostOnceAcrossReconnects(t *testing.T) {
1984 learnerDone := make(chan struct{})1985 learnerDone := make(chan struct{})
1985 1986
1986 tr1 := &recordingTransport{recvs: []recvItem{{f: welcomeFrame(11)}, {err: io.EOF}}}1987 tr1 := &recordingTransport{recvs: []recvItem{{f: welcomeFrame(11)}, {err: io.EOF}}}
1987- tr2 := &recordingTransport{recvs: []recvItem{{f: welcomeFrame(22)}, {f: frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))}}}1988+ tr2 := &recordingTransport{recvs: []recvItem{{f: welcomeFrame(22)}, {f: frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))}}}
1988 d := &sequenceDialer{trs: []ports.Transport{tr1, tr2}}1989 d := &sequenceDialer{trs: []ports.Transport{tr1, tr2}}
1989 1990
1990 deps := testDependencies(d, term, realClock{}, nil, nil)1991 deps := testDependencies(d, term, realClock{}, nil, nil)
@@ -2033,11 +2034,11 @@ func TestAttachRememberRemoteHostDoesNotBlockAfterWelcome(t *testing.T) {
2033 learnerDone := make(chan struct{})2034 learnerDone := make(chan struct{})
2034 2035
2035 tr := portsmocks.NewMockTransport(t)2036 tr := portsmocks.NewMockTransport(t)
2036- tr.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()2037+ tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
2037- tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once()2038+ tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once()
2038 unblock := scriptRecv(tr,2039 unblock := scriptRecv(tr,
2039- recvItem{f: frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},2040+ recvItem{f: frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},
2040- recvItem{f: frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},2041+ recvItem{f: frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},
2041 )2042 )
2042 defer unblock()2043 defer unblock()
2043 tr.EXPECT().Close().Return(nil).Once()2044 tr.EXPECT().Close().Return(nil).Once()
@@ -2114,11 +2115,11 @@ func TestRunRestoresTerminalBeforeWaitingForLearner(t *testing.T) {
2114 learnerDone := make(chan struct{})2115 learnerDone := make(chan struct{})
2115 2116
2116 tr := portsmocks.NewMockTransport(t)2117 tr := portsmocks.NewMockTransport(t)
2117- tr.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()2118+ tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
2118- tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once()2119+ tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once()
2119 unblock := scriptRecv(tr,2120 unblock := scriptRecv(tr,
2120- recvItem{f: frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},2121+ recvItem{f: frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},
2121- recvItem{f: frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},2122+ recvItem{f: frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},
2122 )2123 )
2123 defer unblock()2124 defer unblock()
2124 tr.EXPECT().Close().Return(nil).Once()2125 tr.EXPECT().Close().Return(nil).Once()
@@ -2211,11 +2212,11 @@ func TestRunReturnsWhenRemoteHostLearnerStalls(t *testing.T) {
2211 }).Maybe()2212 }).Maybe()
2212 2213
2213 tr := portsmocks.NewMockTransport(t)2214 tr := portsmocks.NewMockTransport(t)
2214- tr.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()2215+ tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
2215- tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once()2216+ tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once()
2216 unblock := scriptRecv(tr,2217 unblock := scriptRecv(tr,
2217- recvItem{f: frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},2218+ recvItem{f: frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},
2218- recvItem{f: frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},2219+ recvItem{f: frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},
2219 )2220 )
2220 defer unblock()2221 defer unblock()
2221 tr.EXPECT().Close().Return(nil).Once()2222 tr.EXPECT().Close().Return(nil).Once()
@@ -2267,7 +2268,7 @@ func TestRunReturnsWhenRemoteHostLearnerStalls(t *testing.T) {
2267 func TestRunDoesNotRetryTerminalDetachedError(t *testing.T) {2268 func TestRunDoesNotRetryTerminalDetachedError(t *testing.T) {
2268 term := newRunTerminal()2269 term := newRunTerminal()
2269 defer term.in.unblock()2270 defer term.in.unblock()
2270- tr := &recordingTransport{recvs: []recvItem{{f: welcomeFrame(11)}, {f: frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonSessionKilled}))}}}2271+ tr := &recordingTransport{recvs: []recvItem{{f: welcomeFrame(11)}, {f: frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonSessionKilled}))}}}
2271 d := &sequenceDialer{trs: []ports.Transport{tr}}2272 d := &sequenceDialer{trs: []ports.Transport{tr}}
2272 2273
2273 err := runTestClient(context.Background(), testDependencies(d, term, realClock{}, nil, nil), client.AttachRequest{Intent: protocol.IntentAttach, SessionName: "main", Remote: false})2274 err := runTestClient(context.Background(), testDependencies(d, term, realClock{}, nil, nil), client.AttachRequest{Intent: protocol.IntentAttach, SessionName: "main", Remote: false})
M internal/usecase/client/clipboard_intercept_test.go +25 -24
@@ -15,6 +15,7 @@ import (
15 "github.com/bnema/vev/internal/ports"15 "github.com/bnema/vev/internal/ports"
16 portsmocks "github.com/bnema/vev/internal/ports/mocks"16 portsmocks "github.com/bnema/vev/internal/ports/mocks"
17 "github.com/bnema/vev/internal/protocol"17 "github.com/bnema/vev/internal/protocol"
18+ "github.com/bnema/vev/internal/protocol/wire"
18 "github.com/bnema/vev/internal/usecase/client"19 "github.com/bnema/vev/internal/usecase/client"
19 )20 )
20 21
@@ -44,11 +45,11 @@ func runRemoteClipboardTest(t *testing.T, remote bool, clip ports.ClipboardReade
44 allowDetach := make(chan struct{})45 allowDetach := make(chan struct{})
45 46
46 tr := portsmocks.NewMockTransport(t)47 tr := portsmocks.NewMockTransport(t)
47- tr.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()48+ tr.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
48- tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once()49+ tr.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once()
49- tr.EXPECT().Send(isType(ports.MsgResize)).Return(nil).Maybe()50+ tr.EXPECT().Send(isType(wire.MsgResize)).Return(nil).Maybe()
50- tr.EXPECT().Send(isType(ports.MsgInput)).RunAndReturn(func(f ports.Frame) error {51+ tr.EXPECT().Send(isType(wire.MsgInput)).RunAndReturn(func(f wire.Frame) error {
51- in, err := ports.UnmarshalInput(f.Payload)52+ in, err := wire.UnmarshalInput(f.Payload)
52 require.NoError(t, err)53 require.NoError(t, err)
53 gotInput <- append([]byte(nil), in.Data...)54 gotInput <- append([]byte(nil), in.Data...)
54 if bytes.IndexByte(in.Data, triggerByte) >= 0 {55 if bytes.IndexByte(in.Data, triggerByte) >= 0 {
@@ -56,33 +57,33 @@ func runRemoteClipboardTest(t *testing.T, remote bool, clip ports.ClipboardReade
56 }57 }
57 return nil58 return nil
58 }).Maybe()59 }).Maybe()
59- tr.EXPECT().Send(isType(ports.MsgImagePush)).RunAndReturn(func(f ports.Frame) error {60+ tr.EXPECT().Send(isType(wire.MsgImagePush)).RunAndReturn(func(f wire.Frame) error {
60- ip, err := ports.UnmarshalImagePush(f.Payload)61+ ip, err := wire.UnmarshalImagePush(f.Payload)
61 require.NoError(t, err)62 require.NoError(t, err)
62 gotImage <- ip63 gotImage <- ip
63 return nil64 return nil
64 }).Maybe()65 }).Maybe()
65- tr.EXPECT().Send(isType(ports.MsgClientNotice)).Return(nil).Maybe()66+ tr.EXPECT().Send(isType(wire.MsgClientNotice)).Return(nil).Maybe()
66 67
67- welcome := frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))68+ welcome := frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))
68- detached := frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))69+ detached := frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))
69 recvCh := make(chan recvItem, 1)70 recvCh := make(chan recvItem, 1)
70 recvCh <- recvItem{f: welcome}71 recvCh <- recvItem{f: welcome}
71 closed := make(chan struct{})72 closed := make(chan struct{})
72- tr.EXPECT().Recv().RunAndReturn(func() (ports.Frame, error) {73+ tr.EXPECT().Recv().RunAndReturn(func() (wire.Frame, error) {
73 select {74 select {
74 case it := <-recvCh:75 case it := <-recvCh:
75 return it.f, it.err76 return it.f, it.err
76 case <-allowDetach:77 case <-allowDetach:
77 select {78 select {
78 case <-closed:79 case <-closed:
79- return ports.Frame{}, io.EOF80+ return wire.Frame{}, io.EOF
80 default:81 default:
81 close(closed)82 close(closed)
82 return detached, nil83 return detached, nil
83 }84 }
84 case <-closed:85 case <-closed:
85- return ports.Frame{}, io.EOF86+ return wire.Frame{}, io.EOF
86 }87 }
87 }).Maybe()88 }).Maybe()
88 tr.EXPECT().Close().Return(nil).Once()89 tr.EXPECT().Close().Return(nil).Once()
@@ -142,17 +143,17 @@ func TestRunRemoteClipboardCtrlVWithNoImageForwardsCtrlV(t *testing.T) {
142 }143 }
143 144
144 type clipboardToastLifecycleTransport struct {145 type clipboardToastLifecycleTransport struct {
145- recv chan ports.Frame146+ recv chan wire.Frame
146- sends chan ports.Frame147+ sends chan wire.Frame
147 }148 }
148 149
149-func (t *clipboardToastLifecycleTransport) Send(f ports.Frame) error {150+func (t *clipboardToastLifecycleTransport) Send(f wire.Frame) error {
150 t.sends <- f151 t.sends <- f
151 return nil152 return nil
152 }153 }
153 154
154-func (t *clipboardToastLifecycleTransport) Recv() (ports.Frame, error) { return <-t.recv, nil }155+func (t *clipboardToastLifecycleTransport) Recv() (wire.Frame, error) { return <-t.recv, nil }
155-func (*clipboardToastLifecycleTransport) Close() error { return nil }156+func (*clipboardToastLifecycleTransport) Close() error { return nil }
156 157
157 type clipboardToastLifecycleDialer struct{ transport ports.Transport }158 type clipboardToastLifecycleDialer struct{ transport ports.Transport }
158 159
@@ -175,8 +176,8 @@ func TestRunRemoteClipboardFailureNotifiesDaemonAndWritesOutputVerbatim(t *testi
175 176
176 clipboard := portsmocks.NewMockClipboardReader(t)177 clipboard := portsmocks.NewMockClipboardReader(t)
177 clipboard.EXPECT().ReadImage(mock.Anything).Return("", nil, errors.New("read failed")).Once()178 clipboard.EXPECT().ReadImage(mock.Anything).Return("", nil, errors.New("read failed")).Once()
178- transport := &clipboardToastLifecycleTransport{recv: make(chan ports.Frame, 8), sends: make(chan ports.Frame, 16)}179+ transport := &clipboardToastLifecycleTransport{recv: make(chan wire.Frame, 8), sends: make(chan wire.Frame, 16)}
179- transport.recv <- frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))180+ transport.recv <- frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))
180 181
181 result := make(chan error, 1)182 result := make(chan error, 1)
182 go func() {183 go func() {
@@ -187,10 +188,10 @@ func TestRunRemoteClipboardFailureNotifiesDaemonAndWritesOutputVerbatim(t *testi
187 for !gotNotice {188 for !gotNotice {
188 select {189 select {
189 case sent := <-transport.sends:190 case sent := <-transport.sends:
190- if sent.Type != ports.MsgClientNotice {191+ if sent.Type != wire.MsgClientNotice {
191 continue192 continue
192 }193 }
193- notice, err := ports.UnmarshalClientNotice(sent.Payload)194+ notice, err := wire.UnmarshalClientNotice(sent.Payload)
194 require.NoError(t, err)195 require.NoError(t, err)
195 require.Equal(t, protocol.ClientNoticeClipboardFallback, notice.Action)196 require.Equal(t, protocol.ClientNoticeClipboardFallback, notice.Action)
196 gotNotice = true197 gotNotice = true
@@ -199,8 +200,8 @@ func TestRunRemoteClipboardFailureNotifiesDaemonAndWritesOutputVerbatim(t *testi
199 }200 }
200 }201 }
201 beforeOutput := out.String()202 beforeOutput := out.String()
202- transport.recv <- frameOf(ports.MsgOutput, mustMarshalOutput(protocol.Output{Epoch: 1, Base: 0, New: 1, Size: domain.Size{Cols: 1, Rows: 1}, Full: true, Data: []byte("incremental")}))203+ transport.recv <- frameOf(wire.MsgOutput, mustMarshalOutput(protocol.Output{Epoch: 1, Base: 0, New: 1, Size: domain.Size{Cols: 1, Rows: 1}, Full: true, Data: []byte("incremental")}))
203- transport.recv <- frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))204+ transport.recv <- frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))
204 205
205 select {206 select {
206 case err := <-result:207 case err := <-result:
M internal/usecase/client/palette_attach_generation_test.go +14 -13
@@ -17,6 +17,7 @@ import (
17 "github.com/bnema/vev/internal/domain"17 "github.com/bnema/vev/internal/domain"
18 "github.com/bnema/vev/internal/ports"18 "github.com/bnema/vev/internal/ports"
19 "github.com/bnema/vev/internal/protocol"19 "github.com/bnema/vev/internal/protocol"
20+ "github.com/bnema/vev/internal/protocol/wire"
20 )21 )
21 22
22 // attachPaletteClock deliberately exposes each independently-created timer.23 // attachPaletteClock deliberately exposes each independently-created timer.
@@ -115,11 +116,11 @@ func (*attachPaletteTerminalHarness) Flush() error { r
115 116
116 type attachPaletteTransport struct {117 type attachPaletteTransport struct {
117 mu sync.Mutex118 mu sync.Mutex
118- frames []ports.Frame119+ frames []wire.Frame
119 themes []protocol.Theme120 themes []protocol.Theme
120 themeCh chan protocol.Theme121 themeCh chan protocol.Theme
121 inputCh chan protocol.Input122 inputCh chan protocol.Input
122- detached chan ports.Frame123+ detached chan wire.Frame
123 themeSendErr error124 themeSendErr error
124 welcomed bool125 welcomed bool
125 }126 }
@@ -128,20 +129,20 @@ func newAttachPaletteTransport() *attachPaletteTransport {
128 return &attachPaletteTransport{129 return &attachPaletteTransport{
129 themeCh: make(chan protocol.Theme, 16),130 themeCh: make(chan protocol.Theme, 16),
130 inputCh: make(chan protocol.Input, 16),131 inputCh: make(chan protocol.Input, 16),
131- detached: make(chan ports.Frame, 1),132+ detached: make(chan wire.Frame, 1),
132 }133 }
133 }134 }
134-func (t *attachPaletteTransport) Send(frame ports.Frame) error {135+func (t *attachPaletteTransport) Send(frame wire.Frame) error {
135 t.mu.Lock()136 t.mu.Lock()
136 t.frames = append(t.frames, frame)137 t.frames = append(t.frames, frame)
137 themeSendErr := t.themeSendErr138 themeSendErr := t.themeSendErr
138 t.mu.Unlock()139 t.mu.Unlock()
139 switch frame.Type {140 switch frame.Type {
140- case ports.MsgTheme:141+ case wire.MsgTheme:
141 if themeSendErr != nil {142 if themeSendErr != nil {
142 return themeSendErr143 return themeSendErr
143 }144 }
144- got, err := ports.UnmarshalTheme(frame.Payload)145+ got, err := wire.UnmarshalTheme(frame.Payload)
145 if err != nil {146 if err != nil {
146 return err147 return err
147 }148 }
@@ -149,8 +150,8 @@ func (t *attachPaletteTransport) Send(frame ports.Frame) error {
149 t.themes = append(t.themes, got)150 t.themes = append(t.themes, got)
150 t.mu.Unlock()151 t.mu.Unlock()
151 t.themeCh <- got152 t.themeCh <- got
152- case ports.MsgInput:153+ case wire.MsgInput:
153- got, err := ports.UnmarshalInput(frame.Payload)154+ got, err := wire.UnmarshalInput(frame.Payload)
154 if err != nil {155 if err != nil {
155 return err156 return err
156 }157 }
@@ -158,12 +159,12 @@ func (t *attachPaletteTransport) Send(frame ports.Frame) error {
158 }159 }
159 return nil160 return nil
160 }161 }
161-func (t *attachPaletteTransport) Recv() (ports.Frame, error) {162+func (t *attachPaletteTransport) Recv() (wire.Frame, error) {
162 t.mu.Lock()163 t.mu.Lock()
163 if !t.welcomed {164 if !t.welcomed {
164 t.welcomed = true165 t.welcomed = true
165 t.mu.Unlock()166 t.mu.Unlock()
166- return ports.Frame{Type: ports.MsgWelcome, Payload: ports.MarshalWelcome(protocol.Welcome{SessionID: "s"})}, nil167+ return wire.Frame{Type: wire.MsgWelcome, Payload: wire.MarshalWelcome(protocol.Welcome{SessionID: "s"})}, nil
167 }168 }
168 t.mu.Unlock()169 t.mu.Unlock()
169 return <-t.detached, nil170 return <-t.detached, nil
@@ -239,7 +240,7 @@ func (h *attachPaletteHarness) nextTimer(t *testing.T) *attachPaletteTimer {
239 }240 }
240 func (h *attachPaletteHarness) detach(t *testing.T) {241 func (h *attachPaletteHarness) detach(t *testing.T) {
241 t.Helper()242 t.Helper()
242- h.transport.detached <- ports.Frame{Type: ports.MsgDetached, Payload: ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach})}243+ h.transport.detached <- wire.Frame{Type: wire.MsgDetached, Payload: wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach})}
243 result := <-h.done244 result := <-h.done
244 require.NoError(t, result.err)245 require.NoError(t, result.err)
245 select {246 select {
@@ -325,7 +326,7 @@ func TestAttachReconnectPreservesStandaloneEscapeBeforeClaimRevocation(t *testin
325 326
326 // Detach starts reconnect teardown. The residual must already be owned by327 // Detach starts reconnect teardown. The residual must already be owned by
327 // the lifecycle reader at the precise point the old claim is revoked.328 // the lifecycle reader at the precise point the old claim is revoked.
328- first.transport.detached <- ports.Frame{Type: ports.MsgDetached, Payload: ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach})}329+ first.transport.detached <- wire.Frame{Type: wire.MsgDetached, Payload: wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach})}
329 require.Equal(t, []byte("\x1b"), <-revoked)330 require.Equal(t, []byte("\x1b"), <-revoked)
330 release()331 release()
331 require.NoError(t, (<-first.done).err)332 require.NoError(t, (<-first.done).err)
@@ -437,7 +438,7 @@ func TestStdinPumpSameReadSchemeAndOldMarkerCannotAdvanceReplacement(t *testing.
437 // notification and publish its replacement generation before the scanner438 // notification and publish its replacement generation before the scanner
438 // can deliver the old marker from the same terminal Read.439 // can deliver the old marker from the same terminal Read.
439 events := make(chan paletteGenerationEvent)440 events := make(chan paletteGenerationEvent)
440- out := make(chan ports.Frame, 1)441+ out := make(chan wire.Frame, 1)
441 schemeHandled := make(chan struct{})442 schemeHandled := make(chan struct{})
442 pump := &stdinPump{443 pump := &stdinPump{
443 ctx: ctx,444 ctx: ctx,
M internal/usecase/client/palette_attach_test.go +9 -8
@@ -15,6 +15,7 @@ import (
15 "github.com/bnema/vev/internal/domain"15 "github.com/bnema/vev/internal/domain"
16 "github.com/bnema/vev/internal/ports"16 "github.com/bnema/vev/internal/ports"
17 "github.com/bnema/vev/internal/protocol"17 "github.com/bnema/vev/internal/protocol"
18+ "github.com/bnema/vev/internal/protocol/wire"
18 )19 )
19 20
20 type paletteAttachClock struct{}21 type paletteAttachClock struct{}
@@ -78,18 +79,18 @@ func (t *paletteAttachTerminal) Flush() error { return n
78 79
79 type paletteAttachTransport struct {80 type paletteAttachTransport struct {
80 mu sync.Mutex81 mu sync.Mutex
81- sent []ports.Frame82+ sent []wire.Frame
82 themes []protocol.Theme83 themes []protocol.Theme
83 finals int84 finals int
84 finalSet chan struct{}85 finalSet chan struct{}
85 }86 }
86 87
87-func (t *paletteAttachTransport) Send(frame ports.Frame) error {88+func (t *paletteAttachTransport) Send(frame wire.Frame) error {
88 t.mu.Lock()89 t.mu.Lock()
89 defer t.mu.Unlock()90 defer t.mu.Unlock()
90 t.sent = append(t.sent, frame)91 t.sent = append(t.sent, frame)
91- if frame.Type == ports.MsgTheme {92+ if frame.Type == wire.MsgTheme {
92- theme, err := ports.UnmarshalTheme(frame.Payload)93+ theme, err := wire.UnmarshalTheme(frame.Payload)
93 if err != nil {94 if err != nil {
94 return err95 return err
95 }96 }
@@ -104,15 +105,15 @@ func (t *paletteAttachTransport) Send(frame ports.Frame) error {
104 return nil105 return nil
105 }106 }
106 107
107-func (t *paletteAttachTransport) Recv() (ports.Frame, error) {108+func (t *paletteAttachTransport) Recv() (wire.Frame, error) {
108 t.mu.Lock()109 t.mu.Lock()
109 first := len(t.sent) == 1110 first := len(t.sent) == 1
110 t.mu.Unlock()111 t.mu.Unlock()
111 if first {112 if first {
112- return ports.Frame{Type: ports.MsgWelcome, Payload: ports.MarshalWelcome(protocol.Welcome{SessionID: "s"})}, nil113+ return wire.Frame{Type: wire.MsgWelcome, Payload: wire.MarshalWelcome(protocol.Welcome{SessionID: "s"})}, nil
113 }114 }
114 <-t.finalSet115 <-t.finalSet
115- return ports.Frame{Type: ports.MsgDetached, Payload: ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach})}, nil116+ return wire.Frame{Type: wire.MsgDetached, Payload: wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach})}, nil
116 }117 }
117 func (*paletteAttachTransport) Close() error { return nil }118 func (*paletteAttachTransport) Close() error { return nil }
118 119
@@ -155,7 +156,7 @@ func TestAttachPublishesOnlyClearedAndDefinitiveInitialPalette(t *testing.T) {
155 transport.mu.Lock()156 transport.mu.Lock()
156 defer transport.mu.Unlock()157 defer transport.mu.Unlock()
157 require.Len(t, transport.sent, 3)158 require.Len(t, transport.sent, 3)
158- require.Equal(t, []ports.MsgType{ports.MsgHello, ports.MsgTheme, ports.MsgTheme}, []ports.MsgType{159+ require.Equal(t, []wire.MsgType{wire.MsgHello, wire.MsgTheme, wire.MsgTheme}, []wire.MsgType{
159 transport.sent[0].Type, transport.sent[1].Type, transport.sent[2].Type,160 transport.sent[0].Type, transport.sent[1].Type, transport.sent[2].Type,
160 }, "the protocol requires Hello before the two palette publications")161 }, "the protocol requires Hello before the two palette publications")
161 require.Len(t, transport.themes, 2)162 require.Len(t, transport.themes, 2)
M internal/usecase/client/reconnect_toast_test.go +61 -60
@@ -21,6 +21,7 @@ import (
21 "github.com/bnema/vev/internal/ports"21 "github.com/bnema/vev/internal/ports"
22 portsmocks "github.com/bnema/vev/internal/ports/mocks"22 portsmocks "github.com/bnema/vev/internal/ports/mocks"
23 "github.com/bnema/vev/internal/protocol"23 "github.com/bnema/vev/internal/protocol"
24+ "github.com/bnema/vev/internal/protocol/wire"
24 )25 )
25 26
26 type reconnectToastTerminalHarness struct {27 type reconnectToastTerminalHarness struct {
@@ -129,20 +130,20 @@ func (r *reconnectToastOutputRecorder) String() string {
129 return r.all.String()130 return r.all.String()
130 }131 }
131 132
132-func reconnectToastWelcome(token uint64) ports.Frame {133+func reconnectToastWelcome(token uint64) wire.Frame {
133 return reconnectToastWelcomeNamed("", token)134 return reconnectToastWelcomeNamed("", token)
134 }135 }
135 136
136-func reconnectToastWelcomeNamed(name string, token uint64) ports.Frame {137+func reconnectToastWelcomeNamed(name string, token uint64) wire.Frame {
137- return ports.Frame{Type: ports.MsgWelcome, Payload: ports.MarshalWelcome(protocol.Welcome{SessionID: "s1", SessionName: name, ResumeToken: token, Capabilities: protocol.CapabilityResume})}138+ return wire.Frame{Type: wire.MsgWelcome, Payload: wire.MarshalWelcome(protocol.Welcome{SessionID: "s1", SessionName: name, ResumeToken: token, Capabilities: protocol.CapabilityResume})}
138 }139 }
139 140
140-func reconnectToastDetach(reason uint8) ports.Frame {141+func reconnectToastDetach(reason uint8) wire.Frame {
141- return ports.Frame{Type: ports.MsgDetached, Payload: ports.MarshalDetached(protocol.Detached{Reason: reason})}142+ return wire.Frame{Type: wire.MsgDetached, Payload: wire.MarshalDetached(protocol.Detached{Reason: reason})}
142 }143 }
143 144
144 type reconnectToastRecv struct {145 type reconnectToastRecv struct {
145- frame ports.Frame146+ frame wire.Frame
146 err error147 err error
147 }148 }
148 149
@@ -163,7 +164,7 @@ func (d *reconnectToastSequenceDialer) Dial(context.Context) (ports.Transport, e
163 type reconnectToastRecordingTransport struct {164 type reconnectToastRecordingTransport struct {
164 mu sync.Mutex165 mu sync.Mutex
165 recvs []reconnectToastRecv166 recvs []reconnectToastRecv
166- sends []ports.Frame167+ sends []wire.Frame
167 closed bool168 closed bool
168 }169 }
169 170
@@ -173,7 +174,7 @@ func newReconnectToastRecordingTransport(recvs ...reconnectToastRecv) *reconnect
173 174
174 type reconnectToastSentFrames struct {175 type reconnectToastSentFrames struct {
175 mu sync.Mutex176 mu sync.Mutex
176- frames []ports.Frame177+ frames []wire.Frame
177 changed chan struct{}178 changed chan struct{}
178 }179 }
179 180
@@ -181,7 +182,7 @@ func newReconnectToastSentFrames() *reconnectToastSentFrames {
181 return &reconnectToastSentFrames{changed: make(chan struct{}, 1)}182 return &reconnectToastSentFrames{changed: make(chan struct{}, 1)}
182 }183 }
183 184
184-func (s *reconnectToastSentFrames) record(frame ports.Frame) {185+func (s *reconnectToastSentFrames) record(frame wire.Frame) {
185 s.mu.Lock()186 s.mu.Lock()
186 s.frames = append(s.frames, frame)187 s.frames = append(s.frames, frame)
187 s.mu.Unlock()188 s.mu.Unlock()
@@ -191,7 +192,7 @@ func (s *reconnectToastSentFrames) record(frame ports.Frame) {
191 }192 }
192 }193 }
193 194
194-func (s *reconnectToastSentFrames) find(match func(ports.Frame) bool) (ports.Frame, bool) {195+func (s *reconnectToastSentFrames) find(match func(wire.Frame) bool) (wire.Frame, bool) {
195 s.mu.Lock()196 s.mu.Lock()
196 defer s.mu.Unlock()197 defer s.mu.Unlock()
197 for _, frame := range s.frames {198 for _, frame := range s.frames {
@@ -199,7 +200,7 @@ func (s *reconnectToastSentFrames) find(match func(ports.Frame) bool) (ports.Fra
199 return frame, true200 return frame, true
200 }201 }
201 }202 }
202- return ports.Frame{}, false203+ return wire.Frame{}, false
203 }204 }
204 205
205 type reconnectToastLinkTransport struct {206 type reconnectToastLinkTransport struct {
@@ -220,12 +221,12 @@ func newReconnectToastLinkTransport() *reconnectToastLinkTransport {
220 }221 }
221 }222 }
222 223
223-func (t *reconnectToastLinkTransport) Send(f ports.Frame) error {224+func (t *reconnectToastLinkTransport) Send(f wire.Frame) error {
224 t.sends.record(f)225 t.sends.record(f)
225- if f.Type != ports.MsgClientNotice {226+ if f.Type != wire.MsgClientNotice {
226 return nil227 return nil
227 }228 }
228- notice, err := ports.UnmarshalClientNotice(f.Payload)229+ notice, err := wire.UnmarshalClientNotice(f.Payload)
229 if err != nil {230 if err != nil {
230 return err231 return err
231 }232 }
@@ -233,7 +234,7 @@ func (t *reconnectToastLinkTransport) Send(f ports.Frame) error {
233 return nil234 return nil
234 }235 }
235 236
236-func (t *reconnectToastLinkTransport) Recv() (ports.Frame, error) {237+func (t *reconnectToastLinkTransport) Recv() (wire.Frame, error) {
237 recv := <-t.recvCh238 recv := <-t.recvCh
238 return recv.frame, recv.err239 return recv.frame, recv.err
239 }240 }
@@ -244,18 +245,18 @@ func (t *reconnectToastLinkTransport) LinkEvents() <-chan ports.LinkEvent { retu
244 245
245 func (t *reconnectToastLinkTransport) LinkState() ports.LinkState { return t.state }246 func (t *reconnectToastLinkTransport) LinkState() ports.LinkState { return t.state }
246 247
247-func (t *reconnectToastRecordingTransport) Send(f ports.Frame) error {248+func (t *reconnectToastRecordingTransport) Send(f wire.Frame) error {
248 t.mu.Lock()249 t.mu.Lock()
249 defer t.mu.Unlock()250 defer t.mu.Unlock()
250 t.sends = append(t.sends, f)251 t.sends = append(t.sends, f)
251 return nil252 return nil
252 }253 }
253 254
254-func (t *reconnectToastRecordingTransport) Recv() (ports.Frame, error) {255+func (t *reconnectToastRecordingTransport) Recv() (wire.Frame, error) {
255 t.mu.Lock()256 t.mu.Lock()
256 defer t.mu.Unlock()257 defer t.mu.Unlock()
257 if len(t.recvs) == 0 {258 if len(t.recvs) == 0 {
258- return ports.Frame{}, io.EOF259+ return wire.Frame{}, io.EOF
259 }260 }
260 recv := t.recvs[0]261 recv := t.recvs[0]
261 t.recvs = t.recvs[1:]262 t.recvs = t.recvs[1:]
@@ -269,10 +270,10 @@ func (t *reconnectToastRecordingTransport) Close() error {
269 return nil270 return nil
270 }271 }
271 272
272-func (t *reconnectToastRecordingTransport) sentFrames() []ports.Frame {273+func (t *reconnectToastRecordingTransport) sentFrames() []wire.Frame {
273 t.mu.Lock()274 t.mu.Lock()
274 defer t.mu.Unlock()275 defer t.mu.Unlock()
275- return append([]ports.Frame(nil), t.sends...)276+ return append([]wire.Frame(nil), t.sends...)
276 }277 }
277 278
278 func (t *reconnectToastRecordingTransport) wasClosed() bool {279 func (t *reconnectToastRecordingTransport) wasClosed() bool {
@@ -288,9 +289,9 @@ func assertReconnectToastAttemptPublishesOnlyClearedTheme(t *testing.T, tr *reco
288 t.Helper()289 t.Helper()
289 frames := tr.sentFrames()290 frames := tr.sentFrames()
290 require.GreaterOrEqual(t, len(frames), 2)291 require.GreaterOrEqual(t, len(frames), 2)
291- require.Equal(t, ports.MsgHello, frames[0].Type)292+ require.Equal(t, wire.MsgHello, frames[0].Type)
292- require.Equal(t, ports.MsgTheme, frames[1].Type)293+ require.Equal(t, wire.MsgTheme, frames[1].Type)
293- theme, err := ports.UnmarshalTheme(frames[1].Payload)294+ theme, err := wire.UnmarshalTheme(frames[1].Payload)
294 require.NoError(t, err)295 require.NoError(t, err)
295 require.False(t, theme.HasForeground)296 require.False(t, theme.HasForeground)
296 require.False(t, theme.HasBackground)297 require.False(t, theme.HasBackground)
@@ -302,8 +303,8 @@ func reconnectToastHelloFromSend(t *testing.T, tr *reconnectToastRecordingTransp
302 t.Helper()303 t.Helper()
303 frames := tr.sentFrames()304 frames := tr.sentFrames()
304 require.NotEmpty(t, frames)305 require.NotEmpty(t, frames)
305- require.Equal(t, ports.MsgHello, frames[0].Type)306+ require.Equal(t, wire.MsgHello, frames[0].Type)
306- hello, err := ports.UnmarshalHello(frames[0].Payload)307+ hello, err := wire.UnmarshalHello(frames[0].Payload)
307 require.NoError(t, err)308 require.NoError(t, err)
308 return hello309 return hello
309 }310 }
@@ -394,7 +395,7 @@ func TestProbingToastReconcilesDaemonOutputAndDismissal(t *testing.T) {
394 require.Contains(t, firstToast, "┌")395 require.Contains(t, firstToast, "┌")
395 require.Contains(t, firstToast, "probing UDP path")396 require.Contains(t, firstToast, "probing UDP path")
396 397
397- tr.recvCh <- reconnectToastRecv{frame: ports.Frame{Type: ports.MsgOutput, Payload: mustMarshalOutput(protocol.Output{398+ tr.recvCh <- reconnectToastRecv{frame: wire.Frame{Type: wire.MsgOutput, Payload: mustMarshalOutput(protocol.Output{
398 Epoch: 1,399 Epoch: 1,
399 Base: 0,400 Base: 0,
400 New: 2,401 New: 2,
@@ -413,7 +414,7 @@ func TestProbingToastReconcilesDaemonOutputAndDismissal(t *testing.T) {
413 414
414 beforeAwaitingReset := out.String()415 beforeAwaitingReset := out.String()
415 beforeStatelessFlushes := flushes.Load()416 beforeStatelessFlushes := flushes.Load()
416- tr.recvCh <- reconnectToastRecv{frame: ports.Frame{Type: ports.MsgOutput, Payload: mustMarshalOutput(protocol.Output{417+ tr.recvCh <- reconnectToastRecv{frame: wire.Frame{Type: wire.MsgOutput, Payload: mustMarshalOutput(protocol.Output{
417 Epoch: 1,418 Epoch: 1,
418 Base: 0,419 Base: 0,
419 New: 0,420 New: 0,
@@ -425,7 +426,7 @@ func TestProbingToastReconcilesDaemonOutputAndDismissal(t *testing.T) {
425 }, time.Second, time.Millisecond)426 }, time.Second, time.Millisecond)
426 427
427 afterStateless := out.String()428 afterStateless := out.String()
428- tr.recvCh <- reconnectToastRecv{frame: ports.Frame{Type: ports.MsgOutput, Payload: mustMarshalOutput(protocol.Output{429+ tr.recvCh <- reconnectToastRecv{frame: wire.Frame{Type: wire.MsgOutput, Payload: mustMarshalOutput(protocol.Output{
429 Epoch: 1,430 Epoch: 1,
430 Base: 2,431 Base: 2,
431 New: 3,432 New: 3,
@@ -436,7 +437,7 @@ func TestProbingToastReconcilesDaemonOutputAndDismissal(t *testing.T) {
436 require.Contains(t, out.String(), "intervening incremental")437 require.Contains(t, out.String(), "intervening incremental")
437 require.Contains(t, afterStateless[len(beforeAwaitingReset):], "stateless side effect")438 require.Contains(t, afterStateless[len(beforeAwaitingReset):], "stateless side effect")
438 439
439- tr.recvCh <- reconnectToastRecv{frame: ports.Frame{Type: ports.MsgOutput, Payload: mustMarshalOutput(protocol.Output{440+ tr.recvCh <- reconnectToastRecv{frame: wire.Frame{Type: wire.MsgOutput, Payload: mustMarshalOutput(protocol.Output{
440 Epoch: 2,441 Epoch: 2,
441 Base: 0,442 Base: 0,
442 New: 4,443 New: 4,
@@ -449,7 +450,7 @@ func TestProbingToastReconcilesDaemonOutputAndDismissal(t *testing.T) {
449 require.NotContains(t, out.String()[len(beforeAwaitingReset):], strings.Repeat(" ", reconnectToastBoundsFor(term.size, "probing UDP path").Width))450 require.NotContains(t, out.String()[len(beforeAwaitingReset):], strings.Repeat(" ", reconnectToastBoundsFor(term.size, "probing UDP path").Width))
450 451
451 beforeIncrementFlushes := flushes.Load()452 beforeIncrementFlushes := flushes.Load()
452- tr.recvCh <- reconnectToastRecv{frame: ports.Frame{Type: ports.MsgOutput, Payload: mustMarshalOutput(protocol.Output{453+ tr.recvCh <- reconnectToastRecv{frame: wire.Frame{Type: wire.MsgOutput, Payload: mustMarshalOutput(protocol.Output{
453 Epoch: 2,454 Epoch: 2,
454 Base: 4,455 Base: 4,
455 New: 5,456 New: 5,
@@ -496,30 +497,30 @@ func TestActiveReconnectToastStageTransitionReconcilesBeforeRedraw(t *testing.T)
496 {Epoch: 1, Base: 1, New: 2, Size: domain.Size{Cols: 1, Rows: 1}, Data: []byte("first skipped increment")},497 {Epoch: 1, Base: 1, New: 2, Size: domain.Size{Cols: 1, Rows: 1}, Data: []byte("first skipped increment")},
497 {Epoch: 1, Base: 2, New: 3, Size: domain.Size{Cols: 1, Rows: 1}, Data: []byte("second skipped increment")},498 {Epoch: 1, Base: 2, New: 3, Size: domain.Size{Cols: 1, Rows: 1}, Data: []byte("second skipped increment")},
498 } {499 } {
499- tr.recvCh <- reconnectToastRecv{frame: ports.Frame{Type: ports.MsgOutput, Payload: mustMarshalOutput(output)}}500+ tr.recvCh <- reconnectToastRecv{frame: wire.Frame{Type: wire.MsgOutput, Payload: mustMarshalOutput(output)}}
500 require.Equal(t, beforeReset, out.String())501 require.Equal(t, beforeReset, out.String())
501 }502 }
502- requireSentFrame(t, tr.sends, "output reset request", func(frame ports.Frame) bool {503+ requireSentFrame(t, tr.sends, "output reset request", func(frame wire.Frame) bool {
503- return frame.Type == ports.MsgOutputResetRequest504+ return frame.Type == wire.MsgOutputResetRequest
504 })505 })
505- _, sentAck := tr.sends.find(func(frame ports.Frame) bool { return frame.Type == ports.MsgAck })506+ _, sentAck := tr.sends.find(func(frame wire.Frame) bool { return frame.Type == wire.MsgAck })
506 require.False(t, sentAck, "discarded output must not be ACKed")507 require.False(t, sentAck, "discarded output must not be ACKed")
507 508
508 // Handoff cleanup is an ordered terminal side effect, not a replay state.509 // Handoff cleanup is an ordered terminal side effect, not a replay state.
509 // It must cross the outstanding reset gate, flush before the control handoff,510 // It must cross the outstanding reset gate, flush before the control handoff,
510 // and must not manufacture an independent ACK.511 // and must not manufacture an independent ACK.
511- tr.recvCh <- reconnectToastRecv{frame: ports.Frame{Type: ports.MsgOutput, Payload: mustMarshalOutput(protocol.Output{512+ tr.recvCh <- reconnectToastRecv{frame: wire.Frame{Type: wire.MsgOutput, Payload: mustMarshalOutput(protocol.Output{
512 Epoch: 2,513 Epoch: 2,
513 Size: domain.Size{Cols: 1, Rows: 1},514 Size: domain.Size{Cols: 1, Rows: 1},
514 Data: []byte("handoff graphics cleanup"),515 Data: []byte("handoff graphics cleanup"),
515 })}}516 })}}
516 cleanup := requireReconnectToastOutput(t, out.completed)517 cleanup := requireReconnectToastOutput(t, out.completed)
517 require.Contains(t, cleanup, "handoff graphics cleanup")518 require.Contains(t, cleanup, "handoff graphics cleanup")
518- _, sentAck = tr.sends.find(func(frame ports.Frame) bool { return frame.Type == ports.MsgAck })519+ _, sentAck = tr.sends.find(func(frame wire.Frame) bool { return frame.Type == wire.MsgAck })
519 require.False(t, sentAck, "state-independent cleanup must not be ACKed")520 require.False(t, sentAck, "state-independent cleanup must not be ACKed")
520 521
521 beforeResetFlushes := flushes.Load()522 beforeResetFlushes := flushes.Load()
522- tr.recvCh <- reconnectToastRecv{frame: ports.Frame{Type: ports.MsgOutput, Payload: mustMarshalOutput(protocol.Output{523+ tr.recvCh <- reconnectToastRecv{frame: wire.Frame{Type: wire.MsgOutput, Payload: mustMarshalOutput(protocol.Output{
523 Epoch: 2,524 Epoch: 2,
524 Base: 0,525 Base: 0,
525 New: 4,526 New: 4,
@@ -534,7 +535,7 @@ func TestActiveReconnectToastStageTransitionReconcilesBeforeRedraw(t *testing.T)
534 requireAckedState(t, tr.sends, 2, 4)535 requireAckedState(t, tr.sends, 2, 4)
535 536
536 beforeIncrementFlushes := flushes.Load()537 beforeIncrementFlushes := flushes.Load()
537- tr.recvCh <- reconnectToastRecv{frame: ports.Frame{Type: ports.MsgOutput, Payload: mustMarshalOutput(protocol.Output{538+ tr.recvCh <- reconnectToastRecv{frame: wire.Frame{Type: wire.MsgOutput, Payload: mustMarshalOutput(protocol.Output{
538 Epoch: 2,539 Epoch: 2,
539 Base: 4,540 Base: 4,
540 New: 5,541 New: 5,
@@ -563,7 +564,7 @@ func requireReconnectToastOutput(t *testing.T, completed <-chan string) string {
563 }564 }
564 }565 }
565 566
566-func requireSentFrame(t *testing.T, sends *reconnectToastSentFrames, description string, match func(ports.Frame) bool) ports.Frame {567+func requireSentFrame(t *testing.T, sends *reconnectToastSentFrames, description string, match func(wire.Frame) bool) wire.Frame {
567 t.Helper()568 t.Helper()
568 timer := time.NewTimer(time.Second)569 timer := time.NewTimer(time.Second)
569 defer timer.Stop()570 defer timer.Stop()
@@ -575,31 +576,31 @@ func requireSentFrame(t *testing.T, sends *reconnectToastSentFrames, description
575 case <-sends.changed:576 case <-sends.changed:
576 case <-timer.C:577 case <-timer.C:
577 t.Fatalf("timed out waiting for %s", description)578 t.Fatalf("timed out waiting for %s", description)
578- return ports.Frame{}579+ return wire.Frame{}
579 }580 }
580 }581 }
581 }582 }
582 583
583 func requireResize(t *testing.T, sends *reconnectToastSentFrames) protocol.Resize {584 func requireResize(t *testing.T, sends *reconnectToastSentFrames) protocol.Resize {
584 t.Helper()585 t.Helper()
585- frame := requireSentFrame(t, sends, "Resize frame", func(frame ports.Frame) bool {586+ frame := requireSentFrame(t, sends, "Resize frame", func(frame wire.Frame) bool {
586- return frame.Type == ports.MsgResize587+ return frame.Type == wire.MsgResize
587 })588 })
588- resize, err := ports.UnmarshalResize(frame.Payload)589+ resize, err := wire.UnmarshalResize(frame.Payload)
589 require.NoError(t, err)590 require.NoError(t, err)
590 return resize591 return resize
591 }592 }
592 593
593 func requireAckedState(t *testing.T, sends *reconnectToastSentFrames, epoch, state uint64) {594 func requireAckedState(t *testing.T, sends *reconnectToastSentFrames, epoch, state uint64) {
594 t.Helper()595 t.Helper()
595- frame := requireSentFrame(t, sends, fmt.Sprintf("ACK for epoch %d state %d", epoch, state), func(frame ports.Frame) bool {596+ frame := requireSentFrame(t, sends, fmt.Sprintf("ACK for epoch %d state %d", epoch, state), func(frame wire.Frame) bool {
596- if frame.Type != ports.MsgAck {597+ if frame.Type != wire.MsgAck {
597 return false598 return false
598 }599 }
599- ack, err := ports.UnmarshalAck(frame.Payload)600+ ack, err := wire.UnmarshalAck(frame.Payload)
600 return err == nil && ack.Epoch == epoch && ack.State >= state601 return err == nil && ack.Epoch == epoch && ack.State >= state
601 })602 })
602- ack, err := ports.UnmarshalAck(frame.Payload)603+ ack, err := wire.UnmarshalAck(frame.Payload)
603 require.NoError(t, err)604 require.NoError(t, err)
604 require.Equal(t, epoch, ack.Epoch)605 require.Equal(t, epoch, ack.Epoch)
605 require.GreaterOrEqual(t, ack.State, state)606 require.GreaterOrEqual(t, ack.State, state)
@@ -707,7 +708,7 @@ func TestLocalReconnectStatusFlushesDaemonOutputBeforeObservationAndAck(t *testi
707 require.Eventually(t, func() bool { return flushes.Load() > beforeStatus }, time.Second, time.Millisecond)708 require.Eventually(t, func() bool { return flushes.Load() > beforeStatus }, time.Second, time.Millisecond)
708 beforeOutput := flushes.Load()709 beforeOutput := flushes.Load()
709 710
710- tr.recvCh <- reconnectToastRecv{frame: ports.Frame{Type: ports.MsgOutput, Payload: mustMarshalOutput(protocol.Output{711+ tr.recvCh <- reconnectToastRecv{frame: wire.Frame{Type: wire.MsgOutput, Payload: mustMarshalOutput(protocol.Output{
711 Epoch: 1,712 Epoch: 1,
712 Base: 0,713 Base: 0,
713 New: 2,714 New: 2,
@@ -759,7 +760,7 @@ func TestLocalReconnectStatusTransitionsDoNotRequestAuthoritativeReset(t *testin
759 }760 }
760 761
761 const output = "local status output remains visible"762 const output = "local status output remains visible"
762- tr.recvCh <- reconnectToastRecv{frame: ports.Frame{Type: ports.MsgOutput, Payload: mustMarshalOutput(protocol.Output{763+ tr.recvCh <- reconnectToastRecv{frame: wire.Frame{Type: wire.MsgOutput, Payload: mustMarshalOutput(protocol.Output{
763 Epoch: 1,764 Epoch: 1,
764 Base: 0,765 Base: 0,
765 New: 2,766 New: 2,
@@ -769,7 +770,7 @@ func TestLocalReconnectStatusTransitionsDoNotRequestAuthoritativeReset(t *testin
769 })}}770 })}}
770 require.Eventually(t, func() bool { return strings.Contains(out.String(), output) }, time.Second, time.Millisecond)771 require.Eventually(t, func() bool { return strings.Contains(out.String(), output) }, time.Second, time.Millisecond)
771 requireAckedState(t, tr.sends, 1, 2)772 requireAckedState(t, tr.sends, 1, 2)
772- _, sentResize := tr.sends.find(func(frame ports.Frame) bool { return frame.Type == ports.MsgResize })773+ _, sentResize := tr.sends.find(func(frame wire.Frame) bool { return frame.Type == wire.MsgResize })
773 require.False(t, sentResize, "local reconnect status must not request a daemon reset")774 require.False(t, sentResize, "local reconnect status must not request a daemon reset")
774 775
775 tr.recvCh <- reconnectToastRecv{frame: reconnectToastDetach(protocol.ReasonDetach)}776 tr.recvCh <- reconnectToastRecv{frame: reconnectToastDetach(protocol.ReasonDetach)}
@@ -827,7 +828,7 @@ func TestReconnectOverlayRedrawFailureDoesNotObserveOrAckOutput(t *testing.T) {
827 tr.events <- ports.LinkEvent{State: ports.LinkStateProbing}828 tr.events <- ports.LinkEvent{State: ports.LinkStateProbing}
828 requireReconnectToastOutput(t, out.completed)829 requireReconnectToastOutput(t, out.completed)
829 arm()830 arm()
830- tr.recvCh <- reconnectToastRecv{frame: ports.Frame{Type: ports.MsgOutput, Payload: mustMarshalOutput(protocol.Output{831+ tr.recvCh <- reconnectToastRecv{frame: wire.Frame{Type: wire.MsgOutput, Payload: mustMarshalOutput(protocol.Output{
831 Epoch: 1,832 Epoch: 1,
832 Base: 0,833 Base: 0,
833 New: 2,834 New: 2,
@@ -932,8 +933,8 @@ func newReconnectResetBlockingTransport() *reconnectResetBlockingTransport {
932 }933 }
933 }934 }
934 935
935-func (t *reconnectResetBlockingTransport) Send(frame ports.Frame) error {936+func (t *reconnectResetBlockingTransport) Send(frame wire.Frame) error {
936- if frame.Type != ports.MsgResize {937+ if frame.Type != wire.MsgResize {
937 return nil938 return nil
938 }939 }
939 t.enterOnce.Do(func() { close(t.entered) })940 t.enterOnce.Do(func() { close(t.entered) })
@@ -941,11 +942,11 @@ func (t *reconnectResetBlockingTransport) Send(frame ports.Frame) error {
941 return io.ErrClosedPipe942 return io.ErrClosedPipe
942 }943 }
943 944
944-func (t *reconnectResetBlockingTransport) Recv() (ports.Frame, error) {945+func (t *reconnectResetBlockingTransport) Recv() (wire.Frame, error) {
945 t.mu.Lock()946 t.mu.Lock()
946 defer t.mu.Unlock()947 defer t.mu.Unlock()
947 if len(t.recvs) == 0 {948 if len(t.recvs) == 0 {
948- return ports.Frame{}, io.EOF949+ return wire.Frame{}, io.EOF
949 }950 }
950 recv := t.recvs[0]951 recv := t.recvs[0]
951 t.recvs = t.recvs[1:]952 t.recvs = t.recvs[1:]
@@ -1014,7 +1015,7 @@ func TestPreSenderReconnectResetIsBounded(t *testing.T) {
1014 type reconnectToastBlockingSendTransport struct {1015 type reconnectToastBlockingSendTransport struct {
1015 *reconnectToastLinkTransport1016 *reconnectToastLinkTransport
1016 armed atomic.Bool1017 armed atomic.Bool
1017- blockType ports.MsgType1018+ blockType wire.MsgType
1018 sendErr error1019 sendErr error
1019 entered chan struct{}1020 entered chan struct{}
1020 release chan struct{}1021 release chan struct{}
@@ -1025,13 +1026,13 @@ type reconnectToastBlockingSendTransport struct {
1025 func newReconnectToastBlockingSendTransport() *reconnectToastBlockingSendTransport {1026 func newReconnectToastBlockingSendTransport() *reconnectToastBlockingSendTransport {
1026 return &reconnectToastBlockingSendTransport{1027 return &reconnectToastBlockingSendTransport{
1027 reconnectToastLinkTransport: newReconnectToastLinkTransport(),1028 reconnectToastLinkTransport: newReconnectToastLinkTransport(),
1028- blockType: ports.MsgClientNotice,1029+ blockType: wire.MsgClientNotice,
1029 entered: make(chan struct{}),1030 entered: make(chan struct{}),
1030 release: make(chan struct{}),1031 release: make(chan struct{}),
1031 }1032 }
1032 }1033 }
1033 1034
1034-func (t *reconnectToastBlockingSendTransport) Send(frame ports.Frame) error {1035+func (t *reconnectToastBlockingSendTransport) Send(frame wire.Frame) error {
1035 if err := t.reconnectToastLinkTransport.Send(frame); err != nil {1036 if err := t.reconnectToastLinkTransport.Send(frame); err != nil {
1036 return err1037 return err
1037 }1038 }
@@ -1083,7 +1084,7 @@ func TestReconnectResetEnqueueCancellationExitsCleanly(t *testing.T) {
1083 1084
1084 tr.events <- ports.LinkEvent{State: ports.LinkStateProbing}1085 tr.events <- ports.LinkEvent{State: ports.LinkStateProbing}
1085 requireTerminalSizeCalls(t, term, 2)1086 requireTerminalSizeCalls(t, term, 2)
1086- tr.blockType = ports.MsgResize1087+ tr.blockType = wire.MsgResize
1087 tr.armed.Store(true)1088 tr.armed.Store(true)
1088 term.resizeCh <- domain.Geometry{Size: term.size}1089 term.resizeCh <- domain.Geometry{Size: term.size}
1089 select {1090 select {
@@ -1123,7 +1124,7 @@ func TestReconnectResetCancellationPreservesQueuedSenderError(t *testing.T) {
1123 1124
1124 tr.events <- ports.LinkEvent{State: ports.LinkStateProbing}1125 tr.events <- ports.LinkEvent{State: ports.LinkStateProbing}
1125 requireTerminalSizeCalls(t, term, 2)1126 requireTerminalSizeCalls(t, term, 2)
1126- tr.blockType = ports.MsgResize1127+ tr.blockType = wire.MsgResize
1127 tr.armed.Store(true)1128 tr.armed.Store(true)
1128 term.resizeCh <- domain.Geometry{Size: term.size}1129 term.resizeCh <- domain.Geometry{Size: term.size}
1129 select {1130 select {
M internal/usecase/client/same_peer_switch_test.go +5 -5
@@ -8,8 +8,8 @@ import (
8 "github.com/stretchr/testify/require"8 "github.com/stretchr/testify/require"
9 9
10 "github.com/bnema/vev/internal/domain"10 "github.com/bnema/vev/internal/domain"
11- "github.com/bnema/vev/internal/ports"
12 "github.com/bnema/vev/internal/protocol"11 "github.com/bnema/vev/internal/protocol"
12+ "github.com/bnema/vev/internal/protocol/wire"
13 )13 )
14 14
15 func TestCloseAndDialAttachTargetSkipsSamePeerSwitch(t *testing.T) {15 func TestCloseAndDialAttachTargetSkipsSamePeerSwitch(t *testing.T) {
@@ -39,8 +39,8 @@ func TestCloseAndDialAttachTargetSkipsSamePeerSwitch(t *testing.T) {
39 Session: "stopped", Intent: protocol.IntentAttach, ExactTarget: &exact,39 Session: "stopped", Intent: protocol.IntentAttach, ExactTarget: &exact,
40 EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned,40 EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned,
41 }41 }
42- transport.recvCh <- reconnectToastRecv{frame: ports.Frame{42+ transport.recvCh <- reconnectToastRecv{frame: wire.Frame{
43- Type: ports.MsgAttachTarget, Payload: ports.MarshalAttachTarget(target),43+ Type: wire.MsgAttachTarget, Payload: wire.MarshalAttachTarget(target),
44 }}44 }}
45 45
46 select {46 select {
@@ -50,8 +50,8 @@ func TestCloseAndDialAttachTargetSkipsSamePeerSwitch(t *testing.T) {
50 case <-time.After(time.Second):50 case <-time.After(time.Second):
51 t.Fatal("close-and-dial target did not return a handoff")51 t.Fatal("close-and-dial target did not return a handoff")
52 }52 }
53- _, sentSamePeer := transport.sends.find(func(frame ports.Frame) bool {53+ _, sentSamePeer := transport.sends.find(func(frame wire.Frame) bool {
54- return frame.Type == ports.MsgSamePeerSwitchRequest54+ return frame.Type == wire.MsgSamePeerSwitchRequest
55 })55 })
56 require.False(t, sentSamePeer)56 require.False(t, sentSamePeer)
57 }57 }
M internal/usecase/client/terminal_input_pump_test.go +23 -22
@@ -13,6 +13,7 @@ import (
13 13
14 "github.com/bnema/vev/internal/domain"14 "github.com/bnema/vev/internal/domain"
15 "github.com/bnema/vev/internal/ports"15 "github.com/bnema/vev/internal/ports"
16+ "github.com/bnema/vev/internal/protocol/wire"
16 )17 )
17 18
18 type lifecycleReader struct {19 type lifecycleReader struct {
@@ -81,14 +82,14 @@ func requireSignal(t *testing.T, ch <-chan struct{}, message string) {
81 }82 }
82 }83 }
83 84
84-func requireFrame(t *testing.T, ch <-chan ports.Frame, message string) ports.Frame {85+func requireFrame(t *testing.T, ch <-chan wire.Frame, message string) wire.Frame {
85 t.Helper()86 t.Helper()
86 select {87 select {
87 case frame := <-ch:88 case frame := <-ch:
88 return frame89 return frame
89 case <-time.After(time.Second):90 case <-time.After(time.Second):
90 t.Fatal(message)91 t.Fatal(message)
91- return ports.Frame{}92+ return wire.Frame{}
92 }93 }
93 }94 }
94 95
@@ -181,7 +182,7 @@ func TestTerminalInputPumpCancellationHandoffPreservesQueuedBytes(t *testing.T)
181 go func() {182 go func() {
182 (&stdinPump{183 (&stdinPump{
183 ctx: cancelledCtx, cancel: cancelCancelled, input: input,184 ctx: cancelledCtx, cancel: cancelCancelled, input: input,
184- out: make(chan ports.Frame, 1), clock: newAttachPaletteClock(),185+ out: make(chan wire.Frame, 1), clock: newAttachPaletteClock(),
185 logger: slog.New(slog.DiscardHandler), paletteEvents: make(chan paletteGenerationEvent),186 logger: slog.New(slog.DiscardHandler), paletteEvents: make(chan paletteGenerationEvent),
186 activeGeneration: &activeGeneration,187 activeGeneration: &activeGeneration,
187 }).run()188 }).run()
@@ -191,7 +192,7 @@ func TestTerminalInputPumpCancellationHandoffPreservesQueuedBytes(t *testing.T)
191 192
192 replacementCtx, cancelReplacement := context.WithCancel(context.Background())193 replacementCtx, cancelReplacement := context.WithCancel(context.Background())
193 defer cancelReplacement()194 defer cancelReplacement()
194- out := make(chan ports.Frame, len(want))195+ out := make(chan wire.Frame, len(want))
195 replacementDone := make(chan struct{})196 replacementDone := make(chan struct{})
196 go func() {197 go func() {
197 (&stdinPump{198 (&stdinPump{
@@ -206,7 +207,7 @@ func TestTerminalInputPumpCancellationHandoffPreservesQueuedBytes(t *testing.T)
206 var got []byte207 var got []byte
207 for len(got) < len(want) {208 for len(got) < len(want) {
208 frame := <-out209 frame := <-out
209- input, err := ports.UnmarshalInput(frame.Payload)210+ input, err := wire.UnmarshalInput(frame.Payload)
210 require.NoError(t, err)211 require.NoError(t, err)
211 got = append(got, input.Data...)212 got = append(got, input.Data...)
212 }213 }
@@ -229,7 +230,7 @@ func TestTerminalInputPumpCancellationAfterTakeRequeuesRawRead(t *testing.T) {
229 go func() {230 go func() {
230 (&stdinPump{231 (&stdinPump{
231 ctx: ctx, cancel: cancel, input: input, consumer: consumer,232 ctx: ctx, cancel: cancel, input: input, consumer: consumer,
232- out: make(chan ports.Frame, 1), clock: newAttachPaletteClock(),233+ out: make(chan wire.Frame, 1), clock: newAttachPaletteClock(),
233 logger: slog.New(slog.DiscardHandler), paletteEvents: make(chan paletteGenerationEvent),234 logger: slog.New(slog.DiscardHandler), paletteEvents: make(chan paletteGenerationEvent),
234 afterInputTake: func() {235 afterInputTake: func() {
235 close(taken)236 close(taken)
@@ -263,7 +264,7 @@ func TestTerminalInputPumpCancellationPreservesStandaloneEscapeForReplacement(t
263 oldDone := make(chan struct{})264 oldDone := make(chan struct{})
264 go func() {265 go func() {
265 (&stdinPump{266 (&stdinPump{
266- ctx: oldCtx, cancel: cancelOld, input: input, out: make(chan ports.Frame, 1),267+ ctx: oldCtx, cancel: cancelOld, input: input, out: make(chan wire.Frame, 1),
267 clock: oldClock, logger: slog.New(slog.DiscardHandler), paletteEvents: make(chan paletteGenerationEvent),268 clock: oldClock, logger: slog.New(slog.DiscardHandler), paletteEvents: make(chan paletteGenerationEvent),
268 activeGeneration: &activeGeneration,269 activeGeneration: &activeGeneration,
269 }).run()270 }).run()
@@ -278,7 +279,7 @@ func TestTerminalInputPumpCancellationPreservesStandaloneEscapeForReplacement(t
278 newCtx, cancelNew := context.WithCancel(context.Background())279 newCtx, cancelNew := context.WithCancel(context.Background())
279 defer cancelNew()280 defer cancelNew()
280 newClock := newAttachPaletteClock()281 newClock := newAttachPaletteClock()
281- out := make(chan ports.Frame, 1)282+ out := make(chan wire.Frame, 1)
282 newDone := make(chan struct{})283 newDone := make(chan struct{})
283 go func() {284 go func() {
284 (&stdinPump{285 (&stdinPump{
@@ -291,7 +292,7 @@ func TestTerminalInputPumpCancellationPreservesStandaloneEscapeForReplacement(t
291 (<-newClock.timers).fire() // marker ambiguity deadline292 (<-newClock.timers).fire() // marker ambiguity deadline
292 (<-newClock.timers).fire() // paste coalescer's lone-ESC deadline293 (<-newClock.timers).fire() // paste coalescer's lone-ESC deadline
293 frame := <-out294 frame := <-out
294- got, err := ports.UnmarshalInput(frame.Payload)295+ got, err := wire.UnmarshalInput(frame.Payload)
295 require.NoError(t, err)296 require.NoError(t, err)
296 require.Equal(t, []byte("\x1b"), got.Data)297 require.Equal(t, []byte("\x1b"), got.Data)
297 298
@@ -306,7 +307,7 @@ func TestTerminalInputPumpCancellationAfterInputBeforeOSCPreservesOnlyUndelivere
306 ctx, cancel := context.WithCancel(context.Background())307 ctx, cancel := context.WithCancel(context.Background())
307 delivered := make(chan struct{})308 delivered := make(chan struct{})
308 done := make(chan struct{})309 done := make(chan struct{})
309- out := make(chan ports.Frame, 1)310+ out := make(chan wire.Frame, 1)
310 go func() {311 go func() {
311 (&stdinPump{312 (&stdinPump{
312 ctx: ctx, cancel: cancel, input: input, out: out, clock: newAttachPaletteClock(),313 ctx: ctx, cancel: cancel, input: input, out: out, clock: newAttachPaletteClock(),
@@ -320,14 +321,14 @@ func TestTerminalInputPumpCancellationAfterInputBeforeOSCPreservesOnlyUndelivere
320 close(done)321 close(done)
321 }()322 }()
322 frame := requireFrame(t, out, "timed out waiting for delivered input prefix")323 frame := requireFrame(t, out, "timed out waiting for delivered input prefix")
323- got, err := ports.UnmarshalInput(frame.Payload)324+ got, err := wire.UnmarshalInput(frame.Payload)
324 require.NoError(t, err)325 require.NoError(t, err)
325 require.Equal(t, []byte("x"), got.Data)326 require.Equal(t, []byte("x"), got.Data)
326 requireSignal(t, delivered, "timed out waiting for cancellation after input delivery")327 requireSignal(t, delivered, "timed out waiting for cancellation after input delivery")
327 requireSignal(t, done, "timed out waiting for cancelled stdin pump")328 requireSignal(t, done, "timed out waiting for cancelled stdin pump")
328 329
329 replacementCtx, cancelReplacement := context.WithCancel(context.Background())330 replacementCtx, cancelReplacement := context.WithCancel(context.Background())
330- replacementOut := make(chan ports.Frame, 1)331+ replacementOut := make(chan wire.Frame, 1)
331 replacementDone := make(chan struct{})332 replacementDone := make(chan struct{})
332 go func() {333 go func() {
333 (&stdinPump{334 (&stdinPump{
@@ -339,7 +340,7 @@ func TestTerminalInputPumpCancellationAfterInputBeforeOSCPreservesOnlyUndelivere
339 close(replacementDone)340 close(replacementDone)
340 }()341 }()
341 frame = requireFrame(t, replacementOut, "timed out waiting for undelivered input suffix")342 frame = requireFrame(t, replacementOut, "timed out waiting for undelivered input suffix")
342- got, err = ports.UnmarshalInput(frame.Payload)343+ got, err = wire.UnmarshalInput(frame.Payload)
343 require.NoError(t, err)344 require.NoError(t, err)
344 require.Equal(t, []byte("y"), got.Data, "replacement must receive only the undelivered ordinary suffix")345 require.Equal(t, []byte("y"), got.Data, "replacement must receive only the undelivered ordinary suffix")
345 346
@@ -354,7 +355,7 @@ func TestTerminalInputPumpCancellationPreservesCoalescerHeldSuffix(t *testing.T)
354 input.enqueue(terminalReadResult{data: raw}, 1)355 input.enqueue(terminalReadResult{data: raw}, 1)
355 var activeGeneration atomic.Uint64356 var activeGeneration atomic.Uint64
356 ctx, cancel := context.WithCancel(context.Background())357 ctx, cancel := context.WithCancel(context.Background())
357- out := make(chan ports.Frame, 1)358+ out := make(chan wire.Frame, 1)
358 done := make(chan struct{})359 done := make(chan struct{})
359 go func() {360 go func() {
360 (&stdinPump{361 (&stdinPump{
@@ -366,14 +367,14 @@ func TestTerminalInputPumpCancellationPreservesCoalescerHeldSuffix(t *testing.T)
366 close(done)367 close(done)
367 }()368 }()
368 frame := requireFrame(t, out, "timed out waiting for delivered input prefix")369 frame := requireFrame(t, out, "timed out waiting for delivered input prefix")
369- got, err := ports.UnmarshalInput(frame.Payload)370+ got, err := wire.UnmarshalInput(frame.Payload)
370 require.NoError(t, err)371 require.NoError(t, err)
371 require.Equal(t, []byte("x"), got.Data)372 require.Equal(t, []byte("x"), got.Data)
372 requireSignal(t, done, "timed out waiting for cancelled stdin pump")373 requireSignal(t, done, "timed out waiting for cancelled stdin pump")
373 374
374 replacementCtx, cancelReplacement := context.WithCancel(context.Background())375 replacementCtx, cancelReplacement := context.WithCancel(context.Background())
375 replacementClock := newAttachPaletteClock()376 replacementClock := newAttachPaletteClock()
376- replacementOut := make(chan ports.Frame, 1)377+ replacementOut := make(chan wire.Frame, 1)
377 replacementDone := make(chan struct{})378 replacementDone := make(chan struct{})
378 go func() {379 go func() {
379 (&stdinPump{380 (&stdinPump{
@@ -386,7 +387,7 @@ func TestTerminalInputPumpCancellationPreservesCoalescerHeldSuffix(t *testing.T)
386 }()387 }()
387 requireTimer(t, replacementClock.timers, "timed out waiting for paste-prefix ambiguity timer").fire()388 requireTimer(t, replacementClock.timers, "timed out waiting for paste-prefix ambiguity timer").fire()
388 frame = requireFrame(t, replacementOut, "timed out waiting for coalescer-held suffix")389 frame = requireFrame(t, replacementOut, "timed out waiting for coalescer-held suffix")
389- got, err = ports.UnmarshalInput(frame.Payload)390+ got, err = wire.UnmarshalInput(frame.Payload)
390 require.NoError(t, err)391 require.NoError(t, err)
391 require.Equal(t, held, got.Data, "replacement must receive the coalescer-held suffix exactly once")392 require.Equal(t, held, got.Data, "replacement must receive the coalescer-held suffix exactly once")
392 393
@@ -424,7 +425,7 @@ func TestTerminalInputPumpCancellationAfterDeliveredCallbackDoesNotReplay(t *tes
424 ctx, cancel := context.WithCancel(context.Background())425 ctx, cancel := context.WithCancel(context.Background())
425 delivered := make(chan struct{})426 delivered := make(chan struct{})
426 done := make(chan struct{})427 done := make(chan struct{})
427- out := make(chan ports.Frame, 1)428+ out := make(chan wire.Frame, 1)
428 go func() {429 go func() {
429 (&stdinPump{430 (&stdinPump{
430 ctx: ctx, cancel: cancel, input: input, out: out, clock: newAttachPaletteClock(),431 ctx: ctx, cancel: cancel, input: input, out: out, clock: newAttachPaletteClock(),
@@ -438,7 +439,7 @@ func TestTerminalInputPumpCancellationAfterDeliveredCallbackDoesNotReplay(t *tes
438 close(done)439 close(done)
439 }()440 }()
440 frame := <-out441 frame := <-out
441- got, err := ports.UnmarshalInput(frame.Payload)442+ got, err := wire.UnmarshalInput(frame.Payload)
442 require.NoError(t, err)443 require.NoError(t, err)
443 require.Equal(t, []byte("x"), got.Data)444 require.Equal(t, []byte("x"), got.Data)
444 <-delivered445 <-delivered
@@ -463,7 +464,7 @@ func TestTerminalInputPumpReusesOneReaderAcrossCancelledAttempts(t *testing.T) {
463 go func() {464 go func() {
464 (&stdinPump{465 (&stdinPump{
465 ctx: firstCtx, cancel: cancelFirst, input: input,466 ctx: firstCtx, cancel: cancelFirst, input: input,
466- out: make(chan ports.Frame), clock: newAttachPaletteClock(),467+ out: make(chan wire.Frame), clock: newAttachPaletteClock(),
467 logger: slog.New(slog.DiscardHandler), paletteEvents: make(chan paletteGenerationEvent),468 logger: slog.New(slog.DiscardHandler), paletteEvents: make(chan paletteGenerationEvent),
468 activeGeneration: &activeGeneration,469 activeGeneration: &activeGeneration,
469 }).run()470 }).run()
@@ -474,7 +475,7 @@ func TestTerminalInputPumpReusesOneReaderAcrossCancelledAttempts(t *testing.T) {
474 475
475 secondCtx, cancelSecond := context.WithCancel(context.Background())476 secondCtx, cancelSecond := context.WithCancel(context.Background())
476 defer cancelSecond()477 defer cancelSecond()
477- out := make(chan ports.Frame, 1)478+ out := make(chan wire.Frame, 1)
478 secondDone := make(chan struct{})479 secondDone := make(chan struct{})
479 go func() {480 go func() {
480 (&stdinPump{481 (&stdinPump{
@@ -488,7 +489,7 @@ func TestTerminalInputPumpReusesOneReaderAcrossCancelledAttempts(t *testing.T) {
488 489
489 reader.chunks <- []byte("x")490 reader.chunks <- []byte("x")
490 frame := <-out491 frame := <-out
491- got, err := ports.UnmarshalInput(frame.Payload)492+ got, err := wire.UnmarshalInput(frame.Payload)
492 require.NoError(t, err)493 require.NoError(t, err)
493 require.Equal(t, []byte("x"), got.Data)494 require.Equal(t, []byte("x"), got.Data)
494 require.Equal(t, 1, reader.maxActive(), "cancelled attempts must not leave competing reads")495 require.Equal(t, 1, reader.maxActive(), "cancelled attempts must not leave competing reads")
M internal/usecase/client/transport_observability_test.go +12 -11
@@ -15,6 +15,7 @@ import (
15 "github.com/bnema/vev/internal/ports"15 "github.com/bnema/vev/internal/ports"
16 portsmocks "github.com/bnema/vev/internal/ports/mocks"16 portsmocks "github.com/bnema/vev/internal/ports/mocks"
17 "github.com/bnema/vev/internal/protocol"17 "github.com/bnema/vev/internal/protocol"
18+ "github.com/bnema/vev/internal/protocol/wire"
18 "github.com/bnema/vev/internal/usecase/client"19 "github.com/bnema/vev/internal/usecase/client"
19 )20 )
20 21
@@ -46,12 +47,12 @@ func TestBlockingRuntimeObserverDoesNotDelayTerminalFlushOrACK(t *testing.T) {
46 defer reporter.Close()47 defer reporter.Close()
47 acked := make(chan struct{})48 acked := make(chan struct{})
48 transport := portsmocks.NewMockTransport(t)49 transport := portsmocks.NewMockTransport(t)
49- transport.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once()50+ transport.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once()
50- transport.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()51+ transport.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
51- transport.EXPECT().Send(isType(ports.MsgAck)).Run(func(ports.Frame) { close(acked) }).Return(nil).Once()52+ transport.EXPECT().Send(isType(wire.MsgAck)).Run(func(wire.Frame) { close(acked) }).Return(nil).Once()
52 unblock := scriptRecv(transport,53 unblock := scriptRecv(transport,
53- recvItem{f: frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},54+ recvItem{f: frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},
54- recvItem{f: frameOf(ports.MsgOutput, mustMarshalOutput(protocol.Output{Epoch: 1, Size: domain.Size{Cols: 1, Rows: 1}, Full: true, Data: []byte("flush-before-observe"), New: 3}))},55+ recvItem{f: frameOf(wire.MsgOutput, mustMarshalOutput(protocol.Output{Epoch: 1, Size: domain.Size{Cols: 1, Rows: 1}, Full: true, Data: []byte("flush-before-observe"), New: 3}))},
55 )56 )
56 defer unblock()57 defer unblock()
57 transport.EXPECT().Close().Return(nil).Once()58 transport.EXPECT().Close().Return(nil).Once()
@@ -87,15 +88,15 @@ func TestTerminalFlushBoundaryTransportObservability(t *testing.T) {
87 reporter := ports.NewSerializedRuntimeObserver(observer, 64)88 reporter := ports.NewSerializedRuntimeObserver(observer, 64)
88 defer reporter.Close()89 defer reporter.Close()
89 transport := portsmocks.NewMockTransport(t)90 transport := portsmocks.NewMockTransport(t)
90- transport.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once()91+ transport.EXPECT().Send(isType(wire.MsgHello)).Return(nil).Once()
91- transport.EXPECT().Send(isType(ports.MsgTheme)).Return(nil).Maybe()92+ transport.EXPECT().Send(isType(wire.MsgTheme)).Return(nil).Maybe()
92 unblock := scriptRecv(transport,93 unblock := scriptRecv(transport,
93- recvItem{f: frameOf(ports.MsgWelcome, ports.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},94+ recvItem{f: frameOf(wire.MsgWelcome, wire.MarshalWelcome(protocol.Welcome{SessionID: "s1"}))},
94- recvItem{f: frameOf(ports.MsgOutput, mustMarshalOutput(protocol.Output{Epoch: 1, Size: domain.Size{Cols: 1, Rows: 1}, Full: true, Data: []byte("unchanged-by-observer"), New: 3}))},95+ recvItem{f: frameOf(wire.MsgOutput, mustMarshalOutput(protocol.Output{Epoch: 1, Size: domain.Size{Cols: 1, Rows: 1}, Full: true, Data: []byte("unchanged-by-observer"), New: 3}))},
95- recvItem{f: frameOf(ports.MsgDetached, ports.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},96+ recvItem{f: frameOf(wire.MsgDetached, wire.MarshalDetached(protocol.Detached{Reason: protocol.ReasonDetach}))},
96 )97 )
97 defer unblock()98 defer unblock()
98- transport.EXPECT().Send(isType(ports.MsgAck)).Return(nil).Maybe()99+ transport.EXPECT().Send(isType(wire.MsgAck)).Return(nil).Maybe()
99 transport.EXPECT().Close().Return(nil).Once()100 transport.EXPECT().Close().Return(nil).Once()
100 101
101 err := runTestClient(context.Background(), testDependencies(transportDialer{transport: transport}, term, realClock{}, nil, reporter), client.AttachRequest{Intent: protocol.IntentEphemeral})102 err := runTestClient(context.Background(), testDependencies(transportDialer{transport: transport}, term, realClock{}, nil, reporter), client.AttachRequest{Intent: protocol.IntentEphemeral})
M internal/usecase/client/wire_external_test_helpers_test.go +2 -2
@@ -1,12 +1,12 @@
1 package client_test1 package client_test
2 2
3 import (3 import (
4- "github.com/bnema/vev/internal/ports"
5 "github.com/bnema/vev/internal/protocol"4 "github.com/bnema/vev/internal/protocol"
5+ "github.com/bnema/vev/internal/protocol/wire"
6 )6 )
7 7
8 func mustMarshalOutput(m protocol.Output) []byte {8 func mustMarshalOutput(m protocol.Output) []byte {
9- payload, err := ports.MarshalOutput(m)9+ payload, err := wire.MarshalOutput(m)
10 if err != nil {10 if err != nil {
11 panic(err)11 panic(err)
12 }12 }
M internal/usecase/client/wire_test_helpers_test.go +2 -2
@@ -1,12 +1,12 @@
1 package client1 package client
2 2
3 import (3 import (
4- "github.com/bnema/vev/internal/ports"
5 "github.com/bnema/vev/internal/protocol"4 "github.com/bnema/vev/internal/protocol"
5+ "github.com/bnema/vev/internal/protocol/wire"
6 )6 )
7 7
8 func mustMarshalOutput(m protocol.Output) []byte {8 func mustMarshalOutput(m protocol.Output) []byte {
9- payload, err := ports.MarshalOutput(m)9+ payload, err := wire.MarshalOutput(m)
10 if err != nil {10 if err != nil {
11 panic(err)11 panic(err)
12 }12 }
M internal/usecase/daemon/attachment_effect_gate.go +2 -1
@@ -6,6 +6,7 @@ import (
6 "sync/atomic"6 "sync/atomic"
7 7
8 "github.com/bnema/vev/internal/ports"8 "github.com/bnema/vev/internal/ports"
9+ "github.com/bnema/vev/internal/protocol/wire"
9 )10 )
10 11
11 type attachmentEffectPhase uint812 type attachmentEffectPhase uint8
@@ -188,7 +189,7 @@ func (t *attachmentEffect) endTransportSend() {
188 g.mu.Unlock()189 g.mu.Unlock()
189 }190 }
190 191
191-func (t *attachmentEffect) sendControl(frame ports.Frame) error {192+func (t *attachmentEffect) sendControl(frame wire.Frame) error {
192 if !t.current() || t.ac == nil || t.transport.transport == nil {193 if !t.current() || t.ac == nil || t.transport.transport == nil {
193 return errAttachmentTransition194 return errAttachmentTransition
194 }195 }
M internal/usecase/daemon/attachment_effect_gate_test.go +17 -17
@@ -11,8 +11,8 @@ import (
11 11
12 renderer "github.com/bnema/vev-vt"12 renderer "github.com/bnema/vev-vt"
13 "github.com/bnema/vev/internal/domain"13 "github.com/bnema/vev/internal/domain"
14- "github.com/bnema/vev/internal/ports"
15 "github.com/bnema/vev/internal/protocol"14 "github.com/bnema/vev/internal/protocol"
15+ "github.com/bnema/vev/internal/protocol/wire"
16 "github.com/bnema/vev/internal/usecase/keys"16 "github.com/bnema/vev/internal/usecase/keys"
17 )17 )
18 18
@@ -44,14 +44,14 @@ func newBlockedRenderReplacementTransport(d *Daemon, sess *session) *blockedRend
44 }44 }
45 }45 }
46 46
47-func (t *blockedRenderReplacementTransport) Send(ports.Frame) error {47+func (t *blockedRenderReplacementTransport) Send(wire.Frame) error {
48 t.sendOnce.Do(func() { close(t.sendEntered) })48 t.sendOnce.Do(func() { close(t.sendEntered) })
49 <-t.closed49 <-t.closed
50 return errors.New("transport closed")50 return errors.New("transport closed")
51 }51 }
52 52
53-func (*blockedRenderReplacementTransport) Recv() (ports.Frame, error) {53+func (*blockedRenderReplacementTransport) Recv() (wire.Frame, error) {
54- return ports.Frame{}, io.EOF54+ return wire.Frame{}, io.EOF
55 }55 }
56 56
57 func (t *blockedRenderReplacementTransport) Close() error {57 func (t *blockedRenderReplacementTransport) Close() error {
@@ -97,13 +97,13 @@ func newTeardownBlockingTransport() *teardownBlockingTransport {
97 return &teardownBlockingTransport{sendEntered: make(chan struct{}), closed: make(chan struct{})}97 return &teardownBlockingTransport{sendEntered: make(chan struct{}), closed: make(chan struct{})}
98 }98 }
99 99
100-func (t *teardownBlockingTransport) Send(ports.Frame) error {100+func (t *teardownBlockingTransport) Send(wire.Frame) error {
101 t.sendOnce.Do(func() { close(t.sendEntered) })101 t.sendOnce.Do(func() { close(t.sendEntered) })
102 <-t.closed102 <-t.closed
103 return errors.New("transport closed")103 return errors.New("transport closed")
104 }104 }
105 105
106-func (*teardownBlockingTransport) Recv() (ports.Frame, error) { return ports.Frame{}, io.EOF }106+func (*teardownBlockingTransport) Recv() (wire.Frame, error) { return wire.Frame{}, io.EOF }
107 107
108 func (t *teardownBlockingTransport) Close() error {108 func (t *teardownBlockingTransport) Close() error {
109 t.closeOnce.Do(func() { close(t.closed) })109 t.closeOnce.Do(func() { close(t.closed) })
@@ -568,7 +568,7 @@ func TestAttachmentEffectGateAdmittedActiveEffectsFinishBeforeReplacement(t *tes
568 tests := []struct {568 tests := []struct {
569 name string569 name string
570 setup func(*Daemon, *session, *attachedClient, *transactionalResizePTY)570 setup func(*Daemon, *session, *attachedClient, *transactionalResizePTY)
571- frame ports.Frame571+ frame wire.Frame
572 assert func(*testing.T, *attachedClient, *transactionalResizePTY)572 assert func(*testing.T, *attachedClient, *transactionalResizePTY)
573 }{573 }{
574 {574 {
@@ -612,7 +612,7 @@ func TestAttachmentEffectGateAdmittedActiveEffectsFinishBeforeReplacement(t *tes
612 },612 },
613 {613 {
614 name: "theme",614 name: "theme",
615- frame: ports.Frame{Type: ports.MsgTheme, Payload: ports.MarshalTheme(protocol.Theme{615+ frame: wire.Frame{Type: wire.MsgTheme, Payload: wire.MarshalTheme(protocol.Theme{
616 HasForeground: true, Foreground: renderer.RGB{R: 1, G: 2, B: 3},616 HasForeground: true, Foreground: renderer.RGB{R: 1, G: 2, B: 3},
617 HasBackground: true, Background: renderer.RGB{R: 4, G: 5, B: 6},617 HasBackground: true, Background: renderer.RGB{R: 4, G: 5, B: 6},
618 })},618 })},
@@ -625,7 +625,7 @@ func TestAttachmentEffectGateAdmittedActiveEffectsFinishBeforeReplacement(t *tes
625 setup: func(_ *Daemon, _ *session, ac *attachedClient, _ *transactionalResizePTY) {625 setup: func(_ *Daemon, _ *session, ac *attachedClient, _ *transactionalResizePTY) {
626 ac.output.next = 3626 ac.output.next = 3
627 },627 },
628- frame: ports.Frame{Type: ports.MsgAck, Payload: mustMarshalAck(protocol.Ack{Epoch: 1, State: 1})},628+ frame: wire.Frame{Type: wire.MsgAck, Payload: mustMarshalAck(protocol.Ack{Epoch: 1, State: 1})},
629 assert: func(t *testing.T, ac *attachedClient, _ *transactionalResizePTY) {629 assert: func(t *testing.T, ac *attachedClient, _ *transactionalResizePTY) {
630 ac.sendMu.Lock()630 ac.sendMu.Lock()
631 defer ac.sendMu.Unlock()631 defer ac.sendMu.Unlock()
@@ -697,10 +697,10 @@ func TestAttachedRouteSnapshotIsAcceptedAsAnAttachmentValue(t *testing.T) {
697 Home: activeRef,697 Home: activeRef,
698 Entries: []protocol.RecentRouteEntry{testRouteEntry(7, 3, "previous", 7, protocol.RouteKindLocal)},698 Entries: []protocol.RecentRouteEntry{testRouteEntry(7, 3, "previous", 7, protocol.RouteKindLocal)},
699 }699 }
700- payload, err := ports.MarshalRecentRouteSnapshot(snapshot)700+ payload, err := wire.MarshalRecentRouteSnapshot(snapshot)
701 require.NoError(t, err)701 require.NoError(t, err)
702 702
703- require.False(t, d.handleAttachmentClientFrame(token, ports.Frame{Type: ports.MsgRecentRouteSnapshot, Payload: payload}))703+ require.False(t, d.handleAttachmentClientFrame(token, wire.Frame{Type: wire.MsgRecentRouteSnapshot, Payload: payload}))
704 require.Equal(t, snapshot, ac.routeSnapshotCopy())704 require.Equal(t, snapshot, ac.routeSnapshotCopy())
705 }705 }
706 706
@@ -726,31 +726,31 @@ func TestAttachedNavigationCommandSendsResultAfterLocalTransition(t *testing.T)
726 token := source.captureAttachmentCapability(ac, transport)726 token := source.captureAttachmentCapability(ac, transport)
727 token.lease = rc.attachmentLease(ac)727 token.lease = rc.attachmentLease(ac)
728 ac.installTestAttachmentCapability(token)728 ac.installTestAttachmentCapability(token)
729- payload, err := ports.MarshalCommandRequest(protocol.CommandRequest{729+ payload, err := wire.MarshalCommandRequest(protocol.CommandRequest{
730 Version: protocol.Version, RequestID: 1, Slug: "back-session", Attached: true,730 Version: protocol.Version, RequestID: 1, Slug: "back-session", Attached: true,
731 })731 })
732 require.NoError(t, err)732 require.NoError(t, err)
733 733
734- require.False(t, d.handleAttachmentClientFrame(token, ports.Frame{Type: ports.MsgCommand, Payload: payload}))734+ require.False(t, d.handleAttachmentClientFrame(token, wire.Frame{Type: wire.MsgCommand, Payload: payload}))
735 require.Same(t, source, ac.currentAttachmentSession())735 require.Same(t, source, ac.currentAttachmentSession())
736 frames := transport.Sends()736 frames := transport.Sends()
737 require.NotEmpty(t, frames)737 require.NotEmpty(t, frames)
738 var result protocol.CommandResult738 var result protocol.CommandResult
739 for _, frame := range frames {739 for _, frame := range frames {
740- if frame.Type != ports.MsgCommandResult {740+ if frame.Type != wire.MsgCommandResult {
741 continue741 continue
742 }742 }
743- result, err = ports.UnmarshalCommandResult(frame.Payload)743+ result, err = wire.UnmarshalCommandResult(frame.Payload)
744 require.NoError(t, err)744 require.NoError(t, err)
745 break745 break
746 }746 }
747 require.True(t, result.OK, result.Text)747 require.True(t, result.OK, result.Text)
748 var action protocol.RouteNavigationAction748 var action protocol.RouteNavigationAction
749 for _, frame := range frames {749 for _, frame := range frames {
750- if frame.Type != ports.MsgNavigateRecentRoute {750+ if frame.Type != wire.MsgNavigateRecentRoute {
751 continue751 continue
752 }752 }
753- action, err = ports.UnmarshalRouteNavigationAction(frame.Payload)753+ action, err = wire.UnmarshalRouteNavigationAction(frame.Payload)
754 require.NoError(t, err)754 require.NoError(t, err)
755 }755 }
756 require.Equal(t, protocol.RouteNavigationAction{SnapshotGeneration: 2, Key: 1, Generation: 1}, action)756 require.Equal(t, protocol.RouteNavigationAction{SnapshotGeneration: 2, Key: 1, Generation: 1}, action)
M internal/usecase/daemon/attachment_output.go +10 -9
@@ -10,6 +10,7 @@ import (
10 "github.com/bnema/vev/internal/domain"10 "github.com/bnema/vev/internal/domain"
11 "github.com/bnema/vev/internal/ports"11 "github.com/bnema/vev/internal/ports"
12 "github.com/bnema/vev/internal/protocol"12 "github.com/bnema/vev/internal/protocol"
13+ "github.com/bnema/vev/internal/protocol/wire"
13 )14 )
14 15
15 // attachmentOutput owns the terminal-output dependency chain and emitted16 // attachmentOutput owns the terminal-output dependency chain and emitted
@@ -153,13 +154,13 @@ func (o *attachmentOutput) prepareFrame(d *Daemon, state *capturedRenderState, f
153 return &preparedAttachmentOutput{output: o, ansi: ansi, graphics: preparedGraphics, cursor: cursor, data: data}, nil154 return &preparedAttachmentOutput{output: o, ansi: ansi, graphics: preparedGraphics, cursor: cursor, data: data}, nil
154 }155 }
155 156
156-func (p *preparedAttachmentOutput) send(echoAck uint64, send func(ports.Frame) error) error {157+func (p *preparedAttachmentOutput) send(echoAck uint64, send func(wire.Frame) error) error {
157 if p == nil || p.ansi == nil {158 if p == nil || p.ansi == nil {
158 return nil159 return nil
159 }160 }
160 sendFrame := send161 sendFrame := send
161 if p.graphics != nil {162 if p.graphics != nil {
162- sendFrame = func(frame ports.Frame) error {163+ sendFrame = func(frame wire.Frame) error {
163 p.graphics.markSendAttempted()164 p.graphics.markSendAttempted()
164 return send(frame)165 return send(frame)
165 }166 }
@@ -285,7 +286,7 @@ func (s *attachmentOutput) prepare(frame renderer.Frame, damage []renderer.Damag
285 }, nil286 }, nil
286 }287 }
287 288
288-func (p *preparedOutput) send(data []byte, echoAck uint64, send func(ports.Frame) error) error {289+func (p *preparedOutput) send(data []byte, echoAck uint64, send func(wire.Frame) error) error {
289 if p == nil || p.stream == nil || p.attempted {290 if p == nil || p.stream == nil || p.attempted {
290 return nil291 return nil
291 }292 }
@@ -379,21 +380,21 @@ func (p *preparedOutput) currentLocked() bool {
379 (p.stream.attachment == nil || p.stream.attachment.lifecycle.generationValue() == p.connectionGeneration)380 (p.stream.attachment == nil || p.stream.attachment.lifecycle.generationValue() == p.connectionGeneration)
380 }381 }
381 382
382-func marshalOutputState(data []byte, epoch, base, next, echoAck, viewRevision uint64, size domain.Size, full bool) (ports.Frame, error) {383+func marshalOutputState(data []byte, epoch, base, next, echoAck, viewRevision uint64, size domain.Size, full bool) (wire.Frame, error) {
383- payload, err := ports.MarshalOutput(protocol.Output{384+ payload, err := wire.MarshalOutput(protocol.Output{
384 Epoch: epoch, Base: base, New: next, Echo: echoAck, ViewRevision: viewRevision,385 Epoch: epoch, Base: base, New: next, Echo: echoAck, ViewRevision: viewRevision,
385 Size: size, Full: full, Data: data,386 Size: size, Full: full, Data: data,
386 })387 })
387 if err != nil {388 if err != nil {
388- return ports.Frame{}, err389+ return wire.Frame{}, err
389 }390 }
390- return ports.Frame{Type: ports.MsgOutput, Payload: payload}, nil391+ return wire.Frame{Type: wire.MsgOutput, Payload: payload}, nil
391 }392 }
392 393
393 // sideEffect builds an output frame without advancing the state stream. When394 // sideEffect builds an output frame without advancing the state stream. When
394 // an attachment is present, callers must hold its sendMu; attachment-bound395 // an attachment is present, callers must hold its sendMu; attachment-bound
395 // sends use sideEffectLocked after validating their transport incarnation.396 // sends use sideEffectLocked after validating their transport incarnation.
396-func (s *attachmentOutput) sideEffect(data []byte, echoAck uint64) (ports.Frame, error) {397+func (s *attachmentOutput) sideEffect(data []byte, echoAck uint64) (wire.Frame, error) {
397 s.lockView()398 s.lockView()
398 defer s.unlockView()399 defer s.unlockView()
399 return s.sideEffectLocked(data, echoAck)400 return s.sideEffectLocked(data, echoAck)
@@ -402,7 +403,7 @@ func (s *attachmentOutput) sideEffect(data []byte, echoAck uint64) (ports.Frame,
402 // sideEffectLocked requires the attachment view lock for both frame creation403 // sideEffectLocked requires the attachment view lock for both frame creation
403 // and transport emission. Keeping that lock across the send prevents a view404 // and transport emission. Keeping that lock across the send prevents a view
404 // publication from overtaking a frame that was fenced just before it.405 // publication from overtaking a frame that was fenced just before it.
405-func (s *attachmentOutput) sideEffectLocked(data []byte, echoAck uint64) (ports.Frame, error) {406+func (s *attachmentOutput) sideEffectLocked(data []byte, echoAck uint64) (wire.Frame, error) {
406 _, epoch, viewRevision := s.fenceLocked()407 _, epoch, viewRevision := s.fenceLocked()
407 size := domain.Size{Cols: 1, Rows: 1}408 size := domain.Size{Cols: 1, Rows: 1}
408 if s != nil && s.attachment != nil {409 if s != nil && s.attachment != nil {
M internal/usecase/daemon/attachment_output_test.go +32 -31
@@ -11,6 +11,7 @@ import (
11 "github.com/bnema/vev/internal/domain"11 "github.com/bnema/vev/internal/domain"
12 "github.com/bnema/vev/internal/ports"12 "github.com/bnema/vev/internal/ports"
13 "github.com/bnema/vev/internal/protocol"13 "github.com/bnema/vev/internal/protocol"
14+ "github.com/bnema/vev/internal/protocol/wire"
14 "github.com/bnema/vev/internal/usecase/layout"15 "github.com/bnema/vev/internal/usecase/layout"
15 "github.com/stretchr/testify/require"16 "github.com/stretchr/testify/require"
16 )17 )
@@ -22,7 +23,7 @@ func TestAttachmentOutputBuildsPipelinedDependencyChain(t *testing.T) {
22 first, ok, err := drawOutputState(t, stream, firstFrame, nil, true, 7)23 first, ok, err := drawOutputState(t, stream, firstFrame, nil, true, 7)
23 require.NoError(t, err)24 require.NoError(t, err)
24 require.True(t, ok)25 require.True(t, ok)
25- firstOut, err := ports.UnmarshalOutput(first.Payload)26+ firstOut, err := wire.UnmarshalOutput(first.Payload)
26 require.NoError(t, err)27 require.NoError(t, err)
27 require.Equal(t, uint64(0), firstOut.Base)28 require.Equal(t, uint64(0), firstOut.Base)
28 require.Equal(t, uint64(1), firstOut.New)29 require.Equal(t, uint64(1), firstOut.New)
@@ -33,7 +34,7 @@ func TestAttachmentOutputBuildsPipelinedDependencyChain(t *testing.T) {
33 second, ok, err := drawOutputState(t, stream, secondFrame, []renderer.Damage{{Kind: renderer.DamageText, Width: 1, Height: 1}}, false, 8)34 second, ok, err := drawOutputState(t, stream, secondFrame, []renderer.Damage{{Kind: renderer.DamageText, Width: 1, Height: 1}}, false, 8)
34 require.NoError(t, err)35 require.NoError(t, err)
35 require.True(t, ok)36 require.True(t, ok)
36- secondOut, err := ports.UnmarshalOutput(second.Payload)37+ secondOut, err := wire.UnmarshalOutput(second.Payload)
37 require.NoError(t, err)38 require.NoError(t, err)
38 require.Equal(t, firstOut.New, secondOut.Base)39 require.Equal(t, firstOut.New, secondOut.Base)
39 require.Equal(t, uint64(2), secondOut.New)40 require.Equal(t, uint64(2), secondOut.New)
@@ -91,7 +92,7 @@ func TestAttachmentOutputCapacityProbeDoesNotRaceWithSend(t *testing.T) {
91 release := make(chan struct{})92 release := make(chan struct{})
92 sendDone := make(chan error, 1)93 sendDone := make(chan error, 1)
93 go func() {94 go func() {
94- sendDone <- prepared.send(prepared.data, 0, func(ports.Frame) error {95+ sendDone <- prepared.send(prepared.data, 0, func(wire.Frame) error {
95 close(entered)96 close(entered)
96 <-release97 <-release
97 return nil98 return nil
@@ -132,12 +133,12 @@ func TestAttachmentOutputEpochsAreAttachmentLocalAndPreparedFramesAreFenced(t *t
132 second := newAttachment()133 second := newAttachment()
133 firstPrepared, err := first.prepare(frame, nil, true)134 firstPrepared, err := first.prepare(frame, nil, true)
134 require.NoError(t, err)135 require.NoError(t, err)
135- var firstFrame ports.Frame136+ var firstFrame wire.Frame
136- require.NoError(t, firstPrepared.send(firstPrepared.data, 0, func(frame ports.Frame) error {137+ require.NoError(t, firstPrepared.send(firstPrepared.data, 0, func(frame wire.Frame) error {
137 firstFrame = frame138 firstFrame = frame
138 return nil139 return nil
139 }))140 }))
140- firstOutput, err := ports.UnmarshalOutput(firstFrame.Payload)141+ firstOutput, err := wire.UnmarshalOutput(firstFrame.Payload)
141 require.NoError(t, err)142 require.NoError(t, err)
142 require.Equal(t, uint64(1), firstOutput.Epoch)143 require.Equal(t, uint64(1), firstOutput.Epoch)
143 require.Equal(t, uint64(1), firstOutput.New)144 require.Equal(t, uint64(1), firstOutput.New)
@@ -146,7 +147,7 @@ func TestAttachmentOutputEpochsAreAttachmentLocalAndPreparedFramesAreFenced(t *t
146 147
147 secondPrepared, err := second.prepare(frame, nil, true)148 secondPrepared, err := second.prepare(frame, nil, true)
148 require.NoError(t, err)149 require.NoError(t, err)
149- require.NoError(t, secondPrepared.send(secondPrepared.data, 0, func(ports.Frame) error { return nil }))150+ require.NoError(t, secondPrepared.send(secondPrepared.data, 0, func(wire.Frame) error { return nil }))
150 require.Equal(t, uint64(1), second.epoch)151 require.Equal(t, uint64(1), second.epoch)
151 require.Equal(t, uint64(1), second.next)152 require.Equal(t, uint64(1), second.next)
152 require.Equal(t, uint64(1), first.next)153 require.Equal(t, uint64(1), first.next)
@@ -160,7 +161,7 @@ func TestAttachmentOutputEpochsAreAttachmentLocalAndPreparedFramesAreFenced(t *t
160 require.Zero(t, first.next)161 require.Zero(t, first.next)
161 require.Zero(t, first.acked)162 require.Zero(t, first.acked)
162 var staleSent bool163 var staleSent bool
163- require.NoError(t, pending.send(pending.data, 0, func(ports.Frame) error {164+ require.NoError(t, pending.send(pending.data, 0, func(wire.Frame) error {
164 staleSent = true165 staleSent = true
165 return nil166 return nil
166 }))167 }))
@@ -171,12 +172,12 @@ func TestAttachmentOutputEpochsAreAttachmentLocalAndPreparedFramesAreFenced(t *t
171 172
172 reset, err := first.prepare(changed, []renderer.Damage{{Kind: renderer.DamageText, Width: 1, Height: 1}}, false)173 reset, err := first.prepare(changed, []renderer.Damage{{Kind: renderer.DamageText, Width: 1, Height: 1}}, false)
173 require.NoError(t, err)174 require.NoError(t, err)
174- var resetFrame ports.Frame175+ var resetFrame wire.Frame
175- require.NoError(t, reset.send(reset.data, 0, func(frame ports.Frame) error {176+ require.NoError(t, reset.send(reset.data, 0, func(frame wire.Frame) error {
176 resetFrame = frame177 resetFrame = frame
177 return nil178 return nil
178 }))179 }))
179- resetOutput, err := ports.UnmarshalOutput(resetFrame.Payload)180+ resetOutput, err := wire.UnmarshalOutput(resetFrame.Payload)
180 require.NoError(t, err)181 require.NoError(t, err)
181 require.Equal(t, uint64(2), resetOutput.Epoch)182 require.Equal(t, uint64(2), resetOutput.Epoch)
182 require.Zero(t, resetOutput.Base)183 require.Zero(t, resetOutput.Base)
@@ -204,14 +205,14 @@ func TestPreparedOutputDropsReplacedConnectionAndView(t *testing.T) {
204 fillOutputStateRows(frame, []string{"abc"})205 fillOutputStateRows(frame, []string{"abc"})
205 initial, err := stream.prepare(frame, nil, true)206 initial, err := stream.prepare(frame, nil, true)
206 require.NoError(t, err)207 require.NoError(t, err)
207- require.NoError(t, initial.send(initial.data, 0, func(ports.Frame) error { return nil }))208+ require.NoError(t, initial.send(initial.data, 0, func(wire.Frame) error { return nil }))
208 changed := frame.Clone()209 changed := frame.Clone()
209 changed.Set(0, 0, renderer.Cell{Rune: 'x', Style: renderer.DefaultStyle()})210 changed.Set(0, 0, renderer.Cell{Rune: 'x', Style: renderer.DefaultStyle()})
210 pending, err := stream.prepare(changed, []renderer.Damage{{Kind: renderer.DamageText, Width: 1, Height: 1}}, false)211 pending, err := stream.prepare(changed, []renderer.Damage{{Kind: renderer.DamageText, Width: 1, Height: 1}}, false)
211 require.NoError(t, err)212 require.NoError(t, err)
212 tt.change(ac)213 tt.change(ac)
213 var sent bool214 var sent bool
214- require.NoError(t, pending.send(pending.data, 0, func(ports.Frame) error {215+ require.NoError(t, pending.send(pending.data, 0, func(wire.Frame) error {
215 sent = true216 sent = true
216 return nil217 return nil
217 }))218 }))
@@ -220,7 +221,7 @@ func TestPreparedOutputDropsReplacedConnectionAndView(t *testing.T) {
220 221
221 retry, err := stream.prepare(changed, []renderer.Damage{{Kind: renderer.DamageText, Width: 1, Height: 1}}, false)222 retry, err := stream.prepare(changed, []renderer.Damage{{Kind: renderer.DamageText, Width: 1, Height: 1}}, false)
222 require.NoError(t, err)223 require.NoError(t, err)
223- require.NoError(t, retry.send(retry.data, 0, func(ports.Frame) error { return nil }))224+ require.NoError(t, retry.send(retry.data, 0, func(wire.Frame) error { return nil }))
224 require.Equal(t, uint64(2), stream.next)225 require.Equal(t, uint64(2), stream.next)
225 })226 })
226 }227 }
@@ -243,7 +244,7 @@ func TestOutputStateSideEffectsDoNotAdvanceEpochStateOrACK(t *testing.T) {
243 beforeEpoch, beforeNext, beforeAck := stream.epoch, stream.next, stream.acked244 beforeEpoch, beforeNext, beforeAck := stream.epoch, stream.next, stream.acked
244 frame, err := stream.sideEffect([]byte("pty"), 9)245 frame, err := stream.sideEffect([]byte("pty"), 9)
245 require.NoError(t, err)246 require.NoError(t, err)
246- out, err := ports.UnmarshalOutput(frame.Payload)247+ out, err := wire.UnmarshalOutput(frame.Payload)
247 require.NoError(t, err)248 require.NoError(t, err)
248 require.Equal(t, beforeEpoch, out.Epoch)249 require.Equal(t, beforeEpoch, out.Epoch)
249 require.Zero(t, out.Base)250 require.Zero(t, out.Base)
@@ -260,7 +261,7 @@ func TestAttachmentOutputFailedSendRetriesSnapshotWithoutAdvancing(t *testing.T)
260 fillOutputStateRows(initial, []string{"abc"})261 fillOutputStateRows(initial, []string{"abc"})
261 first, err := stream.prepare(initial, nil, true)262 first, err := stream.prepare(initial, nil, true)
262 require.NoError(t, err)263 require.NoError(t, err)
263- require.NoError(t, first.send(first.data, 0, func(ports.Frame) error { return nil }))264+ require.NoError(t, first.send(first.data, 0, func(wire.Frame) error { return nil }))
264 require.Equal(t, uint64(1), stream.next)265 require.Equal(t, uint64(1), stream.next)
265 266
266 changed := initial.Clone()267 changed := initial.Clone()
@@ -272,7 +273,7 @@ func TestAttachmentOutputFailedSendRetriesSnapshotWithoutAdvancing(t *testing.T)
272 require.Empty(t, probe.Bytes(), "preparation must not advance the renderer shadow")273 require.Empty(t, probe.Bytes(), "preparation must not advance the renderer shadow")
273 274
274 sendErr := errors.New("send failed")275 sendErr := errors.New("send failed")
275- require.ErrorIs(t, pending.send(pending.data, 0, func(ports.Frame) error { return sendErr }), sendErr)276+ require.ErrorIs(t, pending.send(pending.data, 0, func(wire.Frame) error { return sendErr }), sendErr)
276 require.Equal(t, uint64(1), stream.next, "failed send must not advance the state chain")277 require.Equal(t, uint64(1), stream.next, "failed send must not advance the state chain")
277 pending.commitNoSend()278 pending.commitNoSend()
278 probe, err = stream.renderer.Prepare(initial, nil, false)279 probe, err = stream.renderer.Prepare(initial, nil, false)
@@ -281,17 +282,17 @@ func TestAttachmentOutputFailedSendRetriesSnapshotWithoutAdvancing(t *testing.T)
281 282
282 retry, err := stream.prepare(changed, []renderer.Damage{{Kind: renderer.DamageText, Width: 1, Height: 1}}, false)283 retry, err := stream.prepare(changed, []renderer.Damage{{Kind: renderer.DamageText, Width: 1, Height: 1}}, false)
283 require.NoError(t, err)284 require.NoError(t, err)
284- var sent ports.Frame285+ var sent wire.Frame
285- require.NoError(t, retry.send(retry.data, 0, func(frame ports.Frame) error {286+ require.NoError(t, retry.send(retry.data, 0, func(frame wire.Frame) error {
286 sent = frame287 sent = frame
287 return nil288 return nil
288 }))289 }))
289- out, err := ports.UnmarshalOutput(sent.Payload)290+ out, err := wire.UnmarshalOutput(sent.Payload)
290 require.NoError(t, err)291 require.NoError(t, err)
291 require.Zero(t, out.Base, "retry after ambiguous send failure must be dependency-free")292 require.Zero(t, out.Base, "retry after ambiguous send failure must be dependency-free")
292 require.Equal(t, uint64(2), out.New, "state numbers remain monotonic across rebases")293 require.Equal(t, uint64(2), out.New, "state numbers remain monotonic across rebases")
293 require.Equal(t, uint64(2), stream.next, "successful retry advances the chain exactly once")294 require.Equal(t, uint64(2), stream.next, "successful retry advances the chain exactly once")
294- require.NoError(t, retry.send(retry.data, 0, func(ports.Frame) error {295+ require.NoError(t, retry.send(retry.data, 0, func(wire.Frame) error {
295 t.Fatal("completed output sent twice")296 t.Fatal("completed output sent twice")
296 return nil297 return nil
297 }))298 }))
@@ -307,7 +308,7 @@ func TestAttachmentOutputNoByteCommitAdvancesShadowWithoutState(t *testing.T) {
307 fillOutputStateRows(frame, []string{"abc"})308 fillOutputStateRows(frame, []string{"abc"})
308 initial, err := stream.prepare(frame, nil, true)309 initial, err := stream.prepare(frame, nil, true)
309 require.NoError(t, err)310 require.NoError(t, err)
310- require.NoError(t, initial.send(initial.data, 0, func(ports.Frame) error { return nil }))311+ require.NoError(t, initial.send(initial.data, 0, func(wire.Frame) error { return nil }))
311 312
312 noOp, err := stream.prepare(frame, nil, false)313 noOp, err := stream.prepare(frame, nil, false)
313 require.NoError(t, err)314 require.NoError(t, err)
@@ -371,7 +372,7 @@ func (s *attachmentOutput) render(frame renderer.Frame, damage []renderer.Damage
371 return prepared.data, nil372 return prepared.data, nil
372 }373 }
373 374
374-func outputStateFrame(stream *attachmentOutput, data []byte, reset bool, echoAck uint64) ports.Frame {375+func outputStateFrame(stream *attachmentOutput, data []byte, reset bool, echoAck uint64) wire.Frame {
375 stream.next++376 stream.next++
376 base := stream.next - 1377 base := stream.next - 1
377 if reset {378 if reset {
@@ -384,22 +385,22 @@ func outputStateFrame(stream *attachmentOutput, data []byte, reset bool, echoAck
384 return frame385 return frame
385 }386 }
386 387
387-func drawOutputState(t *testing.T, stream *attachmentOutput, frame renderer.Frame, damage []renderer.Damage, reset bool, echoAck uint64) (ports.Frame, bool, error) {388+func drawOutputState(t *testing.T, stream *attachmentOutput, frame renderer.Frame, damage []renderer.Damage, reset bool, echoAck uint64) (wire.Frame, bool, error) {
388 t.Helper()389 t.Helper()
389 prepared, err := stream.prepare(frame, damage, reset)390 prepared, err := stream.prepare(frame, damage, reset)
390 if err != nil || len(prepared.data) == 0 {391 if err != nil || len(prepared.data) == 0 {
391 if prepared != nil {392 if prepared != nil {
392 prepared.commitNoSend()393 prepared.commitNoSend()
393 }394 }
394- return ports.Frame{}, false, err395+ return wire.Frame{}, false, err
395 }396 }
396- var output ports.Frame397+ var output wire.Frame
397- err = prepared.send(prepared.data, echoAck, func(frame ports.Frame) error {398+ err = prepared.send(prepared.data, echoAck, func(frame wire.Frame) error {
398 output = frame399 output = frame
399 return nil400 return nil
400 })401 })
401 if err != nil {402 if err != nil {
402- return ports.Frame{}, false, err403+ return wire.Frame{}, false, err
403 }404 }
404 return output, true, nil405 return output, true, nil
405 }406 }
@@ -416,7 +417,7 @@ func TestAttachmentOutputResizeFrameThenNoopAndDamageAreDifferential(t *testing.
416 resized, ok, err := drawOutputState(t, stream, screen.Frame, screen.Damage(), true, 0)417 resized, ok, err := drawOutputState(t, stream, screen.Frame, screen.Damage(), true, 0)
417 require.NoError(t, err)418 require.NoError(t, err)
418 require.True(t, ok)419 require.True(t, ok)
419- resizeOutput, err := ports.UnmarshalOutput(resized.Payload)420+ resizeOutput, err := wire.UnmarshalOutput(resized.Payload)
420 require.NoError(t, err)421 require.NoError(t, err)
421 require.Zero(t, resizeOutput.Base, "resize must emit the one reset frame")422 require.Zero(t, resizeOutput.Base, "resize must emit the one reset frame")
422 screen.ClearDamage()423 screen.ClearDamage()
@@ -429,7 +430,7 @@ func TestAttachmentOutputResizeFrameThenNoopAndDamageAreDifferential(t *testing.
429 damaged, ok, err := drawOutputState(t, stream, screen.Frame, []renderer.Damage{{Kind: renderer.DamageText, Width: 1, Height: 1}}, false, 0)430 damaged, ok, err := drawOutputState(t, stream, screen.Frame, []renderer.Damage{{Kind: renderer.DamageText, Width: 1, Height: 1}}, false, 0)
430 require.NoError(t, err)431 require.NoError(t, err)
431 require.True(t, ok)432 require.True(t, ok)
432- damageOutput, err := ports.UnmarshalOutput(damaged.Payload)433+ damageOutput, err := wire.UnmarshalOutput(damaged.Payload)
433 require.NoError(t, err)434 require.NoError(t, err)
434 require.Equal(t, resizeOutput.New, damageOutput.Base, "later damage must remain incremental")435 require.Equal(t, resizeOutput.New, damageOutput.Base, "later damage must remain incremental")
435 }436 }
@@ -455,7 +456,7 @@ func TestAttachmentOutputFailedSendKeepsTextCursorAndGraphicsSpeculative(t *test
455 require.NoError(t, err)456 require.NoError(t, err)
456 457
457 sendErr := errors.New("send failed")458 sendErr := errors.New("send failed")
458- require.ErrorIs(t, prepared.send(0, func(ports.Frame) error { return sendErr }), sendErr)459+ require.ErrorIs(t, prepared.send(0, func(wire.Frame) error { return sendErr }), sendErr)
459 460
460 require.Equal(t, cursorOut{valid: true, row: 1, col: 1}, output.lastCursor)461 require.Equal(t, cursorOut{valid: true, row: 1, col: 1}, output.lastCursor)
461 require.Empty(t, graphicsState.assets)462 require.Empty(t, graphicsState.assets)
M internal/usecase/daemon/attachment_parking_test.go +5 -5
@@ -6,15 +6,15 @@ import (
6 6
7 "github.com/stretchr/testify/require"7 "github.com/stretchr/testify/require"
8 8
9- "github.com/bnema/vev/internal/ports"
10 "github.com/bnema/vev/internal/protocol"9 "github.com/bnema/vev/internal/protocol"
10+ "github.com/bnema/vev/internal/protocol/wire"
11 )11 )
12 12
13 type resumeWelcomeFailureTransport struct {13 type resumeWelcomeFailureTransport struct {
14 closeTrackingTransport14 closeTrackingTransport
15 }15 }
16 16
17-func (*resumeWelcomeFailureTransport) Send(ports.Frame) error { return errWelcomeSendFailed }17+func (*resumeWelcomeFailureTransport) Send(wire.Frame) error { return errWelcomeSendFailed }
18 18
19 var errWelcomeSendFailed = errors.New("welcome send failed")19 var errWelcomeSendFailed = errors.New("welcome send failed")
20 20
@@ -142,7 +142,7 @@ func TestSuccessfulResumeRejectsOldCredentialAfterWelcome(t *testing.T) {
142 d.clientGone(sess, ac, oldTransport, false)142 d.clientGone(sess, ac, oldTransport, false)
143 143
144 resumedTransport := &closeTrackingTransport{}144 resumedTransport := &closeTrackingTransport{}
145- d.handleHello(resumedTransport, ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(helloResumeCapable(protocol.IntentResume, "work", oldToken))})145+ d.handleHello(resumedTransport, wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(helloResumeCapable(protocol.IntentResume, "work", oldToken))})
146 newToken := ac.resumeToken146 newToken := ac.resumeToken
147 require.NotEqual(t, oldToken, newToken)147 require.NotEqual(t, oldToken, newToken)
148 d.mu.Lock()148 d.mu.Lock()
@@ -152,7 +152,7 @@ func TestSuccessfulResumeRejectsOldCredentialAfterWelcome(t *testing.T) {
152 require.False(t, oldRetained)152 require.False(t, oldRetained)
153 require.NotNil(t, newParked)153 require.NotNil(t, newParked)
154 require.NotEmpty(t, resumedTransport.Sends())154 require.NotEmpty(t, resumedTransport.Sends())
155- require.Equal(t, ports.MsgWelcome, resumedTransport.Sends()[0].Type, "Welcome must remain the first server frame after Hello")155+ require.Equal(t, wire.MsgWelcome, resumedTransport.Sends()[0].Type, "Welcome must remain the first server frame after Hello")
156 }156 }
157 157
158 func TestFailedResumeHandshakeKeepsParkedCredential(t *testing.T) {158 func TestFailedResumeHandshakeKeepsParkedCredential(t *testing.T) {
@@ -166,7 +166,7 @@ func TestFailedResumeHandshakeKeepsParkedCredential(t *testing.T) {
166 d.clientGone(sess, ac, oldTransport, false)166 d.clientGone(sess, ac, oldTransport, false)
167 167
168 failed := &resumeWelcomeFailureTransport{}168 failed := &resumeWelcomeFailureTransport{}
169- d.handleHello(failed, ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(helloResumeCapable(protocol.IntentResume, "work", token))})169+ d.handleHello(failed, wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(helloResumeCapable(protocol.IntentResume, "work", token))})
170 require.Nil(t, ac.transport())170 require.Nil(t, ac.transport())
171 d.mu.Lock()171 d.mu.Lock()
172 parked := d.parked[token]172 parked := d.parked[token]
M internal/usecase/daemon/attachment_registry_test.go +3 -3
@@ -5,7 +5,7 @@ import (
5 "testing"5 "testing"
6 6
7 "github.com/bnema/vev/internal/domain"7 "github.com/bnema/vev/internal/domain"
8- "github.com/bnema/vev/internal/ports"8+ "github.com/bnema/vev/internal/protocol/wire"
9 "github.com/stretchr/testify/require"9 "github.com/stretchr/testify/require"
10 )10 )
11 11
@@ -99,7 +99,7 @@ func TestTabForAttachmentRepairRebasesOutputBeforePublishingRevision(t *testing.
99 require.Greater(t, ac.output.currentEpoch(), beforeEpoch)99 require.Greater(t, ac.output.currentEpoch(), beforeEpoch)
100 frame, err := ac.output.sideEffect([]byte("x"), 0)100 frame, err := ac.output.sideEffect([]byte("x"), 0)
101 require.NoError(t, err)101 require.NoError(t, err)
102- output, err := ports.UnmarshalOutput(frame.Payload)102+ output, err := wire.UnmarshalOutput(frame.Payload)
103 require.NoError(t, err)103 require.NoError(t, err)
104 require.Equal(t, after.revision, output.ViewRevision)104 require.Equal(t, after.revision, output.ViewRevision)
105 require.Equal(t, ac.output.currentEpoch(), output.Epoch)105 require.Equal(t, ac.output.currentEpoch(), output.Epoch)
@@ -129,7 +129,7 @@ func TestPrepareRemovedTabViewRebasesOutputBeforeSideEffect(t *testing.T) {
129 129
130 frame, err := ac.output.sideEffect([]byte("replacement"), 0)130 frame, err := ac.output.sideEffect([]byte("replacement"), 0)
131 require.NoError(t, err)131 require.NoError(t, err)
132- output, err := ports.UnmarshalOutput(frame.Payload)132+ output, err := wire.UnmarshalOutput(frame.Payload)
133 require.NoError(t, err)133 require.NoError(t, err)
134 require.Equal(t, after.revision, output.ViewRevision)134 require.Equal(t, after.revision, output.ViewRevision)
135 require.NotEqual(t, beforeEpoch, output.Epoch)135 require.NotEqual(t, beforeEpoch, output.Epoch)
M internal/usecase/daemon/attachment_render_test.go +22 -21
@@ -10,27 +10,28 @@ import (
10 "github.com/bnema/vev/internal/domain"10 "github.com/bnema/vev/internal/domain"
11 "github.com/bnema/vev/internal/ports"11 "github.com/bnema/vev/internal/ports"
12 "github.com/bnema/vev/internal/protocol"12 "github.com/bnema/vev/internal/protocol"
13+ "github.com/bnema/vev/internal/protocol/wire"
13 "github.com/stretchr/testify/require"14 "github.com/stretchr/testify/require"
14 )15 )
15 16
16 type blockedAttachmentTransport struct {17 type blockedAttachmentTransport struct {
17 entered chan struct{}18 entered chan struct{}
18 release chan struct{}19 release chan struct{}
19- sends chan ports.Frame20+ sends chan wire.Frame
20 blocked atomic.Bool21 blocked atomic.Bool
21 once sync.Once22 once sync.Once
22 }23 }
23 24
24-func (t *blockedAttachmentTransport) Send(frame ports.Frame) error {25+func (t *blockedAttachmentTransport) Send(frame wire.Frame) error {
25- if frame.Type == ports.MsgOutput && t.blocked.Load() {26+ if frame.Type == wire.MsgOutput && t.blocked.Load() {
26 t.once.Do(func() { close(t.entered) })27 t.once.Do(func() { close(t.entered) })
27 <-t.release28 <-t.release
28 }29 }
29 t.sends <- frame30 t.sends <- frame
30 return nil31 return nil
31 }32 }
32-func (*blockedAttachmentTransport) Recv() (ports.Frame, error) { return ports.Frame{}, io.EOF }33+func (*blockedAttachmentTransport) Recv() (wire.Frame, error) { return wire.Frame{}, io.EOF }
33-func (*blockedAttachmentTransport) Close() error { return nil }34+func (*blockedAttachmentTransport) Close() error { return nil }
34 35
35 func TestStaleCapacityReadinessRequeuesWithoutAnotherInvalidation(t *testing.T) {36 func TestStaleCapacityReadinessRequeuesWithoutAnotherInvalidation(t *testing.T) {
36 d, sess, ac, sends := newManualSessionWithPTYs(t, nil)37 d, sess, ac, sends := newManualSessionWithPTYs(t, nil)
@@ -85,8 +86,8 @@ func TestStaleCapacityReadinessRequeuesWithoutAnotherInvalidation(t *testing.T)
85 ac.output.syncCapacityLocked()86 ac.output.syncCapacityLocked()
86 ac.sendMu.Unlock()87 ac.sendMu.Unlock()
87 close(releaseRetry)88 close(releaseRetry)
88- frame := awaitFrame(t, sends, ports.MsgOutput)89+ frame := awaitFrame(t, sends, wire.MsgOutput)
89- output, err := ports.UnmarshalOutput(frame.Payload)90+ output, err := wire.UnmarshalOutput(frame.Payload)
90 require.NoError(t, err)91 require.NoError(t, err)
91 require.Contains(t, string(output.Data), "capacity race")92 require.Contains(t, string(output.Data), "capacity race")
92 awaitTestCompletion(t, fireDone, "initial fire did not finish")93 awaitTestCompletion(t, fireDone, "initial fire did not finish")
@@ -146,10 +147,10 @@ func TestAttachmentFirstPaintDoesNotWaitForBlockedPeer(t *testing.T) {
146 defer releasePTY()147 defer releasePTY()
147 clock := &signalClock{timers: make(chan *signalTimer, 16)}148 clock := &signalClock{timers: make(chan *signalTimer, 16)}
148 d := newTestDaemon(t, newFactory(t, pty), clock)149 d := newTestDaemon(t, newFactory(t, pty), clock)
149- oldTransport := &blockedAttachmentTransport{entered: make(chan struct{}), release: make(chan struct{}), sends: make(chan ports.Frame, 8)}150+ oldTransport := &blockedAttachmentTransport{entered: make(chan struct{}), release: make(chan struct{}), sends: make(chan wire.Frame, 8)}
150 sess, old, err := d.route(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentNew, Name: "work", Size: domain.Size{Cols: 80, Rows: 24}, ClientID: [16]byte{1}}, oldTransport)151 sess, old, err := d.route(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentNew, Name: "work", Size: domain.Size{Cols: 80, Rows: 24}, ClientID: [16]byte{1}}, oldTransport)
151 require.NoError(t, err)152 require.NoError(t, err)
152- newTransport := &blockedAttachmentTransport{entered: make(chan struct{}), release: make(chan struct{}), sends: make(chan ports.Frame, 8)}153+ newTransport := &blockedAttachmentTransport{entered: make(chan struct{}), release: make(chan struct{}), sends: make(chan wire.Frame, 8)}
153 _, fresh, err := d.route(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentAttach, Name: "work", Size: domain.Size{Cols: 80, Rows: 24}, ClientID: [16]byte{2}}, newTransport)154 _, fresh, err := d.route(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentAttach, Name: "work", Size: domain.Size{Cols: 80, Rows: 24}, ClientID: [16]byte{2}}, newTransport)
154 require.NoError(t, err)155 require.NoError(t, err)
155 156
@@ -172,7 +173,7 @@ func TestAttachmentFirstPaintDoesNotWaitForBlockedPeer(t *testing.T) {
172 painted := make(chan bool, 1)173 painted := make(chan bool, 1)
173 go func() { painted <- d.firstPaintForTransition(token) }()174 go func() { painted <- d.firstPaintForTransition(token) }()
174 frame := awaitTestValue(t, newTransport.sends, "healthy attachment first paint was gated by slow peer")175 frame := awaitTestValue(t, newTransport.sends, "healthy attachment first paint was gated by slow peer")
175- require.Equal(t, ports.MsgOutput, frame.Type)176+ require.Equal(t, wire.MsgOutput, frame.Type)
176 require.True(t, <-painted)177 require.True(t, <-painted)
177 close(oldTransport.release)178 close(oldTransport.release)
178 }179 }
@@ -182,7 +183,7 @@ func TestMultiAttachmentHandshakeFirstPaintNotGatedByBlockedPeer(t *testing.T) {
182 defer releasePTY()183 defer releasePTY()
183 clock := &signalClock{timers: make(chan *signalTimer, 16)}184 clock := &signalClock{timers: make(chan *signalTimer, 16)}
184 d := newTestDaemon(t, newFactory(t, pty), clock)185 d := newTestDaemon(t, newFactory(t, pty), clock)
185- oldTransport := &blockedAttachmentTransport{entered: make(chan struct{}), release: make(chan struct{}), sends: make(chan ports.Frame, 8)}186+ oldTransport := &blockedAttachmentTransport{entered: make(chan struct{}), release: make(chan struct{}), sends: make(chan wire.Frame, 8)}
186 sess, old, err := d.route(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentNew, Name: "work", Size: domain.Size{Cols: 80, Rows: 24}, ClientID: [16]byte{1}}, oldTransport)187 sess, old, err := d.route(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentNew, Name: "work", Size: domain.Size{Cols: 80, Rows: 24}, ClientID: [16]byte{1}}, oldTransport)
187 require.NoError(t, err)188 require.NoError(t, err)
188 rc := sess.renderCoordinator()189 rc := sess.renderCoordinator()
@@ -202,16 +203,16 @@ func TestMultiAttachmentHandshakeFirstPaintNotGatedByBlockedPeer(t *testing.T) {
202 awaitTestCompletion(t, oldTransport.entered, "slow attachment did not begin its blocked output")203 awaitTestCompletion(t, oldTransport.entered, "slow attachment did not begin its blocked output")
203 204
204 hello := protocol.Hello{Version: protocol.Version, Intent: protocol.IntentAttach, Name: "work", Size: domain.Size{Cols: 80, Rows: 24}, ClientID: [16]byte{2}}205 hello := protocol.Hello{Version: protocol.Version, Intent: protocol.IntentAttach, Name: "work", Size: domain.Size{Cols: 80, Rows: 24}, ClientID: [16]byte{2}}
205- tr, sends, releaseConn := newConn(t, ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(hello)})206+ tr, sends, releaseConn := newConn(t, wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(hello)})
206 defer releaseConn()207 defer releaseConn()
207 handshakeDone := make(chan struct{})208 handshakeDone := make(chan struct{})
208 go func() {209 go func() {
209 d.handleConn(tr)210 d.handleConn(tr)
210 close(handshakeDone)211 close(handshakeDone)
211 }()212 }()
212- awaitFrame(t, sends, ports.MsgWelcome)213+ awaitFrame(t, sends, wire.MsgWelcome)
213- firstPaint := awaitFrame(t, sends, ports.MsgOutput)214+ firstPaint := awaitFrame(t, sends, wire.MsgOutput)
214- require.Equal(t, ports.MsgOutput, firstPaint.Type)215+ require.Equal(t, wire.MsgOutput, firstPaint.Type)
215 releaseConn()216 releaseConn()
216 awaitTestCompletion(t, handshakeDone, "multi-attachment handshake did not complete after first paint")217 awaitTestCompletion(t, handshakeDone, "multi-attachment handshake did not complete after first paint")
217 close(oldTransport.release)218 close(oldTransport.release)
@@ -222,10 +223,10 @@ func TestAttachmentPaintFanoutDoesNotWaitForBlockedPeer(t *testing.T) {
222 pty, releasePTY := newBlockingPTY(t)223 pty, releasePTY := newBlockingPTY(t)
223 defer releasePTY()224 defer releasePTY()
224 d := newTestDaemon(t, newFactory(t, pty), stubClock{})225 d := newTestDaemon(t, newFactory(t, pty), stubClock{})
225- firstTransport := &blockedAttachmentTransport{entered: make(chan struct{}), release: make(chan struct{}), sends: make(chan ports.Frame, 8)}226+ firstTransport := &blockedAttachmentTransport{entered: make(chan struct{}), release: make(chan struct{}), sends: make(chan wire.Frame, 8)}
226 sess, _, err := d.route(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentNew, Name: "work", Size: domain.Size{Cols: 80, Rows: 24}, ClientID: [16]byte{1}}, firstTransport)227 sess, _, err := d.route(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentNew, Name: "work", Size: domain.Size{Cols: 80, Rows: 24}, ClientID: [16]byte{1}}, firstTransport)
227 require.NoError(t, err)228 require.NoError(t, err)
228- secondTransport := &blockedAttachmentTransport{entered: make(chan struct{}), release: make(chan struct{}), sends: make(chan ports.Frame, 8)}229+ secondTransport := &blockedAttachmentTransport{entered: make(chan struct{}), release: make(chan struct{}), sends: make(chan wire.Frame, 8)}
229 _, _, err = d.route(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentAttach, Name: "work", Size: domain.Size{Cols: 80, Rows: 24}, ClientID: [16]byte{2}}, secondTransport)230 _, _, err = d.route(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentAttach, Name: "work", Size: domain.Size{Cols: 80, Rows: 24}, ClientID: [16]byte{2}}, secondTransport)
230 require.NoError(t, err)231 require.NoError(t, err)
231 rc := sess.renderCoordinator()232 rc := sess.renderCoordinator()
@@ -244,8 +245,8 @@ func TestAttachmentPaintFanoutDoesNotWaitForBlockedPeer(t *testing.T) {
244 }245 }
245 select {246 select {
246 case frame := <-secondTransport.sends:247 case frame := <-secondTransport.sends:
247- require.Equal(t, ports.MsgOutput, frame.Type)248+ require.Equal(t, wire.MsgOutput, frame.Type)
248- out, err := ports.UnmarshalOutput(frame.Payload)249+ out, err := wire.UnmarshalOutput(frame.Payload)
249 require.NoError(t, err)250 require.NoError(t, err)
250 require.True(t, out.Full, "fan-out peers must send a fresh first frame even after another attachment acknowledges shared damage")251 require.True(t, out.Full, "fan-out peers must send a fresh first frame even after another attachment acknowledges shared damage")
251 case <-time.After(time.Second):252 case <-time.After(time.Second):
@@ -284,8 +285,8 @@ func TestAttachmentResizeUsesLatestClaimedSessionGeometry(t *testing.T) {
284 require.True(t, rc.markAttachmentReady(secondLease))285 require.True(t, rc.markAttachmentReady(secondLease))
285 resize := func(ac *attachedClient, tr ports.Transport, size domain.Size) {286 resize := func(ac *attachedClient, tr ports.Transport, size domain.Size) {
286 token := sess.captureAttachmentCapability(ac, tr)287 token := sess.captureAttachmentCapability(ac, tr)
287- d.handleAttachmentClientFrame(token, ports.Frame{288+ d.handleAttachmentClientFrame(token, wire.Frame{
288- Type: ports.MsgResize,289+ Type: wire.MsgResize,
289 Payload: mustMarshalResize(protocol.Resize{Size: size}),290 Payload: mustMarshalResize(protocol.Resize{Size: size}),
290 })291 })
291 }292 }
M internal/usecase/daemon/attachment_route_identity_test.go +5 -5
@@ -5,8 +5,8 @@ import (
5 "testing"5 "testing"
6 6
7 "github.com/bnema/vev/internal/domain"7 "github.com/bnema/vev/internal/domain"
8- "github.com/bnema/vev/internal/ports"
9 "github.com/bnema/vev/internal/protocol"8 "github.com/bnema/vev/internal/protocol"
9+ "github.com/bnema/vev/internal/protocol/wire"
10 "github.com/stretchr/testify/require"10 "github.com/stretchr/testify/require"
11 )11 )
12 12
@@ -14,8 +14,8 @@ type committedIdentityErrorTransport struct {
14 closeTrackingTransport14 closeTrackingTransport
15 }15 }
16 16
17-func (t *committedIdentityErrorTransport) Send(frame ports.Frame) error {17+func (t *committedIdentityErrorTransport) Send(frame wire.Frame) error {
18- if frame.Type == ports.MsgCommittedRouteIdentity {18+ if frame.Type == wire.MsgCommittedRouteIdentity {
19 return errors.New("committed identity send failed")19 return errors.New("committed identity send failed")
20 }20 }
21 return t.closeTrackingTransport.Send(frame)21 return t.closeTrackingTransport.Send(frame)
@@ -58,11 +58,11 @@ func TestAttachmentTransitionPublishesCommittedRouteIdentity(t *testing.T) {
58 var identity protocol.CommittedRouteIdentity58 var identity protocol.CommittedRouteIdentity
59 found := false59 found := false
60 for _, frame := range transport.Sends() {60 for _, frame := range transport.Sends() {
61- if frame.Type != ports.MsgCommittedRouteIdentity {61+ if frame.Type != wire.MsgCommittedRouteIdentity {
62 continue62 continue
63 }63 }
64 var decodeErr error64 var decodeErr error
65- identity, decodeErr = ports.UnmarshalCommittedRouteIdentity(frame.Payload)65+ identity, decodeErr = wire.UnmarshalCommittedRouteIdentity(frame.Payload)
66 require.NoError(t, decodeErr)66 require.NoError(t, decodeErr)
67 found = true67 found = true
68 }68 }
M internal/usecase/daemon/attention_test.go +2 -1
@@ -14,6 +14,7 @@ import (
14 "github.com/bnema/vev/internal/domain"14 "github.com/bnema/vev/internal/domain"
15 "github.com/bnema/vev/internal/ports"15 "github.com/bnema/vev/internal/ports"
16 portsmocks "github.com/bnema/vev/internal/ports/mocks"16 portsmocks "github.com/bnema/vev/internal/ports/mocks"
17+ "github.com/bnema/vev/internal/protocol/wire"
17 "github.com/bnema/vev/internal/usecase/keys"18 "github.com/bnema/vev/internal/usecase/keys"
18 "github.com/bnema/vev/internal/usecase/layout"19 "github.com/bnema/vev/internal/usecase/layout"
19 "github.com/bnema/vev/internal/usecase/ui"20 "github.com/bnema/vev/internal/usecase/ui"
@@ -166,7 +167,7 @@ func TestNoteAttentionDoesNotBlockOnWedgedOtherClient(t *testing.T) {
166 trW := portsmocks.NewMockTransport(t)167 trW := portsmocks.NewMockTransport(t)
167 block := make(chan struct{})168 block := make(chan struct{})
168 t.Cleanup(func() { close(block) })169 t.Cleanup(func() { close(block) })
169- trW.EXPECT().Send(mock.Anything).RunAndReturn(func(ports.Frame) error {170+ trW.EXPECT().Send(mock.Anything).RunAndReturn(func(wire.Frame) error {
170 <-block171 <-block
171 return nil172 return nil
172 }).Maybe()173 }).Maybe()
M internal/usecase/daemon/client.go +5 -4
@@ -14,6 +14,7 @@ import (
14 "github.com/bnema/vev/internal/domain"14 "github.com/bnema/vev/internal/domain"
15 "github.com/bnema/vev/internal/ports"15 "github.com/bnema/vev/internal/ports"
16 "github.com/bnema/vev/internal/protocol"16 "github.com/bnema/vev/internal/protocol"
17+ "github.com/bnema/vev/internal/protocol/wire"
17 "github.com/bnema/vev/internal/usecase/keys"18 "github.com/bnema/vev/internal/usecase/keys"
18 "github.com/bnema/vev/internal/usecase/mouse"19 "github.com/bnema/vev/internal/usecase/mouse"
19 themeui "github.com/bnema/vev/internal/usecase/theme"20 themeui "github.com/bnema/vev/internal/usecase/theme"
@@ -409,7 +410,7 @@ var errTransportReplaced = errors.New("client transport was replaced")
409 410
410 // sendExpectedTransport writes only when expected is still the attachment's411 // sendExpectedTransport writes only when expected is still the attachment's
411 // current transport incarnation. It preserves sendMu -> linkMu lock ordering.412 // current transport incarnation. It preserves sendMu -> linkMu lock ordering.
412-func (ac *attachedClient) sendExpectedTransport(expected transportSnapshot, f ports.Frame) error {413+func (ac *attachedClient) sendExpectedTransport(expected transportSnapshot, f wire.Frame) error {
413 ac.sendMu.Lock()414 ac.sendMu.Lock()
414 defer ac.sendMu.Unlock()415 defer ac.sendMu.Unlock()
415 if !ac.transportSnapshotCurrent(expected) {416 if !ac.transportSnapshotCurrent(expected) {
@@ -435,7 +436,7 @@ func (ac *attachedClient) beginExpectedTransportSendLocked(expected transportSna
435 return nil436 return nil
436 }437 }
437 438
438-func (ac *attachedClient) sendExpectedTransportForAttachment(expected transportSnapshot, f ports.Frame, ticket *attachmentEffect) error {439+func (ac *attachedClient) sendExpectedTransportForAttachment(expected transportSnapshot, f wire.Frame, ticket *attachmentEffect) error {
439 ac.sendMu.Lock()440 ac.sendMu.Lock()
440 defer ac.sendMu.Unlock()441 defer ac.sendMu.Unlock()
441 // A attachment-bound send requires a live ticket, and reports every rejection,442 // A attachment-bound send requires a live ticket, and reports every rejection,
@@ -459,11 +460,11 @@ func (ac *attachedClient) sendExpectedTransportForAttachment(expected transportS
459 // detachNotifyTimeout, the transport is force-closed, failing the in-flight460 // detachNotifyTimeout, the transport is force-closed, failing the in-flight
460 // write. Detach/kill/shutdown paths use this so they are never gated on a461 // write. Detach/kill/shutdown paths use this so they are never gated on a
461 // client that has stopped draining its socket.462 // client that has stopped draining its socket.
462-func (d *Daemon) boundedSend(ac *attachedClient, f ports.Frame) {463+func (d *Daemon) boundedSend(ac *attachedClient, f wire.Frame) {
463 _ = d.boundedSendErr(ac, f)464 _ = d.boundedSendErr(ac, f)
464 }465 }
465 466
466-func (d *Daemon) boundedSendErr(ac *attachedClient, f ports.Frame) error {467+func (d *Daemon) boundedSendErr(ac *attachedClient, f wire.Frame) error {
467 expected := ac.transportSnapshot()468 expected := ac.transportSnapshot()
468 if expected.transport == nil {469 if expected.transport == nil {
469 return errors.New("client transport is nil")470 return errors.New("client transport is nil")
M internal/usecase/daemon/client_frame_routing.go +30 -29
@@ -7,6 +7,7 @@ import (
7 "github.com/bnema/vev/internal/domain"7 "github.com/bnema/vev/internal/domain"
8 "github.com/bnema/vev/internal/ports"8 "github.com/bnema/vev/internal/ports"
9 "github.com/bnema/vev/internal/protocol"9 "github.com/bnema/vev/internal/protocol"
10+ "github.com/bnema/vev/internal/protocol/wire"
10 "github.com/bnema/vev/internal/usecase/command"11 "github.com/bnema/vev/internal/usecase/command"
11 )12 )
12 13
@@ -75,7 +76,7 @@ func (d *Daemon) runConnLoop(ac *attachedClient) {
75 }76 }
76 77
77 // handleAttachmentClientFrame owns the attached-client protocol.78 // handleAttachmentClientFrame owns the attached-client protocol.
78-func (d *Daemon) handleAttachmentClientFrame(capability attachmentCapability, f ports.Frame) bool {79+func (d *Daemon) handleAttachmentClientFrame(capability attachmentCapability, f wire.Frame) bool {
79 if !capability.current() {80 if !capability.current() {
80 return false81 return false
81 }82 }
@@ -91,57 +92,57 @@ func (d *Daemon) handleAttachmentClientFrame(capability attachmentCapability, f
91 d.afterAttachmentEffectAdmitted(effect.capability())92 d.afterAttachmentEffectAdmitted(effect.capability())
92 }93 }
93 switch f.Type {94 switch f.Type {
94- case ports.MsgInput:95+ case wire.MsgInput:
95- if in, derr := ports.UnmarshalInput(f.Payload); derr == nil {96+ if in, derr := wire.UnmarshalInput(f.Payload); derr == nil {
96 d.handleSequencedInputForAttachment(effect, in.InputSeq, in.Data)97 d.handleSequencedInputForAttachment(effect, in.InputSeq, in.Data)
97 }98 }
98- case ports.MsgResize:99+ case wire.MsgResize:
99- if rz, derr := ports.UnmarshalResize(f.Payload); derr == nil && effect.current() {100+ if rz, derr := wire.UnmarshalResize(f.Payload); derr == nil && effect.current() {
100 d.resizeAttachmentGeometryForLease(effect, rz.Geometry())101 d.resizeAttachmentGeometryForLease(effect, rz.Geometry())
101 }102 }
102- case ports.MsgTheme:103+ case wire.MsgTheme:
103- if th, derr := ports.UnmarshalTheme(f.Payload); derr == nil {104+ if th, derr := wire.UnmarshalTheme(f.Payload); derr == nil {
104 d.applyThemeForAttachment(effect, th)105 d.applyThemeForAttachment(effect, th)
105 }106 }
106- case ports.MsgImagePush:107+ case wire.MsgImagePush:
107- if ip, derr := ports.UnmarshalImagePush(f.Payload); derr == nil {108+ if ip, derr := wire.UnmarshalImagePush(f.Payload); derr == nil {
108 d.handleSequencedImagePushForAttachment(effect, ip.InputSeq, ip)109 d.handleSequencedImagePushForAttachment(effect, ip.InputSeq, ip)
109 }110 }
110- case ports.MsgClientNotice:111+ case wire.MsgClientNotice:
111- if notice, derr := ports.UnmarshalClientNotice(f.Payload); derr == nil {112+ if notice, derr := wire.UnmarshalClientNotice(f.Payload); derr == nil {
112 d.handleClientNoticeForAttachment(effect, notice)113 d.handleClientNoticeForAttachment(effect, notice)
113 } else {114 } else {
114 d.log.Warn("malformed client notice", "err", derr)115 d.log.Warn("malformed client notice", "err", derr)
115 }116 }
116- case ports.MsgDetach:117+ case wire.MsgDetach:
117 if effect.current() {118 if effect.current() {
118 d.clientGoneForAttachment(effect, true)119 d.clientGoneForAttachment(effect, true)
119 return true120 return true
120 }121 }
121- case ports.MsgAck:122+ case wire.MsgAck:
122- if ack, derr := ports.UnmarshalAck(f.Payload); derr == nil {123+ if ack, derr := wire.UnmarshalAck(f.Payload); derr == nil {
123 d.ackOutput(effect, ack.Epoch, ack.State)124 d.ackOutput(effect, ack.Epoch, ack.State)
124 }125 }
125- case ports.MsgOutputResetRequest:126+ case wire.MsgOutputResetRequest:
126- if _, derr := ports.UnmarshalOutputResetRequest(f.Payload); derr == nil {127+ if _, derr := wire.UnmarshalOutputResetRequest(f.Payload); derr == nil {
127 d.resetOutput(effect)128 d.resetOutput(effect)
128 }129 }
129- case ports.MsgSamePeerSwitchRequest:130+ case wire.MsgSamePeerSwitchRequest:
130- request, derr := ports.UnmarshalSamePeerSwitchRequest(f.Payload)131+ request, derr := wire.UnmarshalSamePeerSwitchRequest(f.Payload)
131 if derr != nil {132 if derr != nil {
132 d.log.Warn("malformed same-peer switch request", "err", derr)133 d.log.Warn("malformed same-peer switch request", "err", derr)
133 break134 break
134 }135 }
135 d.switchSamePeerForAttachment(effect, request)136 d.switchSamePeerForAttachment(effect, request)
136- case ports.MsgParkedRouteRequest:137+ case wire.MsgParkedRouteRequest:
137- request, derr := ports.UnmarshalParkedRouteRequest(f.Payload)138+ request, derr := wire.UnmarshalParkedRouteRequest(f.Payload)
138 if derr != nil {139 if derr != nil {
139 d.log.Warn("malformed parked-route request", "err", derr)140 d.log.Warn("malformed parked-route request", "err", derr)
140 break141 break
141 }142 }
142 d.handleParkedRouteRequest(effect, request)143 d.handleParkedRouteRequest(effect, request)
143- case ports.MsgRecentRouteSnapshot:144+ case wire.MsgRecentRouteSnapshot:
144- snapshot, derr := ports.UnmarshalRecentRouteSnapshot(f.Payload)145+ snapshot, derr := wire.UnmarshalRecentRouteSnapshot(f.Payload)
145 if derr != nil {146 if derr != nil {
146 d.log.Warn("malformed recent route snapshot", "err", derr)147 d.log.Warn("malformed recent route snapshot", "err", derr)
147 break148 break
@@ -154,16 +155,16 @@ func (d *Daemon) handleAttachmentClientFrame(capability attachmentCapability, f
154 if err := d.sendCommittedRouteIdentityForAttachment(effect); err != nil {155 if err := d.sendCommittedRouteIdentityForAttachment(effect); err != nil {
155 d.detachOnAttachmentSendError(effect.capability(), effect.transport.transport)156 d.detachOnAttachmentSendError(effect.capability(), effect.transport.transport)
156 }157 }
157- case ports.MsgRouteAttentionSubscription:158+ case wire.MsgRouteAttentionSubscription:
158- subscription, derr := ports.UnmarshalRouteAttentionSubscription(f.Payload)159+ subscription, derr := wire.UnmarshalRouteAttentionSubscription(f.Payload)
159 if derr != nil {160 if derr != nil {
160 d.log.Warn("malformed route attention subscription", "err", derr)161 d.log.Warn("malformed route attention subscription", "err", derr)
161 break162 break
162 }163 }
163 effect.ac.setRouteAttentionSubscription(subscription)164 effect.ac.setRouteAttentionSubscription(subscription)
164 d.invalidateRender(effect.sess, effect.ac, false, "client_frame_routing.go:route-attention")165 d.invalidateRender(effect.sess, effect.ac, false, "client_frame_routing.go:route-attention")
165- case ports.MsgRouteNavigationFailure:166+ case wire.MsgRouteNavigationFailure:
166- failure, derr := ports.UnmarshalRouteNavigationFailure(f.Payload)167+ failure, derr := wire.UnmarshalRouteNavigationFailure(f.Payload)
167 if derr != nil {168 if derr != nil {
168 d.log.Warn("malformed route navigation failure", "err", derr)169 d.log.Warn("malformed route navigation failure", "err", derr)
169 break170 break
@@ -182,8 +183,8 @@ func (d *Daemon) handleAttachmentClientFrame(capability attachmentCapability, f
182 message = "that recent route no longer exists"183 message = "that recent route no longer exists"
183 }184 }
184 d.notify(effect.sess, domain.NoticeWarn, domain.NoticeSessionUnavailable, message, nil)185 d.notify(effect.sess, domain.NoticeWarn, domain.NoticeSessionUnavailable, message, nil)
185- case ports.MsgCommand:186+ case wire.MsgCommand:
186- request, derr := ports.UnmarshalCommandRequest(f.Payload)187+ request, derr := wire.UnmarshalCommandRequest(f.Payload)
187 if derr != nil {188 if derr != nil {
188 return false189 return false
189 }190 }
@@ -200,7 +201,7 @@ func (d *Daemon) handleAttachmentClientFrame(capability attachmentCapability, f
200 return true201 return true
201 }202 }
202 resultEffect.End()203 resultEffect.End()
203- case ports.MsgPing:204+ case wire.MsgPing:
204 if err := effect.sendControl(framePong()); err != nil {205 if err := effect.sendControl(framePong()); err != nil {
205 effect.End()206 effect.End()
206 d.detachOnAttachmentSendError(effect.capability(), effect.transport.transport)207 d.detachOnAttachmentSendError(effect.capability(), effect.transport.transport)
M internal/usecase/daemon/clipboard_test.go +14 -13
@@ -19,6 +19,7 @@ import (
19 "github.com/bnema/vev/internal/ports"19 "github.com/bnema/vev/internal/ports"
20 portsmocks "github.com/bnema/vev/internal/ports/mocks"20 portsmocks "github.com/bnema/vev/internal/ports/mocks"
21 "github.com/bnema/vev/internal/protocol"21 "github.com/bnema/vev/internal/protocol"
22+ "github.com/bnema/vev/internal/protocol/wire"
22 scopy "github.com/bnema/vev/internal/usecase/copy"23 scopy "github.com/bnema/vev/internal/usecase/copy"
23 )24 )
24 25
@@ -243,8 +244,8 @@ func TestPTYReaderForwardsOSC52ClipboardToAttachedClient(t *testing.T) {
243 244
244 var clipboardOutput string245 var clipboardOutput string
245 for range 3 {246 for range 3 {
246- f := awaitFrame(t, sends, ports.MsgOutput)247+ f := awaitFrame(t, sends, wire.MsgOutput)
247- out, err := ports.UnmarshalOutput(f.Payload)248+ out, err := wire.UnmarshalOutput(f.Payload)
248 require.NoError(t, err)249 require.NoError(t, err)
249 clipboardOutput = string(out.Data)250 clipboardOutput = string(out.Data)
250 if strings.Contains(clipboardOutput, "\x1b]52;c;aGVsbG8=\x07") {251 if strings.Contains(clipboardOutput, "\x1b]52;c;aGVsbG8=\x07") {
@@ -276,7 +277,7 @@ func TestPTYReaderDropsOversizedClipboardPayload(t *testing.T) {
276 277
277 select {278 select {
278 case f := <-sends:279 case f := <-sends:
279- require.NotEqual(t, ports.MsgOutput, f.Type)280+ require.NotEqual(t, wire.MsgOutput, f.Type)
280 default:281 default:
281 }282 }
282 }283 }
@@ -301,7 +302,7 @@ func TestPTYReaderDropsInvalidBase64Clipboard(t *testing.T) {
301 302
302 select {303 select {
303 case f := <-sends:304 case f := <-sends:
304- require.NotEqual(t, ports.MsgOutput, f.Type)305+ require.NotEqual(t, wire.MsgOutput, f.Type)
305 default:306 default:
306 }307 }
307 }308 }
@@ -328,15 +329,15 @@ type staleClipboardErrorTransport struct {
328 sends int329 sends int
329 }330 }
330 331
331-func (t *staleClipboardErrorTransport) Send(ports.Frame) error {332+func (t *staleClipboardErrorTransport) Send(wire.Frame) error {
332 t.mu.Lock()333 t.mu.Lock()
333 t.sends++334 t.sends++
334 t.mu.Unlock()335 t.mu.Unlock()
335 return errors.New("stale clipboard send")336 return errors.New("stale clipboard send")
336 }337 }
337 338
338-func (*staleClipboardErrorTransport) Recv() (ports.Frame, error) { return ports.Frame{}, io.EOF }339+func (*staleClipboardErrorTransport) Recv() (wire.Frame, error) { return wire.Frame{}, io.EOF }
339-func (*staleClipboardErrorTransport) Close() error { return nil }340+func (*staleClipboardErrorTransport) Close() error { return nil }
340 341
341 func (t *staleClipboardErrorTransport) sendCount() int {342 func (t *staleClipboardErrorTransport) sendCount() int {
342 t.mu.Lock()343 t.mu.Lock()
@@ -383,13 +384,13 @@ func newMovingClipboardErrorTransport() *movingClipboardErrorTransport {
383 }384 }
384 }385 }
385 386
386-func (t *movingClipboardErrorTransport) Send(ports.Frame) error {387+func (t *movingClipboardErrorTransport) Send(wire.Frame) error {
387 t.startedOnce.Do(func() { close(t.started) })388 t.startedOnce.Do(func() { close(t.started) })
388 <-t.release389 <-t.release
389 return errors.New("clipboard send failed after pane move")390 return errors.New("clipboard send failed after pane move")
390 }391 }
391 392
392-func (*movingClipboardErrorTransport) Recv() (ports.Frame, error) { return ports.Frame{}, io.EOF }393+func (*movingClipboardErrorTransport) Recv() (wire.Frame, error) { return wire.Frame{}, io.EOF }
393 394
394 func (t *movingClipboardErrorTransport) Close() error {395 func (t *movingClipboardErrorTransport) Close() error {
395 t.closeOnce.Do(func() { close(t.closed) })396 t.closeOnce.Do(func() { close(t.closed) })
@@ -509,8 +510,8 @@ func newBlockingClipboardTransport() *blockingClipboardTransport {
509 }510 }
510 }511 }
511 512
512-func (tr *blockingClipboardTransport) Send(f ports.Frame) error {513+func (tr *blockingClipboardTransport) Send(f wire.Frame) error {
513- out, err := ports.UnmarshalOutput(f.Payload)514+ out, err := wire.UnmarshalOutput(f.Payload)
514 if err != nil {515 if err != nil {
515 return err516 return err
516 }517 }
@@ -532,5 +533,5 @@ func (tr *blockingClipboardTransport) Send(f ports.Frame) error {
532 return nil533 return nil
533 }534 }
534 535
535-func (tr *blockingClipboardTransport) Recv() (ports.Frame, error) { return ports.Frame{}, io.EOF }536+func (tr *blockingClipboardTransport) Recv() (wire.Frame, error) { return wire.Frame{}, io.EOF }
536-func (tr *blockingClipboardTransport) Close() error { return nil }537+func (tr *blockingClipboardTransport) Close() error { return nil }
M internal/usecase/daemon/config_test.go +5 -5
@@ -8,8 +8,8 @@ import (
8 8
9 renderer "github.com/bnema/vev-vt"9 renderer "github.com/bnema/vev-vt"
10 "github.com/bnema/vev/internal/domain"10 "github.com/bnema/vev/internal/domain"
11- "github.com/bnema/vev/internal/ports"
12 "github.com/bnema/vev/internal/protocol"11 "github.com/bnema/vev/internal/protocol"
12+ "github.com/bnema/vev/internal/protocol/wire"
13 "github.com/bnema/vev/internal/usecase/keys"13 "github.com/bnema/vev/internal/usecase/keys"
14 themeui "github.com/bnema/vev/internal/usecase/theme"14 themeui "github.com/bnema/vev/internal/usecase/theme"
15 )15 )
@@ -318,11 +318,11 @@ func TestApplyConfigRepaintsActivePaletteWithoutReplacingModel(t *testing.T) {
318 defer release()318 defer release()
319 d, sess, ac, sends := newManualSessionWithPTYs(t, p)319 d, sess, ac, sends := newManualSessionWithPTYs(t, p)
320 d.enterPalette(sess, ac)320 d.enterPalette(sess, ac)
321- awaitFrame(t, sends, ports.MsgOutput)321+ awaitFrame(t, sends, wire.MsgOutput)
322 d.handlePaletteInput(ac, []byte("new"))322 d.handlePaletteInput(ac, []byte("new"))
323- awaitFrame(t, sends, ports.MsgOutput)323+ awaitFrame(t, sends, wire.MsgOutput)
324 d.handlePaletteInput(ac, []byte{0x0e})324 d.handlePaletteInput(ac, []byte{0x0e})
325- awaitFrame(t, sends, ports.MsgOutput)325+ awaitFrame(t, sends, wire.MsgOutput)
326 326
327 ac.overlays.paletteMu.Lock()327 ac.overlays.paletteMu.Lock()
328 model := ac.overlays.palette328 model := ac.overlays.palette
@@ -334,7 +334,7 @@ func TestApplyConfigRepaintsActivePaletteWithoutReplacingModel(t *testing.T) {
334 cfg := domain.Defaults()334 cfg := domain.Defaults()
335 cfg.Palette = domain.PaletteConfig{Anchor: domain.AnchorTopLeft, AnchorSet: true}335 cfg.Palette = domain.PaletteConfig{Anchor: domain.AnchorTopLeft, AnchorSet: true}
336 d.ApplyConfig(cfg)336 d.ApplyConfig(cfg)
337- awaitFrame(t, sends, ports.MsgOutput)337+ awaitFrame(t, sends, wire.MsgOutput)
338 338
339 ac.overlays.paletteMu.Lock()339 ac.overlays.paletteMu.Lock()
340 defer ac.overlays.paletteMu.Unlock()340 defer ac.overlays.paletteMu.Unlock()
M internal/usecase/daemon/control.go +7 -6
@@ -13,19 +13,20 @@ import (
13 "github.com/bnema/vev/internal/domain"13 "github.com/bnema/vev/internal/domain"
14 "github.com/bnema/vev/internal/ports"14 "github.com/bnema/vev/internal/ports"
15 "github.com/bnema/vev/internal/protocol"15 "github.com/bnema/vev/internal/protocol"
16+ "github.com/bnema/vev/internal/protocol/wire"
16 "github.com/bnema/vev/internal/usecase/command"17 "github.com/bnema/vev/internal/usecase/command"
17 "github.com/bnema/vev/internal/usecase/layout"18 "github.com/bnema/vev/internal/usecase/layout"
18 )19 )
19 20
20 // handleCommand serves one one-shot control request. The leading version is21 // handleCommand serves one one-shot control request. The leading version is
21 // checked before decoding the versioned body so newer layouts fail cleanly.22 // checked before decoding the versioned body so newer layouts fail cleanly.
22-func (d *Daemon) handleCommand(tr ports.Transport, f ports.Frame) error {23+func (d *Daemon) handleCommand(tr ports.Transport, f wire.Frame) error {
23 defer func() { _ = tr.Close() }()24 defer func() { _ = tr.Close() }()
24 25
25- if version, ok := ports.PeekCommandVersion(f.Payload); !ok || version != protocol.Version {26+ if version, ok := wire.PeekCommandVersion(f.Payload); !ok || version != protocol.Version {
26 return d.sendCommandResult(tr, protocol.CommandResult{Code: protocol.ErrVersionMismatch, Text: "protocol version mismatch"})27 return d.sendCommandResult(tr, protocol.CommandResult{Code: protocol.ErrVersionMismatch, Text: "protocol version mismatch"})
27 }28 }
28- request, err := ports.UnmarshalCommandRequest(f.Payload)29+ request, err := wire.UnmarshalCommandRequest(f.Payload)
29 if err != nil {30 if err != nil {
30 return d.sendCommandResult(tr, protocol.CommandResult{Code: protocol.ErrInternal, Text: "malformed command request"})31 return d.sendCommandResult(tr, protocol.CommandResult{Code: protocol.ErrInternal, Text: "malformed command request"})
31 }32 }
@@ -79,7 +80,7 @@ func (d *Daemon) sendCommandResult(tr ports.Transport, result protocol.CommandRe
79 // boundedControlSend keeps one-shot control handlers from waiting forever on a80 // boundedControlSend keeps one-shot control handlers from waiting forever on a
80 // client that stopped reading. A timeout closes the exact transport so a81 // client that stopped reading. A timeout closes the exact transport so a
81 // blocked Send can unwind before the handler returns.82 // blocked Send can unwind before the handler returns.
82-func (d *Daemon) boundedControlSend(tr ports.Transport, frame ports.Frame) error {83+func (d *Daemon) boundedControlSend(tr ports.Transport, frame wire.Frame) error {
83 _, err := d.boundedSendWith(tr, func() error { return tr.Send(frame) })84 _, err := d.boundedSendWith(tr, func() error { return tr.Send(frame) })
84 if errors.Is(err, errSendTimedOut) {85 if errors.Is(err, errSendTimedOut) {
85 _ = tr.Close()86 _ = tr.Close()
@@ -87,8 +88,8 @@ func (d *Daemon) boundedControlSend(tr ports.Transport, frame ports.Frame) error
87 return err88 return err
88 }89 }
89 90
90-func frameCommandResult(result protocol.CommandResult) ports.Frame {91+func frameCommandResult(result protocol.CommandResult) wire.Frame {
91- return ports.Frame{Type: ports.MsgCommandResult, Payload: ports.MarshalCommandResult(result)}92+ return wire.Frame{Type: wire.MsgCommandResult, Payload: wire.MarshalCommandResult(result)}
92 }93 }
93 94
94 func (d *Daemon) dispatchCommand(ctx context.Context, request protocol.CommandRequest) protocol.CommandResult {95 func (d *Daemon) dispatchCommand(ctx context.Context, request protocol.CommandRequest) protocol.CommandResult {
M internal/usecase/daemon/control_test.go +16 -15
@@ -17,6 +17,7 @@ import (
17 "github.com/bnema/vev/internal/ports"17 "github.com/bnema/vev/internal/ports"
18 portsmocks "github.com/bnema/vev/internal/ports/mocks"18 portsmocks "github.com/bnema/vev/internal/ports/mocks"
19 "github.com/bnema/vev/internal/protocol"19 "github.com/bnema/vev/internal/protocol"
20+ "github.com/bnema/vev/internal/protocol/wire"
20 "github.com/bnema/vev/internal/usecase/command"21 "github.com/bnema/vev/internal/usecase/command"
21 "github.com/bnema/vev/internal/usecase/layout"22 "github.com/bnema/vev/internal/usecase/layout"
22 "github.com/stretchr/testify/require"23 "github.com/stretchr/testify/require"
@@ -196,7 +197,7 @@ func TestHandleCommandAttachedRejectionReturnsSendFailure(t *testing.T) {
196 func TestHandleCommandRejectsVersionBeforeDecodeOrDispatch(t *testing.T) {197 func TestHandleCommandRejectsVersionBeforeDecodeOrDispatch(t *testing.T) {
197 // The version prefix is valid and mismatched, while the remainder is not a198 // The version prefix is valid and mismatched, while the remainder is not a
198 // decodable request. The daemon must still return ErrVersionMismatch.199 // decodable request. The daemon must still return ErrVersionMismatch.
199- frame := ports.Frame{Type: ports.MsgCommand, Payload: []byte{0, byte(protocol.Version + 1)}}200+ frame := wire.Frame{Type: wire.MsgCommand, Payload: []byte{0, byte(protocol.Version + 1)}}
200 tr, sends, _ := newConn(t, frame)201 tr, sends, _ := newConn(t, frame)
201 202
202 d := newTestDaemon(t, nil, stubClock{})203 d := newTestDaemon(t, nil, stubClock{})
@@ -210,9 +211,9 @@ func TestHandleCommandRejectsVersionBeforeDecodeOrDispatch(t *testing.T) {
210 func TestHandleConnRoutesCommand(t *testing.T) {211 func TestHandleConnRoutesCommand(t *testing.T) {
211 d := newTestDaemon(t, nil, stubClock{})212 d := newTestDaemon(t, nil, stubClock{})
212 request := protocol.CommandRequest{Version: protocol.Version, Slug: "list-sessions"}213 request := protocol.CommandRequest{Version: protocol.Version, Slug: "list-sessions"}
213- payload, err := ports.MarshalCommandRequest(request)214+ payload, err := wire.MarshalCommandRequest(request)
214 require.NoError(t, err)215 require.NoError(t, err)
215- frame := ports.Frame{Type: ports.MsgCommand, Payload: payload}216+ frame := wire.Frame{Type: wire.MsgCommand, Payload: payload}
216 tr, sends, _ := newConn(t, frame)217 tr, sends, _ := newConn(t, frame)
217 218
218 d.handleConn(tr)219 d.handleConn(tr)
@@ -1299,7 +1300,7 @@ func TestHandleCommandOppositeMoveCommandsDoNotDeadlock(t *testing.T) {
1299 done <- struct{}{}1300 done <- struct{}{}
1300 }()1301 }()
1301 close(start)1302 close(start)
1302- for _, sends := range []chan ports.Frame{leftSends, rightSends} {1303+ for _, sends := range []chan wire.Frame{leftSends, rightSends} {
1303 select {1304 select {
1304 case <-done:1305 case <-done:
1305 result := awaitCommandResult(t, sends)1306 result := awaitCommandResult(t, sends)
@@ -1324,14 +1325,14 @@ func addNamedMoveDestination(d *Daemon, name, tabID, paneID string) *session {
1324 return sess1325 return sess
1325 }1326 }
1326 1327
1327-func commandFrame(t *testing.T, request protocol.CommandRequest) ports.Frame {1328+func commandFrame(t *testing.T, request protocol.CommandRequest) wire.Frame {
1328 t.Helper()1329 t.Helper()
1329 if request.Version == 0 {1330 if request.Version == 0 {
1330 request.Version = protocol.Version1331 request.Version = protocol.Version
1331 }1332 }
1332- payload, err := ports.MarshalCommandRequest(request)1333+ payload, err := wire.MarshalCommandRequest(request)
1333 require.NoError(t, err)1334 require.NoError(t, err)
1334- return ports.Frame{Type: ports.MsgCommand, Payload: payload}1335+ return wire.Frame{Type: wire.MsgCommand, Payload: payload}
1335 }1336 }
1336 1337
1337 func sendCommand(t *testing.T, d *Daemon, request protocol.CommandRequest) protocol.CommandResult {1338 func sendCommand(t *testing.T, d *Daemon, request protocol.CommandRequest) protocol.CommandResult {
@@ -1342,10 +1343,10 @@ func sendCommand(t *testing.T, d *Daemon, request protocol.CommandRequest) proto
1342 return awaitCommandResult(t, sends)1343 return awaitCommandResult(t, sends)
1343 }1344 }
1344 1345
1345-func awaitCommandResult(t *testing.T, sends chan ports.Frame) protocol.CommandResult {1346+func awaitCommandResult(t *testing.T, sends chan wire.Frame) protocol.CommandResult {
1346 t.Helper()1347 t.Helper()
1347- reply := awaitFrame(t, sends, ports.MsgCommandResult)1348+ reply := awaitFrame(t, sends, wire.MsgCommandResult)
1348- result, err := ports.UnmarshalCommandResult(reply.Payload)1349+ result, err := wire.UnmarshalCommandResult(reply.Payload)
1349 require.NoError(t, err)1350 require.NoError(t, err)
1350 return result1351 return result
1351 }1352 }
@@ -1397,13 +1398,13 @@ func newBlockingControlSendTransport() *blockingControlSendTransport {
1397 return &blockingControlSendTransport{started: make(chan struct{}), closed: make(chan struct{})}1398 return &blockingControlSendTransport{started: make(chan struct{}), closed: make(chan struct{})}
1398 }1399 }
1399 1400
1400-func (tr *blockingControlSendTransport) Send(ports.Frame) error {1401+func (tr *blockingControlSendTransport) Send(wire.Frame) error {
1401 tr.once.Do(func() { close(tr.started) })1402 tr.once.Do(func() { close(tr.started) })
1402 <-tr.closed1403 <-tr.closed
1403 return io.ErrClosedPipe1404 return io.ErrClosedPipe
1404 }1405 }
1405 1406
1406-func (*blockingControlSendTransport) Recv() (ports.Frame, error) { return ports.Frame{}, io.EOF }1407+func (*blockingControlSendTransport) Recv() (wire.Frame, error) { return wire.Frame{}, io.EOF }
1407 func (tr *blockingControlSendTransport) Close() error {1408 func (tr *blockingControlSendTransport) Close() error {
1408 tr.once.Do(func() { close(tr.started) })1409 tr.once.Do(func() { close(tr.started) })
1409 select {1410 select {
@@ -1419,9 +1420,9 @@ type commandSendErrorTransport struct {
1419 closed bool1420 closed bool
1420 }1421 }
1421 1422
1422-func (tr *commandSendErrorTransport) Send(ports.Frame) error { return tr.err }1423+func (tr *commandSendErrorTransport) Send(wire.Frame) error { return tr.err }
1423-func (*commandSendErrorTransport) Recv() (ports.Frame, error) { return ports.Frame{}, io.EOF }1424+func (*commandSendErrorTransport) Recv() (wire.Frame, error) { return wire.Frame{}, io.EOF }
1424-func (tr *commandSendErrorTransport) Close() error { tr.closed = true; return nil }1425+func (tr *commandSendErrorTransport) Close() error { tr.closed = true; return nil }
1425 1426
1426 func addControlSession(d *Daemon, name, tabID, paneID string) *session {1427 func addControlSession(d *Daemon, name, tabID, paneID string) *session {
1427 tb := newTabWithStableID(tabID, paneID, newQuietPTY(), domain.Size{Cols: 80, Rows: 22})1428 tb := newTabWithStableID(tabID, paneID, newQuietPTY(), domain.Size{Cols: 80, Rows: 22})
M internal/usecase/daemon/copymode_test.go +72 -71
@@ -19,6 +19,7 @@ import (
19 "github.com/bnema/vev/internal/ports"19 "github.com/bnema/vev/internal/ports"
20 portsmocks "github.com/bnema/vev/internal/ports/mocks"20 portsmocks "github.com/bnema/vev/internal/ports/mocks"
21 "github.com/bnema/vev/internal/protocol"21 "github.com/bnema/vev/internal/protocol"
22+ "github.com/bnema/vev/internal/protocol/wire"
22 scopy "github.com/bnema/vev/internal/usecase/copy"23 scopy "github.com/bnema/vev/internal/usecase/copy"
23 "github.com/bnema/vev/internal/usecase/layout"24 "github.com/bnema/vev/internal/usecase/layout"
24 "github.com/bnema/vev/internal/usecase/ui"25 "github.com/bnema/vev/internal/usecase/ui"
@@ -47,7 +48,7 @@ func TestOwnedSynchronousSendReturnsCapturedTransportAcrossReplacement(t *testin
47 replacement := &closeTrackingTransport{}48 replacement := &closeTrackingTransport{}
48 sendErr := errors.New("owned send failed")49 sendErr := errors.New("owned send failed")
49 ac := &attachedClient{output: newOutputStateStream()}50 ac := &attachedClient{output: newOutputStateStream()}
50- failed := &ownedSwapErrorTransport{ac: ac, replacement: replacement, err: sendErr, sent: make(chan ports.Frame, 1)}51+ failed := &ownedSwapErrorTransport{ac: ac, replacement: replacement, err: sendErr, sent: make(chan wire.Frame, 1)}
51 ac.replaceTransport(failed)52 ac.replaceTransport(failed)
52 sess := &session{sessionCore: sessionCore{name: "work", attachments: map[*attachedClient]struct{}{ac: {}}}}53 sess := &session{sessionCore: sessionCore{name: "work", attachments: map[*attachedClient]struct{}{ac: {}}}}
53 ac.setSession(sess)54 ac.setSession(sess)
@@ -57,7 +58,7 @@ func TestOwnedSynchronousSendReturnsCapturedTransportAcrossReplacement(t *testin
57 require.ErrorIs(t, err, sendErr)58 require.ErrorIs(t, err, sendErr)
58 require.Same(t, failed, used)59 require.Same(t, failed, used)
59 require.Same(t, replacement, ac.transport())60 require.Same(t, replacement, ac.transport())
60- out, decodeErr := ports.UnmarshalOutput((<-failed.sent).Payload)61+ out, decodeErr := wire.UnmarshalOutput((<-failed.sent).Payload)
61 require.NoError(t, decodeErr)62 require.NoError(t, decodeErr)
62 require.Equal(t, []byte("copy"), out.Data)63 require.Equal(t, []byte("copy"), out.Data)
63 d.detachOnSendError(sess, ac, used)64 d.detachOnSendError(sess, ac, used)
@@ -71,29 +72,29 @@ type swapErrorTransport struct {
71 err error72 err error
72 }73 }
73 74
74-func (t *swapErrorTransport) Send(ports.Frame) error {75+func (t *swapErrorTransport) Send(wire.Frame) error {
75 t.ac.replaceTransport(t.replacement)76 t.ac.replaceTransport(t.replacement)
76 return t.err77 return t.err
77 }78 }
78 79
79-func (t *swapErrorTransport) Recv() (ports.Frame, error) { return ports.Frame{}, io.EOF }80+func (t *swapErrorTransport) Recv() (wire.Frame, error) { return wire.Frame{}, io.EOF }
80-func (t *swapErrorTransport) Close() error { return nil }81+func (t *swapErrorTransport) Close() error { return nil }
81 82
82 type ownedSwapErrorTransport struct {83 type ownedSwapErrorTransport struct {
83 ac *attachedClient84 ac *attachedClient
84 replacement ports.Transport85 replacement ports.Transport
85 err error86 err error
86- sent chan ports.Frame87+ sent chan wire.Frame
87 }88 }
88 89
89-func (t *ownedSwapErrorTransport) Send(f ports.Frame) error { return t.SendSynchronous(f) }90+func (t *ownedSwapErrorTransport) Send(f wire.Frame) error { return t.SendSynchronous(f) }
90-func (t *ownedSwapErrorTransport) SendSynchronous(f ports.Frame) error {91+func (t *ownedSwapErrorTransport) SendSynchronous(f wire.Frame) error {
91 t.sent <- f92 t.sent <- f
92 t.ac.replaceTransport(t.replacement)93 t.ac.replaceTransport(t.replacement)
93 return t.err94 return t.err
94 }95 }
95-func (t *ownedSwapErrorTransport) Recv() (ports.Frame, error) { return ports.Frame{}, io.EOF }96+func (t *ownedSwapErrorTransport) Recv() (wire.Frame, error) { return wire.Frame{}, io.EOF }
96-func (t *ownedSwapErrorTransport) Close() error { return nil }97+func (t *ownedSwapErrorTransport) Close() error { return nil }
97 98
98 // stubClock returns timers whose channel never fires, so a scheduler under it99 // stubClock returns timers whose channel never fires, so a scheduler under it
99 // blocks in its debounce loop until the session context is cancelled. Used by100 // blocks in its debounce loop until the session context is cancelled. Used by
@@ -117,7 +118,7 @@ func TestCopyModeDocumentCarriesPaneRowIDs(t *testing.T) {
117 require.NotEqual(t, historyID, liveID)118 require.NotEqual(t, historyID, liveID)
118 119
119 d.enterCopyMode(sess, ac)120 d.enterCopyMode(sess, ac)
120- awaitFrame(t, sends, ports.MsgOutput)121+ awaitFrame(t, sends, wire.MsgOutput)
121 doc := ac.overlays.copyDocument122 doc := ac.overlays.copyDocument
122 123
123 require.Equal(t, 0, doc.FindRowID(historyID))124 require.Equal(t, 0, doc.FindRowID(historyID))
@@ -197,7 +198,7 @@ func TestCopyModePaletteCommandEntersAndDoesNotForward(t *testing.T) {
197 sess.tabs[0].focusedPane().screen.Write([]byte("live"))198 sess.tabs[0].focusedPane().screen.Write([]byte("live"))
198 199
199 d.handleInput(sess, ac, []byte("\x1b "))200 d.handleInput(sess, ac, []byte("\x1b "))
200- awaitFrame(t, sends, ports.MsgOutput)201+ awaitFrame(t, sends, wire.MsgOutput)
201 d.handleInput(sess, ac, []byte("VIS\r"))202 d.handleInput(sess, ac, []byte("VIS\r"))
202 203
203 if ac.overlays.copyMode == nil {204 if ac.overlays.copyMode == nil {
@@ -208,16 +209,16 @@ func TestCopyModePaletteCommandEntersAndDoesNotForward(t *testing.T) {
208 t.Fatalf("scrollback binding forwarded to PTY: %q", got)209 t.Fatalf("scrollback binding forwarded to PTY: %q", got)
209 default:210 default:
210 }211 }
211- out := awaitFrame(t, sends, ports.MsgOutput)212+ out := awaitFrame(t, sends, wire.MsgOutput)
212- msg, err := ports.UnmarshalOutput(out.Payload)213+ msg, err := wire.UnmarshalOutput(out.Payload)
213 require.NoError(t, err)214 require.NoError(t, err)
214 if got := string(msg.Data); !strings.Contains(got, "[SCROLL]") || strings.Contains(got, "[SELECT]") || strings.Contains(got, "[COPY]") {215 if got := string(msg.Data); !strings.Contains(got, "[SCROLL]") || strings.Contains(got, "[SELECT]") || strings.Contains(got, "[COPY]") {
215 t.Fatalf("passive scrollback paint = %q, want [SCROLL] without [SELECT]/[COPY]", got)216 t.Fatalf("passive scrollback paint = %q, want [SCROLL] without [SELECT]/[COPY]", got)
216 }217 }
217 218
218 d.handleInput(sess, ac, []byte(" "))219 d.handleInput(sess, ac, []byte(" "))
219- out = awaitFrame(t, sends, ports.MsgOutput)220+ out = awaitFrame(t, sends, wire.MsgOutput)
220- msg, err = ports.UnmarshalOutput(out.Payload)221+ msg, err = wire.UnmarshalOutput(out.Payload)
221 require.NoError(t, err)222 require.NoError(t, err)
222 if got := string(msg.Data); !strings.Contains(got, "[SELECT]") || strings.Contains(got, "[SCROLL]") {223 if got := string(msg.Data); !strings.Contains(got, "[SELECT]") || strings.Contains(got, "[SCROLL]") {
223 t.Fatalf("visual selection paint = %q, want [SELECT] without [SCROLL]", got)224 t.Fatalf("visual selection paint = %q, want [SELECT] without [SCROLL]", got)
@@ -233,9 +234,9 @@ func TestCopyModeSearchModalRoutesBatchedInputAfterSlash(t *testing.T) {
233 copy(pane.screen.Frame.Row(1), testRow("beta alpha"))234 copy(pane.screen.Frame.Row(1), testRow("beta alpha"))
234 235
235 d.enterCopyMode(sess, ac)236 d.enterCopyMode(sess, ac)
236- awaitFrame(t, sends, ports.MsgOutput)237+ awaitFrame(t, sends, wire.MsgOutput)
237 d.handleInput(sess, ac, []byte("/alpha\r"))238 d.handleInput(sess, ac, []byte("/alpha\r"))
238- awaitFrame(t, sends, ports.MsgOutput)239+ awaitFrame(t, sends, wire.MsgOutput)
239 240
240 require.Nil(t, ac.overlays.copySearch)241 require.Nil(t, ac.overlays.copySearch)
241 require.NotNil(t, ac.overlays.copyMode)242 require.NotNil(t, ac.overlays.copyMode)
@@ -257,32 +258,32 @@ func TestCopyModeSearchModalJumpsAndKeepsNavigation(t *testing.T) {
257 copy(pane.screen.Frame.Row(2), testRow("gamma"))258 copy(pane.screen.Frame.Row(2), testRow("gamma"))
258 259
259 d.enterCopyMode(sess, ac)260 d.enterCopyMode(sess, ac)
260- awaitFrame(t, sends, ports.MsgOutput)261+ awaitFrame(t, sends, wire.MsgOutput)
261 d.handleInput(sess, ac, []byte("/"))262 d.handleInput(sess, ac, []byte("/"))
262- out := awaitFrame(t, sends, ports.MsgOutput)263+ out := awaitFrame(t, sends, wire.MsgOutput)
263- msg, err := ports.UnmarshalOutput(out.Payload)264+ msg, err := wire.UnmarshalOutput(out.Payload)
264 require.NoError(t, err)265 require.NoError(t, err)
265 require.Contains(t, string(msg.Data), "Search")266 require.Contains(t, string(msg.Data), "Search")
266 require.NotNil(t, ac.overlays.copySearch)267 require.NotNil(t, ac.overlays.copySearch)
267 268
268 d.handleInput(sess, ac, []byte("alpha"))269 d.handleInput(sess, ac, []byte("alpha"))
269- out = awaitFrame(t, sends, ports.MsgOutput)270+ out = awaitFrame(t, sends, wire.MsgOutput)
270- msg, err = ports.UnmarshalOutput(out.Payload)271+ msg, err = wire.UnmarshalOutput(out.Payload)
271 require.NoError(t, err)272 require.NoError(t, err)
272 require.Contains(t, string(msg.Data), "/alpha")273 require.Contains(t, string(msg.Data), "/alpha")
273 require.Contains(t, string(msg.Data), "1:1 alpha")274 require.Contains(t, string(msg.Data), "1:1 alpha")
274 require.Contains(t, string(msg.Data), "2:6 beta alpha")275 require.Contains(t, string(msg.Data), "2:6 beta alpha")
275 276
276 d.handleInput(sess, ac, []byte("\r"))277 d.handleInput(sess, ac, []byte("\r"))
277- awaitFrame(t, sends, ports.MsgOutput)278+ awaitFrame(t, sends, wire.MsgOutput)
278 require.Nil(t, ac.overlays.copySearch)279 require.Nil(t, ac.overlays.copySearch)
279 require.Equal(t, 0, ac.overlays.copyMode.Cursor().Row)280 require.Equal(t, 0, ac.overlays.copyMode.Cursor().Row)
280 281
281 d.handleInput(sess, ac, []byte("n"))282 d.handleInput(sess, ac, []byte("n"))
282- awaitFrame(t, sends, ports.MsgOutput)283+ awaitFrame(t, sends, wire.MsgOutput)
283 require.Equal(t, 1, ac.overlays.copyMode.Cursor().Row)284 require.Equal(t, 1, ac.overlays.copyMode.Cursor().Row)
284 d.handleInput(sess, ac, []byte("N"))285 d.handleInput(sess, ac, []byte("N"))
285- awaitFrame(t, sends, ports.MsgOutput)286+ awaitFrame(t, sends, wire.MsgOutput)
286 require.Equal(t, 0, ac.overlays.copyMode.Cursor().Row)287 require.Equal(t, 0, ac.overlays.copyMode.Cursor().Row)
287 288
288 select {289 select {
@@ -301,17 +302,17 @@ func TestCopyModeSearchModalSelectionPreviewsBehindModal(t *testing.T) {
301 copy(pane.screen.Frame.Row(2), testRow("gamma"))302 copy(pane.screen.Frame.Row(2), testRow("gamma"))
302 303
303 d.enterCopyMode(sess, ac)304 d.enterCopyMode(sess, ac)
304- awaitFrame(t, sends, ports.MsgOutput)305+ awaitFrame(t, sends, wire.MsgOutput)
305 306
306 d.handleInput(sess, ac, []byte("/"))307 d.handleInput(sess, ac, []byte("/"))
307- awaitFrame(t, sends, ports.MsgOutput)308+ awaitFrame(t, sends, wire.MsgOutput)
308 d.handleInput(sess, ac, []byte("alpha"))309 d.handleInput(sess, ac, []byte("alpha"))
309- awaitFrame(t, sends, ports.MsgOutput)310+ awaitFrame(t, sends, wire.MsgOutput)
310 require.NotNil(t, ac.overlays.copySearch)311 require.NotNil(t, ac.overlays.copySearch)
311 require.Equal(t, 0, ac.overlays.copyMode.Cursor().Row, "typing a query previews the selected first result behind the modal")312 require.Equal(t, 0, ac.overlays.copyMode.Cursor().Row, "typing a query previews the selected first result behind the modal")
312 313
313 d.handleInput(sess, ac, []byte{0x0e})314 d.handleInput(sess, ac, []byte{0x0e})
314- awaitFrame(t, sends, ports.MsgOutput)315+ awaitFrame(t, sends, wire.MsgOutput)
315 require.NotNil(t, ac.overlays.copySearch)316 require.NotNil(t, ac.overlays.copySearch)
316 require.Equal(t, 1, ac.overlays.copyMode.Cursor().Row, "moving modal selection previews that result without Enter")317 require.Equal(t, 1, ac.overlays.copyMode.Cursor().Row, "moving modal selection previews that result without Enter")
317 }318 }
@@ -325,9 +326,9 @@ func TestCopyModeSearchModalCapturesMouseAndClearsOnExit(t *testing.T) {
325 copy(pane.screen.Frame.Row(0), testRow("live alpha"))326 copy(pane.screen.Frame.Row(0), testRow("live alpha"))
326 327
327 d.enterCopyMode(sess, ac)328 d.enterCopyMode(sess, ac)
328- awaitFrame(t, sends, ports.MsgOutput)329+ awaitFrame(t, sends, wire.MsgOutput)
329 d.handleInput(sess, ac, []byte("/alpha"))330 d.handleInput(sess, ac, []byte("/alpha"))
330- awaitFrame(t, sends, ports.MsgOutput)331+ awaitFrame(t, sends, wire.MsgOutput)
331 require.NotNil(t, ac.overlays.copySearch)332 require.NotNil(t, ac.overlays.copySearch)
332 cursor := ac.overlays.copyMode.Cursor().Row333 cursor := ac.overlays.copyMode.Cursor().Row
333 334
@@ -336,11 +337,11 @@ func TestCopyModeSearchModalCapturesMouseAndClearsOnExit(t *testing.T) {
336 require.Equal(t, cursor, ac.overlays.copyMode.Cursor().Row)337 require.Equal(t, cursor, ac.overlays.copyMode.Cursor().Row)
337 338
338 d.handleInput(sess, ac, []byte("\x1b"))339 d.handleInput(sess, ac, []byte("\x1b"))
339- awaitFrame(t, sends, ports.MsgOutput)340+ awaitFrame(t, sends, wire.MsgOutput)
340 require.Nil(t, ac.overlays.copySearch)341 require.Nil(t, ac.overlays.copySearch)
341 require.NotNil(t, ac.overlays.copyMode)342 require.NotNil(t, ac.overlays.copyMode)
342 d.handleInput(sess, ac, []byte("q"))343 d.handleInput(sess, ac, []byte("q"))
343- awaitFrame(t, sends, ports.MsgOutput)344+ awaitFrame(t, sends, wire.MsgOutput)
344 require.Nil(t, ac.overlays.copySearch)345 require.Nil(t, ac.overlays.copySearch)
345 require.Nil(t, ac.overlays.copyMode)346 require.Nil(t, ac.overlays.copyMode)
346 }347 }
@@ -355,7 +356,7 @@ func TestCopyModeInputNotForwardedAndOSC52Copy(t *testing.T) {
355 sess.tabs[0].focusedPane().screen.Write([]byte("live"))356 sess.tabs[0].focusedPane().screen.Write([]byte("live"))
356 357
357 d.enterCopyMode(sess, ac)358 d.enterCopyMode(sess, ac)
358- awaitFrame(t, sends, ports.MsgOutput)359+ awaitFrame(t, sends, wire.MsgOutput)
359 d.handleInput(sess, ac, []byte{'g', ' ', 'j', 'y'})360 d.handleInput(sess, ac, []byte{'g', ' ', 'j', 'y'})
360 361
361 select {362 select {
@@ -363,8 +364,8 @@ func TestCopyModeInputNotForwardedAndOSC52Copy(t *testing.T) {
363 t.Fatalf("copy-mode navigation forwarded to PTY: %q", got)364 t.Fatalf("copy-mode navigation forwarded to PTY: %q", got)
364 default:365 default:
365 }366 }
366- out := awaitFrame(t, sends, ports.MsgOutput)367+ out := awaitFrame(t, sends, wire.MsgOutput)
367- msg, err := ports.UnmarshalOutput(out.Payload)368+ msg, err := wire.UnmarshalOutput(out.Payload)
368 require.NoError(t, err)369 require.NoError(t, err)
369 if got, want := string(msg.Data), "\x1b]52;c;b2xkMQpvbGQy\x07"; got != want {370 if got, want := string(msg.Data), "\x1b]52;c;b2xkMQpvbGQy\x07"; got != want {
370 t.Fatalf("OSC52 = %q, want %q", got, want)371 t.Fatalf("OSC52 = %q, want %q", got, want)
@@ -372,8 +373,8 @@ func TestCopyModeInputNotForwardedAndOSC52Copy(t *testing.T) {
372 if ac.overlays.copyMode != nil {373 if ac.overlays.copyMode != nil {
373 t.Fatal("copy mode still active after yank")374 t.Fatal("copy mode still active after yank")
374 }375 }
375- live := awaitFrame(t, sends, ports.MsgOutput)376+ live := awaitFrame(t, sends, wire.MsgOutput)
376- liveMsg, err := ports.UnmarshalOutput(live.Payload)377+ liveMsg, err := wire.UnmarshalOutput(live.Payload)
377 require.NoError(t, err)378 require.NoError(t, err)
378 if strings.Contains(string(liveMsg.Data), "[COPY]") || strings.Contains(string(liveMsg.Data), "[SCROLL]") {379 if strings.Contains(string(liveMsg.Data), "[COPY]") || strings.Contains(string(liveMsg.Data), "[SCROLL]") {
379 t.Fatalf("live repaint still contains copy/scroll status: %q", string(liveMsg.Data))380 t.Fatalf("live repaint still contains copy/scroll status: %q", string(liveMsg.Data))
@@ -386,8 +387,8 @@ func TestCopyModeInputNotForwardedAndOSC52Copy(t *testing.T) {
386 }387 }
387 388
388 d.paint(sess, ac, true, nil)389 d.paint(sess, ac, true, nil)
389- followup := awaitFrame(t, sends, ports.MsgOutput)390+ followup := awaitFrame(t, sends, wire.MsgOutput)
390- followupMsg, err := ports.UnmarshalOutput(followup.Payload)391+ followupMsg, err := wire.UnmarshalOutput(followup.Payload)
391 require.NoError(t, err)392 require.NoError(t, err)
392 if strings.Contains(string(followupMsg.Data), "copied 9 chars to clipboard") {393 if strings.Contains(string(followupMsg.Data), "copied 9 chars to clipboard") {
393 t.Fatalf("copy feedback persisted after next repaint: %q", string(followupMsg.Data))394 t.Fatalf("copy feedback persisted after next repaint: %q", string(followupMsg.Data))
@@ -430,9 +431,9 @@ func TestScrollbackEvictionFeedsCopyModeYank(t *testing.T) {
430 }431 }
431 var hg sync.WaitGroup432 var hg sync.WaitGroup
432 hg.Go(func() { d.handleConn(tr) })433 hg.Go(func() { d.handleConn(tr) })
433- awaitFrame(t, sends, ports.MsgWelcome)434+ awaitFrame(t, sends, wire.MsgWelcome)
434 advanceRender() // initial coordinator invalidation435 advanceRender() // initial coordinator invalidation
435- awaitFrame(t, sends, ports.MsgOutput)436+ awaitFrame(t, sends, wire.MsgOutput)
436 437
437 for i := range 12 {438 for i := range 12 {
438 reads <- fmt.Appendf(nil, "line-%02d\r\n", i)439 reads <- fmt.Appendf(nil, "line-%02d\r\n", i)
@@ -469,8 +470,8 @@ func TestScrollbackEvictionFeedsCopyModeYank(t *testing.T) {
469 470
470 var payload string471 var payload string
471 require.Eventually(t, func() bool {472 require.Eventually(t, func() bool {
472- out := awaitFrame(t, sends, ports.MsgOutput)473+ out := awaitFrame(t, sends, wire.MsgOutput)
473- msg, err := ports.UnmarshalOutput(out.Payload)474+ msg, err := wire.UnmarshalOutput(out.Payload)
474 require.NoError(t, err)475 require.NoError(t, err)
475 payload = string(msg.Data)476 payload = string(msg.Data)
476 return strings.HasPrefix(payload, "\x1b]52;c;")477 return strings.HasPrefix(payload, "\x1b]52;c;")
@@ -495,14 +496,14 @@ func TestCopyModeEscapeRestoresLiveFullRepaint(t *testing.T) {
495 sess.tabs[0].focusedPane().screen.Write([]byte("live"))496 sess.tabs[0].focusedPane().screen.Write([]byte("live"))
496 497
497 d.enterCopyMode(sess, ac)498 d.enterCopyMode(sess, ac)
498- awaitFrame(t, sends, ports.MsgOutput)499+ awaitFrame(t, sends, wire.MsgOutput)
499 d.handleInput(sess, ac, []byte("q"))500 d.handleInput(sess, ac, []byte("q"))
500 501
501 if ac.overlays.copyMode != nil {502 if ac.overlays.copyMode != nil {
502 t.Fatal("copy mode still active after q")503 t.Fatal("copy mode still active after q")
503 }504 }
504- out := awaitFrame(t, sends, ports.MsgOutput)505+ out := awaitFrame(t, sends, wire.MsgOutput)
505- msg, err := ports.UnmarshalOutput(out.Payload)506+ msg, err := wire.UnmarshalOutput(out.Payload)
506 require.NoError(t, err)507 require.NoError(t, err)
507 if strings.Contains(string(msg.Data), "[COPY]") || !strings.Contains(string(msg.Data), "live") {508 if strings.Contains(string(msg.Data), "[COPY]") || !strings.Contains(string(msg.Data), "live") {
508 t.Fatalf("exit repaint = %q, want live full repaint without copy status", string(msg.Data))509 t.Fatalf("exit repaint = %q, want live full repaint without copy status", string(msg.Data))
@@ -531,7 +532,7 @@ func TestCopyModeSplitArrowDoesNotExit(t *testing.T) {
531 sess.tabs[0].focusedPane().screen.Write([]byte("live"))532 sess.tabs[0].focusedPane().screen.Write([]byte("live"))
532 533
533 d.enterCopyMode(sess, ac)534 d.enterCopyMode(sess, ac)
534- awaitFrame(t, sends, ports.MsgOutput)535+ awaitFrame(t, sends, wire.MsgOutput)
535 for _, input := range tc.input {536 for _, input := range tc.input {
536 d.handleInput(sess, ac, input)537 d.handleInput(sess, ac, input)
537 }538 }
@@ -551,11 +552,11 @@ func TestCopyModeOversizedYankShowsTooLargeFeedback(t *testing.T) {
551 copy(sess.tabs[0].focusedPane().screen.Frame.Row(0), testRow(longLine))552 copy(sess.tabs[0].focusedPane().screen.Frame.Row(0), testRow(longLine))
552 553
553 d.enterCopyMode(sess, ac)554 d.enterCopyMode(sess, ac)
554- awaitFrame(t, sends, ports.MsgOutput)555+ awaitFrame(t, sends, wire.MsgOutput)
555 d.handleInput(sess, ac, []byte{' ', 'y'})556 d.handleInput(sess, ac, []byte{' ', 'y'})
556 557
557- out := awaitFrame(t, sends, ports.MsgOutput)558+ out := awaitFrame(t, sends, wire.MsgOutput)
558- msg, err := ports.UnmarshalOutput(out.Payload)559+ msg, err := wire.UnmarshalOutput(out.Payload)
559 require.NoError(t, err)560 require.NoError(t, err)
560 if strings.Contains(string(msg.Data), "\x1b]52;") {561 if strings.Contains(string(msg.Data), "\x1b]52;") {
561 t.Fatalf("oversized yank emitted OSC52: %q", string(msg.Data))562 t.Fatalf("oversized yank emitted OSC52: %q", string(msg.Data))
@@ -574,7 +575,7 @@ func TestCopyModeLoneEscapeExitsAfterDelay(t *testing.T) {
574 sess.tabs[0].focusedPane().screen.Write([]byte("live"))575 sess.tabs[0].focusedPane().screen.Write([]byte("live"))
575 576
576 d.enterCopyMode(sess, ac)577 d.enterCopyMode(sess, ac)
577- awaitFrame(t, sends, ports.MsgOutput)578+ awaitFrame(t, sends, wire.MsgOutput)
578 d.handleInput(sess, ac, []byte("\x1b"))579 d.handleInput(sess, ac, []byte("\x1b"))
579 timer := <-clk.timers580 timer := <-clk.timers
580 ac.overlays.copyMu.Lock()581 ac.overlays.copyMu.Lock()
@@ -598,11 +599,11 @@ func TestCopyModePendingEscapeDoesNotCloseNewMode(t *testing.T) {
598 sess.tabs[0].focusedPane().screen.Write([]byte("live"))599 sess.tabs[0].focusedPane().screen.Write([]byte("live"))
599 600
600 d.enterCopyMode(sess, ac)601 d.enterCopyMode(sess, ac)
601- awaitFrame(t, sends, ports.MsgOutput)602+ awaitFrame(t, sends, wire.MsgOutput)
602 d.handleInput(sess, ac, []byte("\x1b"))603 d.handleInput(sess, ac, []byte("\x1b"))
603 timer := <-clk.timers604 timer := <-clk.timers
604 d.enterCopyMode(sess, ac)605 d.enterCopyMode(sess, ac)
605- awaitFrame(t, sends, ports.MsgOutput)606+ awaitFrame(t, sends, wire.MsgOutput)
606 607
607 ac.overlays.copyMu.Lock()608 ac.overlays.copyMu.Lock()
608 require.Nil(t, ac.overlays.copyESC.timer)609 require.Nil(t, ac.overlays.copyESC.timer)
@@ -641,14 +642,14 @@ func TestCopyModeEmptyYankDoesNotClearClipboard(t *testing.T) {
641 sess.tabs[0].focusedPane().screen.Write([]byte("live"))642 sess.tabs[0].focusedPane().screen.Write([]byte("live"))
642 643
643 d.enterCopyMode(sess, ac)644 d.enterCopyMode(sess, ac)
644- awaitFrame(t, sends, ports.MsgOutput)645+ awaitFrame(t, sends, wire.MsgOutput)
645 d.handleInput(sess, ac, tc.input)646 d.handleInput(sess, ac, tc.input)
646 647
647 if ac.overlays.copyMode != nil {648 if ac.overlays.copyMode != nil {
648 t.Fatal("copy mode still active after empty yank")649 t.Fatal("copy mode still active after empty yank")
649 }650 }
650- out := awaitFrame(t, sends, ports.MsgOutput)651+ out := awaitFrame(t, sends, wire.MsgOutput)
651- msg, err := ports.UnmarshalOutput(out.Payload)652+ msg, err := wire.UnmarshalOutput(out.Payload)
652 require.NoError(t, err)653 require.NoError(t, err)
653 if strings.Contains(string(msg.Data), "\x1b]52;") {654 if strings.Contains(string(msg.Data), "\x1b]52;") {
654 t.Fatalf("empty yank emitted OSC52 clipboard clear: %q", string(msg.Data))655 t.Fatalf("empty yank emitted OSC52 clipboard clear: %q", string(msg.Data))
@@ -667,7 +668,7 @@ func TestHandleCopyInputUsesImmutableSnapshotWithoutPaneLock(t *testing.T) {
667 pane := testAttachmentTab(sess).focusedPane()668 pane := testAttachmentTab(sess).focusedPane()
668 pane.screen.Write([]byte("live"))669 pane.screen.Write([]byte("live"))
669 d.enterCopyMode(sess, ac)670 d.enterCopyMode(sess, ac)
670- awaitFrame(t, sends, ports.MsgOutput)671+ awaitFrame(t, sends, wire.MsgOutput)
671 672
672 pane.mu.Lock()673 pane.mu.Lock()
673 defer pane.mu.Unlock()674 defer pane.mu.Unlock()
@@ -769,7 +770,7 @@ func TestCopyModeCapturesSourceAndRetainsItAcrossFocusMove(t *testing.T) {
769 }770 }
770 771
771 d.enterCopyMode(sess, ac)772 d.enterCopyMode(sess, ac)
772- awaitFrame(t, sends, ports.MsgOutput)773+ awaitFrame(t, sends, wire.MsgOutput)
773 ac.overlays.copyMu.Lock()774 ac.overlays.copyMu.Lock()
774 captured := ac.overlays.copyPane775 captured := ac.overlays.copyPane
775 ac.overlays.copyMu.Unlock()776 ac.overlays.copyMu.Unlock()
@@ -780,8 +781,8 @@ func TestCopyModeCapturesSourceAndRetainsItAcrossFocusMove(t *testing.T) {
780 tb.mu.Unlock()781 tb.mu.Unlock()
781 782
782 d.handleInput(sess, ac, []byte{'g', ' ', 'j', 'y'})783 d.handleInput(sess, ac, []byte{'g', ' ', 'j', 'y'})
783- out := awaitFrame(t, sends, ports.MsgOutput)784+ out := awaitFrame(t, sends, wire.MsgOutput)
784- msg, err := ports.UnmarshalOutput(out.Payload)785+ msg, err := wire.UnmarshalOutput(out.Payload)
785 require.NoError(t, err)786 require.NoError(t, err)
786 wantOSC := "\x1b]52;c;" + base64.StdEncoding.EncodeToString([]byte(tc.wantText)) + "\x07"787 wantOSC := "\x1b]52;c;" + base64.StdEncoding.EncodeToString([]byte(tc.wantText)) + "\x07"
787 require.Equal(t, wantOSC, string(msg.Data))788 require.Equal(t, wantOSC, string(msg.Data))
@@ -809,15 +810,15 @@ func TestFloatingCopyModeWheelUsesCapturedSnapshot(t *testing.T) {
809 fp.mu.Unlock()810 fp.mu.Unlock()
810 811
811 d.enterCopyMode(sess, ac)812 d.enterCopyMode(sess, ac)
812- awaitFrame(t, sends, ports.MsgOutput)813+ awaitFrame(t, sends, wire.MsgOutput)
813 require.Equal(t, total-1, ac.overlays.copyMode.Cursor().Row)814 require.Equal(t, total-1, ac.overlays.copyMode.Cursor().Row)
814 815
815 d.copyWheel(sess, ac, -3)816 d.copyWheel(sess, ac, -3)
816- awaitFrame(t, sends, ports.MsgOutput)817+ awaitFrame(t, sends, wire.MsgOutput)
817 require.Equal(t, total-4, ac.overlays.copyMode.Cursor().Row)818 require.Equal(t, total-4, ac.overlays.copyMode.Cursor().Row)
818 819
819 d.copyWheel(sess, ac, 3)820 d.copyWheel(sess, ac, 3)
820- awaitFrame(t, sends, ports.MsgOutput)821+ awaitFrame(t, sends, wire.MsgOutput)
821 require.Nil(t, ac.overlays.copyMode, "wheel down reaching the captured bottom exits copy mode")822 require.Nil(t, ac.overlays.copyMode, "wheel down reaching the captured bottom exits copy mode")
822 }823 }
823 824
@@ -832,7 +833,7 @@ func TestFloatingCopyModeMouseSelectsFloatingRows(t *testing.T) {
832 fp.screen.Write([]byte("live"))833 fp.screen.Write([]byte("live"))
833 834
834 d.enterCopyMode(sess, ac)835 d.enterCopyMode(sess, ac)
835- awaitFrame(t, sends, ports.MsgOutput)836+ awaitFrame(t, sends, wire.MsgOutput)
836 ac.overlays.copyMu.Lock()837 ac.overlays.copyMu.Lock()
837 viewportTop := ac.overlays.copyMode.ViewportTop838 viewportTop := ac.overlays.copyMode.ViewportTop
838 ac.overlays.copyMu.Unlock()839 ac.overlays.copyMu.Unlock()
@@ -842,12 +843,12 @@ func TestFloatingCopyModeMouseSelectsFloatingRows(t *testing.T) {
842 // offset); clicking a row must select exactly that row.843 // offset); clicking a row must select exactly that row.
843 press := fmt.Sprintf("\x1b[<0;%d;%dM", inner.X+3, inner.Y+3)844 press := fmt.Sprintf("\x1b[<0;%d;%dM", inner.X+3, inner.Y+3)
844 d.handleInput(sess, ac, []byte(press))845 d.handleInput(sess, ac, []byte(press))
845- awaitFrame(t, sends, ports.MsgOutput)846+ awaitFrame(t, sends, wire.MsgOutput)
846 require.Equal(t, viewportTop+1, ac.overlays.copyMode.Cursor().Row)847 require.Equal(t, viewportTop+1, ac.overlays.copyMode.Cursor().Row)
847 848
848 motion := fmt.Sprintf("\x1b[<32;%d;%dM", inner.X+3, inner.Y+4)849 motion := fmt.Sprintf("\x1b[<32;%d;%dM", inner.X+3, inner.Y+4)
849 d.handleInput(sess, ac, []byte(motion))850 d.handleInput(sess, ac, []byte(motion))
850- awaitFrame(t, sends, ports.MsgOutput)851+ awaitFrame(t, sends, wire.MsgOutput)
851 selection := ac.overlays.copyMode.Selection()852 selection := ac.overlays.copyMode.Selection()
852 require.True(t, selection.Enabled)853 require.True(t, selection.Enabled)
853 require.Equal(t, viewportTop+1, selection.Anchor.Row)854 require.Equal(t, viewportTop+1, selection.Anchor.Row)
@@ -863,7 +864,7 @@ func TestFloatingExitClearsCopyModeBeforeRepaint(t *testing.T) {
863 fp.screen.Write([]byte("flt-live"))864 fp.screen.Write([]byte("flt-live"))
864 865
865 d.enterCopyMode(sess, ac)866 d.enterCopyMode(sess, ac)
866- awaitFrame(t, sends, ports.MsgOutput)867+ awaitFrame(t, sends, wire.MsgOutput)
867 tb.mu.Lock()868 tb.mu.Lock()
868 generation := tb.floating.generation869 generation := tb.floating.generation
869 publishPaneOwner(fp, sess, tb, generation)870 publishPaneOwner(fp, sess, tb, generation)
@@ -896,8 +897,8 @@ func TestMouseDragCopyEntryCapturesSourceForYank(t *testing.T) {
896 require.Same(t, pane, captured, "drag entry must capture the copy source pane")897 require.Same(t, pane, captured, "drag entry must capture the copy source pane")
897 898
898 d.handleInput(sess, ac, []byte("y"))899 d.handleInput(sess, ac, []byte("y"))
899- out := awaitFrame(t, sends, ports.MsgOutput)900+ out := awaitFrame(t, sends, wire.MsgOutput)
900- msg, err := ports.UnmarshalOutput(out.Payload)901+ msg, err := wire.UnmarshalOutput(out.Payload)
901 require.NoError(t, err)902 require.NoError(t, err)
902 want := "\x1b]52;c;" + base64.StdEncoding.EncodeToString([]byte("alpha\nb")) + "\x07"903 want := "\x1b]52;c;" + base64.StdEncoding.EncodeToString([]byte("alpha\nb")) + "\x07"
903 require.Equal(t, want, string(msg.Data))904 require.Equal(t, want, string(msg.Data))
M internal/usecase/daemon/daemon.go +14 -13
@@ -18,6 +18,7 @@ import (
18 "github.com/bnema/vev/internal/domain"18 "github.com/bnema/vev/internal/domain"
19 "github.com/bnema/vev/internal/ports"19 "github.com/bnema/vev/internal/ports"
20 "github.com/bnema/vev/internal/protocol"20 "github.com/bnema/vev/internal/protocol"
21+ "github.com/bnema/vev/internal/protocol/wire"
21 "github.com/bnema/vev/internal/usecase/keys"22 "github.com/bnema/vev/internal/usecase/keys"
22 recoveryusecase "github.com/bnema/vev/internal/usecase/recovery"23 recoveryusecase "github.com/bnema/vev/internal/usecase/recovery"
23 )24 )
@@ -1020,7 +1021,7 @@ func (d *Daemon) handleConn(tr ports.Transport) {
1020 defer finishHandshake()1021 defer finishHandshake()
1021 defer stopTransport()1022 defer stopTransport()
1022 1023
1023- var first ports.Frame1024+ var first wire.Frame
1024 err := boundedHandshakeOperation(handshakeCtx, tr, func() error {1025 err := boundedHandshakeOperation(handshakeCtx, tr, func() error {
1025 var recvErr error1026 var recvErr error
1026 first, recvErr = tr.Recv()1027 first, recvErr = tr.Recv()
@@ -1033,27 +1034,27 @@ func (d *Daemon) handleConn(tr ports.Transport) {
1033 return1034 return
1034 }1035 }
1035 switch first.Type {1036 switch first.Type {
1036- case ports.MsgList:1037+ case wire.MsgList:
1037 stopTransport()1038 stopTransport()
1038 finishHandshake()1039 finishHandshake()
1039 d.handleList(tr)1040 d.handleList(tr)
1040- case ports.MsgCommand:1041+ case wire.MsgCommand:
1041 stopTransport()1042 stopTransport()
1042 finishHandshake()1043 finishHandshake()
1043 if err := d.handleCommand(tr, first); err != nil {1044 if err := d.handleCommand(tr, first); err != nil {
1044 d.log.Warn("command handler failed", "err", err)1045 d.log.Warn("command handler failed", "err", err)
1045 }1046 }
1046- case ports.MsgRemotePreviewRequest:1047+ case wire.MsgRemotePreviewRequest:
1047 stopTransport()1048 stopTransport()
1048 finishHandshake()1049 finishHandshake()
1049 if err := d.handleRemotePreview(tr, first); err != nil {1050 if err := d.handleRemotePreview(tr, first); err != nil {
1050 d.log.Warn("remote preview handler failed", "err", err)1051 d.log.Warn("remote preview handler failed", "err", err)
1051 }1052 }
1052- case ports.MsgKill:1053+ case wire.MsgKill:
1053 stopTransport()1054 stopTransport()
1054 finishHandshake()1055 finishHandshake()
1055 d.handleKill(tr, first)1056 d.handleKill(tr, first)
1056- case ports.MsgHello:1057+ case wire.MsgHello:
1057 d.handleHelloWithContext(handshakeCtx, timedOut, stopTransport, finishHandshake, tr, first)1058 d.handleHelloWithContext(handshakeCtx, timedOut, stopTransport, finishHandshake, tr, first)
1058 default:1059 default:
1059 d.log.Warn("hello rejected", "err", "expected hello", "type", first.Type)1060 d.log.Warn("hello rejected", "err", "expected hello", "type", first.Type)
@@ -1110,10 +1111,10 @@ func (d *Daemon) handleList(tr ports.Transport) {
1110 // handleKill terminates the requested live session or stopped named session,1111 // handleKill terminates the requested live session or stopped named session,
1111 // or all sessions, and closes the control connection; the resulting EOF is the1112 // or all sessions, and closes the control connection; the resulting EOF is the
1112 // client's success signal.1113 // client's success signal.
1113-func (d *Daemon) handleKill(tr ports.Transport, f ports.Frame) {1114+func (d *Daemon) handleKill(tr ports.Transport, f wire.Frame) {
1114 defer func() { _ = tr.Close() }()1115 defer func() { _ = tr.Close() }()
1115 1116
1116- k, err := ports.UnmarshalKill(f.Payload)1117+ k, err := wire.UnmarshalKill(f.Payload)
1117 if err != nil {1118 if err != nil {
1118 _ = d.boundedControlSend(tr, frameError(protocol.ErrInternal, "malformed kill request"))1119 _ = d.boundedControlSend(tr, frameError(protocol.ErrInternal, "malformed kill request"))
1119 return1120 return
@@ -1151,7 +1152,7 @@ func (d *Daemon) handleKill(tr ports.Transport, f ports.Frame) {
1151 // handleHello runs the attach handshake for direct package callers. Accepted1152 // handleHello runs the attach handshake for direct package callers. Accepted
1152 // connections use handleHelloWithContext so the deadline also covers the first1153 // connections use handleHelloWithContext so the deadline also covers the first
1153 // frame read in handleConn.1154 // frame read in handleConn.
1154-func (d *Daemon) handleHello(tr ports.Transport, f ports.Frame) {1155+func (d *Daemon) handleHello(tr ports.Transport, f wire.Frame) {
1155 handshakeCtx, timedOut, finishHandshake := d.newHandshakeContext(context.Background())1156 handshakeCtx, timedOut, finishHandshake := d.newHandshakeContext(context.Background())
1156 stopTransport := watchHandshakeTransport(handshakeCtx, tr)1157 stopTransport := watchHandshakeTransport(handshakeCtx, tr)
1157 defer finishHandshake()1158 defer finishHandshake()
@@ -1161,21 +1162,21 @@ func (d *Daemon) handleHello(tr ports.Transport, f ports.Frame) {
1161 1162
1162 // handleHelloWithContext runs the attach handshake: version check, intent1163 // handleHelloWithContext runs the attach handshake: version check, intent
1163 // routing, Welcome, guaranteed first paint, then the per-connection input loop.1164 // routing, Welcome, guaranteed first paint, then the per-connection input loop.
1164-func (d *Daemon) handleHelloWithContext(handshakeCtx context.Context, timedOut <-chan struct{}, stopHandshakeTransport, finishHandshake func(), tr ports.Transport, f ports.Frame) {1165+func (d *Daemon) handleHelloWithContext(handshakeCtx context.Context, timedOut <-chan struct{}, stopHandshakeTransport, finishHandshake func(), tr ports.Transport, f wire.Frame) {
1165 if err := handshakeContextError(context.Background(), timedOut, handshakeCtx.Err()); err != nil {1166 if err := handshakeContextError(context.Background(), timedOut, handshakeCtx.Err()); err != nil {
1166 _ = tr.Close()1167 _ = tr.Close()
1167 return1168 return
1168 }1169 }
1169- sendHandshake := func(frame ports.Frame) error {1170+ sendHandshake := func(frame wire.Frame) error {
1170 err := boundedHandshakeOperation(handshakeCtx, tr, func() error { return tr.Send(frame) })1171 err := boundedHandshakeOperation(handshakeCtx, tr, func() error { return tr.Send(frame) })
1171 if err != nil {1172 if err != nil {
1172 return handshakeContextError(context.Background(), timedOut, err)1173 return handshakeContextError(context.Background(), timedOut, err)
1173 }1174 }
1174 return nil1175 return nil
1175 }1176 }
1176- h, err := ports.UnmarshalHello(f.Payload)1177+ h, err := wire.UnmarshalHello(f.Payload)
1177 if err != nil {1178 if err != nil {
1178- if version, ok := ports.PeekHelloVersion(f.Payload); ok && version != protocol.Version {1179+ if version, ok := wire.PeekHelloVersion(f.Payload); ok && version != protocol.Version {
1179 d.log.Warn("hello rejected", "err", "protocol version mismatch", "version", version, "expected", protocol.Version)1180 d.log.Warn("hello rejected", "err", "protocol version mismatch", "version", version, "expected", protocol.Version)
1180 _ = sendHandshake(frameError(protocol.ErrVersionMismatch, "protocol version mismatch"))1181 _ = sendHandshake(frameError(protocol.ErrVersionMismatch, "protocol version mismatch"))
1181 } else {1182 } else {
M internal/usecase/daemon/daemon_test.go +36 -35
@@ -17,6 +17,7 @@ import (
17 "github.com/bnema/vev/internal/ports"17 "github.com/bnema/vev/internal/ports"
18 portsmocks "github.com/bnema/vev/internal/ports/mocks"18 portsmocks "github.com/bnema/vev/internal/ports/mocks"
19 "github.com/bnema/vev/internal/protocol"19 "github.com/bnema/vev/internal/protocol"
20+ "github.com/bnema/vev/internal/protocol/wire"
20 "github.com/bnema/vev/internal/usecase/keys"21 "github.com/bnema/vev/internal/usecase/keys"
21 )22 )
22 23
@@ -236,11 +237,11 @@ func newFactorySeq(t *testing.T, ptys ...ports.PTY) *portsmocks.MockPTYFactory {
236 // newConn scripts a MockTransport: Recv yields first then more (in order),237 // newConn scripts a MockTransport: Recv yields first then more (in order),
237 // then blocks until the connection is released or Closed (returning io.EOF).238 // then blocks until the connection is released or Closed (returning io.EOF).
238 // Every Send is captured on the returned channel.239 // Every Send is captured on the returned channel.
239-func newConn(t *testing.T, first ports.Frame, more ...ports.Frame) (*portsmocks.MockTransport, chan ports.Frame, func()) {240+func newConn(t *testing.T, first wire.Frame, more ...wire.Frame) (*portsmocks.MockTransport, chan wire.Frame, func()) {
240 t.Helper()241 t.Helper()
241 tr := portsmocks.NewMockTransport(t)242 tr := portsmocks.NewMockTransport(t)
242- sends := make(chan ports.Frame, 64)243+ sends := make(chan wire.Frame, 64)
243- recvCh := make(chan ports.Frame, 1+len(more))244+ recvCh := make(chan wire.Frame, 1+len(more))
244 recvCh <- first245 recvCh <- first
245 for _, f := range more {246 for _, f := range more {
246 recvCh <- f247 recvCh <- f
@@ -249,15 +250,15 @@ func newConn(t *testing.T, first ports.Frame, more ...ports.Frame) (*portsmocks.
249 var once sync.Once250 var once sync.Once
250 closeDone := func() { once.Do(func() { close(done) }) }251 closeDone := func() { once.Do(func() { close(done) }) }
251 252
252- tr.EXPECT().Recv().RunAndReturn(func() (ports.Frame, error) {253+ tr.EXPECT().Recv().RunAndReturn(func() (wire.Frame, error) {
253 select {254 select {
254 case f := <-recvCh:255 case f := <-recvCh:
255 return f, nil256 return f, nil
256 case <-done:257 case <-done:
257- return ports.Frame{}, io.EOF258+ return wire.Frame{}, io.EOF
258 }259 }
259 }).Maybe()260 }).Maybe()
260- tr.EXPECT().Send(mock.Anything).RunAndReturn(func(f ports.Frame) error {261+ tr.EXPECT().Send(mock.Anything).RunAndReturn(func(f wire.Frame) error {
261 sends <- f262 sends <- f
262 return nil263 return nil
263 }).Maybe()264 }).Maybe()
@@ -265,14 +266,14 @@ func newConn(t *testing.T, first ports.Frame, more ...ports.Frame) (*portsmocks.
265 return tr, sends, closeDone266 return tr, sends, closeDone
266 }267 }
267 268
268-func mustHello(intent uint8, name string, sz domain.Size) ports.Frame {269+func mustHello(intent uint8, name string, sz domain.Size) wire.Frame {
269- return ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(protocol.Hello{270+ return wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(protocol.Hello{
270 Version: protocol.Version, Intent: intent, Name: name, Size: sz, TermEnv: "xterm-256color",271 Version: protocol.Version, Intent: intent, Name: name, Size: sz, TermEnv: "xterm-256color",
271 })}272 })}
272 }273 }
273 274
274-func frameInput(data []byte) ports.Frame {275+func frameInput(data []byte) wire.Frame {
275- return ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(protocol.Input{Data: data})}276+ return wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(protocol.Input{Data: data})}
276 }277 }
277 278
278 func screenLineText(s *vt.Screen, y int) string {279 func screenLineText(s *vt.Screen, y int) string {
@@ -284,7 +285,7 @@ func screenLineText(s *vt.Screen, y int) string {
284 }285 }
285 286
286 // awaitFrame waits for the next frame of type typ on ch, failing on timeout.287 // awaitFrame waits for the next frame of type typ on ch, failing on timeout.
287-func awaitFrame(t *testing.T, ch chan ports.Frame, typ ports.MsgType) ports.Frame {288+func awaitFrame(t *testing.T, ch chan wire.Frame, typ wire.MsgType) wire.Frame {
288 t.Helper()289 t.Helper()
289 deadline := time.After(2 * time.Second)290 deadline := time.After(2 * time.Second)
290 for {291 for {
@@ -318,10 +319,10 @@ func sessionCount(d *Daemon) int {
318 319
319 func listSessions(t *testing.T, d *Daemon) protocol.Sessions {320 func listSessions(t *testing.T, d *Daemon) protocol.Sessions {
320 t.Helper()321 t.Helper()
321- tr, sends, _ := newConn(t, ports.Frame{Type: ports.MsgList, Payload: ports.MarshalList(protocol.List{})})322+ tr, sends, _ := newConn(t, wire.Frame{Type: wire.MsgList, Payload: wire.MarshalList(protocol.List{})})
322 d.handleList(tr)323 d.handleList(tr)
323- f := awaitFrame(t, sends, ports.MsgSessions)324+ f := awaitFrame(t, sends, wire.MsgSessions)
324- sessions, err := ports.UnmarshalSessions(f.Payload)325+ sessions, err := wire.UnmarshalSessions(f.Payload)
325 require.NoError(t, err)326 require.NoError(t, err)
326 return sessions327 return sessions
327 }328 }
@@ -331,7 +332,7 @@ func listSessions(t *testing.T, d *Daemon) protocol.Sessions {
331 // transport mock while channels make both sides of the wire deterministic.332 // transport mock while channels make both sides of the wire deterministic.
332 type welcomeBlockingTransport struct {333 type welcomeBlockingTransport struct {
333 tr *portsmocks.MockTransport334 tr *portsmocks.MockTransport
334- sends chan ports.Frame335+ sends chan wire.Frame
335 welcomeEntered chan struct{}336 welcomeEntered chan struct{}
336 releaseWelcome chan struct{}337 releaseWelcome chan struct{}
337 recvDone chan struct{}338 recvDone chan struct{}
@@ -342,23 +343,23 @@ type welcomeBlockingTransport struct {
342 func newWelcomeBlockingTransport(t *testing.T) *welcomeBlockingTransport {343 func newWelcomeBlockingTransport(t *testing.T) *welcomeBlockingTransport {
343 t.Helper()344 t.Helper()
344 b := &welcomeBlockingTransport{345 b := &welcomeBlockingTransport{
345- sends: make(chan ports.Frame, 8),346+ sends: make(chan wire.Frame, 8),
346 welcomeEntered: make(chan struct{}),347 welcomeEntered: make(chan struct{}),
347 releaseWelcome: make(chan struct{}),348 releaseWelcome: make(chan struct{}),
348 recvDone: make(chan struct{}),349 recvDone: make(chan struct{}),
349 }350 }
350 b.tr = portsmocks.NewMockTransport(t)351 b.tr = portsmocks.NewMockTransport(t)
351- b.tr.EXPECT().Send(mock.Anything).RunAndReturn(func(f ports.Frame) error {352+ b.tr.EXPECT().Send(mock.Anything).RunAndReturn(func(f wire.Frame) error {
352 b.sends <- f353 b.sends <- f
353- if f.Type == ports.MsgWelcome {354+ if f.Type == wire.MsgWelcome {
354 close(b.welcomeEntered)355 close(b.welcomeEntered)
355 <-b.releaseWelcome356 <-b.releaseWelcome
356 }357 }
357 return nil358 return nil
358 }).Maybe()359 }).Maybe()
359- b.tr.EXPECT().Recv().RunAndReturn(func() (ports.Frame, error) {360+ b.tr.EXPECT().Recv().RunAndReturn(func() (wire.Frame, error) {
360 <-b.recvDone361 <-b.recvDone
361- return ports.Frame{}, io.EOF362+ return wire.Frame{}, io.EOF
362 }).Maybe()363 }).Maybe()
363 b.tr.EXPECT().Close().Return(nil).Maybe()364 b.tr.EXPECT().Close().Return(nil).Maybe()
364 t.Cleanup(b.finish)365 t.Cleanup(b.finish)
@@ -374,11 +375,11 @@ func (b *welcomeBlockingTransport) finish() {
374 b.closeOnce.Do(func() { close(b.recvDone) })375 b.closeOnce.Do(func() { close(b.recvDone) })
375 }376 }
376 377
377-func newCapturingTransport(t testing.TB) (*portsmocks.MockTransport, chan ports.Frame) {378+func newCapturingTransport(t testing.TB) (*portsmocks.MockTransport, chan wire.Frame) {
378 t.Helper()379 t.Helper()
379 tr := portsmocks.NewMockTransport(t)380 tr := portsmocks.NewMockTransport(t)
380- sends := make(chan ports.Frame, 64)381+ sends := make(chan wire.Frame, 64)
381- tr.EXPECT().Send(mock.Anything).RunAndReturn(func(f ports.Frame) error {382+ tr.EXPECT().Send(mock.Anything).RunAndReturn(func(f wire.Frame) error {
382 sends <- f383 sends <- f
383 return nil384 return nil
384 }).Maybe()385 }).Maybe()
@@ -386,22 +387,22 @@ func newCapturingTransport(t testing.TB) (*portsmocks.MockTransport, chan ports.
386 return tr, sends387 return tr, sends
387 }388 }
388 389
389-func newManualSessionWithPTYs(t testing.TB, ptys ...ports.PTY) (*Daemon, *session, *attachedClient, chan ports.Frame) {390+func newManualSessionWithPTYs(t testing.TB, ptys ...ports.PTY) (*Daemon, *session, *attachedClient, chan wire.Frame) {
390 t.Helper()391 t.Helper()
391 return newManualSessionWithPTYsClock(t, stubClock{}, ptys...)392 return newManualSessionWithPTYsClock(t, stubClock{}, ptys...)
392 }393 }
393 394
394-func newManualSessionWithPTYsClock(t testing.TB, clock ports.Clock, ptys ...ports.PTY) (*Daemon, *session, *attachedClient, chan ports.Frame) {395+func newManualSessionWithPTYsClock(t testing.TB, clock ports.Clock, ptys ...ports.PTY) (*Daemon, *session, *attachedClient, chan wire.Frame) {
395 t.Helper()396 t.Helper()
396 return newManualSessionWithPTYsClockCleanup(t, clock, true, ptys...)397 return newManualSessionWithPTYsClockCleanup(t, clock, true, ptys...)
397 }398 }
398 399
399-func newManualSessionWithPTYsCleanup(t testing.TB, registerCleanup bool, ptys ...ports.PTY) (*Daemon, *session, *attachedClient, chan ports.Frame) {400+func newManualSessionWithPTYsCleanup(t testing.TB, registerCleanup bool, ptys ...ports.PTY) (*Daemon, *session, *attachedClient, chan wire.Frame) {
400 t.Helper()401 t.Helper()
401 return newManualSessionWithPTYsClockCleanup(t, stubClock{}, registerCleanup, ptys...)402 return newManualSessionWithPTYsClockCleanup(t, stubClock{}, registerCleanup, ptys...)
402 }403 }
403 404
404-func newManualSessionWithPTYsClockCleanup(t testing.TB, clock ports.Clock, registerCleanup bool, ptys ...ports.PTY) (*Daemon, *session, *attachedClient, chan ports.Frame) {405+func newManualSessionWithPTYsClockCleanup(t testing.TB, clock ports.Clock, registerCleanup bool, ptys ...ports.PTY) (*Daemon, *session, *attachedClient, chan wire.Frame) {
405 t.Helper()406 t.Helper()
406 d := newTestDaemonWithCleanup(t, nil, clock, registerCleanup)407 d := newTestDaemonWithCleanup(t, nil, clock, registerCleanup)
407 tr, sends := newCapturingTransport(t)408 tr, sends := newCapturingTransport(t)
@@ -432,7 +433,7 @@ func newManualSessionWithPTYsClockCleanup(t testing.TB, clock ports.Clock, regis
432 return d, sess, ac, sends433 return d, sess, ac, sends
433 }434 }
434 435
435-func newManualTabSession(t *testing.T, n int) (*Daemon, *session, *attachedClient, chan ports.Frame, []func()) {436+func newManualTabSession(t *testing.T, n int) (*Daemon, *session, *attachedClient, chan wire.Frame, []func()) {
436 t.Helper()437 t.Helper()
437 ptys := make([]ports.PTY, 0, n)438 ptys := make([]ports.PTY, 0, n)
438 releases := make([]func(), 0, n)439 releases := make([]func(), 0, n)
@@ -671,8 +672,8 @@ func TestHandleHelloDefersFreshOutputUntilWelcome(t *testing.T) {
671 672
672 awaitTestCompletion(t, tr.welcomeEntered, "timed out waiting for Welcome send")673 awaitTestCompletion(t, tr.welcomeEntered, "timed out waiting for Welcome send")
673 handshakeTimer := awaitHandshakeTimer(t, clock)674 handshakeTimer := awaitHandshakeTimer(t, clock)
674- welcome := awaitFrame(t, tr.sends, ports.MsgWelcome)675+ welcome := awaitFrame(t, tr.sends, wire.MsgWelcome)
675- require.Equal(t, ports.MsgWelcome, welcome.Type)676+ require.Equal(t, wire.MsgWelcome, welcome.Type)
676 sess := firstSession(d)677 sess := firstSession(d)
677 sess.mu.Lock()678 sess.mu.Lock()
678 ac := sess.snapshotAttachmentsLocked()[0]679 ac := sess.snapshotAttachmentsLocked()[0]
@@ -695,8 +696,8 @@ func TestHandleHelloDefersFreshOutputUntilWelcome(t *testing.T) {
695 requireNoCoordinatorOutputFrame(t, tr.sends)696 requireNoCoordinatorOutputFrame(t, tr.sends)
696 697
697 tr.release()698 tr.release()
698- output := awaitFrame(t, tr.sends, ports.MsgOutput)699+ output := awaitFrame(t, tr.sends, wire.MsgOutput)
699- first, err := ports.UnmarshalOutput(output.Payload)700+ first, err := wire.UnmarshalOutput(output.Payload)
700 require.NoError(t, err)701 require.NoError(t, err)
701 require.Zero(t, first.Base, "the first post-Welcome frame is full")702 require.Zero(t, first.Base, "the first post-Welcome frame is full")
702 tr.finish()703 tr.finish()
@@ -710,10 +711,10 @@ func TestHandleHelloDefersFreshOutputUntilWelcome(t *testing.T) {
710 // MsgDetached send until the transport is closed (mirroring a full kernel711 // MsgDetached send until the transport is closed (mirroring a full kernel
711 // send buffer: only Close fails the in-flight write). Serve must still tear712 // send buffer: only Close fails the in-flight write). Serve must still tear
712 713
713-func mustOutputData(t *testing.T, sends chan ports.Frame) []byte {714+func mustOutputData(t *testing.T, sends chan wire.Frame) []byte {
714 t.Helper()715 t.Helper()
715- f := awaitFrame(t, sends, ports.MsgOutput)716+ f := awaitFrame(t, sends, wire.MsgOutput)
716- out, err := ports.UnmarshalOutput(f.Payload)717+ out, err := wire.UnmarshalOutput(f.Payload)
717 require.NoError(t, err)718 require.NoError(t, err)
718 return out.Data719 return out.Data
719 }720 }
M internal/usecase/daemon/final_acceptance_test.go +4 -4
@@ -5,8 +5,8 @@ import (
5 5
6 vt "github.com/bnema/vev-vt"6 vt "github.com/bnema/vev-vt"
7 "github.com/bnema/vev/internal/domain"7 "github.com/bnema/vev/internal/domain"
8- "github.com/bnema/vev/internal/ports"
9 "github.com/bnema/vev/internal/protocol"8 "github.com/bnema/vev/internal/protocol"
9+ "github.com/bnema/vev/internal/protocol/wire"
10 "github.com/bnema/vev/internal/usecase/layout"10 "github.com/bnema/vev/internal/usecase/layout"
11 "github.com/stretchr/testify/require"11 "github.com/stretchr/testify/require"
12 )12 )
@@ -149,9 +149,9 @@ func TestAcceptanceAttachmentStateIsolationAcrossResetResizeAndDetach(t *testing
149 firstEpoch := first.output.currentEpoch()149 firstEpoch := first.output.currentEpoch()
150 secondEpoch := second.output.currentEpoch()150 secondEpoch := second.output.currentEpoch()
151 151
152- d.handleAttachmentClientFrame(secondToken, ports.Frame{152+ d.handleAttachmentClientFrame(secondToken, wire.Frame{
153- Type: ports.MsgOutputResetRequest,153+ Type: wire.MsgOutputResetRequest,
154- Payload: ports.MarshalOutputResetRequest(protocol.OutputResetRequest{}),154+ Payload: wire.MarshalOutputResetRequest(protocol.OutputResetRequest{}),
155 })155 })
156 require.Greater(t, second.output.currentEpoch(), secondEpoch)156 require.Greater(t, second.output.currentEpoch(), secondEpoch)
157 require.Equal(t, firstEpoch, first.output.currentEpoch(), "peer output reset crossed attachment boundary")157 require.Equal(t, firstEpoch, first.output.currentEpoch(), "peer output reset crossed attachment boundary")
M internal/usecase/daemon/floating_test.go +6 -5
@@ -18,6 +18,7 @@ import (
18 "github.com/bnema/vev/internal/ports"18 "github.com/bnema/vev/internal/ports"
19 portsmocks "github.com/bnema/vev/internal/ports/mocks"19 portsmocks "github.com/bnema/vev/internal/ports/mocks"
20 "github.com/bnema/vev/internal/protocol"20 "github.com/bnema/vev/internal/protocol"
21+ "github.com/bnema/vev/internal/protocol/wire"
21 scopy "github.com/bnema/vev/internal/usecase/copy"22 scopy "github.com/bnema/vev/internal/usecase/copy"
22 "github.com/bnema/vev/internal/usecase/layout"23 "github.com/bnema/vev/internal/usecase/layout"
23 themeui "github.com/bnema/vev/internal/usecase/theme"24 themeui "github.com/bnema/vev/internal/usecase/theme"
@@ -482,7 +483,7 @@ func TestFloatingTeardownClearsCapturedCopyMode(t *testing.T) {
482 }483 }
483 484
484 func TestFloatingEOFRepaintsVisibleSlotOnly(t *testing.T) {485 func TestFloatingEOFRepaintsVisibleSlotOnly(t *testing.T) {
485- newCase := func(t *testing.T, state floatingState) (*Daemon, *session, *attachedClient, chan ports.Frame, *tab, *pane, func()) {486+ newCase := func(t *testing.T, state floatingState) (*Daemon, *session, *attachedClient, chan wire.Frame, *tab, *pane, func()) {
486 t.Helper()487 t.Helper()
487 normalPTY, releaseNormal := newBlockingPTY(t)488 normalPTY, releaseNormal := newBlockingPTY(t)
488 d, sess, ac, sends := newManualSessionWithPTYs(t, normalPTY)489 d, sess, ac, sends := newManualSessionWithPTYs(t, normalPTY)
@@ -513,8 +514,8 @@ func TestFloatingEOFRepaintsVisibleSlotOnly(t *testing.T) {
513 // Establish a renderer shadow first. The EOF repaint must reset it and514 // Establish a renderer shadow first. The EOF repaint must reset it and
514 // redraw the underlying cell rather than depending on popup damage.515 // redraw the underlying cell rather than depending on popup damage.
515 d.paint(sess, ac, true, nil)516 d.paint(sess, ac, true, nil)
516- baseline := awaitFrame(t, sends, ports.MsgOutput)517+ baseline := awaitFrame(t, sends, wire.MsgOutput)
517- baselineOutput, err := ports.UnmarshalOutput(baseline.Payload)518+ baselineOutput, err := wire.UnmarshalOutput(baseline.Payload)
518 require.NoError(t, err)519 require.NoError(t, err)
519 require.Contains(t, string(baselineOutput.Data), "underlying-cell")520 require.Contains(t, string(baselineOutput.Data), "underlying-cell")
520 521
@@ -541,8 +542,8 @@ func TestFloatingEOFRepaintsVisibleSlotOnly(t *testing.T) {
541 require.Equal(t, floatingUninitialized, tb.floating.state)542 require.Equal(t, floatingUninitialized, tb.floating.state)
542 require.Nil(t, tb.floating.pane)543 require.Nil(t, tb.floating.pane)
543 tb.mu.Unlock()544 tb.mu.Unlock()
544- repaint := awaitFrame(t, sends, ports.MsgOutput)545+ repaint := awaitFrame(t, sends, wire.MsgOutput)
545- output, err := ports.UnmarshalOutput(repaint.Payload)546+ output, err := wire.UnmarshalOutput(repaint.Payload)
546 require.NoError(t, err)547 require.NoError(t, err)
547 require.Zero(t, output.Base, "visible EOF must force a dependency-free full repaint")548 require.Zero(t, output.Base, "visible EOF must force a dependency-free full repaint")
548 require.Contains(t, string(output.Data), "underlying-cell", "repaint must restore cells previously covered by the popup")549 require.Contains(t, string(output.Data), "underlying-cell", "repaint must restore cells previously covered by the popup")
M internal/usecase/daemon/graphics_output_test.go +12 -11
@@ -15,6 +15,7 @@ import (
15 "github.com/bnema/vev/internal/domain"15 "github.com/bnema/vev/internal/domain"
16 "github.com/bnema/vev/internal/ports"16 "github.com/bnema/vev/internal/ports"
17 "github.com/bnema/vev/internal/protocol"17 "github.com/bnema/vev/internal/protocol"
18+ "github.com/bnema/vev/internal/protocol/wire"
18 "github.com/bnema/vev/internal/usecase/layout"19 "github.com/bnema/vev/internal/usecase/layout"
19 "github.com/stretchr/testify/require"20 "github.com/stretchr/testify/require"
20 )21 )
@@ -53,7 +54,7 @@ type lateGraphicsCleanupTransport struct {
53 started chan struct{}54 started chan struct{}
54 release chan struct{}55 release chan struct{}
55 finished chan struct{}56 finished chan struct{}
56- sends chan ports.Frame57+ sends chan wire.Frame
57 startedOnce sync.Once58 startedOnce sync.Once
58 finishOnce sync.Once59 finishOnce sync.Once
59 }60 }
@@ -63,11 +64,11 @@ func newLateGraphicsCleanupTransport() *lateGraphicsCleanupTransport {
63 started: make(chan struct{}),64 started: make(chan struct{}),
64 release: make(chan struct{}),65 release: make(chan struct{}),
65 finished: make(chan struct{}),66 finished: make(chan struct{}),
66- sends: make(chan ports.Frame, 1),67+ sends: make(chan wire.Frame, 1),
67 }68 }
68 }69 }
69 70
70-func (t *lateGraphicsCleanupTransport) Send(frame ports.Frame) error {71+func (t *lateGraphicsCleanupTransport) Send(frame wire.Frame) error {
71 t.startedOnce.Do(func() { close(t.started) })72 t.startedOnce.Do(func() { close(t.started) })
72 <-t.release73 <-t.release
73 t.sends <- frame74 t.sends <- frame
@@ -75,8 +76,8 @@ func (t *lateGraphicsCleanupTransport) Send(frame ports.Frame) error {
75 return nil76 return nil
76 }77 }
77 78
78-func (*lateGraphicsCleanupTransport) Recv() (ports.Frame, error) { return ports.Frame{}, io.EOF }79+func (*lateGraphicsCleanupTransport) Recv() (wire.Frame, error) { return wire.Frame{}, io.EOF }
79-func (*lateGraphicsCleanupTransport) Close() error { return nil }80+func (*lateGraphicsCleanupTransport) Close() error { return nil }
80 81
81 func TestGraphicsOutputReplaysStaticKittenIcatFixture(t *testing.T) {82 func TestGraphicsOutputReplaysStaticKittenIcatFixture(t *testing.T) {
82 state := newGraphicsOutputState()83 state := newGraphicsOutputState()
@@ -127,8 +128,8 @@ func TestKittyAttachmentPaintCarriesGraphicsInTheOutputStateRecord(t *testing.T)
127 pane.screen.Write(fixture)128 pane.screen.Write(fixture)
128 129
129 d.paint(sess, ac, true, nil)130 d.paint(sess, ac, true, nil)
130- frame := awaitFrame(t, sends, ports.MsgOutput)131+ frame := awaitFrame(t, sends, wire.MsgOutput)
131- output, err := ports.UnmarshalOutput(frame.Payload)132+ output, err := wire.UnmarshalOutput(frame.Payload)
132 require.NoError(t, err)133 require.NoError(t, err)
133 require.Contains(t, string(output.Data), "\x1b_Ga=t,i=")134 require.Contains(t, string(output.Data), "\x1b_Ga=t,i=")
134 require.Contains(t, string(output.Data), ",f=100")135 require.Contains(t, string(output.Data), ",f=100")
@@ -157,8 +158,8 @@ func TestFreshCapabilityDowngradePreservesForeignTerminalGraphics(t *testing.T)
157 require.False(t, ac.output.graphicsUnsupportedWarned.Load(), "replacement terminal must receive its own unsupported-graphics warning")158 require.False(t, ac.output.graphicsUnsupportedWarned.Load(), "replacement terminal must receive its own unsupported-graphics warning")
158 159
159 d.paint(sess, ac, true, nil)160 d.paint(sess, ac, true, nil)
160- frame := awaitFrame(t, sends, ports.MsgOutput)161+ frame := awaitFrame(t, sends, wire.MsgOutput)
161- output, err := ports.UnmarshalOutput(frame.Payload)162+ output, err := wire.UnmarshalOutput(frame.Payload)
162 require.NoError(t, err)163 require.NoError(t, err)
163 require.Positive(t, output.New)164 require.Positive(t, output.New)
164 require.NotContains(t, string(output.Data), "\x1b_Ga=d,d=A", "vev must never globally delete terminal images")165 require.NotContains(t, string(output.Data), "\x1b_Ga=d,d=A", "vev must never globally delete terminal images")
@@ -449,7 +450,7 @@ func TestGraphicsNamespaceStaysQuarantinedAfterSocketSendSuccessWithoutTerminalF
449 require.Nil(t, ac.output.graphicsOutput)450 require.Nil(t, ac.output.graphicsOutput)
450 sends := transport.Sends()451 sends := transport.Sends()
451 require.Len(t, sends, 1)452 require.Len(t, sends, 1)
452- cleanup, err := ports.UnmarshalOutput(sends[0].Payload)453+ cleanup, err := wire.UnmarshalOutput(sends[0].Payload)
453 require.NoError(t, err)454 require.NoError(t, err)
454 require.Zero(t, cleanup.New, "graphics cleanup is an unacknowledged side-effect frame")455 require.Zero(t, cleanup.New, "graphics cleanup is an unacknowledged side-effect frame")
455 456
@@ -572,7 +573,7 @@ func TestGraphicsNamespaceQuarantineFencesTimedOutLateDelete(t *testing.T) {
572 close(transport.release)573 close(transport.release)
573 staleFrame := awaitTestValue(t, transport.sends, "late stale cleanup did not execute")574 staleFrame := awaitTestValue(t, transport.sends, "late stale cleanup did not execute")
574 awaitTestCompletion(t, transport.finished, "late stale cleanup did not return")575 awaitTestCompletion(t, transport.finished, "late stale cleanup did not return")
575- staleOutput, err := ports.UnmarshalOutput(staleFrame.Payload)576+ staleOutput, err := wire.UnmarshalOutput(staleFrame.Payload)
576 require.NoError(t, err)577 require.NoError(t, err)
577 require.Contains(t, string(staleOutput.Data), "i="+strconv.FormatUint(oldID, 10))578 require.Contains(t, string(staleOutput.Data), "i="+strconv.FormatUint(oldID, 10))
578 require.NotContains(t, string(staleOutput.Data), "i="+strconv.FormatUint(newID, 10), "late cleanup must never target the fresh attachment")579 require.NotContains(t, string(staleOutput.Data), "i="+strconv.FormatUint(newID, 10), "late cleanup must never target the fresh attachment")
M internal/usecase/daemon/handshake_test.go +6 -6
@@ -10,8 +10,8 @@ import (
10 "github.com/stretchr/testify/require"10 "github.com/stretchr/testify/require"
11 11
12 "github.com/bnema/vev/internal/domain"12 "github.com/bnema/vev/internal/domain"
13- "github.com/bnema/vev/internal/ports"
14 "github.com/bnema/vev/internal/protocol"13 "github.com/bnema/vev/internal/protocol"
14+ "github.com/bnema/vev/internal/protocol/wire"
15 )15 )
16 16
17 type handshakeBlockingTransport struct {17 type handshakeBlockingTransport struct {
@@ -29,8 +29,8 @@ func newHandshakeBlockingTransport(blockWelcome bool) *handshakeBlockingTranspor
29 }29 }
30 }30 }
31 31
32-func (t *handshakeBlockingTransport) Send(frame ports.Frame) error {32+func (t *handshakeBlockingTransport) Send(frame wire.Frame) error {
33- if t.blockWelcome && frame.Type == ports.MsgWelcome {33+ if t.blockWelcome && frame.Type == wire.MsgWelcome {
34 close(t.welcome)34 close(t.welcome)
35 <-t.closed35 <-t.closed
36 return io.ErrClosedPipe36 return io.ErrClosedPipe
@@ -38,9 +38,9 @@ func (t *handshakeBlockingTransport) Send(frame ports.Frame) error {
38 return nil38 return nil
39 }39 }
40 40
41-func (t *handshakeBlockingTransport) Recv() (ports.Frame, error) {41+func (t *handshakeBlockingTransport) Recv() (wire.Frame, error) {
42 <-t.closed42 <-t.closed
43- return ports.Frame{}, io.ErrClosedPipe43+ return wire.Frame{}, io.ErrClosedPipe
44 }44 }
45 45
46 func (t *handshakeBlockingTransport) Close() error {46 func (t *handshakeBlockingTransport) Close() error {
@@ -183,7 +183,7 @@ func TestFailedResumeHandshakeRestoresParkedCredential(t *testing.T) {
183 resumeTransport := newHandshakeBlockingTransport(true)183 resumeTransport := newHandshakeBlockingTransport(true)
184 done := make(chan struct{})184 done := make(chan struct{})
185 go func() {185 go func() {
186- d.handleHello(resumeTransport, ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(helloResumeCapable(protocol.IntentResume, sess.name, token))})186+ d.handleHello(resumeTransport, wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(helloResumeCapable(protocol.IntentResume, sess.name, token))})
187 close(done)187 close(done)
188 }()188 }()
189 189
M internal/usecase/daemon/input_test.go +29 -29
@@ -15,9 +15,9 @@ import (
15 15
16 vt "github.com/bnema/vev-vt"16 vt "github.com/bnema/vev-vt"
17 "github.com/bnema/vev/internal/domain"17 "github.com/bnema/vev/internal/domain"
18- "github.com/bnema/vev/internal/ports"
19 portsmocks "github.com/bnema/vev/internal/ports/mocks"18 portsmocks "github.com/bnema/vev/internal/ports/mocks"
20 "github.com/bnema/vev/internal/protocol"19 "github.com/bnema/vev/internal/protocol"
20+ "github.com/bnema/vev/internal/protocol/wire"
21 scopy "github.com/bnema/vev/internal/usecase/copy"21 scopy "github.com/bnema/vev/internal/usecase/copy"
22 "github.com/bnema/vev/internal/usecase/keys"22 "github.com/bnema/vev/internal/usecase/keys"
23 "github.com/bnema/vev/internal/usecase/layout"23 "github.com/bnema/vev/internal/usecase/layout"
@@ -196,8 +196,8 @@ func TestSwitchTabFirstFrameDoesNotReuseSamePaneIDCapture(t *testing.T) {
196 source.screen.Write([]byte("source"))196 source.screen.Write([]byte("source"))
197 source.mu.Unlock()197 source.mu.Unlock()
198 d.paint(sess, ac, true, nil)198 d.paint(sess, ac, true, nil)
199- first := awaitFrame(t, sends, ports.MsgOutput)199+ first := awaitFrame(t, sends, wire.MsgOutput)
200- firstOutput, err := ports.UnmarshalOutput(first.Payload)200+ firstOutput, err := wire.UnmarshalOutput(first.Payload)
201 require.NoError(t, err)201 require.NoError(t, err)
202 terminal := vt.NewScreen(ac.size.Cols, ac.size.Rows)202 terminal := vt.NewScreen(ac.size.Cols, ac.size.Rows)
203 terminal.Write(firstOutput.Data)203 terminal.Write(firstOutput.Data)
@@ -212,8 +212,8 @@ func TestSwitchTabFirstFrameDoesNotReuseSamePaneIDCapture(t *testing.T) {
212 daemonKeyHandler{d: d, ac: ac}.Action(keys.ActionSwitchTab2, nil)212 daemonKeyHandler{d: d, ac: ac}.Action(keys.ActionSwitchTab2, nil)
213 require.Equal(t, 1, testAttachmentTabIndex(sess))213 require.Equal(t, 1, testAttachmentTabIndex(sess))
214 214
215- second := awaitFrame(t, sends, ports.MsgOutput)215+ second := awaitFrame(t, sends, wire.MsgOutput)
216- secondOutput, err := ports.UnmarshalOutput(second.Payload)216+ secondOutput, err := wire.UnmarshalOutput(second.Payload)
217 require.NoError(t, err)217 require.NoError(t, err)
218 require.Zero(t, secondOutput.Base, "tab switch must emit the complete target frame first")218 require.Zero(t, secondOutput.Base, "tab switch must emit the complete target frame first")
219 terminal.Write(secondOutput.Data)219 terminal.Write(secondOutput.Data)
@@ -309,11 +309,11 @@ func TestAltFToggleRetainedFloatingPaneRepaintsImmediately(t *testing.T) {
309 309
310 // Establish the client shadow while the retained popup is hidden.310 // Establish the client shadow while the retained popup is hidden.
311 d.paint(sess, ac, true, nil)311 d.paint(sess, ac, true, nil)
312- mustApplyOutput(t, client, awaitFrame(t, sends, ports.MsgOutput))312+ mustApplyOutput(t, client, awaitFrame(t, sends, wire.MsgOutput))
313 313
314 // Route the real Alt+F binding. Showing a retained pane must paint once.314 // Route the real Alt+F binding. Showing a retained pane must paint once.
315 d.handleInput(sess, ac, []byte("\x1bf"))315 d.handleInput(sess, ac, []byte("\x1bf"))
316- mustApplyOutput(t, client, awaitFrame(t, sends, ports.MsgOutput))316+ mustApplyOutput(t, client, awaitFrame(t, sends, wire.MsgOutput))
317 require.Contains(t, strings.Join(frameRows(client.Frame), "\n"), "popup-content")317 require.Contains(t, strings.Join(frameRows(client.Frame), "\n"), "popup-content")
318 require.Contains(t, strings.Join(frameRows(client.Frame), "\n"), "┌")318 require.Contains(t, strings.Join(frameRows(client.Frame), "\n"), "┌")
319 select {319 select {
@@ -325,7 +325,7 @@ func TestAltFToggleRetainedFloatingPaneRepaintsImmediately(t *testing.T) {
325 // The second real key must repaint immediately, rather than waiting for325 // The second real key must repaint immediately, rather than waiting for
326 // unrelated output, while retaining the existing PTY and context.326 // unrelated output, while retaining the existing PTY and context.
327 d.handleInput(sess, ac, []byte("\x1bf"))327 d.handleInput(sess, ac, []byte("\x1bf"))
328- mustApplyOutput(t, client, awaitFrame(t, sends, ports.MsgOutput))328+ mustApplyOutput(t, client, awaitFrame(t, sends, wire.MsgOutput))
329 require.NotContains(t, strings.Join(frameRows(client.Frame), "\n"), "popup-content")329 require.NotContains(t, strings.Join(frameRows(client.Frame), "\n"), "popup-content")
330 tb := testAttachmentTab(sess)330 tb := testAttachmentTab(sess)
331 tb.mu.Lock()331 tb.mu.Lock()
@@ -343,7 +343,7 @@ func TestAltFToggleRetainedFloatingPaneRepaintsImmediately(t *testing.T) {
343 // A third show uses the installed pane; it must not launch or emit a second343 // A third show uses the installed pane; it must not launch or emit a second
344 // resize/start frame.344 // resize/start frame.
345 d.handleInput(sess, ac, []byte("\x1bf"))345 d.handleInput(sess, ac, []byte("\x1bf"))
346- mustApplyOutput(t, client, awaitFrame(t, sends, ports.MsgOutput))346+ mustApplyOutput(t, client, awaitFrame(t, sends, wire.MsgOutput))
347 require.Contains(t, strings.Join(frameRows(client.Frame), "\n"), "popup-content")347 require.Contains(t, strings.Join(frameRows(client.Frame), "\n"), "popup-content")
348 tb.mu.Lock()348 tb.mu.Lock()
349 require.Equal(t, floatingVisible, tb.floating.state)349 require.Equal(t, floatingVisible, tb.floating.state)
@@ -630,13 +630,13 @@ func TestBracketedMultilinePasteForwardsDelimitersAndNewlines(t *testing.T) {
630 func TestOverlayInputPrecedence(t *testing.T) {630 func TestOverlayInputPrecedence(t *testing.T) {
631 cases := []struct {631 cases := []struct {
632 name string632 name string
633- setup func(t *testing.T, d *Daemon, sess *session, ac *attachedClient, sends chan ports.Frame)633+ setup func(t *testing.T, d *Daemon, sess *session, ac *attachedClient, sends chan wire.Frame)
634 input []byte634 input []byte
635 check func(t *testing.T, ac *attachedClient, writes chan []byte)635 check func(t *testing.T, ac *attachedClient, writes chan []byte)
636 }{636 }{
637 {637 {
638 name: "prompt before palette picker copy and normal",638 name: "prompt before palette picker copy and normal",
639- setup: func(t *testing.T, d *Daemon, sess *session, ac *attachedClient, sends chan ports.Frame) {639+ setup: func(t *testing.T, d *Daemon, sess *session, ac *attachedClient, sends chan wire.Frame) {
640 t.Helper()640 t.Helper()
641 d.enterCopyMode(sess, ac)641 d.enterCopyMode(sess, ac)
642 d.enterPicker(sess, ac)642 d.enterPicker(sess, ac)
@@ -655,7 +655,7 @@ func TestOverlayInputPrecedence(t *testing.T) {
655 },655 },
656 {656 {
657 name: "palette before picker copy and normal",657 name: "palette before picker copy and normal",
658- setup: func(t *testing.T, d *Daemon, sess *session, ac *attachedClient, sends chan ports.Frame) {658+ setup: func(t *testing.T, d *Daemon, sess *session, ac *attachedClient, sends chan wire.Frame) {
659 t.Helper()659 t.Helper()
660 d.enterCopyMode(sess, ac)660 d.enterCopyMode(sess, ac)
661 d.enterPicker(sess, ac)661 d.enterPicker(sess, ac)
@@ -672,7 +672,7 @@ func TestOverlayInputPrecedence(t *testing.T) {
672 },672 },
673 {673 {
674 name: "picker before copy and normal",674 name: "picker before copy and normal",
675- setup: func(t *testing.T, d *Daemon, sess *session, ac *attachedClient, sends chan ports.Frame) {675+ setup: func(t *testing.T, d *Daemon, sess *session, ac *attachedClient, sends chan wire.Frame) {
676 t.Helper()676 t.Helper()
677 d.enterCopyMode(sess, ac)677 d.enterCopyMode(sess, ac)
678 d.enterPicker(sess, ac)678 d.enterPicker(sess, ac)
@@ -689,7 +689,7 @@ func TestOverlayInputPrecedence(t *testing.T) {
689 },689 },
690 {690 {
691 name: "copy before normal",691 name: "copy before normal",
692- setup: func(t *testing.T, d *Daemon, sess *session, ac *attachedClient, sends chan ports.Frame) {692+ setup: func(t *testing.T, d *Daemon, sess *session, ac *attachedClient, sends chan wire.Frame) {
693 t.Helper()693 t.Helper()
694 d.enterCopyMode(sess, ac)694 d.enterCopyMode(sess, ac)
695 },695 },
@@ -798,8 +798,8 @@ func TestPaletteBackSessionSendsClientPreviousRouteAction(t *testing.T) {
798 })798 })
799 799
800 require.NoError(t, d.backSessionForAttachment(effect))800 require.NoError(t, d.backSessionForAttachment(effect))
801- frame := awaitFrame(t, sends, ports.MsgNavigateRecentRoute)801+ frame := awaitFrame(t, sends, wire.MsgNavigateRecentRoute)
802- action, err := ports.UnmarshalRouteNavigationAction(frame.Payload)802+ action, err := wire.UnmarshalRouteNavigationAction(frame.Payload)
803 require.NoError(t, err)803 require.NoError(t, err)
804 require.Equal(t, protocol.RouteNavigationAction{SnapshotGeneration: 3, Key: 2, Generation: 2}, action)804 require.Equal(t, protocol.RouteNavigationAction{SnapshotGeneration: 3, Key: 2, Generation: 2}, action)
805 }805 }
@@ -825,7 +825,7 @@ func TestSwitchSourceDoesNotOwnPreviousRoute(t *testing.T) {
825 require.Equal(t, uint64(0), ac.routeSnapshotCopy().Generation, "route ownership starts in the client")825 require.Equal(t, uint64(0), ac.routeSnapshotCopy().Generation, "route ownership starts in the client")
826 }826 }
827 827
828-func newRecentNavigationTestSessions(t *testing.T) (*Daemon, *session, *attachedClient, chan ports.Frame, []func()) {828+func newRecentNavigationTestSessions(t *testing.T) (*Daemon, *session, *attachedClient, chan wire.Frame, []func()) {
829 t.Helper()829 t.Helper()
830 p1, release1 := newBlockingPTY(t)830 p1, release1 := newBlockingPTY(t)
831 p2, release2 := newBlockingPTY(t)831 p2, release2 := newBlockingPTY(t)
@@ -881,8 +881,8 @@ func TestAltDDetachesCurrentClient(t *testing.T) {
881 d.handleInput(sess, ac, []byte("DET\r"))881 d.handleInput(sess, ac, []byte("DET\r"))
882 882
883 require.Empty(t, sess.snapshotAttachments())883 require.Empty(t, sess.snapshotAttachments())
884- f := awaitFrame(t, sends, ports.MsgDetached)884+ f := awaitFrame(t, sends, wire.MsgDetached)
885- det, err := ports.UnmarshalDetached(f.Payload)885+ det, err := wire.UnmarshalDetached(f.Payload)
886 require.NoError(t, err)886 require.NoError(t, err)
887 require.Equal(t, protocol.ReasonDetach, det.Reason)887 require.Equal(t, protocol.ReasonDetach, det.Reason)
888 }888 }
@@ -894,13 +894,13 @@ func TestRNSOpensPromptAndEnterPromotesEphemeralSession(t *testing.T) {
894 sess.name = "0"894 sess.name = "0"
895 895
896 d.handleInput(sess, ac, []byte("\x1b "))896 d.handleInput(sess, ac, []byte("\x1b "))
897- awaitFrame(t, sends, ports.MsgOutput)897+ awaitFrame(t, sends, wire.MsgOutput)
898 d.handleInput(sess, ac, []byte("RNS\r"))898 d.handleInput(sess, ac, []byte("RNS\r"))
899- awaitFrame(t, sends, ports.MsgOutput)899+ awaitFrame(t, sends, wire.MsgOutput)
900 require.True(t, ac.overlays.promptActive())900 require.True(t, ac.overlays.promptActive())
901 901
902 d.handleInput(sess, ac, []byte("\r"))902 d.handleInput(sess, ac, []byte("\r"))
903- awaitFrame(t, sends, ports.MsgOutput)903+ awaitFrame(t, sends, wire.MsgOutput)
904 904
905 require.False(t, ac.overlays.promptActive())905 require.False(t, ac.overlays.promptActive())
906 require.False(t, sess.ephemeral)906 require.False(t, sess.ephemeral)
@@ -917,16 +917,16 @@ func TestRNTOpensPromptAndRenamesActiveTab(t *testing.T) {
917 selectTestAttachmentTab(sess, 1)917 selectTestAttachmentTab(sess, 1)
918 918
919 d.handleInput(sess, ac, []byte("\x1b "))919 d.handleInput(sess, ac, []byte("\x1b "))
920- awaitFrame(t, sends, ports.MsgOutput)920+ awaitFrame(t, sends, wire.MsgOutput)
921 d.handleInput(sess, ac, []byte("RNT\r"))921 d.handleInput(sess, ac, []byte("RNT\r"))
922- out := awaitFrame(t, sends, ports.MsgOutput)922+ out := awaitFrame(t, sends, wire.MsgOutput)
923 require.True(t, ac.overlays.promptActive())923 require.True(t, ac.overlays.promptActive())
924- msg, err := ports.UnmarshalOutput(out.Payload)924+ msg, err := wire.UnmarshalOutput(out.Payload)
925 require.NoError(t, err)925 require.NoError(t, err)
926 require.Contains(t, string(msg.Data), "> 2")926 require.Contains(t, string(msg.Data), "> 2")
927 927
928 d.handleInput(sess, ac, []byte("\x7flogs\r"))928 d.handleInput(sess, ac, []byte("\x7flogs\r"))
929- awaitFrame(t, sends, ports.MsgOutput)929+ awaitFrame(t, sends, wire.MsgOutput)
930 930
931 require.False(t, ac.overlays.promptActive())931 require.False(t, ac.overlays.promptActive())
932 require.Equal(t, "logs", sess.tabs[1].name)932 require.Equal(t, "logs", sess.tabs[1].name)
@@ -1091,9 +1091,9 @@ func TestCopyModeMouseHorizontalReverseDragUsesExactOSC52(t *testing.T) {
1091 d.handleInput(sess, ac, []byte("y"))1091 d.handleInput(sess, ac, []byte("y"))
1092 var msg protocol.Output1092 var msg protocol.Output
1093 require.Eventually(t, func() bool {1093 require.Eventually(t, func() bool {
1094- frame := awaitFrame(t, sends, ports.MsgOutput)1094+ frame := awaitFrame(t, sends, wire.MsgOutput)
1095 var err error1095 var err error
1096- msg, err = ports.UnmarshalOutput(frame.Payload)1096+ msg, err = wire.UnmarshalOutput(frame.Payload)
1097 return err == nil && string(msg.Data) == string(scopy.OSC52("lph")[0])1097 return err == nil && string(msg.Data) == string(scopy.OSC52("lph")[0])
1098 }, 2*time.Second, 5*time.Millisecond)1098 }, 2*time.Second, 5*time.Millisecond)
1099 require.Equal(t, scopy.OSC52("lph")[0], msg.Data)1099 require.Equal(t, scopy.OSC52("lph")[0], msg.Data)
@@ -1573,7 +1573,7 @@ func TestMouseGatedWhileNoticesOverlayActive(t *testing.T) {
1573 1573
1574 d.notices.record(domain.Notification{Code: domain.NoticePaneSpawn, Message: "m", Time: time.Unix(1, 0)})1574 d.notices.record(domain.Notification{Code: domain.NoticePaneSpawn, Message: "m", Time: time.Unix(1, 0)})
1575 d.enterNotices(sess, ac)1575 d.enterNotices(sess, ac)
1576- awaitFrame(t, sends, ports.MsgOutput)1576+ awaitFrame(t, sends, wire.MsgOutput)
1577 require.True(t, ac.overlays.noticesActive())1577 require.True(t, ac.overlays.noticesActive())
1578 1578
1579 // Same coordinates that TestMouseHitTestFocusesPaneAndTranslatesSGRColumns1579 // Same coordinates that TestMouseHitTestFocusesPaneAndTranslatesSGRColumns
M internal/usecase/daemon/notice_render_test.go +7 -7
@@ -9,24 +9,24 @@ import (
9 9
10 vt "github.com/bnema/vev-vt"10 vt "github.com/bnema/vev-vt"
11 "github.com/bnema/vev/internal/domain"11 "github.com/bnema/vev/internal/domain"
12- "github.com/bnema/vev/internal/ports"12+ "github.com/bnema/vev/internal/protocol/wire"
13 )13 )
14 14
15 // replayOutputFrames decodes and writes each MsgOutput frame's terminal bytes15 // replayOutputFrames decodes and writes each MsgOutput frame's terminal bytes
16 // into screen, growing its shadow the same way a real client would.16 // into screen, growing its shadow the same way a real client would.
17-func replayOutputFrames(t *testing.T, screen *vt.Screen, frames []ports.Frame) {17+func replayOutputFrames(t *testing.T, screen *vt.Screen, frames []wire.Frame) {
18 t.Helper()18 t.Helper()
19 for _, f := range frames {19 for _, f := range frames {
20- require.Equal(t, ports.MsgOutput, f.Type)20+ require.Equal(t, wire.MsgOutput, f.Type)
21- out, err := ports.UnmarshalOutput(f.Payload)21+ out, err := wire.UnmarshalOutput(f.Payload)
22 require.NoError(t, err)22 require.NoError(t, err)
23 screen.Write(out.Data)23 screen.Write(out.Data)
24 }24 }
25 }25 }
26 26
27 // drainAllFrames collects every frame already queued on sends without blocking.27 // drainAllFrames collects every frame already queued on sends without blocking.
28-func drainAllFrames(sends chan ports.Frame) []ports.Frame {28+func drainAllFrames(sends chan wire.Frame) []wire.Frame {
29- var out []ports.Frame29+ var out []wire.Frame
30 for {30 for {
31 select {31 select {
32 case f := <-sends:32 case f := <-sends:
@@ -96,7 +96,7 @@ func TestPaintComposesNoticeToastTopRightAndExpiresOnTTL(t *testing.T) {
96 awaitToastCount(t, ac, 0)96 awaitToastCount(t, ac, 0)
97 select {97 select {
98 case f := <-sends:98 case f := <-sends:
99- replayOutputFrames(t, screen, append([]ports.Frame{f}, drainAllFrames(sends)...))99+ replayOutputFrames(t, screen, append([]wire.Frame{f}, drainAllFrames(sends)...))
100 case <-time.After(2 * time.Second):100 case <-time.After(2 * time.Second):
101 t.Fatal("expiry did not invalidate the render")101 t.Fatal("expiry did not invalidate the render")
102 }102 }
M internal/usecase/daemon/notices_overlay_test.go +31 -31
@@ -11,7 +11,7 @@ import (
11 11
12 renderer "github.com/bnema/vev-vt"12 renderer "github.com/bnema/vev-vt"
13 "github.com/bnema/vev/internal/domain"13 "github.com/bnema/vev/internal/domain"
14- "github.com/bnema/vev/internal/ports"14+ "github.com/bnema/vev/internal/protocol/wire"
15 "github.com/bnema/vev/internal/usecase/notices"15 "github.com/bnema/vev/internal/usecase/notices"
16 themeui "github.com/bnema/vev/internal/usecase/theme"16 themeui "github.com/bnema/vev/internal/usecase/theme"
17 )17 )
@@ -47,13 +47,13 @@ func TestNoticesOpenViaPaletteCommandShowsModalWithHistory(t *testing.T) {
47 d.notices.record(domain.Notification{Code: domain.NoticePaneSpawn, Severity: domain.NoticeError, Message: "couldn't open pane", Time: time.Unix(1, 0)})47 d.notices.record(domain.Notification{Code: domain.NoticePaneSpawn, Severity: domain.NoticeError, Message: "couldn't open pane", Time: time.Unix(1, 0)})
48 48
49 d.enterPalette(sess, ac)49 d.enterPalette(sess, ac)
50- awaitFrame(t, sends, ports.MsgOutput)50+ awaitFrame(t, sends, wire.MsgOutput)
51 d.handlePaletteInput(ac, []byte("NTC\r"))51 d.handlePaletteInput(ac, []byte("NTC\r"))
52- out := awaitFrame(t, sends, ports.MsgOutput)52+ out := awaitFrame(t, sends, wire.MsgOutput)
53 53
54 require.False(t, ac.overlays.paletteActive())54 require.False(t, ac.overlays.paletteActive())
55 require.NotNil(t, ac.overlays.noticesOverlay)55 require.NotNil(t, ac.overlays.noticesOverlay)
56- msg, err := ports.UnmarshalOutput(out.Payload)56+ msg, err := wire.UnmarshalOutput(out.Payload)
57 require.NoError(t, err)57 require.NoError(t, err)
58 require.Contains(t, string(msg.Data), "Notifications")58 require.Contains(t, string(msg.Data), "Notifications")
59 require.Contains(t, string(msg.Data), "pane-spawn")59 require.Contains(t, string(msg.Data), "pane-spawn")
@@ -69,32 +69,32 @@ func TestNoticesJKNavigatesSelection(t *testing.T) {
69 d.notices.record(domain.Notification{Code: domain.NoticeInternal, Message: "third", Time: time.Unix(3, 0)})69 d.notices.record(domain.Notification{Code: domain.NoticeInternal, Message: "third", Time: time.Unix(3, 0)})
70 70
71 d.enterNotices(sess, ac)71 d.enterNotices(sess, ac)
72- awaitFrame(t, sends, ports.MsgOutput)72+ awaitFrame(t, sends, wire.MsgOutput)
73 selected, ok := ac.overlays.noticesOverlay.Selected()73 selected, ok := ac.overlays.noticesOverlay.Selected()
74 require.True(t, ok)74 require.True(t, ok)
75 require.Equal(t, "third", selected.Message)75 require.Equal(t, "third", selected.Message)
76 76
77 d.handleInput(sess, ac, []byte("j"))77 d.handleInput(sess, ac, []byte("j"))
78- awaitFrame(t, sends, ports.MsgOutput)78+ awaitFrame(t, sends, wire.MsgOutput)
79 selected, ok = ac.overlays.noticesOverlay.Selected()79 selected, ok = ac.overlays.noticesOverlay.Selected()
80 require.True(t, ok)80 require.True(t, ok)
81 require.Equal(t, "second", selected.Message)81 require.Equal(t, "second", selected.Message)
82 82
83 d.handleInput(sess, ac, []byte("j"))83 d.handleInput(sess, ac, []byte("j"))
84- awaitFrame(t, sends, ports.MsgOutput)84+ awaitFrame(t, sends, wire.MsgOutput)
85 selected, ok = ac.overlays.noticesOverlay.Selected()85 selected, ok = ac.overlays.noticesOverlay.Selected()
86 require.True(t, ok)86 require.True(t, ok)
87 require.Equal(t, "first", selected.Message)87 require.Equal(t, "first", selected.Message)
88 88
89 // One more 'j' at the bottom must clamp rather than wrap.89 // One more 'j' at the bottom must clamp rather than wrap.
90 d.handleInput(sess, ac, []byte("j"))90 d.handleInput(sess, ac, []byte("j"))
91- awaitFrame(t, sends, ports.MsgOutput)91+ awaitFrame(t, sends, wire.MsgOutput)
92 selected, ok = ac.overlays.noticesOverlay.Selected()92 selected, ok = ac.overlays.noticesOverlay.Selected()
93 require.True(t, ok)93 require.True(t, ok)
94 require.Equal(t, "first", selected.Message)94 require.Equal(t, "first", selected.Message)
95 95
96 d.handleInput(sess, ac, []byte("k"))96 d.handleInput(sess, ac, []byte("k"))
97- awaitFrame(t, sends, ports.MsgOutput)97+ awaitFrame(t, sends, wire.MsgOutput)
98 selected, ok = ac.overlays.noticesOverlay.Selected()98 selected, ok = ac.overlays.noticesOverlay.Selected()
99 require.True(t, ok)99 require.True(t, ok)
100 require.Equal(t, "second", selected.Message)100 require.Equal(t, "second", selected.Message)
@@ -109,11 +109,11 @@ func TestNoticesQAndCtrlCCloseImmediatelyAndClearState(t *testing.T) {
109 d.notices.record(domain.Notification{Code: domain.NoticePaneSpawn, Message: "m", Time: time.Unix(1, 0)})109 d.notices.record(domain.Notification{Code: domain.NoticePaneSpawn, Message: "m", Time: time.Unix(1, 0)})
110 110
111 d.enterNotices(sess, ac)111 d.enterNotices(sess, ac)
112- awaitFrame(t, sends, ports.MsgOutput)112+ awaitFrame(t, sends, wire.MsgOutput)
113 require.True(t, ac.overlays.noticesActive())113 require.True(t, ac.overlays.noticesActive())
114 114
115 d.handleInput(sess, ac, []byte{key})115 d.handleInput(sess, ac, []byte{key})
116- awaitFrame(t, sends, ports.MsgOutput)116+ awaitFrame(t, sends, wire.MsgOutput)
117 117
118 require.False(t, ac.overlays.noticesActive())118 require.False(t, ac.overlays.noticesActive())
119 require.Nil(t, ac.overlays.noticesOverlay)119 require.Nil(t, ac.overlays.noticesOverlay)
@@ -128,7 +128,7 @@ func TestNoticesLoneEscapeClosesAfterDelay(t *testing.T) {
128 d.notices.record(domain.Notification{Code: domain.NoticePaneSpawn, Message: "m", Time: time.Unix(1, 0)})128 d.notices.record(domain.Notification{Code: domain.NoticePaneSpawn, Message: "m", Time: time.Unix(1, 0)})
129 129
130 d.enterNotices(sess, ac)130 d.enterNotices(sess, ac)
131- awaitFrame(t, sends, ports.MsgOutput)131+ awaitFrame(t, sends, wire.MsgOutput)
132 132
133 clk := &signalClock{timers: make(chan *signalTimer, 1)}133 clk := &signalClock{timers: make(chan *signalTimer, 1)}
134 d.clock = clk134 d.clock = clk
@@ -138,7 +138,7 @@ func TestNoticesLoneEscapeClosesAfterDelay(t *testing.T) {
138 138
139 timer.ch <- time.Now()139 timer.ch <- time.Now()
140 require.Eventually(t, func() bool { return !ac.overlays.noticesActive() }, time.Second, 5*time.Millisecond)140 require.Eventually(t, func() bool { return !ac.overlays.noticesActive() }, time.Second, 5*time.Millisecond)
141- awaitFrame(t, sends, ports.MsgOutput)141+ awaitFrame(t, sends, wire.MsgOutput)
142 }142 }
143 143
144 func TestNoticesSplitArrowNavigatesWithoutClosing(t *testing.T) {144 func TestNoticesSplitArrowNavigatesWithoutClosing(t *testing.T) {
@@ -161,7 +161,7 @@ func TestNoticesSplitArrowNavigatesWithoutClosing(t *testing.T) {
161 d.notices.record(domain.Notification{Code: domain.NoticeTabSpawn, Message: "second", Time: time.Unix(2, 0)})161 d.notices.record(domain.Notification{Code: domain.NoticeTabSpawn, Message: "second", Time: time.Unix(2, 0)})
162 162
163 d.enterNotices(sess, ac)163 d.enterNotices(sess, ac)
164- awaitFrame(t, sends, ports.MsgOutput)164+ awaitFrame(t, sends, wire.MsgOutput)
165 for _, input := range tc.input {165 for _, input := range tc.input {
166 d.handleInput(sess, ac, input)166 d.handleInput(sess, ac, input)
167 }167 }
@@ -343,10 +343,10 @@ func TestNoticesOpenWithEmptyHistoryShowsPlaceholderWithoutPanic(t *testing.T) {
343 defer release()343 defer release()
344 344
345 require.NotPanics(t, func() { d.enterNotices(sess, ac) })345 require.NotPanics(t, func() { d.enterNotices(sess, ac) })
346- out := awaitFrame(t, sends, ports.MsgOutput)346+ out := awaitFrame(t, sends, wire.MsgOutput)
347 347
348 require.NotNil(t, ac.overlays.noticesOverlay)348 require.NotNil(t, ac.overlays.noticesOverlay)
349- msg, err := ports.UnmarshalOutput(out.Payload)349+ msg, err := wire.UnmarshalOutput(out.Payload)
350 require.NoError(t, err)350 require.NoError(t, err)
351 require.Contains(t, string(msg.Data), "Notifications")351 require.Contains(t, string(msg.Data), "Notifications")
352 require.Contains(t, string(msg.Data), "No notifications")352 require.Contains(t, string(msg.Data), "No notifications")
@@ -391,11 +391,11 @@ func TestYankLastNotificationCommandCopiesOSC52AndShowsFeedback(t *testing.T) {
391 want := noticeYankPayload(n)391 want := noticeYankPayload(n)
392 392
393 d.enterPalette(sess, ac)393 d.enterPalette(sess, ac)
394- awaitFrame(t, sends, ports.MsgOutput)394+ awaitFrame(t, sends, wire.MsgOutput)
395 d.handlePaletteInput(ac, []byte("YLN\r"))395 d.handlePaletteInput(ac, []byte("YLN\r"))
396 396
397- out := awaitFrame(t, sends, ports.MsgOutput)397+ out := awaitFrame(t, sends, wire.MsgOutput)
398- msg, err := ports.UnmarshalOutput(out.Payload)398+ msg, err := wire.UnmarshalOutput(out.Payload)
399 require.NoError(t, err)399 require.NoError(t, err)
400 payload := string(msg.Data)400 payload := string(msg.Data)
401 require.True(t, strings.HasPrefix(payload, "\x1b]52;c;"), "OSC52 payload = %q", payload)401 require.True(t, strings.HasPrefix(payload, "\x1b]52;c;"), "OSC52 payload = %q", payload)
@@ -404,8 +404,8 @@ func TestYankLastNotificationCommandCopiesOSC52AndShowsFeedback(t *testing.T) {
404 require.NoError(t, err)404 require.NoError(t, err)
405 require.Equal(t, want, string(decoded))405 require.Equal(t, want, string(decoded))
406 406
407- live := awaitFrame(t, sends, ports.MsgOutput)407+ live := awaitFrame(t, sends, wire.MsgOutput)
408- liveMsg, err := ports.UnmarshalOutput(live.Payload)408+ liveMsg, err := wire.UnmarshalOutput(live.Payload)
409 require.NoError(t, err)409 require.NoError(t, err)
410 require.Contains(t, string(liveMsg.Data), "copied notification details")410 require.Contains(t, string(liveMsg.Data), "copied notification details")
411 }411 }
@@ -416,11 +416,11 @@ func TestYankLastNotificationWithEmptyHistoryShowsWarnToast(t *testing.T) {
416 defer release()416 defer release()
417 417
418 d.enterPalette(sess, ac)418 d.enterPalette(sess, ac)
419- awaitFrame(t, sends, ports.MsgOutput)419+ awaitFrame(t, sends, wire.MsgOutput)
420 d.handlePaletteInput(ac, []byte("YLN\r"))420 d.handlePaletteInput(ac, []byte("YLN\r"))
421 421
422- out := awaitFrame(t, sends, ports.MsgOutput)422+ out := awaitFrame(t, sends, wire.MsgOutput)
423- msg, err := ports.UnmarshalOutput(out.Payload)423+ msg, err := wire.UnmarshalOutput(out.Payload)
424 require.NoError(t, err)424 require.NoError(t, err)
425 require.Contains(t, string(msg.Data), "no notifications yet")425 require.Contains(t, string(msg.Data), "no notifications yet")
426 426
@@ -439,7 +439,7 @@ func TestNoticesYKeyYanksSelectedNotification(t *testing.T) {
439 d.notices.record(domain.Notification{Code: domain.NoticeTabSpawn, Severity: domain.NoticeError, Message: "second", Time: time.Unix(2, 0)})439 d.notices.record(domain.Notification{Code: domain.NoticeTabSpawn, Severity: domain.NoticeError, Message: "second", Time: time.Unix(2, 0)})
440 440
441 d.enterNotices(sess, ac)441 d.enterNotices(sess, ac)
442- awaitFrame(t, sends, ports.MsgOutput)442+ awaitFrame(t, sends, wire.MsgOutput)
443 selected, ok := ac.overlays.noticesOverlay.Selected()443 selected, ok := ac.overlays.noticesOverlay.Selected()
444 require.True(t, ok)444 require.True(t, ok)
445 require.Equal(t, "second", selected.Message)445 require.Equal(t, "second", selected.Message)
@@ -447,8 +447,8 @@ func TestNoticesYKeyYanksSelectedNotification(t *testing.T) {
447 447
448 d.handleInput(sess, ac, []byte("y"))448 d.handleInput(sess, ac, []byte("y"))
449 449
450- out := awaitFrame(t, sends, ports.MsgOutput)450+ out := awaitFrame(t, sends, wire.MsgOutput)
451- msg, err := ports.UnmarshalOutput(out.Payload)451+ msg, err := wire.UnmarshalOutput(out.Payload)
452 require.NoError(t, err)452 require.NoError(t, err)
453 payload := string(msg.Data)453 payload := string(msg.Data)
454 require.True(t, strings.HasPrefix(payload, "\x1b]52;c;"), "OSC52 payload = %q", payload)454 require.True(t, strings.HasPrefix(payload, "\x1b]52;c;"), "OSC52 payload = %q", payload)
@@ -481,11 +481,11 @@ func TestNoticesBatchedYankCapturesSelectionAtY(t *testing.T) {
481 d.notices.record(domain.Notification{Code: domain.NoticeInternal, Message: "third", Time: time.Unix(3, 0)})481 d.notices.record(domain.Notification{Code: domain.NoticeInternal, Message: "third", Time: time.Unix(3, 0)})
482 482
483 d.enterNotices(sess, ac)483 d.enterNotices(sess, ac)
484- awaitFrame(t, sends, ports.MsgOutput)484+ awaitFrame(t, sends, wire.MsgOutput)
485 d.handleInput(sess, ac, tt.input)485 d.handleInput(sess, ac, tt.input)
486 486
487- out := awaitFrame(t, sends, ports.MsgOutput)487+ out := awaitFrame(t, sends, wire.MsgOutput)
488- msg, err := ports.UnmarshalOutput(out.Payload)488+ msg, err := wire.UnmarshalOutput(out.Payload)
489 require.NoError(t, err)489 require.NoError(t, err)
490 payload := strings.TrimSuffix(strings.TrimPrefix(string(msg.Data), "\x1b]52;c;"), "\a")490 payload := strings.TrimSuffix(strings.TrimPrefix(string(msg.Data), "\x1b]52;c;"), "\a")
491 decoded, err := base64.StdEncoding.DecodeString(payload)491 decoded, err := base64.StdEncoding.DecodeString(payload)
@@ -505,7 +505,7 @@ func TestNoticesYKeyWithNoSelectionIsNoOp(t *testing.T) {
505 defer release()505 defer release()
506 506
507 require.NotPanics(t, func() { d.enterNotices(sess, ac) })507 require.NotPanics(t, func() { d.enterNotices(sess, ac) })
508- awaitFrame(t, sends, ports.MsgOutput)508+ awaitFrame(t, sends, wire.MsgOutput)
509 509
510 require.NotPanics(t, func() { d.handleInput(sess, ac, []byte("y")) })510 require.NotPanics(t, func() { d.handleInput(sess, ac, []byte("y")) })
511 require.True(t, ac.overlays.noticesActive())511 require.True(t, ac.overlays.noticesActive())
M internal/usecase/daemon/notify_test.go +10 -9
@@ -17,6 +17,7 @@ import (
17 "github.com/bnema/vev/internal/ports"17 "github.com/bnema/vev/internal/ports"
18 portsmocks "github.com/bnema/vev/internal/ports/mocks"18 portsmocks "github.com/bnema/vev/internal/ports/mocks"
19 "github.com/bnema/vev/internal/protocol"19 "github.com/bnema/vev/internal/protocol"
20+ "github.com/bnema/vev/internal/protocol/wire"
20 )21 )
21 22
22 func TestReportAttachmentErrorPreservesLocalOrNilScope(t *testing.T) {23 func TestReportAttachmentErrorPreservesLocalOrNilScope(t *testing.T) {
@@ -56,11 +57,11 @@ func TestMalformedClientNoticeIsIgnored(t *testing.T) {
56 d.attachCoordinator(sess, nil, ac, true)57 d.attachCoordinator(sess, nil, ac, true)
57 tr, ok := ac.tr.(*portsmocks.MockTransport)58 tr, ok := ac.tr.(*portsmocks.MockTransport)
58 require.True(t, ok, "attached client transport must be a MockTransport")59 require.True(t, ok, "attached client transport must be a MockTransport")
59- frames := []ports.Frame{60+ frames := []wire.Frame{
60- {Type: ports.MsgClientNotice, Payload: []byte{0xff}},61+ {Type: wire.MsgClientNotice, Payload: []byte{0xff}},
61- {Type: ports.MsgDetach, Payload: ports.MarshalDetach(protocol.Detach{})},62+ {Type: wire.MsgDetach, Payload: wire.MarshalDetach(protocol.Detach{})},
62 }63 }
63- tr.EXPECT().Recv().RunAndReturn(func() (ports.Frame, error) {64+ tr.EXPECT().Recv().RunAndReturn(func() (wire.Frame, error) {
64 frame := frames[0]65 frame := frames[0]
65 frames = frames[1:]66 frames = frames[1:]
66 return frame, nil67 return frame, nil
@@ -268,7 +269,7 @@ func (c *noticeClock) durations() []time.Duration {
268 // caller-supplied clock so toast TTLs are deterministic. The session has no269 // caller-supplied clock so toast TTLs are deterministic. The session has no
269 // render coordinator, so invalidateRender paints directly and every repaint is270 // render coordinator, so invalidateRender paints directly and every repaint is
270 // observable as a frame on the returned channel.271 // observable as a frame on the returned channel.
271-func newNoticeFixture(t *testing.T, clk ports.Clock) (*Daemon, *session, *attachedClient, chan ports.Frame) {272+func newNoticeFixture(t *testing.T, clk ports.Clock) (*Daemon, *session, *attachedClient, chan wire.Frame) {
272 t.Helper()273 t.Helper()
273 p, _ := newBlockingPTY(t)274 p, _ := newBlockingPTY(t)
274 d := newTestDaemon(t, nil, clk)275 d := newTestDaemon(t, nil, clk)
@@ -864,8 +865,8 @@ func TestToastExpiresOnFakeClock(t *testing.T) {
864 865
865 d.notify(sess, domain.NoticeError, domain.NoticePaneSpawn, "could not open pane", nil)866 d.notify(sess, domain.NoticeError, domain.NoticePaneSpawn, "could not open pane", nil)
866 awaitToastCount(t, ac, 1)867 awaitToastCount(t, ac, 1)
867- shown := awaitFrame(t, sends, ports.MsgOutput)868+ shown := awaitFrame(t, sends, wire.MsgOutput)
868- shownOutput, err := ports.UnmarshalOutput(shown.Payload)869+ shownOutput, err := wire.UnmarshalOutput(shown.Payload)
869 require.NoError(t, err)870 require.NoError(t, err)
870 terminal := vt.NewScreen(80, 25)871 terminal := vt.NewScreen(80, 25)
871 terminal.Write(shownOutput.Data)872 terminal.Write(shownOutput.Data)
@@ -880,8 +881,8 @@ func TestToastExpiresOnFakeClock(t *testing.T) {
880 _, overflow := visibleToasts(ac)881 _, overflow := visibleToasts(ac)
881 require.Zero(t, overflow, "overflow resets once no toast is visible")882 require.Zero(t, overflow, "overflow resets once no toast is visible")
882 883
883- expired := awaitFrame(t, sends, ports.MsgOutput)884+ expired := awaitFrame(t, sends, wire.MsgOutput)
884- expiredOutput, err := ports.UnmarshalOutput(expired.Payload)885+ expiredOutput, err := wire.UnmarshalOutput(expired.Payload)
885 require.NoError(t, err)886 require.NoError(t, err)
886 terminal.Write(expiredOutput.Data)887 terminal.Write(expiredOutput.Data)
887 ac.sendMu.Lock()888 ac.sendMu.Lock()
M internal/usecase/daemon/output_state_sync_test.go +37 -36
@@ -13,36 +13,37 @@ import (
13 "github.com/bnema/vev/internal/domain"13 "github.com/bnema/vev/internal/domain"
14 "github.com/bnema/vev/internal/ports"14 "github.com/bnema/vev/internal/ports"
15 "github.com/bnema/vev/internal/protocol"15 "github.com/bnema/vev/internal/protocol"
16+ "github.com/bnema/vev/internal/protocol/wire"
16 "github.com/stretchr/testify/require"17 "github.com/stretchr/testify/require"
17 )18 )
18 19
19 type datagramTestTransport struct {20 type datagramTestTransport struct {
20- sends chan ports.Frame21+ sends chan wire.Frame
21- recv chan ports.Frame22+ recv chan wire.Frame
22 once sync.Once23 once sync.Once
23 }24 }
24 25
25 type failingOutputTransport struct{}26 type failingOutputTransport struct{}
26 27
27-func (failingOutputTransport) Send(ports.Frame) error { return errors.New("send failed") }28+func (failingOutputTransport) Send(wire.Frame) error { return errors.New("send failed") }
28-func (failingOutputTransport) Recv() (ports.Frame, error) { return ports.Frame{}, io.EOF }29+func (failingOutputTransport) Recv() (wire.Frame, error) { return wire.Frame{}, io.EOF }
29-func (failingOutputTransport) Close() error { return nil }30+func (failingOutputTransport) Close() error { return nil }
30 31
31 type asyncPaintTransport struct {32 type asyncPaintTransport struct {
32- syncSends chan ports.Frame33+ syncSends chan wire.Frame
33- asyncSends chan ports.Frame34+ asyncSends chan wire.Frame
34 }35 }
35 36
36-func (t *asyncPaintTransport) Send(f ports.Frame) error { t.syncSends <- f; return nil }37+func (t *asyncPaintTransport) Send(f wire.Frame) error { t.syncSends <- f; return nil }
37-func (t *asyncPaintTransport) SendAsync(f ports.Frame) error { t.asyncSends <- f; return nil }38+func (t *asyncPaintTransport) SendAsync(f wire.Frame) error { t.asyncSends <- f; return nil }
38-func (t *asyncPaintTransport) Recv() (ports.Frame, error) { return ports.Frame{}, io.EOF }39+func (t *asyncPaintTransport) Recv() (wire.Frame, error) { return wire.Frame{}, io.EOF }
39-func (t *asyncPaintTransport) Close() error { return nil }40+func (t *asyncPaintTransport) Close() error { return nil }
40 41
41 type timedSideEffectTransport struct {42 type timedSideEffectTransport struct {
42 closeTrackingTransport43 closeTrackingTransport
43 }44 }
44 45
45-func (t *timedSideEffectTransport) SendSynchronous(f ports.Frame) error {46+func (t *timedSideEffectTransport) SendSynchronous(f wire.Frame) error {
46 return t.Send(f)47 return t.Send(f)
47 }48 }
48 49
@@ -54,14 +55,14 @@ func (noWatchdogClock) NewTimer(time.Duration) ports.Timer {
54 }55 }
55 56
56 func newDatagramTestTransport() *datagramTestTransport {57 func newDatagramTestTransport() *datagramTestTransport {
57- return &datagramTestTransport{sends: make(chan ports.Frame, 64), recv: make(chan ports.Frame, 64)}58+ return &datagramTestTransport{sends: make(chan wire.Frame, 64), recv: make(chan wire.Frame, 64)}
58 }59 }
59 60
60-func (t *datagramTestTransport) Send(f ports.Frame) error { t.sends <- f; return nil }61+func (t *datagramTestTransport) Send(f wire.Frame) error { t.sends <- f; return nil }
61-func (t *datagramTestTransport) Recv() (ports.Frame, error) {62+func (t *datagramTestTransport) Recv() (wire.Frame, error) {
62 f, ok := <-t.recv63 f, ok := <-t.recv
63 if !ok {64 if !ok {
64- return ports.Frame{}, io.EOF65+ return wire.Frame{}, io.EOF
65 }66 }
66 return f, nil67 return f, nil
67 }68 }
@@ -90,20 +91,20 @@ func TestDatagramAttachPipelinesRendererBeforeAck(t *testing.T) {
90 91
91 sess.tabs[0].focusedPane().screen.Write([]byte("A"))92 sess.tabs[0].focusedPane().screen.Write([]byte("A"))
92 d.paint(sess, ac, false, nil)93 d.paint(sess, ac, false, nil)
93- first := awaitFrame(t, tr.sends, ports.MsgOutput)94+ first := awaitFrame(t, tr.sends, wire.MsgOutput)
94- out, err := ports.UnmarshalOutput(first.Payload)95+ out, err := wire.UnmarshalOutput(first.Payload)
95 require.NoError(t, err)96 require.NoError(t, err)
96 require.Equal(t, uint64(1), out.New)97 require.Equal(t, uint64(1), out.New)
97 98
98 sess.tabs[0].focusedPane().screen.Write([]byte("\rB"))99 sess.tabs[0].focusedPane().screen.Write([]byte("\rB"))
99 d.paint(sess, ac, false, nil)100 d.paint(sess, ac, false, nil)
100- updated := awaitFrame(t, tr.sends, ports.MsgOutput)101+ updated := awaitFrame(t, tr.sends, wire.MsgOutput)
101- out, err = ports.UnmarshalOutput(updated.Payload)102+ out, err = wire.UnmarshalOutput(updated.Payload)
102 require.NoError(t, err)103 require.NoError(t, err)
103 require.Equal(t, uint64(2), out.New)104 require.Equal(t, uint64(2), out.New)
104 // Both states were emitted before the MsgAck; the ordered dependency chain105 // Both states were emitted before the MsgAck; the ordered dependency chain
105 // must therefore remain valid without waiting for renderer acknowledgement.106 // must therefore remain valid without waiting for renderer acknowledgement.
106- tr.recv <- ports.Frame{Type: ports.MsgAck, Payload: mustMarshalAck(protocol.Ack{Epoch: out.Epoch, State: out.New})}107+ tr.recv <- wire.Frame{Type: wire.MsgAck, Payload: mustMarshalAck(protocol.Ack{Epoch: out.Epoch, State: out.New})}
107 require.NoError(t, tr.Close())108 require.NoError(t, tr.Close())
108 d.runConnLoop(ac)109 d.runConnLoop(ac)
109 110
@@ -117,13 +118,13 @@ func TestPaintExplicitlyUsesAsyncTransportCapability(t *testing.T) {
117 p, releasePTY := newBlockingPTY(t)118 p, releasePTY := newBlockingPTY(t)
118 defer releasePTY()119 defer releasePTY()
119 d, sess, ac, _ := newManualSessionWithPTYs(t, p)120 d, sess, ac, _ := newManualSessionWithPTYs(t, p)
120- tr := &asyncPaintTransport{syncSends: make(chan ports.Frame, 1), asyncSends: make(chan ports.Frame, 1)}121+ tr := &asyncPaintTransport{syncSends: make(chan wire.Frame, 1), asyncSends: make(chan wire.Frame, 1)}
121 ac.replaceTransport(tr)122 ac.replaceTransport(tr)
122 sess.tabs[0].focusedPane().screen.Write([]byte("A"))123 sess.tabs[0].focusedPane().screen.Write([]byte("A"))
123 124
124 d.paint(sess, ac, false, nil)125 d.paint(sess, ac, false, nil)
125 126
126- awaitFrame(t, tr.asyncSends, ports.MsgOutput)127+ awaitFrame(t, tr.asyncSends, wire.MsgOutput)
127 select {128 select {
128 case frame := <-tr.syncSends:129 case frame := <-tr.syncSends:
129 t.Fatalf("paint used synchronous Send: %+v", frame)130 t.Fatalf("paint used synchronous Send: %+v", frame)
@@ -155,7 +156,7 @@ func TestDatagramMultipleUnackedScrollPaintsMatchLatestFrame(t *testing.T) {
155 pane.screen.ClearDamage()156 pane.screen.ClearDamage()
156 client := vt.NewScreen(80, 25)157 client := vt.NewScreen(80, 25)
157 d.paint(sess, ac, true, nil)158 d.paint(sess, ac, true, nil)
158- first := mustApplyOutput(t, client, awaitFrame(t, tr.sends, ports.MsgOutput))159+ first := mustApplyOutput(t, client, awaitFrame(t, tr.sends, wire.MsgOutput))
159 ac.ackOutputState(first.Epoch, first.New)160 ac.ackOutputState(first.Epoch, first.New)
160 161
161 // Preserve the frame after one scroll while inducing a real VT scroll162 // Preserve the frame after one scroll while inducing a real VT scroll
@@ -175,7 +176,7 @@ func TestDatagramMultipleUnackedScrollPaintsMatchLatestFrame(t *testing.T) {
175 pane.screen.Write([]byte("\nq"))176 pane.screen.Write([]byte("\nq"))
176 pane.screen.Frame = desired.Clone()177 pane.screen.Frame = desired.Clone()
177 d.paint(sess, ac, false, nil)178 d.paint(sess, ac, false, nil)
178- mustApplyOutput(t, client, awaitFrame(t, tr.sends, ports.MsgOutput))179+ mustApplyOutput(t, client, awaitFrame(t, tr.sends, wire.MsgOutput))
179 }180 }
180 181
181 require.Equal(t, 'B', client.Frame.At(0, 1).Rune,182 require.Equal(t, 'B', client.Frame.At(0, 1).Rune,
@@ -198,9 +199,9 @@ func outputStateRow(row []renderer.Cell) string {
198 return string(runes)199 return string(runes)
199 }200 }
200 201
201-func mustApplyOutput(t *testing.T, screen *vt.Screen, frame ports.Frame) protocol.Output {202+func mustApplyOutput(t *testing.T, screen *vt.Screen, frame wire.Frame) protocol.Output {
202 t.Helper()203 t.Helper()
203- out, err := ports.UnmarshalOutput(frame.Payload)204+ out, err := wire.UnmarshalOutput(frame.Payload)
204 require.NoError(t, err)205 require.NoError(t, err)
205 screen.Write(out.Data)206 screen.Write(out.Data)
206 return out207 return out
@@ -213,7 +214,7 @@ func TestLocalAttachStillAdvancesRendererOnSend(t *testing.T) {
213 214
214 sess.tabs[0].focusedPane().screen.Write([]byte("A"))215 sess.tabs[0].focusedPane().screen.Write([]byte("A"))
215 d.paint(sess, ac, false, nil)216 d.paint(sess, ac, false, nil)
216- awaitFrame(t, sends, ports.MsgOutput)217+ awaitFrame(t, sends, wire.MsgOutput)
217 218
218 // The first paint updates the renderer shadow without waiting for MsgAck.219 // The first paint updates the renderer shadow without waiting for MsgAck.
219 d.paint(sess, ac, false, nil)220 d.paint(sess, ac, false, nil)
@@ -228,13 +229,13 @@ func TestLocalOutputAckDoesNotMoveRendererShadowBackward(t *testing.T) {
228 229
229 pane.screen.Write([]byte("A"))230 pane.screen.Write([]byte("A"))
230 d.paint(sess, ac, false, nil)231 d.paint(sess, ac, false, nil)
231- first := awaitFrame(t, sends, ports.MsgOutput)232+ first := awaitFrame(t, sends, wire.MsgOutput)
232- out, err := ports.UnmarshalOutput(first.Payload)233+ out, err := wire.UnmarshalOutput(first.Payload)
233 require.NoError(t, err)234 require.NoError(t, err)
234 235
235 pane.screen.Write([]byte("\rB"))236 pane.screen.Write([]byte("\rB"))
236 d.paint(sess, ac, false, nil)237 d.paint(sess, ac, false, nil)
237- awaitFrame(t, sends, ports.MsgOutput)238+ awaitFrame(t, sends, wire.MsgOutput)
238 239
239 ac.ackOutputState(out.Epoch, out.New)240 ac.ackOutputState(out.Epoch, out.New)
240 d.paint(sess, ac, false, nil)241 d.paint(sess, ac, false, nil)
@@ -252,7 +253,7 @@ func TestRawTerminalSideEffectDoesNotEnterFullOutputWindow(t *testing.T) {
252 require.Equal(t, uint64(maxUnackedOutputStates), ac.output.outstanding())253 require.Equal(t, uint64(maxUnackedOutputStates), ac.output.outstanding())
253 sends := tr.Sends()254 sends := tr.Sends()
254 require.Len(t, sends, 1)255 require.Len(t, sends, 1)
255- out, err := ports.UnmarshalOutput(sends[0].Payload)256+ out, err := wire.UnmarshalOutput(sends[0].Payload)
256 require.NoError(t, err)257 require.NoError(t, err)
257 require.Zero(t, out.Base)258 require.Zero(t, out.Base)
258 require.Zero(t, out.New)259 require.Zero(t, out.New)
@@ -304,7 +305,7 @@ func TestResizeGrowthFirstFrameIncludesConcurrentPTYRedraw(t *testing.T) {
304 pane.screen.Write([]byte(strings.Repeat("A", 79)))305 pane.screen.Write([]byte(strings.Repeat("A", 79)))
305 d.paint(sess, ac, true, nil)306 d.paint(sess, ac, true, nil)
306 client := vt.NewScreen(80, 24)307 client := vt.NewScreen(80, 24)
307- initial := mustApplyOutput(t, client, awaitFrame(t, sends, ports.MsgOutput))308+ initial := mustApplyOutput(t, client, awaitFrame(t, sends, wire.MsgOutput))
308 ac.ackOutputState(initial.Epoch, initial.New)309 ac.ackOutputState(initial.Epoch, initial.New)
309 310
310 d.sessWg.Add(1)311 d.sessWg.Add(1)
@@ -325,7 +326,7 @@ func TestResizeGrowthFirstFrameIncludesConcurrentPTYRedraw(t *testing.T) {
325 require.Equal(t, 'B', pane.screen.Frame.At(100, 0).Rune, "replay must parse the redraw before commit")326 require.Equal(t, 'B', pane.screen.Frame.At(100, 0).Rune, "replay must parse the redraw before commit")
326 327
327 client.Resize(120, 24)328 client.Resize(120, 24)
328- mustApplyOutput(t, client, awaitFrame(t, sends, ports.MsgOutput))329+ mustApplyOutput(t, client, awaitFrame(t, sends, wire.MsgOutput))
329 require.Equal(t, 'B', client.Frame.At(100, 1).Rune,330 require.Equal(t, 'B', client.Frame.At(100, 1).Rune,
330 "first grown frame exposed stale pre-SIGWINCH pane content")331 "first grown frame exposed stale pre-SIGWINCH pane content")
331 select {332 select {
@@ -356,7 +357,7 @@ func TestResizeWithoutPTYOutputFlushesOneFullFrameAtDeadline(t *testing.T) {
356 done := captureResizeCallbackDone(t, sess.renderCoordinator())357 done := captureResizeCallbackDone(t, sess.renderCoordinator())
357 timer.ch <- time.Now()358 timer.ch <- time.Now()
358 awaitTestCompletion(t, done, "resize callback did not complete")359 awaitTestCompletion(t, done, "resize callback did not complete")
359- frame := awaitFrame(t, sends, ports.MsgOutput)360+ frame := awaitFrame(t, sends, wire.MsgOutput)
360 client := vt.NewScreen(120, 24)361 client := vt.NewScreen(120, 24)
361 out := mustApplyOutput(t, client, frame)362 out := mustApplyOutput(t, client, frame)
362 require.Zero(t, out.Base)363 require.Zero(t, out.Base)
@@ -395,7 +396,7 @@ func TestResizeBurstFlushesOnlyLatestGeometry(t *testing.T) {
395 requireNoOutputFrame(t, sends)396 requireNoOutputFrame(t, sends)
396 latest.ch <- time.Now()397 latest.ch <- time.Now()
397 awaitTestCompletion(t, latestDone, "latest resize callback did not complete")398 awaitTestCompletion(t, latestDone, "latest resize callback did not complete")
398- awaitFrame(t, sends, ports.MsgOutput)399+ awaitFrame(t, sends, wire.MsgOutput)
399 require.Equal(t, domain.Size{Cols: 120, Rows: 24}, ac.size)400 require.Equal(t, domain.Size{Cols: 120, Rows: 24}, ac.size)
400 require.Equal(t, 120, testAttachmentTab(sess).focusedPane().screen.Frame.Width)401 require.Equal(t, 120, testAttachmentTab(sess).focusedPane().screen.Frame.Width)
401 requireNoOutputFrame(t, sends)402 requireNoOutputFrame(t, sends)
M internal/usecase/daemon/paint_gate_test.go +3 -3
@@ -4,16 +4,16 @@ import (
4 "testing"4 "testing"
5 "time"5 "time"
6 6
7- "github.com/bnema/vev/internal/ports"7+ "github.com/bnema/vev/internal/protocol/wire"
8 )8 )
9 9
10-func requireNoOutputFrame(t *testing.T, sends chan ports.Frame) {10+func requireNoOutputFrame(t *testing.T, sends chan wire.Frame) {
11 t.Helper()11 t.Helper()
12 deadline := time.After(50 * time.Millisecond)12 deadline := time.After(50 * time.Millisecond)
13 for {13 for {
14 select {14 select {
15 case f := <-sends:15 case f := <-sends:
16- if f.Type == ports.MsgOutput {16+ if f.Type == wire.MsgOutput {
17 t.Fatalf("unexpected output frame: %+v", f)17 t.Fatalf("unexpected output frame: %+v", f)
18 }18 }
19 case <-deadline:19 case <-deadline:
M internal/usecase/daemon/palette_test.go +80 -79
@@ -14,6 +14,7 @@ import (
14 "github.com/bnema/vev/internal/ports"14 "github.com/bnema/vev/internal/ports"
15 portsmocks "github.com/bnema/vev/internal/ports/mocks"15 portsmocks "github.com/bnema/vev/internal/ports/mocks"
16 "github.com/bnema/vev/internal/protocol"16 "github.com/bnema/vev/internal/protocol"
17+ "github.com/bnema/vev/internal/protocol/wire"
17 "github.com/bnema/vev/internal/usecase/command"18 "github.com/bnema/vev/internal/usecase/command"
18 "github.com/bnema/vev/internal/usecase/palette"19 "github.com/bnema/vev/internal/usecase/palette"
19 "github.com/bnema/vev/internal/usecase/picker"20 "github.com/bnema/vev/internal/usecase/picker"
@@ -130,8 +131,8 @@ func TestPaletteOpenTypeEnterRunAndEscClose(t *testing.T) {
130 131
131 d.handleInput(sess, ac, []byte("\x1b "))132 d.handleInput(sess, ac, []byte("\x1b "))
132 require.True(t, ac.overlays.paletteActive())133 require.True(t, ac.overlays.paletteActive())
133- out := awaitFrame(t, sends, ports.MsgOutput)134+ out := awaitFrame(t, sends, wire.MsgOutput)
134- msg, err := ports.UnmarshalOutput(out.Payload)135+ msg, err := wire.UnmarshalOutput(out.Payload)
135 require.NoError(t, err)136 require.NoError(t, err)
136 require.Contains(t, string(msg.Data), "Commands")137 require.Contains(t, string(msg.Data), "Commands")
137 138
@@ -139,13 +140,13 @@ func TestPaletteOpenTypeEnterRunAndEscClose(t *testing.T) {
139 require.False(t, ac.overlays.paletteActive())140 require.False(t, ac.overlays.paletteActive())
140 require.Equal(t, 1, testAttachmentTabIndex(sess))141 require.Equal(t, 1, testAttachmentTabIndex(sess))
141 requireFloatingInitialized(t, testAttachmentTab(sess))142 requireFloatingInitialized(t, testAttachmentTab(sess))
142- awaitFrame(t, sends, ports.MsgOutput)143+ awaitFrame(t, sends, wire.MsgOutput)
143 144
144 d.handleInput(sess, ac, []byte("\x1b "))145 d.handleInput(sess, ac, []byte("\x1b "))
145- awaitFrame(t, sends, ports.MsgOutput)146+ awaitFrame(t, sends, wire.MsgOutput)
146 d.handleInput(sess, ac, []byte("\x1b"))147 d.handleInput(sess, ac, []byte("\x1b"))
147 require.False(t, ac.overlays.paletteActive())148 require.False(t, ac.overlays.paletteActive())
148- awaitFrame(t, sends, ports.MsgOutput)149+ awaitFrame(t, sends, wire.MsgOutput)
149 }150 }
150 151
151 // TestPaletteCommandFailureSurfacesAsNotice drives a palette command whose Run152 // TestPaletteCommandFailureSurfacesAsNotice drives a palette command whose Run
@@ -162,9 +163,9 @@ func TestPaletteCommandFailureSurfacesAsNotice(t *testing.T) {
162 d.ptys = ptys163 d.ptys = ptys
163 164
164 d.handleInput(sess, ac, []byte("\x1b "))165 d.handleInput(sess, ac, []byte("\x1b "))
165- awaitFrame(t, sends, ports.MsgOutput)166+ awaitFrame(t, sends, wire.MsgOutput)
166 d.handleInput(sess, ac, []byte("CNT\r"))167 d.handleInput(sess, ac, []byte("CNT\r"))
167- awaitFrame(t, sends, ports.MsgOutput)168+ awaitFrame(t, sends, wire.MsgOutput)
168 169
169 history := d.notices.history()170 history := d.notices.history()
170 require.NotEmpty(t, history, "failed palette command must record a notice")171 require.NotEmpty(t, history, "failed palette command must record a notice")
@@ -291,13 +292,13 @@ func TestPaletteEnterFreezesActiveSessionSelectionAgainstTrailingInput(t *testin
291 target.createdAt = 42292 target.createdAt = 42
292 293
293 d.handleInput(current, ac, []byte("\x1b "))294 d.handleInput(current, ac, []byte("\x1b "))
294- awaitFrame(t, sends, ports.MsgOutput)295+ awaitFrame(t, sends, wire.MsgOutput)
295 d.handleInput(current, ac, []byte("recent\rx"))296 d.handleInput(current, ac, []byte("recent\rx"))
296 297
297 require.Same(t, target, ac.currentSession(), "Enter must retain the selected session despite trailing frame bytes")298 require.Same(t, target, ac.currentSession(), "Enter must retain the selected session despite trailing frame bytes")
298 require.False(t, ac.overlays.paletteActive(), "trailing input must not prevent the captured selection from closing the palette")299 require.False(t, ac.overlays.paletteActive(), "trailing input must not prevent the captured selection from closing the palette")
299- awaitFrame(t, sends, ports.MsgOutput)300+ awaitFrame(t, sends, wire.MsgOutput)
300- awaitFrame(t, sends, ports.MsgOutput)301+ awaitFrame(t, sends, wire.MsgOutput)
301 }302 }
302 303
303 func TestPaletteSelectedActiveSessionSwitchesWithoutRecordingCommandRecency(t *testing.T) {304 func TestPaletteSelectedActiveSessionSwitchesWithoutRecordingCommandRecency(t *testing.T) {
@@ -307,15 +308,15 @@ func TestPaletteSelectedActiveSessionSwitchesWithoutRecordingCommandRecency(t *t
307 target.createdAt = 42308 target.createdAt = 42
308 309
309 d.handleInput(current, ac, []byte("\x1b "))310 d.handleInput(current, ac, []byte("\x1b "))
310- awaitFrame(t, sends, ports.MsgOutput)311+ awaitFrame(t, sends, wire.MsgOutput)
311 d.handleInput(current, ac, []byte("recent\r"))312 d.handleInput(current, ac, []byte("recent\r"))
312 313
313 require.Same(t, target, ac.currentSession())314 require.Same(t, target, ac.currentSession())
314 require.Contains(t, target.snapshotAttachments(), ac, "canonical handoff reuses the attached client")315 require.Contains(t, target.snapshotAttachments(), ac, "canonical handoff reuses the attached client")
315 require.False(t, ac.overlays.paletteActive())316 require.False(t, ac.overlays.paletteActive())
316 require.Empty(t, d.paletteRecent, "session selections are not command recency")317 require.Empty(t, d.paletteRecent, "session selections are not command recency")
317- awaitFrame(t, sends, ports.MsgOutput)318+ awaitFrame(t, sends, wire.MsgOutput)
318- awaitFrame(t, sends, ports.MsgOutput)319+ awaitFrame(t, sends, wire.MsgOutput)
319 }320 }
320 321
321 func TestPaletteStoppedSessionResumeFailureKeepsPaletteAndSourceAttachment(t *testing.T) {322 func TestPaletteStoppedSessionResumeFailureKeepsPaletteAndSourceAttachment(t *testing.T) {
@@ -329,7 +330,7 @@ func TestPaletteStoppedSessionResumeFailureKeepsPaletteAndSourceAttachment(t *te
329 d.ptys = ptys330 d.ptys = ptys
330 331
331 d.handleInput(current, ac, []byte("\x1b "))332 d.handleInput(current, ac, []byte("\x1b "))
332- awaitFrame(t, sends, ports.MsgOutput)333+ awaitFrame(t, sends, wire.MsgOutput)
333 d.handleInput(current, ac, []byte("stopped\r"))334 d.handleInput(current, ac, []byte("stopped\r"))
334 335
335 require.Same(t, current, ac.currentSession(), "failed resume must retain the source attachment")336 require.Same(t, current, ac.currentSession(), "failed resume must retain the source attachment")
@@ -341,7 +342,7 @@ func TestPaletteStoppedSessionResumeFailureKeepsPaletteAndSourceAttachment(t *te
341 require.Equal(t, "requested session is unavailable", ac.overlays.paletteFeedback)342 require.Equal(t, "requested session is unavailable", ac.overlays.paletteFeedback)
342 ac.overlays.paletteMu.Unlock()343 ac.overlays.paletteMu.Unlock()
343 require.Equal(t, stopped, d.inactive[stopped.name], "failed resume must retain stopped lifecycle metadata")344 require.Equal(t, stopped, d.inactive[stopped.name], "failed resume must retain stopped lifecycle metadata")
344- awaitFrame(t, sends, ports.MsgOutput)345+ awaitFrame(t, sends, wire.MsgOutput)
345 }346 }
346 347
347 func TestPaletteSelectedStoppedSessionResumesAndSwitches(t *testing.T) {348 func TestPaletteSelectedStoppedSessionResumesAndSwitches(t *testing.T) {
@@ -354,7 +355,7 @@ func TestPaletteSelectedStoppedSessionResumesAndSwitches(t *testing.T) {
354 d.inactive["stopped"] = inactiveSession{name: "stopped", cwd: "/tmp", createdAt: 42, state: protocol.SessionDown}355 d.inactive["stopped"] = inactiveSession{name: "stopped", cwd: "/tmp", createdAt: 42, state: protocol.SessionDown}
355 356
356 d.handleInput(current, ac, []byte("\x1b "))357 d.handleInput(current, ac, []byte("\x1b "))
357- awaitFrame(t, sends, ports.MsgOutput)358+ awaitFrame(t, sends, wire.MsgOutput)
358 generation := ac.lifecycle.generationValue()359 generation := ac.lifecycle.generationValue()
359 d.handleInput(current, ac, []byte("stopped\r"))360 d.handleInput(current, ac, []byte("stopped\r"))
360 361
@@ -364,11 +365,11 @@ func TestPaletteSelectedStoppedSessionResumesAndSwitches(t *testing.T) {
364 require.Equal(t, true, resumed.attachmentRegistered(ac))365 require.Equal(t, true, resumed.attachmentRegistered(ac))
365 require.Greater(t, ac.lifecycle.generationValue(), generation, "stopped-session handoff must publish through the attachment transition")366 require.Greater(t, ac.lifecycle.generationValue(), generation, "stopped-session handoff must publish through the attachment transition")
366 require.False(t, ac.overlays.paletteActive())367 require.False(t, ac.overlays.paletteActive())
367- firstPaint := awaitFrame(t, sends, ports.MsgOutput)368+ firstPaint := awaitFrame(t, sends, wire.MsgOutput)
368- firstOutput, err := ports.UnmarshalOutput(firstPaint.Payload)369+ firstOutput, err := wire.UnmarshalOutput(firstPaint.Payload)
369 require.NoError(t, err)370 require.NoError(t, err)
370 require.Zero(t, firstOutput.Base, "stopped-session first paint must reset moving output state")371 require.Zero(t, firstOutput.Base, "stopped-session first paint must reset moving output state")
371- awaitFrame(t, sends, ports.MsgOutput)372+ awaitFrame(t, sends, wire.MsgOutput)
372 }373 }
373 374
374 func TestPaletteJRSActivatesOnlyExactContextualHint(t *testing.T) {375 func TestPaletteJRSActivatesOnlyExactContextualHint(t *testing.T) {
@@ -378,9 +379,9 @@ func TestPaletteJRSActivatesOnlyExactContextualHint(t *testing.T) {
378 ac.setRouteSnapshot(protocol.RecentRouteSnapshot{Generation: 1, Active: protocol.RouteRef{Key: 1, Generation: 1}})379 ac.setRouteSnapshot(protocol.RecentRouteSnapshot{Generation: 1, Active: protocol.RouteRef{Key: 1, Generation: 1}})
379 380
380 d.handleInput(sess, ac, []byte("\x1b "))381 d.handleInput(sess, ac, []byte("\x1b "))
381- awaitFrame(t, sends, ports.MsgOutput)382+ awaitFrame(t, sends, wire.MsgOutput)
382 d.handleInput(sess, ac, []byte("JRS"))383 d.handleInput(sess, ac, []byte("JRS"))
383- awaitFrame(t, sends, ports.MsgOutput)384+ awaitFrame(t, sends, wire.MsgOutput)
384 385
385 ac.overlays.paletteMu.Lock()386 ac.overlays.paletteMu.Lock()
386 require.Equal(t, command.ContextHintRecentSessions, ac.overlays.paletteHints.Kind)387 require.Equal(t, command.ContextHintRecentSessions, ac.overlays.paletteHints.Kind)
@@ -388,7 +389,7 @@ func TestPaletteJRSActivatesOnlyExactContextualHint(t *testing.T) {
388 ac.overlays.paletteMu.Unlock()389 ac.overlays.paletteMu.Unlock()
389 390
390 d.handleInput(sess, ac, []byte("\b\b\bRNS"))391 d.handleInput(sess, ac, []byte("\b\b\bRNS"))
391- awaitFrame(t, sends, ports.MsgOutput)392+ awaitFrame(t, sends, wire.MsgOutput)
392 ac.overlays.paletteMu.Lock()393 ac.overlays.paletteMu.Lock()
393 require.Equal(t, command.ContextHintNone, ac.overlays.paletteHints.Kind)394 require.Equal(t, command.ContextHintNone, ac.overlays.paletteHints.Kind)
394 ac.overlays.paletteMu.Unlock()395 ac.overlays.paletteMu.Unlock()
@@ -402,25 +403,25 @@ func TestPaletteJRSUsesEffectiveOverrideOnly(t *testing.T) {
402 d.ApplyConfig(domain.Config{Codes: map[string]string{"jump-recent-session": "RJS"}})403 d.ApplyConfig(domain.Config{Codes: map[string]string{"jump-recent-session": "RJS"}})
403 404
404 d.handleInputForAttachment(token, []byte("\x1b "))405 d.handleInputForAttachment(token, []byte("\x1b "))
405- awaitFrame(t, sends, ports.MsgOutput)406+ awaitFrame(t, sends, wire.MsgOutput)
406 d.handleInputForAttachment(token, []byte("RJS"))407 d.handleInputForAttachment(token, []byte("RJS"))
407- awaitFrame(t, sends, ports.MsgOutput)408+ awaitFrame(t, sends, wire.MsgOutput)
408 ac.overlays.paletteMu.Lock()409 ac.overlays.paletteMu.Lock()
409 require.Equal(t, command.ContextHintRecentSessions, ac.overlays.paletteHints.Kind)410 require.Equal(t, command.ContextHintRecentSessions, ac.overlays.paletteHints.Kind)
410 ac.overlays.paletteMu.Unlock()411 ac.overlays.paletteMu.Unlock()
411 d.handleInputForAttachment(token, []byte(" 1\r"))412 d.handleInputForAttachment(token, []byte(" 1\r"))
412 413
413- actionFrame := awaitFrame(t, sends, ports.MsgNavigateRecentRoute)414+ actionFrame := awaitFrame(t, sends, wire.MsgNavigateRecentRoute)
414- action, err := ports.UnmarshalRouteNavigationAction(actionFrame.Payload)415+ action, err := wire.UnmarshalRouteNavigationAction(actionFrame.Payload)
415 require.NoError(t, err)416 require.NoError(t, err)
416 require.Equal(t, protocol.RouteNavigationAction{SnapshotGeneration: 1, Key: 2, Generation: 1}, action)417 require.Equal(t, protocol.RouteNavigationAction{SnapshotGeneration: 1, Key: 2, Generation: 1}, action)
417 require.Same(t, current, ac.currentSession())418 require.Same(t, current, ac.currentSession())
418 require.False(t, ac.overlays.paletteActive())419 require.False(t, ac.overlays.paletteActive())
419 420
420 d.handleInputForAttachment(token, []byte("\x1b "))421 d.handleInputForAttachment(token, []byte("\x1b "))
421- awaitFrame(t, sends, ports.MsgOutput)422+ awaitFrame(t, sends, wire.MsgOutput)
422 d.handleInputForAttachment(token, []byte("JRS"))423 d.handleInputForAttachment(token, []byte("JRS"))
423- awaitFrame(t, sends, ports.MsgOutput)424+ awaitFrame(t, sends, wire.MsgOutput)
424 ac.overlays.paletteMu.Lock()425 ac.overlays.paletteMu.Lock()
425 require.Equal(t, command.ContextHintNone, ac.overlays.paletteHints.Kind)426 require.Equal(t, command.ContextHintNone, ac.overlays.paletteHints.Kind)
426 ac.overlays.paletteMu.Unlock()427 ac.overlays.paletteMu.Unlock()
@@ -588,11 +589,11 @@ func TestPaletteRemoteCatalogSelectionSendsExactAttachTarget(t *testing.T) {
588 token := beginRecentRoutePaletteEffect(t, d, current, ac)589 token := beginRecentRoutePaletteEffect(t, d, current, ac)
589 590
590 d.handleInputForAttachment(token, []byte("\x1b "))591 d.handleInputForAttachment(token, []byte("\x1b "))
591- awaitFrame(t, sends, ports.MsgOutput)592+ awaitFrame(t, sends, wire.MsgOutput)
592 d.handleInputForAttachment(token, []byte("work@arch\r"))593 d.handleInputForAttachment(token, []byte("work@arch\r"))
593 594
594- frame := awaitFrame(t, sends, ports.MsgAttachTarget)595+ frame := awaitFrame(t, sends, wire.MsgAttachTarget)
595- target, err := ports.UnmarshalAttachTarget(frame.Payload)596+ target, err := wire.UnmarshalAttachTarget(frame.Payload)
596 require.NoError(t, err)597 require.NoError(t, err)
597 remoteTarget := domain.RemoteSessionTarget{598 remoteTarget := domain.RemoteSessionTarget{
598 Endpoint: "user@arch", DisplayOrigin: "arch", LifecycleID: remoteLifecycle,599 Endpoint: "user@arch", DisplayOrigin: "arch", LifecycleID: remoteLifecycle,
@@ -627,7 +628,7 @@ func TestPaletteCachedRemoteSelectionFailsClosed(t *testing.T) {
627 token := beginRecentRoutePaletteEffect(t, d, current, ac)628 token := beginRecentRoutePaletteEffect(t, d, current, ac)
628 629
629 d.handleInputForAttachment(token, []byte("\x1b "))630 d.handleInputForAttachment(token, []byte("\x1b "))
630- awaitFrame(t, sends, ports.MsgOutput)631+ awaitFrame(t, sends, wire.MsgOutput)
631 d.handleInputForAttachment(token, []byte("cached@arch\r"))632 d.handleInputForAttachment(token, []byte("cached@arch\r"))
632 633
633 require.Same(t, current, ac.currentAttachmentSession())634 require.Same(t, current, ac.currentAttachmentSession())
@@ -639,7 +640,7 @@ func TestPaletteCachedRemoteSelectionFailsClosed(t *testing.T) {
639 for {640 for {
640 select {641 select {
641 case frame := <-sends:642 case frame := <-sends:
642- require.NotEqual(t, ports.MsgAttachTarget, frame.Type)643+ require.NotEqual(t, wire.MsgAttachTarget, frame.Type)
643 default:644 default:
644 return645 return
645 }646 }
@@ -772,7 +773,7 @@ func TestPaletteLifecycleTargetRejectsSameNameReplacement(t *testing.T) {
772 for {773 for {
773 select {774 select {
774 case frame := <-sends:775 case frame := <-sends:
775- require.NotEqual(t, ports.MsgAttachTarget, frame.Type)776+ require.NotEqual(t, wire.MsgAttachTarget, frame.Type)
776 default:777 default:
777 return778 return
778 }779 }
@@ -793,11 +794,11 @@ func TestPaletteFuzzyRemoteRecentRouteSendsExactNavigationAction(t *testing.T) {
793 token := beginRecentRoutePaletteEffect(t, d, current, ac)794 token := beginRecentRoutePaletteEffect(t, d, current, ac)
794 795
795 d.handleInputForAttachment(token, []byte("\x1b "))796 d.handleInputForAttachment(token, []byte("\x1b "))
796- awaitFrame(t, sends, ports.MsgOutput)797+ awaitFrame(t, sends, wire.MsgOutput)
797 d.handleInputForAttachment(token, []byte("logs@edge\r"))798 d.handleInputForAttachment(token, []byte("logs@edge\r"))
798 799
799- actionFrame := awaitFrame(t, sends, ports.MsgNavigateRecentRoute)800+ actionFrame := awaitFrame(t, sends, wire.MsgNavigateRecentRoute)
800- action, err := ports.UnmarshalRouteNavigationAction(actionFrame.Payload)801+ action, err := wire.UnmarshalRouteNavigationAction(actionFrame.Payload)
801 require.NoError(t, err)802 require.NoError(t, err)
802 require.Equal(t, protocol.RouteNavigationAction{SnapshotGeneration: 9, Key: 8, Generation: 4}, action)803 require.Equal(t, protocol.RouteNavigationAction{SnapshotGeneration: 9, Key: 8, Generation: 4}, action)
803 require.False(t, ac.overlays.paletteActive())804 require.False(t, ac.overlays.paletteActive())
@@ -809,12 +810,12 @@ func TestPaletteFuzzySelectedStaticCommandExecutes(t *testing.T) {
809 defer releases[1]()810 defer releases[1]()
810 811
811 d.handleInput(sess, ac, []byte("\x1b "))812 d.handleInput(sess, ac, []byte("\x1b "))
812- awaitFrame(t, sends, ports.MsgOutput)813+ awaitFrame(t, sends, wire.MsgOutput)
813 d.handleInput(sess, ac, []byte("next\r"))814 d.handleInput(sess, ac, []byte("next\r"))
814 815
815 require.False(t, ac.overlays.paletteActive())816 require.False(t, ac.overlays.paletteActive())
816 require.Equal(t, 1, testAttachmentTabIndex(sess))817 require.Equal(t, 1, testAttachmentTabIndex(sess))
817- awaitFrame(t, sends, ports.MsgOutput)818+ awaitFrame(t, sends, wire.MsgOutput)
818 }819 }
819 820
820 func TestPaletteJRSUsesCapturedRankAfterMRUChanges(t *testing.T) {821 func TestPaletteJRSUsesCapturedRankAfterMRUChanges(t *testing.T) {
@@ -823,13 +824,13 @@ func TestPaletteJRSUsesCapturedRankAfterMRUChanges(t *testing.T) {
823 ac.setRouteSnapshot(testRecentRouteSnapshot())824 ac.setRouteSnapshot(testRecentRouteSnapshot())
824 token := beginRecentRoutePaletteEffect(t, d, current, ac)825 token := beginRecentRoutePaletteEffect(t, d, current, ac)
825 d.handleInputForAttachment(token, []byte("\x1b "))826 d.handleInputForAttachment(token, []byte("\x1b "))
826- awaitFrame(t, sends, ports.MsgOutput)827+ awaitFrame(t, sends, wire.MsgOutput)
827 // Reordering live MRU after opening must not shift rank 1 from its capture.828 // Reordering live MRU after opening must not shift rank 1 from its capture.
828 d.sessions[domain.SessionID("older")].core().mruAt.Store(100)829 d.sessions[domain.SessionID("older")].core().mruAt.Store(100)
829 d.handleInputForAttachment(token, []byte("JRS 1\r"))830 d.handleInputForAttachment(token, []byte("JRS 1\r"))
830 831
831- actionFrame := awaitFrame(t, sends, ports.MsgNavigateRecentRoute)832+ actionFrame := awaitFrame(t, sends, wire.MsgNavigateRecentRoute)
832- action, err := ports.UnmarshalRouteNavigationAction(actionFrame.Payload)833+ action, err := wire.UnmarshalRouteNavigationAction(actionFrame.Payload)
833 require.NoError(t, err)834 require.NoError(t, err)
834 require.Equal(t, protocol.RouteNavigationAction{SnapshotGeneration: 1, Key: 2, Generation: 1}, action)835 require.Equal(t, protocol.RouteNavigationAction{SnapshotGeneration: 1, Key: 2, Generation: 1}, action)
835 require.Same(t, current, ac.currentSession())836 require.Same(t, current, ac.currentSession())
@@ -877,7 +878,7 @@ func TestPaletteJRSDoesNotRevalidateTargetInDaemon(t *testing.T) {
877 }878 }
878 token := beginRecentRoutePaletteEffect(t, d, sess, ac)879 token := beginRecentRoutePaletteEffect(t, d, sess, ac)
879 d.handleInputForAttachment(token, []byte("\x1b "))880 d.handleInputForAttachment(token, []byte("\x1b "))
880- awaitFrame(t, sends, ports.MsgOutput)881+ awaitFrame(t, sends, wire.MsgOutput)
881 inputHandled := make(chan struct{})882 inputHandled := make(chan struct{})
882 go func() {883 go func() {
883 d.handleInputForAttachment(token, []byte("JRS 1\r"))884 d.handleInputForAttachment(token, []byte("JRS 1\r"))
@@ -889,8 +890,8 @@ func TestPaletteJRSDoesNotRevalidateTargetInDaemon(t *testing.T) {
889 close(releaseHandoff)890 close(releaseHandoff)
890 awaitTestCompletion(t, inputHandled, "JRS route-action input did not complete")891 awaitTestCompletion(t, inputHandled, "JRS route-action input did not complete")
891 892
892- actionFrame := awaitFrame(t, sends, ports.MsgNavigateRecentRoute)893+ actionFrame := awaitFrame(t, sends, wire.MsgNavigateRecentRoute)
893- action, err := ports.UnmarshalRouteNavigationAction(actionFrame.Payload)894+ action, err := wire.UnmarshalRouteNavigationAction(actionFrame.Payload)
894 require.NoError(t, err)895 require.NoError(t, err)
895 require.Equal(t, protocol.RouteNavigationAction{SnapshotGeneration: 7, Key: 9, Generation: 4}, action)896 require.Equal(t, protocol.RouteNavigationAction{SnapshotGeneration: 7, Key: 9, Generation: 4}, action)
896 require.False(t, ac.overlays.paletteActive())897 require.False(t, ac.overlays.paletteActive())
@@ -903,7 +904,7 @@ func TestPaletteJRSOutOfRangeKeepsPaletteOpenWithoutClamping(t *testing.T) {
903 token := beginRecentRoutePaletteEffect(t, d, current, ac)904 token := beginRecentRoutePaletteEffect(t, d, current, ac)
904 905
905 d.handleInputForAttachment(token, []byte("\x1b "))906 d.handleInputForAttachment(token, []byte("\x1b "))
906- awaitFrame(t, sends, ports.MsgOutput)907+ awaitFrame(t, sends, wire.MsgOutput)
907 d.handleInputForAttachment(token, []byte("JRS 3\r"))908 d.handleInputForAttachment(token, []byte("JRS 3\r"))
908 909
909 require.Same(t, current, ac.currentSession())910 require.Same(t, current, ac.currentSession())
@@ -912,7 +913,7 @@ func TestPaletteJRSOutOfRangeKeepsPaletteOpenWithoutClamping(t *testing.T) {
912 require.Equal(t, "JRS 3", ac.overlays.palette.Query())913 require.Equal(t, "JRS 3", ac.overlays.palette.Query())
913 require.Equal(t, "requested recent session is unavailable", ac.overlays.paletteFeedback)914 require.Equal(t, "requested recent session is unavailable", ac.overlays.paletteFeedback)
914 ac.overlays.paletteMu.Unlock()915 ac.overlays.paletteMu.Unlock()
915- awaitFrame(t, sends, ports.MsgOutput)916+ awaitFrame(t, sends, wire.MsgOutput)
916 }917 }
917 918
918 func TestPaletteJRSMalformedRankFeedback(t *testing.T) {919 func TestPaletteJRSMalformedRankFeedback(t *testing.T) {
@@ -920,7 +921,7 @@ func TestPaletteJRSMalformedRankFeedback(t *testing.T) {
920 defer releaseAll(releases)921 defer releaseAll(releases)
921 922
922 d.handleInput(current, ac, []byte("\x1b "))923 d.handleInput(current, ac, []byte("\x1b "))
923- awaitFrame(t, sends, ports.MsgOutput)924+ awaitFrame(t, sends, wire.MsgOutput)
924 d.handleInput(current, ac, []byte("JRS 0\r"))925 d.handleInput(current, ac, []byte("JRS 0\r"))
925 926
926 require.Same(t, current, ac.currentSession())927 require.Same(t, current, ac.currentSession())
@@ -929,7 +930,7 @@ func TestPaletteJRSMalformedRankFeedback(t *testing.T) {
929 require.Equal(t, "JRS 0", ac.overlays.palette.Query())930 require.Equal(t, "JRS 0", ac.overlays.palette.Query())
930 require.Equal(t, "rank must be one positive decimal", ac.overlays.paletteFeedback)931 require.Equal(t, "rank must be one positive decimal", ac.overlays.paletteFeedback)
931 ac.overlays.paletteMu.Unlock()932 ac.overlays.paletteMu.Unlock()
932- awaitFrame(t, sends, ports.MsgOutput)933+ awaitFrame(t, sends, wire.MsgOutput)
933 }934 }
934 935
935 func TestPaletteFailureDoesNotOverwriteChangedQueryInSameGeneration(t *testing.T) {936 func TestPaletteFailureDoesNotOverwriteChangedQueryInSameGeneration(t *testing.T) {
@@ -964,8 +965,8 @@ func TestPaletteFailureDoesNotOverwriteNewerInteraction(t *testing.T) {
964 ac.overlays.paletteMu.Lock()965 ac.overlays.paletteMu.Lock()
965 require.Empty(t, ac.overlays.paletteFeedback)966 require.Empty(t, ac.overlays.paletteFeedback)
966 ac.overlays.paletteMu.Unlock()967 ac.overlays.paletteMu.Unlock()
967- awaitFrame(t, sends, ports.MsgOutput)968+ awaitFrame(t, sends, wire.MsgOutput)
968- awaitFrame(t, sends, ports.MsgOutput)969+ awaitFrame(t, sends, wire.MsgOutput)
969 }970 }
970 971
971 func TestPaletteTFPExecutesFloatingToggle(t *testing.T) {972 func TestPaletteTFPExecutesFloatingToggle(t *testing.T) {
@@ -976,14 +977,14 @@ func TestPaletteTFPExecutesFloatingToggle(t *testing.T) {
976 installTestFloating(tb, newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}), false)977 installTestFloating(tb, newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}), false)
977 978
978 d.handleInput(sess, ac, []byte("\x1b "))979 d.handleInput(sess, ac, []byte("\x1b "))
979- awaitFrame(t, sends, ports.MsgOutput)980+ awaitFrame(t, sends, wire.MsgOutput)
980 d.handleInput(sess, ac, []byte("TFP\r"))981 d.handleInput(sess, ac, []byte("TFP\r"))
981 982
982 require.False(t, ac.overlays.paletteActive())983 require.False(t, ac.overlays.paletteActive())
983 tb.mu.Lock()984 tb.mu.Lock()
984 require.Equal(t, floatingVisible, tb.floating.state)985 require.Equal(t, floatingVisible, tb.floating.state)
985 tb.mu.Unlock()986 tb.mu.Unlock()
986- awaitFrame(t, sends, ports.MsgOutput)987+ awaitFrame(t, sends, wire.MsgOutput)
987 }988 }
988 989
989 func TestPaletteCNSPromptsForSessionNameThenCreatesAndSwitches(t *testing.T) {990 func TestPaletteCNSPromptsForSessionNameThenCreatesAndSwitches(t *testing.T) {
@@ -995,10 +996,10 @@ func TestPaletteCNSPromptsForSessionNameThenCreatesAndSwitches(t *testing.T) {
995 d.ptys = newFactorySeq(t, p2)996 d.ptys = newFactorySeq(t, p2)
996 997
997 d.handleInput(sess, ac, []byte("\x1b "))998 d.handleInput(sess, ac, []byte("\x1b "))
998- awaitFrame(t, sends, ports.MsgOutput)999+ awaitFrame(t, sends, wire.MsgOutput)
999 d.handleInput(sess, ac, []byte("CNS\r"))1000 d.handleInput(sess, ac, []byte("CNS\r"))
1000- promptFrame := awaitFrame(t, sends, ports.MsgOutput)1001+ promptFrame := awaitFrame(t, sends, wire.MsgOutput)
1001- promptOutput, err := ports.UnmarshalOutput(promptFrame.Payload)1002+ promptOutput, err := wire.UnmarshalOutput(promptFrame.Payload)
1002 require.NoError(t, err)1003 require.NoError(t, err)
1003 require.False(t, ac.overlays.paletteActive())1004 require.False(t, ac.overlays.paletteActive())
1004 require.True(t, ac.overlays.promptActive())1005 require.True(t, ac.overlays.promptActive())
@@ -1009,12 +1010,12 @@ func TestPaletteCNSPromptsForSessionNameThenCreatesAndSwitches(t *testing.T) {
1009 // The submit first paints the newly attached session while the prompt is1010 // The submit first paints the newly attached session while the prompt is
1010 // still open, then handlePromptInput closes the prompt and repaints the1011 // still open, then handlePromptInput closes the prompt and repaints the
1011 // client's current session. The final frame must be for the new session.1012 // client's current session. The final frame must be for the new session.
1012- firstPaint := awaitFrame(t, sends, ports.MsgOutput)1013+ firstPaint := awaitFrame(t, sends, wire.MsgOutput)
1013- firstOutput, err := ports.UnmarshalOutput(firstPaint.Payload)1014+ firstOutput, err := wire.UnmarshalOutput(firstPaint.Payload)
1014 require.NoError(t, err)1015 require.NoError(t, err)
1015 require.Zero(t, firstOutput.Base, "new-session first paint must reset moving output state")1016 require.Zero(t, firstOutput.Base, "new-session first paint must reset moving output state")
1016- finalRepaint := awaitFrame(t, sends, ports.MsgOutput)1017+ finalRepaint := awaitFrame(t, sends, wire.MsgOutput)
1017- finalOutput, err := ports.UnmarshalOutput(finalRepaint.Payload)1018+ finalOutput, err := wire.UnmarshalOutput(finalRepaint.Payload)
1018 require.NoError(t, err)1019 require.NoError(t, err)
1019 require.False(t, ac.overlays.promptActive())1020 require.False(t, ac.overlays.promptActive())
1020 require.Equal(t, 2, sessionCount(d))1021 require.Equal(t, 2, sessionCount(d))
@@ -1049,9 +1050,9 @@ func TestPaletteDirectSessionCreation(t *testing.T) {
1049 d.ptys = newFactorySeq(t, p2)1050 d.ptys = newFactorySeq(t, p2)
1050 1051
1051 d.handleInput(sess, ac, []byte("\x1b "))1052 d.handleInput(sess, ac, []byte("\x1b "))
1052- awaitFrame(t, sends, ports.MsgOutput)1053+ awaitFrame(t, sends, wire.MsgOutput)
1053 d.handleInput(sess, ac, []byte(tc.input))1054 d.handleInput(sess, ac, []byte(tc.input))
1054- awaitFrame(t, sends, ports.MsgOutput)1055+ awaitFrame(t, sends, wire.MsgOutput)
1055 1056
1056 require.False(t, ac.overlays.paletteActive())1057 require.False(t, ac.overlays.paletteActive())
1057 require.False(t, ac.overlays.promptActive())1058 require.False(t, ac.overlays.promptActive())
@@ -1072,13 +1073,13 @@ func TestPaletteReopensWithSuccessfulCommandFirst(t *testing.T) {
1072 defer releases[1]()1073 defer releases[1]()
1073 1074
1074 d.handleInput(sess, ac, []byte("\x1b "))1075 d.handleInput(sess, ac, []byte("\x1b "))
1075- awaitFrame(t, sends, ports.MsgOutput)1076+ awaitFrame(t, sends, wire.MsgOutput)
1076 d.handleInput(sess, ac, []byte("NXT\r"))1077 d.handleInput(sess, ac, []byte("NXT\r"))
1077 require.False(t, ac.overlays.paletteActive())1078 require.False(t, ac.overlays.paletteActive())
1078- awaitFrame(t, sends, ports.MsgOutput)1079+ awaitFrame(t, sends, wire.MsgOutput)
1079 1080
1080 d.handleInput(sess, ac, []byte("\x1b "))1081 d.handleInput(sess, ac, []byte("\x1b "))
1081- awaitFrame(t, sends, ports.MsgOutput)1082+ awaitFrame(t, sends, wire.MsgOutput)
1082 result, ok := ac.overlays.palette.Selected()1083 result, ok := ac.overlays.palette.Selected()
1083 require.True(t, ok)1084 require.True(t, ok)
1084 cmd, ok := result.Command()1085 cmd, ok := result.Command()
@@ -1163,15 +1164,15 @@ func TestPaletteCommandNoopRepaintsAfterClose(t *testing.T) {
1163 defer releases[0]()1164 defer releases[0]()
1164 1165
1165 d.handleInput(sess, ac, []byte("\x1b "))1166 d.handleInput(sess, ac, []byte("\x1b "))
1166- paletteFrame := awaitFrame(t, sends, ports.MsgOutput)1167+ paletteFrame := awaitFrame(t, sends, wire.MsgOutput)
1167- paletteOutput, err := ports.UnmarshalOutput(paletteFrame.Payload)1168+ paletteOutput, err := wire.UnmarshalOutput(paletteFrame.Payload)
1168 require.NoError(t, err)1169 require.NoError(t, err)
1169 require.Contains(t, string(paletteOutput.Data), "Commands")1170 require.Contains(t, string(paletteOutput.Data), "Commands")
1170 1171
1171 d.handleInput(sess, ac, []byte("NXT\r"))1172 d.handleInput(sess, ac, []byte("NXT\r"))
1172 require.False(t, ac.overlays.paletteActive())1173 require.False(t, ac.overlays.paletteActive())
1173- repaint := awaitFrame(t, sends, ports.MsgOutput)1174+ repaint := awaitFrame(t, sends, wire.MsgOutput)
1174- repaintOutput, err := ports.UnmarshalOutput(repaint.Payload)1175+ repaintOutput, err := wire.UnmarshalOutput(repaint.Payload)
1175 require.NoError(t, err)1176 require.NoError(t, err)
1176 require.NotContains(t, string(repaintOutput.Data), "Commands")1177 require.NotContains(t, string(repaintOutput.Data), "Commands")
1177 }1178 }
@@ -1184,11 +1185,11 @@ func TestPaletteCreateTabErrorRepaintsAfterClose(t *testing.T) {
1184 d.ptys = ptys1185 d.ptys = ptys
1185 1186
1186 d.handleInput(sess, ac, []byte("\x1b "))1187 d.handleInput(sess, ac, []byte("\x1b "))
1187- awaitFrame(t, sends, ports.MsgOutput)1188+ awaitFrame(t, sends, wire.MsgOutput)
1188 d.handleInput(sess, ac, []byte("CNT\r"))1189 d.handleInput(sess, ac, []byte("CNT\r"))
1189 require.False(t, ac.overlays.paletteActive())1190 require.False(t, ac.overlays.paletteActive())
1190- repaint := awaitFrame(t, sends, ports.MsgOutput)1191+ repaint := awaitFrame(t, sends, wire.MsgOutput)
1191- repaintOutput, err := ports.UnmarshalOutput(repaint.Payload)1192+ repaintOutput, err := wire.UnmarshalOutput(repaint.Payload)
1192 require.NoError(t, err)1193 require.NoError(t, err)
1193 require.NotContains(t, string(repaintOutput.Data), "Commands")1194 require.NotContains(t, string(repaintOutput.Data), "Commands")
1194 require.Len(t, sess.tabs, 1)1195 require.Len(t, sess.tabs, 1)
@@ -1199,10 +1200,10 @@ func TestPaletteEnterNoMatchKeepsOpenAndEscapeSplit(t *testing.T) {
1199 defer releases[0]()1200 defer releases[0]()
1200 1201
1201 d.handleInput(sess, ac, []byte("\x1b "))1202 d.handleInput(sess, ac, []byte("\x1b "))
1202- awaitFrame(t, sends, ports.MsgOutput)1203+ awaitFrame(t, sends, wire.MsgOutput)
1203 d.handleInput(sess, ac, []byte("zzzz\r"))1204 d.handleInput(sess, ac, []byte("zzzz\r"))
1204 require.True(t, ac.overlays.paletteActive())1205 require.True(t, ac.overlays.paletteActive())
1205- awaitFrame(t, sends, ports.MsgOutput)1206+ awaitFrame(t, sends, wire.MsgOutput)
1206 1207
1207 d.handlePaletteInput(ac, []byte{0x1b, '['})1208 d.handlePaletteInput(ac, []byte{0x1b, '['})
1208 require.True(t, ac.overlays.paletteActive())1209 require.True(t, ac.overlays.paletteActive())
@@ -1210,7 +1211,7 @@ func TestPaletteEnterNoMatchKeepsOpenAndEscapeSplit(t *testing.T) {
1210 d.handlePaletteInput(ac, []byte{'A'})1211 d.handlePaletteInput(ac, []byte{'A'})
1211 require.True(t, ac.overlays.paletteActive())1212 require.True(t, ac.overlays.paletteActive())
1212 require.Empty(t, ac.overlays.palettePending)1213 require.Empty(t, ac.overlays.palettePending)
1213- awaitFrame(t, sends, ports.MsgOutput)1214+ awaitFrame(t, sends, wire.MsgOutput)
1214 }1215 }
1215 1216
1216 func TestPaletteCtrlNAndCtrlPNavigate(t *testing.T) {1217 func TestPaletteCtrlNAndCtrlPNavigate(t *testing.T) {
@@ -1218,10 +1219,10 @@ func TestPaletteCtrlNAndCtrlPNavigate(t *testing.T) {
1218 defer releases[0]()1219 defer releases[0]()
1219 1220
1220 d.handleInput(sess, ac, []byte("\x1b "))1221 d.handleInput(sess, ac, []byte("\x1b "))
1221- awaitFrame(t, sends, ports.MsgOutput)1222+ awaitFrame(t, sends, wire.MsgOutput)
1222 1223
1223 d.handlePaletteInput(ac, []byte{0x0e})1224 d.handlePaletteInput(ac, []byte{0x0e})
1224- awaitFrame(t, sends, ports.MsgOutput)1225+ awaitFrame(t, sends, wire.MsgOutput)
1225 result, ok := ac.overlays.palette.Selected()1226 result, ok := ac.overlays.palette.Selected()
1226 require.True(t, ok)1227 require.True(t, ok)
1227 cmd, ok := result.Command()1228 cmd, ok := result.Command()
@@ -1229,7 +1230,7 @@ func TestPaletteCtrlNAndCtrlPNavigate(t *testing.T) {
1229 require.Equal(t, "CNS", cmd.Code)1230 require.Equal(t, "CNS", cmd.Code)
1230 1231
1231 d.handlePaletteInput(ac, []byte{0x10})1232 d.handlePaletteInput(ac, []byte{0x10})
1232- awaitFrame(t, sends, ports.MsgOutput)1233+ awaitFrame(t, sends, wire.MsgOutput)
1233 result, ok = ac.overlays.palette.Selected()1234 result, ok = ac.overlays.palette.Selected()
1234 require.True(t, ok)1235 require.True(t, ok)
1235 cmd, ok = result.Command()1236 cmd, ok = result.Command()
@@ -1337,7 +1338,7 @@ func TestPaletteUTF8PendingCompletesFilter(t *testing.T) {
1337 defer releases[0]()1338 defer releases[0]()
1338 1339
1339 d.handleInput(sess, ac, []byte("\x1b "))1340 d.handleInput(sess, ac, []byte("\x1b "))
1340- awaitFrame(t, sends, ports.MsgOutput)1341+ awaitFrame(t, sends, wire.MsgOutput)
1341 d.handlePaletteInput(ac, []byte{0xc3})1342 d.handlePaletteInput(ac, []byte{0xc3})
1342 require.Equal(t, []byte{0xc3}, ac.overlays.palettePending)1343 require.Equal(t, []byte{0xc3}, ac.overlays.palettePending)
1343 d.handlePaletteInput(ac, []byte{0xa9})1344 d.handlePaletteInput(ac, []byte{0xa9})
M internal/usecase/daemon/pane_actions_test.go +3 -2
@@ -16,6 +16,7 @@ import (
16 "github.com/bnema/vev/internal/domain"16 "github.com/bnema/vev/internal/domain"
17 "github.com/bnema/vev/internal/ports"17 "github.com/bnema/vev/internal/ports"
18 portsmocks "github.com/bnema/vev/internal/ports/mocks"18 portsmocks "github.com/bnema/vev/internal/ports/mocks"
19+ "github.com/bnema/vev/internal/protocol/wire"
19 scopy "github.com/bnema/vev/internal/usecase/copy"20 scopy "github.com/bnema/vev/internal/usecase/copy"
20 "github.com/bnema/vev/internal/usecase/layout"21 "github.com/bnema/vev/internal/usecase/layout"
21 )22 )
@@ -695,7 +696,7 @@ func TestClosePaneRepaintFanoutRespectsAttachmentScope(t *testing.T) {
695 closingPTY.EXPECT().Close().Return(nil).Once()696 closingPTY.EXPECT().Close().Return(nil).Once()
696 697
697 clients := make([]*attachedClient, 2)698 clients := make([]*attachedClient, 2)
698- sends := make([]chan ports.Frame, 2)699+ sends := make([]chan wire.Frame, 2)
699 for i := range clients {700 for i := range clients {
700 tr, sent := newCapturingTransport(t)701 tr, sent := newCapturingTransport(t)
701 clients[i] = &attachedClient{tr: tr, output: newOutputStateStream(), size: domain.Size{Cols: 41, Rows: 12}}702 clients[i] = &attachedClient{tr: tr, output: newOutputStateStream(), size: domain.Size{Cols: 41, Rows: 12}}
@@ -715,7 +716,7 @@ func TestClosePaneRepaintFanoutRespectsAttachmentScope(t *testing.T) {
715 select {716 select {
716 case frame := <-sends[i]:717 case frame := <-sends[i]:
717 require.True(t, want, "attachment %d unexpectedly repainted: %#v", i, frame)718 require.True(t, want, "attachment %d unexpectedly repainted: %#v", i, frame)
718- require.Equal(t, ports.MsgOutput, frame.Type)719+ require.Equal(t, wire.MsgOutput, frame.Type)
719 default:720 default:
720 require.False(t, want, "attachment %d was not repainted", i)721 require.False(t, want, "attachment %d was not repainted", i)
721 }722 }
M internal/usecase/daemon/parked_route.go +5 -4
@@ -9,6 +9,7 @@ import (
9 "github.com/bnema/vev/internal/domain"9 "github.com/bnema/vev/internal/domain"
10 "github.com/bnema/vev/internal/ports"10 "github.com/bnema/vev/internal/ports"
11 "github.com/bnema/vev/internal/protocol"11 "github.com/bnema/vev/internal/protocol"
12+ "github.com/bnema/vev/internal/protocol/wire"
12 )13 )
13 14
14 const parkedRouteLeaseTTL = 15 * time.Minute15 const parkedRouteLeaseTTL = 15 * time.Minute
@@ -237,12 +238,12 @@ func (ac *attachedClient) consumePublishedParkedRoute(previous, published attach
237 return 0238 return 0
238 }239 }
239 240
240-func parkedRouteResponseFrame(response protocol.ParkedRouteResponse) (ports.Frame, error) {241+func parkedRouteResponseFrame(response protocol.ParkedRouteResponse) (wire.Frame, error) {
241- payload := ports.MarshalParkedRouteResponse(response)242+ payload := wire.MarshalParkedRouteResponse(response)
242 if payload == nil {243 if payload == nil {
243- return ports.Frame{}, errAttachmentTransition244+ return wire.Frame{}, errAttachmentTransition
244 }245 }
245- return ports.Frame{Type: ports.MsgParkedRouteResponse, Payload: payload}, nil246+ return wire.Frame{Type: wire.MsgParkedRouteResponse, Payload: payload}, nil
246 }247 }
247 248
248 func (d *Daemon) sendParkedRouteResponse(effect *attachmentEffect, response protocol.ParkedRouteResponse) error {249 func (d *Daemon) sendParkedRouteResponse(effect *attachmentEffect, response protocol.ParkedRouteResponse) error {
M internal/usecase/daemon/parked_route_test.go +37 -36
@@ -14,28 +14,29 @@ import (
14 "github.com/bnema/vev/internal/ports"14 "github.com/bnema/vev/internal/ports"
15 portsmocks "github.com/bnema/vev/internal/ports/mocks"15 portsmocks "github.com/bnema/vev/internal/ports/mocks"
16 "github.com/bnema/vev/internal/protocol"16 "github.com/bnema/vev/internal/protocol"
17+ "github.com/bnema/vev/internal/protocol/wire"
17 )18 )
18 19
19 type parkedRouteFailTransport struct{}20 type parkedRouteFailTransport struct{}
20 21
21-func (parkedRouteFailTransport) Send(ports.Frame) error { return errors.New("send failed") }22+func (parkedRouteFailTransport) Send(wire.Frame) error { return errors.New("send failed") }
22-func (parkedRouteFailTransport) Recv() (ports.Frame, error) { return ports.Frame{}, io.EOF }23+func (parkedRouteFailTransport) Recv() (wire.Frame, error) { return wire.Frame{}, io.EOF }
23-func (parkedRouteFailTransport) Close() error { return nil }24+func (parkedRouteFailTransport) Close() error { return nil }
24 25
25 type parkedRouteExpiryTransport struct {26 type parkedRouteExpiryTransport struct {
26- sends chan ports.Frame27+ sends chan wire.Frame
27 closed chan struct{}28 closed chan struct{}
28 once sync.Once29 once sync.Once
29 }30 }
30 31
31-func (t *parkedRouteExpiryTransport) Send(frame ports.Frame) error {32+func (t *parkedRouteExpiryTransport) Send(frame wire.Frame) error {
32 t.sends <- frame33 t.sends <- frame
33 return nil34 return nil
34 }35 }
35 36
36-func (t *parkedRouteExpiryTransport) Recv() (ports.Frame, error) {37+func (t *parkedRouteExpiryTransport) Recv() (wire.Frame, error) {
37 <-t.closed38 <-t.closed
38- return ports.Frame{}, io.EOF39+ return wire.Frame{}, io.EOF
39 }40 }
40 41
41 func (t *parkedRouteExpiryTransport) Close() error {42 func (t *parkedRouteExpiryTransport) Close() error {
@@ -62,7 +63,7 @@ func (c *parkedRouteClock) setNow(now time.Time) {
62 c.mu.Unlock()63 c.mu.Unlock()
63 }64 }
64 65
65-func armAndPrepareParkedRoute(t *testing.T, d *Daemon, source *session, ac *attachedClient, sends chan ports.Frame) protocol.ParkedRouteLeaseID {66+func armAndPrepareParkedRoute(t *testing.T, d *Daemon, source *session, ac *attachedClient, sends chan wire.Frame) protocol.ParkedRouteLeaseID {
66 t.Helper()67 t.Helper()
67 ac.navigationCapabilities |= protocol.NavigationCapabilityHomePicker68 ac.navigationCapabilities |= protocol.NavigationCapabilityHomePicker
68 token := source.captureAttachmentCapability(ac, ac.transport())69 token := source.captureAttachmentCapability(ac, ac.transport())
@@ -70,14 +71,14 @@ func armAndPrepareParkedRoute(t *testing.T, d *Daemon, source *session, ac *atta
70 require.True(t, admitted)71 require.True(t, admitted)
71 require.NoError(t, d.sendNavigationActionForAttachment(effect, protocol.NavigationOpenHomePicker))72 require.NoError(t, d.sendNavigationActionForAttachment(effect, protocol.NavigationOpenHomePicker))
72 effect.End()73 effect.End()
73- directiveFrame := awaitFrame(t, sends, ports.MsgNavigationAction)74+ directiveFrame := awaitFrame(t, sends, wire.MsgNavigationAction)
74- directive, err := ports.UnmarshalNavigationDirective(directiveFrame.Payload)75+ directive, err := wire.UnmarshalNavigationDirective(directiveFrame.Payload)
75 require.NoError(t, err)76 require.NoError(t, err)
76 77
77 prepare := protocol.ParkedRouteRequest{RequestID: 1, LeaseID: directive.LeaseID, Action: protocol.ParkedRoutePrepare}78 prepare := protocol.ParkedRouteRequest{RequestID: 1, LeaseID: directive.LeaseID, Action: protocol.ParkedRoutePrepare}
78- require.False(t, d.handleAttachmentClientFrame(source.captureAttachmentCapability(ac, ac.transport()), ports.Frame{Type: ports.MsgParkedRouteRequest, Payload: ports.MarshalParkedRouteRequest(prepare)}))79+ require.False(t, d.handleAttachmentClientFrame(source.captureAttachmentCapability(ac, ac.transport()), wire.Frame{Type: wire.MsgParkedRouteRequest, Payload: wire.MarshalParkedRouteRequest(prepare)}))
79- readyFrame := awaitFrame(t, sends, ports.MsgParkedRouteResponse)80+ readyFrame := awaitFrame(t, sends, wire.MsgParkedRouteResponse)
80- ready, err := ports.UnmarshalParkedRouteResponse(readyFrame.Payload)81+ ready, err := wire.UnmarshalParkedRouteResponse(readyFrame.Payload)
81 require.NoError(t, err)82 require.NoError(t, err)
82 require.Equal(t, protocol.ParkedRouteResponse{RequestID: 1, Status: protocol.ParkedRouteReady}, ready)83 require.Equal(t, protocol.ParkedRouteResponse{RequestID: 1, Status: protocol.ParkedRouteReady}, ready)
83 return directive.LeaseID84 return directive.LeaseID
@@ -108,12 +109,12 @@ func TestParkedRouteSwitchesExactLiveTargetOnRetainedTransport(t *testing.T) {
108 SessionName: "target", LiveTabID: domain.TabStableID(target.tabs[0].stableID),109 SessionName: "target", LiveTabID: domain.TabStableID(target.tabs[0].stableID),
109 }110 }
110 switchRequest := protocol.ParkedRouteRequest{RequestID: 2, LeaseID: leaseID, Action: protocol.ParkedRouteSwitch, Target: &targetRequest}111 switchRequest := protocol.ParkedRouteRequest{RequestID: 2, LeaseID: leaseID, Action: protocol.ParkedRouteSwitch, Target: &targetRequest}
111- require.False(t, d.handleAttachmentClientFrame(source.captureAttachmentCapability(ac, ac.transport()), ports.Frame{Type: ports.MsgParkedRouteRequest, Payload: ports.MarshalParkedRouteRequest(switchRequest)}))112+ require.False(t, d.handleAttachmentClientFrame(source.captureAttachmentCapability(ac, ac.transport()), wire.Frame{Type: wire.MsgParkedRouteRequest, Payload: wire.MarshalParkedRouteRequest(switchRequest)}))
112 113
113 require.Same(t, target, ac.currentAttachmentSession())114 require.Same(t, target, ac.currentAttachmentSession())
114 require.False(t, ac.parkedRouteOutput.Load())115 require.False(t, ac.parkedRouteOutput.Load())
115- responseFrame := awaitFrame(t, sends, ports.MsgParkedRouteResponse)116+ responseFrame := awaitFrame(t, sends, wire.MsgParkedRouteResponse)
116- response, err := ports.UnmarshalParkedRouteResponse(responseFrame.Payload)117+ response, err := wire.UnmarshalParkedRouteResponse(responseFrame.Payload)
117 require.NoError(t, err)118 require.NoError(t, err)
118 require.Equal(t, protocol.ParkedRouteResponse{RequestID: 2, Status: protocol.ParkedRouteSwitched}, response)119 require.Equal(t, protocol.ParkedRouteResponse{RequestID: 2, Status: protocol.ParkedRouteSwitched}, response)
119 }120 }
@@ -147,11 +148,11 @@ func TestParkedRouteSwitchAcceptedBeforeLeaseExpiryCommitsAtomically(t *testing.
147 SessionName: "target", LiveTabID: domain.TabStableID(target.tabs[0].stableID),148 SessionName: "target", LiveTabID: domain.TabStableID(target.tabs[0].stableID),
148 }149 }
149 switchRequest := protocol.ParkedRouteRequest{RequestID: 2, LeaseID: leaseID, Action: protocol.ParkedRouteSwitch, Target: &targetRequest}150 switchRequest := protocol.ParkedRouteRequest{RequestID: 2, LeaseID: leaseID, Action: protocol.ParkedRouteSwitch, Target: &targetRequest}
150- d.handleAttachmentClientFrame(source.captureAttachmentCapability(ac, ac.transport()), ports.Frame{Type: ports.MsgParkedRouteRequest, Payload: ports.MarshalParkedRouteRequest(switchRequest)})151+ d.handleAttachmentClientFrame(source.captureAttachmentCapability(ac, ac.transport()), wire.Frame{Type: wire.MsgParkedRouteRequest, Payload: wire.MarshalParkedRouteRequest(switchRequest)})
151 152
152 require.Same(t, target, ac.currentAttachmentSession())153 require.Same(t, target, ac.currentAttachmentSession())
153- responseFrame := awaitFrame(t, sends, ports.MsgParkedRouteResponse)154+ responseFrame := awaitFrame(t, sends, wire.MsgParkedRouteResponse)
154- response, err := ports.UnmarshalParkedRouteResponse(responseFrame.Payload)155+ response, err := wire.UnmarshalParkedRouteResponse(responseFrame.Payload)
155 require.NoError(t, err)156 require.NoError(t, err)
156 require.Equal(t, protocol.ParkedRouteSwitched, response.Status)157 require.Equal(t, protocol.ParkedRouteSwitched, response.Status)
157 }158 }
@@ -185,7 +186,7 @@ func TestParkedRouteRestoresExactStoppedTargetWithDaemonEnvironment(t *testing.T
185 SessionName: "stopped", Stopped: true, StoppedTab: domain.NewStableTabSelector("tab-b"),186 SessionName: "stopped", Stopped: true, StoppedTab: domain.NewStableTabSelector("tab-b"),
186 }187 }
187 switchRequest := protocol.ParkedRouteRequest{RequestID: 2, LeaseID: leaseID, Action: protocol.ParkedRouteSwitch, Target: &target}188 switchRequest := protocol.ParkedRouteRequest{RequestID: 2, LeaseID: leaseID, Action: protocol.ParkedRouteSwitch, Target: &target}
188- d.handleAttachmentClientFrame(source.captureAttachmentCapability(ac, ac.transport()), ports.Frame{Type: ports.MsgParkedRouteRequest, Payload: ports.MarshalParkedRouteRequest(switchRequest)})189+ d.handleAttachmentClientFrame(source.captureAttachmentCapability(ac, ac.transport()), wire.Frame{Type: wire.MsgParkedRouteRequest, Payload: wire.MarshalParkedRouteRequest(switchRequest)})
189 190
190 restored := ac.currentAttachmentSession()191 restored := ac.currentAttachmentSession()
191 require.NotNil(t, restored)192 require.NotNil(t, restored)
@@ -210,23 +211,23 @@ func TestParkedRouteStaleTargetLeavesSourceResumable(t *testing.T) {
210 SessionName: "missing", LiveTabID: "tab-1",211 SessionName: "missing", LiveTabID: "tab-1",
211 }212 }
212 switchRequest := protocol.ParkedRouteRequest{RequestID: 2, LeaseID: leaseID, Action: protocol.ParkedRouteSwitch, Target: &missing}213 switchRequest := protocol.ParkedRouteRequest{RequestID: 2, LeaseID: leaseID, Action: protocol.ParkedRouteSwitch, Target: &missing}
213- d.handleAttachmentClientFrame(source.captureAttachmentCapability(ac, ac.transport()), ports.Frame{Type: ports.MsgParkedRouteRequest, Payload: ports.MarshalParkedRouteRequest(switchRequest)})214+ d.handleAttachmentClientFrame(source.captureAttachmentCapability(ac, ac.transport()), wire.Frame{Type: wire.MsgParkedRouteRequest, Payload: wire.MarshalParkedRouteRequest(switchRequest)})
214- staleFrame := awaitFrame(t, sends, ports.MsgParkedRouteResponse)215+ staleFrame := awaitFrame(t, sends, wire.MsgParkedRouteResponse)
215- stale, err := ports.UnmarshalParkedRouteResponse(staleFrame.Payload)216+ stale, err := wire.UnmarshalParkedRouteResponse(staleFrame.Payload)
216 require.NoError(t, err)217 require.NoError(t, err)
217 require.Equal(t, protocol.ParkedRouteStaleTarget, stale.Status)218 require.Equal(t, protocol.ParkedRouteStaleTarget, stale.Status)
218 require.Same(t, source, ac.currentAttachmentSession())219 require.Same(t, source, ac.currentAttachmentSession())
219 require.True(t, ac.parkedRouteOutput.Load())220 require.True(t, ac.parkedRouteOutput.Load())
220 221
221 resume := protocol.ParkedRouteRequest{RequestID: 3, LeaseID: leaseID, Action: protocol.ParkedRouteResume}222 resume := protocol.ParkedRouteRequest{RequestID: 3, LeaseID: leaseID, Action: protocol.ParkedRouteResume}
222- d.handleAttachmentClientFrame(source.captureAttachmentCapability(ac, ac.transport()), ports.Frame{Type: ports.MsgParkedRouteRequest, Payload: ports.MarshalParkedRouteRequest(resume)})223+ d.handleAttachmentClientFrame(source.captureAttachmentCapability(ac, ac.transport()), wire.Frame{Type: wire.MsgParkedRouteRequest, Payload: wire.MarshalParkedRouteRequest(resume)})
223- resumedFrame := awaitFrame(t, sends, ports.MsgParkedRouteResponse)224+ resumedFrame := awaitFrame(t, sends, wire.MsgParkedRouteResponse)
224- resumed, err := ports.UnmarshalParkedRouteResponse(resumedFrame.Payload)225+ resumed, err := wire.UnmarshalParkedRouteResponse(resumedFrame.Payload)
225 require.NoError(t, err)226 require.NoError(t, err)
226 require.Equal(t, protocol.ParkedRouteResumed, resumed.Status)227 require.Equal(t, protocol.ParkedRouteResumed, resumed.Status)
227 require.False(t, ac.parkedRouteOutput.Load())228 require.False(t, ac.parkedRouteOutput.Load())
228- outputFrame := awaitFrame(t, sends, ports.MsgOutput)229+ outputFrame := awaitFrame(t, sends, wire.MsgOutput)
229- output, err := ports.UnmarshalOutput(outputFrame.Payload)230+ output, err := wire.UnmarshalOutput(outputFrame.Payload)
230 require.NoError(t, err)231 require.NoError(t, err)
231 require.True(t, output.Full)232 require.True(t, output.Full)
232 }233 }
@@ -245,10 +246,10 @@ func TestParkedRouteDoesNotReplayOneShotEffectsAfterResume(t *testing.T) {
245 }246 }
246 247
247 resume := protocol.ParkedRouteRequest{RequestID: 2, LeaseID: leaseID, Action: protocol.ParkedRouteResume}248 resume := protocol.ParkedRouteRequest{RequestID: 2, LeaseID: leaseID, Action: protocol.ParkedRouteResume}
248- d.handleAttachmentClientFrame(source.captureAttachmentCapability(ac, ac.transport()), ports.Frame{Type: ports.MsgParkedRouteRequest, Payload: ports.MarshalParkedRouteRequest(resume)})249+ d.handleAttachmentClientFrame(source.captureAttachmentCapability(ac, ac.transport()), wire.Frame{Type: wire.MsgParkedRouteRequest, Payload: wire.MarshalParkedRouteRequest(resume)})
249- _ = awaitFrame(t, sends, ports.MsgParkedRouteResponse)250+ _ = awaitFrame(t, sends, wire.MsgParkedRouteResponse)
250- outputFrame := awaitFrame(t, sends, ports.MsgOutput)251+ outputFrame := awaitFrame(t, sends, wire.MsgOutput)
251- output, err := ports.UnmarshalOutput(outputFrame.Payload)252+ output, err := wire.UnmarshalOutput(outputFrame.Payload)
252 require.NoError(t, err)253 require.NoError(t, err)
253 require.True(t, output.Full)254 require.True(t, output.Full)
254 require.NotContains(t, string(output.Data), "one-shot effect")255 require.NotContains(t, string(output.Data), "one-shot effect")
@@ -270,9 +271,9 @@ func TestParkedRouteExpiredLeaseFailsClosed(t *testing.T) {
270 ac.parkedRoute.expiresAt = d.clock.Now()271 ac.parkedRoute.expiresAt = d.clock.Now()
271 ac.parkedRouteMu.Unlock()272 ac.parkedRouteMu.Unlock()
272 resume := protocol.ParkedRouteRequest{RequestID: 2, LeaseID: leaseID, Action: protocol.ParkedRouteResume}273 resume := protocol.ParkedRouteRequest{RequestID: 2, LeaseID: leaseID, Action: protocol.ParkedRouteResume}
273- d.handleAttachmentClientFrame(source.captureAttachmentCapability(ac, ac.transport()), ports.Frame{Type: ports.MsgParkedRouteRequest, Payload: ports.MarshalParkedRouteRequest(resume)})274+ d.handleAttachmentClientFrame(source.captureAttachmentCapability(ac, ac.transport()), wire.Frame{Type: wire.MsgParkedRouteRequest, Payload: wire.MarshalParkedRouteRequest(resume)})
274- responseFrame := awaitFrame(t, sends, ports.MsgParkedRouteResponse)275+ responseFrame := awaitFrame(t, sends, wire.MsgParkedRouteResponse)
275- response, err := ports.UnmarshalParkedRouteResponse(responseFrame.Payload)276+ response, err := wire.UnmarshalParkedRouteResponse(responseFrame.Payload)
276 require.NoError(t, err)277 require.NoError(t, err)
277 require.Equal(t, protocol.ParkedRouteExpired, response.Status)278 require.Equal(t, protocol.ParkedRouteExpired, response.Status)
278 require.Same(t, source, ac.currentAttachmentSession())279 require.Same(t, source, ac.currentAttachmentSession())
@@ -308,7 +309,7 @@ func TestParkedRouteLeaseExpiryClosesExactRetainedTransport(t *testing.T) {
308 clock.EXPECT().Now().Return(time.Time{}).Maybe()309 clock.EXPECT().Now().Return(time.Time{}).Maybe()
309 clock.EXPECT().NewTimer(mock.Anything).Return(timer).Once()310 clock.EXPECT().NewTimer(mock.Anything).Return(timer).Once()
310 d.clock = clock311 d.clock = clock
311- transport := &parkedRouteExpiryTransport{sends: make(chan ports.Frame, 8), closed: make(chan struct{})}312+ transport := &parkedRouteExpiryTransport{sends: make(chan wire.Frame, 8), closed: make(chan struct{})}
312 ac.replaceTransport(transport)313 ac.replaceTransport(transport)
313 314
314 _ = armAndPrepareParkedRoute(t, d, source, ac, transport.sends)315 _ = armAndPrepareParkedRoute(t, d, source, ac, transport.sends)
M internal/usecase/daemon/performance_benchmark_test.go +9 -8
@@ -16,6 +16,7 @@ import (
16 "github.com/bnema/vev/internal/domain"16 "github.com/bnema/vev/internal/domain"
17 "github.com/bnema/vev/internal/ports"17 "github.com/bnema/vev/internal/ports"
18 "github.com/bnema/vev/internal/protocol"18 "github.com/bnema/vev/internal/protocol"
19+ "github.com/bnema/vev/internal/protocol/wire"
19 "github.com/bnema/vev/internal/usecase/layout"20 "github.com/bnema/vev/internal/usecase/layout"
20 snapcodec "github.com/bnema/vev/internal/usecase/snapshot"21 snapcodec "github.com/bnema/vev/internal/usecase/snapshot"
21 )22 )
@@ -184,12 +185,12 @@ func TestPerformanceFixtureExplicitCloseReleasesIterationState(t *testing.T) {
184 func TestCountingOutputTransportCountsOpaquePayloadAndRejectsShortPayload(t *testing.T) {185 func TestCountingOutputTransportCountsOpaquePayloadAndRejectsShortPayload(t *testing.T) {
185 transport := &countingOutputTransport{}186 transport := &countingOutputTransport{}
186 187
187- err := transport.Send(ports.Frame{Type: ports.MsgOutput, Payload: make([]byte, 23)})188+ err := transport.Send(wire.Frame{Type: wire.MsgOutput, Payload: make([]byte, 23)})
188 require.Error(t, err)189 require.Error(t, err)
189 require.Equal(t, countingOutputMetrics{}, transport.metrics())190 require.Equal(t, countingOutputMetrics{}, transport.metrics())
190 191
191 payload := append(make([]byte, 24), 0xff, 0x00, 0xfe)192 payload := append(make([]byte, 24), 0xff, 0x00, 0xfe)
192- require.NoError(t, transport.Send(ports.Frame{Type: ports.MsgOutput, Payload: payload}))193+ require.NoError(t, transport.Send(wire.Frame{Type: wire.MsgOutput, Payload: payload}))
193 require.Equal(t, countingOutputMetrics{frames: 1, bytes: 3, payloadBytes: uint64(len(payload))}, transport.metrics())194 require.Equal(t, countingOutputMetrics{frames: 1, bytes: 3, payloadBytes: uint64(len(payload))}, transport.metrics())
194 require.Equal(t, payload, transport.lastPayload())195 require.Equal(t, payload, transport.lastPayload())
195 196
@@ -230,7 +231,7 @@ func TestPerformanceFixtureCounters(t *testing.T) {
230 func TestRenderStageHooksCountProductionBoundariesOnFailedSend(t *testing.T) {231 func TestRenderStageHooksCountProductionBoundariesOnFailedSend(t *testing.T) {
231 d, sess, ac, sends := newManualSessionWithPTYs(t, nil)232 d, sess, ac, sends := newManualSessionWithPTYs(t, nil)
232 d.paint(sess, ac, true, nil)233 d.paint(sess, ac, true, nil)
233- _ = awaitFrame(t, sends, ports.MsgOutput)234+ _ = awaitFrame(t, sends, wire.MsgOutput)
234 var captures, compositions, emissions int235 var captures, compositions, emissions int
235 ac.renderStages = renderStageHooks{236 ac.renderStages = renderStageHooks{
236 capture: func() { captures++ },237 capture: func() { captures++ },
@@ -446,7 +447,7 @@ func benchmarkDaemonLargeHistory(b *testing.B, workload string, run func(*perfor
446 }447 }
447 metrics := fixture.metrics()448 metrics := fixture.metrics()
448 if payload := fixture.output.lastPayload(); payload != nil {449 if payload := fixture.output.lastPayload(); payload != nil {
449- if _, err := ports.UnmarshalOutput(payload); err != nil {450+ if _, err := wire.UnmarshalOutput(payload); err != nil {
450 b.Fatalf("decode last output: %v", err)451 b.Fatalf("decode last output: %v", err)
451 }452 }
452 }453 }
@@ -1347,8 +1348,8 @@ type countingOutputTransport struct {
1347 last []byte1348 last []byte
1348 }1349 }
1349 1350
1350-func (t *countingOutputTransport) Send(frame ports.Frame) error {1351+func (t *countingOutputTransport) Send(frame wire.Frame) error {
1351- if frame.Type != ports.MsgOutput {1352+ if frame.Type != wire.MsgOutput {
1352 return nil1353 return nil
1353 }1354 }
1354 if len(frame.Payload) < outputPayloadHeaderBytes {1355 if len(frame.Payload) < outputPayloadHeaderBytes {
@@ -1362,8 +1363,8 @@ func (t *countingOutputTransport) Send(frame ports.Frame) error {
1362 t.mu.Unlock()1363 t.mu.Unlock()
1363 return nil1364 return nil
1364 }1365 }
1365-func (*countingOutputTransport) Recv() (ports.Frame, error) { return ports.Frame{}, io.EOF }1366+func (*countingOutputTransport) Recv() (wire.Frame, error) { return wire.Frame{}, io.EOF }
1366-func (*countingOutputTransport) Close() error { return nil }1367+func (*countingOutputTransport) Close() error { return nil }
1367 func (t *countingOutputTransport) reset() {1368 func (t *countingOutputTransport) reset() {
1368 t.mu.Lock()1369 t.mu.Lock()
1369 t.countingOutputMetrics = countingOutputMetrics{}1370 t.countingOutputMetrics = countingOutputMetrics{}
M internal/usecase/daemon/picker.go +5 -4
@@ -11,6 +11,7 @@ import (
11 "github.com/bnema/vev/internal/domain"11 "github.com/bnema/vev/internal/domain"
12 "github.com/bnema/vev/internal/ports"12 "github.com/bnema/vev/internal/ports"
13 "github.com/bnema/vev/internal/protocol"13 "github.com/bnema/vev/internal/protocol"
14+ "github.com/bnema/vev/internal/protocol/wire"
14 "github.com/bnema/vev/internal/usecase/layout"15 "github.com/bnema/vev/internal/usecase/layout"
15 "github.com/bnema/vev/internal/usecase/picker"16 "github.com/bnema/vev/internal/usecase/picker"
16 "github.com/bnema/vev/internal/usecase/ui"17 "github.com/bnema/vev/internal/usecase/ui"
@@ -923,7 +924,7 @@ func (d *Daemon) sendLocalAttachTargetForAttachment(effect *attachmentEffect, ta
923 // Explicit tab rows already take the direct transition path above, while924 // Explicit tab rows already take the direct transition path above, while
924 // stopped sessions have no active target session and retain the fallback.925 // stopped sessions have no active target session and retain the fallback.
925 samePeerEligible := guard.allowSamePeer && targetSess != nil && target.TabIndex <= 0926 samePeerEligible := guard.allowSamePeer && targetSess != nil && target.TabIndex <= 0
926- payload := ports.MarshalAttachTarget(protocol.AttachTarget{927+ payload := wire.MarshalAttachTarget(protocol.AttachTarget{
927 Session: sessionName,928 Session: sessionName,
928 Intent: protocol.IntentAttach,929 Intent: protocol.IntentAttach,
929 ExactTarget: exactTarget,930 ExactTarget: exactTarget,
@@ -943,7 +944,7 @@ func (d *Daemon) sendLocalAttachTargetForAttachment(effect *attachmentEffect, ta
943 } else {944 } else {
944 effect.ac.offerSamePeerTarget(*exactTarget)945 effect.ac.offerSamePeerTarget(*exactTarget)
945 }946 }
946- if err := effect.sendControl(ports.Frame{Type: ports.MsgAttachTarget, Payload: payload}); err != nil {947+ if err := effect.sendControl(wire.Frame{Type: wire.MsgAttachTarget, Payload: payload}); err != nil {
947 if samePeerEligible {948 if samePeerEligible {
948 effect.ac.clearSamePeerOffer()949 effect.ac.clearSamePeerOffer()
949 }950 }
@@ -989,11 +990,11 @@ func (d *Daemon) sendRemoteAttachTargetForAttachment(effect *attachmentEffect, t
989 RemoteTarget: &remoteTarget,990 RemoteTarget: &remoteTarget,
990 EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned,991 EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned,
991 }992 }
992- payload := ports.MarshalAttachTarget(handoff)993+ payload := wire.MarshalAttachTarget(handoff)
993 if payload == nil {994 if payload == nil {
994 return failUnavailable()995 return failUnavailable()
995 }996 }
996- if err := effect.sendControl(ports.Frame{Type: ports.MsgAttachTarget, Payload: payload}); err != nil {997+ if err := effect.sendControl(wire.Frame{Type: wire.MsgAttachTarget, Payload: payload}); err != nil {
997 return domain.UserErr(domain.NoticeSessionUnavailable, "couldn't attach to remote session", err)998 return domain.UserErr(domain.NoticeSessionUnavailable, "couldn't attach to remote session", err)
998 }999 }
999 if guard.closePicker {1000 if guard.closePicker {
M internal/usecase/daemon/picker_test.go +30 -29
@@ -17,6 +17,7 @@ import (
17 "github.com/bnema/vev/internal/ports"17 "github.com/bnema/vev/internal/ports"
18 portsmocks "github.com/bnema/vev/internal/ports/mocks"18 portsmocks "github.com/bnema/vev/internal/ports/mocks"
19 "github.com/bnema/vev/internal/protocol"19 "github.com/bnema/vev/internal/protocol"
20+ "github.com/bnema/vev/internal/protocol/wire"
20 "github.com/bnema/vev/internal/usecase/layout"21 "github.com/bnema/vev/internal/usecase/layout"
21 "github.com/bnema/vev/internal/usecase/picker"22 "github.com/bnema/vev/internal/usecase/picker"
22 recoveryusecase "github.com/bnema/vev/internal/usecase/recovery"23 recoveryusecase "github.com/bnema/vev/internal/usecase/recovery"
@@ -34,9 +35,9 @@ type remotePickerSendErrorTransport struct {
34 err error35 err error
35 }36 }
36 37
37-func (t *remotePickerSendErrorTransport) Send(ports.Frame) error { return t.err }38+func (t *remotePickerSendErrorTransport) Send(wire.Frame) error { return t.err }
38-func (*remotePickerSendErrorTransport) Recv() (ports.Frame, error) { return ports.Frame{}, io.EOF }39+func (*remotePickerSendErrorTransport) Recv() (wire.Frame, error) { return wire.Frame{}, io.EOF }
39-func (*remotePickerSendErrorTransport) Close() error { return nil }40+func (*remotePickerSendErrorTransport) Close() error { return nil }
40 41
41 type refusingSnapshotDeleteRepository struct {42 type refusingSnapshotDeleteRepository struct {
42 noOpSnapshotRepository43 noOpSnapshotRepository
@@ -222,11 +223,11 @@ func TestPickerSortToggleFlipsModeAndKeepsSelection(t *testing.T) {
222 d.mu.Unlock()223 d.mu.Unlock()
223 224
224 d.enterPicker(sess, ac)225 d.enterPicker(sess, ac)
225- awaitFrame(t, sends, ports.MsgOutput)226+ awaitFrame(t, sends, wire.MsgOutput)
226 // Move off the attached session's active tab: a navigate rebuild snaps227 // Move off the attached session's active tab: a navigate rebuild snaps
227 // back to it unless the toggle preserves the selection.228 // back to it unless the toggle preserves the selection.
228 d.handleInput(sess, ac, tc.navigate)229 d.handleInput(sess, ac, tc.navigate)
229- awaitFrame(t, sends, ports.MsgOutput)230+ awaitFrame(t, sends, wire.MsgOutput)
230 ac.overlays.pickerMu.Lock()231 ac.overlays.pickerMu.Lock()
231 before, ok := ac.overlays.picker.Selected()232 before, ok := ac.overlays.picker.Selected()
232 ac.overlays.pickerMu.Unlock()233 ac.overlays.pickerMu.Unlock()
@@ -239,7 +240,7 @@ func TestPickerSortToggleFlipsModeAndKeepsSelection(t *testing.T) {
239 }240 }
240 241
241 d.handleInput(sess, ac, []byte("s"))242 d.handleInput(sess, ac, []byte("s"))
242- awaitFrame(t, sends, ports.MsgOutput)243+ awaitFrame(t, sends, wire.MsgOutput)
243 244
244 require.Equal(t, uint32(pickerSortGrouped), d.pickerSort.Load())245 require.Equal(t, uint32(pickerSortGrouped), d.pickerSort.Load())
245 require.True(t, ac.overlays.pickerActive())246 require.True(t, ac.overlays.pickerActive())
@@ -252,7 +253,7 @@ func TestPickerSortToggleFlipsModeAndKeepsSelection(t *testing.T) {
252 require.Equal(t, " Sessions · grouped ", title)253 require.Equal(t, " Sessions · grouped ", title)
253 254
254 d.handleInput(sess, ac, []byte("s"))255 d.handleInput(sess, ac, []byte("s"))
255- awaitFrame(t, sends, ports.MsgOutput)256+ awaitFrame(t, sends, wire.MsgOutput)
256 257
257 require.Equal(t, uint32(pickerSortRecent), d.pickerSort.Load())258 require.Equal(t, uint32(pickerSortRecent), d.pickerSort.Load())
258 require.True(t, ac.overlays.pickerActive())259 require.True(t, ac.overlays.pickerActive())
@@ -812,7 +813,7 @@ func TestPickerResumesStoppedSessionWithPersistedTabNames(t *testing.T) {
812 d.inactive["work"] = inactiveSession{name: "work", cwd: "/tmp/work", createdAt: 7, tabNames: []string{"shell", "logs"}, record: domain.CatalogueRecord{Name: "work"}, state: protocol.SessionDown}813 d.inactive["work"] = inactiveSession{name: "work", cwd: "/tmp/work", createdAt: 7, tabNames: []string{"shell", "logs"}, record: domain.CatalogueRecord{Name: "work"}, state: protocol.SessionDown}
813 814
814 d.resumeStoppedAndSwitch(from, ac, picker.Target{Name: "work", Stopped: true})815 d.resumeStoppedAndSwitch(from, ac, picker.Target{Name: "work", Stopped: true})
815- awaitFrame(t, sends, ports.MsgOutput)816+ awaitFrame(t, sends, wire.MsgOutput)
816 817
817 target := ac.currentSession()818 target := ac.currentSession()
818 require.NotNil(t, target)819 require.NotNil(t, target)
@@ -835,16 +836,16 @@ func TestPickerSameSessionNavigationSwitchAndEscClose(t *testing.T) {
835 }()836 }()
836 837
837 d.enterPicker(sess, ac)838 d.enterPicker(sess, ac)
838- awaitFrame(t, sends, ports.MsgOutput)839+ awaitFrame(t, sends, wire.MsgOutput)
839 d.handleInput(sess, ac, []byte("j"))840 d.handleInput(sess, ac, []byte("j"))
840- awaitFrame(t, sends, ports.MsgOutput)841+ awaitFrame(t, sends, wire.MsgOutput)
841 d.handleInput(sess, ac, []byte("\r"))842 d.handleInput(sess, ac, []byte("\r"))
842 843
843 require.Equal(t, 1, testAttachmentTabIndex(sess))844 require.Equal(t, 1, testAttachmentTabIndex(sess))
844 requireFloatingInitialized(t, testAttachmentTab(sess))845 requireFloatingInitialized(t, testAttachmentTab(sess))
845- awaitFrame(t, sends, ports.MsgOutput)846+ awaitFrame(t, sends, wire.MsgOutput)
846 d.enterPicker(sess, ac)847 d.enterPicker(sess, ac)
847- awaitFrame(t, sends, ports.MsgOutput)848+ awaitFrame(t, sends, wire.MsgOutput)
848 clk := &signalClock{timers: make(chan *signalTimer, 1)}849 clk := &signalClock{timers: make(chan *signalTimer, 1)}
849 d.clock = clk850 d.clock = clk
850 d.handleInput(sess, ac, []byte("\x1b"))851 d.handleInput(sess, ac, []byte("\x1b"))
@@ -852,7 +853,7 @@ func TestPickerSameSessionNavigationSwitchAndEscClose(t *testing.T) {
852 require.True(t, ac.overlays.pickerActive())853 require.True(t, ac.overlays.pickerActive())
853 timer.ch <- time.Now()854 timer.ch <- time.Now()
854 require.Eventually(t, func() bool { return !ac.overlays.pickerActive() }, time.Second, 5*time.Millisecond)855 require.Eventually(t, func() bool { return !ac.overlays.pickerActive() }, time.Second, 5*time.Millisecond)
855- awaitFrame(t, sends, ports.MsgOutput)856+ awaitFrame(t, sends, wire.MsgOutput)
856 }857 }
857 858
858 func TestPickerSplitArrowNavigatesWithoutExiting(t *testing.T) {859 func TestPickerSplitArrowNavigatesWithoutExiting(t *testing.T) {
@@ -876,7 +877,7 @@ func TestPickerSplitArrowNavigatesWithoutExiting(t *testing.T) {
876 }()877 }()
877 878
878 d.enterPicker(sess, ac)879 d.enterPicker(sess, ac)
879- awaitFrame(t, sends, ports.MsgOutput)880+ awaitFrame(t, sends, wire.MsgOutput)
880 for _, input := range tc.input {881 for _, input := range tc.input {
881 d.handleInput(sess, ac, input)882 d.handleInput(sess, ac, input)
882 }883 }
@@ -950,7 +951,7 @@ func TestPickerLoneEscapeExitsAfterDelay(t *testing.T) {
950 defer releases[0]()951 defer releases[0]()
951 952
952 d.enterPicker(sess, ac)953 d.enterPicker(sess, ac)
953- awaitFrame(t, sends, ports.MsgOutput)954+ awaitFrame(t, sends, wire.MsgOutput)
954 clk := &signalClock{timers: make(chan *signalTimer, 1)}955 clk := &signalClock{timers: make(chan *signalTimer, 1)}
955 d.clock = clk956 d.clock = clk
956 d.handleInput(sess, ac, []byte("\x1b"))957 d.handleInput(sess, ac, []byte("\x1b"))
@@ -984,10 +985,10 @@ func TestPickerStalePaintAfterSessionSwitchSendsNoFrame(t *testing.T) {
984 d.sessions[sess2.id] = sess2985 d.sessions[sess2.id] = sess2
985 986
986 d.firstPaint(sess1, ac1)987 d.firstPaint(sess1, ac1)
987- awaitFrame(t, sends1, ports.MsgOutput)988+ awaitFrame(t, sends1, wire.MsgOutput)
988 d.stealClientForTarget(sess1, ac1, sess2, picker.Target{Session: sess2.id})989 d.stealClientForTarget(sess1, ac1, sess2, picker.Target{Session: sess2.id})
989 d.firstPaint(sess2, ac1)990 d.firstPaint(sess2, ac1)
990- awaitFrame(t, sends1, ports.MsgOutput)991+ awaitFrame(t, sends1, wire.MsgOutput)
991 require.Same(t, sess2, ac1.currentSession())992 require.Same(t, sess2, ac1.currentSession())
992 for len(sends1) > 0 {993 for len(sends1) > 0 {
993 <-sends1994 <-sends1
@@ -1011,7 +1012,7 @@ func TestPickerSessionSwitchPublishesBeforeInFlightPaintSendCompletes(t *testing
1011 enteredSend := make(chan struct{})1012 enteredSend := make(chan struct{})
1012 releaseSend := make(chan struct{})1013 releaseSend := make(chan struct{})
1013 tr := portsmocks.NewMockTransport(t)1014 tr := portsmocks.NewMockTransport(t)
1014- tr.EXPECT().Send(mock.Anything).RunAndReturn(func(ports.Frame) error {1015+ tr.EXPECT().Send(mock.Anything).RunAndReturn(func(wire.Frame) error {
1015 close(enteredSend)1016 close(enteredSend)
1016 <-releaseSend1017 <-releaseSend
1017 return nil1018 return nil
@@ -1370,14 +1371,14 @@ func TestPickerResizeRecomposesModal(t *testing.T) {
1370 defer releasePTY()1371 defer releasePTY()
1371 1372
1372 d.enterPicker(sess, ac)1373 d.enterPicker(sess, ac)
1373- awaitFrame(t, sends, ports.MsgOutput)1374+ awaitFrame(t, sends, wire.MsgOutput)
1374 d.resize(sess, ac, domain.Size{Cols: 100, Rows: 30})1375 d.resize(sess, ac, domain.Size{Cols: 100, Rows: 30})
1375 // Picker recomposition is the relevant event here; do not depend on a real1376 // Picker recomposition is the relevant event here; do not depend on a real
1376 // resize-idle timer in this synchronous rendering test.1377 // resize-idle timer in this synchronous rendering test.
1377 d.paint(sess, ac, false, nil)1378 d.paint(sess, ac, false, nil)
1378 1379
1379- out := awaitFrame(t, sends, ports.MsgOutput)1380+ out := awaitFrame(t, sends, wire.MsgOutput)
1380- msg, err := ports.UnmarshalOutput(out.Payload)1381+ msg, err := wire.UnmarshalOutput(out.Payload)
1381 require.NoError(t, err)1382 require.NoError(t, err)
1382 require.Contains(t, string(msg.Data), "┌")1383 require.Contains(t, string(msg.Data), "┌")
1383 require.Contains(t, string(msg.Data), "Sessions")1384 require.Contains(t, string(msg.Data), "Sessions")
@@ -1455,11 +1456,11 @@ func TestPickerEnterOnStoppedSessionRestoreFailureSurfacesNoticeAndStaysPut(t *t
1455 d.ptys = ptys1456 d.ptys = ptys
1456 1457
1457 d.enterPicker(from, ac)1458 d.enterPicker(from, ac)
1458- awaitFrame(t, sends, ports.MsgOutput)1459+ awaitFrame(t, sends, wire.MsgOutput)
1459 d.handleInput(from, ac, []byte("j"))1460 d.handleInput(from, ac, []byte("j"))
1460- awaitFrame(t, sends, ports.MsgOutput)1461+ awaitFrame(t, sends, wire.MsgOutput)
1461 d.handleInput(from, ac, []byte("\r"))1462 d.handleInput(from, ac, []byte("\r"))
1462- awaitFrame(t, sends, ports.MsgOutput)1463+ awaitFrame(t, sends, wire.MsgOutput)
1463 1464
1464 history := d.notices.history()1465 history := d.notices.history()
1465 require.Len(t, history, 1, "failed stopped-session restore must record exactly one notice")1466 require.Len(t, history, 1, "failed stopped-session restore must record exactly one notice")
@@ -1615,10 +1616,10 @@ func TestPickerKillActiveSessionSnapshotDeleteRefusalReportsOnceAndKeepsPicker(t
1615 require.NoError(t, d.catalogue.Create(record))1616 require.NoError(t, d.catalogue.Create(record))
1616 1617
1617 d.enterPicker(from, ac)1618 d.enterPicker(from, ac)
1618- awaitFrame(t, sends, ports.MsgOutput)1619+ awaitFrame(t, sends, wire.MsgOutput)
1619 // MRU-desc order is from(current), recent, older; "j" moves onto "recent".1620 // MRU-desc order is from(current), recent, older; "j" moves onto "recent".
1620 d.handleInput(from, ac, []byte("j"))1621 d.handleInput(from, ac, []byte("j"))
1621- awaitFrame(t, sends, ports.MsgOutput)1622+ awaitFrame(t, sends, wire.MsgOutput)
1622 d.handleInput(from, ac, []byte("x"))1623 d.handleInput(from, ac, []byte("x"))
1623 1624
1624 history := d.notices.history()1625 history := d.notices.history()
@@ -1662,11 +1663,11 @@ func TestPickerKillStoppedSessionPersistDeleteFailureSurfacesNoticeAndKeepsEntry
1662 d.inactive["stopped"] = inactiveSession{name: "stopped", cwd: "/tmp/stopped", createdAt: 7, incarnation: record.IncarnationID, record: record}1663 d.inactive["stopped"] = inactiveSession{name: "stopped", cwd: "/tmp/stopped", createdAt: 7, incarnation: record.IncarnationID, record: record}
1663 1664
1664 d.enterPicker(from, ac)1665 d.enterPicker(from, ac)
1665- awaitFrame(t, sends, ports.MsgOutput)1666+ awaitFrame(t, sends, wire.MsgOutput)
1666 d.handleInput(from, ac, []byte("j"))1667 d.handleInput(from, ac, []byte("j"))
1667- awaitFrame(t, sends, ports.MsgOutput)1668+ awaitFrame(t, sends, wire.MsgOutput)
1668 d.handleInput(from, ac, []byte("x"))1669 d.handleInput(from, ac, []byte("x"))
1669- awaitFrame(t, sends, ports.MsgOutput)1670+ awaitFrame(t, sends, wire.MsgOutput)
1670 1671
1671 history := d.notices.history()1672 history := d.notices.history()
1672 require.NotEmpty(t, history, "failed persisted-record delete must record a notice")1673 require.NotEmpty(t, history, "failed persisted-record delete must record a notice")
M internal/usecase/daemon/prompt_test.go +20 -19
@@ -9,6 +9,7 @@ import (
9 9
10 "github.com/bnema/vev/internal/domain"10 "github.com/bnema/vev/internal/domain"
11 "github.com/bnema/vev/internal/ports"11 "github.com/bnema/vev/internal/ports"
12+ "github.com/bnema/vev/internal/protocol/wire"
12 )13 )
13 14
14 type failingPTYFactory struct{ err error }15 type failingPTYFactory struct{ err error }
@@ -29,8 +30,8 @@ func TestEnterPromptRendersTitleAndPrefill(t *testing.T) {
29 defer release()30 defer release()
30 31
31 d.enterPrompt(sess, ac, " Rename session ", "0", func(string) error { return nil })32 d.enterPrompt(sess, ac, " Rename session ", "0", func(string) error { return nil })
32- out := awaitFrame(t, sends, ports.MsgOutput)33+ out := awaitFrame(t, sends, wire.MsgOutput)
33- msg, err := ports.UnmarshalOutput(out.Payload)34+ msg, err := wire.UnmarshalOutput(out.Payload)
34 require.NoError(t, err)35 require.NoError(t, err)
35 require.Contains(t, string(msg.Data), "Rename session")36 require.Contains(t, string(msg.Data), "Rename session")
36 require.Contains(t, string(msg.Data), "> 0")37 require.Contains(t, string(msg.Data), "> 0")
@@ -45,14 +46,14 @@ func TestPromptSubmitRenamesAndPromotesSession(t *testing.T) {
45 sess.ephemeral = true46 sess.ephemeral = true
46 47
47 d.enterPrompt(sess, ac, " Rename session ", sess.name, func(name string) error { return d.renameSession(sess, name) })48 d.enterPrompt(sess, ac, " Rename session ", sess.name, func(name string) error { return d.renameSession(sess, name) })
48- awaitFrame(t, sends, ports.MsgOutput)49+ awaitFrame(t, sends, wire.MsgOutput)
49 d.handlePromptInput(ac, []byte("work\r"))50 d.handlePromptInput(ac, []byte("work\r"))
50- repaint := awaitFrame(t, sends, ports.MsgOutput)51+ repaint := awaitFrame(t, sends, wire.MsgOutput)
51 52
52 require.False(t, ac.overlays.promptActive())53 require.False(t, ac.overlays.promptActive())
53 require.Equal(t, "0work", sess.name)54 require.Equal(t, "0work", sess.name)
54 require.False(t, sess.ephemeral)55 require.False(t, sess.ephemeral)
55- out, err := ports.UnmarshalOutput(repaint.Payload)56+ out, err := wire.UnmarshalOutput(repaint.Payload)
56 require.NoError(t, err)57 require.NoError(t, err)
57 require.Contains(t, string(out.Data), "0work")58 require.Contains(t, string(out.Data), "0work")
58 require.NotContains(t, string(out.Data), "0work*")59 require.NotContains(t, string(out.Data), "0work*")
@@ -69,13 +70,13 @@ func TestPromptSubmitErrorKeepsPromptOpen(t *testing.T) {
69 d.sessions["other"] = &session{sessionCore: sessionCore{id: "other", name: "taken"}, ctx: ctx, cancel: cancel, tabs: []*tab{newTestTabWithContext(p2, ctx, cancel)}}70 d.sessions["other"] = &session{sessionCore: sessionCore{id: "other", name: "taken"}, ctx: ctx, cancel: cancel, tabs: []*tab{newTestTabWithContext(p2, ctx, cancel)}}
70 71
71 d.enterPrompt(sess, ac, " Rename session ", "", func(name string) error { return d.renameSession(sess, name) })72 d.enterPrompt(sess, ac, " Rename session ", "", func(name string) error { return d.renameSession(sess, name) })
72- awaitFrame(t, sends, ports.MsgOutput)73+ awaitFrame(t, sends, wire.MsgOutput)
73 d.handlePromptInput(ac, []byte("taken\r"))74 d.handlePromptInput(ac, []byte("taken\r"))
74- repaint := awaitFrame(t, sends, ports.MsgOutput)75+ repaint := awaitFrame(t, sends, wire.MsgOutput)
75 76
76 require.True(t, ac.overlays.promptActive())77 require.True(t, ac.overlays.promptActive())
77 require.NotEqual(t, "taken", sess.name)78 require.NotEqual(t, "taken", sess.name)
78- out, err := ports.UnmarshalOutput(repaint.Payload)79+ out, err := wire.UnmarshalOutput(repaint.Payload)
79 require.NoError(t, err)80 require.NoError(t, err)
80 require.Contains(t, string(out.Data), "name already in use")81 require.Contains(t, string(out.Data), "name already in use")
81 }82 }
@@ -92,10 +93,10 @@ func TestPromptSubmitSessionSpawnFailureReportsOneSafeNotice(t *testing.T) {
92 d.enterPrompt(sess, ac, " Create session ", "", func(name string) error {93 d.enterPrompt(sess, ac, " Create session ", "", func(name string) error {
93 return d.createSessionAndSwitch(sess, ac, name)94 return d.createSessionAndSwitch(sess, ac, name)
94 })95 })
95- awaitFrame(t, sends, ports.MsgOutput)96+ awaitFrame(t, sends, wire.MsgOutput)
96 d.handlePromptInput(ac, []byte("new\r"))97 d.handlePromptInput(ac, []byte("new\r"))
97- repaint := awaitFrame(t, sends, ports.MsgOutput)98+ repaint := awaitFrame(t, sends, wire.MsgOutput)
98- output, err := ports.UnmarshalOutput(repaint.Payload)99+ output, err := wire.UnmarshalOutput(repaint.Payload)
99 require.NoError(t, err)100 require.NoError(t, err)
100 require.Contains(t, string(output.Data), "couldn't create session: shell failed to start")101 require.Contains(t, string(output.Data), "couldn't create session: shell failed to start")
101 102
@@ -140,10 +141,10 @@ func TestPromptSubmitValidationErrorStaysInlineOnly(t *testing.T) {
140 d.enterPrompt(sess, ac, " Create session ", "", func(name string) error {141 d.enterPrompt(sess, ac, " Create session ", "", func(name string) error {
141 return d.createSessionAndSwitch(sess, ac, name)142 return d.createSessionAndSwitch(sess, ac, name)
142 })143 })
143- awaitFrame(t, sends, ports.MsgOutput)144+ awaitFrame(t, sends, wire.MsgOutput)
144 d.handlePromptInput(ac, tt.input)145 d.handlePromptInput(ac, tt.input)
145- repaint := awaitFrame(t, sends, ports.MsgOutput)146+ repaint := awaitFrame(t, sends, wire.MsgOutput)
146- output, err := ports.UnmarshalOutput(repaint.Payload)147+ output, err := wire.UnmarshalOutput(repaint.Payload)
147 require.NoError(t, err)148 require.NoError(t, err)
148 require.Contains(t, string(output.Data), tt.wantErr.Error())149 require.Contains(t, string(output.Data), tt.wantErr.Error())
149 150
@@ -160,9 +161,9 @@ func TestPromptEscapeCancelsWithoutRename(t *testing.T) {
160 oldName := sess.name161 oldName := sess.name
161 162
162 d.enterPrompt(sess, ac, " Rename session ", oldName, func(name string) error { return d.renameSession(sess, name) })163 d.enterPrompt(sess, ac, " Rename session ", oldName, func(name string) error { return d.renameSession(sess, name) })
163- awaitFrame(t, sends, ports.MsgOutput)164+ awaitFrame(t, sends, wire.MsgOutput)
164 d.handlePromptInput(ac, []byte("new\x1b"))165 d.handlePromptInput(ac, []byte("new\x1b"))
165- awaitFrame(t, sends, ports.MsgOutput)166+ awaitFrame(t, sends, wire.MsgOutput)
166 167
167 require.False(t, ac.overlays.promptActive())168 require.False(t, ac.overlays.promptActive())
168 require.Equal(t, oldName, sess.name)169 require.Equal(t, oldName, sess.name)
@@ -176,13 +177,13 @@ func TestPaletteRNSOpensRenamePrompt(t *testing.T) {
176 sess.ephemeral = true177 sess.ephemeral = true
177 178
178 d.enterPalette(sess, ac)179 d.enterPalette(sess, ac)
179- awaitFrame(t, sends, ports.MsgOutput)180+ awaitFrame(t, sends, wire.MsgOutput)
180 d.handlePaletteInput(ac, []byte("RNS\r"))181 d.handlePaletteInput(ac, []byte("RNS\r"))
181- out := awaitFrame(t, sends, ports.MsgOutput)182+ out := awaitFrame(t, sends, wire.MsgOutput)
182 183
183 require.False(t, ac.overlays.paletteActive())184 require.False(t, ac.overlays.paletteActive())
184 require.True(t, ac.overlays.promptActive())185 require.True(t, ac.overlays.promptActive())
185- msg, err := ports.UnmarshalOutput(out.Payload)186+ msg, err := wire.UnmarshalOutput(out.Payload)
186 require.NoError(t, err)187 require.NoError(t, err)
187 require.Contains(t, string(msg.Data), "Rename session")188 require.Contains(t, string(msg.Data), "Rename session")
188 require.Contains(t, string(msg.Data), "> 0")189 require.Contains(t, string(msg.Data), "> 0")
M internal/usecase/daemon/recent_route_snapshot_test.go +6 -6
@@ -4,8 +4,8 @@ import (
4 "testing"4 "testing"
5 5
6 "github.com/bnema/vev/internal/domain"6 "github.com/bnema/vev/internal/domain"
7- "github.com/bnema/vev/internal/ports"
8 "github.com/bnema/vev/internal/protocol"7 "github.com/bnema/vev/internal/protocol"
8+ "github.com/bnema/vev/internal/protocol/wire"
9 "github.com/stretchr/testify/require"9 "github.com/stretchr/testify/require"
10 )10 )
11 11
@@ -69,11 +69,11 @@ func TestRecentRouteSnapshotRepaintsWithoutDeferredIdentity(t *testing.T) {
69 ac.installTestAttachmentCapability(token)69 ac.installTestAttachmentCapability(token)
70 ac.setRouteSnapshot(protocol.RecentRouteSnapshot{Generation: 1})70 ac.setRouteSnapshot(protocol.RecentRouteSnapshot{Generation: 1})
71 71
72- payload, err := ports.MarshalRecentRouteSnapshot(protocol.RecentRouteSnapshot{Generation: 2})72+ payload, err := wire.MarshalRecentRouteSnapshot(protocol.RecentRouteSnapshot{Generation: 2})
73 require.NoError(t, err)73 require.NoError(t, err)
74- require.False(t, d.handleAttachmentClientFrame(token, ports.Frame{Type: ports.MsgRecentRouteSnapshot, Payload: payload}))74+ require.False(t, d.handleAttachmentClientFrame(token, wire.Frame{Type: wire.MsgRecentRouteSnapshot, Payload: payload}))
75 75
76- awaitFrame(t, sends, ports.MsgOutput)76+ awaitFrame(t, sends, wire.MsgOutput)
77 }77 }
78 78
79 func TestAttachmentRouteSnapshotCopiesPublishedEntries(t *testing.T) {79 func TestAttachmentRouteSnapshotCopiesPublishedEntries(t *testing.T) {
@@ -115,11 +115,11 @@ func TestPaletteRecentRouteSelectionSendsTypedClientAction(t *testing.T) {
115 var action protocol.RouteNavigationAction115 var action protocol.RouteNavigationAction
116 found := false116 found := false
117 for _, frame := range frames {117 for _, frame := range frames {
118- if frame.Type != ports.MsgNavigateRecentRoute {118+ if frame.Type != wire.MsgNavigateRecentRoute {
119 continue119 continue
120 }120 }
121 var err error121 var err error
122- action, err = ports.UnmarshalRouteNavigationAction(frame.Payload)122+ action, err = wire.UnmarshalRouteNavigationAction(frame.Payload)
123 require.NoError(t, err)123 require.NoError(t, err)
124 found = true124 found = true
125 }125 }
M internal/usecase/daemon/remote_attach.go +7 -6
@@ -7,6 +7,7 @@ import (
7 "github.com/bnema/vev/internal/domain"7 "github.com/bnema/vev/internal/domain"
8 "github.com/bnema/vev/internal/ports"8 "github.com/bnema/vev/internal/ports"
9 "github.com/bnema/vev/internal/protocol"9 "github.com/bnema/vev/internal/protocol"
10+ "github.com/bnema/vev/internal/protocol/wire"
10 )11 )
11 12
12 // routeRemoteTargetWithContext is the exact-target branch for picker handoffs.13 // routeRemoteTargetWithContext is the exact-target branch for picker handoffs.
@@ -119,12 +120,12 @@ func (d *Daemon) sendNavigationActionForAttachment(effect *attachmentEffect, act
119 effect.ac.clearParkedRoute()120 effect.ac.clearParkedRoute()
120 }121 }
121 }122 }
122- payload := ports.MarshalNavigationDirective(directive)123+ payload := wire.MarshalNavigationDirective(directive)
123 if payload == nil {124 if payload == nil {
124 rollback()125 rollback()
125 return errAttachmentTransition126 return errAttachmentTransition
126 }127 }
127- if err := effect.sendControl(ports.Frame{Type: ports.MsgNavigationAction, Payload: payload}); err != nil {128+ if err := effect.sendControl(wire.Frame{Type: wire.MsgNavigationAction, Payload: payload}); err != nil {
128 rollback()129 rollback()
129 return err130 return err
130 }131 }
@@ -132,11 +133,11 @@ func (d *Daemon) sendNavigationActionForAttachment(effect *attachmentEffect, act
132 }133 }
133 134
134 func (d *Daemon) sendRecentRouteNavigationActionForAttachment(effect *attachmentEffect, action protocol.RouteNavigationAction) error {135 func (d *Daemon) sendRecentRouteNavigationActionForAttachment(effect *attachmentEffect, action protocol.RouteNavigationAction) error {
135- payload, err := ports.MarshalRouteNavigationAction(action)136+ payload, err := wire.MarshalRouteNavigationAction(action)
136 if err != nil {137 if err != nil {
137 return errAttachmentTransition138 return errAttachmentTransition
138 }139 }
139- return effect.sendControl(ports.Frame{Type: ports.MsgNavigateRecentRoute, Payload: payload})140+ return effect.sendControl(wire.Frame{Type: wire.MsgNavigateRecentRoute, Payload: payload})
140 }141 }
141 142
142 func (d *Daemon) sendCommittedRouteIdentityForAttachment(effect *attachmentEffect) error {143 func (d *Daemon) sendCommittedRouteIdentityForAttachment(effect *attachmentEffect) error {
@@ -149,11 +150,11 @@ func (d *Daemon) sendCommittedRouteIdentityForAttachment(effect *attachmentEffec
149 Ephemeral: effect.sess.ephemeral,150 Ephemeral: effect.sess.ephemeral,
150 }151 }
151 effect.sess.mu.Unlock()152 effect.sess.mu.Unlock()
152- payload, err := ports.MarshalCommittedRouteIdentity(identity)153+ payload, err := wire.MarshalCommittedRouteIdentity(identity)
153 if err != nil {154 if err != nil {
154 return errAttachmentTransition155 return errAttachmentTransition
155 }156 }
156- return effect.sendControl(ports.Frame{Type: ports.MsgCommittedRouteIdentity, Payload: payload})157+ return effect.sendControl(wire.Frame{Type: wire.MsgCommittedRouteIdentity, Payload: payload})
157 }158 }
158 159
159 func (d *Daemon) finishRouteAttach(sess *session, tr ports.Transport, sz domain.Size, h protocol.Hello, routeCreated, purge bool) (*attachedClient, error) {160 func (d *Daemon) finishRouteAttach(sess *session, tr ports.Transport, sz domain.Size, h protocol.Hello, routeCreated, purge bool) (*attachedClient, error) {
M internal/usecase/daemon/remote_attach_test.go +8 -7
@@ -9,6 +9,7 @@ import (
9 "github.com/bnema/vev/internal/domain"9 "github.com/bnema/vev/internal/domain"
10 "github.com/bnema/vev/internal/ports"10 "github.com/bnema/vev/internal/ports"
11 "github.com/bnema/vev/internal/protocol"11 "github.com/bnema/vev/internal/protocol"
12+ "github.com/bnema/vev/internal/protocol/wire"
12 "github.com/bnema/vev/internal/usecase/picker"13 "github.com/bnema/vev/internal/usecase/picker"
13 )14 )
14 15
@@ -30,7 +31,7 @@ func TestLocalPickerOfferCarriesExactLifecycle(t *testing.T) {
30 31
31 require.NoError(t, d.switchToTargetForAttachment(effect, picker.Target{Session: target.id}, sessionHandoffGuard{allowSamePeer: true}, "picker-select"))32 require.NoError(t, d.switchToTargetForAttachment(effect, picker.Target{Session: target.id}, sessionHandoffGuard{allowSamePeer: true}, "picker-select"))
32 frame := receiveRemotePicker(t, sends, "local attach target")33 frame := receiveRemotePicker(t, sends, "local attach target")
33- got, err := ports.UnmarshalAttachTarget(frame.Payload)34+ got, err := wire.UnmarshalAttachTarget(frame.Payload)
34 require.NoError(t, err)35 require.NoError(t, err)
35 require.Empty(t, got.Endpoint)36 require.Empty(t, got.Endpoint)
36 require.Equal(t, protocol.IntentAttach, got.Intent)37 require.Equal(t, protocol.IntentAttach, got.Intent)
@@ -61,10 +62,10 @@ func TestStoppedLocalPickerHandoffWaitsForClientClose(t *testing.T) {
61 }, sessionHandoffGuard{closePicker: true, allowSamePeer: true}, "picker-select"))62 }, sessionHandoffGuard{closePicker: true, allowSamePeer: true}, "picker-select"))
62 63
63 cleanup := receiveRemotePicker(t, sends, "graphics cleanup")64 cleanup := receiveRemotePicker(t, sends, "graphics cleanup")
64- require.Equal(t, ports.MsgOutput, cleanup.Type)65+ require.Equal(t, wire.MsgOutput, cleanup.Type)
65 handoff := receiveRemotePicker(t, sends, "stopped local attach target")66 handoff := receiveRemotePicker(t, sends, "stopped local attach target")
66- require.Equal(t, ports.MsgAttachTarget, handoff.Type)67+ require.Equal(t, wire.MsgAttachTarget, handoff.Type)
67- target, err := ports.UnmarshalAttachTarget(handoff.Payload)68+ target, err := wire.UnmarshalAttachTarget(handoff.Payload)
68 require.NoError(t, err)69 require.NoError(t, err)
69 require.False(t, target.SamePeer)70 require.False(t, target.SamePeer)
70 require.Equal(t, &protocol.ExactSessionTarget{LifecycleID: lifecycle, SessionName: "stopped"}, target.ExactTarget)71 require.Equal(t, &protocol.ExactSessionTarget{LifecycleID: lifecycle, SessionName: "stopped"}, target.ExactTarget)
@@ -93,7 +94,7 @@ func TestRemotePickerRichHandoffCarriesLifecycleTabAndPolicy(t *testing.T) {
93 target := picker.Target{Session: key.ID(), RemoteKey: &key, RemoteTarget: &remoteTarget, TabID: "tab-1"}94 target := picker.Target{Session: key.ID(), RemoteKey: &key, RemoteTarget: &remoteTarget, TabID: "tab-1"}
94 require.NoError(t, d.sendRemoteAttachTargetForAttachment(effect, target, sessionHandoffGuard{closePicker: true}, "picker-select"))95 require.NoError(t, d.sendRemoteAttachTargetForAttachment(effect, target, sessionHandoffGuard{closePicker: true}, "picker-select"))
95 frame := receiveRemotePicker(t, sends, "rich attach target")96 frame := receiveRemotePicker(t, sends, "rich attach target")
96- got, err := ports.UnmarshalAttachTarget(frame.Payload)97+ got, err := wire.UnmarshalAttachTarget(frame.Payload)
97 require.NoError(t, err)98 require.NoError(t, err)
98 require.NotNil(t, got.RemoteTarget)99 require.NotNil(t, got.RemoteTarget)
99 require.Equal(t, remoteTarget, *got.RemoteTarget)100 require.Equal(t, remoteTarget, *got.RemoteTarget)
@@ -140,7 +141,7 @@ func TestRemotePickerRichHandoffRejectsMismatchedRouteKey(t *testing.T) {
140 for {141 for {
141 select {142 select {
142 case frame := <-sends:143 case frame := <-sends:
143- require.NotEqual(t, ports.MsgAttachTarget, frame.Type)144+ require.NotEqual(t, wire.MsgAttachTarget, frame.Type)
144 default:145 default:
145 return146 return
146 }147 }
@@ -174,7 +175,7 @@ func TestRemotePickerRichHandoffRejectsReplacedLifecycle(t *testing.T) {
174 for {175 for {
175 select {176 select {
176 case frame := <-sends:177 case frame := <-sends:
177- require.NotEqual(t, ports.MsgAttachTarget, frame.Type, "rejected handoff must not send an attach target")178+ require.NotEqual(t, wire.MsgAttachTarget, frame.Type, "rejected handoff must not send an attach target")
178 default:179 default:
179 return180 return
180 }181 }
M internal/usecase/daemon/remote_picker_test.go +9 -8
@@ -18,6 +18,7 @@ import (
18 "github.com/bnema/vev/internal/ports"18 "github.com/bnema/vev/internal/ports"
19 portsmocks "github.com/bnema/vev/internal/ports/mocks"19 portsmocks "github.com/bnema/vev/internal/ports/mocks"
20 "github.com/bnema/vev/internal/protocol"20 "github.com/bnema/vev/internal/protocol"
21+ "github.com/bnema/vev/internal/protocol/wire"
21 "github.com/bnema/vev/internal/usecase/picker"22 "github.com/bnema/vev/internal/usecase/picker"
22 )23 )
23 24
@@ -696,8 +697,8 @@ func TestRemotePickerHandoffSendsTargetAndLeavesNoShadowSession(t *testing.T) {
696 require.NoError(t, d.sendRemoteAttachTargetForAttachment(effect, target, sessionHandoffGuard{closePicker: true}, "picker-select"))697 require.NoError(t, d.sendRemoteAttachTargetForAttachment(effect, target, sessionHandoffGuard{closePicker: true}, "picker-select"))
697 698
698 frame := receiveRemotePicker(t, sends, "attach target")699 frame := receiveRemotePicker(t, sends, "attach target")
699- require.Equal(t, ports.MsgAttachTarget, frame.Type)700+ require.Equal(t, wire.MsgAttachTarget, frame.Type)
700- got, err := ports.UnmarshalAttachTarget(frame.Payload)701+ got, err := wire.UnmarshalAttachTarget(frame.Payload)
701 require.NoError(t, err)702 require.NoError(t, err)
702 require.Equal(t, protocol.AttachTarget{Endpoint: "arch", Session: "work", Intent: protocol.IntentAttach, RemoteTarget: &remoteTarget, EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned}, got)703 require.Equal(t, protocol.AttachTarget{Endpoint: "arch", Session: "work", Intent: protocol.IntentAttach, RemoteTarget: &remoteTarget, EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned}, got)
703 require.Nil(t, ac.currentAttachmentSession())704 require.Nil(t, ac.currentAttachmentSession())
@@ -748,7 +749,7 @@ func TestRemotePickerSelectsStoppedRemoteTabAndRestoresIt(t *testing.T) {
748 require.NoError(t, local.sendRemoteAttachTargetForAttachment(effect, selected, sessionHandoffGuard{closePicker: true}, "picker-select"))749 require.NoError(t, local.sendRemoteAttachTargetForAttachment(effect, selected, sessionHandoffGuard{closePicker: true}, "picker-select"))
749 750
750 frame := receiveRemotePicker(t, sends, "stopped remote target")751 frame := receiveRemotePicker(t, sends, "stopped remote target")
751- handoff, err := ports.UnmarshalAttachTarget(frame.Payload)752+ handoff, err := wire.UnmarshalAttachTarget(frame.Payload)
752 require.NoError(t, err)753 require.NoError(t, err)
753 require.NotNil(t, handoff.RemoteTarget)754 require.NotNil(t, handoff.RemoteTarget)
754 require.Equal(t, selected.RemoteTarget, handoff.RemoteTarget)755 require.Equal(t, selected.RemoteTarget, handoff.RemoteTarget)
@@ -819,7 +820,7 @@ func TestRemotePickerResurrectsStoppedRemoteSessionWithoutTabMetadata(t *testing
819 require.NoError(t, local.sendRemoteAttachTargetForAttachment(effect, selected, sessionHandoffGuard{closePicker: true}, "picker-select"))820 require.NoError(t, local.sendRemoteAttachTargetForAttachment(effect, selected, sessionHandoffGuard{closePicker: true}, "picker-select"))
820 821
821 frame := receiveRemotePicker(t, sends, "stopped remote target without tab metadata")822 frame := receiveRemotePicker(t, sends, "stopped remote target without tab metadata")
822- handoff, err := ports.UnmarshalAttachTarget(frame.Payload)823+ handoff, err := wire.UnmarshalAttachTarget(frame.Payload)
823 require.NoError(t, err)824 require.NoError(t, err)
824 require.Equal(t, selected.RemoteTarget, handoff.RemoteTarget)825 require.Equal(t, selected.RemoteTarget, handoff.RemoteTarget)
825 826
@@ -862,8 +863,8 @@ func TestNavigationActionHandoffSendsBoundedAction(t *testing.T) {
862 defer effect.End()863 defer effect.End()
863 require.NoError(t, d.sendNavigationActionForAttachment(effect, tt.action))864 require.NoError(t, d.sendNavigationActionForAttachment(effect, tt.action))
864 frame := receiveRemotePicker(t, sends, "navigation action")865 frame := receiveRemotePicker(t, sends, "navigation action")
865- require.Equal(t, ports.MsgNavigationAction, frame.Type)866+ require.Equal(t, wire.MsgNavigationAction, frame.Type)
866- directive, err := ports.UnmarshalNavigationDirective(frame.Payload)867+ directive, err := wire.UnmarshalNavigationDirective(frame.Payload)
867 require.NoError(t, err)868 require.NoError(t, err)
868 require.Equal(t, tt.action, directive.Action)869 require.Equal(t, tt.action, directive.Action)
869 if tt.action == protocol.NavigationOpenHomePicker {870 if tt.action == protocol.NavigationOpenHomePicker {
@@ -920,10 +921,10 @@ func TestRemotePickerHandoffSendFailureKeepsPickerOpen(t *testing.T) {
920 require.Same(t, sess, ac.currentAttachmentSession())921 require.Same(t, sess, ac.currentAttachmentSession())
921 }922 }
922 923
923-func addRemoteRefreshPickerOwner(t *testing.T, d *Daemon, id domain.SessionID, transports ...ports.Transport) (*session, *attachedClient, chan ports.Frame) {924+func addRemoteRefreshPickerOwner(t *testing.T, d *Daemon, id domain.SessionID, transports ...ports.Transport) (*session, *attachedClient, chan wire.Frame) {
924 t.Helper()925 t.Helper()
925 var tr ports.Transport926 var tr ports.Transport
926- var sends chan ports.Frame927+ var sends chan wire.Frame
927 if len(transports) != 0 {928 if len(transports) != 0 {
928 tr = transports[0]929 tr = transports[0]
929 } else {930 } else {
M internal/usecase/daemon/remote_preview.go +6 -5
@@ -7,11 +7,12 @@ import (
7 "github.com/bnema/vev/internal/domain"7 "github.com/bnema/vev/internal/domain"
8 "github.com/bnema/vev/internal/ports"8 "github.com/bnema/vev/internal/ports"
9 "github.com/bnema/vev/internal/protocol"9 "github.com/bnema/vev/internal/protocol"
10+ "github.com/bnema/vev/internal/protocol/wire"
10 )11 )
11 12
12-func (d *Daemon) handleRemotePreview(tr ports.Transport, frame ports.Frame) error {13+func (d *Daemon) handleRemotePreview(tr ports.Transport, frame wire.Frame) error {
13 defer func() { _ = tr.Close() }()14 defer func() { _ = tr.Close() }()
14- request, err := ports.UnmarshalRemotePreviewRequest(frame.Payload)15+ request, err := wire.UnmarshalRemotePreviewRequest(frame.Payload)
15 if err != nil {16 if err != nil {
16 return d.sendRemotePreview(tr, protocol.RemotePreview{Version: protocol.RemotePreviewSchemaVersion, Status: protocol.RemotePreviewMalformed})17 return d.sendRemotePreview(tr, protocol.RemotePreview{Version: protocol.RemotePreviewSchemaVersion, Status: protocol.RemotePreviewMalformed})
17 }18 }
@@ -27,11 +28,11 @@ func (d *Daemon) handleRemotePreview(tr ports.Transport, frame ports.Frame) erro
27 }28 }
28 29
29 func (d *Daemon) sendRemotePreview(tr ports.Transport, preview protocol.RemotePreview) error {30 func (d *Daemon) sendRemotePreview(tr ports.Transport, preview protocol.RemotePreview) error {
30- payload := ports.MarshalRemotePreview(preview)31+ payload := wire.MarshalRemotePreview(preview)
31 if payload == nil {32 if payload == nil {
32- payload = ports.MarshalRemotePreview(protocol.RemotePreview{Version: protocol.RemotePreviewSchemaVersion, Status: protocol.RemotePreviewMalformed})33+ payload = wire.MarshalRemotePreview(protocol.RemotePreview{Version: protocol.RemotePreviewSchemaVersion, Status: protocol.RemotePreviewMalformed})
33 }34 }
34- return d.boundedControlSend(tr, ports.Frame{Type: ports.MsgRemotePreviewResponse, Payload: payload})35+ return d.boundedControlSend(tr, wire.Frame{Type: wire.MsgRemotePreviewResponse, Payload: payload})
35 }36 }
36 37
37 var errRemotePreviewNoSuchTarget = errors.New("daemon: remote preview target does not exist")38 var errRemotePreviewNoSuchTarget = errors.New("daemon: remote preview target does not exist")
M internal/usecase/daemon/render_coordinator_handshake_test.go +7 -7
@@ -9,13 +9,13 @@ import (
9 "github.com/stretchr/testify/require"9 "github.com/stretchr/testify/require"
10 10
11 "github.com/bnema/vev/internal/domain"11 "github.com/bnema/vev/internal/domain"
12- "github.com/bnema/vev/internal/ports"12+ "github.com/bnema/vev/internal/protocol/wire"
13 )13 )
14 14
15 // fanoutBlockingTransport blocks only the first output send. The paired15 // fanoutBlockingTransport blocks only the first output send. The paired
16 // healthy attachment must still receive its frame while this peer is stuck.16 // healthy attachment must still receive its frame while this peer is stuck.
17 type fanoutBlockingTransport struct {17 type fanoutBlockingTransport struct {
18- sent chan ports.Frame18+ sent chan wire.Frame
19 entered chan struct{}19 entered chan struct{}
20 release chan struct{}20 release chan struct{}
21 done chan struct{}21 done chan struct{}
@@ -26,16 +26,16 @@ type fanoutBlockingTransport struct {
26 26
27 func newFanoutBlockingTransport() *fanoutBlockingTransport {27 func newFanoutBlockingTransport() *fanoutBlockingTransport {
28 return &fanoutBlockingTransport{28 return &fanoutBlockingTransport{
29- sent: make(chan ports.Frame, 8),29+ sent: make(chan wire.Frame, 8),
30 entered: make(chan struct{}),30 entered: make(chan struct{}),
31 release: make(chan struct{}),31 release: make(chan struct{}),
32 done: make(chan struct{}),32 done: make(chan struct{}),
33 }33 }
34 }34 }
35 35
36-func (t *fanoutBlockingTransport) Send(frame ports.Frame) error {36+func (t *fanoutBlockingTransport) Send(frame wire.Frame) error {
37 t.sent <- frame37 t.sent <- frame
38- if frame.Type == ports.MsgOutput {38+ if frame.Type == wire.MsgOutput {
39 t.enteredOne.Do(func() { close(t.entered) })39 t.enteredOne.Do(func() { close(t.entered) })
40 <-t.release40 <-t.release
41 }41 }
@@ -43,7 +43,7 @@ func (t *fanoutBlockingTransport) Send(frame ports.Frame) error {
43 return nil43 return nil
44 }44 }
45 45
46-func (t *fanoutBlockingTransport) Recv() (ports.Frame, error) { return ports.Frame{}, io.EOF }46+func (t *fanoutBlockingTransport) Recv() (wire.Frame, error) { return wire.Frame{}, io.EOF }
47 47
48 func (t *fanoutBlockingTransport) Close() error {48 func (t *fanoutBlockingTransport) Close() error {
49 t.releaseOne.Do(func() { close(t.release) })49 t.releaseOne.Do(func() { close(t.release) })
@@ -102,7 +102,7 @@ func TestRenderCoordinatorFanoutDoesNotWaitForSlowTransport(t *testing.T) {
102 102
103 select {103 select {
104 case frame := <-healthySends:104 case frame := <-healthySends:
105- require.Equal(t, ports.MsgOutput, frame.Type, "healthy attachment did not receive output")105+ require.Equal(t, wire.MsgOutput, frame.Type, "healthy attachment did not receive output")
106 case <-time.After(2 * time.Second):106 case <-time.After(2 * time.Second):
107 t.Fatal("healthy attachment remained blocked behind the slow transport")107 t.Fatal("healthy attachment remained blocked behind the slow transport")
108 }108 }
M internal/usecase/daemon/render_coordinator_test.go +4 -3
@@ -15,6 +15,7 @@ import (
15 "github.com/bnema/vev/internal/ports"15 "github.com/bnema/vev/internal/ports"
16 portsmocks "github.com/bnema/vev/internal/ports/mocks"16 portsmocks "github.com/bnema/vev/internal/ports/mocks"
17 "github.com/bnema/vev/internal/protocol"17 "github.com/bnema/vev/internal/protocol"
18+ "github.com/bnema/vev/internal/protocol/wire"
18 "github.com/bnema/vev/internal/usecase/keys"19 "github.com/bnema/vev/internal/usecase/keys"
19 "github.com/bnema/vev/internal/usecase/layout"20 "github.com/bnema/vev/internal/usecase/layout"
20 )21 )
@@ -267,12 +268,12 @@ func awaitLatestCoordinatorTimer(t *testing.T, clk *coordinatorMockClock) *coord
267 }268 }
268 }269 }
269 270
270-func requireNoCoordinatorOutputFrame(t *testing.T, sends chan ports.Frame) {271+func requireNoCoordinatorOutputFrame(t *testing.T, sends chan wire.Frame) {
271 t.Helper()272 t.Helper()
272 for {273 for {
273 select {274 select {
274 case frame := <-sends:275 case frame := <-sends:
275- if frame.Type == ports.MsgRoutePosition {276+ if frame.Type == wire.MsgRoutePosition {
276 continue277 continue
277 }278 }
278 t.Fatalf("unexpected output frame: %+v", frame)279 t.Fatalf("unexpected output frame: %+v", frame)
@@ -1509,7 +1510,7 @@ func TestRenderCoordinatorSyncBatchSurvivesAttachmentLifecycle(t *testing.T) {
1509 }1510 }
1510 1511
1511 func TestRenderCoordinatorResizeEpochDispatch(t *testing.T) {1512 func TestRenderCoordinatorResizeEpochDispatch(t *testing.T) {
1512- newResizeFixture := func(t *testing.T) (*Daemon, *session, *attachedClient, chan ports.Frame, *coordinatorMockClock, chan renderInvalidation) {1513+ newResizeFixture := func(t *testing.T) (*Daemon, *session, *attachedClient, chan wire.Frame, *coordinatorMockClock, chan renderInvalidation) {
1513 t.Helper()1514 t.Helper()
1514 p, releasePTY := newBlockingPTY(t)1515 p, releasePTY := newBlockingPTY(t)
1515 t.Cleanup(releasePTY)1516 t.Cleanup(releasePTY)
M internal/usecase/daemon/render_damage_receipt_test.go +3 -2
@@ -6,6 +6,7 @@ import (
6 renderer "github.com/bnema/vev-vt"6 renderer "github.com/bnema/vev-vt"
7 "github.com/bnema/vev/internal/domain"7 "github.com/bnema/vev/internal/domain"
8 "github.com/bnema/vev/internal/ports"8 "github.com/bnema/vev/internal/ports"
9+ "github.com/bnema/vev/internal/protocol/wire"
9 "github.com/bnema/vev/internal/usecase/layout"10 "github.com/bnema/vev/internal/usecase/layout"
10 "github.com/bnema/vev/internal/usecase/picker"11 "github.com/bnema/vev/internal/usecase/picker"
11 "github.com/stretchr/testify/require"12 "github.com/stretchr/testify/require"
@@ -156,7 +157,7 @@ func TestRenderDamageReceiptRetainsRealVTDamageAcrossFailedEmission(t *testing.T
156 state, composed = captureComposeForReceiptTest(t, sess, ac)157 state, composed = captureComposeForReceiptTest(t, sess, ac)
157 require.True(t, d.emitFrame(sess, ac, state, composed))158 require.True(t, d.emitFrame(sess, ac, state, composed))
158 frame := <-sends159 frame := <-sends
159- out, err := ports.UnmarshalOutput(frame.Payload)160+ out, err := wire.UnmarshalOutput(frame.Payload)
160 require.NoError(t, err)161 require.NoError(t, err)
161 require.Contains(t, string(out.Data), "changed", "retry must emit the retained terminal bytes")162 require.Contains(t, string(out.Data), "changed", "retry must emit the retained terminal bytes")
162 p.mu.Lock()163 p.mu.Lock()
@@ -223,7 +224,7 @@ func TestPrepareFailureFallbackOnlySuppressesRecursiveNoticePaint(t *testing.T)
223 require.True(t, d.emitFrame(sess, ac, state, composed))224 require.True(t, d.emitFrame(sess, ac, state, composed))
224 require.Len(t, d.notices.history(), want)225 require.Len(t, d.notices.history(), want)
225 frame := <-sends226 frame := <-sends
226- require.Equal(t, ports.MsgOutput, frame.Type, "outer failure %d must repaint its notice", want)227+ require.Equal(t, wire.MsgOutput, frame.Type, "outer failure %d must repaint its notice", want)
227 require.False(t, ac.prepareFailureFallback.Load())228 require.False(t, ac.prepareFailureFallback.Load())
228 }229 }
229 }230 }
M internal/usecase/daemon/render_pipeline.go +3 -2
@@ -7,6 +7,7 @@ import (
7 "github.com/bnema/vev/internal/domain"7 "github.com/bnema/vev/internal/domain"
8 "github.com/bnema/vev/internal/ports"8 "github.com/bnema/vev/internal/ports"
9 "github.com/bnema/vev/internal/protocol"9 "github.com/bnema/vev/internal/protocol"
10+ "github.com/bnema/vev/internal/protocol/wire"
10 "github.com/bnema/vev/internal/usecase/layout"11 "github.com/bnema/vev/internal/usecase/layout"
11 "github.com/bnema/vev/internal/usecase/notices"12 "github.com/bnema/vev/internal/usecase/notices"
12 "github.com/bnema/vev/internal/usecase/palette"13 "github.com/bnema/vev/internal/usecase/palette"
@@ -539,7 +540,7 @@ func (d *Daemon) emitFrame(entry *session, ac *attachedClient, state *capturedRe
539 }540 }
540 541
541 if position.ActiveTabID != "" && position != ac.output.lastRoutePosition {542 if position.ActiveTabID != "" && position != ac.output.lastRoutePosition {
542- payload, positionMarshalErr := ports.MarshalRoutePosition(position)543+ payload, positionMarshalErr := wire.MarshalRoutePosition(position)
543 if positionMarshalErr != nil {544 if positionMarshalErr != nil {
544 d.log.Error("marshal route position", "err", positionMarshalErr)545 d.log.Error("marshal route position", "err", positionMarshalErr)
545 } else {546 } else {
@@ -560,7 +561,7 @@ func (d *Daemon) emitFrame(entry *session, ac *attachedClient, state *capturedRe
560 if async, ok := sendTr.(ports.AsyncTransport); ok {561 if async, ok := sendTr.(ports.AsyncTransport); ok {
561 send = async.SendAsync562 send = async.SendAsync
562 }563 }
563- sendErr = send(ports.Frame{Type: ports.MsgRoutePosition, Payload: payload})564+ sendErr = send(wire.Frame{Type: wire.MsgRoutePosition, Payload: payload})
564 }565 }
565 }566 }
566 if marks.attachmentEffect != nil && interruptible {567 if marks.attachmentEffect != nil && interruptible {
M internal/usecase/daemon/render_pipeline_cache_test.go +5 -5
@@ -8,7 +8,7 @@ import (
8 8
9 renderer "github.com/bnema/vev-vt"9 renderer "github.com/bnema/vev-vt"
10 "github.com/bnema/vev/internal/domain"10 "github.com/bnema/vev/internal/domain"
11- "github.com/bnema/vev/internal/ports"11+ "github.com/bnema/vev/internal/protocol/wire"
12 "github.com/bnema/vev/internal/usecase/layout"12 "github.com/bnema/vev/internal/usecase/layout"
13 themeui "github.com/bnema/vev/internal/usecase/theme"13 themeui "github.com/bnema/vev/internal/usecase/theme"
14 "github.com/bnema/vev/internal/usecase/ui"14 "github.com/bnema/vev/internal/usecase/ui"
@@ -17,9 +17,9 @@ import (
17 17
18 type cacheFailTransport struct{}18 type cacheFailTransport struct{}
19 19
20-func (cacheFailTransport) Send(ports.Frame) error { return errors.New("send failed") }20+func (cacheFailTransport) Send(wire.Frame) error { return errors.New("send failed") }
21-func (cacheFailTransport) Recv() (ports.Frame, error) { return ports.Frame{}, io.EOF }21+func (cacheFailTransport) Recv() (wire.Frame, error) { return wire.Frame{}, io.EOF }
22-func (cacheFailTransport) Close() error { return nil }22+func (cacheFailTransport) Close() error { return nil }
23 23
24 func TestPipelineCachePublishesOnlyAfterEmission(t *testing.T) {24 func TestPipelineCachePublishesOnlyAfterEmission(t *testing.T) {
25 for _, failure := range []struct {25 for _, failure := range []struct {
@@ -69,7 +69,7 @@ func TestPipelineCachePublishesOnlyAfterEmission(t *testing.T) {
69 require.True(t, d.emitFrame(sess, ac, &state, pending))69 require.True(t, d.emitFrame(sess, ac, &state, pending))
70 require.Equal(t, pending.cache, ac.pipelineCache)70 require.Equal(t, pending.cache, ac.pipelineCache)
71 frame := <-sends71 frame := <-sends
72- output, err := ports.UnmarshalOutput(frame.Payload)72+ output, err := wire.UnmarshalOutput(frame.Payload)
73 require.NoError(t, err)73 require.NoError(t, err)
74 require.Contains(t, string(output.Data), "next", "the retry must emit state retained after the failed emission")74 require.Contains(t, string(output.Data), "next", "the retry must emit state retained after the failed emission")
75 })75 })
M internal/usecase/daemon/render_pipeline_test.go +10 -10
@@ -14,7 +14,7 @@ import (
14 vt "github.com/bnema/vev-vt"14 vt "github.com/bnema/vev-vt"
15 renderer "github.com/bnema/vev-vt/ansi"15 renderer "github.com/bnema/vev-vt/ansi"
16 "github.com/bnema/vev/internal/domain"16 "github.com/bnema/vev/internal/domain"
17- "github.com/bnema/vev/internal/ports"17+ "github.com/bnema/vev/internal/protocol/wire"
18 "github.com/bnema/vev/internal/testutil/replaytest"18 "github.com/bnema/vev/internal/testutil/replaytest"
19 scopy "github.com/bnema/vev/internal/usecase/copy"19 scopy "github.com/bnema/vev/internal/usecase/copy"
20 "github.com/bnema/vev/internal/usecase/layout"20 "github.com/bnema/vev/internal/usecase/layout"
@@ -32,11 +32,11 @@ import (
32 // assertions must not depend on any adapter's framing implementation.32 // assertions must not depend on any adapter's framing implementation.
33 type scriptedReplayTransport struct {33 type scriptedReplayTransport struct {
34 t *testing.T34 t *testing.T
35- frames []ports.Frame35+ frames []wire.Frame
36 next int36 next int
37 }37 }
38 38
39-func (s *scriptedReplayTransport) Send(got ports.Frame) error {39+func (s *scriptedReplayTransport) Send(got wire.Frame) error {
40 s.t.Helper()40 s.t.Helper()
41 if s.next >= len(s.frames) {41 if s.next >= len(s.frames) {
42 s.t.Errorf("unexpected frame %#v", got)42 s.t.Errorf("unexpected frame %#v", got)
@@ -48,8 +48,8 @@ func (s *scriptedReplayTransport) Send(got ports.Frame) error {
48 require.Equal(s.t, want.Payload, got.Payload)48 require.Equal(s.t, want.Payload, got.Payload)
49 return nil49 return nil
50 }50 }
51-func (*scriptedReplayTransport) Recv() (ports.Frame, error) { return ports.Frame{}, io.EOF }51+func (*scriptedReplayTransport) Recv() (wire.Frame, error) { return wire.Frame{}, io.EOF }
52-func (*scriptedReplayTransport) Close() error { return nil }52+func (*scriptedReplayTransport) Close() error { return nil }
53 53
54 func TestTransportReplayFinalShadowAndTerminalBytes(t *testing.T) {54 func TestTransportReplayFinalShadowAndTerminalBytes(t *testing.T) {
55 frames := replaytest.Transcript()55 frames := replaytest.Transcript()
@@ -57,7 +57,7 @@ func TestTransportReplayFinalShadowAndTerminalBytes(t *testing.T) {
57 terminal := vt.NewScreen(8, 3)57 terminal := vt.NewScreen(8, 3)
58 for _, frame := range frames {58 for _, frame := range frames {
59 require.NoError(t, transport.Send(frame))59 require.NoError(t, transport.Send(frame))
60- output, err := ports.UnmarshalOutput(frame.Payload)60+ output, err := wire.UnmarshalOutput(frame.Payload)
61 require.NoError(t, err)61 require.NoError(t, err)
62 require.Equal(t, frame.Payload, mustMarshalOutput(output), "output payload must remain byte exact")62 require.Equal(t, frame.Payload, mustMarshalOutput(output), "output payload must remain byte exact")
63 terminal.Write(output.Data)63 terminal.Write(output.Data)
@@ -253,7 +253,7 @@ func TestEmitFrameFailedSendDoesNotPublishCursorOrOutputState(t *testing.T) {
253 require.True(t, d.emitFrame(sess, ac, &state, composed))253 require.True(t, d.emitFrame(sess, ac, &state, composed))
254 require.Equal(t, cursorOut{valid: true, row: 3, col: 4, style: 2, hasStyle: true}, ac.output.lastCursor)254 require.Equal(t, cursorOut{valid: true, row: 3, col: 4, style: 2, hasStyle: true}, ac.output.lastCursor)
255 require.Equal(t, uint64(1), ac.output.next)255 require.Equal(t, uint64(1), ac.output.next)
256- out, err := ports.UnmarshalOutput((<-sends).Payload)256+ out, err := wire.UnmarshalOutput((<-sends).Payload)
257 require.NoError(t, err)257 require.NoError(t, err)
258 require.Zero(t, out.Base)258 require.Zero(t, out.Base)
259 require.Equal(t, uint64(1), out.New)259 require.Equal(t, uint64(1), out.New)
@@ -332,12 +332,12 @@ func TestComposeEmitExactReplayTiledFloatingBarsOverlayAndCursor(t *testing.T) {
332 stream := newOutputStateStream()332 stream := newOutputStateStream()
333 prepared, err := stream.prepareFrame(nil, &state, composed.frame, composed.damage, composed.reset, composed.cursor)333 prepared, err := stream.prepareFrame(nil, &state, composed.frame, composed.damage, composed.reset, composed.cursor)
334 require.NoError(t, err)334 require.NoError(t, err)
335- var outputFrame ports.Frame335+ var outputFrame wire.Frame
336- require.NoError(t, prepared.send(0, func(frame ports.Frame) error {336+ require.NoError(t, prepared.send(0, func(frame wire.Frame) error {
337 outputFrame = frame337 outputFrame = frame
338 return nil338 return nil
339 }))339 }))
340- output, err := ports.UnmarshalOutput(outputFrame.Payload)340+ output, err := wire.UnmarshalOutput(outputFrame.Payload)
341 require.NoError(t, err)341 require.NoError(t, err)
342 terminalBytes := output.Data342 terminalBytes := output.Data
343 require.Equal(t, "\x1b[1;1H\x1b[0;7m tab \x1b[0m R\x1b[2;1HAAAAAAAAAAAA\x1b[3;1HBBB┌─fl─┐BBB\x1b[4;1H───Prompt───\x1b[5;1HPROMPT\x1b[K\x1b[B\x1b[2K\x1b[7;1H\x1b[0;7m sess \x1b[0m B\x1b[0m\x1b[?25l", string(terminalBytes))343 require.Equal(t, "\x1b[1;1H\x1b[0;7m tab \x1b[0m R\x1b[2;1HAAAAAAAAAAAA\x1b[3;1HBBB┌─fl─┐BBB\x1b[4;1H───Prompt───\x1b[5;1HPROMPT\x1b[K\x1b[B\x1b[2K\x1b[7;1H\x1b[0;7m sess \x1b[0m B\x1b[0m\x1b[?25l", string(terminalBytes))
M internal/usecase/daemon/render_test.go +26 -26
@@ -19,9 +19,9 @@ import (
19 vt "github.com/bnema/vev-vt"19 vt "github.com/bnema/vev-vt"
20 renderer "github.com/bnema/vev-vt/ansi"20 renderer "github.com/bnema/vev-vt/ansi"
21 "github.com/bnema/vev/internal/domain"21 "github.com/bnema/vev/internal/domain"
22- "github.com/bnema/vev/internal/ports"
23 portsmocks "github.com/bnema/vev/internal/ports/mocks"22 portsmocks "github.com/bnema/vev/internal/ports/mocks"
24 "github.com/bnema/vev/internal/protocol"23 "github.com/bnema/vev/internal/protocol"
24+ "github.com/bnema/vev/internal/protocol/wire"
25 scopy "github.com/bnema/vev/internal/usecase/copy"25 scopy "github.com/bnema/vev/internal/usecase/copy"
26 "github.com/bnema/vev/internal/usecase/layout"26 "github.com/bnema/vev/internal/usecase/layout"
27 themeui "github.com/bnema/vev/internal/usecase/theme"27 themeui "github.com/bnema/vev/internal/usecase/theme"
@@ -38,14 +38,14 @@ func TestPaletteBackdropDimsSimultaneousCopyMode(t *testing.T) {
38 pane.screen.Write([]byte("\x1b[38;2;180;90;30mX"))38 pane.screen.Write([]byte("\x1b[38;2;180;90;30mX"))
39 39
40 d.enterCopyMode(sess, ac)40 d.enterCopyMode(sess, ac)
41- mustApplyOutput(t, client, awaitFrame(t, sends, ports.MsgOutput))41+ mustApplyOutput(t, client, awaitFrame(t, sends, wire.MsgOutput))
42 undimmed := client.Frame.At(0, 1)42 undimmed := client.Frame.At(0, 1)
43 require.Equal(t, 'X', undimmed.Rune, "fixture must address copy-mode pane content")43 require.Equal(t, 'X', undimmed.Rune, "fixture must address copy-mode pane content")
44 copyBar := append([]renderer.Cell(nil), client.Frame.Row(client.Frame.Height-1)...)44 copyBar := append([]renderer.Cell(nil), client.Frame.Row(client.Frame.Height-1)...)
45 require.Contains(t, rowText(copyBar), "[SCROLL]", "fixture must capture the copy status bar")45 require.Contains(t, rowText(copyBar), "[SCROLL]", "fixture must capture the copy status bar")
46 46
47 d.enterPalette(sess, ac)47 d.enterPalette(sess, ac)
48- mustApplyOutput(t, client, awaitFrame(t, sends, ports.MsgOutput))48+ mustApplyOutput(t, client, awaitFrame(t, sends, wire.MsgOutput))
49 dimmed := client.Frame.At(0, 1)49 dimmed := client.Frame.At(0, 1)
50 require.Equal(t, undimmed.Rune, dimmed.Rune, "palette backdrop must preserve copy content")50 require.Equal(t, undimmed.Rune, dimmed.Rune, "palette backdrop must preserve copy content")
51 require.Equal(t, themeui.NewDimmer(theme).Dim(undimmed.Style), dimmed.Style, "palette backdrop must dim the composed copy frame")51 require.Equal(t, themeui.NewDimmer(theme).Dim(undimmed.Style), dimmed.Style, "palette backdrop must dim the composed copy frame")
@@ -84,8 +84,8 @@ func TestFirstPaintRetainedFloatingPaneEmitsOneReset(t *testing.T) {
84 84
85 d.firstPaint(sess, ac)85 d.firstPaint(sess, ac)
86 86
87- frame := awaitFrame(t, sends, ports.MsgOutput)87+ frame := awaitFrame(t, sends, wire.MsgOutput)
88- output, err := ports.UnmarshalOutput(frame.Payload)88+ output, err := wire.UnmarshalOutput(frame.Payload)
89 require.NoError(t, err)89 require.NoError(t, err)
90 require.Zero(t, output.Base, "first paint must be a mandatory reset")90 require.Zero(t, output.Base, "first paint must be a mandatory reset")
91 select {91 select {
@@ -107,13 +107,13 @@ func TestPaletteBackdropDimsSimultaneousPicker(t *testing.T) {
107 pane.screen.Write([]byte("X"))107 pane.screen.Write([]byte("X"))
108 108
109 d.enterPicker(sess, ac)109 d.enterPicker(sess, ac)
110- mustApplyOutput(t, client, awaitFrame(t, sends, ports.MsgOutput))110+ mustApplyOutput(t, client, awaitFrame(t, sends, wire.MsgOutput))
111 pickerTitle := client.Frame.At(31, 2)111 pickerTitle := client.Frame.At(31, 2)
112 require.Equal(t, 'S', pickerTitle.Rune, "fixture must address the picker title")112 require.Equal(t, 'S', pickerTitle.Rune, "fixture must address the picker title")
113 undimmedPane := client.Frame.At(0, 1)113 undimmedPane := client.Frame.At(0, 1)
114 114
115 d.enterPalette(sess, ac)115 d.enterPalette(sess, ac)
116- mustApplyOutput(t, client, awaitFrame(t, sends, ports.MsgOutput))116+ mustApplyOutput(t, client, awaitFrame(t, sends, wire.MsgOutput))
117 dimmedPickerTitle := pickerTitle117 dimmedPickerTitle := pickerTitle
118 dimmedPickerTitle.Style = themeui.NewDimmer(backdropTheme()).Dim(pickerTitle.Style)118 dimmedPickerTitle.Style = themeui.NewDimmer(backdropTheme()).Dim(pickerTitle.Style)
119 require.Equal(t, dimmedPickerTitle, client.Frame.At(31, 2), "the lower-priority picker must be part of the palette backdrop")119 require.Equal(t, dimmedPickerTitle, client.Frame.At(31, 2), "the lower-priority picker must be part of the palette backdrop")
@@ -129,13 +129,13 @@ func TestPaletteBackdropProductionRenderAndDismissal(t *testing.T) {
129 pane := sess.tabs[0].focusedPane()129 pane := sess.tabs[0].focusedPane()
130 pane.screen.Write([]byte("X"))130 pane.screen.Write([]byte("X"))
131 d.paint(sess, ac, true, nil)131 d.paint(sess, ac, true, nil)
132- mustApplyOutput(t, client, awaitFrame(t, sends, ports.MsgOutput))132+ mustApplyOutput(t, client, awaitFrame(t, sends, wire.MsgOutput))
133 undimmed := client.Frame.At(0, 1)133 undimmed := client.Frame.At(0, 1)
134 topBar := client.Frame.At(0, 0)134 topBar := client.Frame.At(0, 0)
135 bottomBar := client.Frame.At(0, 24)135 bottomBar := client.Frame.At(0, 24)
136 136
137 d.handleInput(sess, ac, []byte("\x1b "))137 d.handleInput(sess, ac, []byte("\x1b "))
138- mustApplyOutput(t, client, awaitFrame(t, sends, ports.MsgOutput))138+ mustApplyOutput(t, client, awaitFrame(t, sends, wire.MsgOutput))
139 dimmed := client.Frame.At(0, 1)139 dimmed := client.Frame.At(0, 1)
140 require.Equal(t, 'X', dimmed.Rune)140 require.Equal(t, 'X', dimmed.Rune)
141 require.Equal(t, themeui.NewDimmer(backdropTheme()).Dim(undimmed.Style), dimmed.Style, "open palette must use the theme dim style")141 require.Equal(t, themeui.NewDimmer(backdropTheme()).Dim(undimmed.Style), dimmed.Style, "open palette must use the theme dim style")
@@ -147,7 +147,7 @@ func TestPaletteBackdropProductionRenderAndDismissal(t *testing.T) {
147 require.Equal(t, dimmedBottomBar, client.Frame.At(0, 24), "bottom chrome is part of the complete backdrop")147 require.Equal(t, dimmedBottomBar, client.Frame.At(0, 24), "bottom chrome is part of the complete backdrop")
148 148
149 d.handleInput(sess, ac, []byte("\x1b"))149 d.handleInput(sess, ac, []byte("\x1b"))
150- mustApplyOutput(t, client, awaitFrame(t, sends, ports.MsgOutput))150+ mustApplyOutput(t, client, awaitFrame(t, sends, wire.MsgOutput))
151 require.Equal(t, undimmed, client.Frame.At(0, 1), "full redraw restores pane rune and style")151 require.Equal(t, undimmed, client.Frame.At(0, 1), "full redraw restores pane rune and style")
152 require.Equal(t, topBar, client.Frame.At(0, 0))152 require.Equal(t, topBar, client.Frame.At(0, 0))
153 require.Equal(t, bottomBar, client.Frame.At(0, 24))153 require.Equal(t, bottomBar, client.Frame.At(0, 24))
@@ -189,10 +189,10 @@ func awaitPTYReadProcessed(t *testing.T, processed <-chan struct{}) {
189 <-processed189 <-processed
190 }190 }
191 191
192-func awaitOutputFrameWithoutSleep(t *testing.T, sends <-chan ports.Frame) ports.Frame {192+func awaitOutputFrameWithoutSleep(t *testing.T, sends <-chan wire.Frame) wire.Frame {
193 t.Helper()193 t.Helper()
194 frame := <-sends194 frame := <-sends
195- require.Equal(t, ports.MsgOutput, frame.Type)195+ require.Equal(t, wire.MsgOutput, frame.Type)
196 return frame196 return frame
197 }197 }
198 198
@@ -332,7 +332,7 @@ func TestPTYReaderSyncVisibilityTransitions(t *testing.T) {
332 awaitPTYReadProcessed(t, inactiveProcessed)332 awaitPTYReadProcessed(t, inactiveProcessed)
333 fireCoordinatorTimer(t, rc, drainCoordinatorTimers(clock), urgentRenderDeadline)333 fireCoordinatorTimer(t, rc, drainCoordinatorTimers(clock), urgentRenderDeadline)
334 frame := awaitOutputFrameWithoutSleep(t, sends)334 frame := awaitOutputFrameWithoutSleep(t, sends)
335- output, err := ports.UnmarshalOutput(frame.Payload)335+ output, err := wire.UnmarshalOutput(frame.Payload)
336 require.NoError(t, err)336 require.NoError(t, err)
337 require.Contains(t, string(output.Data), "partial complete")337 require.Contains(t, string(output.Data), "partial complete")
338 requireNoCoordinatorOutputFrame(t, sends)338 requireNoCoordinatorOutputFrame(t, sends)
@@ -365,7 +365,7 @@ func TestPTYReaderSyncVisibilityTransitions(t *testing.T) {
365 awaitPTYReadProcessed(t, newProcessed)365 awaitPTYReadProcessed(t, newProcessed)
366 fireCoordinatorTimer(t, rc, drainCoordinatorTimers(clock), minOutputRenderDeadline)366 fireCoordinatorTimer(t, rc, drainCoordinatorTimers(clock), minOutputRenderDeadline)
367 frame := awaitOutputFrameWithoutSleep(t, sends)367 frame := awaitOutputFrameWithoutSleep(t, sends)
368- output, err := ports.UnmarshalOutput(frame.Payload)368+ output, err := wire.UnmarshalOutput(frame.Payload)
369 require.NoError(t, err)369 require.NoError(t, err)
370 require.Contains(t, string(output.Data), "newly active")370 require.Contains(t, string(output.Data), "newly active")
371 require.NotContains(t, string(output.Data), "old partial")371 require.NotContains(t, string(output.Data), "old partial")
@@ -515,7 +515,7 @@ func TestPaneRenderableActiveAttachmentDoesNotScanPickerPreviews(t *testing.T) {
515 }515 }
516 516
517 func TestNonRenderablePaneDamageRemainsPendingForCapture(t *testing.T) {517 func TestNonRenderablePaneDamageRemainsPendingForCapture(t *testing.T) {
518- newFixture := func(t *testing.T) (*Daemon, *session, *tab, *pane, chan ports.Frame) {518+ newFixture := func(t *testing.T) (*Daemon, *session, *tab, *pane, chan wire.Frame) {
519 t.Helper()519 t.Helper()
520 p, release := newBlockingPTY(t)520 p, release := newBlockingPTY(t)
521 t.Cleanup(release)521 t.Cleanup(release)
@@ -589,8 +589,8 @@ func TestAltXClosesFinalTabAndDetaches(t *testing.T) {
589 d.handleInput(sess, ac, []byte("CLT\r"))589 d.handleInput(sess, ac, []byte("CLT\r"))
590 590
591 require.Equal(t, 0, sessionCount(d))591 require.Equal(t, 0, sessionCount(d))
592- f := awaitFrame(t, sends, ports.MsgDetached)592+ f := awaitFrame(t, sends, wire.MsgDetached)
593- det, err := ports.UnmarshalDetached(f.Payload)593+ det, err := wire.UnmarshalDetached(f.Payload)
594 require.NoError(t, err)594 require.NoError(t, err)
595 require.Equal(t, protocol.ReasonSessionKilled, det.Reason)595 require.Equal(t, protocol.ReasonSessionKilled, det.Reason)
596 }596 }
@@ -610,8 +610,8 @@ func TestPTYEOFClosesActiveNonFinalTabAndRepaintsRemaining(t *testing.T) {
610 require.Eventually(t, func() bool { return tabCount(sess) == 1 }, 2*time.Second, 5*time.Millisecond)610 require.Eventually(t, func() bool { return tabCount(sess) == 1 }, 2*time.Second, 5*time.Millisecond)
611 require.Equal(t, 1, sessionCount(d))611 require.Equal(t, 1, sessionCount(d))
612 require.Equal(t, 0, testAttachmentTabIndex(sess))612 require.Equal(t, 0, testAttachmentTabIndex(sess))
613- f := awaitFrame(t, sends, ports.MsgOutput)613+ f := awaitFrame(t, sends, wire.MsgOutput)
614- out, err := ports.UnmarshalOutput(f.Payload)614+ out, err := wire.UnmarshalOutput(f.Payload)
615 require.NoError(t, err)615 require.NoError(t, err)
616 data := string(out.Data)616 data := string(out.Data)
617 require.Contains(t, data, "remaining")617 require.Contains(t, data, "remaining")
@@ -636,8 +636,8 @@ func TestPTYEOFClosesInactiveNonFinalTabAndRepaintsStatus(t *testing.T) {
636 require.Eventually(t, func() bool { return tabCount(sess) == 1 }, 2*time.Second, 5*time.Millisecond)636 require.Eventually(t, func() bool { return tabCount(sess) == 1 }, 2*time.Second, 5*time.Millisecond)
637 require.Equal(t, 1, sessionCount(d))637 require.Equal(t, 1, sessionCount(d))
638 require.Equal(t, 0, testAttachmentTabIndex(sess))638 require.Equal(t, 0, testAttachmentTabIndex(sess))
639- f := awaitFrame(t, sends, ports.MsgOutput)639+ f := awaitFrame(t, sends, wire.MsgOutput)
640- out, err := ports.UnmarshalOutput(f.Payload)640+ out, err := wire.UnmarshalOutput(f.Payload)
641 require.NoError(t, err)641 require.NoError(t, err)
642 data := string(out.Data)642 data := string(out.Data)
643 require.Contains(t, data, "active")643 require.Contains(t, data, "active")
@@ -655,8 +655,8 @@ func TestPTYEOFFinalTabKillsSessionAndDetaches(t *testing.T) {
655 releases[0]()655 releases[0]()
656 656
657 require.Eventually(t, func() bool { return sessionCount(d) == 0 }, 2*time.Second, 5*time.Millisecond)657 require.Eventually(t, func() bool { return sessionCount(d) == 0 }, 2*time.Second, 5*time.Millisecond)
658- f := awaitFrame(t, sends, ports.MsgDetached)658+ f := awaitFrame(t, sends, wire.MsgDetached)
659- det, err := ports.UnmarshalDetached(f.Payload)659+ det, err := wire.UnmarshalDetached(f.Payload)
660 require.NoError(t, err)660 require.NoError(t, err)
661 require.Equal(t, protocol.ReasonSessionKilled, det.Reason)661 require.Equal(t, protocol.ReasonSessionKilled, det.Reason)
662 662
@@ -933,8 +933,8 @@ func TestResizeOrdersPTYBeforeScreen(t *testing.T) {
933 var gotOutput atomic.Bool933 var gotOutput atomic.Bool
934 tr := portsmocks.NewMockTransport(t)934 tr := portsmocks.NewMockTransport(t)
935 tr.EXPECT().Close().Return(nil).Maybe()935 tr.EXPECT().Close().Return(nil).Maybe()
936- tr.EXPECT().Send(mock.Anything).RunAndReturn(func(f ports.Frame) error {936+ tr.EXPECT().Send(mock.Anything).RunAndReturn(func(f wire.Frame) error {
937- if f.Type == ports.MsgOutput {937+ if f.Type == wire.MsgOutput {
938 gotOutput.Store(true)938 gotOutput.Store(true)
939 }939 }
940 return nil940 return nil
@@ -1053,7 +1053,7 @@ func TestPTYKittyIcatDetectionGetsResponsesWrittenBackToPTY(t *testing.T) {
1053 }1053 }
1054 select {1054 select {
1055 case f := <-sends:1055 case f := <-sends:
1056- require.NotEqual(t, ports.MsgOutput, f.Type)1056+ require.NotEqual(t, wire.MsgOutput, f.Type)
1057 default:1057 default:
1058 }1058 }
1059 }1059 }
M internal/usecase/daemon/resume_test.go +24 -23
@@ -16,23 +16,24 @@ import (
16 "github.com/bnema/vev/internal/ports"16 "github.com/bnema/vev/internal/ports"
17 portsmocks "github.com/bnema/vev/internal/ports/mocks"17 portsmocks "github.com/bnema/vev/internal/ports/mocks"
18 "github.com/bnema/vev/internal/protocol"18 "github.com/bnema/vev/internal/protocol"
19+ "github.com/bnema/vev/internal/protocol/wire"
19 "github.com/bnema/vev/internal/usecase/layout"20 "github.com/bnema/vev/internal/usecase/layout"
20 )21 )
21 22
22 type closeTrackingTransport struct {23 type closeTrackingTransport struct {
23 mu sync.Mutex24 mu sync.Mutex
24 closed bool25 closed bool
25- sends []ports.Frame26+ sends []wire.Frame
26 }27 }
27 28
28-func (t *closeTrackingTransport) Send(f ports.Frame) error {29+func (t *closeTrackingTransport) Send(f wire.Frame) error {
29 t.mu.Lock()30 t.mu.Lock()
30 defer t.mu.Unlock()31 defer t.mu.Unlock()
31 t.sends = append(t.sends, f)32 t.sends = append(t.sends, f)
32 return nil33 return nil
33 }34 }
34-func (t *closeTrackingTransport) Recv() (ports.Frame, error) {35+func (t *closeTrackingTransport) Recv() (wire.Frame, error) {
35- return ports.Frame{}, errors.New("closed")36+ return wire.Frame{}, errors.New("closed")
36 }37 }
37 func (t *closeTrackingTransport) Close() error {38 func (t *closeTrackingTransport) Close() error {
38 t.mu.Lock()39 t.mu.Lock()
@@ -46,10 +47,10 @@ func (t *closeTrackingTransport) Closed() bool {
46 return t.closed47 return t.closed
47 }48 }
48 49
49-func (t *closeTrackingTransport) Sends() []ports.Frame {50+func (t *closeTrackingTransport) Sends() []wire.Frame {
50 t.mu.Lock()51 t.mu.Lock()
51 defer t.mu.Unlock()52 defer t.mu.Unlock()
52- return append([]ports.Frame(nil), t.sends...)53+ return append([]wire.Frame(nil), t.sends...)
53 }54 }
54 55
55 type closeCountingBlockedTransport struct {56 type closeCountingBlockedTransport struct {
@@ -63,13 +64,13 @@ func newCloseCountingBlockedTransport() *closeCountingBlockedTransport {
63 return &closeCountingBlockedTransport{closed: make(chan struct{})}64 return &closeCountingBlockedTransport{closed: make(chan struct{})}
64 }65 }
65 66
66-func (t *closeCountingBlockedTransport) Send(ports.Frame) error {67+func (t *closeCountingBlockedTransport) Send(wire.Frame) error {
67 <-t.closed68 <-t.closed
68 return errors.New("closed")69 return errors.New("closed")
69 }70 }
70 71
71-func (t *closeCountingBlockedTransport) Recv() (ports.Frame, error) {72+func (t *closeCountingBlockedTransport) Recv() (wire.Frame, error) {
72- return ports.Frame{}, errors.New("closed")73+ return wire.Frame{}, errors.New("closed")
73 }74 }
74 75
75 func (t *closeCountingBlockedTransport) Close() error {76 func (t *closeCountingBlockedTransport) Close() error {
@@ -326,14 +327,14 @@ func TestHandleHelloResumeDefersFreshOutputUntilWelcome(t *testing.T) {
326 done := make(chan struct{})327 done := make(chan struct{})
327 resumeHello := helloResumeCapable(protocol.IntentResume, sess.name, token)328 resumeHello := helloResumeCapable(protocol.IntentResume, sess.name, token)
328 go func() {329 go func() {
329- d.handleHello(tr.tr, ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(resumeHello)})330+ d.handleHello(tr.tr, wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(resumeHello)})
330 close(done)331 close(done)
331 }()332 }()
332 333
333 <-tr.welcomeEntered334 <-tr.welcomeEntered
334 handshakeTimer := awaitHandshakeTimer(t, clock)335 handshakeTimer := awaitHandshakeTimer(t, clock)
335 welcome := <-tr.sends336 welcome := <-tr.sends
336- require.Equal(t, ports.MsgWelcome, welcome.Type)337+ require.Equal(t, wire.MsgWelcome, welcome.Type)
337 sess.mu.Lock()338 sess.mu.Lock()
338 resumed := sess.snapshotAttachmentsLocked()[0]339 resumed := sess.snapshotAttachmentsLocked()[0]
339 sess.mu.Unlock()340 sess.mu.Unlock()
@@ -351,8 +352,8 @@ func TestHandleHelloResumeDefersFreshOutputUntilWelcome(t *testing.T) {
351 requireNoCoordinatorOutputFrame(t, tr.sends)352 requireNoCoordinatorOutputFrame(t, tr.sends)
352 353
353 tr.release()354 tr.release()
354- output := awaitFrame(t, tr.sends, ports.MsgOutput)355+ output := awaitFrame(t, tr.sends, wire.MsgOutput)
355- first, err := ports.UnmarshalOutput(output.Payload)356+ first, err := wire.UnmarshalOutput(output.Payload)
356 require.NoError(t, err)357 require.NoError(t, err)
357 require.Zero(t, first.Base)358 require.Zero(t, first.Base)
358 tr.finish()359 tr.finish()
@@ -1094,7 +1095,7 @@ func TestOutputAckLagAloneDoesNotForceFullStateRepaint(t *testing.T) {
1094 1095
1095 f := outputStateFrame(ac.output, []byte("incremental while reliable backlog drains"), reset, 0)1096 f := outputStateFrame(ac.output, []byte("incremental while reliable backlog drains"), reset, 0)
1096 ac.sendMu.Unlock()1097 ac.sendMu.Unlock()
1097- out, err := ports.UnmarshalOutput(f.Payload)1098+ out, err := wire.UnmarshalOutput(f.Payload)
1098 require.NoError(t, err)1099 require.NoError(t, err)
1099 require.Equal(t, uint64(5), out.Base, "output should remain incremental unless an explicit reset is requested")1100 require.Equal(t, uint64(5), out.Base, "output should remain incremental unless an explicit reset is requested")
1100 require.Equal(t, uint64(6), out.New)1101 require.Equal(t, uint64(6), out.New)
@@ -1104,7 +1105,7 @@ func TestOutputAckLagAloneDoesNotForceFullStateRepaint(t *testing.T) {
1104 require.True(t, reset, "explicit reset should still force full repaint")1105 require.True(t, reset, "explicit reset should still force full repaint")
1105 full := outputStateFrame(ac.output, []byte("explicit full repaint"), reset, 0)1106 full := outputStateFrame(ac.output, []byte("explicit full repaint"), reset, 0)
1106 ac.sendMu.Unlock()1107 ac.sendMu.Unlock()
1107- fullOut, err := ports.UnmarshalOutput(full.Payload)1108+ fullOut, err := wire.UnmarshalOutput(full.Payload)
1108 require.NoError(t, err)1109 require.NoError(t, err)
1109 require.Equal(t, uint64(0), fullOut.Base)1110 require.Equal(t, uint64(0), fullOut.Base)
1110 require.Equal(t, uint64(7), fullOut.New)1111 require.Equal(t, uint64(7), fullOut.New)
@@ -1185,9 +1186,9 @@ func TestResumeRebasesFullOutputWindowBeforeFirstPaint(t *testing.T) {
1185 require.Same(t, ac, resumedAC)1186 require.Same(t, ac, resumedAC)
1186 d.paint(resumedSess, resumedAC, true, nil)1187 d.paint(resumedSess, resumedAC, true, nil)
1187 1188
1188- sends := testFramesOfType(newTr.Sends(), ports.MsgOutput)1189+ sends := testFramesOfType(newTr.Sends(), wire.MsgOutput)
1189 require.Len(t, sends, 1)1190 require.Len(t, sends, 1)
1190- first, err := ports.UnmarshalOutput(sends[0].Payload)1191+ first, err := wire.UnmarshalOutput(sends[0].Payload)
1191 require.NoError(t, err)1192 require.NoError(t, err)
1192 require.Zero(t, first.Base)1193 require.Zero(t, first.Base)
1193 require.Equal(t, uint64(1), first.New)1194 require.Equal(t, uint64(1), first.New)
@@ -1195,9 +1196,9 @@ func TestResumeRebasesFullOutputWindowBeforeFirstPaint(t *testing.T) {
1195 1196
1196 resumedSess.tabs[0].focusedPane().screen.Write([]byte("A"))1197 resumedSess.tabs[0].focusedPane().screen.Write([]byte("A"))
1197 d.paint(resumedSess, resumedAC, false, nil)1198 d.paint(resumedSess, resumedAC, false, nil)
1198- sends = testFramesOfType(newTr.Sends(), ports.MsgOutput)1199+ sends = testFramesOfType(newTr.Sends(), wire.MsgOutput)
1199 require.Len(t, sends, 2)1200 require.Len(t, sends, 2)
1200- second, err := ports.UnmarshalOutput(sends[1].Payload)1201+ second, err := wire.UnmarshalOutput(sends[1].Payload)
1201 require.NoError(t, err)1202 require.NoError(t, err)
1202 require.Equal(t, first.New, second.Base)1203 require.Equal(t, first.New, second.Base)
1203 }1204 }
@@ -1245,9 +1246,9 @@ func TestParkingReleasesPaneCapturesBeforeHeadlessCloseAndResume(t *testing.T) {
1245 require.True(t, resumedSess.renderCoordinator().markAttachmentReady(resumedSess.renderCoordinator().attachmentLease(resumedAC)))1246 require.True(t, resumedSess.renderCoordinator().markAttachmentReady(resumedSess.renderCoordinator().attachmentLease(resumedAC)))
1246 d.firstPaint(resumedSess, resumedAC)1247 d.firstPaint(resumedSess, resumedAC)
1247 1248
1248- sends := testFramesOfType(newTransport.Sends(), ports.MsgOutput)1249+ sends := testFramesOfType(newTransport.Sends(), wire.MsgOutput)
1249 require.Len(t, sends, 1)1250 require.Len(t, sends, 1)
1250- output, err := ports.UnmarshalOutput(sends[0].Payload)1251+ output, err := wire.UnmarshalOutput(sends[0].Payload)
1251 require.NoError(t, err)1252 require.NoError(t, err)
1252 require.Zero(t, output.Base, "resume must start with a complete frame")1253 require.Zero(t, output.Base, "resume must start with a complete frame")
1253 terminal := vt.NewScreen(resumedAC.size.Cols, resumedAC.size.Rows)1254 terminal := vt.NewScreen(resumedAC.size.Cols, resumedAC.size.Rows)
@@ -1543,9 +1544,9 @@ func TestRawTerminalSideEffectsAreOutputStateNeutral(t *testing.T) {
1543 1544
1544 sends := tr.Sends()1545 sends := tr.Sends()
1545 require.Len(t, sends, 2)1546 require.Len(t, sends, 2)
1546- first, err := ports.UnmarshalOutput(sends[0].Payload)1547+ first, err := wire.UnmarshalOutput(sends[0].Payload)
1547 require.NoError(t, err)1548 require.NoError(t, err)
1548- second, err := ports.UnmarshalOutput(sends[1].Payload)1549+ second, err := wire.UnmarshalOutput(sends[1].Payload)
1549 require.NoError(t, err)1550 require.NoError(t, err)
1550 require.Zero(t, first.Base)1551 require.Zero(t, first.Base)
1551 require.Zero(t, first.New)1552 require.Zero(t, first.New)
M internal/usecase/daemon/route_position_test.go +3 -3
@@ -7,8 +7,8 @@ import (
7 "github.com/stretchr/testify/require"7 "github.com/stretchr/testify/require"
8 8
9 "github.com/bnema/vev/internal/domain"9 "github.com/bnema/vev/internal/domain"
10- "github.com/bnema/vev/internal/ports"
11 "github.com/bnema/vev/internal/protocol"10 "github.com/bnema/vev/internal/protocol"
11+ "github.com/bnema/vev/internal/protocol/wire"
12 )12 )
13 13
14 func TestRouteAppliesPreferredTabPerAttachment(t *testing.T) {14 func TestRouteAppliesPreferredTabPerAttachment(t *testing.T) {
@@ -96,10 +96,10 @@ func TestPaintPublishesChangedAttachmentRoutePosition(t *testing.T) {
96 96
97 var positions []protocol.RoutePosition97 var positions []protocol.RoutePosition
98 for _, frame := range tr.Sends() {98 for _, frame := range tr.Sends() {
99- if frame.Type != ports.MsgRoutePosition {99+ if frame.Type != wire.MsgRoutePosition {
100 continue100 continue
101 }101 }
102- position, err := ports.UnmarshalRoutePosition(frame.Payload)102+ position, err := wire.UnmarshalRoutePosition(frame.Payload)
103 require.NoError(t, err)103 require.NoError(t, err)
104 positions = append(positions, position)104 positions = append(positions, position)
105 }105 }
M internal/usecase/daemon/same_peer_switch.go +3 -3
@@ -2,8 +2,8 @@ package daemon
2 2
3 import (3 import (
4 "github.com/bnema/vev/internal/domain"4 "github.com/bnema/vev/internal/domain"
5- "github.com/bnema/vev/internal/ports"
6 "github.com/bnema/vev/internal/protocol"5 "github.com/bnema/vev/internal/protocol"
6+ "github.com/bnema/vev/internal/protocol/wire"
7 )7 )
8 8
9 func (ac *attachedClient) offerSamePeerTarget(target protocol.ExactSessionTarget) {9 func (ac *attachedClient) offerSamePeerTarget(target protocol.ExactSessionTarget) {
@@ -122,11 +122,11 @@ func (d *Daemon) sendSamePeerSwitchFailure(effect *attachmentEffect, requestID u
122 }122 }
123 defer sender.End()123 defer sender.End()
124 }124 }
125- payload, err := ports.MarshalSamePeerSwitchFailure(protocol.SamePeerSwitchFailure{RequestID: requestID, Code: code})125+ payload, err := wire.MarshalSamePeerSwitchFailure(protocol.SamePeerSwitchFailure{RequestID: requestID, Code: code})
126 if err != nil {126 if err != nil {
127 return127 return
128 }128 }
129- if err := sender.sendControl(ports.Frame{Type: ports.MsgSamePeerSwitchFailure, Payload: payload}); err != nil {129+ if err := sender.sendControl(wire.Frame{Type: wire.MsgSamePeerSwitchFailure, Payload: payload}); err != nil {
130 d.detachOnAttachmentSendError(sender.capability(), sender.transport.transport)130 d.detachOnAttachmentSendError(sender.capability(), sender.transport.transport)
131 }131 }
132 }132 }
M internal/usecase/daemon/same_peer_switch_test.go +10 -10
@@ -6,8 +6,8 @@ import (
6 "github.com/stretchr/testify/require"6 "github.com/stretchr/testify/require"
7 7
8 "github.com/bnema/vev/internal/domain"8 "github.com/bnema/vev/internal/domain"
9- "github.com/bnema/vev/internal/ports"
10 "github.com/bnema/vev/internal/protocol"9 "github.com/bnema/vev/internal/protocol"
10+ "github.com/bnema/vev/internal/protocol/wire"
11 )11 )
12 12
13 func TestPaletteSessionSelectionCompletesSamePeerSwitch(t *testing.T) {13 func TestPaletteSessionSelectionCompletesSamePeerSwitch(t *testing.T) {
@@ -31,10 +31,10 @@ func TestPaletteSessionSelectionCompletesSamePeerSwitch(t *testing.T) {
31 31
32 token := beginRecentRoutePaletteEffect(t, d, source, ac)32 token := beginRecentRoutePaletteEffect(t, d, source, ac)
33 d.handleInputForAttachment(token, []byte("\x1b "))33 d.handleInputForAttachment(token, []byte("\x1b "))
34- awaitFrame(t, sends, ports.MsgOutput)34+ awaitFrame(t, sends, wire.MsgOutput)
35 d.handleInputForAttachment(token, []byte("target\r"))35 d.handleInputForAttachment(token, []byte("target\r"))
36- targetFrame := awaitFrame(t, sends, ports.MsgAttachTarget)36+ targetFrame := awaitFrame(t, sends, wire.MsgAttachTarget)
37- attachTarget, err := ports.UnmarshalAttachTarget(targetFrame.Payload)37+ attachTarget, err := wire.UnmarshalAttachTarget(targetFrame.Payload)
38 require.NoError(t, err)38 require.NoError(t, err)
39 require.Equal(t, &protocol.ExactSessionTarget{LifecycleID: lifecycle, SessionName: "target"}, attachTarget.ExactTarget)39 require.Equal(t, &protocol.ExactSessionTarget{LifecycleID: lifecycle, SessionName: "target"}, attachTarget.ExactTarget)
40 40
@@ -43,8 +43,8 @@ func TestPaletteSessionSelectionCompletesSamePeerSwitch(t *testing.T) {
43 })43 })
44 44
45 require.Same(t, target, ac.currentAttachmentSession())45 require.Same(t, target, ac.currentAttachmentSession())
46- identityFrame := awaitFrame(t, sends, ports.MsgCommittedRouteIdentity)46+ identityFrame := awaitFrame(t, sends, wire.MsgCommittedRouteIdentity)
47- identity, err := ports.UnmarshalCommittedRouteIdentity(identityFrame.Payload)47+ identity, err := wire.UnmarshalCommittedRouteIdentity(identityFrame.Payload)
48 require.NoError(t, err)48 require.NoError(t, err)
49 require.Equal(t, attachTarget.ExactTarget, &identity.Target)49 require.Equal(t, attachTarget.ExactTarget, &identity.Target)
50 }50 }
@@ -86,8 +86,8 @@ func TestSamePeerSwitchTransitionsExactTargetAndPreferredTab(t *testing.T) {
86 86
87 require.Same(t, target, ac.currentAttachmentSession())87 require.Same(t, target, ac.currentAttachmentSession())
88 require.Equal(t, domain.TabStableID(target.tabs[1].stableID), ac.viewSnapshot().tabID)88 require.Equal(t, domain.TabStableID(target.tabs[1].stableID), ac.viewSnapshot().tabID)
89- identityFrame := awaitFrame(t, sends, ports.MsgCommittedRouteIdentity)89+ identityFrame := awaitFrame(t, sends, wire.MsgCommittedRouteIdentity)
90- identity, err := ports.UnmarshalCommittedRouteIdentity(identityFrame.Payload)90+ identity, err := wire.UnmarshalCommittedRouteIdentity(identityFrame.Payload)
91 require.NoError(t, err)91 require.NoError(t, err)
92 require.Equal(t, protocol.ExactSessionTarget{LifecycleID: lifecycle, SessionName: "target"}, identity.Target)92 require.Equal(t, protocol.ExactSessionTarget{LifecycleID: lifecycle, SessionName: "target"}, identity.Target)
93 }93 }
@@ -120,8 +120,8 @@ func TestSamePeerSwitchRejectsStaleTargetWithoutMutation(t *testing.T) {
120 })120 })
121 121
122 require.Same(t, source, ac.currentAttachmentSession())122 require.Same(t, source, ac.currentAttachmentSession())
123- failureFrame := awaitFrame(t, sends, ports.MsgSamePeerSwitchFailure)123+ failureFrame := awaitFrame(t, sends, wire.MsgSamePeerSwitchFailure)
124- failure, err := ports.UnmarshalSamePeerSwitchFailure(failureFrame.Payload)124+ failure, err := wire.UnmarshalSamePeerSwitchFailure(failureFrame.Payload)
125 require.NoError(t, err)125 require.NoError(t, err)
126 require.Equal(t, protocol.SamePeerSwitchFailure{RequestID: 1, Code: protocol.SamePeerSwitchStaleTarget}, failure)126 require.Equal(t, protocol.SamePeerSwitchFailure{RequestID: 1, Code: protocol.SamePeerSwitchStaleTarget}, failure)
127 }127 }
M internal/usecase/daemon/session_back_route_test.go +3 -3
@@ -3,8 +3,8 @@ package daemon
3 import (3 import (
4 "testing"4 "testing"
5 5
6- "github.com/bnema/vev/internal/ports"
7 "github.com/bnema/vev/internal/protocol"6 "github.com/bnema/vev/internal/protocol"
7+ "github.com/bnema/vev/internal/protocol/wire"
8 "github.com/stretchr/testify/require"8 "github.com/stretchr/testify/require"
9 )9 )
10 10
@@ -32,11 +32,11 @@ func TestBackSessionUsesClientPreviousRouteAfterSnapshotPublication(t *testing.T
32 var action protocol.RouteNavigationAction32 var action protocol.RouteNavigationAction
33 found := false33 found := false
34 for _, frame := range frames {34 for _, frame := range frames {
35- if frame.Type != ports.MsgNavigateRecentRoute {35+ if frame.Type != wire.MsgNavigateRecentRoute {
36 continue36 continue
37 }37 }
38 var err error38 var err error
39- action, err = ports.UnmarshalRouteNavigationAction(frame.Payload)39+ action, err = wire.UnmarshalRouteNavigationAction(frame.Payload)
40 require.NoError(t, err)40 require.NoError(t, err)
41 found = true41 found = true
42 }42 }
M internal/usecase/daemon/session_rename_purge_test.go +7 -7
@@ -6,9 +6,9 @@ import (
6 "github.com/stretchr/testify/require"6 "github.com/stretchr/testify/require"
7 7
8 "github.com/bnema/vev/internal/domain"8 "github.com/bnema/vev/internal/domain"
9- "github.com/bnema/vev/internal/ports"
10 portsmocks "github.com/bnema/vev/internal/ports/mocks"9 portsmocks "github.com/bnema/vev/internal/ports/mocks"
11 "github.com/bnema/vev/internal/protocol"10 "github.com/bnema/vev/internal/protocol"
11+ "github.com/bnema/vev/internal/protocol/wire"
12 )12 )
13 13
14 func TestRenamePublishesCommittedRouteIdentityToAttachedClients(t *testing.T) {14 func TestRenamePublishesCommittedRouteIdentityToAttachedClients(t *testing.T) {
@@ -24,8 +24,8 @@ func TestRenamePublishesCommittedRouteIdentityToAttachedClients(t *testing.T) {
24 24
25 require.NoError(t, d.renameSession(sess, "vps-infra"))25 require.NoError(t, d.renameSession(sess, "vps-infra"))
26 26
27- frame := awaitFrame(t, sends, ports.MsgCommittedRouteIdentity)27+ frame := awaitFrame(t, sends, wire.MsgCommittedRouteIdentity)
28- identity, err := ports.UnmarshalCommittedRouteIdentity(frame.Payload)28+ identity, err := wire.UnmarshalCommittedRouteIdentity(frame.Payload)
29 require.NoError(t, err)29 require.NoError(t, err)
30 require.Equal(t, protocol.ExactSessionTarget{30 require.Equal(t, protocol.ExactSessionTarget{
31 LifecycleID: domain.IncarnationID{1},31 LifecycleID: domain.IncarnationID{1},
@@ -54,12 +54,12 @@ func TestRenameDefersCommittedRouteIdentityUntilFirstRouteSnapshot(t *testing.T)
54 ActiveEntry: protocol.RecentRouteEntry{Key: 1, Generation: 1, Target: testRouteTarget("0", 1), Name: "0", Kind: protocol.RouteKindLocal},54 ActiveEntry: protocol.RecentRouteEntry{Key: 1, Generation: 1, Target: testRouteTarget("0", 1), Name: "0", Kind: protocol.RouteKindLocal},
55 Home: activeRef,55 Home: activeRef,
56 }56 }
57- payload, err := ports.MarshalRecentRouteSnapshot(snapshot)57+ payload, err := wire.MarshalRecentRouteSnapshot(snapshot)
58 require.NoError(t, err)58 require.NoError(t, err)
59- require.False(t, d.handleAttachmentClientFrame(token, ports.Frame{Type: ports.MsgRecentRouteSnapshot, Payload: payload}))59+ require.False(t, d.handleAttachmentClientFrame(token, wire.Frame{Type: wire.MsgRecentRouteSnapshot, Payload: payload}))
60 60
61- frame := awaitFrame(t, sends, ports.MsgCommittedRouteIdentity)61+ frame := awaitFrame(t, sends, wire.MsgCommittedRouteIdentity)
62- identity, err := ports.UnmarshalCommittedRouteIdentity(frame.Payload)62+ identity, err := wire.UnmarshalCommittedRouteIdentity(frame.Payload)
63 require.NoError(t, err)63 require.NoError(t, err)
64 require.Equal(t, protocol.ExactSessionTarget{64 require.Equal(t, protocol.ExactSessionTarget{
65 LifecycleID: domain.IncarnationID{1},65 LifecycleID: domain.IncarnationID{1},
M internal/usecase/daemon/session_test.go +46 -45
@@ -20,6 +20,7 @@ import (
20 "github.com/bnema/vev/internal/ports"20 "github.com/bnema/vev/internal/ports"
21 portsmocks "github.com/bnema/vev/internal/ports/mocks"21 portsmocks "github.com/bnema/vev/internal/ports/mocks"
22 "github.com/bnema/vev/internal/protocol"22 "github.com/bnema/vev/internal/protocol"
23+ "github.com/bnema/vev/internal/protocol/wire"
23 "github.com/bnema/vev/internal/usecase/picker"24 "github.com/bnema/vev/internal/usecase/picker"
24 themeui "github.com/bnema/vev/internal/usecase/theme"25 themeui "github.com/bnema/vev/internal/usecase/theme"
25 )26 )
@@ -42,12 +43,12 @@ func TestHandshakeEphemeralHappy(t *testing.T) {
42 var hg sync.WaitGroup43 var hg sync.WaitGroup
43 hg.Go(func() { d.handleConn(tr) })44 hg.Go(func() { d.handleConn(tr) })
44 45
45- w := awaitFrame(t, sends, ports.MsgWelcome)46+ w := awaitFrame(t, sends, wire.MsgWelcome)
46- welcome, err := ports.UnmarshalWelcome(w.Payload)47+ welcome, err := wire.UnmarshalWelcome(w.Payload)
47 require.NoError(t, err)48 require.NoError(t, err)
48 require.Equal(t, "0", welcome.SessionName)49 require.Equal(t, "0", welcome.SessionName)
49 require.True(t, welcome.Ephemeral)50 require.True(t, welcome.Ephemeral)
50- awaitFrame(t, sends, ports.MsgOutput) // guaranteed first paint51+ awaitFrame(t, sends, wire.MsgOutput) // guaranteed first paint
51 require.Equal(t, 1, sessionCount(d))52 require.Equal(t, 1, sessionCount(d))
52 53
53 releaseConn()54 releaseConn()
@@ -64,8 +65,8 @@ func TestHandshakeNewHappy(t *testing.T) {
64 var hg sync.WaitGroup65 var hg sync.WaitGroup
65 hg.Go(func() { d.handleConn(tr) })66 hg.Go(func() { d.handleConn(tr) })
66 67
67- w := awaitFrame(t, sends, ports.MsgWelcome)68+ w := awaitFrame(t, sends, wire.MsgWelcome)
68- welcome, err := ports.UnmarshalWelcome(w.Payload)69+ welcome, err := wire.UnmarshalWelcome(w.Payload)
69 require.NoError(t, err)70 require.NoError(t, err)
70 require.Equal(t, "work", welcome.SessionName)71 require.Equal(t, "work", welcome.SessionName)
71 require.False(t, welcome.Ephemeral)72 require.False(t, welcome.Ephemeral)
@@ -84,15 +85,15 @@ func TestHandshakeNewHappy(t *testing.T) {
84 func TestHandshakeVersionMismatch(t *testing.T) {85 func TestHandshakeVersionMismatch(t *testing.T) {
85 // No Open expectation: the factory must never be asked to spawn a PTY.86 // No Open expectation: the factory must never be asked to spawn a PTY.
86 d := newTestDaemon(t, portsmocks.NewMockPTYFactory(t), stubClock{})87 d := newTestDaemon(t, portsmocks.NewMockPTYFactory(t), stubClock{})
87- bad := ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(protocol.Hello{88+ bad := wire.Frame{Type: wire.MsgHello, Payload: wire.MarshalHello(protocol.Hello{
88 Version: protocol.Version + 99, Intent: protocol.IntentEphemeral, Size: domain.Size{Cols: 80, Rows: 24},89 Version: protocol.Version + 99, Intent: protocol.IntentEphemeral, Size: domain.Size{Cols: 80, Rows: 24},
89 })}90 })}
90 tr, sends, _ := newConn(t, bad)91 tr, sends, _ := newConn(t, bad)
91 92
92 d.handleConn(tr) // returns after the rejection; no session, no goroutines93 d.handleConn(tr) // returns after the rejection; no session, no goroutines
93 94
94- e := awaitFrame(t, sends, ports.MsgError)95+ e := awaitFrame(t, sends, wire.MsgError)
95- em, err := ports.UnmarshalErrorMsg(e.Payload)96+ em, err := wire.UnmarshalErrorMsg(e.Payload)
96 require.NoError(t, err)97 require.NoError(t, err)
97 require.Equal(t, protocol.ErrVersionMismatch, em.Code)98 require.Equal(t, protocol.ErrVersionMismatch, em.Code)
98 require.Equal(t, 0, sessionCount(d))99 require.Equal(t, 0, sessionCount(d))
@@ -108,11 +109,11 @@ func TestHandshakeOldHelloLayoutReportsVersionMismatch(t *testing.T) {
108 0x00, 0x18,109 0x00, 0x18,
109 0x00, 0x00,110 0x00, 0x00,
110 }111 }
111- tr, sends, _ := newConn(t, ports.Frame{Type: ports.MsgHello, Payload: oldLayout})112+ tr, sends, _ := newConn(t, wire.Frame{Type: wire.MsgHello, Payload: oldLayout})
112 d.handleConn(tr)113 d.handleConn(tr)
113 114
114- e := awaitFrame(t, sends, ports.MsgError)115+ e := awaitFrame(t, sends, wire.MsgError)
115- em, err := ports.UnmarshalErrorMsg(e.Payload)116+ em, err := wire.UnmarshalErrorMsg(e.Payload)
116 require.NoError(t, err)117 require.NoError(t, err)
117 require.Equal(t, protocol.ErrVersionMismatch, em.Code)118 require.Equal(t, protocol.ErrVersionMismatch, em.Code)
118 require.Equal(t, 0, sessionCount(d))119 require.Equal(t, 0, sessionCount(d))
@@ -126,8 +127,8 @@ func TestHandshakeNameTaken(t *testing.T) {
126 tr, sends, _ := newConn(t, mustHello(protocol.IntentNew, "work", domain.Size{Cols: 80, Rows: 24}))127 tr, sends, _ := newConn(t, mustHello(protocol.IntentNew, "work", domain.Size{Cols: 80, Rows: 24}))
127 d.handleConn(tr)128 d.handleConn(tr)
128 129
129- e := awaitFrame(t, sends, ports.MsgError)130+ e := awaitFrame(t, sends, wire.MsgError)
130- em, _ := ports.UnmarshalErrorMsg(e.Payload)131+ em, _ := wire.UnmarshalErrorMsg(e.Payload)
131 require.Equal(t, protocol.ErrNameTaken, em.Code)132 require.Equal(t, protocol.ErrNameTaken, em.Code)
132 }133 }
133 134
@@ -136,14 +137,14 @@ func TestHandshakeNoSuchSession(t *testing.T) {
136 tr, sends, _ := newConn(t, mustHello(protocol.IntentAttach, "ghost", domain.Size{Cols: 80, Rows: 24}))137 tr, sends, _ := newConn(t, mustHello(protocol.IntentAttach, "ghost", domain.Size{Cols: 80, Rows: 24}))
137 d.handleConn(tr)138 d.handleConn(tr)
138 139
139- e := awaitFrame(t, sends, ports.MsgError)140+ e := awaitFrame(t, sends, wire.MsgError)
140- em, _ := ports.UnmarshalErrorMsg(e.Payload)141+ em, _ := wire.UnmarshalErrorMsg(e.Payload)
141 require.Equal(t, protocol.ErrNoSuchSession, em.Code)142 require.Equal(t, protocol.ErrNoSuchSession, em.Code)
142 }143 }
143 144
144 func TestKillAllEmptyDaemonSignalsShutdown(t *testing.T) {145 func TestKillAllEmptyDaemonSignalsShutdown(t *testing.T) {
145 d := newTestDaemon(t, portsmocks.NewMockPTYFactory(t), stubClock{})146 d := newTestDaemon(t, portsmocks.NewMockPTYFactory(t), stubClock{})
146- tr, _, _ := newConn(t, ports.Frame{Type: ports.MsgKill, Payload: ports.MarshalKill(protocol.Kill{All: true})})147+ tr, _, _ := newConn(t, wire.Frame{Type: wire.MsgKill, Payload: wire.MarshalKill(protocol.Kill{All: true})})
147 d.handleConn(tr)148 d.handleConn(tr)
148 149
149 select {150 select {
@@ -232,8 +233,8 @@ func TestCreateTabClosesPTYIfSessionKilledDuringOpen(t *testing.T) {
232 tr, sends, releaseConn := newConn(t, mustHello(protocol.IntentNew, "work", domain.Size{Cols: 80, Rows: 24}))233 tr, sends, releaseConn := newConn(t, mustHello(protocol.IntentNew, "work", domain.Size{Cols: 80, Rows: 24}))
233 var hg sync.WaitGroup234 var hg sync.WaitGroup
234 hg.Go(func() { d.handleConn(tr) })235 hg.Go(func() { d.handleConn(tr) })
235- awaitFrame(t, sends, ports.MsgWelcome)236+ awaitFrame(t, sends, wire.MsgWelcome)
236- awaitFrame(t, sends, ports.MsgOutput)237+ awaitFrame(t, sends, wire.MsgOutput)
237 sess := firstSession(d)238 sess := firstSession(d)
238 require.NotNil(t, sess)239 require.NotNil(t, sess)
239 240
@@ -289,13 +290,13 @@ func TestDetachKeepsEphemeralHeadless(t *testing.T) {
289 d := newTestDaemon(t, newFactory(t, p), stubClock{})290 d := newTestDaemon(t, newFactory(t, p), stubClock{})
290 tr, sends, _ := newConn(t,291 tr, sends, _ := newConn(t,
291 mustHello(protocol.IntentEphemeral, "", domain.Size{Cols: 80, Rows: 24}),292 mustHello(protocol.IntentEphemeral, "", domain.Size{Cols: 80, Rows: 24}),
292- ports.Frame{Type: ports.MsgDetach, Payload: ports.MarshalDetach(protocol.Detach{})},293+ wire.Frame{Type: wire.MsgDetach, Payload: wire.MarshalDetach(protocol.Detach{})},
293 )294 )
294 295
295 var hg sync.WaitGroup296 var hg sync.WaitGroup
296 hg.Go(func() { d.handleConn(tr) })297 hg.Go(func() { d.handleConn(tr) })
297- awaitFrame(t, sends, ports.MsgWelcome)298+ awaitFrame(t, sends, wire.MsgWelcome)
298- awaitFrame(t, sends, ports.MsgDetached)299+ awaitFrame(t, sends, wire.MsgDetached)
299 hg.Wait()300 hg.Wait()
300 301
301 require.Equal(t, 1, sessionCount(d), "ephemeral session survives explicit detach")302 require.Equal(t, 1, sessionCount(d), "ephemeral session survives explicit detach")
@@ -338,14 +339,14 @@ func TestDetachKeepsNamed(t *testing.T) {
338 d := newTestDaemon(t, newFactory(t, p), stubClock{})339 d := newTestDaemon(t, newFactory(t, p), stubClock{})
339 tr, sends, _ := newConn(t,340 tr, sends, _ := newConn(t,
340 mustHello(protocol.IntentNew, "keep", domain.Size{Cols: 80, Rows: 24}),341 mustHello(protocol.IntentNew, "keep", domain.Size{Cols: 80, Rows: 24}),
341- ports.Frame{Type: ports.MsgDetach, Payload: ports.MarshalDetach(protocol.Detach{})},342+ wire.Frame{Type: wire.MsgDetach, Payload: wire.MarshalDetach(protocol.Detach{})},
342 )343 )
343 344
344 var hg sync.WaitGroup345 var hg sync.WaitGroup
345 hg.Go(func() { d.handleConn(tr) })346 hg.Go(func() { d.handleConn(tr) })
346- awaitFrame(t, sends, ports.MsgWelcome)347+ awaitFrame(t, sends, wire.MsgWelcome)
347- awaitFrame(t, sends, ports.MsgDetached) // ack for explicit detach348+ awaitFrame(t, sends, wire.MsgDetached) // ack for explicit detach
348- hg.Wait() // handler returns after detach349+ hg.Wait() // handler returns after detach
349 350
350 require.Equal(t, 1, sessionCount(d), "named session survives detach")351 require.Equal(t, 1, sessionCount(d), "named session survives detach")
351 sess := firstSession(d)352 sess := firstSession(d)
@@ -375,8 +376,8 @@ func TestReaderEOFRemovesSessionAndSignalsShutdown(t *testing.T) {
375 hg.Go(func() { d.handleConn(tr) })376 hg.Go(func() { d.handleConn(tr) })
376 377
377 // The client is detached with ReasonSessionKilled when the child exits.378 // The client is detached with ReasonSessionKilled when the child exits.
378- det := awaitFrame(t, sends, ports.MsgDetached)379+ det := awaitFrame(t, sends, wire.MsgDetached)
379- dm, _ := ports.UnmarshalDetached(det.Payload)380+ dm, _ := wire.UnmarshalDetached(det.Payload)
380 require.Equal(t, protocol.ReasonSessionKilled, dm.Reason)381 require.Equal(t, protocol.ReasonSessionKilled, dm.Reason)
381 382
382 select {383 select {
@@ -418,8 +419,8 @@ func TestServeReturnsWhenLastSessionExits(t *testing.T) {
418 served := make(chan error, 1)419 served := make(chan error, 1)
419 go func() { served <- d.Serve(context.Background(), l) }()420 go func() { served <- d.Serve(context.Background(), l) }()
420 421
421- awaitFrame(t, sends, ports.MsgWelcome)422+ awaitFrame(t, sends, wire.MsgWelcome)
422- awaitFrame(t, sends, ports.MsgOutput)423+ awaitFrame(t, sends, wire.MsgOutput)
423 424
424 // Child exits -> session removed -> registry empties -> Serve returns.425 // Child exits -> session removed -> registry empties -> Serve returns.
425 releasePTY()426 releasePTY()
@@ -459,13 +460,13 @@ func TestServeGracefulShutdownOnContextCancel(t *testing.T) {
459 served := make(chan error, 1)460 served := make(chan error, 1)
460 go func() { served <- d.Serve(ctx, l) }()461 go func() { served <- d.Serve(ctx, l) }()
461 462
462- awaitFrame(t, sends, ports.MsgWelcome)463+ awaitFrame(t, sends, wire.MsgWelcome)
463- awaitFrame(t, sends, ports.MsgOutput)464+ awaitFrame(t, sends, wire.MsgOutput)
464 465
465 cancel() // graceful shutdown466 cancel() // graceful shutdown
466 467
467- det := awaitFrame(t, sends, ports.MsgDetached)468+ det := awaitFrame(t, sends, wire.MsgDetached)
468- dm, _ := ports.UnmarshalDetached(det.Payload)469+ dm, _ := wire.UnmarshalDetached(det.Payload)
469 require.Equal(t, protocol.ReasonServerShutdown, dm.Reason)470 require.Equal(t, protocol.ReasonServerShutdown, dm.Reason)
470 471
471 select {472 select {
@@ -515,11 +516,11 @@ func TestHelloRacingShutdownIsRejected(t *testing.T) {
515 tr1, sends1, release1 := newConn(t, mustHello(protocol.IntentEphemeral, "", domain.Size{Cols: 80, Rows: 24}))516 tr1, sends1, release1 := newConn(t, mustHello(protocol.IntentEphemeral, "", domain.Size{Cols: 80, Rows: 24}))
516 var hg sync.WaitGroup517 var hg sync.WaitGroup
517 hg.Go(func() { d.handleConn(tr1) })518 hg.Go(func() { d.handleConn(tr1) })
518- awaitFrame(t, sends1, ports.MsgWelcome)519+ awaitFrame(t, sends1, wire.MsgWelcome)
519 // firstPaint starts the background prewarm asynchronously. It is valid for520 // firstPaint starts the background prewarm asynchronously. It is valid for
520 // this Open to occur before shutdown, so do not mistake it for the racing521 // this Open to occur before shutdown, so do not mistake it for the racing
521 // Hello's forbidden launch.522 // Hello's forbidden launch.
522- awaitFrame(t, sends1, ports.MsgOutput)523+ awaitFrame(t, sends1, wire.MsgOutput)
523 select {524 select {
524 case <-preShutdownFloatingOpen:525 case <-preShutdownFloatingOpen:
525 case <-time.After(time.Second):526 case <-time.After(time.Second):
@@ -548,8 +549,8 @@ func TestHelloRacingShutdownIsRejected(t *testing.T) {
548 } {549 } {
549 tr2, sends2, _ := newConn(t, mustHello(intent.intent, intent.sess, domain.Size{Cols: 80, Rows: 24}))550 tr2, sends2, _ := newConn(t, mustHello(intent.intent, intent.sess, domain.Size{Cols: 80, Rows: 24}))
550 d.handleConn(tr2)551 d.handleConn(tr2)
551- e := awaitFrame(t, sends2, ports.MsgError)552+ e := awaitFrame(t, sends2, wire.MsgError)
552- em, err := ports.UnmarshalErrorMsg(e.Payload)553+ em, err := wire.UnmarshalErrorMsg(e.Payload)
553 require.NoError(t, err)554 require.NoError(t, err)
554 require.Equal(t, protocol.ErrServerShutdown, em.Code, "%s hello racing shutdown must be rejected", intent.name)555 require.Equal(t, protocol.ErrServerShutdown, em.Code, "%s hello racing shutdown must be rejected", intent.name)
555 }556 }
@@ -573,23 +574,23 @@ func TestServeReturnsDespiteWedgedClientOnShutdown(t *testing.T) {
573 p, _ := newBlockingPTY(t) // Close unblocks the parked reader574 p, _ := newBlockingPTY(t) // Close unblocks the parked reader
574 575
575 tr := portsmocks.NewMockTransport(t)576 tr := portsmocks.NewMockTransport(t)
576- sends := make(chan ports.Frame, 64)577+ sends := make(chan wire.Frame, 64)
577- recvCh := make(chan ports.Frame, 1)578+ recvCh := make(chan wire.Frame, 1)
578 recvCh <- mustHello(protocol.IntentNew, "wedge", domain.Size{Cols: 80, Rows: 24})579 recvCh <- mustHello(protocol.IntentNew, "wedge", domain.Size{Cols: 80, Rows: 24})
579 connDone := make(chan struct{})580 connDone := make(chan struct{})
580 var connOnce sync.Once581 var connOnce sync.Once
581 closeConn := func() { connOnce.Do(func() { close(connDone) }) }582 closeConn := func() { connOnce.Do(func() { close(connDone) }) }
582 583
583- tr.EXPECT().Recv().RunAndReturn(func() (ports.Frame, error) {584+ tr.EXPECT().Recv().RunAndReturn(func() (wire.Frame, error) {
584 select {585 select {
585 case f := <-recvCh:586 case f := <-recvCh:
586 return f, nil587 return f, nil
587 case <-connDone:588 case <-connDone:
588- return ports.Frame{}, io.EOF589+ return wire.Frame{}, io.EOF
589 }590 }
590 }).Maybe()591 }).Maybe()
591- tr.EXPECT().Send(mock.Anything).RunAndReturn(func(f ports.Frame) error {592+ tr.EXPECT().Send(mock.Anything).RunAndReturn(func(f wire.Frame) error {
592- if f.Type == ports.MsgDetached {593+ if f.Type == wire.MsgDetached {
593 // Wedged: blocks until the transport is force-closed.594 // Wedged: blocks until the transport is force-closed.
594 <-connDone595 <-connDone
595 return io.ErrClosedPipe596 return io.ErrClosedPipe
@@ -621,8 +622,8 @@ func TestServeReturnsDespiteWedgedClientOnShutdown(t *testing.T) {
621 served := make(chan error, 1)622 served := make(chan error, 1)
622 go func() { served <- d.Serve(ctx, l) }()623 go func() { served <- d.Serve(ctx, l) }()
623 624
624- awaitFrame(t, sends, ports.MsgWelcome)625+ awaitFrame(t, sends, wire.MsgWelcome)
625- awaitFrame(t, sends, ports.MsgOutput)626+ awaitFrame(t, sends, wire.MsgOutput)
626 627
627 cancel() // shutdown with the wedged client still attached628 cancel() // shutdown with the wedged client still attached
628 629
M internal/usecase/daemon/snapshot_scheduler_test.go +4 -3
@@ -15,6 +15,7 @@ import (
15 "github.com/bnema/vev/internal/ports"15 "github.com/bnema/vev/internal/ports"
16 portsmocks "github.com/bnema/vev/internal/ports/mocks"16 portsmocks "github.com/bnema/vev/internal/ports/mocks"
17 "github.com/bnema/vev/internal/protocol"17 "github.com/bnema/vev/internal/protocol"
18+ "github.com/bnema/vev/internal/protocol/wire"
18 )19 )
19 20
20 func TestSnapshotCoordinatorQuarantineCancelsPublicationBeforeDelete(t *testing.T) {21 func TestSnapshotCoordinatorQuarantineCancelsPublicationBeforeDelete(t *testing.T) {
@@ -311,7 +312,7 @@ func TestServeShutdownDeadlineStillJoinsPaneExitWriter(t *testing.T) {
311 served := make(chan error, 1)312 served := make(chan error, 1)
312 go func() { served <- d.Serve(ctx, listener) }()313 go func() { served <- d.Serve(ctx, listener) }()
313 314
314- awaitFrame(t, sends, ports.MsgWelcome)315+ awaitFrame(t, sends, wire.MsgWelcome)
315 sess := firstSession(d)316 sess := firstSession(d)
316 require.NotNil(t, sess)317 require.NotNil(t, sess)
317 318
@@ -476,7 +477,7 @@ func TestServeShutdownCheckpointsBeforeStoppingSnapshotWorker(t *testing.T) {
476 served := make(chan error, 1)477 served := make(chan error, 1)
477 go func() { served <- d.Serve(ctx, l) }()478 go func() { served <- d.Serve(ctx, l) }()
478 479
479- awaitFrame(t, sends, ports.MsgWelcome)480+ awaitFrame(t, sends, wire.MsgWelcome)
480 markSnapshotDirty(firstSession(d))481 markSnapshotDirty(firstSession(d))
481 d.snapshotWake <- struct{}{}482 d.snapshotWake <- struct{}{}
482 first := <-published483 first := <-published
@@ -533,7 +534,7 @@ func TestServeShutdownDeadlineStillJoinsUncooperativeSnapshotRepository(t *testi
533 served := make(chan error, 1)534 served := make(chan error, 1)
534 go func() { served <- d.Serve(ctx, l) }()535 go func() { served <- d.Serve(ctx, l) }()
535 536
536- awaitFrame(t, sends, ports.MsgWelcome)537+ awaitFrame(t, sends, wire.MsgWelcome)
537 markSnapshotDirty(firstSession(d))538 markSnapshotDirty(firstSession(d))
538 d.snapshotWake <- struct{}{}539 d.snapshotWake <- struct{}{}
539 select {540 select {
M internal/usecase/daemon/status_test.go +6 -6
@@ -13,8 +13,8 @@ import (
13 13
14 renderer "github.com/bnema/vev-vt/ansi"14 renderer "github.com/bnema/vev-vt/ansi"
15 "github.com/bnema/vev/internal/domain"15 "github.com/bnema/vev/internal/domain"
16- "github.com/bnema/vev/internal/ports"
17 "github.com/bnema/vev/internal/protocol"16 "github.com/bnema/vev/internal/protocol"
17+ "github.com/bnema/vev/internal/protocol/wire"
18 "github.com/bnema/vev/internal/usecase/command"18 "github.com/bnema/vev/internal/usecase/command"
19 "github.com/bnema/vev/internal/usecase/layout"19 "github.com/bnema/vev/internal/usecase/layout"
20 "github.com/bnema/vev/internal/usecase/palette"20 "github.com/bnema/vev/internal/usecase/palette"
@@ -1445,13 +1445,13 @@ func TestStatusCoalescesCreateSwitchAndResize(t *testing.T) {
1445 frameInput([]byte("\x1b ")),1445 frameInput([]byte("\x1b ")),
1446 frameInput([]byte("CNT\r")),1446 frameInput([]byte("CNT\r")),
1447 frameInput([]byte("\x1b1")),1447 frameInput([]byte("\x1b1")),
1448- ports.Frame{Type: ports.MsgResize, Payload: mustMarshalResize(protocol.Resize{Size: domain.Size{Cols: 22, Rows: 6}})},1448+ wire.Frame{Type: wire.MsgResize, Payload: mustMarshalResize(protocol.Resize{Size: domain.Size{Cols: 22, Rows: 6}})},
1449 )1449 )
1450 1450
1451 var hg sync.WaitGroup1451 var hg sync.WaitGroup
1452 hg.Go(func() { d.handleConn(tr) })1452 hg.Go(func() { d.handleConn(tr) })
1453- awaitFrame(t, sends, ports.MsgWelcome)1453+ awaitFrame(t, sends, wire.MsgWelcome)
1454- first := awaitFrame(t, sends, ports.MsgOutput)1454+ first := awaitFrame(t, sends, wire.MsgOutput)
1455 require.Eventually(t, func() bool {1455 require.Eventually(t, func() bool {
1456 sessions := listSessions(t, d)1456 sessions := listSessions(t, d)
1457 return len(sessions.Sessions) == 1 && sessions.Sessions[0].Tabs == 21457 return len(sessions.Sessions) == 1 && sessions.Sessions[0].Tabs == 2
@@ -1466,8 +1466,8 @@ func TestStatusCoalescesCreateSwitchAndResize(t *testing.T) {
1466 "timed out awaiting resized output frame",1466 "timed out awaiting resized output frame",
1467 )1467 )
1468 1468
1469- for _, f := range []ports.Frame{first, resized} {1469+ for _, f := range []wire.Frame{first, resized} {
1470- out, err := ports.UnmarshalOutput(f.Payload)1470+ out, err := wire.UnmarshalOutput(f.Payload)
1471 require.NoError(t, err)1471 require.NoError(t, err)
1472 require.Contains(t, string(out.Data), "work")1472 require.Contains(t, string(out.Data), "work")
1473 require.Contains(t, string(out.Data), ";7m", "active status tab should be inverse-highlighted")1473 require.Contains(t, string(out.Data), ";7m", "active status tab should be inverse-highlighted")
M internal/usecase/daemon/test_wait_helpers_test.go +8 -8
@@ -4,13 +4,13 @@ import (
4 "testing"4 "testing"
5 "time"5 "time"
6 6
7- "github.com/bnema/vev/internal/ports"7+ "github.com/bnema/vev/internal/protocol/wire"
8 )8 )
9 9
10 const testWaitTimeout = 2 * time.Second10 const testWaitTimeout = 2 * time.Second
11 11
12-func testFramesOfType(frames []ports.Frame, typ ports.MsgType) []ports.Frame {12+func testFramesOfType(frames []wire.Frame, typ wire.MsgType) []wire.Frame {
13- matched := make([]ports.Frame, 0, len(frames))13+ matched := make([]wire.Frame, 0, len(frames))
14 for _, frame := range frames {14 for _, frame := range frames {
15 if frame.Type == typ {15 if frame.Type == typ {
16 matched = append(matched, frame)16 matched = append(matched, frame)
@@ -40,21 +40,21 @@ func awaitTestCompletion(t *testing.T, done <-chan struct{}, failure string) {
40 40
41 func awaitCoordinatorOutput(41 func awaitCoordinatorOutput(
42 t *testing.T,42 t *testing.T,
43- sends <-chan ports.Frame,43+ sends <-chan wire.Frame,
44 timers <-chan *coordinatorMockTimer,44 timers <-chan *coordinatorMockTimer,
45 frameContext string,45 frameContext string,
46 timeoutFailure string,46 timeoutFailure string,
47-) ports.Frame {47+) wire.Frame {
48 t.Helper()48 t.Helper()
49 deadline := time.NewTimer(testWaitTimeout)49 deadline := time.NewTimer(testWaitTimeout)
50 defer deadline.Stop()50 defer deadline.Stop()
51 for {51 for {
52 select {52 select {
53 case frame := <-sends:53 case frame := <-sends:
54- if frame.Type == ports.MsgRoutePosition {54+ if frame.Type == wire.MsgRoutePosition {
55 continue55 continue
56 }56 }
57- if frame.Type != ports.MsgOutput {57+ if frame.Type != wire.MsgOutput {
58 t.Fatalf("unexpected frame type %d %s", frame.Type, frameContext)58 t.Fatalf("unexpected frame type %d %s", frame.Type, frameContext)
59 }59 }
60 return frame60 return frame
@@ -62,7 +62,7 @@ func awaitCoordinatorOutput(
62 timer.ch <- time.Time{}62 timer.ch <- time.Time{}
63 case <-deadline.C:63 case <-deadline.C:
64 t.Fatal(timeoutFailure)64 t.Fatal(timeoutFailure)
65- return ports.Frame{}65+ return wire.Frame{}
66 }66 }
67 }67 }
68 }68 }
M internal/usecase/daemon/transactional_resize_test.go +5 -4
@@ -12,6 +12,7 @@ import (
12 12
13 "github.com/bnema/vev/internal/domain"13 "github.com/bnema/vev/internal/domain"
14 "github.com/bnema/vev/internal/ports"14 "github.com/bnema/vev/internal/ports"
15+ "github.com/bnema/vev/internal/protocol/wire"
15 "github.com/bnema/vev/internal/usecase/layout"16 "github.com/bnema/vev/internal/usecase/layout"
16 "github.com/bnema/vev/internal/usecase/ui"17 "github.com/bnema/vev/internal/usecase/ui"
17 "github.com/bnema/vev/internal/usecase/visualsearch"18 "github.com/bnema/vev/internal/usecase/visualsearch"
@@ -189,8 +190,8 @@ func TestProcessPTYDataRetainsCallbacksDuringResizeReplay(t *testing.T) {
189 // normal asynchronous client path, proving replay uses processPTYData.190 // normal asynchronous client path, proving replay uses processPTYData.
190 var clipboardOutput string191 var clipboardOutput string
191 for {192 for {
192- frame := awaitFrame(t, sends, ports.MsgOutput)193+ frame := awaitFrame(t, sends, wire.MsgOutput)
193- out, err := ports.UnmarshalOutput(frame.Payload)194+ out, err := wire.UnmarshalOutput(frame.Payload)
194 require.NoError(t, err)195 require.NoError(t, err)
195 clipboardOutput = string(out.Data)196 clipboardOutput = string(out.Data)
196 if strings.Contains(clipboardOutput, "\x1b]52;c;YQ==\a") {197 if strings.Contains(clipboardOutput, "\x1b]52;c;YQ==\a") {
@@ -839,8 +840,8 @@ func TestTransactionalResizeObsoleteTimerCallbacksCommitOnlyLatestEpoch(t *testi
839 // The resize deadline is the only debounce: commit fires its sticky reset840 // The resize deadline is the only debounce: commit fires its sticky reset
840 // through S2 immediately (subject to ACK/sync gates).841 // through S2 immediately (subject to ACK/sync gates).
841 frame := <-frames842 frame := <-frames
842- require.Equal(t, ports.MsgOutput, frame.Type, "the committed epoch emits a full frame")843+ require.Equal(t, wire.MsgOutput, frame.Type, "the committed epoch emits a full frame")
843- output, err := ports.UnmarshalOutput(frame.Payload)844+ output, err := wire.UnmarshalOutput(frame.Payload)
844 require.NoError(t, err)845 require.NoError(t, err)
845 require.Zero(t, output.Base, "the accepted resize frame must reset the renderer state")846 require.Zero(t, output.Base, "the accepted resize frame must reset the renderer state")
846 select {847 select {
M internal/usecase/daemon/transport_observability_test.go +9 -8
@@ -9,6 +9,7 @@ import (
9 9
10 "github.com/bnema/vev/internal/domain"10 "github.com/bnema/vev/internal/domain"
11 "github.com/bnema/vev/internal/ports"11 "github.com/bnema/vev/internal/ports"
12+ "github.com/bnema/vev/internal/protocol/wire"
12 )13 )
13 14
14 // This acceptance boundary exercises the attached resize, render, and ACK15 // This acceptance boundary exercises the attached resize, render, and ACK
@@ -25,11 +26,11 @@ func TestTransportObservabilityDaemonBoundaries(t *testing.T) {
25 if !d.resizeForFirstPaint(sess, ac, domain.Size{Cols: 90, Rows: 25}) {26 if !d.resizeForFirstPaint(sess, ac, domain.Size{Cols: 90, Rows: 25}) {
26 t.Fatal("first resize was not accepted")27 t.Fatal("first resize was not accepted")
27 }28 }
28- first := awaitFrame(t, sends, ports.MsgOutput)29+ first := awaitFrame(t, sends, wire.MsgOutput)
29- if first.Type != ports.MsgOutput {30+ if first.Type != wire.MsgOutput {
30 t.Fatalf("first frame = %v, want output", first.Type)31 t.Fatalf("first frame = %v, want output", first.Type)
31 }32 }
32- output, err := ports.UnmarshalOutput(first.Payload)33+ output, err := wire.UnmarshalOutput(first.Payload)
33 if err != nil {34 if err != nil {
34 t.Fatalf("decode first output: %v", err)35 t.Fatalf("decode first output: %v", err)
35 }36 }
@@ -38,8 +39,8 @@ func TestTransportObservabilityDaemonBoundaries(t *testing.T) {
38 }39 }
39 ac.ackOutputState(output.Epoch, output.New)40 ac.ackOutputState(output.Epoch, output.New)
40 rc.notifyAck()41 rc.notifyAck()
41- second := awaitFrame(t, sends, ports.MsgOutput)42+ second := awaitFrame(t, sends, wire.MsgOutput)
42- if second.Type != ports.MsgOutput {43+ if second.Type != wire.MsgOutput {
43 t.Fatalf("second frame = %v, want output", second.Type)44 t.Fatalf("second frame = %v, want output", second.Type)
44 }45 }
45 46
@@ -118,8 +119,8 @@ func TestTransportObservabilityBlockedRenderObserverReleasesArchitectureLocks(t
118 close(paintDone)119 close(paintDone)
119 }()120 }()
120 awaitDaemonObserver(t, observer.entered, "blocked render observer")121 awaitDaemonObserver(t, observer.entered, "blocked render observer")
121- frame := awaitFrame(t, sends, ports.MsgOutput) // The frame was emitted before the observer began its I/O.122+ frame := awaitFrame(t, sends, wire.MsgOutput) // The frame was emitted before the observer began its I/O.
122- output, err := ports.UnmarshalOutput(frame.Payload)123+ output, err := wire.UnmarshalOutput(frame.Payload)
123 if err != nil {124 if err != nil {
124 t.Fatalf("decode emitted output: %v", err)125 t.Fatalf("decode emitted output: %v", err)
125 }126 }
@@ -153,7 +154,7 @@ func TestTransportObservabilityBlockedRenderObserverReleasesArchitectureLocks(t
153 d.paint(sess, ac, false, nil)154 d.paint(sess, ac, false, nil)
154 close(secondPaint)155 close(secondPaint)
155 }()156 }()
156- _ = awaitFrame(t, sends, ports.MsgOutput) // The second frame is live even though serialized observer I/O waits.157+ _ = awaitFrame(t, sends, wire.MsgOutput) // The second frame is live even though serialized observer I/O waits.
157 158
158 close(observer.release)159 close(observer.release)
159 awaitDaemonObserver(t, paintDone, "first render completion")160 awaitDaemonObserver(t, paintDone, "first render completion")
M internal/usecase/daemon/wire.go +17 -17
@@ -3,11 +3,11 @@ package daemon
3 3
4 import (4 import (
5 "github.com/bnema/vev/internal/domain"5 "github.com/bnema/vev/internal/domain"
6- "github.com/bnema/vev/internal/ports"
7 "github.com/bnema/vev/internal/protocol"6 "github.com/bnema/vev/internal/protocol"
7+ "github.com/bnema/vev/internal/protocol/wire"
8 )8 )
9 9
10-func frameWelcome(s *session, resumeToken uint64) (ports.Frame, error) {10+func frameWelcome(s *session, resumeToken uint64) (wire.Frame, error) {
11 s.mu.Lock()11 s.mu.Lock()
12 var identity *protocol.CommittedRouteIdentity12 var identity *protocol.CommittedRouteIdentity
13 if s.incarnation != (domain.SessionLifecycleID{}) {13 if s.incarnation != (domain.SessionLifecycleID{}) {
@@ -25,36 +25,36 @@ func frameWelcome(s *session, resumeToken uint64) (ports.Frame, error) {
25 }25 }
26 s.mu.Unlock()26 s.mu.Unlock()
27 w.ResumeToken = resumeToken27 w.ResumeToken = resumeToken
28- payload := ports.MarshalWelcome(w)28+ payload := wire.MarshalWelcome(w)
29 if payload == nil {29 if payload == nil {
30- return ports.Frame{}, protocol.ErrInvalidRouteWire30+ return wire.Frame{}, protocol.ErrInvalidRouteWire
31 }31 }
32- return ports.Frame{Type: ports.MsgWelcome, Payload: payload}, nil32+ return wire.Frame{Type: wire.MsgWelcome, Payload: payload}, nil
33 }33 }
34 34
35-func frameError(code uint16, text string) ports.Frame {35+func frameError(code uint16, text string) wire.Frame {
36- return ports.Frame{Type: ports.MsgError, Payload: ports.MarshalErrorMsg(protocol.ErrorMsg{Code: code, Text: text})}36+ return wire.Frame{Type: wire.MsgError, Payload: wire.MarshalErrorMsg(protocol.ErrorMsg{Code: code, Text: text})}
37 }37 }
38 38
39-func frameOutputState(b []byte, baseState uint64, state uint64, echoAck uint64) (ports.Frame, error) {39+func frameOutputState(b []byte, baseState uint64, state uint64, echoAck uint64) (wire.Frame, error) {
40- payload, err := ports.MarshalOutput(protocol.Output{40+ payload, err := wire.MarshalOutput(protocol.Output{
41 Epoch: 1, Base: baseState, New: state, Echo: echoAck,41 Epoch: 1, Base: baseState, New: state, Echo: echoAck,
42 Size: domain.Size{Cols: 1, Rows: 1}, Full: state != 0 && baseState == 0, Data: b,42 Size: domain.Size{Cols: 1, Rows: 1}, Full: state != 0 && baseState == 0, Data: b,
43 })43 })
44 if err != nil {44 if err != nil {
45- return ports.Frame{}, err45+ return wire.Frame{}, err
46 }46 }
47- return ports.Frame{Type: ports.MsgOutput, Payload: payload}, nil47+ return wire.Frame{Type: wire.MsgOutput, Payload: payload}, nil
48 }48 }
49 49
50-func frameDetached(reason uint8) ports.Frame {50+func frameDetached(reason uint8) wire.Frame {
51- return ports.Frame{Type: ports.MsgDetached, Payload: ports.MarshalDetached(protocol.Detached{Reason: reason})}51+ return wire.Frame{Type: wire.MsgDetached, Payload: wire.MarshalDetached(protocol.Detached{Reason: reason})}
52 }52 }
53 53
54-func framePong() ports.Frame {54+func framePong() wire.Frame {
55- return ports.Frame{Type: ports.MsgPong, Payload: ports.MarshalPong(protocol.Pong{})}55+ return wire.Frame{Type: wire.MsgPong, Payload: wire.MarshalPong(protocol.Pong{})}
56 }56 }
57 57
58-func frameSessions(infos []protocol.SessionInfo) ports.Frame {58+func frameSessions(infos []protocol.SessionInfo) wire.Frame {
59- return ports.Frame{Type: ports.MsgSessions, Payload: ports.MarshalSessions(protocol.Sessions{Sessions: infos})}59+ return wire.Frame{Type: wire.MsgSessions, Payload: wire.MarshalSessions(protocol.Sessions{Sessions: infos})}
60 }60 }
M internal/usecase/daemon/wire_metadata_test.go +2 -2
@@ -5,7 +5,7 @@ import (
5 "sync"5 "sync"
6 "testing"6 "testing"
7 7
8- "github.com/bnema/vev/internal/ports"8+ "github.com/bnema/vev/internal/protocol/wire"
9 )9 )
10 10
11 func TestFrameWelcomeSnapshotsSessionMetadataUnderLock(t *testing.T) {11 func TestFrameWelcomeSnapshotsSessionMetadataUnderLock(t *testing.T) {
@@ -27,7 +27,7 @@ func TestFrameWelcomeSnapshotsSessionMetadataUnderLock(t *testing.T) {
27 if frameErr != nil {27 if frameErr != nil {
28 t.Fatalf("frameWelcome() error = %v", frameErr)28 t.Fatalf("frameWelcome() error = %v", frameErr)
29 }29 }
30- welcome, err := ports.UnmarshalWelcome(frame.Payload)30+ welcome, err := wire.UnmarshalWelcome(frame.Payload)
31 if err != nil {31 if err != nil {
32 t.Fatalf("UnmarshalWelcome() error = %v", err)32 t.Fatalf("UnmarshalWelcome() error = %v", err)
33 }33 }
M internal/usecase/daemon/wire_test_helpers_test.go +4 -4
@@ -1,12 +1,12 @@
1 package daemon1 package daemon
2 2
3 import (3 import (
4- "github.com/bnema/vev/internal/ports"
5 "github.com/bnema/vev/internal/protocol"4 "github.com/bnema/vev/internal/protocol"
5+ "github.com/bnema/vev/internal/protocol/wire"
6 )6 )
7 7
8 func mustMarshalOutput(m protocol.Output) []byte {8 func mustMarshalOutput(m protocol.Output) []byte {
9- payload, err := ports.MarshalOutput(m)9+ payload, err := wire.MarshalOutput(m)
10 if err != nil {10 if err != nil {
11 panic(err)11 panic(err)
12 }12 }
@@ -14,7 +14,7 @@ func mustMarshalOutput(m protocol.Output) []byte {
14 }14 }
15 15
16 func mustMarshalAck(m protocol.Ack) []byte {16 func mustMarshalAck(m protocol.Ack) []byte {
17- payload, err := ports.MarshalAck(m)17+ payload, err := wire.MarshalAck(m)
18 if err != nil {18 if err != nil {
19 panic(err)19 panic(err)
20 }20 }
@@ -22,7 +22,7 @@ func mustMarshalAck(m protocol.Ack) []byte {
22 }22 }
23 23
24 func mustMarshalResize(m protocol.Resize) []byte {24 func mustMarshalResize(m protocol.Resize) []byte {
25- payload, err := ports.MarshalResize(m)25+ payload, err := wire.MarshalResize(m)
26 if err != nil {26 if err != nil {
27 panic(err)27 panic(err)
28 }28 }
M scripts/remote-picker-harness/main.go +32 -31
@@ -21,6 +21,7 @@ import (
21 "github.com/bnema/vev/internal/domain"21 "github.com/bnema/vev/internal/domain"
22 "github.com/bnema/vev/internal/ports"22 "github.com/bnema/vev/internal/ports"
23 "github.com/bnema/vev/internal/protocol"23 "github.com/bnema/vev/internal/protocol"
24+ "github.com/bnema/vev/internal/protocol/wire"
24 )25 )
25 26
26 const (27 const (
@@ -458,7 +459,7 @@ func localEnvironment() []string {
458 }459 }
459 460
460 type harnessFrame struct {461 type harnessFrame struct {
461- frame ports.Frame462+ frame wire.Frame
462 err error463 err error
463 }464 }
464 465
@@ -642,44 +643,44 @@ func attachAndCheck(ctx context.Context, tr *harnessTransport, hello protocol.He
642 if hello.EnvironmentPolicy == 0 {643 if hello.EnvironmentPolicy == 0 {
643 hello.EnvironmentPolicy = protocol.EnvironmentPolicyClientOwned644 hello.EnvironmentPolicy = protocol.EnvironmentPolicyClientOwned
644 }645 }
645- payload := ports.MarshalHello(hello)646+ payload := wire.MarshalHello(hello)
646- if err := tr.Send(ports.Frame{Type: ports.MsgHello, Payload: payload}); err != nil {647+ if err := tr.Send(wire.Frame{Type: wire.MsgHello, Payload: payload}); err != nil {
647 return err648 return err
648 }649 }
649 frame, err := receiveWithTimeout(ctx, tr)650 frame, err := receiveWithTimeout(ctx, tr)
650 if err != nil {651 if err != nil {
651 return err652 return err
652 }653 }
653- if frame.Type == ports.MsgError {654+ if frame.Type == wire.MsgError {
654- message, decodeErr := ports.UnmarshalErrorMsg(frame.Payload)655+ message, decodeErr := wire.UnmarshalErrorMsg(frame.Payload)
655 if decodeErr != nil {656 if decodeErr != nil {
656 return decodeErr657 return decodeErr
657 }658 }
658 return fmt.Errorf("remote error code %d", message.Code)659 return fmt.Errorf("remote error code %d", message.Code)
659 }660 }
660- if frame.Type != ports.MsgWelcome {661+ if frame.Type != wire.MsgWelcome {
661 return fmt.Errorf("welcome frame type %d", frame.Type)662 return fmt.Errorf("welcome frame type %d", frame.Type)
662 }663 }
663 tr.probe.recordControl(probeEventWelcome)664 tr.probe.recordControl(probeEventWelcome)
664- if err := tr.Send(ports.Frame{Type: ports.MsgTheme, Payload: ports.MarshalTheme(protocol.Theme{TrueColor: hello.TrueColor, SchemeKnown: true})}); err != nil {665+ if err := tr.Send(wire.Frame{Type: wire.MsgTheme, Payload: wire.MarshalTheme(protocol.Theme{TrueColor: hello.TrueColor, SchemeKnown: true})}); err != nil {
665 return fmt.Errorf("send initial theme: %w", err)666 return fmt.Errorf("send initial theme: %w", err)
666 }667 }
667 return nil668 return nil
668 }669 }
669 670
670 func sendRawInput(tr *harnessTransport, data string) error {671 func sendRawInput(tr *harnessTransport, data string) error {
671- return tr.Send(ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(protocol.Input{Data: []byte(data)})})672+ return tr.Send(wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(protocol.Input{Data: []byte(data)})})
672 }673 }
673 674
674-func processOutputFrame(tr *harnessTransport, frame ports.Frame) error {675+func processOutputFrame(tr *harnessTransport, frame wire.Frame) error {
675- output, err := ports.UnmarshalOutput(frame.Payload)676+ output, err := wire.UnmarshalOutput(frame.Payload)
676 if err != nil {677 if err != nil {
677 return err678 return err
678 }679 }
679 result := tr.probe.apply(output)680 result := tr.probe.apply(output)
680 if !result.Accepted {681 if !result.Accepted {
681 if !tr.probe.resetPending {682 if !tr.probe.resetPending {
682- if err := tr.Send(ports.Frame{Type: ports.MsgOutputResetRequest, Payload: ports.MarshalOutputResetRequest(protocol.OutputResetRequest{})}); err != nil {683+ if err := tr.Send(wire.Frame{Type: wire.MsgOutputResetRequest, Payload: wire.MarshalOutputResetRequest(protocol.OutputResetRequest{})}); err != nil {
683 return err684 return err
684 }685 }
685 tr.probe.resetPending = true686 tr.probe.resetPending = true
@@ -690,11 +691,11 @@ func processOutputFrame(tr *harnessTransport, frame ports.Frame) error {
690 if !result.StateBearing {691 if !result.StateBearing {
691 return nil692 return nil
692 }693 }
693- payload, err := ports.MarshalAck(result.Ack)694+ payload, err := wire.MarshalAck(result.Ack)
694 if err != nil {695 if err != nil {
695 return err696 return err
696 }697 }
697- if err := tr.Send(ports.Frame{Type: ports.MsgAck, Payload: payload}); err != nil {698+ if err := tr.Send(wire.Frame{Type: wire.MsgAck, Payload: payload}); err != nil {
698 return err699 return err
699 }700 }
700 tr.probe.resetPending = false701 tr.probe.resetPending = false
@@ -714,7 +715,7 @@ func awaitOutputContains(ctx context.Context, tr *harnessTransport, want string)
714 if err != nil {715 if err != nil {
715 return err716 return err
716 }717 }
717- if frame.Type != ports.MsgOutput {718+ if frame.Type != wire.MsgOutput {
718 continue719 continue
719 }720 }
720 if err := processOutputFrame(tr, frame); err != nil {721 if err := processOutputFrame(tr, frame); err != nil {
@@ -738,7 +739,7 @@ func awaitRemotePickerPreview(ctx context.Context, tr *harnessTransport, marker
738 if err != nil {739 if err != nil {
739 return fmt.Errorf("no rendered remote preview after %d output frames (loading=%t): %w", tr.probe.outputFrames, sawLoading, err)740 return fmt.Errorf("no rendered remote preview after %d output frames (loading=%t): %w", tr.probe.outputFrames, sawLoading, err)
740 }741 }
741- if frame.Type != ports.MsgOutput {742+ if frame.Type != wire.MsgOutput {
742 continue743 continue
743 }744 }
744 if err := processOutputFrame(tr, frame); err != nil {745 if err := processOutputFrame(tr, frame); err != nil {
@@ -768,7 +769,7 @@ func remotePreviewContains(preview protocol.RemotePreview, want string) bool {
768 }769 }
769 770
770 func sendInputAndAwait(ctx context.Context, tr *harnessTransport, command, want string) error {771 func sendInputAndAwait(ctx context.Context, tr *harnessTransport, command, want string) error {
771- if err := tr.Send(ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(protocol.Input{Data: []byte(command + "\n")})}); err != nil {772+ if err := tr.Send(wire.Frame{Type: wire.MsgInput, Payload: wire.MarshalInput(protocol.Input{Data: []byte(command + "\n")})}); err != nil {
772 return err773 return err
773 }774 }
774 deadline, cancel := context.WithTimeout(ctx, 5*time.Second)775 deadline, cancel := context.WithTimeout(ctx, 5*time.Second)
@@ -779,7 +780,7 @@ func sendInputAndAwait(ctx context.Context, tr *harnessTransport, command, want
779 if err != nil {780 if err != nil {
780 return fmt.Errorf("waiting for marker after %d output frames/%d bytes: %w", tr.probe.outputFrames, tr.probe.outputBytes, err)781 return fmt.Errorf("waiting for marker after %d output frames/%d bytes: %w", tr.probe.outputFrames, tr.probe.outputBytes, err)
781 }782 }
782- if frame.Type != ports.MsgOutput {783+ if frame.Type != wire.MsgOutput {
783 continue784 continue
784 }785 }
785 if err := processOutputFrame(tr, frame); err != nil {786 if err := processOutputFrame(tr, frame); err != nil {
@@ -801,11 +802,11 @@ func sendCommand(ctx context.Context, tr *harnessTransport, slug string) error {
801 Attached: true,802 Attached: true,
802 Slug: slug,803 Slug: slug,
803 }804 }
804- payload, err := ports.MarshalCommandRequest(request)805+ payload, err := wire.MarshalCommandRequest(request)
805 if err != nil {806 if err != nil {
806 return err807 return err
807 }808 }
808- if err := tr.Send(ports.Frame{Type: ports.MsgCommand, Payload: payload}); err != nil {809+ if err := tr.Send(wire.Frame{Type: wire.MsgCommand, Payload: payload}); err != nil {
809 return err810 return err
810 }811 }
811 deadline, cancel := context.WithTimeout(ctx, 5*time.Second)812 deadline, cancel := context.WithTimeout(ctx, 5*time.Second)
@@ -815,16 +816,16 @@ func sendCommand(ctx context.Context, tr *harnessTransport, slug string) error {
815 if err != nil {816 if err != nil {
816 return err817 return err
817 }818 }
818- if frame.Type == ports.MsgOutput {819+ if frame.Type == wire.MsgOutput {
819 if err := processOutputFrame(tr, frame); err != nil {820 if err := processOutputFrame(tr, frame); err != nil {
820 return err821 return err
821 }822 }
822 continue823 continue
823 }824 }
824- if frame.Type != ports.MsgCommandResult {825+ if frame.Type != wire.MsgCommandResult {
825 continue826 continue
826 }827 }
827- result, err := ports.UnmarshalCommandResult(frame.Payload)828+ result, err := wire.UnmarshalCommandResult(frame.Payload)
828 if err != nil {829 if err != nil {
829 return err830 return err
830 }831 }
@@ -836,7 +837,7 @@ func sendCommand(ctx context.Context, tr *harnessTransport, slug string) error {
836 }837 }
837 838
838 func detach(ctx context.Context, tr *harnessTransport) error {839 func detach(ctx context.Context, tr *harnessTransport) error {
839- if err := tr.Send(ports.Frame{Type: ports.MsgDetach, Payload: ports.MarshalDetach(protocol.Detach{})}); err != nil {840+ if err := tr.Send(wire.Frame{Type: wire.MsgDetach, Payload: wire.MarshalDetach(protocol.Detach{})}); err != nil {
840 return err841 return err
841 }842 }
842 deadline, cancel := context.WithTimeout(ctx, 5*time.Second)843 deadline, cancel := context.WithTimeout(ctx, 5*time.Second)
@@ -846,7 +847,7 @@ func detach(ctx context.Context, tr *harnessTransport) error {
846 if err != nil {847 if err != nil {
847 return err848 return err
848 }849 }
849- if frame.Type == ports.MsgDetached {850+ if frame.Type == wire.MsgDetached {
850 return nil851 return nil
851 }852 }
852 }853 }
@@ -871,18 +872,18 @@ func expectNoSuchTarget(ctx context.Context, tr *harnessTransport, target domain
871 RemoteTarget: &target,872 RemoteTarget: &target,
872 EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned,873 EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned,
873 }874 }
874- payload := ports.MarshalHello(hello)875+ payload := wire.MarshalHello(hello)
875- if err := tr.Send(ports.Frame{Type: ports.MsgHello, Payload: payload}); err != nil {876+ if err := tr.Send(wire.Frame{Type: wire.MsgHello, Payload: payload}); err != nil {
876 return err877 return err
877 }878 }
878 frame, err := receiveWithTimeout(ctx, tr)879 frame, err := receiveWithTimeout(ctx, tr)
879 if err != nil {880 if err != nil {
880 return err881 return err
881 }882 }
882- if frame.Type != ports.MsgError {883+ if frame.Type != wire.MsgError {
883 return fmt.Errorf("stale target returned frame type %d", frame.Type)884 return fmt.Errorf("stale target returned frame type %d", frame.Type)
884 }885 }
885- message, err := ports.UnmarshalErrorMsg(frame.Payload)886+ message, err := wire.UnmarshalErrorMsg(frame.Payload)
886 if err != nil {887 if err != nil {
887 return err888 return err
888 }889 }
@@ -1002,7 +1003,7 @@ func assertNoDirectHandoff(tr *harnessTransport) error {
1002 queued := len(tr.frames)1003 queued := len(tr.frames)
1003 for range queued {1004 for range queued {
1004 outcome := <-tr.frames1005 outcome := <-tr.frames
1005- if outcome.frame.Type == ports.MsgAttachTarget {1006+ if outcome.frame.Type == wire.MsgAttachTarget {
1006 tr.probe.recordIncoming(outcome.frame)1007 tr.probe.recordIncoming(outcome.frame)
1007 } else {1008 } else {
1008 tr.queued = append(tr.queued, outcome)1009 tr.queued = append(tr.queued, outcome)
@@ -1014,7 +1015,7 @@ func assertNoDirectHandoff(tr *harnessTransport) error {
1014 return nil1015 return nil
1015 }1016 }
1016 1017
1017-func receiveWithTimeout(ctx context.Context, tr *harnessTransport) (ports.Frame, error) {1018+func receiveWithTimeout(ctx context.Context, tr *harnessTransport) (wire.Frame, error) {
1018 if len(tr.queued) != 0 {1019 if len(tr.queued) != 0 {
1019 outcome := tr.queued[0]1020 outcome := tr.queued[0]
1020 tr.queued = tr.queued[1:]1021 tr.queued = tr.queued[1:]
@@ -1025,7 +1026,7 @@ func receiveWithTimeout(ctx context.Context, tr *harnessTransport) (ports.Frame,
1025 }1026 }
1026 select {1027 select {
1027 case <-ctx.Done():1028 case <-ctx.Done():
1028- return ports.Frame{}, ctx.Err()1029+ return wire.Frame{}, ctx.Err()
1029 case outcome := <-tr.frames:1030 case outcome := <-tr.frames:
1030 if outcome.err == nil {1031 if outcome.err == nil {
1031 tr.probe.recordIncoming(outcome.frame)1032 tr.probe.recordIncoming(outcome.frame)
M scripts/remote-picker-harness/main_test.go +4 -4
@@ -5,7 +5,7 @@ import (
5 "strings"5 "strings"
6 "testing"6 "testing"
7 7
8- "github.com/bnema/vev/internal/ports"8+ "github.com/bnema/vev/internal/protocol/wire"
9 )9 )
10 10
11 func TestBoundedStderrRetainsAtMostDiagnosticLimit(t *testing.T) {11 func TestBoundedStderrRetainsAtMostDiagnosticLimit(t *testing.T) {
@@ -50,13 +50,13 @@ func TestAssertNoDirectHandoffProcessesInitialFramesAndRetainsOutput(t *testing.
50 probe: &visualProbe{},50 probe: &visualProbe{},
51 frames: make(chan harnessFrame, 2),51 frames: make(chan harnessFrame, 2),
52 }52 }
53- tr.frames <- harnessFrame{frame: ports.Frame{Type: ports.MsgOutput}}53+ tr.frames <- harnessFrame{frame: wire.Frame{Type: wire.MsgOutput}}
54- tr.frames <- harnessFrame{frame: ports.Frame{Type: ports.MsgAttachTarget}}54+ tr.frames <- harnessFrame{frame: wire.Frame{Type: wire.MsgAttachTarget}}
55 55
56 if err := assertNoDirectHandoff(tr); err == nil {56 if err := assertNoDirectHandoff(tr); err == nil {
57 t.Fatal("direct handoff was not rejected")57 t.Fatal("direct handoff was not rejected")
58 }58 }
59- if len(tr.queued) != 1 || tr.queued[0].frame.Type != ports.MsgOutput {59+ if len(tr.queued) != 1 || tr.queued[0].frame.Type != wire.MsgOutput {
60 t.Fatalf("queued frames = %#v, want one retained output", tr.queued)60 t.Fatalf("queued frames = %#v, want one retained output", tr.queued)
61 }61 }
62 if tr.probe.unexpectedHandoffs != 1 {62 if tr.probe.unexpectedHandoffs != 1 {
M scripts/remote-picker-harness/visual_probe.go +3 -3
@@ -11,8 +11,8 @@ import (
11 11
12 vt "github.com/bnema/vev-vt"12 vt "github.com/bnema/vev-vt"
13 "github.com/bnema/vev/internal/domain"13 "github.com/bnema/vev/internal/domain"
14- "github.com/bnema/vev/internal/ports"
15 "github.com/bnema/vev/internal/protocol"14 "github.com/bnema/vev/internal/protocol"
15+ "github.com/bnema/vev/internal/protocol/wire"
16 )16 )
17 17
18 const (18 const (
@@ -237,8 +237,8 @@ func (p *visualProbe) contains(want string) bool {
237 return strings.Contains(p.text(), want)237 return strings.Contains(p.text(), want)
238 }238 }
239 239
240-func (p *visualProbe) recordIncoming(frame ports.Frame) {240+func (p *visualProbe) recordIncoming(frame wire.Frame) {
241- if p == nil || frame.Type != ports.MsgAttachTarget {241+ if p == nil || frame.Type != wire.MsgAttachTarget {
242 return242 return
243 }243 }
244 p.unexpectedHandoffs++244 p.unexpectedHandoffs++
M scripts/remote-picker-harness/visual_probe_test.go +11 -11
@@ -8,8 +8,8 @@ import (
8 "testing"8 "testing"
9 9
10 "github.com/bnema/vev/internal/domain"10 "github.com/bnema/vev/internal/domain"
11- "github.com/bnema/vev/internal/ports"
12 "github.com/bnema/vev/internal/protocol"11 "github.com/bnema/vev/internal/protocol"
12+ "github.com/bnema/vev/internal/protocol/wire"
13 "github.com/stretchr/testify/require"13 "github.com/stretchr/testify/require"
14 )14 )
15 15
@@ -19,24 +19,24 @@ const (
19 )19 )
20 20
21 type probeTestTransport struct {21 type probeTestTransport struct {
22- sent []ports.Frame22+ sent []wire.Frame
23 }23 }
24 24
25-func (t *probeTestTransport) Send(frame ports.Frame) error {25+func (t *probeTestTransport) Send(frame wire.Frame) error {
26 t.sent = append(t.sent, frame)26 t.sent = append(t.sent, frame)
27 return nil27 return nil
28 }28 }
29 29
30-func (*probeTestTransport) Recv() (ports.Frame, error) { return ports.Frame{}, errors.New("not used") }30+func (*probeTestTransport) Recv() (wire.Frame, error) { return wire.Frame{}, errors.New("not used") }
31-func (*probeTestTransport) Close() error { return nil }31+func (*probeTestTransport) Close() error { return nil }
32 32
33-func probeTestOutput(t *testing.T, output protocol.Output) ports.Frame {33+func probeTestOutput(t *testing.T, output protocol.Output) wire.Frame {
34 t.Helper()34 t.Helper()
35- payload, err := ports.MarshalOutput(output)35+ payload, err := wire.MarshalOutput(output)
36 if err != nil {36 if err != nil {
37 t.Fatalf("marshal output: %v", err)37 t.Fatalf("marshal output: %v", err)
38 }38 }
39- return ports.Frame{Type: ports.MsgOutput, Payload: payload}39+ return wire.Frame{Type: wire.MsgOutput, Payload: payload}
40 }40 }
41 41
42 func TestVisualProbePersistsFullAndIncrementalOutput(t *testing.T) {42 func TestVisualProbePersistsFullAndIncrementalOutput(t *testing.T) {
@@ -240,9 +240,9 @@ func TestProcessOutputFrameACKsOnlyAcceptedStateBearingFrames(t *testing.T) {
240 var ackCount, resetCount int240 var ackCount, resetCount int
241 for _, frame := range transport.sent {241 for _, frame := range transport.sent {
242 switch frame.Type {242 switch frame.Type {
243- case ports.MsgAck:243+ case wire.MsgAck:
244 ackCount++244 ackCount++
245- case ports.MsgOutputResetRequest:245+ case wire.MsgOutputResetRequest:
246 resetCount++246 resetCount++
247 }247 }
248 }248 }
@@ -268,7 +268,7 @@ func TestCaptureUnifiedPickerRowsRequiresLocalAndRemoteEntries(t *testing.T) {
268 268
269 func TestVisualProbeRecordsUnexpectedAttachTarget(t *testing.T) {269 func TestVisualProbeRecordsUnexpectedAttachTarget(t *testing.T) {
270 probe := newVisualProbe(domain.Size{Cols: probeTestCols, Rows: probeTestRows})270 probe := newVisualProbe(domain.Size{Cols: probeTestCols, Rows: probeTestRows})
271- probe.recordIncoming(ports.Frame{Type: ports.MsgAttachTarget})271+ probe.recordIncoming(wire.Frame{Type: wire.MsgAttachTarget})
272 272
273 if probe.unexpectedHandoffs != 1 {273 if probe.unexpectedHandoffs != 1 {
274 t.Fatalf("unexpected handoffs = %d, want 1", probe.unexpectedHandoffs)274 t.Fatalf("unexpected handoffs = %d, want 1", probe.unexpectedHandoffs)
M transport_replay_integration_test.go +6 -5
@@ -12,6 +12,7 @@ import (
12 "github.com/bnema/vev/internal/adapters/sshstdio"12 "github.com/bnema/vev/internal/adapters/sshstdio"
13 "github.com/bnema/vev/internal/ports"13 "github.com/bnema/vev/internal/ports"
14 "github.com/bnema/vev/internal/protocol"14 "github.com/bnema/vev/internal/protocol"
15+ "github.com/bnema/vev/internal/protocol/wire"
15 "github.com/bnema/vev/internal/testutil/replaytest"16 "github.com/bnema/vev/internal/testutil/replaytest"
16 "github.com/stretchr/testify/require"17 "github.com/stretchr/testify/require"
17 )18 )
@@ -39,7 +40,7 @@ func TestTransportReplayIntegration(t *testing.T) {
39 got, err := receiver.Recv()40 got, err := receiver.Recv()
40 require.NoError(t, err)41 require.NoError(t, err)
41 require.Equal(t, want, got)42 require.Equal(t, want, got)
42- output, err := ports.UnmarshalOutput(got.Payload)43+ output, err := wire.UnmarshalOutput(got.Payload)
43 require.NoError(t, err)44 require.NoError(t, err)
44 bytes = append(bytes, output.Data...)45 bytes = append(bytes, output.Data...)
45 terminal.Write(output.Data)46 terminal.Write(output.Data)
@@ -102,7 +103,7 @@ func TestThemeGenerationTransportSequences(t *testing.T) {
102 done := make(chan error, 1)103 done := make(chan error, 1)
103 go func() {104 go func() {
104 for _, snapshot := range sequence {105 for _, snapshot := range sequence {
105- if err := sender.Send(ports.Frame{Type: ports.MsgTheme, Payload: ports.MarshalTheme(snapshot)}); err != nil {106+ if err := sender.Send(wire.Frame{Type: wire.MsgTheme, Payload: wire.MarshalTheme(snapshot)}); err != nil {
106 done <- err107 done <- err
107 return108 return
108 }109 }
@@ -113,9 +114,9 @@ func TestThemeGenerationTransportSequences(t *testing.T) {
113 for _, want := range sequence {114 for _, want := range sequence {
114 frame, err := receiver.Recv()115 frame, err := receiver.Recv()
115 require.NoError(t, err)116 require.NoError(t, err)
116- require.Equal(t, ports.MsgTheme, frame.Type)117+ require.Equal(t, wire.MsgTheme, frame.Type)
117- require.Equal(t, ports.MarshalTheme(want), frame.Payload, "theme payload must be preserved byte-for-byte before decode")118+ require.Equal(t, wire.MarshalTheme(want), frame.Payload, "theme payload must be preserved byte-for-byte before decode")
118- got, err := ports.UnmarshalTheme(frame.Payload)119+ got, err := wire.UnmarshalTheme(frame.Payload)
119 require.NoError(t, err)120 require.NoError(t, err)
120 require.Equal(t, want, got)121 require.Equal(t, want, got)
121 }122 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close