Older/MediaServer/libflv/source/amf0.c

592 lines
20 KiB
C
Raw Normal View History

2024-10-01 00:12:57 +08:00
#include "amf0.h"
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <assert.h>
static double s_double = 1.0; // 3ff0 0000 0000 0000
static uint8_t* AMFWriteInt16(uint8_t* ptr, const uint8_t* end, uint16_t value)
{
if (ptr + 2 > end) return NULL;
ptr[0] = value >> 8;
ptr[1] = value & 0xFF;
return ptr + 2;
}
static uint8_t* AMFWriteInt32(uint8_t* ptr, const uint8_t* end, uint32_t value)
{
if (ptr + 4 > end) return NULL;
ptr[0] = (uint8_t)(value >> 24);
ptr[1] = (uint8_t)(value >> 16);
ptr[2] = (uint8_t)(value >> 8);
ptr[3] = (uint8_t)(value & 0xFF);
return ptr + 4;
}
static uint8_t* AMFWriteString16(uint8_t* ptr, const uint8_t* end, const char* string, size_t length)
{
if (ptr + 2 + length > end) return NULL;
ptr = AMFWriteInt16(ptr, end, (uint16_t)length);
memcpy(ptr, string, length);
return ptr + length;
}
static uint8_t* AMFWriteString32(uint8_t* ptr, const uint8_t* end, const char* string, size_t length)
{
if (ptr + 4 + length > end) return NULL;
ptr = AMFWriteInt32(ptr, end, (uint32_t)length);
memcpy(ptr, string, length);
return ptr + length;
}
uint8_t* AMFWriteNull(uint8_t* ptr, const uint8_t* end)
{
if (!ptr || ptr + 1 > end) return NULL;
*ptr++ = AMF_NULL;
return ptr;
}
uint8_t* AMFWriteUndefined(uint8_t* ptr, const uint8_t* end)
{
if (!ptr || ptr + 1 > end) return NULL;
*ptr++ = AMF_UNDEFINED;
return ptr;
}
uint8_t* AMFWriteObject(uint8_t* ptr, const uint8_t* end)
{
if (!ptr || ptr + 1 > end) return NULL;
*ptr++ = AMF_OBJECT;
return ptr;
}
uint8_t* AMFWriteObjectEnd(uint8_t* ptr, const uint8_t* end)
{
if (!ptr || ptr + 3 > end) return NULL;
/* end of object - 0x00 0x00 0x09 */
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = AMF_OBJECT_END;
return ptr;
}
uint8_t* AMFWriteTypedObject(uint8_t* ptr, const uint8_t* end)
{
if (!ptr || ptr + 1 > end) return NULL;
*ptr++ = AMF_TYPED_OBJECT;
return ptr;
}
uint8_t* AMFWriteECMAArarry(uint8_t* ptr, const uint8_t* end)
{
if (!ptr || ptr + 1 > end) return NULL;
*ptr++ = AMF_ECMA_ARRAY;
return AMFWriteInt32(ptr, end, 0); // U32 associative-count
}
uint8_t* AMFWriteBoolean(uint8_t* ptr, const uint8_t* end, uint8_t value)
{
if (!ptr || ptr + 2 > end) return NULL;
ptr[0] = AMF_BOOLEAN;
ptr[1] = 0 == value ? 0 : 1;
return ptr + 2;
}
uint8_t* AMFWriteDouble(uint8_t* ptr, const uint8_t* end, double value)
{
if (!ptr || ptr + 9 > end) return NULL;
assert(8 == sizeof(double));
*ptr++ = AMF_NUMBER;
// Little-Endian
if (0x00 == *(char*)&s_double)
{
*ptr++ = ((uint8_t*)&value)[7];
*ptr++ = ((uint8_t*)&value)[6];
*ptr++ = ((uint8_t*)&value)[5];
*ptr++ = ((uint8_t*)&value)[4];
*ptr++ = ((uint8_t*)&value)[3];
*ptr++ = ((uint8_t*)&value)[2];
*ptr++ = ((uint8_t*)&value)[1];
*ptr++ = ((uint8_t*)&value)[0];
}
else
{
memcpy(ptr, &value, 8);
}
return ptr;
}
uint8_t* AMFWriteString(uint8_t* ptr, const uint8_t* end, const char* string, size_t length)
{
if (!ptr || ptr + 1 + (length < 65536 ? 2 : 4) + length > end || length > UINT32_MAX)
return NULL;
if (length < 65536)
{
*ptr++ = AMF_STRING;
AMFWriteString16(ptr, end, string, length);
ptr += 2;
}
else
{
*ptr++ = AMF_LONG_STRING;
AMFWriteString32(ptr, end, string, length);
ptr += 4;
}
return ptr + length;
}
uint8_t* AMFWriteDate(uint8_t* ptr, const uint8_t* end, double milliseconds, int16_t timezone)
{
if (!ptr || ptr + 11 > end)
return NULL;
AMFWriteDouble(ptr, end, milliseconds);
*ptr = AMF_DATE; // rewrite to date
return AMFWriteInt16(ptr + 8, end, timezone);
}
uint8_t* AMFWriteNamedBoolean(uint8_t* ptr, const uint8_t* end, const char* name, size_t length, uint8_t value)
{
if (ptr + length + 2 + 2 > end)
return NULL;
ptr = AMFWriteString16(ptr, end, name, length);
return ptr ? AMFWriteBoolean(ptr, end, value) : NULL;
}
uint8_t* AMFWriteNamedDouble(uint8_t* ptr, const uint8_t* end, const char* name, size_t length, double value)
{
if (ptr + length + 2 + 8 + 1 > end)
return NULL;
ptr = AMFWriteString16(ptr, end, name, length);
return ptr ? AMFWriteDouble(ptr, end, value) : NULL;
}
uint8_t* AMFWriteNamedString(uint8_t* ptr, const uint8_t* end, const char* name, size_t length, const char* value, size_t length2)
{
if (ptr + length + 2 + length2 + 3 > end)
return NULL;
ptr = AMFWriteString16(ptr, end, name, length);
return ptr ? AMFWriteString(ptr, end, value, length2) : NULL;
}
static const uint8_t* AMFReadInt16(const uint8_t* ptr, const uint8_t* end, uint32_t* value)
{
if (!ptr || ptr + 2 > end)
return NULL;
if (value)
{
*value = ((uint32_t)ptr[0] << 8) | ptr[1];
}
return ptr + 2;
}
static const uint8_t* AMFReadInt32(const uint8_t* ptr, const uint8_t* end, uint32_t* value)
{
if (!ptr || ptr + 4 > end)
return NULL;
if (value)
{
*value = ((uint32_t)ptr[0] << 24) | ((uint32_t)ptr[1] << 16) | ((uint32_t)ptr[2] << 8) | ptr[3];
}
return ptr + 4;
}
const uint8_t* AMFReadNull(const uint8_t* ptr, const uint8_t* end)
{
(void)end;
return ptr;
}
const uint8_t* AMFReadUndefined(const uint8_t* ptr, const uint8_t* end)
{
(void)end;
return ptr;
}
const uint8_t* AMFReadBoolean(const uint8_t* ptr, const uint8_t* end, uint8_t* value)
{
if (!ptr || ptr + 1 > end)
return NULL;
if (value)
{
*value = ptr[0];
}
return ptr + 1;
}
const uint8_t* AMFReadDouble(const uint8_t* ptr, const uint8_t* end, double* value)
{
uint8_t* p = (uint8_t*)value;
if (!ptr || ptr + 8 > end)
return NULL;
if (value)
{
if (0x00 == *(char*)&s_double)
{// Little-Endian
*p++ = ptr[7];
*p++ = ptr[6];
*p++ = ptr[5];
*p++ = ptr[4];
*p++ = ptr[3];
*p++ = ptr[2];
*p++ = ptr[1];
*p++ = ptr[0];
}
else
{
memcpy(value, ptr, 8);
}
}
return ptr + 8;
}
const uint8_t* AMFReadString(const uint8_t* ptr, const uint8_t* end, int isLongString, char* string, size_t length)
{
uint32_t len = 0;
if (0 == isLongString)
ptr = AMFReadInt16(ptr, end, &len);
else
ptr = AMFReadInt32(ptr, end, &len);
if (!ptr || ptr + len > end)
return NULL;
if (string && length > len)
{
memcpy(string, ptr, len);
string[len] = 0;
}
return ptr + len;
}
const uint8_t* AMFReadDate(const uint8_t* ptr, const uint8_t* end, double *milliseconds, int16_t *timezone)
{
uint32_t v;
ptr = AMFReadDouble(ptr, end, milliseconds);
if (ptr)
{
ptr = AMFReadInt16(ptr, end, &v);
if(timezone)
*timezone = (int16_t)v;
}
return ptr;
}
static const uint8_t* amf_read_object(const uint8_t* data, const uint8_t* end, struct amf_object_item_t* items, size_t n);
static const uint8_t* amf_read_ecma_array(const uint8_t* data, const uint8_t* end, struct amf_object_item_t* items, size_t n);
static const uint8_t* amf_read_strict_array(const uint8_t* ptr, const uint8_t* end, struct amf_object_item_t* items, size_t n);
static const uint8_t* amf_read_item(const uint8_t* data, const uint8_t* end, enum AMFDataType type, struct amf_object_item_t* item)
{
switch (type)
{
case AMF_BOOLEAN:
return AMFReadBoolean(data, end, (uint8_t*)(item ? item->value : NULL));
case AMF_NUMBER:
return AMFReadDouble(data, end, (double*)(item ? item->value : NULL));
case AMF_STRING:
return AMFReadString(data, end, 0, (char*)(item ? item->value : NULL), item ? item->size : 0);
case AMF_LONG_STRING:
return AMFReadString(data, end, 1, (char*)(item ? item->value : NULL), item ? item->size : 0);
case AMF_DATE:
return AMFReadDate(data, end, (double*)(item ? item->value : NULL), (int16_t*)(item ? (char*)item->value + 8 : NULL));
case AMF_OBJECT:
return amf_read_object(data, end, (struct amf_object_item_t*)(item ? item->value : NULL), item ? item->size : 0);
case AMF_NULL:
return data;
case AMF_UNDEFINED:
return data;
case AMF_ECMA_ARRAY:
return amf_read_ecma_array(data, end, (struct amf_object_item_t*)(item ? item->value : NULL), item ? item->size : 0);
case AMF_STRICT_ARRAY:
return amf_read_strict_array(data, end, (struct amf_object_item_t*)(item ? item->value : NULL), item ? item->size : 0);
default:
assert(0);
return NULL;
}
}
static inline int amf_read_item_type_check(uint8_t type0, uint8_t itemtype)
{
// decode AMF_ECMA_ARRAY as AMF_OBJECT
return (type0 == itemtype || (AMF_OBJECT == itemtype && (AMF_ECMA_ARRAY == type0 || AMF_NULL == type0))) ? 1 : 0;
}
static const uint8_t* amf_read_strict_array(const uint8_t* ptr, const uint8_t* end, struct amf_object_item_t* items, size_t n)
{
uint8_t type;
uint32_t i, count;
if (!ptr || ptr + 4 > end)
return NULL;
ptr = AMFReadInt32(ptr, end, &count); // U32 array-count
for (i = 0; i < count && ptr && ptr < end; i++)
{
type = *ptr++;
ptr = amf_read_item(ptr, end, type, (i < n && amf_read_item_type_check(type, items[i].type)) ? &items[i] : NULL);
}
return ptr;
}
static const uint8_t* amf_read_ecma_array(const uint8_t* ptr, const uint8_t* end, struct amf_object_item_t* items, size_t n)
{
if (!ptr || ptr + 4 > end)
return NULL;
ptr += 4; // U32 associative-count
return amf_read_object(ptr, end, items, n);
}
static const uint8_t* amf_read_object(const uint8_t* data, const uint8_t* end, struct amf_object_item_t* items, size_t n)
{
uint8_t type;
uint32_t len;
size_t i;
while (data && data + 2 <= end)
{
len = *data++ << 8;
len |= *data++;
if (0 == len)
break; // last item
if (data + len + 1 > end)
return NULL; // invalid
for (i = 0; i < n; i++)
{
if (strlen(items[i].name) == len && 0 == memcmp(items[i].name, data, len) && amf_read_item_type_check(data[len], items[i].type))
break;
}
data += len; // skip name string
type = *data++; // value type
data = amf_read_item(data, end, type, i < n ? &items[i] : NULL);
}
if (data && data < end && AMF_OBJECT_END == *data)
return data + 1;
return NULL; // invalid object
}
const uint8_t* amf_read_items(const uint8_t* data, const uint8_t* end, struct amf_object_item_t* items, size_t count)
{
size_t i;
uint8_t type;
for (i = 0; i < count && data && data < end; i++)
{
type = *data++;
if (!amf_read_item_type_check(type, items[i].type))
return NULL;
data = amf_read_item(data, end, type, &items[i]);
}
return data;
}
#if defined(_DEBUG) || defined(DEBUG)
struct rtmp_amf0_command_t
{
char fmsVer[64];
double capabilities;
double mode;
};
struct rtmp_amf0_data_t
{
char version[64];
};
struct rtmp_amf0_information_t
{
char code[64]; // NetStream.Play.Start
char level[8]; // warning/status/error
char description[256];
double clientid;
double objectEncoding;
struct rtmp_amf0_data_t data;
};
static void amf0_test_1(void)
{
const uint8_t amf0[] = {
0x02, 0x00, 0x07, 0x5F, 0x72, 0x65, 0x73, 0x75, 0x6C, 0x74,
0x00, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03,
0x00, 0x06, 0x66, 0x6D, 0x73, 0x56, 0x65, 0x72, 0x02, 0x00, 0x0E, 0x46, 0x4D, 0x53, 0x2F, 0x33, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x32, 0x30, 0x30, 0x34,
0x00, 0x0C, 0x63, 0x61, 0x70,0x61, 0x62, 0x69, 0x6C, 0x69, 0x74, 0x69, 0x65, 0x73, 0x00, 0x40, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x04, 0x6D, 0x6F, 0x64, 0x65, 0x00, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x09,
0x03,
0x00, 0x05, 0x6C, 0x65, 0x76, 0x65, 0x6C, 0x02, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
0x00, 0x04, 0x63, 0x6F, 0x64, 0x65, 0x02, 0x00, 0x1D, 0x4E, 0x65, 0x74, 0x43, 0x6F, 0x6E, 0x6E, 0x65, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x2E, 0x43, 0x6F, 0x6E, 0x6E, 0x65, 0x63, 0x74, 0x2E, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73,
0x00, 0x0B, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x02, 0x00, 0x15, 0x43, 0x6F, 0x6E, 0x6E, 0x65, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x73, 0x75, 0x63, 0x63, 0x65, 0x65, 0x64, 0x65, 0x64, 0x2E,
0x00, 0x04, 0x64, 0x61, 0x74, 0x61,
0x08, 0x00, 0x00, 0x00, 0x01,
0x00, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x02, 0x00, 0x0A, 0x33, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x32, 0x30, 0x30, 0x34,
0x00, 0x00, 0x09,
0x00, 0x08, 0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x69, 0x64, 0x00, 0x41, 0xD7, 0x9B, 0x78, 0x7C, 0xC0, 0x00, 0x00,
0x00, 0x0E, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x45, 0x6E, 0x63, 0x6F, 0x64, 0x69, 0x6E, 0x67, 0x00, 0x40, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x09,
};
char reply[8];
const uint8_t* end;
double transactionId;
struct rtmp_amf0_command_t fms;
struct rtmp_amf0_information_t result;
struct amf_object_item_t cmd[3];
struct amf_object_item_t data[1];
struct amf_object_item_t info[6];
struct amf_object_item_t items[4];
#define AMF_OBJECT_ITEM_VALUE(v, amf_type, amf_name, amf_value, amf_size) { v.type=amf_type; v.name=amf_name; v.value=amf_value; v.size=amf_size; }
AMF_OBJECT_ITEM_VALUE(cmd[0], AMF_STRING, "fmsVer", fms.fmsVer, sizeof(fms.fmsVer));
AMF_OBJECT_ITEM_VALUE(cmd[1], AMF_NUMBER, "capabilities", &fms.capabilities, sizeof(fms.capabilities));
AMF_OBJECT_ITEM_VALUE(cmd[2], AMF_NUMBER, "mode", &fms.mode, sizeof(fms.mode));
AMF_OBJECT_ITEM_VALUE(data[0], AMF_STRING, "version", result.data.version, sizeof(result.data.version));
AMF_OBJECT_ITEM_VALUE(info[0], AMF_STRING, "code", result.code, sizeof(result.code));
AMF_OBJECT_ITEM_VALUE(info[1], AMF_STRING, "level", result.level, sizeof(result.level));
AMF_OBJECT_ITEM_VALUE(info[2], AMF_STRING, "description", result.description, sizeof(result.description));
AMF_OBJECT_ITEM_VALUE(info[3], AMF_ECMA_ARRAY, "data", data, sizeof(data)/sizeof(data[0]));
AMF_OBJECT_ITEM_VALUE(info[4], AMF_NUMBER, "clientid", &result.clientid, sizeof(result.clientid));
AMF_OBJECT_ITEM_VALUE(info[5], AMF_NUMBER, "objectEncoding", &result.objectEncoding, sizeof(result.objectEncoding));
AMF_OBJECT_ITEM_VALUE(items[0], AMF_STRING, "reply", reply, sizeof(reply)); // Command object
AMF_OBJECT_ITEM_VALUE(items[1], AMF_NUMBER, "transaction", &transactionId, sizeof(transactionId)); // Command object
AMF_OBJECT_ITEM_VALUE(items[2], AMF_OBJECT, "command", cmd, sizeof(cmd)/sizeof(cmd[0])); // Command object
AMF_OBJECT_ITEM_VALUE(items[3], AMF_OBJECT, "information", info, sizeof(info) / sizeof(info[0])); // Information object
end = amf0 + sizeof(amf0);
assert(end == amf_read_items(amf0, end, items, sizeof(items) / sizeof(items[0])));
assert(0 == strcmp(fms.fmsVer, "FMS/3,5,5,2004"));
assert(fms.capabilities == 31.0);
assert(fms.mode == 1.0);
assert(0 == strcmp(result.code, "NetConnection.Connect.Success"));
assert(0 == strcmp(result.level, "status"));
assert(0 == strcmp(result.description, "Connection succeeded."));
assert(0 == strcmp(result.data.version, "3,5,5,2004"));
assert(1584259571.0 == result.clientid);
assert(3.0 == result.objectEncoding);
}
struct rtmp_amf0_connect_t
{
char app[64]; // Server application name, e.g.: testapp
char flashver[32]; // Flash Player version, FMSc/1.0
char swfUrl[256]; // URL of the source SWF file
char tcUrl[256]; // URL of the Server, rtmp://host:1935/testapp/instance1
uint8_t fpad; // boolean: True if proxy is being used.
double capabilities; // double default: 15
double audioCodecs; // double default: 4071
double videoCodecs; // double default: 252
double videoFunction; // double default: 1
double encoding;
char pageUrl[256]; // http://host/sample.html
};
static void amf0_test_2(void)
{
const uint8_t amf0[] = {
0x02, 0x00, 0x07, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x00, 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x00,
0x03, 0x61, 0x70, 0x70,
0x02,
0x00, 0x05, 0x6c, 0x69, 0x76, 0x65, 0x2f,
0x00, 0x05, 0x74, 0x63, 0x55, 0x72, 0x6c,
0x02, 0x00, 0x1A, 0x72, 0x74, 0x6d, 0x70, 0x3a, 0x2f, 0x2f, 0x70, 0x75, 0x73, 0x68, 0x2e, 0x72, 0x74, 0x6d, 0x70, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x76, 0x65, 0x2f,
0x00, 0x04, 0x74, 0x79, 0x70, 0x65,
0x02, 0x00, 0x0a, 0x6e, 0x6f, 0x6e, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x00,
0x08, 0x66, 0x6c, 0x61, 0x73, 0x68, 0x56, 0x65, 0x72, 0x02, 0x00, 0x1f, 0x46, 0x4d, 0x4c, 0x45,
0x2f, 0x33, 0x2e, 0x30, 0x20, 0x28, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x6c,
0x65, 0x3b, 0x20, 0x46, 0x4d, 0x53, 0x63, 0x2f, 0x31, 0x2e, 0x30, 0x29, 0x00, 0x06, 0x73, 0x77,
0x66, 0x55, 0x72, 0x6c,
0x02, 0x00, 0x1A, 0x72, 0x74, 0x6d, 0x70, 0x3a, 0x2f, 0x2f, 0x70, 0x75, 0x73, 0x68, 0x2e, 0x72, 0x74, 0x6d, 0x70, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x76, 0x65, 0x2f,
0x00, 0x04, 0x66, 0x70, 0x61, 0x64, 0x01, 0x00, 0x00, 0x0c, 0x63, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c,
0x69, 0x74, 0x69, 0x65, 0x73, 0x00, 0x40, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b,
0x61, 0x75, 0x64, 0x69, 0x6f, 0x43, 0x6f, 0x64, 0x65, 0x63, 0x73, 0x00, 0x40, 0xa8, 0xee, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x43, 0x6f, 0x64, 0x65,
0x63, 0x73, 0x00, 0x40, 0x6f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x76, 0x69, 0x64,
0x65, 0x6f, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x3f, 0xf0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x07, 0x70, 0x61, 0x67, 0x65, 0x55, 0x72, 0x6c, 0x06, 0x00, 0x0e, 0x6f,
0x62, 0x6a, 0x65, 0x63, 0x74, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09,
};
char reply[8];
const uint8_t* end;
double transactionId;
struct rtmp_amf0_connect_t connect;
struct amf_object_item_t commands[11];
struct amf_object_item_t items[3];
#define AMF_OBJECT_ITEM_VALUE(v, amf_type, amf_name, amf_value, amf_size) { v.type=amf_type; v.name=amf_name; v.value=amf_value; v.size=amf_size; }
AMF_OBJECT_ITEM_VALUE(commands[0], AMF_STRING, "app", connect.app, sizeof(connect.app));
AMF_OBJECT_ITEM_VALUE(commands[1], AMF_STRING, "flashVer", connect.flashver, sizeof(connect.flashver));
AMF_OBJECT_ITEM_VALUE(commands[2], AMF_STRING, "tcUrl", connect.tcUrl, sizeof(connect.tcUrl));
AMF_OBJECT_ITEM_VALUE(commands[3], AMF_BOOLEAN, "fpad", &connect.fpad, 1);
AMF_OBJECT_ITEM_VALUE(commands[4], AMF_NUMBER, "audioCodecs", &connect.audioCodecs, 8);
AMF_OBJECT_ITEM_VALUE(commands[5], AMF_NUMBER, "videoCodecs", &connect.videoCodecs, 8);
AMF_OBJECT_ITEM_VALUE(commands[6], AMF_NUMBER, "videoFunction", &connect.videoFunction, 8);
AMF_OBJECT_ITEM_VALUE(commands[7], AMF_NUMBER, "objectEncoding", &connect.encoding, 8);
AMF_OBJECT_ITEM_VALUE(commands[8], AMF_NUMBER, "capabilities", &connect.capabilities, 8);
AMF_OBJECT_ITEM_VALUE(commands[9], AMF_STRING, "pageUrl", &connect.pageUrl, sizeof(connect.pageUrl));
AMF_OBJECT_ITEM_VALUE(commands[10], AMF_STRING, "swfUrl", &connect.swfUrl, sizeof(connect.swfUrl));
AMF_OBJECT_ITEM_VALUE(items[0], AMF_STRING, "reply", reply, sizeof(reply)); // Command object
AMF_OBJECT_ITEM_VALUE(items[1], AMF_NUMBER, "transaction", &transactionId, sizeof(transactionId)); // Command object
AMF_OBJECT_ITEM_VALUE(items[2], AMF_OBJECT, "command", commands, sizeof(commands) / sizeof(commands[0])); // Command object
end = amf0 + sizeof(amf0);
memset(&connect, 0, sizeof(connect));
assert(end == amf_read_items(amf0, end, items, sizeof(items) / sizeof(items[0])));
assert(0 == strcmp(connect.app, "live/"));
assert(0 == strcmp(connect.tcUrl, "rtmp://push.rtmp.com/live/"));
assert(0 == strcmp(connect.flashver, "FMLE/3.0 (compatible; FMSc/1.0)"));
assert(0 == strcmp(connect.swfUrl, "rtmp://push.rtmp.com/live/"));
assert(0 == strcmp(connect.pageUrl, "")); // pageUrl undefined
assert(connect.fpad == 0);
assert(connect.capabilities == 15);
assert(connect.audioCodecs == 3191);
assert(connect.videoCodecs == 252);
assert(connect.videoFunction == 1);
assert(connect.encoding == 0);
}
void amf0_test(void)
{
amf0_test_1();
amf0_test_2();
}
#endif