1// Wire codecs for vev's IPC message payloads. They live in ports (alongside
2// Frame and the MsgType constants) because wire encoding is protocol
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
7package ports
8
9import (
10 "encoding/binary"
11 "errors"
12
13 "github.com/bnema/vev/internal/domain"
14 "github.com/bnema/vev/pkg/renderer"
15)
16
17// errShortPayload is returned when a payload ends before a required field
18// has been fully read.
19var errShortPayload = errors.New("ports: payload too short")
20
21// errTrailingBytes is returned when a fixed-shape payload has bytes left
22// over after every field has been consumed. Protocol strictness here
23// catches version drift early instead of silently ignoring extra data.
24var errTrailingBytes = errors.New("ports: unexpected trailing bytes in payload")
25
26// Intent values carried by Hello, describing what the client wants to do.
27const (
28 IntentEphemeral uint8 = 0
29 IntentNew uint8 = 1
30 IntentAttach uint8 = 2
31 IntentResume uint8 = 3
32)
33
34// Capability bits advertised in Welcome.
35const (
36 CapabilityResume uint32 = 1 << 0
37 CapabilityUDP uint32 = 1 << 1
38 CapabilityPredict uint32 = 1 << 2
39)
40
41// ErrorMsg codes.
42const (
43 ErrVersionMismatch uint16 = 1
44 ErrNoSuchSession uint16 = 2
45 ErrNameTaken uint16 = 3
46 ErrServerShutdown uint16 = 4
47 ErrInvalidSessionName uint16 = 5
48 ErrUnknownCommand uint16 = 6
49 ErrNotScriptable uint16 = 7
50 ErrInvalidCommandArgs uint16 = 8
51 ErrNoSuchTarget uint16 = 9
52 ErrAmbiguousTarget uint16 = 10
53 ErrInternal uint16 = 255
54)
55
56// Detached reasons.
57const (
58 ReasonDetach uint8 = 0
59 ReasonSessionKilled uint8 = 1
60 ReasonServerShutdown uint8 = 2
61 ReasonReplaced uint8 = 3
62)
63
64// Hello is sent by the client immediately after connecting.
65type Hello struct {
66 Version uint16
67 Intent uint8
68 ClientID [16]byte
69 ResumeToken uint64
70 Name string
71 Size domain.Size
72 TermEnv string
73 Cwd string
74 TrueColor bool
75 // MaxOutputInFlight is the requested maximum number of unacknowledged
76 // state-bearing output frames.
77 MaxOutputInFlight uint8
78 // Env is the complete exported client environment for future PTY children.
79 // Entries are opaque strings so their ordering and contents are preserved.
80 Env []string
81}
82
83// Input carries raw bytes typed/pasted by the client, destined for the PTY.
84type Input struct {
85 InputSeq uint64
86 Data []byte
87}
88
89// Resize notifies the daemon of a client-side terminal size change.
90type Resize struct {
91 Size domain.Size
92}
93
94// Theme reports the client's terminal foreground/background colors, ANSI
95// palette entries, and whether the client terminal supports truecolor.
96type Theme struct {
97 HasForeground bool
98 Foreground renderer.RGB
99 HasBackground bool
100 Background renderer.RGB
101 TrueColor bool
102 SchemeKnown bool
103 Light bool
104 PaletteKnown uint16
105 Palette [16]renderer.RGB
106}
107
108// ImagePush carries a clipboard image from a remote client, to be written to
109// a temp file and injected into the focused pane's PTY as a path (see
110// docs/superpowers/specs/2026-07-04-clipboard-image-transfer-design.md).
111type ImagePush struct {
112 InputSeq uint64
113 Mime string
114 Data []byte
115}
116
117// ClientNotice reports a fixed client-side event for daemon-rendered user
118// feedback. Action values are deliberately closed so a client cannot inject
119// arbitrary display text into a shared session.
120type ClientNotice struct {
121 Action uint8
122}
123
124const (
125 ClientNoticeClipboardFallback uint8 = 1
126 ClientNoticeClipboardTooLarge uint8 = 2
127 ClientNoticeLinkDegraded uint8 = 3
128 ClientNoticeLinkConnected uint8 = 4
129)
130
131func validClientNoticeAction(action uint8) bool {
132 switch action {
133 case ClientNoticeClipboardFallback, ClientNoticeClipboardTooLarge, ClientNoticeLinkDegraded, ClientNoticeLinkConnected:
134 return true
135 default:
136 return false
137 }
138}
139
140// Detach asks the daemon to detach the current client without killing the
141// session.
142type Detach struct{}
143
144// Ping is a keepalive/liveness probe sent by either side.
145type Ping struct{}
146
147// Pong answers a Ping.
148type Pong struct{}
149
150// Ack acknowledges receipt/application of an output state number.
151type Ack struct {
152 AckedStateNum uint64
153}
154
155// Welcome is the daemon's reply to a successful Hello.
156type Welcome struct {
157 SessionID string
158 SessionName string
159 Ephemeral bool
160 ResumeToken uint64
161 Capabilities uint32
162}
163
164// ErrorMsg reports a protocol- or session-level failure to the client.
165type ErrorMsg struct {
166 Code uint16
167 Text string
168}
169
170// Output carries raw PTY output, destined for the client's terminal.
171type Output struct {
172 BaseStateNum uint64
173 NewStateNum uint64
174 EchoAck uint64
175 Data []byte
176}
177
178// Detached tells a client it has been disconnected from its session and why.
179type Detached struct {
180 Reason uint8
181}
182
183// List asks the daemon to enumerate its live sessions. It carries no
184// fields; the request is fully described by its message type.
185type List struct{}
186
187// Kill asks the daemon to terminate one named session or all sessions.
188type Kill struct {
189 Name string
190 All bool
191}
192
193// SessionState is the client-visible lifecycle state of a session.
194type SessionState uint8
195
196const (
197 SessionRunning SessionState = iota
198 SessionStopped
199 SessionBroken
200)
201
202// SessionInfo describes one session in a Sessions listing.
203type SessionInfo struct {
204 SessionID string
205 Name string
206 State SessionState
207 Ephemeral bool
208 Tabs uint16
209 Attached bool
210}
211
212// Sessions is the daemon's reply to a List, enumerating live sessions.
213type Sessions struct {
214 Sessions []SessionInfo
215}
216
217// payloadWriter builds a message payload by appending fields in wire order.
218type payloadWriter struct {
219 b []byte
220}
221
222func (w *payloadWriter) putUint8(v uint8) {
223 w.b = append(w.b, v)
224}
225
226func (w *payloadWriter) putUint16(v uint16) {
227 var tmp [2]byte
228 binary.BigEndian.PutUint16(tmp[:], v)
229 w.b = append(w.b, tmp[:]...)
230}
231
232func (w *payloadWriter) putUint32(v uint32) {
233 var tmp [4]byte
234 binary.BigEndian.PutUint32(tmp[:], v)
235 w.b = append(w.b, tmp[:]...)
236}
237
238func (w *payloadWriter) putUint64(v uint64) {
239 var tmp [8]byte
240 binary.BigEndian.PutUint64(tmp[:], v)
241 w.b = append(w.b, tmp[:]...)
242}
243
244func (w *payloadWriter) putBytes(b []byte) {
245 w.b = append(w.b, b...)
246}
247
248func (w *payloadWriter) putString(s string) {
249 w.putUint16(uint16(len(s)))
250 w.b = append(w.b, s...)
251}
252
253func (w *payloadWriter) putLongString(s string) {
254 w.putUint32(uint32(len(s)))
255 w.b = append(w.b, s...)
256}
257
258// payloadReader consumes a message payload field by field in wire order,
259// erroring instead of panicking on any short read.
260type payloadReader struct {
261 b []byte
262}
263
264func (r *payloadReader) getUint8() (uint8, error) {
265 if len(r.b) < 1 {
266 return 0, errShortPayload
267 }
268 v := r.b[0]
269 r.b = r.b[1:]
270 return v, nil
271}
272
273func (r *payloadReader) getUint16() (uint16, error) {
274 if len(r.b) < 2 {
275 return 0, errShortPayload
276 }
277 v := binary.BigEndian.Uint16(r.b)
278 r.b = r.b[2:]
279 return v, nil
280}
281
282func (r *payloadReader) getUint32() (uint32, error) {
283 if len(r.b) < 4 {
284 return 0, errShortPayload
285 }
286 v := binary.BigEndian.Uint32(r.b)
287 r.b = r.b[4:]
288 return v, nil
289}
290
291func (r *payloadReader) getUint64() (uint64, error) {
292 if len(r.b) < 8 {
293 return 0, errShortPayload
294 }
295 v := binary.BigEndian.Uint64(r.b)
296 r.b = r.b[8:]
297 return v, nil
298}
299
300func (r *payloadReader) getBytes(n int) ([]byte, error) {
301 if len(r.b) < n {
302 return nil, errShortPayload
303 }
304 b := append([]byte(nil), r.b[:n]...)
305 r.b = r.b[n:]
306 return b, nil
307}
308
309func (r *payloadReader) getString() (string, error) {
310 n, err := r.getUint16()
311 if err != nil {
312 return "", err
313 }
314 if len(r.b) < int(n) {
315 return "", errShortPayload
316 }
317 s := string(r.b[:n])
318 r.b = r.b[n:]
319 return s, nil
320}
321
322func (r *payloadReader) getLongString() (string, error) {
323 n, err := r.getUint32()
324 if err != nil {
325 return "", err
326 }
327 if uint64(n) > uint64(len(r.b)) {
328 return "", errShortPayload
329 }
330 s := string(r.b[:int(n)])
331 r.b = r.b[int(n):]
332 return s, nil
333}
334
335// rest consumes and returns all remaining bytes, copied so the result is
336// independent of the reader's backing array.
337func (r *payloadReader) rest() []byte {
338 b := append([]byte(nil), r.b...)
339 r.b = nil
340 return b
341}
342
343// done reports an error if any bytes remain unconsumed.
344func (r *payloadReader) done() error {
345 if len(r.b) != 0 {
346 return errTrailingBytes
347 }
348 return nil
349}
350
351// PeekHelloVersion returns the leading protocol version from a Hello payload.
352func PeekHelloVersion(b []byte) (uint16, bool) {
353 if len(b) < 2 {
354 return 0, false
355 }
356 return binary.BigEndian.Uint16(b), true
357}
358
359// MarshalHello encodes h into a Hello message payload.
360func MarshalHello(h Hello) []byte {
361 w := payloadWriter{}
362 w.putUint16(h.Version)
363 w.putUint8(h.Intent)
364 w.putBytes(h.ClientID[:])
365 w.putUint64(h.ResumeToken)
366 w.putString(h.Name)
367 w.putUint16(uint16(h.Size.Cols))
368 w.putUint16(uint16(h.Size.Rows))
369 w.putString(h.TermEnv)
370 w.putString(h.Cwd)
371 if h.TrueColor {
372 w.putUint8(1)
373 } else {
374 w.putUint8(0)
375 }
376 w.putUint8(h.MaxOutputInFlight)
377 w.putUint32(uint32(len(h.Env)))
378 for _, entry := range h.Env {
379 w.putLongString(entry)
380 }
381 return w.b
382}
383
384// UnmarshalHello decodes a Hello message payload.
385func UnmarshalHello(b []byte) (Hello, error) {
386 r := payloadReader{b: b}
387 var h Hello
388 var err error
389
390 if h.Version, err = r.getUint16(); err != nil {
391 return Hello{}, err
392 }
393 if h.Intent, err = r.getUint8(); err != nil {
394 return Hello{}, err
395 }
396 clientID, err := r.getBytes(len(h.ClientID))
397 if err != nil {
398 return Hello{}, err
399 }
400 copy(h.ClientID[:], clientID)
401 if h.ResumeToken, err = r.getUint64(); err != nil {
402 return Hello{}, err
403 }
404 if h.Name, err = r.getString(); err != nil {
405 return Hello{}, err
406 }
407 cols, err := r.getUint16()
408 if err != nil {
409 return Hello{}, err
410 }
411 rows, err := r.getUint16()
412 if err != nil {
413 return Hello{}, err
414 }
415 h.Size = domain.Size{Cols: int(cols), Rows: int(rows)}
416 if h.TermEnv, err = r.getString(); err != nil {
417 return Hello{}, err
418 }
419 if h.Cwd, err = r.getString(); err != nil {
420 return Hello{}, err
421 }
422 trueColor, err := r.getUint8()
423 if err != nil {
424 return Hello{}, err
425 }
426 h.TrueColor = trueColor != 0
427 if h.MaxOutputInFlight, err = r.getUint8(); err != nil {
428 return Hello{}, err
429 }
430 envCount, err := r.getUint32()
431 if err != nil {
432 return Hello{}, err
433 }
434 // Each entry has at least its uint32 byte length. Check that before
435 // allocating so a malformed count cannot force an excessive allocation.
436 if uint64(envCount) > uint64(len(r.b)/4) {
437 return Hello{}, errShortPayload
438 }
439 if envCount != 0 {
440 h.Env = make([]string, 0, int(envCount))
441 for range int(envCount) {
442 entry, err := r.getLongString()
443 if err != nil {
444 return Hello{}, err
445 }
446 h.Env = append(h.Env, entry)
447 }
448 }
449 if err := r.done(); err != nil {
450 return Hello{}, err
451 }
452 return h, nil
453}
454
455// MarshalInput encodes m into an Input message payload.
456func MarshalInput(m Input) []byte {
457 w := payloadWriter{}
458 w.putUint64(m.InputSeq)
459 w.putBytes(m.Data)
460 return w.b
461}
462
463// UnmarshalInput decodes an Input message payload. After the fixed input
464// sequence, the rest of the payload is data; there is no length prefix.
465func UnmarshalInput(b []byte) (Input, error) {
466 r := payloadReader{b: b}
467 seq, err := r.getUint64()
468 if err != nil {
469 return Input{}, err
470 }
471 return Input{InputSeq: seq, Data: r.rest()}, nil
472}
473
474// MarshalImagePush encodes m into an ImagePush message payload.
475func MarshalImagePush(m ImagePush) []byte {
476 w := payloadWriter{}
477 w.putUint64(m.InputSeq)
478 w.putString(m.Mime)
479 w.putBytes(m.Data)
480 return w.b
481}
482
483// UnmarshalImagePush decodes an ImagePush message payload. After the fixed
484// input sequence and length-prefixed mime string, the rest of the payload is
485// data; there is no length prefix for it.
486func UnmarshalImagePush(b []byte) (ImagePush, error) {
487 r := payloadReader{b: b}
488 seq, err := r.getUint64()
489 if err != nil {
490 return ImagePush{}, err
491 }
492 mime, err := r.getString()
493 if err != nil {
494 return ImagePush{}, err
495 }
496 return ImagePush{InputSeq: seq, Mime: mime, Data: r.rest()}, nil
497}
498
499// MarshalClientNotice encodes a fixed one-byte client notice action.
500func MarshalClientNotice(m ClientNotice) []byte {
501 return []byte{m.Action}
502}
503
504// UnmarshalClientNotice decodes a fixed client notice action and rejects both
505// unknown values and any trailing bytes.
506func UnmarshalClientNotice(b []byte) (ClientNotice, error) {
507 r := payloadReader{b: b}
508 action, err := r.getUint8()
509 if err != nil {
510 return ClientNotice{}, err
511 }
512 if !validClientNoticeAction(action) {
513 return ClientNotice{}, errors.New("ports: unknown client notice action")
514 }
515 if err := r.done(); err != nil {
516 return ClientNotice{}, err
517 }
518 return ClientNotice{Action: action}, nil
519}
520
521// MarshalResize encodes m into a Resize message payload.
522func MarshalResize(m Resize) []byte {
523 w := payloadWriter{}
524 w.putUint16(uint16(m.Size.Cols))
525 w.putUint16(uint16(m.Size.Rows))
526 return w.b
527}
528
529// MarshalTheme encodes m into a 57-byte fixed-width Theme message payload.
530func MarshalTheme(m Theme) []byte {
531 var flags uint8
532 if m.HasForeground {
533 flags |= 0x01
534 }
535 if m.HasBackground {
536 flags |= 0x02
537 }
538 if m.TrueColor {
539 flags |= 0x04
540 }
541 if m.SchemeKnown {
542 flags |= 0x08
543 }
544 if m.Light {
545 flags |= 0x10
546 }
547
548 w := payloadWriter{b: make([]byte, 0, 57)}
549 w.putUint8(flags)
550 w.putUint8(m.Foreground.R)
551 w.putUint8(m.Foreground.G)
552 w.putUint8(m.Foreground.B)
553 w.putUint8(m.Background.R)
554 w.putUint8(m.Background.G)
555 w.putUint8(m.Background.B)
556 w.putUint16(m.PaletteKnown)
557 for _, color := range m.Palette {
558 w.putUint8(color.R)
559 w.putUint8(color.G)
560 w.putUint8(color.B)
561 }
562 return w.b
563}
564
565// UnmarshalTheme decodes a 57-byte fixed-width Theme message payload.
566func UnmarshalTheme(b []byte) (Theme, error) {
567 r := payloadReader{b: b}
568 flags, err := r.getUint8()
569 if err != nil {
570 return Theme{}, err
571 }
572 fgR, err := r.getUint8()
573 if err != nil {
574 return Theme{}, err
575 }
576 fgG, err := r.getUint8()
577 if err != nil {
578 return Theme{}, err
579 }
580 fgB, err := r.getUint8()
581 if err != nil {
582 return Theme{}, err
583 }
584 bgR, err := r.getUint8()
585 if err != nil {
586 return Theme{}, err
587 }
588 bgG, err := r.getUint8()
589 if err != nil {
590 return Theme{}, err
591 }
592 bgB, err := r.getUint8()
593 if err != nil {
594 return Theme{}, err
595 }
596 paletteKnown, err := r.getUint16()
597 if err != nil {
598 return Theme{}, err
599 }
600
601 m := Theme{
602 HasForeground: flags&0x01 != 0,
603 Foreground: renderer.RGB{R: fgR, G: fgG, B: fgB},
604 HasBackground: flags&0x02 != 0,
605 Background: renderer.RGB{R: bgR, G: bgG, B: bgB},
606 TrueColor: flags&0x04 != 0,
607 SchemeKnown: flags&0x08 != 0,
608 Light: flags&0x10 != 0,
609 PaletteKnown: paletteKnown,
610 }
611 for i := range m.Palette {
612 if m.Palette[i].R, err = r.getUint8(); err != nil {
613 return Theme{}, err
614 }
615 if m.Palette[i].G, err = r.getUint8(); err != nil {
616 return Theme{}, err
617 }
618 if m.Palette[i].B, err = r.getUint8(); err != nil {
619 return Theme{}, err
620 }
621 }
622 if err := r.done(); err != nil {
623 return Theme{}, err
624 }
625 return m, nil
626}
627
628// UnmarshalResize decodes a Resize message payload.
629func UnmarshalResize(b []byte) (Resize, error) {
630 r := payloadReader{b: b}
631 cols, err := r.getUint16()
632 if err != nil {
633 return Resize{}, err
634 }
635 rows, err := r.getUint16()
636 if err != nil {
637 return Resize{}, err
638 }
639 if err := r.done(); err != nil {
640 return Resize{}, err
641 }
642 return Resize{Size: domain.Size{Cols: int(cols), Rows: int(rows)}}, nil
643}
644
645// MarshalDetach encodes a Detach message payload (always empty).
646func MarshalDetach(Detach) []byte {
647 return nil
648}
649
650// UnmarshalDetach decodes a Detach message payload.
651func UnmarshalDetach(b []byte) (Detach, error) {
652 r := payloadReader{b: b}
653 if err := r.done(); err != nil {
654 return Detach{}, err
655 }
656 return Detach{}, nil
657}
658
659// MarshalPing encodes a Ping message payload (always empty).
660func MarshalPing(Ping) []byte {
661 return nil
662}
663
664// UnmarshalPing decodes a Ping message payload.
665func UnmarshalPing(b []byte) (Ping, error) {
666 r := payloadReader{b: b}
667 if err := r.done(); err != nil {
668 return Ping{}, err
669 }
670 return Ping{}, nil
671}
672
673// MarshalPong encodes a Pong message payload (always empty).
674func MarshalPong(Pong) []byte {
675 return nil
676}
677
678// UnmarshalPong decodes a Pong message payload.
679func UnmarshalPong(b []byte) (Pong, error) {
680 r := payloadReader{b: b}
681 if err := r.done(); err != nil {
682 return Pong{}, err
683 }
684 return Pong{}, nil
685}
686
687// MarshalAck encodes m into an Ack message payload.
688func MarshalAck(m Ack) []byte {
689 w := payloadWriter{}
690 w.putUint64(m.AckedStateNum)
691 return w.b
692}
693
694// UnmarshalAck decodes an Ack message payload.
695func UnmarshalAck(b []byte) (Ack, error) {
696 r := payloadReader{b: b}
697 acked, err := r.getUint64()
698 if err != nil {
699 return Ack{}, err
700 }
701 if err := r.done(); err != nil {
702 return Ack{}, err
703 }
704 return Ack{AckedStateNum: acked}, nil
705}
706
707// MarshalWelcome encodes m into a Welcome message payload.
708func MarshalWelcome(m Welcome) []byte {
709 w := payloadWriter{}
710 w.putString(m.SessionID)
711 w.putString(m.SessionName)
712 if m.Ephemeral {
713 w.putUint8(1)
714 } else {
715 w.putUint8(0)
716 }
717 w.putUint64(m.ResumeToken)
718 w.putUint32(m.Capabilities)
719 return w.b
720}
721
722// UnmarshalWelcome decodes a Welcome message payload.
723func UnmarshalWelcome(b []byte) (Welcome, error) {
724 r := payloadReader{b: b}
725 var m Welcome
726 var err error
727
728 if m.SessionID, err = r.getString(); err != nil {
729 return Welcome{}, err
730 }
731 if m.SessionName, err = r.getString(); err != nil {
732 return Welcome{}, err
733 }
734 eph, err := r.getUint8()
735 if err != nil {
736 return Welcome{}, err
737 }
738 m.Ephemeral = eph != 0
739 if m.ResumeToken, err = r.getUint64(); err != nil {
740 return Welcome{}, err
741 }
742 if m.Capabilities, err = r.getUint32(); err != nil {
743 return Welcome{}, err
744 }
745 if err := r.done(); err != nil {
746 return Welcome{}, err
747 }
748 return m, nil
749}
750
751// MarshalErrorMsg encodes m into an ErrorMsg message payload.
752func MarshalErrorMsg(m ErrorMsg) []byte {
753 w := payloadWriter{}
754 w.putUint16(m.Code)
755 w.putString(m.Text)
756 return w.b
757}
758
759// UnmarshalErrorMsg decodes an ErrorMsg message payload.
760func UnmarshalErrorMsg(b []byte) (ErrorMsg, error) {
761 r := payloadReader{b: b}
762 var m ErrorMsg
763 var err error
764
765 if m.Code, err = r.getUint16(); err != nil {
766 return ErrorMsg{}, err
767 }
768 if m.Text, err = r.getString(); err != nil {
769 return ErrorMsg{}, err
770 }
771 if err := r.done(); err != nil {
772 return ErrorMsg{}, err
773 }
774 return m, nil
775}
776
777// MarshalOutput encodes m into an Output message payload.
778func MarshalOutput(m Output) []byte {
779 w := payloadWriter{}
780 w.putUint64(m.BaseStateNum)
781 w.putUint64(m.NewStateNum)
782 w.putUint64(m.EchoAck)
783 w.putBytes(m.Data)
784 return w.b
785}
786
787// UnmarshalOutput decodes an Output message payload. After fixed state
788// numbers, the rest of the payload is data; there is no length prefix.
789func UnmarshalOutput(b []byte) (Output, error) {
790 r := payloadReader{b: b}
791 base, err := r.getUint64()
792 if err != nil {
793 return Output{}, err
794 }
795 newState, err := r.getUint64()
796 if err != nil {
797 return Output{}, err
798 }
799 echoAck, err := r.getUint64()
800 if err != nil {
801 return Output{}, err
802 }
803 return Output{BaseStateNum: base, NewStateNum: newState, EchoAck: echoAck, Data: r.rest()}, nil
804}
805
806// MarshalDetached encodes m into a Detached message payload.
807func MarshalDetached(m Detached) []byte {
808 return []byte{m.Reason}
809}
810
811// UnmarshalDetached decodes a Detached message payload.
812func UnmarshalDetached(b []byte) (Detached, error) {
813 r := payloadReader{b: b}
814 reason, err := r.getUint8()
815 if err != nil {
816 return Detached{}, err
817 }
818 if err := r.done(); err != nil {
819 return Detached{}, err
820 }
821 return Detached{Reason: reason}, nil
822}
823
824// MarshalList encodes a List message payload (always empty).
825func MarshalList(List) []byte {
826 return nil
827}
828
829// UnmarshalList decodes a List message payload.
830func UnmarshalList(b []byte) (List, error) {
831 r := payloadReader{b: b}
832 if err := r.done(); err != nil {
833 return List{}, err
834 }
835 return List{}, nil
836}
837
838// MarshalKill encodes m into a Kill message payload.
839func MarshalKill(m Kill) []byte {
840 w := payloadWriter{}
841 w.putString(m.Name)
842 if m.All {
843 w.putUint8(1)
844 }
845 return w.b
846}
847
848// UnmarshalKill decodes a Kill message payload.
849func UnmarshalKill(b []byte) (Kill, error) {
850 r := payloadReader{b: b}
851 name, err := r.getString()
852 if err != nil {
853 return Kill{}, err
854 }
855 var all uint8
856 if len(r.b) > 0 {
857 all, err = r.getUint8()
858 if err != nil {
859 return Kill{}, err
860 }
861 }
862 if err := r.done(); err != nil {
863 return Kill{}, err
864 }
865 return Kill{Name: name, All: all != 0}, nil
866}
867
868// MarshalSessions encodes m into a Sessions message payload: a uint16 count
869// followed by that many session records.
870func MarshalSessions(m Sessions) []byte {
871 w := payloadWriter{}
872 w.putUint16(uint16(len(m.Sessions)))
873 for _, s := range m.Sessions {
874 w.putString(s.SessionID)
875 w.putString(s.Name)
876 if s.Ephemeral {
877 w.putUint8(1)
878 } else {
879 w.putUint8(0)
880 }
881 w.putUint16(s.Tabs)
882 if s.Attached {
883 w.putUint8(1)
884 } else {
885 w.putUint8(0)
886 }
887 w.putUint8(uint8(s.State))
888 }
889 return w.b
890}
891
892// UnmarshalSessions decodes a Sessions message payload.
893func UnmarshalSessions(b []byte) (Sessions, error) {
894 r := payloadReader{b: b}
895 count, err := r.getUint16()
896 if err != nil {
897 return Sessions{}, err
898 }
899 sessions := make([]SessionInfo, 0, count)
900 for range int(count) {
901 var s SessionInfo
902 if s.SessionID, err = r.getString(); err != nil {
903 return Sessions{}, err
904 }
905 if s.Name, err = r.getString(); err != nil {
906 return Sessions{}, err
907 }
908 eph, err := r.getUint8()
909 if err != nil {
910 return Sessions{}, err
911 }
912 s.Ephemeral = eph != 0
913 if s.Tabs, err = r.getUint16(); err != nil {
914 return Sessions{}, err
915 }
916 att, err := r.getUint8()
917 if err != nil {
918 return Sessions{}, err
919 }
920 s.Attached = att != 0
921 state, err := r.getUint8()
922 if err != nil {
923 return Sessions{}, err
924 }
925 s.State = SessionState(state)
926 if s.State > SessionBroken {
927 return Sessions{}, errors.New("ports: invalid session state")
928 }
929 sessions = append(sessions, s)
930 }
931 if err := r.done(); err != nil {
932 return Sessions{}, err
933 }
934 return Sessions{Sessions: sessions}, nil
935}