feat(gateway): repair native Apollo provider path
This commit is contained in:
+89
-54
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user