592 lines
20 KiB
C
592 lines
20 KiB
C
|
#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
|