Files
rvbox/internal/client/supervisor/windows/launcher_protocol.go
T

140 lines
4.2 KiB
Go

package windows
// The launcher wire format is deliberately private to one service/launcher
// pair. It is not a second public protocol: the channel name is random, the
// generation is bound into every frame, and every frame is length- and
// checksum-validated before its payload is decoded.
import (
"context"
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"io"
)
const (
launcherMagic uint32 = 0x52564c31 // RVL1
launcherVersion uint16 = 1
launcherHeaderBytes = 4 + 2 + 2 + 16 + 4 + sha256.Size
launcherMaxFrameBytes = 4 << 20
)
type launcherFrameKind uint16
const (
launcherFrameHello launcherFrameKind = iota + 1
launcherFrameLaunch
launcherFrameShellPrepared
launcherFrameRelease
launcherFrameReleased
launcherFrameAbort
launcherFrameSignalRequest
launcherFrameSignalResult
)
type launcherFrame struct {
Kind launcherFrameKind
Generation [16]byte
Payload []byte
}
func marshalLauncherFrame(frame launcherFrame) ([]byte, error) {
if frame.Kind == 0 || frame.Kind > launcherFrameSignalResult {
return nil, errors.New("launcher frame kind is invalid")
}
if uint64(len(frame.Payload)) > launcherMaxFrameBytes {
return nil, fmt.Errorf("launcher frame payload exceeds %d bytes", launcherMaxFrameBytes)
}
encoded := make([]byte, launcherHeaderBytes+len(frame.Payload))
binary.LittleEndian.PutUint32(encoded[0:4], launcherMagic)
binary.LittleEndian.PutUint16(encoded[4:6], launcherVersion)
binary.LittleEndian.PutUint16(encoded[6:8], uint16(frame.Kind))
copy(encoded[8:24], frame.Generation[:])
binary.LittleEndian.PutUint32(encoded[24:28], uint32(len(frame.Payload)))
copy(encoded[launcherHeaderBytes:], frame.Payload)
check := make([]byte, 28+len(frame.Payload))
copy(check, encoded[:28])
copy(check[28:], frame.Payload)
digest := sha256.Sum256(check)
copy(encoded[28:launcherHeaderBytes], digest[:])
return encoded, nil
}
func readLauncherFrame(reader io.Reader, expected [16]byte) (launcherFrame, error) {
header := make([]byte, launcherHeaderBytes)
if _, err := io.ReadFull(reader, header); err != nil {
return launcherFrame{}, err
}
if binary.LittleEndian.Uint32(header[0:4]) != launcherMagic {
return launcherFrame{}, errors.New("launcher frame magic mismatch")
}
if binary.LittleEndian.Uint16(header[4:6]) != launcherVersion {
return launcherFrame{}, errors.New("launcher frame version mismatch")
}
kind := launcherFrameKind(binary.LittleEndian.Uint16(header[6:8]))
if kind == 0 || kind > launcherFrameSignalResult {
return launcherFrame{}, errors.New("launcher frame kind is invalid")
}
var generation [16]byte
copy(generation[:], header[8:24])
if generation != expected {
return launcherFrame{}, errors.New("launcher frame generation mismatch")
}
length := binary.LittleEndian.Uint32(header[24:28])
if length > launcherMaxFrameBytes {
return launcherFrame{}, fmt.Errorf("launcher frame payload exceeds %d bytes", launcherMaxFrameBytes)
}
payload := make([]byte, int(length))
if _, err := io.ReadFull(reader, payload); err != nil {
return launcherFrame{}, err
}
check := make([]byte, 28+len(payload))
copy(check, header[:28])
copy(check[28:], payload)
digest := sha256.Sum256(check)
if string(digest[:]) != string(header[28:launcherHeaderBytes]) {
return launcherFrame{}, errors.New("launcher frame checksum mismatch")
}
return launcherFrame{Kind: kind, Generation: generation, Payload: payload}, nil
}
func readLauncherFrameContext(ctx context.Context, reader io.Reader, expected [16]byte) (launcherFrame, error) {
result := make(chan struct {
frame launcherFrame
err error
}, 1)
go func() {
frame, err := readLauncherFrame(reader, expected)
result <- struct {
frame launcherFrame
err error
}{frame: frame, err: err}
}()
select {
case <-ctx.Done():
return launcherFrame{}, ctx.Err()
case value := <-result:
return value.frame, value.err
}
}
func writeLauncherFrame(writer io.Writer, frame launcherFrame) error {
encoded, err := marshalLauncherFrame(frame)
if err != nil {
return err
}
for len(encoded) > 0 {
written, err := writer.Write(encoded)
if err != nil {
return err
}
if written <= 0 || written > len(encoded) {
return errors.New("launcher frame writer made no progress")
}
encoded = encoded[written:]
}
return nil
}