fix(gateway): enforce audited production traversal
This commit is contained in:
+52
-18
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -66,8 +67,12 @@ func (i ProviderIdentity) Validate(now time.Time, expected ProviderIdentity) err
|
||||
}
|
||||
|
||||
type ManagementInfo struct {
|
||||
Identity ProviderIdentity
|
||||
Name string
|
||||
Identity ProviderIdentity
|
||||
Name string
|
||||
ServerCodecModeSupport uint32
|
||||
MaxLumaPixelsHEVC uint64
|
||||
HasServerCodecModeSupport bool
|
||||
HasMaxLumaPixelsHEVC bool
|
||||
}
|
||||
|
||||
func ParseManagementXML(data []byte) (ManagementInfo, error) {
|
||||
@@ -82,6 +87,8 @@ func ParseManagementXML(data []byte) (ManagementInfo, error) {
|
||||
NotBefore string `xml:"not_before"`
|
||||
NotAfter string `xml:"not_after"`
|
||||
Name string `xml:"name"`
|
||||
CodecModes string `xml:"ServerCodecModeSupport"`
|
||||
MaxHEVCLuma string `xml:"MaxLumaPixelsHEVC"`
|
||||
}
|
||||
decoder := xml.NewDecoder(strings.NewReader(string(data)))
|
||||
decoder.Strict = true
|
||||
@@ -108,7 +115,24 @@ func ParseManagementXML(data []byte) (ManagementInfo, error) {
|
||||
if identity.UniqueID == "" || len(identity.UniqueID) > 128 || len(identity.Fingerprint) > 256 {
|
||||
return ManagementInfo{}, ErrProviderMalformed
|
||||
}
|
||||
return ManagementInfo{Identity: identity, Name: document.Name}, nil
|
||||
info := ManagementInfo{Identity: identity, Name: document.Name}
|
||||
if document.CodecModes != "" {
|
||||
value, parseErr := strconv.ParseUint(document.CodecModes, 10, 32)
|
||||
if parseErr != nil {
|
||||
return ManagementInfo{}, ErrProviderMalformed
|
||||
}
|
||||
info.ServerCodecModeSupport = uint32(value)
|
||||
info.HasServerCodecModeSupport = true
|
||||
}
|
||||
if document.MaxHEVCLuma != "" {
|
||||
value, parseErr := strconv.ParseUint(document.MaxHEVCLuma, 10, 64)
|
||||
if parseErr != nil {
|
||||
return ManagementInfo{}, ErrProviderMalformed
|
||||
}
|
||||
info.MaxLumaPixelsHEVC = value
|
||||
info.HasMaxLumaPixelsHEVC = true
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
type RTSPResponse struct {
|
||||
@@ -206,14 +230,20 @@ type ProviderTelemetry struct {
|
||||
MediaDrops uint64
|
||||
}
|
||||
|
||||
type ProviderMedia struct {
|
||||
Payload []byte
|
||||
ReceivedAt time.Time
|
||||
EnqueuedAt time.Time
|
||||
}
|
||||
|
||||
type Provider interface {
|
||||
Start(context.Context, LaunchRequest) (ProviderSession, error)
|
||||
}
|
||||
|
||||
type ProviderSession interface {
|
||||
Ready(context.Context) error
|
||||
Video() <-chan []byte
|
||||
Audio() <-chan []byte
|
||||
Video() <-chan ProviderMedia
|
||||
Audio() <-chan ProviderMedia
|
||||
Events() <-chan ProviderEvent
|
||||
Input(context.Context, InputEvent) error
|
||||
Feedback(context.Context, Feedback) error
|
||||
@@ -227,7 +257,7 @@ type ProviderSession interface {
|
||||
|
||||
type ApolloBackend interface {
|
||||
Management(context.Context, LaunchRequest) ([]byte, error)
|
||||
Setup(context.Context, LaunchRequest) ([]byte, error)
|
||||
Setup(context.Context, LaunchRequest, []byte) ([]byte, error)
|
||||
Open(context.Context, LaunchRequest, RTSPResponse) (ProviderSession, error)
|
||||
}
|
||||
|
||||
@@ -268,7 +298,7 @@ func (a *ApolloAdapter) Start(ctx context.Context, request LaunchRequest) (Provi
|
||||
if request.ProviderIdentity != "" && info.Identity.UniqueID != expected.UniqueID {
|
||||
return nil, ErrProviderIdentity
|
||||
}
|
||||
rawRTSP, err := a.backend.Setup(ctx, request)
|
||||
rawRTSP, err := a.backend.Setup(ctx, request, management)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -352,7 +382,7 @@ func (f *FakeApollo) Management(context.Context, LaunchRequest) ([]byte, error)
|
||||
return []byte(fmt.Sprintf("<root><unique_id>%s</unique_id><fingerprint>%s</fingerprint><not_before>%s</not_before><not_after>%s</not_after><name>fixture-apollo</name></root>", identity.UniqueID, identity.Fingerprint, f.config.Now.Add(-time.Hour).Format(time.RFC3339), f.config.Now.Add(time.Hour).Format(time.RFC3339))), nil
|
||||
}
|
||||
|
||||
func (f *FakeApollo) Setup(context.Context, LaunchRequest) ([]byte, error) {
|
||||
func (f *FakeApollo) Setup(context.Context, LaunchRequest, []byte) ([]byte, error) {
|
||||
if f.config.Failure == FakeFailureMalformed {
|
||||
return []byte("RTSP/1.0 200 OK\r\n\r\n"), nil
|
||||
}
|
||||
@@ -362,8 +392,8 @@ func (f *FakeApollo) Setup(context.Context, LaunchRequest) ([]byte, error) {
|
||||
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),
|
||||
video: make(chan ProviderMedia, 16),
|
||||
audio: make(chan ProviderMedia, 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"}},
|
||||
@@ -405,8 +435,8 @@ func (f *FakeApollo) DisconnectProvider() {
|
||||
type fakeSession struct {
|
||||
mu sync.Mutex
|
||||
failure FakeFailure
|
||||
video chan []byte
|
||||
audio chan []byte
|
||||
video chan ProviderMedia
|
||||
audio chan ProviderMedia
|
||||
events chan ProviderEvent
|
||||
state protocol.ProviderState
|
||||
pressed map[string]struct{}
|
||||
@@ -432,8 +462,8 @@ 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 ProviderMedia { return s.video }
|
||||
func (s *fakeSession) Audio() <-chan ProviderMedia { return s.audio }
|
||||
func (s *fakeSession) Events() <-chan ProviderEvent { return s.events }
|
||||
|
||||
func (s *fakeSession) EmitEvent(event ProviderEvent) {
|
||||
@@ -449,15 +479,17 @@ func (s *fakeSession) EmitVideo(payload []byte) {
|
||||
if s.state.State == ProviderStateTerminating || s.state.State == ProviderStateTerminated || s.state.State == ProviderStateDisconnected {
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
media := ProviderMedia{Payload: append([]byte(nil), payload...), ReceivedAt: now, EnqueuedAt: now}
|
||||
select {
|
||||
case s.video <- append([]byte(nil), payload...):
|
||||
case s.video <- media:
|
||||
default:
|
||||
select {
|
||||
case <-s.video:
|
||||
default:
|
||||
}
|
||||
select {
|
||||
case s.video <- append([]byte(nil), payload...):
|
||||
case s.video <- media:
|
||||
default:
|
||||
}
|
||||
}
|
||||
@@ -469,15 +501,17 @@ func (s *fakeSession) EmitAudio(payload []byte) {
|
||||
if s.state.State == ProviderStateTerminating || s.state.State == ProviderStateTerminated || s.state.State == ProviderStateDisconnected {
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
media := ProviderMedia{Payload: append([]byte(nil), payload...), ReceivedAt: now, EnqueuedAt: now}
|
||||
select {
|
||||
case s.audio <- append([]byte(nil), payload...):
|
||||
case s.audio <- media:
|
||||
default:
|
||||
select {
|
||||
case <-s.audio:
|
||||
default:
|
||||
}
|
||||
select {
|
||||
case s.audio <- append([]byte(nil), payload...):
|
||||
case s.audio <- media:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user