feat(gateway): repair native Apollo provider path

This commit is contained in:
sechmachine
2026-07-29 17:24:34 +07:00
parent 67eeb9e898
commit 236c7c2973
23 changed files with 5088 additions and 345 deletions
+89 -54
View File
@@ -2,7 +2,6 @@ package gateway
import (
"context"
"encoding/binary"
"encoding/xml"
"errors"
"fmt"
@@ -159,36 +158,6 @@ func ParseRTSPResponse(data []byte) (RTSPResponse, error) {
return response, nil
}
type ControlPacket struct {
Kind byte
Sequence uint32
Payload []byte
}
func EncodeControlPacket(packet ControlPacket) ([]byte, error) {
if len(packet.Payload) > 4096 {
return nil, ErrProviderMalformed
}
encoded := make([]byte, 11+len(packet.Payload))
copy(encoded[:4], "APC1")
encoded[4] = packet.Kind
binary.BigEndian.PutUint32(encoded[5:9], packet.Sequence)
binary.BigEndian.PutUint16(encoded[9:11], uint16(len(packet.Payload)))
copy(encoded[11:], packet.Payload)
return encoded, nil
}
func DecodeControlPacket(data []byte) (ControlPacket, error) {
if len(data) < 11 || len(data) > 4107 || string(data[:4]) != "APC1" {
return ControlPacket{}, ErrProviderMalformed
}
length := int(binary.BigEndian.Uint16(data[9:11]))
if length > 4096 || len(data) != 11+length {
return ControlPacket{}, ErrProviderMalformed
}
return ControlPacket{Kind: data[4], Sequence: binary.BigEndian.Uint32(data[5:9]), Payload: append([]byte(nil), data[11:]...)}, nil
}
type LaunchRequest struct {
SessionID string
Capabilities protocol.CapabilityProfile
@@ -207,9 +176,35 @@ type InputEvent struct {
type Feedback struct {
Sequence uint32
Kind FeedbackKind
Payload []byte
}
type ProviderEventKind uint8
const (
ProviderEventTerminated ProviderEventKind = iota + 1
ProviderEventRumble
ProviderEventHDR
)
type ProviderEvent struct {
Kind ProviderEventKind
Payload []byte
}
// ProviderTelemetry holds measured provider-channel state only; it never
// contains provider routes, credentials, or payload bytes.
type ProviderTelemetry struct {
State string
ControlRTT time.Duration
ControlJitter time.Duration
ReliableSent uint64
ReliableRetransmits uint64
PendingReliable uint64
MediaDrops uint64
}
type Provider interface {
Start(context.Context, LaunchRequest) (ProviderSession, error)
}
@@ -218,9 +213,12 @@ type ProviderSession interface {
Ready(context.Context) error
Video() <-chan []byte
Audio() <-chan []byte
Events() <-chan ProviderEvent
Input(context.Context, InputEvent) error
Feedback(context.Context, Feedback) error
Reconnect(context.Context) error
ReadClipboard(context.Context) (string, error)
WriteClipboard(context.Context, string) error
Telemetry() ProviderTelemetry
ReleaseAll(context.Context) error
Terminate(context.Context) error
State() protocol.ProviderState
@@ -357,16 +355,18 @@ func (f *FakeApollo) Setup(context.Context, LaunchRequest) ([]byte, error) {
if f.config.Failure == FakeFailureMalformed {
return []byte("RTSP/1.0 200 OK\r\n\r\n"), nil
}
return []byte("RTSP/1.0 200 OK\r\nSession: fixture-session\r\nTransport: RTP/AVP/TCP;interleaved=0-1\r\n\r\n"), nil
return []byte("RTSP/1.0 200 OK\r\nSession: fixture-session\r\nTransport: unicast;server_port=43000\r\n\r\n"), nil
}
func (f *FakeApollo) Open(_ context.Context, request LaunchRequest, _ RTSPResponse) (ProviderSession, error) {
session := &fakeSession{
failure: f.config.Failure,
video: make(chan []byte, 16),
audio: make(chan []byte, 16),
state: protocol.ProviderState{Version: "1", SessionID: request.SessionID, State: ProviderStateStarting, Channels: []string{"video", "audio", "input", "feedback"}},
pressed: make(map[string]struct{}),
failure: f.config.Failure,
video: make(chan []byte, 16),
audio: make(chan []byte, 16),
events: make(chan ProviderEvent, 16),
clipboardWrites: make(chan string, 1),
state: protocol.ProviderState{Version: "1", SessionID: request.SessionID, State: ProviderStateStarting, Channels: []string{"video", "audio", "input", "feedback"}},
pressed: make(map[string]struct{}),
}
for _, payload := range f.config.Video {
session.EmitVideo(payload)
@@ -402,16 +402,19 @@ func (f *FakeApollo) DisconnectProvider() {
}
type fakeSession struct {
mu sync.Mutex
failure FakeFailure
video chan []byte
audio chan []byte
state protocol.ProviderState
pressed map[string]struct{}
inputs []InputEvent
feedback []Feedback
releaseAll int
closeOnce sync.Once
mu sync.Mutex
failure FakeFailure
video chan []byte
audio chan []byte
events chan ProviderEvent
state protocol.ProviderState
pressed map[string]struct{}
inputs []InputEvent
feedback []Feedback
clipboard string
clipboardWrites chan string
releaseAll int
closeOnce sync.Once
}
func (s *fakeSession) Ready(ctx context.Context) error {
@@ -428,8 +431,16 @@ func (s *fakeSession) Ready(ctx context.Context) error {
return nil
}
func (s *fakeSession) Video() <-chan []byte { return s.video }
func (s *fakeSession) Audio() <-chan []byte { return s.audio }
func (s *fakeSession) Video() <-chan []byte { return s.video }
func (s *fakeSession) Audio() <-chan []byte { return s.audio }
func (s *fakeSession) Events() <-chan ProviderEvent { return s.events }
func (s *fakeSession) EmitEvent(event ProviderEvent) {
select {
case s.events <- ProviderEvent{Kind: event.Kind, Payload: append([]byte(nil), event.Payload...)}:
default:
}
}
func (s *fakeSession) EmitVideo(payload []byte) {
select {
@@ -475,13 +486,33 @@ func (s *fakeSession) Feedback(_ context.Context, feedback Feedback) error {
return nil
}
func (s *fakeSession) Reconnect(_ context.Context) error {
func (s *fakeSession) ReadClipboard(ctx context.Context) (string, error) {
if err := ctx.Err(); err != nil {
return "", err
}
s.mu.Lock()
defer s.mu.Unlock()
if s.state.State == ProviderStateTerminated {
return ErrProviderTerminated
if s.state.State != ProviderStateReady {
return "", ErrProviderDisconnected
}
return s.clipboard, nil
}
func (s *fakeSession) WriteClipboard(ctx context.Context, value string) error {
if err := ctx.Err(); err != nil {
return err
}
s.mu.Lock()
if s.state.State != ProviderStateReady {
s.mu.Unlock()
return ErrProviderDisconnected
}
s.clipboard = value
s.mu.Unlock()
select {
case s.clipboardWrites <- value:
default:
}
s.state.State = ProviderStateReady
return nil
}
@@ -528,6 +559,10 @@ func (s *fakeSession) State() protocol.ProviderState {
return s.state
}
func (s *fakeSession) Telemetry() ProviderTelemetry {
return ProviderTelemetry{State: s.State().State}
}
func (s *fakeSession) Disconnect() {
s.mu.Lock()
s.state.State = ProviderStateDisconnected