|
|
|
package flvio
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"math"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"gitlab.com/ics_cinnamon/joy4/utils/bits/pio"
|
|
|
|
)
|
|
|
|
|
|
|
|
type AMF0ParseError struct {
|
|
|
|
Offset int
|
|
|
|
Message string
|
|
|
|
Next *AMF0ParseError
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *AMF0ParseError) Error() string {
|
|
|
|
s := []string{}
|
|
|
|
for p := self; p != nil; p = p.Next {
|
|
|
|
s = append(s, fmt.Sprintf("%s:%d", p.Message, p.Offset))
|
|
|
|
}
|
|
|
|
return "amf0 parse error: " + strings.Join(s, ",")
|
|
|
|
}
|
|
|
|
|
|
|
|
func amf0ParseErr(message string, offset int, err error) error {
|
|
|
|
next, _ := err.(*AMF0ParseError)
|
|
|
|
return &AMF0ParseError{
|
|
|
|
Offset: offset,
|
|
|
|
Message: message,
|
|
|
|
Next: next,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type AMFMap map[string]interface{}
|
|
|
|
type AMFArray []interface{}
|
|
|
|
type AMFECMAArray map[string]interface{}
|
|
|
|
|
|
|
|
func parseBEFloat64(b []byte) float64 {
|
|
|
|
return math.Float64frombits(pio.U64BE(b))
|
|
|
|
}
|
|
|
|
|
|
|
|
func fillBEFloat64(b []byte, f float64) int {
|
|
|
|
pio.PutU64BE(b, math.Float64bits(f))
|
|
|
|
return 8
|
|
|
|
}
|
|
|
|
|
|
|
|
const lenAMF0Number = 9
|
|
|
|
|
|
|
|
func fillAMF0Number(b []byte, f float64) int {
|
|
|
|
b[0] = numbermarker
|
|
|
|
fillBEFloat64(b[1:], f)
|
|
|
|
return lenAMF0Number
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
amf3undefinedmarker = iota
|
|
|
|
amf3nullmarker
|
|
|
|
amf3falsemarker
|
|
|
|
amf3truemarker
|
|
|
|
amf3integermarker
|
|
|
|
amf3doublemarker
|
|
|
|
amf3stringmarker
|
|
|
|
amf3xmldocmarker
|
|
|
|
amf3datemarker
|
|
|
|
amf3arraymarker
|
|
|
|
amf3objectmarker
|
|
|
|
amf3xmlmarker
|
|
|
|
amf3bytearraymarker
|
|
|
|
amf3vectorintmarker
|
|
|
|
amf3vectoruintmarker
|
|
|
|
amf3vectordoublemarker
|
|
|
|
amf3vectorobjectmarker
|
|
|
|
amf3dictionarymarker
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
numbermarker = iota
|
|
|
|
booleanmarker
|
|
|
|
stringmarker
|
|
|
|
objectmarker
|
|
|
|
movieclipmarker
|
|
|
|
nullmarker
|
|
|
|
undefinedmarker
|
|
|
|
referencemarker
|
|
|
|
ecmaarraymarker
|
|
|
|
objectendmarker
|
|
|
|
strictarraymarker
|
|
|
|
datemarker
|
|
|
|
longstringmarker
|
|
|
|
unsupportedmarker
|
|
|
|
recordsetmarker
|
|
|
|
xmldocumentmarker
|
|
|
|
typedobjectmarker
|
|
|
|
avmplusobjectmarker
|
|
|
|
)
|
|
|
|
|
|
|
|
func LenAMF0Val(_val interface{}) (n int) {
|
|
|
|
switch val := _val.(type) {
|
|
|
|
case int8:
|
|
|
|
n += lenAMF0Number
|
|
|
|
case int16:
|
|
|
|
n += lenAMF0Number
|
|
|
|
case int32:
|
|
|
|
n += lenAMF0Number
|
|
|
|
case int64:
|
|
|
|
n += lenAMF0Number
|
|
|
|
case int:
|
|
|
|
n += lenAMF0Number
|
|
|
|
case uint8:
|
|
|
|
n += lenAMF0Number
|
|
|
|
case uint16:
|
|
|
|
n += lenAMF0Number
|
|
|
|
case uint32:
|
|
|
|
n += lenAMF0Number
|
|
|
|
case uint64:
|
|
|
|
n += lenAMF0Number
|
|
|
|
case uint:
|
|
|
|
n += lenAMF0Number
|
|
|
|
case float32:
|
|
|
|
n += lenAMF0Number
|
|
|
|
case float64:
|
|
|
|
n += lenAMF0Number
|
|
|
|
|
|
|
|
case string:
|
|
|
|
u := len(val)
|
|
|
|
if u <= 65536 {
|
|
|
|
n += 3
|
|
|
|
} else {
|
|
|
|
n += 5
|
|
|
|
}
|
|
|
|
n += int(u)
|
|
|
|
|
|
|
|
case AMFECMAArray:
|
|
|
|
n += 5
|
|
|
|
for k, v := range val {
|
|
|
|
n += 2 + len(k)
|
|
|
|
n += LenAMF0Val(v)
|
|
|
|
}
|
|
|
|
n += 3
|
|
|
|
|
|
|
|
case AMFMap:
|
|
|
|
n++
|
|
|
|
for k, v := range val {
|
|
|
|
if len(k) > 0 {
|
|
|
|
n += 2 + len(k)
|
|
|
|
n += LenAMF0Val(v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
n += 3
|
|
|
|
|
|
|
|
case AMFArray:
|
|
|
|
n += 5
|
|
|
|
for _, v := range val {
|
|
|
|
n += LenAMF0Val(v)
|
|
|
|
}
|
|
|
|
|
|
|
|
case time.Time:
|
|
|
|
n += 1 + 8 + 2
|
|
|
|
|
|
|
|
case bool:
|
|
|
|
n += 2
|
|
|
|
|
|
|
|
case nil:
|
|
|
|
n++
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func FillAMF0Val(b []byte, _val interface{}) (n int) {
|
|
|
|
switch val := _val.(type) {
|
|
|
|
case int8:
|
|
|
|
n += fillAMF0Number(b[n:], float64(val))
|
|
|
|
case int16:
|
|
|
|
n += fillAMF0Number(b[n:], float64(val))
|
|
|
|
case int32:
|
|
|
|
n += fillAMF0Number(b[n:], float64(val))
|
|
|
|
case int64:
|
|
|
|
n += fillAMF0Number(b[n:], float64(val))
|
|
|
|
case int:
|
|
|
|
n += fillAMF0Number(b[n:], float64(val))
|
|
|
|
case uint8:
|
|
|
|
n += fillAMF0Number(b[n:], float64(val))
|
|
|
|
case uint16:
|
|
|
|
n += fillAMF0Number(b[n:], float64(val))
|
|
|
|
case uint32:
|
|
|
|
n += fillAMF0Number(b[n:], float64(val))
|
|
|
|
case uint64:
|
|
|
|
n += fillAMF0Number(b[n:], float64(val))
|
|
|
|
case uint:
|
|
|
|
n += fillAMF0Number(b[n:], float64(val))
|
|
|
|
case float32:
|
|
|
|
n += fillAMF0Number(b[n:], float64(val))
|
|
|
|
case float64:
|
|
|
|
n += fillAMF0Number(b[n:], float64(val))
|
|
|
|
|
|
|
|
case string:
|
|
|
|
u := len(val)
|
|
|
|
if u <= 65536 {
|
|
|
|
b[n] = stringmarker
|
|
|
|
n++
|
|
|
|
pio.PutU16BE(b[n:], uint16(u))
|
|
|
|
n += 2
|
|
|
|
} else {
|
|
|
|
b[n] = longstringmarker
|
|
|
|
n++
|
|
|
|
pio.PutU32BE(b[n:], uint32(u))
|
|
|
|
n += 4
|
|
|
|
}
|
|
|
|
copy(b[n:], []byte(val))
|
|
|
|
n += len(val)
|
|
|
|
|
|
|
|
case AMFECMAArray:
|
|
|
|
b[n] = ecmaarraymarker
|
|
|
|
n++
|
|
|
|
pio.PutU32BE(b[n:], uint32(len(val)))
|
|
|
|
n += 4
|
|
|
|
for k, v := range val {
|
|
|
|
pio.PutU16BE(b[n:], uint16(len(k)))
|
|
|
|
n += 2
|
|
|
|
copy(b[n:], []byte(k))
|
|
|
|
n += len(k)
|
|
|
|
n += FillAMF0Val(b[n:], v)
|
|
|
|
}
|
|
|
|
pio.PutU24BE(b[n:], 0x000009)
|
|
|
|
n += 3
|
|
|
|
|
|
|
|
case AMFMap:
|
|
|
|
b[n] = objectmarker
|
|
|
|
n++
|
|
|
|
for k, v := range val {
|
|
|
|
if len(k) > 0 {
|
|
|
|
pio.PutU16BE(b[n:], uint16(len(k)))
|
|
|
|
n += 2
|
|
|
|
copy(b[n:], []byte(k))
|
|
|
|
n += len(k)
|
|
|
|
n += FillAMF0Val(b[n:], v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pio.PutU24BE(b[n:], 0x000009)
|
|
|
|
n += 3
|
|
|
|
|
|
|
|
case AMFArray:
|
|
|
|
b[n] = strictarraymarker
|
|
|
|
n++
|
|
|
|
pio.PutU32BE(b[n:], uint32(len(val)))
|
|
|
|
n += 4
|
|
|
|
for _, v := range val {
|
|
|
|
n += FillAMF0Val(b[n:], v)
|
|
|
|
}
|
|
|
|
|
|
|
|
case time.Time:
|
|
|
|
b[n] = datemarker
|
|
|
|
n++
|
|
|
|
u := val.UnixNano()
|
|
|
|
f := float64(u / 1000000)
|
|
|
|
n += fillBEFloat64(b[n:], f)
|
|
|
|
pio.PutU16BE(b[n:], uint16(0))
|
|
|
|
n += 2
|
|
|
|
|
|
|
|
case bool:
|
|
|
|
b[n] = booleanmarker
|
|
|
|
n++
|
|
|
|
var u uint8
|
|
|
|
if val {
|
|
|
|
u = 1
|
|
|
|
} else {
|
|
|
|
u = 0
|
|
|
|
}
|
|
|
|
b[n] = u
|
|
|
|
n++
|
|
|
|
|
|
|
|
case nil:
|
|
|
|
b[n] = nullmarker
|
|
|
|
n++
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func ParseAMF0Val(b []byte) (val interface{}, n int, err error) {
|
|
|
|
return parseAMF0Val(b, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseAMF0Val(b []byte, offset int) (val interface{}, n int, err error) {
|
|
|
|
if len(b) < n+1 {
|
|
|
|
err = amf0ParseErr("marker", offset+n, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
marker := b[n]
|
|
|
|
n++
|
|
|
|
|
|
|
|
switch marker {
|
|
|
|
case numbermarker:
|
|
|
|
if len(b) < n+8 {
|
|
|
|
err = amf0ParseErr("number", offset+n, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
val = parseBEFloat64(b[n:])
|
|
|
|
n += 8
|
|
|
|
|
|
|
|
case booleanmarker:
|
|
|
|
if len(b) < n+1 {
|
|
|
|
err = amf0ParseErr("boolean", offset+n, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
val = b[n] != 0
|
|
|
|
n++
|
|
|
|
|
|
|
|
case stringmarker:
|
|
|
|
if len(b) < n+2 {
|
|
|
|
err = amf0ParseErr("string.length", offset+n, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
length := int(pio.U16BE(b[n:]))
|
|
|
|
n += 2
|
|
|
|
|
|
|
|
if len(b) < n+length {
|
|
|
|
err = amf0ParseErr("string.body", offset+n, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
val = string(b[n : n+length])
|
|
|
|
n += length
|
|
|
|
|
|
|
|
case objectmarker:
|
|
|
|
obj := AMFMap{}
|
|
|
|
for {
|
|
|
|
if len(b) < n+2 {
|
|
|
|
err = amf0ParseErr("object.key.length", offset+n, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
length := int(pio.U16BE(b[n:]))
|
|
|
|
n += 2
|
|
|
|
if length == 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(b) < n+length {
|
|
|
|
err = amf0ParseErr("object.key.body", offset+n, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
okey := string(b[n : n+length])
|
|
|
|
n += length
|
|
|
|
|
|
|
|
var nval int
|
|
|
|
var oval interface{}
|
|
|
|
if oval, nval, err = parseAMF0Val(b[n:], offset+n); err != nil {
|
|
|
|
err = amf0ParseErr("object.val", offset+n, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
n += nval
|
|
|
|
|
|
|
|
obj[okey] = oval
|
|
|
|
}
|
|
|
|
if len(b) < n+1 {
|
|
|
|
err = amf0ParseErr("object.end", offset+n, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
n++
|
|
|
|
val = obj
|
|
|
|
|
|
|
|
case nullmarker:
|
|
|
|
case undefinedmarker:
|
|
|
|
|
|
|
|
case ecmaarraymarker:
|
|
|
|
if len(b) < n+4 {
|
|
|
|
err = amf0ParseErr("array.count", offset+n, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
n += 4
|
|
|
|
|
|
|
|
obj := AMFMap{}
|
|
|
|
for {
|
|
|
|
if len(b) < n+2 {
|
|
|
|
err = amf0ParseErr("array.key.length", offset+n, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
length := int(pio.U16BE(b[n:]))
|
|
|
|
n += 2
|
|
|
|
|
|
|
|
if length == 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(b) < n+length {
|
|
|
|
err = amf0ParseErr("array.key.body", offset+n, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
okey := string(b[n : n+length])
|
|
|
|
n += length
|
|
|
|
|
|
|
|
var nval int
|
|
|
|
var oval interface{}
|
|
|
|
if oval, nval, err = parseAMF0Val(b[n:], offset+n); err != nil {
|
|
|
|
err = amf0ParseErr("array.val", offset+n, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
n += nval
|
|
|
|
|
|
|
|
obj[okey] = oval
|
|
|
|
}
|
|
|
|
if len(b) < n+1 {
|
|
|
|
err = amf0ParseErr("array.end", offset+n, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
n += 1
|
|
|
|
val = obj
|
|
|
|
|
|
|
|
case objectendmarker:
|
|
|
|
if len(b) < n+3 {
|
|
|
|
err = amf0ParseErr("objectend", offset+n, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
n += 3
|
|
|
|
|
|
|
|
case strictarraymarker:
|
|
|
|
if len(b) < n+4 {
|
|
|
|
err = amf0ParseErr("strictarray.count", offset+n, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
count := int(pio.U32BE(b[n:]))
|
|
|
|
n += 4
|
|
|
|
|
|
|
|
obj := make(AMFArray, count)
|
|
|
|
for i := 0; i < int(count); i++ {
|
|
|
|
var nval int
|
|
|
|
if obj[i], nval, err = parseAMF0Val(b[n:], offset+n); err != nil {
|
|
|
|
err = amf0ParseErr("strictarray.val", offset+n, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
n += nval
|
|
|
|
}
|
|
|
|
val = obj
|
|
|
|
|
|
|
|
case datemarker:
|
|
|
|
if len(b) < n+8+2 {
|
|
|
|
err = amf0ParseErr("date", offset+n, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ts := parseBEFloat64(b[n:])
|
|
|
|
n += 8 + 2
|
|
|
|
|
|
|
|
val = time.Unix(int64(ts/1000), (int64(ts)%1000)*1000000)
|
|
|
|
|
|
|
|
case longstringmarker:
|
|
|
|
if len(b) < n+4 {
|
|
|
|
err = amf0ParseErr("longstring.length", offset+n, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
length := int(pio.U32BE(b[n:]))
|
|
|
|
n += 4
|
|
|
|
|
|
|
|
if len(b) < n+length {
|
|
|
|
err = amf0ParseErr("longstring.body", offset+n, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
val = string(b[n : n+length])
|
|
|
|
n += length
|
|
|
|
|
|
|
|
default:
|
|
|
|
err = amf0ParseErr(fmt.Sprintf("invalidmarker=%d", marker), offset+n, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|