#include "amf0.h" #include #include #include #include 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