140 lines
4.2 KiB
Go
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
|
|
}
|