- Refactor the Go proxy for dependency injection: every proxy server, the bootstrap, the signal handler, the load balancers, and AMF0 now accept functional-option seams (factories/closures) so tests can inject fakes without binding real sockets, talking to real Redis, or racing on package globals. - Drop the package-global `lb.SrsLoadBalancer`. The bootstrap creates the LB locally and threads it through every proxy server constructor. Two old global indirections in `internal/signal` and `internal/rtmp/amf0` are likewise replaced by per-instance fields. - Rename `internal/server` → `internal/proxy` and rename the `lb` public surface for clarity: `SRSLoadBalancer` is split into `OriginService` / `HLSService` / `RTCService` and recomposed as `OriginLoadBalancer`; `SRSServer` → `OriginServer`; all proxy server types gain a `Proxy` qualifier (e.g. `RTMPServer` → `RTMPProxyServer`). - Extract the Redis client behind a new `internal/redisclient` package with a minimal `RedisClient` interface and a counterfeiter fake. - Add counterfeiter fakes (`proxyfakes`, `lbfakes`, `redisclientfakes`) and ~7.5k lines of unit tests covering bootstrap, memory + Redis LBs, all five proxy servers, the signal handler, and AMF0. - Add two new E2E flows — `proxy-e2e-srt-test.sh` (SRT publish through proxy, verify SRT/RTMP/HTTP-FLV/HLS playback) and `proxy-e2e-whip-test.sh` (WHIP publish, verify RTMP/HTTP-FLV/HLS via origin `rtc_to_rtmp`) — plus `setup-ffmpeg-with-whip.sh`, a macOS builder for an ffmpeg with openssl-DTLS WHIP and SRT support that the two scripts auto-invoke when needed. - Workspace reorg: move `memory/` and `skills/` to the repo root so all agent tools (Claude / Codex / Kiro / OpenClaw) share one source of truth via symlinks. Sync `docs/proxy/proxy-load-balancer.md` and `memory/srs-codebase-map.md` with the new names. No protocol, log, HTTP API, or wire-format changes. Refactor only — all externally observable proxy behavior is unchanged. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
901 lines
20 KiB
Go
901 lines
20 KiB
Go
// Copyright (c) 2026 Winlin
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
package rtmp
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"math"
|
|
"sync"
|
|
|
|
"srsx/internal/errors"
|
|
)
|
|
|
|
// Please read @doc amf0_spec_121207.pdf, @page 4, @section 2.1 Types Overview
|
|
type amf0Marker uint8
|
|
|
|
const (
|
|
amf0MarkerNumber amf0Marker = iota // 0
|
|
amf0MarkerBoolean // 1
|
|
amf0MarkerString // 2
|
|
amf0MarkerObject // 3
|
|
amf0MarkerMovieClip // 4
|
|
amf0MarkerNull // 5
|
|
amf0MarkerUndefined // 6
|
|
amf0MarkerReference // 7
|
|
amf0MarkerEcmaArray // 8
|
|
amf0MarkerObjectEnd // 9
|
|
amf0MarkerStrictArray // 10
|
|
amf0MarkerDate // 11
|
|
amf0MarkerLongString // 12
|
|
amf0MarkerUnsupported // 13
|
|
amf0MarkerRecordSet // 14
|
|
amf0MarkerXmlDocument // 15
|
|
amf0MarkerTypedObject // 16
|
|
amf0MarkerAvmPlusObject // 17
|
|
|
|
amf0MarkerForbidden amf0Marker = 0xff
|
|
)
|
|
|
|
func (v amf0Marker) String() string {
|
|
switch v {
|
|
case amf0MarkerNumber:
|
|
return "Amf0Number"
|
|
case amf0MarkerBoolean:
|
|
return "amf0Boolean"
|
|
case amf0MarkerString:
|
|
return "Amf0String"
|
|
case amf0MarkerObject:
|
|
return "Amf0Object"
|
|
case amf0MarkerNull:
|
|
return "Null"
|
|
case amf0MarkerUndefined:
|
|
return "Undefined"
|
|
case amf0MarkerReference:
|
|
return "Reference"
|
|
case amf0MarkerEcmaArray:
|
|
return "EcmaArray"
|
|
case amf0MarkerObjectEnd:
|
|
return "ObjectEnd"
|
|
case amf0MarkerStrictArray:
|
|
return "StrictArray"
|
|
case amf0MarkerDate:
|
|
return "Date"
|
|
case amf0MarkerLongString:
|
|
return "LongString"
|
|
case amf0MarkerUnsupported:
|
|
return "Unsupported"
|
|
case amf0MarkerXmlDocument:
|
|
return "XmlDocument"
|
|
case amf0MarkerTypedObject:
|
|
return "TypedObject"
|
|
case amf0MarkerAvmPlusObject:
|
|
return "AvmPlusObject"
|
|
case amf0MarkerMovieClip:
|
|
return "MovieClip"
|
|
case amf0MarkerRecordSet:
|
|
return "RecordSet"
|
|
default:
|
|
return "Forbidden"
|
|
}
|
|
}
|
|
|
|
// For utest to mock it.
|
|
type amf0Buffer interface {
|
|
Bytes() []byte
|
|
WriteByte(c byte) error
|
|
Write(p []byte) (n int, err error)
|
|
}
|
|
|
|
// defaultBufFactory is the production amf0Buffer factory. Tests override the
|
|
// per-instance bufFactory field on amf0ObjectBase instead of swapping a global.
|
|
func defaultBufFactory() amf0Buffer {
|
|
return &bytes.Buffer{}
|
|
}
|
|
|
|
// All AMF0 things.
|
|
type Amf0Any interface {
|
|
// Binary marshaler and unmarshaler.
|
|
encoding.BinaryUnmarshaler
|
|
encoding.BinaryMarshaler
|
|
// Get the size of bytes to marshal this object.
|
|
Size() int
|
|
|
|
// Get the Marker of any AMF0 stuff.
|
|
amf0Marker() amf0Marker
|
|
}
|
|
|
|
type Amf0Converter interface {
|
|
ToNumber() Amf0Number
|
|
ToBoolean() Amf0Boolean
|
|
ToString() Amf0String
|
|
ToObject() Amf0Object
|
|
ToNull() Amf0Null
|
|
ToUndefined() Amf0Undefined
|
|
ToEcmaArray() Amf0EcmaArray
|
|
ToStrictArray() Amf0StrictArray
|
|
}
|
|
|
|
type amf0Converter struct {
|
|
from Amf0Any
|
|
}
|
|
|
|
func NewAmf0Converter(from Amf0Any) Amf0Converter {
|
|
return &amf0Converter{from: from}
|
|
}
|
|
|
|
func (v *amf0Converter) ToNumber() Amf0Number {
|
|
if r, ok := v.from.(Amf0Number); ok {
|
|
return r
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (v *amf0Converter) ToBoolean() Amf0Boolean {
|
|
if r, ok := v.from.(Amf0Boolean); ok {
|
|
return r
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (v *amf0Converter) ToString() Amf0String {
|
|
if r, ok := v.from.(Amf0String); ok {
|
|
return r
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (v *amf0Converter) ToObject() Amf0Object {
|
|
if r, ok := v.from.(Amf0Object); ok {
|
|
return r
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (v *amf0Converter) ToNull() Amf0Null {
|
|
if r, ok := v.from.(Amf0Null); ok {
|
|
return r
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (v *amf0Converter) ToUndefined() Amf0Undefined {
|
|
if r, ok := v.from.(Amf0Undefined); ok {
|
|
return r
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (v *amf0Converter) ToEcmaArray() Amf0EcmaArray {
|
|
if r, ok := v.from.(Amf0EcmaArray); ok {
|
|
return r
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (v *amf0Converter) ToStrictArray() Amf0StrictArray {
|
|
if r, ok := v.from.(Amf0StrictArray); ok {
|
|
return r
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Discovery the amf0 object from the bytes b.
|
|
func Amf0Discovery(p []byte) (a Amf0Any, err error) {
|
|
if len(p) < 1 {
|
|
return nil, errors.Errorf("require 1 bytes only %v", len(p))
|
|
}
|
|
m := amf0Marker(p[0])
|
|
|
|
switch m {
|
|
case amf0MarkerNumber:
|
|
return NewAmf0Number(0), nil
|
|
case amf0MarkerBoolean:
|
|
return NewAmf0Boolean(false), nil
|
|
case amf0MarkerString:
|
|
return NewAmf0String(""), nil
|
|
case amf0MarkerObject:
|
|
return NewAmf0Object(), nil
|
|
case amf0MarkerNull:
|
|
return NewAmf0Null(), nil
|
|
case amf0MarkerUndefined:
|
|
return NewAmf0Undefined(), nil
|
|
case amf0MarkerReference:
|
|
case amf0MarkerEcmaArray:
|
|
return NewAmf0EcmaArray(), nil
|
|
case amf0MarkerObjectEnd:
|
|
return &amf0ObjectEOF{}, nil
|
|
case amf0MarkerStrictArray:
|
|
return NewAmf0StrictArray(), nil
|
|
case amf0MarkerDate, amf0MarkerLongString, amf0MarkerUnsupported, amf0MarkerXmlDocument,
|
|
amf0MarkerTypedObject, amf0MarkerAvmPlusObject, amf0MarkerForbidden, amf0MarkerMovieClip,
|
|
amf0MarkerRecordSet:
|
|
return nil, errors.Errorf("Marker %v is not supported", m)
|
|
}
|
|
return nil, errors.Errorf("Marker %v is invalid", m)
|
|
}
|
|
|
|
// The UTF8 string, please read @doc amf0_spec_121207.pdf, @page 3, @section 1.3.1 Strings and UTF-8
|
|
type amf0UTF8 string
|
|
|
|
func (v *amf0UTF8) Size() int {
|
|
return 2 + len(string(*v))
|
|
}
|
|
|
|
func (v *amf0UTF8) UnmarshalBinary(data []byte) (err error) {
|
|
var p []byte
|
|
if p = data; len(p) < 2 {
|
|
return errors.Errorf("require 2 bytes only %v", len(p))
|
|
}
|
|
size := uint16(p[0])<<8 | uint16(p[1])
|
|
|
|
if p = data[2:]; len(p) < int(size) {
|
|
return errors.Errorf("require %v bytes only %v", int(size), len(p))
|
|
}
|
|
*v = amf0UTF8(string(p[:size]))
|
|
|
|
return
|
|
}
|
|
|
|
func (v *amf0UTF8) MarshalBinary() (data []byte, err error) {
|
|
data = make([]byte, v.Size())
|
|
|
|
size := uint16(len(string(*v)))
|
|
data[0] = byte(size >> 8)
|
|
data[1] = byte(size)
|
|
|
|
if size > 0 {
|
|
copy(data[2:], []byte(*v))
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Amf0Number is the AMF0 number type.
|
|
type Amf0Number interface {
|
|
Amf0Any
|
|
Float64() float64
|
|
}
|
|
|
|
// The number object, please read @doc amf0_spec_121207.pdf, @page 5, @section 2.2 Number Type
|
|
type amf0Number float64
|
|
|
|
func NewAmf0Number(f float64) Amf0Number {
|
|
v := amf0Number(f)
|
|
return &v
|
|
}
|
|
|
|
func (v *amf0Number) Float64() float64 {
|
|
return float64(*v)
|
|
}
|
|
|
|
func (v *amf0Number) amf0Marker() amf0Marker {
|
|
return amf0MarkerNumber
|
|
}
|
|
|
|
func (v *amf0Number) Size() int {
|
|
return 1 + 8
|
|
}
|
|
|
|
func (v *amf0Number) UnmarshalBinary(data []byte) (err error) {
|
|
var p []byte
|
|
if p = data; len(p) < 9 {
|
|
return errors.Errorf("require 9 bytes only %v", len(p))
|
|
}
|
|
if m := amf0Marker(p[0]); m != amf0MarkerNumber {
|
|
return errors.Errorf("Amf0Number amf0Marker %v is illegal", m)
|
|
}
|
|
|
|
f := binary.BigEndian.Uint64(p[1:])
|
|
*v = amf0Number(math.Float64frombits(f))
|
|
return
|
|
}
|
|
|
|
func (v *amf0Number) MarshalBinary() (data []byte, err error) {
|
|
data = make([]byte, 9)
|
|
data[0] = byte(amf0MarkerNumber)
|
|
f := math.Float64bits(float64(*v))
|
|
binary.BigEndian.PutUint64(data[1:], f)
|
|
return
|
|
}
|
|
|
|
// Amf0String is the AMF0 string type.
|
|
type Amf0String interface {
|
|
Amf0Any
|
|
String() string
|
|
}
|
|
|
|
// The string objet, please read @doc amf0_spec_121207.pdf, @page 5, @section 2.4 String Type
|
|
type amf0String string
|
|
|
|
func NewAmf0String(s string) Amf0String {
|
|
return newAmf0String(s)
|
|
}
|
|
|
|
func newAmf0String(s string) *amf0String {
|
|
v := amf0String(s)
|
|
return &v
|
|
}
|
|
|
|
func (v *amf0String) String() string {
|
|
return string(*v)
|
|
}
|
|
|
|
func (v *amf0String) amf0Marker() amf0Marker {
|
|
return amf0MarkerString
|
|
}
|
|
|
|
func (v *amf0String) Size() int {
|
|
u := amf0UTF8(*v)
|
|
return 1 + u.Size()
|
|
}
|
|
|
|
func (v *amf0String) UnmarshalBinary(data []byte) (err error) {
|
|
var p []byte
|
|
if p = data; len(p) < 1 {
|
|
return errors.Errorf("require 1 bytes only %v", len(p))
|
|
}
|
|
if m := amf0Marker(p[0]); m != amf0MarkerString {
|
|
return errors.Errorf("Amf0String amf0Marker %v is illegal", m)
|
|
}
|
|
|
|
var sv amf0UTF8
|
|
if err = sv.UnmarshalBinary(p[1:]); err != nil {
|
|
return errors.WithMessage(err, "utf8")
|
|
}
|
|
*v = amf0String(string(sv))
|
|
return
|
|
}
|
|
|
|
func (v *amf0String) MarshalBinary() (data []byte, err error) {
|
|
u := amf0UTF8(*v)
|
|
|
|
var pb []byte
|
|
if pb, err = u.MarshalBinary(); err != nil {
|
|
return nil, errors.WithMessage(err, "utf8")
|
|
}
|
|
|
|
data = append([]byte{byte(amf0MarkerString)}, pb...)
|
|
return
|
|
}
|
|
|
|
// The AMF0 object end type, please read @doc amf0_spec_121207.pdf, @page 5, @section 2.11 Object End Type
|
|
type amf0ObjectEOF struct {
|
|
}
|
|
|
|
func (v *amf0ObjectEOF) amf0Marker() amf0Marker {
|
|
return amf0MarkerObjectEnd
|
|
}
|
|
|
|
func (v *amf0ObjectEOF) Size() int {
|
|
return 3
|
|
}
|
|
|
|
func (v *amf0ObjectEOF) UnmarshalBinary(data []byte) (err error) {
|
|
p := data
|
|
|
|
if len(p) < 3 {
|
|
return errors.Errorf("require 3 bytes only %v", len(p))
|
|
}
|
|
|
|
if p[0] != 0 || p[1] != 0 || p[2] != 9 {
|
|
return errors.Errorf("EOF amf0Marker %v is illegal", p[0:3])
|
|
}
|
|
return
|
|
}
|
|
|
|
func (v *amf0ObjectEOF) MarshalBinary() (data []byte, err error) {
|
|
return []byte{0, 0, 9}, nil
|
|
}
|
|
|
|
// Use array for object and ecma array, to keep the original order.
|
|
type amf0Property struct {
|
|
key amf0UTF8
|
|
value Amf0Any
|
|
}
|
|
|
|
// The object-like AMF0 structure, like object and ecma array and strict array.
|
|
type amf0ObjectBase struct {
|
|
properties []*amf0Property
|
|
lock sync.Mutex
|
|
// bufFactory creates the amf0Buffer used by MarshalBinary. Held as a
|
|
// per-instance field (not a package global) so concurrent tests can each
|
|
// install their own buggy buffers without racing on shared state.
|
|
bufFactory func() amf0Buffer
|
|
}
|
|
|
|
func (v *amf0ObjectBase) Size() int {
|
|
v.lock.Lock()
|
|
defer v.lock.Unlock()
|
|
|
|
var size int
|
|
|
|
for _, p := range v.properties {
|
|
key, value := p.key, p.value
|
|
size += key.Size() + value.Size()
|
|
}
|
|
|
|
return size
|
|
}
|
|
|
|
func (v *amf0ObjectBase) Get(key string) Amf0Any {
|
|
v.lock.Lock()
|
|
defer v.lock.Unlock()
|
|
|
|
for _, p := range v.properties {
|
|
if string(p.key) == key {
|
|
return p.value
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (v *amf0ObjectBase) Set(key string, value Amf0Any) *amf0ObjectBase {
|
|
v.lock.Lock()
|
|
defer v.lock.Unlock()
|
|
|
|
prop := &amf0Property{key: amf0UTF8(key), value: value}
|
|
|
|
var ok bool
|
|
for i, p := range v.properties {
|
|
if string(p.key) == key {
|
|
v.properties[i] = prop
|
|
ok = true
|
|
}
|
|
}
|
|
|
|
if !ok {
|
|
v.properties = append(v.properties, prop)
|
|
}
|
|
|
|
return v
|
|
}
|
|
|
|
func (v *amf0ObjectBase) unmarshal(p []byte, eof bool, maxElems int) (err error) {
|
|
// if no eof, elems specified by maxElems.
|
|
if !eof && maxElems < 0 {
|
|
return errors.Errorf("maxElems=%v without eof", maxElems)
|
|
}
|
|
// if eof, maxElems must be -1.
|
|
if eof && maxElems != -1 {
|
|
return errors.Errorf("maxElems=%v with eof", maxElems)
|
|
}
|
|
|
|
readOne := func() (amf0UTF8, Amf0Any, error) {
|
|
var u amf0UTF8
|
|
if err = u.UnmarshalBinary(p); err != nil {
|
|
return "", nil, errors.WithMessage(err, "prop name")
|
|
}
|
|
|
|
p = p[u.Size():]
|
|
var a Amf0Any
|
|
if a, err = Amf0Discovery(p); err != nil {
|
|
return "", nil, errors.WithMessage(err, fmt.Sprintf("discover prop %v", string(u)))
|
|
}
|
|
return u, a, nil
|
|
}
|
|
|
|
pushOne := func(u amf0UTF8, a Amf0Any) error {
|
|
// For object property, consume the whole bytes.
|
|
if err = a.UnmarshalBinary(p); err != nil {
|
|
return errors.WithMessage(err, fmt.Sprintf("unmarshal prop %v", string(u)))
|
|
}
|
|
|
|
v.Set(string(u), a)
|
|
p = p[a.Size():]
|
|
return nil
|
|
}
|
|
|
|
for eof {
|
|
u, a, err := readOne()
|
|
if err != nil {
|
|
return errors.WithMessage(err, "read")
|
|
}
|
|
|
|
// For object EOF, we should only consume total 3bytes.
|
|
if u.Size() == 2 && a.amf0Marker() == amf0MarkerObjectEnd {
|
|
// 2 bytes is consumed by u(name), the a(eof) should only consume 1 byte.
|
|
p = p[1:]
|
|
return nil
|
|
}
|
|
|
|
if err := pushOne(u, a); err != nil {
|
|
return errors.WithMessage(err, "push")
|
|
}
|
|
}
|
|
|
|
for len(v.properties) < maxElems {
|
|
u, a, err := readOne()
|
|
if err != nil {
|
|
return errors.WithMessage(err, "read")
|
|
}
|
|
|
|
if err := pushOne(u, a); err != nil {
|
|
return errors.WithMessage(err, "push")
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (v *amf0ObjectBase) marshal(b amf0Buffer) (err error) {
|
|
v.lock.Lock()
|
|
defer v.lock.Unlock()
|
|
|
|
var pb []byte
|
|
for _, p := range v.properties {
|
|
key, value := p.key, p.value
|
|
|
|
if pb, err = key.MarshalBinary(); err != nil {
|
|
return errors.WithMessage(err, fmt.Sprintf("marshal %v", string(key)))
|
|
}
|
|
if _, err = b.Write(pb); err != nil {
|
|
return errors.Wrapf(err, "write %v", string(key))
|
|
}
|
|
|
|
if pb, err = value.MarshalBinary(); err != nil {
|
|
return errors.WithMessage(err, fmt.Sprintf("marshal value for %v", string(key)))
|
|
}
|
|
if _, err = b.Write(pb); err != nil {
|
|
return errors.Wrapf(err, "marshal value for %v", string(key))
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Amf0Object is the AMF0 object type.
|
|
type Amf0Object interface {
|
|
Amf0Any
|
|
Get(key string) Amf0Any
|
|
Set(key string, value Amf0Any) Amf0Object
|
|
}
|
|
|
|
// The AMF0 object, please read @doc amf0_spec_121207.pdf, @page 5, @section 2.5 Object Type
|
|
type amf0Object struct {
|
|
amf0ObjectBase
|
|
eof amf0ObjectEOF
|
|
}
|
|
|
|
func NewAmf0Object() Amf0Object {
|
|
return newAmf0Object()
|
|
}
|
|
|
|
func newAmf0Object() *amf0Object {
|
|
v := &amf0Object{}
|
|
v.properties = []*amf0Property{}
|
|
v.bufFactory = defaultBufFactory
|
|
return v
|
|
}
|
|
|
|
func (v *amf0Object) amf0Marker() amf0Marker {
|
|
return amf0MarkerObject
|
|
}
|
|
|
|
func (v *amf0Object) Get(key string) Amf0Any {
|
|
return v.amf0ObjectBase.Get(key)
|
|
}
|
|
|
|
func (v *amf0Object) Set(key string, value Amf0Any) Amf0Object {
|
|
v.amf0ObjectBase.Set(key, value)
|
|
return v
|
|
}
|
|
|
|
func (v *amf0Object) Size() int {
|
|
return int(1) + v.eof.Size() + v.amf0ObjectBase.Size()
|
|
}
|
|
|
|
func (v *amf0Object) UnmarshalBinary(data []byte) (err error) {
|
|
var p []byte
|
|
if p = data; len(p) < 1 {
|
|
return errors.Errorf("require 1 byte only %v", len(p))
|
|
}
|
|
if m := amf0Marker(p[0]); m != amf0MarkerObject {
|
|
return errors.Errorf("Amf0Object amf0Marker %v is illegal", m)
|
|
}
|
|
p = p[1:]
|
|
|
|
if err = v.unmarshal(p, true, -1); err != nil {
|
|
return errors.WithMessage(err, "unmarshal")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (v *amf0Object) MarshalBinary() (data []byte, err error) {
|
|
b := v.bufFactory()
|
|
|
|
if err = b.WriteByte(byte(amf0MarkerObject)); err != nil {
|
|
return nil, errors.Wrap(err, "marshal")
|
|
}
|
|
|
|
if err = v.marshal(b); err != nil {
|
|
return nil, errors.WithMessage(err, "marshal")
|
|
}
|
|
|
|
if pb, err := v.eof.MarshalBinary(); err != nil {
|
|
return nil, errors.WithMessage(err, "marshal")
|
|
} else if _, err = b.Write(pb); err != nil {
|
|
return nil, errors.Wrap(err, "marshal")
|
|
}
|
|
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
// Amf0EcmaArray is the AMF0 ECMA array type.
|
|
type Amf0EcmaArray interface {
|
|
Amf0Any
|
|
Get(key string) Amf0Any
|
|
Set(key string, value Amf0Any) Amf0EcmaArray
|
|
}
|
|
|
|
// The AMF0 ecma array, please read @doc amf0_spec_121207.pdf, @page 6, @section 2.10 ECMA Array Type
|
|
type amf0EcmaArray struct {
|
|
amf0ObjectBase
|
|
count uint32
|
|
eof amf0ObjectEOF
|
|
}
|
|
|
|
func NewAmf0EcmaArray() Amf0EcmaArray {
|
|
return newAmf0EcmaArray()
|
|
}
|
|
|
|
func newAmf0EcmaArray() *amf0EcmaArray {
|
|
v := &amf0EcmaArray{}
|
|
v.properties = []*amf0Property{}
|
|
v.bufFactory = defaultBufFactory
|
|
return v
|
|
}
|
|
|
|
func (v *amf0EcmaArray) amf0Marker() amf0Marker {
|
|
return amf0MarkerEcmaArray
|
|
}
|
|
|
|
func (v *amf0EcmaArray) Get(key string) Amf0Any {
|
|
return v.amf0ObjectBase.Get(key)
|
|
}
|
|
|
|
func (v *amf0EcmaArray) Set(key string, value Amf0Any) Amf0EcmaArray {
|
|
v.amf0ObjectBase.Set(key, value)
|
|
return v
|
|
}
|
|
|
|
func (v *amf0EcmaArray) Size() int {
|
|
return int(1) + 4 + v.eof.Size() + v.amf0ObjectBase.Size()
|
|
}
|
|
|
|
func (v *amf0EcmaArray) UnmarshalBinary(data []byte) (err error) {
|
|
var p []byte
|
|
if p = data; len(p) < 5 {
|
|
return errors.Errorf("require 5 bytes only %v", len(p))
|
|
}
|
|
if m := amf0Marker(p[0]); m != amf0MarkerEcmaArray {
|
|
return errors.Errorf("EcmaArray amf0Marker %v is illegal", m)
|
|
}
|
|
v.count = binary.BigEndian.Uint32(p[1:])
|
|
p = p[5:]
|
|
|
|
if err = v.unmarshal(p, true, -1); err != nil {
|
|
return errors.WithMessage(err, "unmarshal")
|
|
}
|
|
return
|
|
}
|
|
|
|
func (v *amf0EcmaArray) MarshalBinary() (data []byte, err error) {
|
|
b := v.bufFactory()
|
|
|
|
if err = b.WriteByte(byte(amf0MarkerEcmaArray)); err != nil {
|
|
return nil, errors.Wrap(err, "marshal")
|
|
}
|
|
|
|
if err = binary.Write(b, binary.BigEndian, v.count); err != nil {
|
|
return nil, errors.Wrap(err, "marshal")
|
|
}
|
|
|
|
if err = v.marshal(b); err != nil {
|
|
return nil, errors.WithMessage(err, "marshal")
|
|
}
|
|
|
|
if pb, err := v.eof.MarshalBinary(); err != nil {
|
|
return nil, errors.WithMessage(err, "marshal")
|
|
} else if _, err = b.Write(pb); err != nil {
|
|
return nil, errors.Wrap(err, "marshal")
|
|
}
|
|
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
// Amf0StrictArray is the AMF0 strict array type.
|
|
type Amf0StrictArray interface {
|
|
Amf0Any
|
|
Get(key string) Amf0Any
|
|
Set(key string, value Amf0Any) Amf0StrictArray
|
|
}
|
|
|
|
// The AMF0 strict array, please read @doc amf0_spec_121207.pdf, @page 7, @section 2.12 Strict Array Type
|
|
type amf0StrictArray struct {
|
|
amf0ObjectBase
|
|
count uint32
|
|
}
|
|
|
|
func NewAmf0StrictArray() Amf0StrictArray {
|
|
v := &amf0StrictArray{}
|
|
v.properties = []*amf0Property{}
|
|
v.bufFactory = defaultBufFactory
|
|
return v
|
|
}
|
|
|
|
func (v *amf0StrictArray) amf0Marker() amf0Marker {
|
|
return amf0MarkerStrictArray
|
|
}
|
|
|
|
func (v *amf0StrictArray) Get(key string) Amf0Any {
|
|
return v.amf0ObjectBase.Get(key)
|
|
}
|
|
|
|
func (v *amf0StrictArray) Set(key string, value Amf0Any) Amf0StrictArray {
|
|
v.amf0ObjectBase.Set(key, value)
|
|
return v
|
|
}
|
|
|
|
func (v *amf0StrictArray) Size() int {
|
|
return int(1) + 4 + v.amf0ObjectBase.Size()
|
|
}
|
|
|
|
func (v *amf0StrictArray) UnmarshalBinary(data []byte) (err error) {
|
|
var p []byte
|
|
if p = data; len(p) < 5 {
|
|
return errors.Errorf("require 5 bytes only %v", len(p))
|
|
}
|
|
if m := amf0Marker(p[0]); m != amf0MarkerStrictArray {
|
|
return errors.Errorf("StrictArray amf0Marker %v is illegal", m)
|
|
}
|
|
v.count = binary.BigEndian.Uint32(p[1:])
|
|
p = p[5:]
|
|
|
|
if int(v.count) <= 0 {
|
|
return
|
|
}
|
|
|
|
if err = v.unmarshal(p, false, int(v.count)); err != nil {
|
|
return errors.WithMessage(err, "unmarshal")
|
|
}
|
|
return
|
|
}
|
|
|
|
func (v *amf0StrictArray) MarshalBinary() (data []byte, err error) {
|
|
b := v.bufFactory()
|
|
|
|
if err = b.WriteByte(byte(amf0MarkerStrictArray)); err != nil {
|
|
return nil, errors.Wrap(err, "marshal")
|
|
}
|
|
|
|
if err = binary.Write(b, binary.BigEndian, v.count); err != nil {
|
|
return nil, errors.Wrap(err, "marshal")
|
|
}
|
|
|
|
if err = v.marshal(b); err != nil {
|
|
return nil, errors.WithMessage(err, "marshal")
|
|
}
|
|
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
// The single amf0Marker object, for all AMF0 which only has the amf0Marker, like null and undefined.
|
|
type amf0SingleMarkerObject struct {
|
|
target amf0Marker
|
|
}
|
|
|
|
func newAmf0SingleMarkerObject(m amf0Marker) amf0SingleMarkerObject {
|
|
return amf0SingleMarkerObject{target: m}
|
|
}
|
|
|
|
func (v *amf0SingleMarkerObject) amf0Marker() amf0Marker {
|
|
return v.target
|
|
}
|
|
|
|
func (v *amf0SingleMarkerObject) Size() int {
|
|
return int(1)
|
|
}
|
|
|
|
func (v *amf0SingleMarkerObject) UnmarshalBinary(data []byte) (err error) {
|
|
var p []byte
|
|
if p = data; len(p) < 1 {
|
|
return errors.Errorf("require 1 byte only %v", len(p))
|
|
}
|
|
if m := amf0Marker(p[0]); m != v.target {
|
|
return errors.Errorf("%v amf0Marker %v is illegal", v.target, m)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (v *amf0SingleMarkerObject) MarshalBinary() (data []byte, err error) {
|
|
return []byte{byte(v.target)}, nil
|
|
}
|
|
|
|
// Amf0Null is the AMF0 null type.
|
|
type Amf0Null interface {
|
|
Amf0Any
|
|
}
|
|
|
|
// The AMF0 null, please read @doc amf0_spec_121207.pdf, @page 6, @section 2.7 null Type
|
|
type amf0Null struct {
|
|
amf0SingleMarkerObject
|
|
}
|
|
|
|
func NewAmf0Null() Amf0Null {
|
|
v := amf0Null{}
|
|
v.amf0SingleMarkerObject = newAmf0SingleMarkerObject(amf0MarkerNull)
|
|
return &v
|
|
}
|
|
|
|
// Amf0Undefined is the AMF0 undefined type.
|
|
type Amf0Undefined interface {
|
|
Amf0Any
|
|
}
|
|
|
|
// The AMF0 undefined, please read @doc amf0_spec_121207.pdf, @page 6, @section 2.8 undefined Type
|
|
type amf0Undefined struct {
|
|
amf0SingleMarkerObject
|
|
}
|
|
|
|
func NewAmf0Undefined() Amf0Undefined {
|
|
v := amf0Undefined{}
|
|
v.amf0SingleMarkerObject = newAmf0SingleMarkerObject(amf0MarkerUndefined)
|
|
return &v
|
|
}
|
|
|
|
// Amf0Boolean is the public typed view of an AMF0 boolean.
|
|
type Amf0Boolean interface {
|
|
Amf0Any
|
|
Bool() bool
|
|
}
|
|
|
|
// The AMF0 boolean, please read @doc amf0_spec_121207.pdf, @page 5, @section 2.3 Boolean Type
|
|
type amf0Boolean bool
|
|
|
|
func NewAmf0Boolean(b bool) Amf0Boolean {
|
|
v := amf0Boolean(b)
|
|
return &v
|
|
}
|
|
|
|
func (v *amf0Boolean) Bool() bool {
|
|
return bool(*v)
|
|
}
|
|
|
|
func (v *amf0Boolean) amf0Marker() amf0Marker {
|
|
return amf0MarkerBoolean
|
|
}
|
|
|
|
func (v *amf0Boolean) Size() int {
|
|
return int(2)
|
|
}
|
|
|
|
func (v *amf0Boolean) UnmarshalBinary(data []byte) (err error) {
|
|
var p []byte
|
|
if p = data; len(p) < 2 {
|
|
return errors.Errorf("require 2 bytes only %v", len(p))
|
|
}
|
|
if m := amf0Marker(p[0]); m != amf0MarkerBoolean {
|
|
return errors.Errorf("BOOL amf0Marker %v is illegal", m)
|
|
}
|
|
if p[1] == 0 {
|
|
*v = false
|
|
} else {
|
|
*v = true
|
|
}
|
|
return
|
|
}
|
|
|
|
func (v *amf0Boolean) MarshalBinary() (data []byte, err error) {
|
|
var b byte
|
|
if *v {
|
|
b = 1
|
|
}
|
|
return []byte{byte(amf0MarkerBoolean), b}, nil
|
|
}
|