stream-server/rtmp/amf/decode.go

136 lines
3.6 KiB
Go
Raw Normal View History

2023-08-10 23:53:23 +05:00
package amf
2023-08-11 15:22:25 +05:00
import (
"errors"
"encoding/binary"
"math"
)
2023-08-21 20:25:54 +05:00
type AMFObj map[interface{}]interface{} // interface mostly so root obj can be included as an index of ints
2023-08-10 23:53:23 +05:00
2023-08-21 20:25:54 +05:00
// always a msg type of 20 for AMF0
2023-08-10 23:53:23 +05:00
func DecodeAMF(data *[]byte) (AMFObj, error) {
2023-08-11 15:22:25 +05:00
var byte_idx uint32
2023-08-21 20:25:54 +05:00
var root_obj_idx int // top level object can be referred to as int
2023-08-11 15:22:25 +05:00
amf_root_obj := make(AMFObj)
for {
2023-08-21 20:25:54 +05:00
// read the next thing and store it under the given root index
2023-08-11 15:22:25 +05:00
top_level_obj, err := read_next(data, &byte_idx)
if err != nil {
return amf_root_obj, err
}
amf_root_obj[root_obj_idx] = top_level_obj
2023-08-12 02:18:03 +05:00
2023-08-11 15:22:25 +05:00
root_obj_idx += 1
2023-08-21 20:25:54 +05:00
if byte_idx == uint32(len(*data)) { // check if full message read
break
}
2023-08-11 15:22:25 +05:00
}
return amf_root_obj, nil
}
2023-08-21 20:25:54 +05:00
// generic wrapper to read n bytes, what I tried to avoid with ReadChunk in rtmp
2023-08-11 15:22:25 +05:00
func read_bytes(data *[]byte, byte_idx *uint32, n uint32) ([]byte, error) {
if int(*byte_idx + n) > len(*data) {
return make([]byte, 0), errors.New("Read goes past end")
}
read_slice := (*data)[*byte_idx:*byte_idx + n]
2023-08-11 15:22:25 +05:00
*byte_idx += n
return read_slice, nil
2023-08-11 15:22:25 +05:00
}
2023-08-21 20:25:54 +05:00
// assuming the next thing is a number, read it
// format is 8 bytes encoded big-endian as float64
2023-08-11 15:22:25 +05:00
func read_number(data *[]byte, byte_idx *uint32) (float64, error) {
float_bytes, err := read_bytes(data, byte_idx, 8)
if err != nil {
return 0.0, err
}
return math.Float64frombits(binary.BigEndian.Uint64(float_bytes)), nil
}
2023-08-21 20:25:54 +05:00
// assuming next is boolean, read
// format is 1 byte, 0 for false 1 for true
func read_bool(data *[]byte, byte_idx *uint32) (bool, error) {
bool_byte, err := read_bytes(data, byte_idx, 1)
if err != nil {
return false, err
}
if bool_byte[0] > 1 {
return false, errors.New("bool byte must be 0 or 1")
}
return bool_byte[0] != 0, nil
}
2023-08-21 20:25:54 +05:00
// assuming next is string, read
// format is 2 bytes for string len n, next n bytes are ascii chars
func read_string(data *[]byte, byte_idx *uint32) (string, error) {
string_len, err := read_bytes(data, byte_idx, 2)
if err != nil {
return "", err
}
string_bytes, err := read_bytes(data, byte_idx, uint32(binary.BigEndian.Uint16(string_len)))
if err != nil {
return "", err
}
return string(string_bytes), err
}
2023-08-21 20:25:54 +05:00
// assuming next is an object, read
// format is always a string for a key, then a marker to determine next data type then data
// ends with an empty string for a key followed by a 9
2023-08-12 02:18:03 +05:00
func read_object(data *[]byte, byte_idx *uint32) (AMFObj, error) {
root_obj := make(AMFObj)
for {
key, err := read_string(data, byte_idx)
if err != nil {
return root_obj, err
}
if key == "" {
if end_byte, err := read_bytes(data, byte_idx, 1); err != nil {
return root_obj, err
} else if end_byte[0] != 9 {
return root_obj, errors.New("Object should end but didnt")
}
return root_obj, nil
}
val, err := read_next(data, byte_idx)
if err != nil {
return root_obj, err
}
root_obj[key] = val
}
}
2023-08-21 20:25:54 +05:00
// read the next thing
// read the next byte to determine the data type, then call one of the above to read
// defined as AMF0 spec
2023-08-11 15:22:25 +05:00
func read_next(data *[]byte, byte_idx *uint32) (interface{}, error) {
data_type, err := read_bytes(data, byte_idx, 1)
var next_obj interface{}
2023-08-11 15:22:25 +05:00
if err != nil {
return nil, err
}
switch data_type[0] {
case 0:
next_obj, err = read_number(data, byte_idx)
case 1:
next_obj, err = read_bool(data, byte_idx)
case 2:
next_obj, err = read_string(data, byte_idx)
2023-08-12 02:18:03 +05:00
case 3:
next_obj, err = read_object(data, byte_idx)
2023-08-21 20:25:54 +05:00
case 5: // null marker, no extra empty bytes, just skip to next object
2023-08-15 14:27:00 +05:00
return nil, nil
default:
return nil, errors.New("Unhandled data type")
}
if err != nil {
return nil, err
2023-08-11 15:22:25 +05:00
}
return next_obj, nil
2023-08-10 23:53:23 +05:00
}
2023-08-11 15:22:25 +05:00