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 }