use bytes::{BufMut, BytesMut};
const MARKER_NUMBER: u8 = 0x00;
const MARKER_BOOLEAN: u8 = 0x01;
const MARKER_STRING: u8 = 0x02;
const MARKER_OBJECT: u8 = 0x03;
const MARKER_NULL: u8 = 0x05;
const MARKER_UNDEFINED: u8 = 0x06;
const MARKER_ECMA_ARRAY: u8 = 0x08;
const MARKER_OBJECT_END: u8 = 0x09;
const MARKER_STRICT_ARRAY: u8 = 0x0A;
#[derive(Debug, Clone, PartialEq)]
pub enum Amf0Value {
Number(f64),
Boolean(bool),
String(String),
Object(Vec<(String, Amf0Value)>),
EcmaArray(Vec<(String, Amf0Value)>),
StrictArray(Vec<Amf0Value>),
Null,
Undefined,
}
impl Amf0Value {
pub fn as_str(&self) -> Option<&str> {
match self {
Amf0Value::String(s) => Some(s),
_ => None,
}
}
pub fn as_number(&self) -> Option<f64> {
match self {
Amf0Value::Number(n) => Some(*n),
_ => None,
}
}
pub fn get(&self, key: &str) -> Option<&Amf0Value> {
match self {
Amf0Value::Object(props) | Amf0Value::EcmaArray(props) => {
props.iter().find(|(k, _)| k == key).map(|(_, v)| v)
}
_ => None,
}
}
pub fn encode(&self, out: &mut BytesMut) {
match self {
Amf0Value::Number(n) => {
out.put_u8(MARKER_NUMBER);
out.put_f64(*n);
}
Amf0Value::Boolean(b) => {
out.put_u8(MARKER_BOOLEAN);
out.put_u8(*b as u8);
}
Amf0Value::String(s) => {
out.put_u8(MARKER_STRING);
encode_string_body(out, s);
}
Amf0Value::Object(props) => {
out.put_u8(MARKER_OBJECT);
encode_object_body(out, props);
}
Amf0Value::EcmaArray(props) => {
out.put_u8(MARKER_ECMA_ARRAY);
out.put_u32(props.len() as u32);
encode_object_body(out, props);
}
Amf0Value::StrictArray(items) => {
out.put_u8(MARKER_STRICT_ARRAY);
out.put_u32(items.len() as u32);
for item in items {
item.encode(out);
}
}
Amf0Value::Null => out.put_u8(MARKER_NULL),
Amf0Value::Undefined => out.put_u8(MARKER_UNDEFINED),
}
}
}
pub fn object(props: Vec<(&str, Amf0Value)>) -> Amf0Value {
Amf0Value::Object(props.into_iter().map(|(k, v)| (k.to_string(), v)).collect())
}
pub fn string(s: impl Into<String>) -> Amf0Value {
Amf0Value::String(s.into())
}
fn encode_string_body(out: &mut BytesMut, s: &str) {
out.put_u16(s.len() as u16);
out.put_slice(s.as_bytes());
}
fn encode_object_body(out: &mut BytesMut, props: &[(String, Amf0Value)]) {
for (k, v) in props {
encode_string_body(out, k);
v.encode(out);
}
out.put_slice(&[0x00, 0x00, MARKER_OBJECT_END]); }
pub fn decode_all(buf: &[u8]) -> Vec<Amf0Value> {
let mut out = Vec::new();
let mut pos = 0;
while pos < buf.len() {
match decode_value(&buf[pos..]) {
Some((v, used)) if used > 0 => {
out.push(v);
pos += used;
}
_ => break,
}
}
out
}
const MAX_DEPTH: usize = 32;
pub fn decode_value(buf: &[u8]) -> Option<(Amf0Value, usize)> {
decode_value_at(buf, 0)
}
fn decode_value_at(buf: &[u8], depth: usize) -> Option<(Amf0Value, usize)> {
if depth > MAX_DEPTH {
return None; }
let marker = *buf.first()?;
let body = &buf[1..];
match marker {
MARKER_NUMBER => {
let bytes: [u8; 8] = body.get(..8)?.try_into().ok()?;
Some((Amf0Value::Number(f64::from_be_bytes(bytes)), 9))
}
MARKER_BOOLEAN => Some((Amf0Value::Boolean(*body.first()? != 0), 2)),
MARKER_STRING => {
let (s, used) = decode_string_body(body)?;
Some((Amf0Value::String(s), 1 + used))
}
MARKER_OBJECT => {
let (props, used) = decode_object_body(body, depth + 1)?;
Some((Amf0Value::Object(props), 1 + used))
}
MARKER_ECMA_ARRAY => {
let count_bytes = body.get(..4)?;
let _count = u32::from_be_bytes(count_bytes.try_into().ok()?);
let (props, used) = decode_object_body(&body[4..], depth + 1)?;
Some((Amf0Value::EcmaArray(props), 1 + 4 + used))
}
MARKER_STRICT_ARRAY => {
let count = u32::from_be_bytes(body.get(..4)?.try_into().ok()?) as usize;
let mut pos = 4;
let mut items = Vec::with_capacity(count.min(64));
for _ in 0..count {
let (v, used) = decode_value_at(body.get(pos..)?, depth + 1)?;
items.push(v);
pos += used;
}
Some((Amf0Value::StrictArray(items), 1 + pos))
}
MARKER_NULL => Some((Amf0Value::Null, 1)),
MARKER_UNDEFINED => Some((Amf0Value::Undefined, 1)),
_ => None,
}
}
fn decode_string_body(buf: &[u8]) -> Option<(String, usize)> {
let len = u16::from_be_bytes(buf.get(..2)?.try_into().ok()?) as usize;
let s = std::str::from_utf8(buf.get(2..2 + len)?).ok()?.to_string();
Some((s, 2 + len))
}
fn decode_object_body(buf: &[u8], depth: usize) -> Option<(Vec<(String, Amf0Value)>, usize)> {
let mut props = Vec::new();
let mut pos = 0;
loop {
if buf.get(pos..pos + 3) == Some(&[0x00, 0x00, MARKER_OBJECT_END]) {
return Some((props, pos + 3));
}
let (key, kused) = decode_string_body(buf.get(pos..)?)?;
pos += kused;
let (val, vused) = decode_value_at(buf.get(pos..)?, depth)?;
pos += vused;
props.push((key, val));
if pos >= buf.len() {
return None; }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrips_a_connect_like_command() {
let mut buf = BytesMut::new();
string("connect").encode(&mut buf);
Amf0Value::Number(1.0).encode(&mut buf);
object(vec![
("app", string("live")),
("tcUrl", string("rtmp://host/live")),
("fpad", Amf0Value::Boolean(false)),
])
.encode(&mut buf);
Amf0Value::Null.encode(&mut buf);
let values = decode_all(&buf);
assert_eq!(values[0].as_str(), Some("connect"));
assert_eq!(values[1].as_number(), Some(1.0));
assert_eq!(values[2].get("app").and_then(|v| v.as_str()), Some("live"));
assert_eq!(values[2].get("fpad"), Some(&Amf0Value::Boolean(false)));
assert_eq!(values[3], Amf0Value::Null);
}
#[test]
fn deeply_nested_object_is_rejected_not_overflowing() {
let mut buf = Vec::new();
for _ in 0..1000 {
buf.push(MARKER_OBJECT);
buf.extend_from_slice(&[0x00, 0x03]); buf.extend_from_slice(b"key");
}
assert!(decode_value(&buf).is_none());
assert!(decode_all(&buf).is_empty());
}
#[test]
fn decodes_ecma_array_as_object() {
let mut buf = BytesMut::new();
Amf0Value::EcmaArray(vec![("width".into(), Amf0Value::Number(1920.0))]).encode(&mut buf);
let v = &decode_all(&buf)[0];
assert_eq!(v.get("width").and_then(|n| n.as_number()), Some(1920.0));
}
#[test]
fn truncated_input_does_not_panic() {
assert!(decode_value(&[MARKER_STRING, 0x00, 0x05, b'h']).is_none());
assert!(decode_value(&[]).is_none());
assert!(decode_all(&[MARKER_NUMBER, 0x01]).is_empty());
}
#[test]
fn random_bytes_never_panic() {
let mut state = 0x1234_5678_9ABC_DEF0u64;
let mut next = || {
state ^= state >> 12;
state ^= state << 25;
state ^= state >> 27;
state.wrapping_mul(0x2545_F491_4F6C_DD1D)
};
for _ in 0..4000 {
let len = (next() % 64) as usize;
let buf: Vec<u8> = (0..len).map(|_| (next() & 0xFF) as u8).collect();
let _ = decode_all(&buf);
let _ = decode_value(&buf);
}
}
}