diff --git a/build/modules.conf.in b/build/modules.conf.in index 1084461f0e..ef3e7eb5bd 100644 --- a/build/modules.conf.in +++ b/build/modules.conf.in @@ -68,6 +68,7 @@ endpoints/mod_loopback #endpoints/mod_skypopen #endpoints/mod_h323 #endpoints/mod_khomp +#endpoints/mod_rtmp #../../libs/openzap/mod_openzap #../../libs/freetdm/mod_freetdm #asr_tts/mod_unimrcp diff --git a/conf/autoload_configs/modules.conf.xml b/conf/autoload_configs/modules.conf.xml index 96079cd227..c9f1c17d7e 100644 --- a/conf/autoload_configs/modules.conf.xml +++ b/conf/autoload_configs/modules.conf.xml @@ -42,6 +42,7 @@ + diff --git a/src/mod/endpoints/mod_rtmp/Makefile b/src/mod/endpoints/mod_rtmp/Makefile new file mode 100644 index 0000000000..c2a7e251a8 --- /dev/null +++ b/src/mod/endpoints/mod_rtmp/Makefile @@ -0,0 +1,7 @@ +BASE=../../../.. + +LIBAMF_OBJS=libamf/src/amf0.o libamf/src/hash.o libamf/src/io.o libamf/src/ptrarray.o libamf/src/types.o +LOCAL_OBJS=rtmp_sig.o rtmp.o rtmp_tcp.o $(LIBAMF_OBJS) +LOCAL_CFLAGS=-Ilibamf/src + +include $(BASE)/build/modmake.rules diff --git a/src/mod/endpoints/mod_rtmp/libamf/CMakeLists.txt b/src/mod/endpoints/mod_rtmp/libamf/CMakeLists.txt new file mode 100644 index 0000000000..af3fca057e --- /dev/null +++ b/src/mod/endpoints/mod_rtmp/libamf/CMakeLists.txt @@ -0,0 +1,64 @@ +cmake_minimum_required(VERSION 2.6) +project(libamf C) + +# generic variables +set(PACKAGE "libamf") +set(PACKAGE_NAME ${PACKAGE}) +set(PACKAGE_BUGREPORT "libamf@sf.net") +set(PACKAGE_VERSION "0.1.0") +set(PACKAGE_STRING "${PACKAGE_NAME} ${PACKAGE_VERSION}") + +#platform tests +include(CheckFunctionExists) +include(CheckIncludeFile) +include(CheckTypeSize) +include(TestBigEndian) + +check_include_file(sys/types.h HAVE_SYS_TYPES_H) +check_include_file(stdint.h HAVE_STDINT_H) +check_include_file(stddef.h HAVE_STDDEF_H) +check_include_file(inttypes.h HAVE_INTTYPES_H) + +check_type_size("double" SIZEOF_DOUBLE) +check_type_size("float" SIZEOF_FLOAT) +check_type_size("long double" SIZEOF_LONG_DOUBLE) + +if(WIN32) + set(int16_t 1) + set(int32_t 1) + set(int64_t 1) + set(int8_t 1) + set(uint16_t 1) + set(uint32_t 1) + set(uint64_t 1) + set(uint8_t 1) +endif(WIN32) + +test_big_endian(IS_BIGENDIAN) +if(IS_BIGENDIAN) + set(WORDS_BIGENDIAN 1) +endif(IS_BIGENDIAN) + +# configuration file +configure_file(amf-cmake.h.in ${CMAKE_BINARY_DIR}/amf.h) +include_directories(${CMAKE_BINARY_DIR}) + +install( + FILES ${CMAKE_BINARY_DIR}/amf.h + DESTINATION include/amf +) + +# Visual C++ specific configuration +if(MSVC) + # use static library + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /MT") + + # C runtime deprecation in Visual C++ 2005 and later + add_definitions(-D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE) +endif(MSVC) + +add_subdirectory(src) + +# tests +enable_testing() +add_subdirectory(tests) diff --git a/src/mod/endpoints/mod_rtmp/libamf/amf-cmake.h.in b/src/mod/endpoints/mod_rtmp/libamf/amf-cmake.h.in new file mode 100644 index 0000000000..40ee41139a --- /dev/null +++ b/src/mod/endpoints/mod_rtmp/libamf/amf-cmake.h.in @@ -0,0 +1,105 @@ +#ifndef __AMF_H__ +#define __AMF_H__ + +/* Name of package */ +#define PACKAGE "@PACKAGE@" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "@PACKAGE_BUGREPORT@" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "@PACKAGE_NAME@" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "@PACKAGE_STRING@" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "@PACKAGE_NAME@" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "@PACKAGE_VERSION@" + +/* The size of `double', as computed by sizeof. */ +#define SIZEOF_DOUBLE @SIZEOF_DOUBLE@ + +/* The size of `float', as computed by sizeof. */ +#define SIZEOF_FLOAT @SIZEOF_FLOAT@ + +/* The size of `long double', as computed by sizeof. */ +#define SIZEOF_LONG_DOUBLE @SIZEOF_LONG_DOUBLE@ + +/* Version number of package */ +#define VERSION "@PACKAGE_VERSION@" + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_TYPES_H + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STDINT_H + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STDDEF_H + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_INTTYPES_H + +/* Define to 1 if your processor stores words with the most significant byte + first (like Motorola and SPARC, unlike Intel and VAX). */ +#cmakedefine WORDS_BIGENDIAN + +/* Define to the type of an integer type of width exactly 16 bits if + such a type exists and the standard includes do not define it. */ +#cmakedefine int16_t short int + +/* Define to the type of an integer type of width exactly 32 bits if + such a type exists and the standard includes do not define it. */ +#cmakedefine int32_t int + +/* Define to the type of an integer type of width exactly 64 bits if + such a type exists and the standard includes do not define it. */ +#cmakedefine int64_t long long int + +/* Define to the type of an integer type of width exactly 8 bits if + such a type exists and the standard includes do not define it. */ +#cmakedefine int8_t char + +/* Define to the type of an unsigned integer type of width exactly 16 bits if + such a type exists and the standard includes do not define it. */ +#cmakedefine uint16_t unsigned short int + +/* Define to the type of an unsigned integer type of width exactly 32 bits if + such a type exists and the standard includes do not define it. */ +#cmakedefine uint32_t unsigned int + +/* Define to the type of an unsigned integer type of width exactly 64 bits if + such a type exists and the standard includes do not define it. */ +#cmakedefine uint64_t unsigned long long int + +/* Define to the type of an unsigned integer type of width exactly 8 bits if + such a type exists and the standard includes do not define it. */ +#cmakedefine uint8_t unsigned char + +#ifdef HAVE_INTTYPES_H +#include +#endif + +#include + +/* AMF number */ +typedef +#if SIZEOF_FLOAT == 8 +float +#elif SIZEOF_DOUBLE == 8 +double +#elif SIZEOF_LONG_DOUBLE == 8 +long double +#else +uint64_t +#endif +number64_t; + +/* custom read/write function type */ +typedef size_t (*read_proc_t)(void * out_buffer, size_t size, void * user_data); +typedef size_t (*write_proc_t)(const void * in_buffer, size_t size, void * user_data); + +#endif /* __AMF_H__ */ diff --git a/src/mod/endpoints/mod_rtmp/libamf/src/CMakeLists.txt b/src/mod/endpoints/mod_rtmp/libamf/src/CMakeLists.txt new file mode 100644 index 0000000000..fdf210abee --- /dev/null +++ b/src/mod/endpoints/mod_rtmp/libamf/src/CMakeLists.txt @@ -0,0 +1,26 @@ +set(libamf_src + amf0.c + amf0.h + hash.c + hash.h + io.c + io.h + ptrarray.c + ptrarray.h + types.c + types.h + ${CMAKE_BINARY_DIR}/amf.h +) + +add_library(amf ${libamf_src}) + +install(TARGETS amf + RUNTIME DESTINATION lib + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +install( + FILES amf0.h amf3.h + DESTINATION include/amf +) diff --git a/src/mod/endpoints/mod_rtmp/libamf/src/amf.h b/src/mod/endpoints/mod_rtmp/libamf/src/amf.h new file mode 100644 index 0000000000..0a424aeafd --- /dev/null +++ b/src/mod/endpoints/mod_rtmp/libamf/src/amf.h @@ -0,0 +1,13 @@ +#ifndef __AMF_H__ +#define __AMF_H__ + +#include + +/* AMF number */ +typedef double number64_t; + +/* custom read/write function type */ +typedef size_t (*read_proc_t)(void * out_buffer, size_t size, void * user_data); +typedef size_t (*write_proc_t)(const void * in_buffer, size_t size, void * user_data); + +#endif /* __AMF_H__ */ diff --git a/src/mod/endpoints/mod_rtmp/libamf/src/amf0.b b/src/mod/endpoints/mod_rtmp/libamf/src/amf0.b new file mode 100644 index 0000000000..65ec535fd0 --- /dev/null +++ b/src/mod/endpoints/mod_rtmp/libamf/src/amf0.b @@ -0,0 +1,1014 @@ +#include + +#include "amf0.h" +#include "io.h" +#include "types.h" + +/* function common to all array types */ +static void amf0_list_init(amf0_list * list) { + if (list != NULL) { + list->size = 0; + list->first_element = NULL; + list->last_element = NULL; + } +} + +static amf0_data * amf0_list_push(amf0_list * list, amf0_data * data) { + amf0_node * node = (amf0_node*)malloc(sizeof(amf0_node)); + if (node != NULL) { + node->data = data; + node->next = NULL; + node->prev = NULL; + if (list->size == 0) { + list->first_element = node; + list->last_element = node; + } + else { + list->last_element->next = node; + node->prev = list->last_element; + list->last_element = node; + } + ++(list->size); + return data; + } + return NULL; +} + +static amf0_data * amf0_list_insert_before(amf0_list * list, amf0_node * node, amf0_data * data) { + if (node != NULL) { + amf0_node * new_node = (amf0_node*)malloc(sizeof(amf0_node)); + if (new_node != NULL) { + new_node->next = node; + new_node->prev = node->prev; + + if (node->prev != NULL) { + node->prev->next = new_node; + node->prev = new_node; + } + if (node == list->first_element) { + list->first_element = new_node; + } + ++(list->size); + new_node->data = data; + return data; + } + } + return NULL; +} + +static amf0_data * amf0_list_insert_after(amf0_list * list, amf0_node * node, amf0_data * data) { + if (node != NULL) { + amf0_node * new_node = (amf0_node*)malloc(sizeof(amf0_node)); + if (new_node != NULL) { + new_node->next = node->next; + new_node->prev = node; + + if (node->next != NULL) { + node->next->prev = new_node; + node->next = new_node; + } + if (node == list->last_element) { + list->last_element = new_node; + } + ++(list->size); + new_node->data = data; + return data; + } + } + return NULL; +} + +static amf0_data * amf0_list_delete(amf0_list * list, amf0_node * node) { + amf0_data * data = NULL; + if (node != NULL) { + if (node->next != NULL) { + node->next->prev = node->prev; + } + if (node->prev != NULL) { + node->prev->next = node->next; + } + if (node == list->first_element) { + list->first_element = node->next; + } + if (node == list->last_element) { + list->last_element = node->prev; + } + data = node->data; + free(node); + --(list->size); + } + return data; +} + +static amf0_data * amf0_list_get_at(amf0_list * list, uint32_t n) { + if (n < list->size) { + uint32_t i; + amf0_node * node = list->first_element; + for (i = 0; i < n; ++i) { + node = node->next; + } + return node->data; + } + return NULL; +} + +static amf0_data * amf0_list_pop(amf0_list * list) { + return amf0_list_delete(list, list->last_element); +} + +static amf0_node * amf0_list_first(amf0_list * list) { + return list->first_element; +} + +static amf0_node * amf0_list_last(amf0_list * list) { + return list->last_element; +} + +static void amf0_list_clear(amf0_list * list) { + amf0_node * tmp; + amf0_node * node = list->first_element; + while (node != NULL) { + amf0_data_free(node->data); + tmp = node; + node = node->next; + free(tmp); + } + list->size = 0; +} + +static amf0_list * amf0_list_clone(amf0_list * list, amf0_list * out_list) { + amf0_node * node; + node = list->first_element; + while (node != NULL) { + amf0_list_push(out_list, amf0_data_clone(node->data)); + node = node->next; + } + return out_list; +} + +/* allocate an AMF data object */ +amf0_data * amf0_data_new(uint8_t type) { + amf0_data * data = (amf0_data*)malloc(sizeof(amf0_data)); + if (data != NULL) { + data->type = type; + } + return data; +} + +/* read AMF data from buffer */ +amf0_data * amf0_data_buffer_read(uint8_t * buffer, size_t maxbytes) { + buffer_context ctxt; + ctxt.start_address = ctxt.current_address = buffer; + ctxt.buffer_size = maxbytes; + return amf0_data_read(buffer_read, &ctxt); +} + +/* write AMF data to buffer */ +size_t amf0_data_buffer_write(amf0_data * data, uint8_t * buffer, size_t maxbytes) { + buffer_context ctxt; + ctxt.start_address = ctxt.current_address = buffer; + ctxt.buffer_size = maxbytes; + return amf0_data_write(data, buffer_write, &ctxt); +} + +/* load AMF data from a file stream */ +amf0_data * amf0_data_file_read(FILE * stream) { + return amf0_data_read(file_read, stream); +} + +/* write AMF data into a file stream */ +size_t amf0_data_file_write(amf0_data * data, FILE * stream) { + return amf0_data_write(data, file_write, stream); +} + +/* read a number */ +static amf0_data * amf0_number_read(read_proc_t read_proc, void * user_data) { + number64_t val; + if (read_proc(&val, sizeof(number64_t), user_data) == sizeof(number64_t)) { + return amf0_number_new(swap_number64(val)); + } + return NULL; +} + +/* read a boolean */ +static amf0_data * amf0_boolean_read(read_proc_t read_proc, void * user_data) { + uint8_t val; + if (read_proc(&val, sizeof(uint8_t), user_data) == sizeof(uint8_t)) { + return amf0_boolean_new(val); + } + return NULL; +} + +/* read a string */ +static amf0_data * amf0_string_read(read_proc_t read_proc, void * user_data) { + uint16_t strsize; + uint8_t * buffer; + if (read_proc(&strsize, sizeof(uint16_t), user_data) == sizeof(uint16_t)) { + strsize = swap_uint16(strsize); + if (strsize > 0) { + buffer = (uint8_t*)calloc(strsize, sizeof(uint8_t)); + if (buffer != NULL && read_proc(buffer, strsize, user_data) == strsize) { + amf0_data * data = amf0_string_new(buffer, strsize); + free(buffer); + return data; + } + } + else { + return amf0_string_new(NULL, 0); + } + } + return NULL; +} + +/* read an object */ +static amf0_data * amf0_object_read(read_proc_t read_proc, void * user_data) { + amf0_data * data = amf0_object_new(); + if (data != NULL) { + amf0_data * name; + amf0_data * element; + while (1) { + name = amf0_string_read(read_proc, user_data); + if (name != NULL) { + element = amf0_data_read(read_proc, user_data); + if (element != NULL) { + if (amf0_object_add(data, (char *)amf0_string_get_uint8_ts(name), element) == NULL) { + amf0_data_free(name); + amf0_data_free(element); + amf0_data_free(data); + return NULL; + } + } + else { + amf0_data_free(name); + break; + } + } + else { + /* invalid name: error */ + amf0_data_free(data); + return NULL; + } + } + } + return data; +} + +/* read an associative array */ +static amf0_data * amf0_associative_array_read(read_proc_t read_proc, void * user_data) { + amf0_data * data = amf0_associative_array_new(); + if (data != NULL) { + amf0_data * name; + amf0_data * element; + uint32_t size; + if (read_proc(&size, sizeof(uint32_t), user_data) == sizeof(uint32_t)) { + /* we ignore the 32 bits array size marker */ + while(1) { + name = amf0_string_read(read_proc, user_data); + if (name != NULL) { + element = amf0_data_read(read_proc, user_data); + if (element != NULL) { + if (amf0_associative_array_add(data, (char *)amf0_string_get_uint8_ts(name), element) == NULL) { + amf0_data_free(name); + amf0_data_free(element); + amf0_data_free(data); + return NULL; + } + } + else { + amf0_data_free(name); + break; + } + } + else { + /* invalid name: error */ + amf0_data_free(data); + return NULL; + } + } + } + else { + amf0_data_free(data); + return NULL; + } + } + return data; +} + +/* read an array */ +static amf0_data * amf0_array_read(read_proc_t read_proc, void * user_data) { + size_t i; + amf0_data * element; + amf0_data * data = amf0_array_new(); + if (data != NULL) { + uint32_t array_size; + if (read_proc(&array_size, sizeof(uint32_t), user_data) == sizeof(uint32_t)) { + array_size = swap_uint32(array_size); + + for (i = 0; i < array_size; ++i) { + element = amf0_data_read(read_proc, user_data); + + if (element != NULL) { + if (amf0_array_push(data, element) == NULL) { + amf0_data_free(element); + amf0_data_free(data); + return NULL; + } + } + else { + amf0_data_free(data); + return NULL; + } + } + } + else { + amf0_data_free(data); + return NULL; + } + } + return data; +} + +/* read a date */ +static amf0_data * amf0_date_read(read_proc_t read_proc, void * user_data) { + number64_t milliseconds; + int16_t timezone; + if (read_proc(&milliseconds, sizeof(number64_t), user_data) == sizeof(number64_t) && + read_proc(&timezone, sizeof(int16_t), user_data) == sizeof(int16_t)) { + return amf0_date_new(swap_number64(milliseconds), swap_sint16(timezone)); + } + else { + return NULL; + } +} + +/* load AMF data from stream */ +amf0_data * amf0_data_read(read_proc_t read_proc, void * user_data) { + uint8_t type; + if (read_proc(&type, sizeof(uint8_t), user_data) == sizeof(uint8_t)) { + switch (type) { + case AMF0_TYPE_NUMBER: + return amf0_number_read(read_proc, user_data); + case AMF0_TYPE_BOOLEAN: + return amf0_boolean_read(read_proc, user_data); + case AMF0_TYPE_STRING: + return amf0_string_read(read_proc, user_data); + case AMF0_TYPE_OBJECT: + return amf0_object_read(read_proc, user_data); + case AMF0_TYPE_NULL: + return amf0_null_new(); + case AMF0_TYPE_UNDEFINED: + return amf0_undefined_new(); + /*case AMF0_TYPE_REFERENCE:*/ + case AMF0_TYPE_ECMA_ARRAY: + return amf0_associative_array_read(read_proc, user_data); + case AMF0_TYPE_STRICT_ARRAY: + return amf0_array_read(read_proc, user_data); + case AMF0_TYPE_DATE: + return amf0_date_read(read_proc, user_data); + /*case AMF0_TYPE_SIMPLEOBJECT:*/ + case AMF0_TYPE_XML_DOCUMENT: + case AMF0_TYPE_TYPED_OBJECT: + case AMF0_TYPE_OBJECT_END: + return NULL; /* end of composite object */ + default: + break; + } + } + return NULL; +} + +/* determines the size of the given AMF data */ +size_t amf0_data_size(amf0_data * data) { + size_t s = 0; + amf0_node * node; + if (data != NULL) { + s += sizeof(uint8_t); + switch (data->type) { + case AMF0_TYPE_NUMBER: + s += sizeof(number64_t); + break; + case AMF0_TYPE_BOOLEAN: + s += sizeof(uint8_t); + break; + case AMF0_TYPE_STRING: + s += sizeof(uint16_t) + (size_t)amf0_string_get_size(data); + break; + case AMF0_TYPE_OBJECT: + node = amf0_object_first(data); + while (node != NULL) { + s += sizeof(uint16_t) + (size_t)amf0_string_get_size(amf0_object_get_name(node)); + s += (size_t)amf0_data_size(amf0_object_get_data(node)); + node = amf0_object_next(node); + } + s += sizeof(uint16_t) + sizeof(uint8_t); + break; + case AMF0_TYPE_NULL: + case AMF0_TYPE_UNDEFINED: + break; + /*case AMF0_TYPE_REFERENCE:*/ + case AMF0_TYPE_ECMA_ARRAY: + s += sizeof(uint32_t); + node = amf0_associative_array_first(data); + while (node != NULL) { + s += sizeof(uint16_t) + (size_t)amf0_string_get_size(amf0_associative_array_get_name(node)); + s += (size_t)amf0_data_size(amf0_associative_array_get_data(node)); + node = amf0_associative_array_next(node); + } + s += sizeof(uint16_t) + sizeof(uint8_t); + break; + case AMF0_TYPE_STRICT_ARRAY: + s += sizeof(uint32_t); + node = amf0_array_first(data); + while (node != NULL) { + s += (size_t)amf0_data_size(amf0_array_get(node)); + node = amf0_array_next(node); + } + break; + case AMF0_TYPE_DATE: + s += sizeof(number64_t) + sizeof(int16_t); + break; + /*case AMF0_TYPE_SIMPLEOBJECT:*/ + case AMF0_TYPE_XML_DOCUMENT: + case AMF0_TYPE_TYPED_OBJECT: + case AMF0_TYPE_OBJECT_END: + break; /* end of composite object */ + default: + break; + } + } + return s; +} + +/* write a number */ +static size_t amf0_number_write(amf0_data * data, write_proc_t write_proc, void * user_data) { + number64_t n = swap_number64(data->number_data); + return write_proc(&n, sizeof(number64_t), user_data); +} + +/* write a boolean */ +static size_t amf0_boolean_write(amf0_data * data, write_proc_t write_proc, void * user_data) { + return write_proc(&(data->boolean_data), sizeof(uint8_t), user_data); +} + +/* write a string */ +static size_t amf0_string_write(amf0_data * data, write_proc_t write_proc, void * user_data) { + uint16_t s; + size_t w = 0; + + s = swap_uint16(data->string_data.size); + w = write_proc(&s, sizeof(uint16_t), user_data); + if (data->string_data.size > 0) { + w += write_proc(data->string_data.mbstr, (size_t)(data->string_data.size), user_data); + } + + return w; +} + +/* write an object */ +static size_t amf0_object_write(amf0_data * data, write_proc_t write_proc, void * user_data) { + amf0_node * node; + size_t w = 0; + uint16_t filler = swap_uint16(0); + uint8_t terminator = AMF0_TYPE_OBJECT_END; + + node = amf0_object_first(data); + while (node != NULL) { + w += amf0_string_write(amf0_object_get_name(node), write_proc, user_data); + w += amf0_data_write(amf0_object_get_data(node), write_proc, user_data); + node = amf0_object_next(node); + } + + /* empty string is the last element */ + w += write_proc(&filler, sizeof(uint16_t), user_data); + /* an object ends with 0x09 */ + w += write_proc(&terminator, sizeof(uint8_t), user_data); + + return w; +} + +/* write an associative array */ +static size_t amf0_associative_array_write(amf0_data * data, write_proc_t write_proc, void * user_data) { + amf0_node * node; + size_t w = 0; + uint32_t s; + uint16_t filler = swap_uint16(0); + uint8_t terminator = AMF0_TYPE_OBJECT_END; + + s = swap_uint32(data->list_data.size) / 2; + w += write_proc(&s, sizeof(uint32_t), user_data); + node = amf0_associative_array_first(data); + while (node != NULL) { + w += amf0_string_write(amf0_associative_array_get_name(node), write_proc, user_data); + w += amf0_data_write(amf0_associative_array_get_data(node), write_proc, user_data); + node = amf0_associative_array_next(node); + } + + /* empty string is the last element */ + w += write_proc(&filler, sizeof(uint16_t), user_data); + /* an object ends with 0x09 */ + w += write_proc(&terminator, sizeof(uint8_t), user_data); + + return w; +} + +/* write an array */ +static size_t amf0_array_write(amf0_data * data, write_proc_t write_proc, void * user_data) { + amf0_node * node; + size_t w = 0; + uint32_t s; + + s = swap_uint32(data->list_data.size); + w += write_proc(&s, sizeof(uint32_t), user_data); + node = amf0_array_first(data); + while (node != NULL) { + w += amf0_data_write(amf0_array_get(node), write_proc, user_data); + node = amf0_array_next(node); + } + + return w; +} + +/* write a date */ +static size_t amf0_date_write(amf0_data * data, write_proc_t write_proc, void * user_data) { + size_t w = 0; + number64_t milli; + int16_t tz; + + milli = swap_number64(data->date_data.milliseconds); + w += write_proc(&milli, sizeof(number64_t), user_data); + tz = swap_sint16(data->date_data.timezone); + w += write_proc(&tz, sizeof(int16_t), user_data); + + return w; +} + +/* write amf data to stream */ +size_t amf0_data_write(amf0_data * data, write_proc_t write_proc, void * user_data) { + size_t s = 0; + if (data != NULL) { + s += write_proc(&(data->type), sizeof(uint8_t), user_data); + switch (data->type) { + case AMF0_TYPE_NUMBER: + s += amf0_number_write(data, write_proc, user_data); + break; + case AMF0_TYPE_BOOLEAN: + s += amf0_boolean_write(data, write_proc, user_data); + break; + case AMF0_TYPE_STRING: + s += amf0_string_write(data, write_proc, user_data); + break; + case AMF0_TYPE_OBJECT: + s += amf0_object_write(data, write_proc, user_data); + break; + case AMF0_TYPE_NULL: + case AMF0_TYPE_UNDEFINED: + break; + /*case AMF0_TYPE_REFERENCE:*/ + case AMF0_TYPE_ECMA_ARRAY: + s += amf0_associative_array_write(data, write_proc, user_data); + break; + case AMF0_TYPE_STRICT_ARRAY: + s += amf0_array_write(data, write_proc, user_data); + break; + case AMF0_TYPE_DATE: + s += amf0_date_write(data, write_proc, user_data); + break; + /*case AMF0_TYPE_SIMPLEOBJECT:*/ + case AMF0_TYPE_XML_DOCUMENT: + case AMF0_TYPE_TYPED_OBJECT: + case AMF0_TYPE_OBJECT_END: + break; /* end of composite object */ + default: + break; + } + } + return s; +} + +/* data type */ +uint8_t amf0_data_get_type(amf0_data * data) { + return (data != NULL) ? data->type : AMF0_TYPE_NULL; +} + +/* clone AMF data */ +amf0_data * amf0_data_clone(amf0_data * data) { + /* we copy data recursively */ + if (data != NULL) { + switch (data->type) { + case AMF0_TYPE_NUMBER: return amf0_number_new(amf0_number_get_value(data)); + case AMF0_TYPE_BOOLEAN: return amf0_boolean_new(amf0_boolean_get_value(data)); + case AMF0_TYPE_STRING: + if (data->string_data.mbstr != NULL) { + return amf0_string_new((uint8_t *)strdup((char *)amf0_string_get_uint8_ts(data)), amf0_string_get_size(data)); + } + else { + return amf0_str(NULL); + } + case AMF0_TYPE_NULL: return NULL; + case AMF0_TYPE_UNDEFINED: return NULL; + /*case AMF0_TYPE_REFERENCE:*/ + case AMF0_TYPE_OBJECT: + case AMF0_TYPE_ECMA_ARRAY: + case AMF0_TYPE_STRICT_ARRAY: + { + amf0_data * d = amf0_data_new(data->type); + if (d != NULL) { + amf0_list_init(&d->list_data); + amf0_list_clone(&data->list_data, &d->list_data); + } + return d; + } + case AMF0_TYPE_DATE: return amf0_date_new(amf0_date_get_milliseconds(data), amf0_date_get_timezone(data)); + /*case AMF0_TYPE_SIMPLEOBJECT:*/ + case AMF0_TYPE_XML_DOCUMENT: return NULL; + case AMF0_TYPE_TYPED_OBJECT: return NULL; + } + } + return NULL; +} + +/* free AMF data */ +void amf0_data_free(amf0_data * data) { + if (data != NULL) { + switch (data->type) { + case AMF0_TYPE_NUMBER: break; + case AMF0_TYPE_BOOLEAN: break; + case AMF0_TYPE_STRING: + if (data->string_data.mbstr != NULL) { + free(data->string_data.mbstr); + } break; + case AMF0_TYPE_NULL: break; + case AMF0_TYPE_UNDEFINED: break; + /*case AMF0_TYPE_REFERENCE:*/ + case AMF0_TYPE_OBJECT: + case AMF0_TYPE_ECMA_ARRAY: + case AMF0_TYPE_STRICT_ARRAY: amf0_list_clear(&data->list_data); break; + case AMF0_TYPE_DATE: break; + /*case AMF0_TYPE_SIMPLEOBJECT:*/ + case AMF0_TYPE_XML_DOCUMENT: break; + case AMF0_TYPE_TYPED_OBJECT: break; + default: break; + } + free(data); + } +} + +/* dump AMF data into a stream as text */ +void amf0_data_dump(FILE * stream, amf0_data * data, int indent_level) { + if (data != NULL) { + amf0_node * node; + time_t time; + struct tm * t; + char datestr[128]; + switch (data->type) { + case AMF0_TYPE_NUMBER: + fprintf(stream, "%.12g", data->number_data); + break; + case AMF0_TYPE_BOOLEAN: + fprintf(stream, "%s", (data->boolean_data) ? "true" : "false"); + break; + case AMF0_TYPE_STRING: + fprintf(stream, "\'%.*s\'", data->string_data.size, data->string_data.mbstr); + break; + case AMF0_TYPE_OBJECT: + node = amf0_object_first(data); + fprintf(stream, "{\n"); + while (node != NULL) { + fprintf(stream, "%*s", (indent_level+1)*4, ""); + amf0_data_dump(stream, amf0_object_get_name(node), indent_level+1); + fprintf(stream, ": "); + amf0_data_dump(stream, amf0_object_get_data(node), indent_level+1); + node = amf0_object_next(node); + fprintf(stream, "\n"); + } + fprintf(stream, "%*s", indent_level*4 + 1, "}"); + break; + case AMF0_TYPE_NULL: + fprintf(stream, "null"); + break; + case AMF0_TYPE_UNDEFINED: + fprintf(stream, "undefined"); + break; + /*case AMF0_TYPE_REFERENCE:*/ + case AMF0_TYPE_ECMA_ARRAY: + node = amf0_associative_array_first(data); + fprintf(stream, "{\n"); + while (node != NULL) { + fprintf(stream, "%*s", (indent_level+1)*4, ""); + amf0_data_dump(stream, amf0_associative_array_get_name(node), indent_level+1); + fprintf(stream, " => "); + amf0_data_dump(stream, amf0_associative_array_get_data(node), indent_level+1); + node = amf0_associative_array_next(node); + fprintf(stream, "\n"); + } + fprintf(stream, "%*s", indent_level*4 + 1, "}"); + break; + case AMF0_TYPE_STRICT_ARRAY: + node = amf0_array_first(data); + fprintf(stream, "[\n"); + while (node != NULL) { + fprintf(stream, "%*s", (indent_level+1)*4, ""); + amf0_data_dump(stream, node->data, indent_level+1); + node = amf0_array_next(node); + fprintf(stream, "\n"); + } + fprintf(stream, "%*s", indent_level*4 + 1, "]"); + break; + case AMF0_TYPE_DATE: + time = amf0_date_to_time_t(data); + tzset(); + t = localtime(&time); + strftime(datestr, sizeof(datestr), "%a, %d %b %Y %H:%M:%S %z", t); + fprintf(stream, "%s", datestr); + break; + /*case AMF0_TYPE_SIMPLEOBJECT:*/ + case AMF0_TYPE_XML_DOCUMENT: break; + case AMF0_TYPE_TYPED_OBJECT: break; + default: break; + } + } +} + +/* number functions */ +amf0_data * amf0_number_new(number64_t value) { + amf0_data * data = amf0_data_new(AMF0_TYPE_NUMBER); + if (data != NULL) { + data->number_data = value; + } + return data; +} + +number64_t amf0_number_get_value(amf0_data * data) { + return (data != NULL) ? data->number_data : 0; +} + +void amf0_number_set_value(amf0_data * data, number64_t value) { + if (data != NULL) { + data->number_data = value; + } +} + +/* boolean functions */ +amf0_data * amf0_boolean_new(uint8_t value) { + amf0_data * data = amf0_data_new(AMF0_TYPE_BOOLEAN); + if (data != NULL) { + data->boolean_data = value; + } + return data; +} + +uint8_t amf0_boolean_get_value(amf0_data * data) { + return (data != NULL) ? data->boolean_data : 0; +} + +void amf0_boolean_set_value(amf0_data * data, uint8_t value) { + if (data != NULL) { + data->boolean_data = value; + } +} + +/* string functions */ +amf0_data * amf0_string_new(uint8_t * str, uint16_t size) { + amf0_data * data = amf0_data_new(AMF0_TYPE_STRING); + if (data != NULL) { + data->string_data.size = size; + data->string_data.mbstr = (uint8_t*)calloc(size+1, sizeof(uint8_t)); + if (data->string_data.mbstr != NULL) { + if (size > 0) { + memcpy(data->string_data.mbstr, str, size); + } + } + else { + amf0_data_free(data); + return NULL; + } + } + return data; +} + +amf0_data * amf0_str(const char * str) { + return amf0_string_new((uint8_t *)str, (uint16_t)(str != NULL ? strlen(str) : 0)); +} + +uint16_t amf0_string_get_size(amf0_data * data) { + return (data != NULL) ? data->string_data.size : 0; +} + +uint8_t * amf0_string_get_uint8_ts(amf0_data * data) { + return (data != NULL) ? data->string_data.mbstr : NULL; +} + +/* object functions */ +amf0_data * amf0_object_new(void) { + amf0_data * data = amf0_data_new(AMF0_TYPE_OBJECT); + if (data != NULL) { + amf0_list_init(&data->list_data); + } + return data; +} + +uint32_t amf0_object_size(amf0_data * data) { + return (data != NULL) ? data->list_data.size / 2 : 0; +} + +amf0_data * amf0_object_add(amf0_data * data, const char * name, amf0_data * element) { + if (data != NULL) { + if (amf0_list_push(&data->list_data, amf0_str(name)) != NULL) { + if (amf0_list_push(&data->list_data, element) != NULL) { + return element; + } + else { + amf0_data_free(amf0_list_pop(&data->list_data)); + } + } + } + return NULL; +} + +amf0_data * amf0_object_get(amf0_data * data, const char * name) { + if (data != NULL) { + amf0_node * node = amf0_list_first(&(data->list_data)); + while (node != NULL) { + if (strncmp((char*)(node->data->string_data.mbstr), name, (size_t)(node->data->string_data.size)) == 0) { + node = node->next; + return (node != NULL) ? node->data : NULL; + } + /* we have to skip the element data to reach the next name */ + node = node->next->next; + } + } + return NULL; +} + +amf0_data * amf0_object_set(amf0_data * data, const char * name, amf0_data * element) { + if (data != NULL) { + amf0_node * node = amf0_list_first(&(data->list_data)); + while (node != NULL) { + if (strncmp((char*)(node->data->string_data.mbstr), name, (size_t)(node->data->string_data.size)) == 0) { + node = node->next; + if (node != NULL && node->data != NULL) { + amf0_data_free(node->data); + node->data = element; + return element; + } + } + /* we have to skip the element data to reach the next name */ + node = node->next->next; + } + } + return NULL; +} + +amf0_data * amf0_object_delete(amf0_data * data, const char * name) { + if (data != NULL) { + amf0_node * node = amf0_list_first(&data->list_data); + while (node != NULL) { + node = node->next; + if (strncmp((char*)(node->data->string_data.mbstr), name, (size_t)(node->data->string_data.size)) == 0) { + amf0_node * data_node = node->next; + amf0_data_free(amf0_list_delete(&data->list_data, node)); + return amf0_list_delete(&data->list_data, data_node); + } + else { + node = node->next; + } + } + } + return NULL; +} + +amf0_node * amf0_object_first(amf0_data * data) { + return (data != NULL) ? amf0_list_first(&data->list_data) : NULL; +} + +amf0_node * amf0_object_last(amf0_data * data) { + if (data != NULL) { + amf0_node * node = amf0_list_last(&data->list_data); + if (node != NULL) { + return node->prev; + } + } + return NULL; +} + +amf0_node * amf0_object_next(amf0_node * node) { + if (node != NULL) { + amf0_node * next = node->next; + if (next != NULL) { + return next->next; + } + } + return NULL; +} + +amf0_node * amf0_object_prev(amf0_node * node) { + if (node != NULL) { + amf0_node * prev = node->prev; + if (prev != NULL) { + return prev->prev; + } + } + return NULL; +} + +amf0_data * amf0_object_get_name(amf0_node * node) { + return (node != NULL) ? node->data : NULL; +} + +amf0_data * amf0_object_get_data(amf0_node * node) { + if (node != NULL) { + amf0_node * next = node->next; + if (next != NULL) { + return next->data; + } + } + return NULL; +} + +/* associative array functions */ +amf0_data * amf0_associative_array_new(void) { + amf0_data * data = amf0_data_new(AMF0_TYPE_ECMA_ARRAY); + if (data != NULL) { + amf0_list_init(&data->list_data); + } + return data; +} + +/* array functions */ +amf0_data * amf0_array_new(void) { + amf0_data * data = amf0_data_new(AMF0_TYPE_STRICT_ARRAY); + if (data != NULL) { + amf0_list_init(&data->list_data); + } + return data; +} + +uint32_t amf0_array_size(amf0_data * data) { + return (data != NULL) ? data->list_data.size : 0; +} + +amf0_data * amf0_array_push(amf0_data * data, amf0_data * element) { + return (data != NULL) ? amf0_list_push(&data->list_data, element) : NULL; +} + +amf0_data * amf0_array_pop(amf0_data * data) { + return (data != NULL) ? amf0_list_pop(&data->list_data) : NULL; +} + +amf0_node * amf0_array_first(amf0_data * data) { + return (data != NULL) ? amf0_list_first(&data->list_data) : NULL; +} + +amf0_node * amf0_array_last(amf0_data * data) { + return (data != NULL) ? amf0_list_last(&data->list_data) : NULL; +} + +amf0_node * amf0_array_next(amf0_node * node) { + return (node != NULL) ? node->next : NULL; +} + +amf0_node * amf0_array_prev(amf0_node * node) { + return (node != NULL) ? node->prev : NULL; +} + +amf0_data * amf0_array_get(amf0_node * node) { + return (node != NULL) ? node->data : NULL; +} + +amf0_data * amf0_array_get_at(amf0_data * data, uint32_t n) { + return (data != NULL) ? amf0_list_get_at(&data->list_data, n) : NULL; +} + +amf0_data * amf0_array_delete(amf0_data * data, amf0_node * node) { + return (data != NULL) ? amf0_list_delete(&data->list_data, node) : NULL; +} + +amf0_data * amf0_array_insert_before(amf0_data * data, amf0_node * node, amf0_data * element) { + return (data != NULL) ? amf0_list_insert_before(&data->list_data, node, element) : NULL; +} + +amf0_data * amf0_array_insert_after(amf0_data * data, amf0_node * node, amf0_data * element) { + return (data != NULL) ? amf0_list_insert_after(&data->list_data, node, element) : NULL; +} + +/* date functions */ +amf0_data * amf0_date_new(number64_t milliseconds, int16_t timezone) { + amf0_data * data = amf0_data_new(AMF0_TYPE_DATE); + if (data != NULL) { + data->date_data.milliseconds = milliseconds; + data->date_data.timezone = timezone; + } + return data; +} + +number64_t amf0_date_get_milliseconds(amf0_data * data) { + return (data != NULL) ? data->date_data.milliseconds : 0.0; +} + +int16_t amf0_date_get_timezone(amf0_data * data) { + return (data != NULL) ? data->date_data.timezone : 0; +} + +time_t amf0_date_to_time_t(amf0_data * data) { + return (time_t)((data != NULL) ? data->date_data.milliseconds / 1000 : 0); +} diff --git a/src/mod/endpoints/mod_rtmp/libamf/src/amf0.c b/src/mod/endpoints/mod_rtmp/libamf/src/amf0.c new file mode 100644 index 0000000000..67a9273e2b --- /dev/null +++ b/src/mod/endpoints/mod_rtmp/libamf/src/amf0.c @@ -0,0 +1,1014 @@ +#include + +#include "amf0.h" +#include "io.h" +#include "types.h" + +/* function common to all array types */ +static void amf0_list_init(amf0_list * list) { + if (list != NULL) { + list->size = 0; + list->first_element = NULL; + list->last_element = NULL; + } +} + +static amf0_data * amf0_list_push(amf0_list * list, amf0_data * data) { + amf0_node * node = (amf0_node*)malloc(sizeof(amf0_node)); + if (node != NULL) { + node->data = data; + node->next = NULL; + node->prev = NULL; + if (list->size == 0) { + list->first_element = node; + list->last_element = node; + } + else { + list->last_element->next = node; + node->prev = list->last_element; + list->last_element = node; + } + ++(list->size); + return data; + } + return NULL; +} + +static amf0_data * amf0_list_insert_before(amf0_list * list, amf0_node * node, amf0_data * data) { + if (node != NULL) { + amf0_node * new_node = (amf0_node*)malloc(sizeof(amf0_node)); + if (new_node != NULL) { + new_node->next = node; + new_node->prev = node->prev; + + if (node->prev != NULL) { + node->prev->next = new_node; + node->prev = new_node; + } + if (node == list->first_element) { + list->first_element = new_node; + } + ++(list->size); + new_node->data = data; + return data; + } + } + return NULL; +} + +static amf0_data * amf0_list_insert_after(amf0_list * list, amf0_node * node, amf0_data * data) { + if (node != NULL) { + amf0_node * new_node = (amf0_node*)malloc(sizeof(amf0_node)); + if (new_node != NULL) { + new_node->next = node->next; + new_node->prev = node; + + if (node->next != NULL) { + node->next->prev = new_node; + node->next = new_node; + } + if (node == list->last_element) { + list->last_element = new_node; + } + ++(list->size); + new_node->data = data; + return data; + } + } + return NULL; +} + +static amf0_data * amf0_list_delete(amf0_list * list, amf0_node * node) { + amf0_data * data = NULL; + if (node != NULL) { + if (node->next != NULL) { + node->next->prev = node->prev; + } + if (node->prev != NULL) { + node->prev->next = node->next; + } + if (node == list->first_element) { + list->first_element = node->next; + } + if (node == list->last_element) { + list->last_element = node->prev; + } + data = node->data; + free(node); + --(list->size); + } + return data; +} + +static amf0_data * amf0_list_get_at(amf0_list * list, uint32_t n) { + if (n < list->size) { + uint32_t i; + amf0_node * node = list->first_element; + for (i = 0; i < n; ++i) { + node = node->next; + } + return node->data; + } + return NULL; +} + +static amf0_data * amf0_list_pop(amf0_list * list) { + return amf0_list_delete(list, list->last_element); +} + +static amf0_node * amf0_list_first(amf0_list * list) { + return list->first_element; +} + +static amf0_node * amf0_list_last(amf0_list * list) { + return list->last_element; +} + +static void amf0_list_clear(amf0_list * list) { + amf0_node * tmp; + amf0_node * node = list->first_element; + while (node != NULL) { + amf0_data_free(node->data); + tmp = node; + node = node->next; + free(tmp); + } + list->size = 0; +} + +static amf0_list * amf0_list_clone(amf0_list * list, amf0_list * out_list) { + amf0_node * node; + node = list->first_element; + while (node != NULL) { + amf0_list_push(out_list, amf0_data_clone(node->data)); + node = node->next; + } + return out_list; +} + +/* allocate an AMF data object */ +amf0_data * amf0_data_new(uint8_t type) { + amf0_data * data = (amf0_data*)malloc(sizeof(amf0_data)); + if (data != NULL) { + data->type = type; + } + return data; +} + +/* read AMF data from buffer */ +amf0_data * amf0_data_buffer_read(uint8_t * buffer, size_t maxbytes) { + buffer_context ctxt; + ctxt.start_address = ctxt.current_address = buffer; + ctxt.buffer_size = maxbytes; + return amf0_data_read(buffer_read, &ctxt); +} + +/* write AMF data to buffer */ +size_t amf0_data_buffer_write(amf0_data * data, uint8_t * buffer, size_t maxbytes) { + buffer_context ctxt; + ctxt.start_address = ctxt.current_address = buffer; + ctxt.buffer_size = maxbytes; + return amf0_data_write(data, buffer_write, &ctxt); +} + +/* load AMF data from a file stream */ +amf0_data * amf0_data_file_read(FILE * stream) { + return amf0_data_read(file_read, stream); +} + +/* write AMF data into a file stream */ +size_t amf0_data_file_write(amf0_data * data, FILE * stream) { + return amf0_data_write(data, file_write, stream); +} + +/* read a number */ +static amf0_data * amf0_number_read(read_proc_t read_proc, void * user_data) { + number64_t val; + if (read_proc(&val, sizeof(number64_t), user_data) == sizeof(number64_t)) { + return amf0_number_new(swap_number64(val)); + } + return NULL; +} + +/* read a boolean */ +static amf0_data * amf0_boolean_read(read_proc_t read_proc, void * user_data) { + uint8_t val; + if (read_proc(&val, sizeof(uint8_t), user_data) == sizeof(uint8_t)) { + return amf0_boolean_new(val); + } + return NULL; +} + +/* read a string */ +static amf0_data * amf0_string_read(read_proc_t read_proc, void * user_data) { + uint16_t strsize; + uint8_t * buffer; + if (read_proc(&strsize, sizeof(uint16_t), user_data) == sizeof(uint16_t)) { + strsize = swap_uint16(strsize); + if (strsize > 0) { + buffer = (uint8_t*)calloc(strsize, sizeof(uint8_t)); + if (buffer != NULL && read_proc(buffer, strsize, user_data) == strsize) { + amf0_data * data = amf0_string_new(buffer, strsize); + free(buffer); + return data; + } + } + else { + return amf0_string_new(NULL, 0); + } + } + return NULL; +} + +/* read an object */ +static amf0_data * amf0_object_read(read_proc_t read_proc, void * user_data) { + amf0_data * data = amf0_object_new(); + if (data != NULL) { + amf0_data * name; + amf0_data * element; + while (1) { + name = amf0_string_read(read_proc, user_data); + if (name != NULL) { + element = amf0_data_read(read_proc, user_data); + if (element != NULL) { + if (amf0_object_add(data, (char *)amf0_string_get_uint8_ts(name), element) == NULL) { + amf0_data_free(name); + amf0_data_free(element); + amf0_data_free(data); + return NULL; + } + } + else { + amf0_data_free(name); + break; + } + } + else { + /* invalid name: error */ + amf0_data_free(data); + return NULL; + } + } + } + return data; +} + +/* read an associative array */ +static amf0_data * amf0_associative_array_read(read_proc_t read_proc, void * user_data) { + amf0_data * data = amf0_associative_array_new(); + if (data != NULL) { + amf0_data * name; + amf0_data * element; + uint32_t size; + if (read_proc(&size, sizeof(uint32_t), user_data) == sizeof(uint32_t)) { + /* we ignore the 32 bits array size marker */ + while(1) { + name = amf0_string_read(read_proc, user_data); + if (name != NULL) { + element = amf0_data_read(read_proc, user_data); + if (element != NULL) { + if (amf0_associative_array_add(data, (char *)amf0_string_get_uint8_ts(name), element) == NULL) { + amf0_data_free(name); + amf0_data_free(element); + amf0_data_free(data); + return NULL; + } + } + else { + amf0_data_free(name); + break; + } + } + else { + /* invalid name: error */ + amf0_data_free(data); + return NULL; + } + } + } + else { + amf0_data_free(data); + return NULL; + } + } + return data; +} + +/* read an array */ +static amf0_data * amf0_array_read(read_proc_t read_proc, void * user_data) { + size_t i; + amf0_data * element; + amf0_data * data = amf0_array_new(); + if (data != NULL) { + uint32_t array_size; + if (read_proc(&array_size, sizeof(uint32_t), user_data) == sizeof(uint32_t)) { + array_size = swap_uint32(array_size); + + for (i = 0; i < array_size; ++i) { + element = amf0_data_read(read_proc, user_data); + + if (element != NULL) { + if (amf0_array_push(data, element) == NULL) { + amf0_data_free(element); + amf0_data_free(data); + return NULL; + } + } + else { + amf0_data_free(data); + return NULL; + } + } + } + else { + amf0_data_free(data); + return NULL; + } + } + return data; +} + +/* read a date */ +static amf0_data * amf0_date_read(read_proc_t read_proc, void * user_data) { + number64_t milliseconds; + int16_t timezone; + if (read_proc(&milliseconds, sizeof(number64_t), user_data) == sizeof(number64_t) && + read_proc(&timezone, sizeof(int16_t), user_data) == sizeof(int16_t)) { + return amf0_date_new(swap_number64(milliseconds), swap_sint16(timezone)); + } + else { + return NULL; + } +} + +/* load AMF data from stream */ +amf0_data * amf0_data_read(read_proc_t read_proc, void * user_data) { + uint8_t type; + if (read_proc(&type, sizeof(uint8_t), user_data) == sizeof(uint8_t)) { + switch (type) { + case AMF0_TYPE_NUMBER: + return amf0_number_read(read_proc, user_data); + case AMF0_TYPE_BOOLEAN: + return amf0_boolean_read(read_proc, user_data); + case AMF0_TYPE_STRING: + return amf0_string_read(read_proc, user_data); + case AMF0_TYPE_OBJECT: + return amf0_object_read(read_proc, user_data); + case AMF0_TYPE_NULL: + return amf0_null_new(); + case AMF0_TYPE_UNDEFINED: + return amf0_undefined_new(); + /*case AMF0_TYPE_REFERENCE:*/ + case AMF0_TYPE_ECMA_ARRAY: + return amf0_associative_array_read(read_proc, user_data); + case AMF0_TYPE_STRICT_ARRAY: + return amf0_array_read(read_proc, user_data); + case AMF0_TYPE_DATE: + return amf0_date_read(read_proc, user_data); + /*case AMF0_TYPE_SIMPLEOBJECT:*/ + case AMF0_TYPE_XML_DOCUMENT: + case AMF0_TYPE_TYPED_OBJECT: + case AMF0_TYPE_OBJECT_END: + return NULL; /* end of composite object */ + default: + break; + } + } + return NULL; +} + +/* determines the size of the given AMF data */ +size_t amf0_data_size(amf0_data * data) { + size_t s = 0; + amf0_node * node; + if (data != NULL) { + s += sizeof(uint8_t); + switch (data->type) { + case AMF0_TYPE_NUMBER: + s += sizeof(number64_t); + break; + case AMF0_TYPE_BOOLEAN: + s += sizeof(uint8_t); + break; + case AMF0_TYPE_STRING: + s += sizeof(uint16_t) + (size_t)amf0_string_get_size(data); + break; + case AMF0_TYPE_OBJECT: + node = amf0_object_first(data); + while (node != NULL) { + s += sizeof(uint16_t) + (size_t)amf0_string_get_size(amf0_object_get_name(node)); + s += (size_t)amf0_data_size(amf0_object_get_data(node)); + node = amf0_object_next(node); + } + s += sizeof(uint16_t) + sizeof(uint8_t); + break; + case AMF0_TYPE_NULL: + case AMF0_TYPE_UNDEFINED: + break; + /*case AMF0_TYPE_REFERENCE:*/ + case AMF0_TYPE_ECMA_ARRAY: + s += sizeof(uint32_t); + node = amf0_associative_array_first(data); + while (node != NULL) { + s += sizeof(uint16_t) + (size_t)amf0_string_get_size(amf0_associative_array_get_name(node)); + s += (size_t)amf0_data_size(amf0_associative_array_get_data(node)); + node = amf0_associative_array_next(node); + } + s += sizeof(uint16_t) + sizeof(uint8_t); + break; + case AMF0_TYPE_STRICT_ARRAY: + s += sizeof(uint32_t); + node = amf0_array_first(data); + while (node != NULL) { + s += (size_t)amf0_data_size(amf0_array_get(node)); + node = amf0_array_next(node); + } + break; + case AMF0_TYPE_DATE: + s += sizeof(number64_t) + sizeof(int16_t); + break; + /*case AMF0_TYPE_SIMPLEOBJECT:*/ + case AMF0_TYPE_XML_DOCUMENT: + case AMF0_TYPE_TYPED_OBJECT: + case AMF0_TYPE_OBJECT_END: + break; /* end of composite object */ + default: + break; + } + } + return s; +} + +/* write a number */ +static size_t amf0_number_write(amf0_data * data, write_proc_t write_proc, void * user_data) { + number64_t n = swap_number64(data->u.number_data); + return write_proc(&n, sizeof(number64_t), user_data); +} + +/* write a boolean */ +static size_t amf0_boolean_write(amf0_data * data, write_proc_t write_proc, void * user_data) { + return write_proc(&(data->u.boolean_data), sizeof(uint8_t), user_data); +} + +/* write a string */ +static size_t amf0_string_write(amf0_data * data, write_proc_t write_proc, void * user_data) { + uint16_t s; + size_t w = 0; + + s = swap_uint16(data->u.string_data.size); + w = write_proc(&s, sizeof(uint16_t), user_data); + if (data->u.string_data.size > 0) { + w += write_proc(data->u.string_data.mbstr, (size_t)(data->u.string_data.size), user_data); + } + + return w; +} + +/* write an object */ +static size_t amf0_object_write(amf0_data * data, write_proc_t write_proc, void * user_data) { + amf0_node * node; + size_t w = 0; + uint16_t filler = swap_uint16(0); + uint8_t terminator = AMF0_TYPE_OBJECT_END; + + node = amf0_object_first(data); + while (node != NULL) { + w += amf0_string_write(amf0_object_get_name(node), write_proc, user_data); + w += amf0_data_write(amf0_object_get_data(node), write_proc, user_data); + node = amf0_object_next(node); + } + + /* empty string is the last element */ + w += write_proc(&filler, sizeof(uint16_t), user_data); + /* an object ends with 0x09 */ + w += write_proc(&terminator, sizeof(uint8_t), user_data); + + return w; +} + +/* write an associative array */ +static size_t amf0_associative_array_write(amf0_data * data, write_proc_t write_proc, void * user_data) { + amf0_node * node; + size_t w = 0; + uint32_t s; + uint16_t filler = swap_uint16(0); + uint8_t terminator = AMF0_TYPE_OBJECT_END; + + s = swap_uint32(data->u.list_data.size) / 2; + w += write_proc(&s, sizeof(uint32_t), user_data); + node = amf0_associative_array_first(data); + while (node != NULL) { + w += amf0_string_write(amf0_associative_array_get_name(node), write_proc, user_data); + w += amf0_data_write(amf0_associative_array_get_data(node), write_proc, user_data); + node = amf0_associative_array_next(node); + } + + /* empty string is the last element */ + w += write_proc(&filler, sizeof(uint16_t), user_data); + /* an object ends with 0x09 */ + w += write_proc(&terminator, sizeof(uint8_t), user_data); + + return w; +} + +/* write an array */ +static size_t amf0_array_write(amf0_data * data, write_proc_t write_proc, void * user_data) { + amf0_node * node; + size_t w = 0; + uint32_t s; + + s = swap_uint32(data->u.list_data.size); + w += write_proc(&s, sizeof(uint32_t), user_data); + node = amf0_array_first(data); + while (node != NULL) { + w += amf0_data_write(amf0_array_get(node), write_proc, user_data); + node = amf0_array_next(node); + } + + return w; +} + +/* write a date */ +static size_t amf0_date_write(amf0_data * data, write_proc_t write_proc, void * user_data) { + size_t w = 0; + number64_t milli; + int16_t tz; + + milli = swap_number64(data->u.date_data.milliseconds); + w += write_proc(&milli, sizeof(number64_t), user_data); + tz = swap_sint16(data->u.date_data.timezone); + w += write_proc(&tz, sizeof(int16_t), user_data); + + return w; +} + +/* write amf data to stream */ +size_t amf0_data_write(amf0_data * data, write_proc_t write_proc, void * user_data) { + size_t s = 0; + if (data != NULL) { + s += write_proc(&(data->type), sizeof(uint8_t), user_data); + switch (data->type) { + case AMF0_TYPE_NUMBER: + s += amf0_number_write(data, write_proc, user_data); + break; + case AMF0_TYPE_BOOLEAN: + s += amf0_boolean_write(data, write_proc, user_data); + break; + case AMF0_TYPE_STRING: + s += amf0_string_write(data, write_proc, user_data); + break; + case AMF0_TYPE_OBJECT: + s += amf0_object_write(data, write_proc, user_data); + break; + case AMF0_TYPE_NULL: + case AMF0_TYPE_UNDEFINED: + break; + /*case AMF0_TYPE_REFERENCE:*/ + case AMF0_TYPE_ECMA_ARRAY: + s += amf0_associative_array_write(data, write_proc, user_data); + break; + case AMF0_TYPE_STRICT_ARRAY: + s += amf0_array_write(data, write_proc, user_data); + break; + case AMF0_TYPE_DATE: + s += amf0_date_write(data, write_proc, user_data); + break; + /*case AMF0_TYPE_SIMPLEOBJECT:*/ + case AMF0_TYPE_XML_DOCUMENT: + case AMF0_TYPE_TYPED_OBJECT: + case AMF0_TYPE_OBJECT_END: + break; /* end of composite object */ + default: + break; + } + } + return s; +} + +/* data type */ +uint8_t amf0_data_get_type(amf0_data * data) { + return (data != NULL) ? data->type : AMF0_TYPE_NULL; +} + +/* clone AMF data */ +amf0_data * amf0_data_clone(amf0_data * data) { + /* we copy data recursively */ + if (data != NULL) { + switch (data->type) { + case AMF0_TYPE_NUMBER: return amf0_number_new(amf0_number_get_value(data)); + case AMF0_TYPE_BOOLEAN: return amf0_boolean_new(amf0_boolean_get_value(data)); + case AMF0_TYPE_STRING: + if (data->u.string_data.mbstr != NULL) { + return amf0_string_new((uint8_t *)strdup((char *)amf0_string_get_uint8_ts(data)), amf0_string_get_size(data)); + } + else { + return amf0_str(NULL); + } + case AMF0_TYPE_NULL: return NULL; + case AMF0_TYPE_UNDEFINED: return NULL; + /*case AMF0_TYPE_REFERENCE:*/ + case AMF0_TYPE_OBJECT: + case AMF0_TYPE_ECMA_ARRAY: + case AMF0_TYPE_STRICT_ARRAY: + { + amf0_data * d = amf0_data_new(data->type); + if (d != NULL) { + amf0_list_init(&d->u.list_data); + amf0_list_clone(&data->u.list_data, &d->u.list_data); + } + return d; + } + case AMF0_TYPE_DATE: return amf0_date_new(amf0_date_get_milliseconds(data), amf0_date_get_timezone(data)); + /*case AMF0_TYPE_SIMPLEOBJECT:*/ + case AMF0_TYPE_XML_DOCUMENT: return NULL; + case AMF0_TYPE_TYPED_OBJECT: return NULL; + } + } + return NULL; +} + +/* free AMF data */ +void amf0_data_free(amf0_data * data) { + if (data != NULL) { + switch (data->type) { + case AMF0_TYPE_NUMBER: break; + case AMF0_TYPE_BOOLEAN: break; + case AMF0_TYPE_STRING: + if (data->u.string_data.mbstr != NULL) { + free(data->u.string_data.mbstr); + } break; + case AMF0_TYPE_NULL: break; + case AMF0_TYPE_UNDEFINED: break; + /*case AMF0_TYPE_REFERENCE:*/ + case AMF0_TYPE_OBJECT: + case AMF0_TYPE_ECMA_ARRAY: + case AMF0_TYPE_STRICT_ARRAY: amf0_list_clear(&data->u.list_data); break; + case AMF0_TYPE_DATE: break; + /*case AMF0_TYPE_SIMPLEOBJECT:*/ + case AMF0_TYPE_XML_DOCUMENT: break; + case AMF0_TYPE_TYPED_OBJECT: break; + default: break; + } + free(data); + } +} + +/* dump AMF data into a stream as text */ +void amf0_data_dump(FILE * stream, amf0_data * data, int indent_level) { + if (data != NULL) { + amf0_node * node; + time_t time; + struct tm * t; + char datestr[128]; + switch (data->type) { + case AMF0_TYPE_NUMBER: + fprintf(stream, "%.12g", (double)data->u.number_data); + break; + case AMF0_TYPE_BOOLEAN: + fprintf(stream, "%s", (data->u.boolean_data) ? "true" : "false"); + break; + case AMF0_TYPE_STRING: + fprintf(stream, "\'%.*s\'", data->u.string_data.size, data->u.string_data.mbstr); + break; + case AMF0_TYPE_OBJECT: + node = amf0_object_first(data); + fprintf(stream, "{\n"); + while (node != NULL) { + fprintf(stream, "%*s", (indent_level+1)*4, ""); + amf0_data_dump(stream, amf0_object_get_name(node), indent_level+1); + fprintf(stream, ": "); + amf0_data_dump(stream, amf0_object_get_data(node), indent_level+1); + node = amf0_object_next(node); + fprintf(stream, "\n"); + } + fprintf(stream, "%*s", indent_level*4 + 1, "}"); + break; + case AMF0_TYPE_NULL: + fprintf(stream, "null"); + break; + case AMF0_TYPE_UNDEFINED: + fprintf(stream, "undefined"); + break; + /*case AMF0_TYPE_REFERENCE:*/ + case AMF0_TYPE_ECMA_ARRAY: + node = amf0_associative_array_first(data); + fprintf(stream, "{\n"); + while (node != NULL) { + fprintf(stream, "%*s", (indent_level+1)*4, ""); + amf0_data_dump(stream, amf0_associative_array_get_name(node), indent_level+1); + fprintf(stream, " => "); + amf0_data_dump(stream, amf0_associative_array_get_data(node), indent_level+1); + node = amf0_associative_array_next(node); + fprintf(stream, "\n"); + } + fprintf(stream, "%*s", indent_level*4 + 1, "}"); + break; + case AMF0_TYPE_STRICT_ARRAY: + node = amf0_array_first(data); + fprintf(stream, "[\n"); + while (node != NULL) { + fprintf(stream, "%*s", (indent_level+1)*4, ""); + amf0_data_dump(stream, node->data, indent_level+1); + node = amf0_array_next(node); + fprintf(stream, "\n"); + } + fprintf(stream, "%*s", indent_level*4 + 1, "]"); + break; + case AMF0_TYPE_DATE: + time = amf0_date_to_time_t(data); + tzset(); + t = localtime(&time); + strftime(datestr, sizeof(datestr), "%a, %d %b %Y %H:%M:%S %z", t); + fprintf(stream, "%s", datestr); + break; + /*case AMF0_TYPE_SIMPLEOBJECT:*/ + case AMF0_TYPE_XML_DOCUMENT: break; + case AMF0_TYPE_TYPED_OBJECT: break; + default: break; + } + } +} + +/* number functions */ +amf0_data * amf0_number_new(number64_t value) { + amf0_data * data = amf0_data_new(AMF0_TYPE_NUMBER); + if (data != NULL) { + data->u.number_data = value; + } + return data; +} + +number64_t amf0_number_get_value(amf0_data * data) { + return (data != NULL) ? data->u.number_data : 0; +} + +void amf0_number_set_value(amf0_data * data, number64_t value) { + if (data != NULL) { + data->u.number_data = value; + } +} + +/* boolean functions */ +amf0_data * amf0_boolean_new(uint8_t value) { + amf0_data * data = amf0_data_new(AMF0_TYPE_BOOLEAN); + if (data != NULL) { + data->u.boolean_data = value; + } + return data; +} + +uint8_t amf0_boolean_get_value(amf0_data * data) { + return (data != NULL) ? data->u.boolean_data : 0; +} + +void amf0_boolean_set_value(amf0_data * data, uint8_t value) { + if (data != NULL) { + data->u.boolean_data = value; + } +} + +/* string functions */ +amf0_data * amf0_string_new(uint8_t * str, uint16_t size) { + amf0_data * data = amf0_data_new(AMF0_TYPE_STRING); + if (data != NULL) { + data->u.string_data.size = size; + data->u.string_data.mbstr = (uint8_t*)calloc(size+1, sizeof(uint8_t)); + if (data->u.string_data.mbstr != NULL) { + if (size > 0) { + memcpy(data->u.string_data.mbstr, str, size); + } + } + else { + amf0_data_free(data); + return NULL; + } + } + return data; +} + +amf0_data * amf0_str(const char * str) { + return amf0_string_new((uint8_t *)str, (uint16_t)(str != NULL ? strlen(str) : 0)); +} + +uint16_t amf0_string_get_size(amf0_data * data) { + return (data != NULL) ? data->u.string_data.size : 0; +} + +uint8_t * amf0_string_get_uint8_ts(amf0_data * data) { + return (data != NULL) ? data->u.string_data.mbstr : NULL; +} + +/* object functions */ +amf0_data * amf0_object_new(void) { + amf0_data * data = amf0_data_new(AMF0_TYPE_OBJECT); + if (data != NULL) { + amf0_list_init(&data->u.list_data); + } + return data; +} + +uint32_t amf0_object_size(amf0_data * data) { + return (data != NULL) ? data->u.list_data.size / 2 : 0; +} + +amf0_data * amf0_object_add(amf0_data * data, const char * name, amf0_data * element) { + if (data != NULL) { + if (amf0_list_push(&data->u.list_data, amf0_str(name)) != NULL) { + if (amf0_list_push(&data->u.list_data, element) != NULL) { + return element; + } + else { + amf0_data_free(amf0_list_pop(&data->u.list_data)); + } + } + } + return NULL; +} + +amf0_data * amf0_object_get(amf0_data * data, const char * name) { + if (data != NULL) { + amf0_node * node = amf0_list_first(&(data->u.list_data)); + while (node != NULL) { + if (strncmp((char*)(node->data->u.string_data.mbstr), name, (size_t)(node->data->u.string_data.size)) == 0) { + node = node->next; + return (node != NULL) ? node->data : NULL; + } + /* we have to skip the element data to reach the next name */ + node = node->next->next; + } + } + return NULL; +} + +amf0_data * amf0_object_set(amf0_data * data, const char * name, amf0_data * element) { + if (data != NULL) { + amf0_node * node = amf0_list_first(&(data->u.list_data)); + while (node != NULL) { + if (strncmp((char*)(node->data->u.string_data.mbstr), name, (size_t)(node->data->u.string_data.size)) == 0) { + node = node->next; + if (node != NULL && node->data != NULL) { + amf0_data_free(node->data); + node->data = element; + return element; + } + } + /* we have to skip the element data to reach the next name */ + node = node->next->next; + } + } + return NULL; +} + +amf0_data * amf0_object_delete(amf0_data * data, const char * name) { + if (data != NULL) { + amf0_node * node = amf0_list_first(&data->u.list_data); + while (node != NULL) { + node = node->next; + if (strncmp((char*)(node->data->u.string_data.mbstr), name, (size_t)(node->data->u.string_data.size)) == 0) { + amf0_node * data_node = node->next; + amf0_data_free(amf0_list_delete(&data->u.list_data, node)); + return amf0_list_delete(&data->u.list_data, data_node); + } + else { + node = node->next; + } + } + } + return NULL; +} + +amf0_node * amf0_object_first(amf0_data * data) { + return (data != NULL) ? amf0_list_first(&data->u.list_data) : NULL; +} + +amf0_node * amf0_object_last(amf0_data * data) { + if (data != NULL) { + amf0_node * node = amf0_list_last(&data->u.list_data); + if (node != NULL) { + return node->prev; + } + } + return NULL; +} + +amf0_node * amf0_object_next(amf0_node * node) { + if (node != NULL) { + amf0_node * next = node->next; + if (next != NULL) { + return next->next; + } + } + return NULL; +} + +amf0_node * amf0_object_prev(amf0_node * node) { + if (node != NULL) { + amf0_node * prev = node->prev; + if (prev != NULL) { + return prev->prev; + } + } + return NULL; +} + +amf0_data * amf0_object_get_name(amf0_node * node) { + return (node != NULL) ? node->data : NULL; +} + +amf0_data * amf0_object_get_data(amf0_node * node) { + if (node != NULL) { + amf0_node * next = node->next; + if (next != NULL) { + return next->data; + } + } + return NULL; +} + +/* associative array functions */ +amf0_data * amf0_associative_array_new(void) { + amf0_data * data = amf0_data_new(AMF0_TYPE_ECMA_ARRAY); + if (data != NULL) { + amf0_list_init(&data->u.list_data); + } + return data; +} + +/* array functions */ +amf0_data * amf0_array_new(void) { + amf0_data * data = amf0_data_new(AMF0_TYPE_STRICT_ARRAY); + if (data != NULL) { + amf0_list_init(&data->u.list_data); + } + return data; +} + +uint32_t amf0_array_size(amf0_data * data) { + return (data != NULL) ? data->u.list_data.size : 0; +} + +amf0_data * amf0_array_push(amf0_data * data, amf0_data * element) { + return (data != NULL) ? amf0_list_push(&data->u.list_data, element) : NULL; +} + +amf0_data * amf0_array_pop(amf0_data * data) { + return (data != NULL) ? amf0_list_pop(&data->u.list_data) : NULL; +} + +amf0_node * amf0_array_first(amf0_data * data) { + return (data != NULL) ? amf0_list_first(&data->u.list_data) : NULL; +} + +amf0_node * amf0_array_last(amf0_data * data) { + return (data != NULL) ? amf0_list_last(&data->u.list_data) : NULL; +} + +amf0_node * amf0_array_next(amf0_node * node) { + return (node != NULL) ? node->next : NULL; +} + +amf0_node * amf0_array_prev(amf0_node * node) { + return (node != NULL) ? node->prev : NULL; +} + +amf0_data * amf0_array_get(amf0_node * node) { + return (node != NULL) ? node->data : NULL; +} + +amf0_data * amf0_array_get_at(amf0_data * data, uint32_t n) { + return (data != NULL) ? amf0_list_get_at(&data->u.list_data, n) : NULL; +} + +amf0_data * amf0_array_delete(amf0_data * data, amf0_node * node) { + return (data != NULL) ? amf0_list_delete(&data->u.list_data, node) : NULL; +} + +amf0_data * amf0_array_insert_before(amf0_data * data, amf0_node * node, amf0_data * element) { + return (data != NULL) ? amf0_list_insert_before(&data->u.list_data, node, element) : NULL; +} + +amf0_data * amf0_array_insert_after(amf0_data * data, amf0_node * node, amf0_data * element) { + return (data != NULL) ? amf0_list_insert_after(&data->u.list_data, node, element) : NULL; +} + +/* date functions */ +amf0_data * amf0_date_new(number64_t milliseconds, int16_t timezone) { + amf0_data * data = amf0_data_new(AMF0_TYPE_DATE); + if (data != NULL) { + data->u.date_data.milliseconds = milliseconds; + data->u.date_data.timezone = timezone; + } + return data; +} + +number64_t amf0_date_get_milliseconds(amf0_data * data) { + return (data != NULL) ? data->u.date_data.milliseconds : 0.0; +} + +int16_t amf0_date_get_timezone(amf0_data * data) { + return (data != NULL) ? data->u.date_data.timezone : 0; +} + +time_t amf0_date_to_time_t(amf0_data * data) { + return (time_t)((data != NULL) ? data->u.date_data.milliseconds / 1000 : 0); +} diff --git a/src/mod/endpoints/mod_rtmp/libamf/src/amf0.h b/src/mod/endpoints/mod_rtmp/libamf/src/amf0.h new file mode 100644 index 0000000000..9cb5b7f1c6 --- /dev/null +++ b/src/mod/endpoints/mod_rtmp/libamf/src/amf0.h @@ -0,0 +1,190 @@ +#ifndef __AMF0_H__ +#define __AMF0_H__ + +#include +#include + +#include "amf.h" + +/* AMF data types */ +#define AMF0_TYPE_NUMBER 0x00 +#define AMF0_TYPE_BOOLEAN 0x01 +#define AMF0_TYPE_STRING 0x02 +#define AMF0_TYPE_OBJECT 0x03 +#define AMF0_TYPE_MOVIECLIP 0x04 /* reserved, not supported */ +#define AMF0_TYPE_NULL 0x05 +#define AMF0_TYPE_UNDEFINED 0x06 +#define AMF0_TYPE_REFERENCE 0x07 +#define AMF0_TYPE_ECMA_ARRAY 0x08 +#define AMF0_TYPE_OBJECT_END 0x09 +#define AMF0_TYPE_STRICT_ARRAY 0x0A +#define AMF0_TYPE_DATE 0x0B +#define AMF0_TYPE_LONG_STRING 0x0C +#define AMF0_TYPE_UNSUPPORTED 0x0D +#define AMF0_TYPE_RECORDSET 0x0E /* reserved, not supported */ +#define AMF0_TYPE_XML_DOCUMENT 0x0F +#define AMF0_TYPE_TYPED_OBJECT 0x10 + +typedef struct __amf0_node * p_amf0_node; + +/* string type */ +typedef struct __amf0_string { + uint16_t size; + uint8_t * mbstr; +} amf0_string; + +/* array type */ +typedef struct __amf0_list { + uint32_t size; + p_amf0_node first_element; + p_amf0_node last_element; +} amf0_list; + +/* date type */ +typedef struct __amf0_date { + number64_t milliseconds; + int16_t timezone; +} amf0_date; + +/* XML string type */ +typedef struct __amf0_xmlstring { + uint32_t size; + uint8_t * mbstr; +} amf0_xmlstring; + +/* class type */ +typedef struct __amf0_class { + amf0_string name; + amf0_list elements; +} amf0_class; + +/* structure encapsulating the various AMF objects */ +typedef struct __amf0_data { + uint8_t type; + union { + number64_t number_data; + uint8_t boolean_data; + amf0_string string_data; + amf0_list list_data; + amf0_date date_data; + amf0_xmlstring xmlstring_data; + amf0_class class_data; + } u; +} amf0_data; + +/* node used in lists, relies on amf0_data */ +typedef struct __amf0_node { + amf0_data * data; + p_amf0_node prev; + p_amf0_node next; +} amf0_node; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* read AMF data */ +amf0_data * amf0_data_read(read_proc_t read_proc, void * user_data); + +/* write AMF data */ +size_t amf0_data_write(amf0_data * data, write_proc_t write_proc, void * user_data); + +/* generic functions */ + +/* allocate an AMF data object */ +amf0_data * amf0_data_new(uint8_t type); +/* load AMF data from buffer */ +amf0_data * amf0_data_buffer_read(uint8_t * buffer, size_t maxbytes); +/* load AMF data from stream */ +amf0_data * amf0_data_file_read(FILE * stream); +/* AMF data size */ +size_t amf0_data_size(amf0_data * data); +/* write encoded AMF data into a buffer */ +size_t amf0_data_buffer_write(amf0_data * data, uint8_t * buffer, size_t maxbytes); +/* write encoded AMF data into a stream */ +size_t amf0_data_file_write(amf0_data * data, FILE * stream); +/* get the type of AMF data */ +uint8_t amf0_data_get_type(amf0_data * data); +/* return a new copy of AMF data */ +amf0_data * amf0_data_clone(amf0_data * data); +/* release the memory of AMF data */ +void amf0_data_free(amf0_data * data); +/* dump AMF data into a stream as text */ +void amf0_data_dump(FILE * stream, amf0_data * data, int indent_level); + +/* number functions */ +amf0_data * amf0_number_new(number64_t value); +number64_t amf0_number_get_value(amf0_data * data); +void amf0_number_set_value(amf0_data * data, number64_t value); + +/* boolean functions */ +amf0_data * amf0_boolean_new(uint8_t value); +uint8_t amf0_boolean_get_value(amf0_data * data); +void amf0_boolean_set_value(amf0_data * data, uint8_t value); + +/* string functions */ +amf0_data * amf0_string_new(uint8_t * str, uint16_t size); +amf0_data * amf0_str(const char * str); +uint16_t amf0_string_get_size(amf0_data * data); +uint8_t * amf0_string_get_uint8_ts(amf0_data * data); + +/* object functions */ +amf0_data * amf0_object_new(void); +uint32_t amf0_object_size(amf0_data * data); +amf0_data * amf0_object_add(amf0_data * data, const char * name, amf0_data * element); +amf0_data * amf0_object_get(amf0_data * data, const char * name); +amf0_data * amf0_object_set(amf0_data * data, const char * name, amf0_data * element); +amf0_data * amf0_object_delete(amf0_data * data, const char * name); +amf0_node * amf0_object_first(amf0_data * data); +amf0_node * amf0_object_last(amf0_data * data); +amf0_node * amf0_object_next(amf0_node * node); +amf0_node * amf0_object_prev(amf0_node * node); +amf0_data * amf0_object_get_name(amf0_node * node); +amf0_data * amf0_object_get_data(amf0_node * node); + +/* null functions */ +#define amf0_null_new() amf0_data_new(AMF0_TYPE_NULL) + +/* undefined functions */ +#define amf0_undefined_new() amf0_data_new(AMF0_TYPE_UNDEFINED) + +/* associative array functions */ +amf0_data * amf0_associative_array_new(void); +#define amf0_associative_array_size(d) amf0_object_size(d) +#define amf0_associative_array_add(d, n, e) amf0_object_add(d, n, e) +#define amf0_associative_array_get(d, n) amf0_object_get(d, n) +#define amf0_associative_array_set(d, n, e) amf0_object_set(d, n, e) +#define amf0_associative_array_delete(d, n) amf0_object_delete(d, n) +#define amf0_associative_array_first(d) amf0_object_first(d) +#define amf0_associative_array_last(d) amf0_object_last(d) +#define amf0_associative_array_next(n) amf0_object_next(n) +#define amf0_associative_array_prev(n) amf0_object_prev(n) +#define amf0_associative_array_get_name(n) amf0_object_get_name(n) +#define amf0_associative_array_get_data(n) amf0_object_get_data(n) + +/* array functions */ +amf0_data * amf0_array_new(void); +uint32_t amf0_array_size(amf0_data * data); +amf0_data * amf0_array_push(amf0_data * data, amf0_data * element); +amf0_data * amf0_array_pop(amf0_data * data); +amf0_node * amf0_array_first(amf0_data * data); +amf0_node * amf0_array_last(amf0_data * data); +amf0_node * amf0_array_next(amf0_node * node); +amf0_node * amf0_array_prev(amf0_node * node); +amf0_data * amf0_array_get(amf0_node * node); +amf0_data * amf0_array_get_at(amf0_data * data, uint32_t n); +amf0_data * amf0_array_delete(amf0_data * data, amf0_node * node); +amf0_data * amf0_array_insert_before(amf0_data * data, amf0_node * node, amf0_data * element); +amf0_data * amf0_array_insert_after(amf0_data * data, amf0_node * node, amf0_data * element); + +/* date functions */ +amf0_data * amf0_date_new(number64_t milliseconds, int16_t timezone); +number64_t amf0_date_get_milliseconds(amf0_data * data); +int16_t amf0_date_get_timezone(amf0_data * data); +time_t amf0_date_to_time_t(amf0_data * data); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __AMF0_H__ */ diff --git a/src/mod/endpoints/mod_rtmp/libamf/src/amf3.h b/src/mod/endpoints/mod_rtmp/libamf/src/amf3.h new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/mod/endpoints/mod_rtmp/libamf/src/amf_list.c b/src/mod/endpoints/mod_rtmp/libamf/src/amf_list.c new file mode 100644 index 0000000000..47fb65f1e6 --- /dev/null +++ b/src/mod/endpoints/mod_rtmp/libamf/src/amf_list.c @@ -0,0 +1,130 @@ +/* function common to all array types */ +static void amf_list_init(amf_list * list) { + if (list != NULL) { + list->size = 0; + list->first_element = NULL; + list->last_element = NULL; + } +} + +static amf_data * amf_list_push(amf_list * list, amf_data * data) { + amf_node * node = (amf_node*)malloc(sizeof(amf_node)); + if (node != NULL) { + node->data = data; + node->next = NULL; + node->prev = NULL; + if (list->size == 0) { + list->first_element = node; + list->last_element = node; + } + else { + list->last_element->next = node; + node->prev = list->last_element; + list->last_element = node; + } + ++(list->size); + return data; + } + return NULL; +} + +static amf_data * amf_list_insert_before(amf_list * list, amf_node * node, amf_data * data) { + if (node != NULL) { + amf_node * new_node = (amf_node*)malloc(sizeof(amf_node)); + if (new_node != NULL) { + new_node->next = node; + new_node->prev = node->prev; + + if (node->prev != NULL) { + node->prev->next = new_node; + node->prev = new_node; + } + if (node == list->first_element) { + list->first_element = new_node; + } + ++(list->size); + new_node->data = data; + return data; + } + } + return NULL; +} + +static amf_data * amf_list_insert_after(amf_list * list, amf_node * node, amf_data * data) { + if (node != NULL) { + amf_node * new_node = (amf_node*)malloc(sizeof(amf_node)); + if (new_node != NULL) { + new_node->next = node->next; + new_node->prev = node; + + if (node->next != NULL) { + node->next->prev = new_node; + node->next = new_node; + } + if (node == list->last_element) { + list->last_element = new_node; + } + ++(list->size); + new_node->data = data; + return data; + } + } + return NULL; +} + +static amf_data * amf_list_delete(amf_list * list, amf_node * node) { + amf_data * data = NULL; + if (node != NULL) { + if (node->next != NULL) { + node->next->prev = node->prev; + } + if (node->prev != NULL) { + node->prev->next = node->next; + } + if (node == list->first_element) { + list->first_element = node->next; + } + if (node == list->last_element) { + list->last_element = node->prev; + } + data = node->data; + free(node); + --(list->size); + } + return data; +} + +static amf_data * amf_list_get_at(amf_list * list, uint32 n) { + if (n < list->size) { + uint32 i; + amf_node * node = list->first_element; + for (i = 0; i < n; ++i) { + node = node->next; + } + return node->data; + } + return NULL; +} + +static amf_data * amf_list_pop(amf_list * list) { + return amf_list_delete(list, list->last_element); +} + +static amf_node * amf_list_first(amf_list * list) { + return list->first_element; +} + +static amf_node * amf_list_last(amf_list * list) { + return list->last_element; +} + +static void amf_list_clear(amf_list * list) { + amf_node * node = list->first_element; + while (node != NULL) { + amf_data_free(node->data); + amf_node * tmp = node; + node = node->next; + free(tmp); + } + list->size = 0; +} diff --git a/src/mod/endpoints/mod_rtmp/libamf/src/amf_list.h b/src/mod/endpoints/mod_rtmp/libamf/src/amf_list.h new file mode 100644 index 0000000000..b22b7eee73 --- /dev/null +++ b/src/mod/endpoints/mod_rtmp/libamf/src/amf_list.h @@ -0,0 +1,5 @@ +typedef struct __amf_list { + uint32 size; + p_amf_node first_element; + p_amf_node last_element; +} amf_list; \ No newline at end of file diff --git a/src/mod/endpoints/mod_rtmp/libamf/src/hash.c b/src/mod/endpoints/mod_rtmp/libamf/src/hash.c new file mode 100644 index 0000000000..eaea511b14 --- /dev/null +++ b/src/mod/endpoints/mod_rtmp/libamf/src/hash.c @@ -0,0 +1,312 @@ +#include +#include + +#include "hash.h" + +#define assert(x) + +/*static void *malloc_and_zero(int n){ + void *p = malloc(n); + if( p ){ + memset(p, 0, n); + } + return p; +}*/ + +/** + Create a hash table +*/ +Hash * HashCreate(char copyKey) { + return HashCreateAlloc(copyKey, malloc, free); +} + +Hash * HashCreateAlloc(char copyKey, void *(*xMalloc)(size_t), void (*xFree)(void *)) { + Hash * pHash = (Hash*)xMalloc(sizeof(Hash)); + if (pHash != NULL) { + HashInit(pHash, copyKey, xMalloc, xFree); + return pHash; + } + else { + return 0; + } +} + +/** + Erase the hash table +*/ +void HashFree(Hash* pHash) { + pHash->xFree(pHash); +} + +/* + insert a string key element +*/ +void * HashInsertSz(Hash *pH, const char *pKey, void *data) { + return HashInsert(pH, pKey, (int) strlen(pKey)+1, data); +} + +/* + find a string key element +*/ +void * HashFindSz(const Hash* pH, const char *pKey) { + return HashFind(pH, pKey, (int) strlen(pKey)+1); +} + +/* Turn bulk memory into a hash table object by initializing the +** fields of the Hash structure. +** +** "pNew" is a pointer to the hash table that is to be initialized. +** "copyKey" is true if the hash table should make its own private +** copy of keys and false if it should just use the supplied pointer. +*/ +void HashInit(Hash* pNew, char copyKey, void *(*xMalloc)(size_t), void (*xFree)(void *)){ + assert( pNew!=0 ); + pNew->copyKey = copyKey; + pNew->first = 0; + pNew->count = 0; + pNew->htsize = 0; + pNew->ht = 0; + pNew->xMalloc = xMalloc; + pNew->xFree = xFree; +} + +/* Remove all entries from a hash table. Reclaim all memory. +** Call this routine to delete a hash table or to reset a hash table +** to the empty state. +*/ +void HashClear(Hash *pH){ + HashElem *elem; /* For looping over all elements of the table */ + + assert( pH!=0 ); + elem = pH->first; + pH->first = 0; + if( pH->ht ) pH->xFree(pH->ht); + pH->ht = 0; + pH->htsize = 0; + while( elem ){ + HashElem *next_elem = elem->next; + if( pH->copyKey && elem->pKey ){ + pH->xFree(elem->pKey); + } + pH->xFree(elem); + elem = next_elem; + } + pH->count = 0; +} + +/* +** Hash and comparison functions +*/ +static int binHash(const void *pKey, int nKey){ + int h = 0; + const char *z = (const char *)pKey; + while( nKey-- > 0 ){ + h = (h<<3) ^ h ^ *(z++); + } + return h & 0x7fffffff; +} + +static int binCompare(const void *pKey1, int n1, const void *pKey2, int n2){ + if( n1!=n2 ) return 1; + return memcmp(pKey1,pKey2,n1); +} + +/* Link an element into the hash table +*/ +static void insertElement( + Hash *pH, /* The complete hash table */ + struct _ht *pEntry, /* The entry into which pNew is inserted */ + HashElem *pNew /* The element to be inserted */ +){ + HashElem *pHead; /* First element already in pEntry */ + pHead = pEntry->chain; + if( pHead ){ + pNew->next = pHead; + pNew->prev = pHead->prev; + if( pHead->prev ){ pHead->prev->next = pNew; } + else { pH->first = pNew; } + pHead->prev = pNew; + }else{ + pNew->next = pH->first; + if( pH->first ){ pH->first->prev = pNew; } + pNew->prev = 0; + pH->first = pNew; + } + pEntry->count++; + pEntry->chain = pNew; +} + + +/* Resize the hash table so that it cantains "new_size" buckets. +** "new_size" must be a power of 2. The hash table might fail +** to resize if malloc fails. +*/ +static void rehash(Hash *pH, int new_size){ + struct _ht *new_ht; /* The new hash table */ + HashElem *elem, *next_elem; /* For looping over existing elements */ + + assert( (new_size & (new_size-1))==0 ); + new_ht = (struct _ht *)pH->xMalloc( new_size*sizeof(struct _ht) ); + if( new_ht==0 ) return; + if( pH->ht ) pH->xFree(pH->ht); + pH->ht = new_ht; + pH->htsize = new_size; + for(elem=pH->first, pH->first=0; elem; elem = next_elem){ + int h = binHash(elem->pKey, elem->nKey) & (new_size-1); + next_elem = elem->next; + insertElement(pH, &new_ht[h], elem); + } +} + +/* This function (for internal use only) locates an element in an +** hash table that matches the given key. The hash for this key has +** already been computed and is passed as the 4th parameter. +*/ +static HashElem *findElementGivenHash( + const Hash *pH, /* The pH to be searched */ + const void *pKey, /* The key we are searching for */ + int nKey, + int h /* The hash for this key. */ +){ + HashElem *elem; /* Used to loop thru the element list */ + int count; /* Number of elements left to test */ + + if( pH->ht ){ + struct _ht *pEntry = &pH->ht[h]; + elem = pEntry->chain; + count = pEntry->count; + while( count-- && elem ){ + if( binCompare(elem->pKey,elem->nKey,pKey,nKey)==0 ){ + return elem; + } + elem = elem->next; + } + } + return 0; +} + +/* Remove a single entry from the hash table given a pointer to that +** element and a hash on the element's key. +*/ +static void removeElementGivenHash( + Hash *pH, /* The pH containing "elem" */ + HashElem* elem, /* The element to be removed from the pH */ + int h /* Hash value for the element */ +){ + struct _ht *pEntry; + if( elem->prev ){ + elem->prev->next = elem->next; + }else{ + pH->first = elem->next; + } + if( elem->next ){ + elem->next->prev = elem->prev; + } + pEntry = &pH->ht[h]; + if( pEntry->chain==elem ){ + pEntry->chain = elem->next; + } + pEntry->count--; + if( pEntry->count<=0 ){ + pEntry->chain = 0; + } + if( pH->copyKey && elem->pKey ){ + pH->xFree(elem->pKey); + } + pH->xFree( elem ); + pH->count--; + if( pH->count<=0 ){ + assert( pH->first==0 ); + assert( pH->count==0 ); + HashClear(pH); + } +} + +/* Attempt to locate an element of the hash table pH with a key +** that matches pKey,nKey. Return the data for this element if it is +** found, or NULL if there is no match. +*/ +void * HashFind(const Hash *pH, const void *pKey, int nKey){ + int h; /* A hash on key */ + HashElem *elem; /* The element that matches key */ + + if( pH==0 || pH->ht==0 ) return 0; + h = binHash(pKey,nKey); + assert( (pH->htsize & (pH->htsize-1))==0 ); + elem = findElementGivenHash(pH,pKey,nKey, h & (pH->htsize-1)); + return elem ? elem->data : 0; +} + +/* Insert an element into the hash table pH. The key is pKey,nKey +** and the data is "data". +** +** If no element exists with a matching key, then a new +** element is created. A copy of the key is made if the copyKey +** flag is set. NULL is returned. +** +** If another element already exists with the same key, then the +** new data replaces the old data and the old data is returned. +** The key is not copied in this instance. If a malloc fails, then +** the new data is returned and the hash table is unchanged. +** +** If the "data" parameter to this function is NULL, then the +** element corresponding to "key" is removed from the hash table. +*/ +void * HashInsert( + Hash *pH, /* The hash table to insert into */ + const void *pKey, /* The key */ + int nKey, /* Number of bytes in the key */ + void *data /* The data */ +){ + int hraw; /* Raw hash value of the key */ + int h; /* the hash of the key modulo hash table size */ + HashElem *elem; /* Used to loop thru the element list */ + HashElem *new_elem; /* New element added to the pH */ + + assert( pH!=0 ); + hraw = binHash(pKey, nKey); + assert( (pH->htsize & (pH->htsize-1))==0 ); + h = hraw & (pH->htsize-1); + elem = findElementGivenHash(pH,pKey,nKey,h); + if( elem ){ + void *old_data = elem->data; + if( data==0 ){ + removeElementGivenHash(pH,elem,h); + }else{ + elem->data = data; + } + return old_data; + } + if( data==0 ) return 0; + new_elem = (HashElem*)pH->xMalloc( sizeof(HashElem) ); + if( new_elem==0 ) return data; + if( pH->copyKey && pKey!=0 ){ + new_elem->pKey = pH->xMalloc( nKey ); + if( new_elem->pKey==0 ){ + pH->xFree(new_elem); + return data; + } + memcpy((void*)new_elem->pKey, pKey, nKey); + }else{ + new_elem->pKey = (void*)pKey; + } + new_elem->nKey = nKey; + pH->count++; + if( pH->htsize==0 ){ + rehash(pH,8); + if( pH->htsize==0 ){ + pH->count = 0; + pH->xFree(new_elem); + return data; + } + } + if( pH->count > pH->htsize ){ + rehash(pH,pH->htsize*2); + } + assert( pH->htsize>0 ); + assert( (pH->htsize & (pH->htsize-1))==0 ); + h = hraw & (pH->htsize-1); + insertElement(pH, &pH->ht[h], new_elem); + new_elem->data = data; + return 0; +} diff --git a/src/mod/endpoints/mod_rtmp/libamf/src/hash.h b/src/mod/endpoints/mod_rtmp/libamf/src/hash.h new file mode 100644 index 0000000000..18ea2f88bf --- /dev/null +++ b/src/mod/endpoints/mod_rtmp/libamf/src/hash.h @@ -0,0 +1,107 @@ +#ifndef _HASH_H_ +#define _HASH_H_ + +/* Forward declarations of structures. */ +typedef struct Hash Hash; +typedef struct HashElem HashElem; + +/* A complete hash table is an instance of the following structure. +** The internals of this structure are intended to be opaque -- client +** code should not attempt to access or modify the fields of this structure +** directly. Change this structure only by using the routines below. +** However, many of the "procedures" and "functions" for modifying and +** accessing this structure are really macros, so we can't really make +** this structure opaque. +*/ +struct Hash { + char copyKey; /* True if copy of key made on insert */ + int count; /* Number of entries in this table */ + HashElem *first; /* The first element of the array */ + void *(*xMalloc)(size_t); /* malloc() function to use */ + void (*xFree)(void *); /* free() function to use */ + int htsize; /* Number of buckets in the hash table */ + struct _ht { /* the hash table */ + int count; /* Number of entries with this hash */ + HashElem *chain; /* Pointer to first entry with this hash */ + } *ht; +}; + +/* Each element in the hash table is an instance of the following +** structure. All elements are stored on a single doubly-linked list. +** +** Again, this structure is intended to be opaque, but it can't really +** be opaque because it is used by macros. +*/ +struct HashElem { + HashElem *next, *prev; /* Next and previous elements in the table */ + void *data; /* Data associated with this element */ + void *pKey; int nKey; /* Key associated with this element */ +}; + +/* +** Access routines. To delete, insert a NULL pointer. +*/ +Hash * HashCreate(char copyKey); +Hash * HashCreateAlloc(char copyKey, void *(*xMalloc)(size_t), void (*xFree)(void *)); + +void HashFree(Hash*); + +void HashInit(Hash*, char copyKey, void *(*xMalloc)(size_t), void (*xFree)(void *)); +void * HashInsert(Hash*, const void *pKey, int nKey, void *pData); +void * HashFind(const Hash*, const void *pKey, int nKey); +void HashClear(Hash*); + +void * HashInsertSz(Hash*, const char *pKey, void *pData); +void * HashFindSz(const Hash*, const char *pKey); + +/* + Element deletion macro +*/ +#define HashDelete(H, K, N) (HashInsert(H, K, N, 0)) + +/* +** Macros for looping over all elements of a hash table. The idiom is +** like this: +** +** Hash h; +** HashElem *p; +** ... +** for(p=HashFirst(&h); p; p=HashNext(p)){ +** SomeStructure *pData = HashData(p); +** // do something with pData +** } +*/ +#define HashFirst(H) ((H)->first) +#define HashNext(E) ((E)->next) +#define HashData(E) ((E)->data) +#define HashKey(E) ((E)->pKey) +#define HashKeysize(E) ((E)->nKey) + +/* +** Number of entries in a hash table +*/ +#define HashCount(H) ((H)->count) + + +/* + more macros +*/ +typedef struct Hash* hash_table; +typedef struct HashElem* hash_elem; + +#define hash_create() (HashCreate(1)) +#define hash_insert(H, K, V) (HashInsertSz(H, K, V)) +#define hash_find(H, K) (HashFindSz(H, K)) +#define hash_delete(H, K) (HashInsertSz(H, K, 0)) +#define hash_clear(H) (HashClear(H)) +#define hash_free(H) (HashFree(H)) + +#define hash_first(H) ((H)->first) +#define hash_next(E) ((E)->next) +#define hash_data(E) ((E)->data) +#define hash_key(E) ((E)->pKey) +#define hash_keysize(E) ((E)->nKey) + +#define hash_count(H) ((H)->count) + +#endif /* _HASH_H_ */ diff --git a/src/mod/endpoints/mod_rtmp/libamf/src/io.c b/src/mod/endpoints/mod_rtmp/libamf/src/io.c new file mode 100644 index 0000000000..9b04631429 --- /dev/null +++ b/src/mod/endpoints/mod_rtmp/libamf/src/io.c @@ -0,0 +1,44 @@ +#include +#include + +#include "io.h" + +/* callback function to mimic fread using a memory buffer */ +size_t buffer_read(void * out_buffer, size_t size, void * user_data) { + buffer_context * ctxt = (buffer_context *)user_data; + if (ctxt->current_address >= ctxt->start_address && + ctxt->current_address + size <= ctxt->start_address + ctxt->buffer_size) { + + memcpy(out_buffer, ctxt->current_address, size); + ctxt->current_address += size; + return size; + } + else { + return 0; + } +} + +/* callback function to mimic fwrite using a memory buffer */ +size_t buffer_write(const void * in_buffer, size_t size, void * user_data) { + buffer_context * ctxt = (buffer_context *)user_data; + if (ctxt->current_address >= ctxt->start_address && + ctxt->current_address + size <= ctxt->start_address + ctxt->buffer_size) { + + memcpy(ctxt->current_address, in_buffer, size); + ctxt->current_address += size; + return size; + } + else { + return 0; + } +} + +/* callback function to read data from a file stream */ +size_t file_read(void * out_buffer, size_t size, void * user_data) { + return fread(out_buffer, sizeof(uint8_t), size, (FILE *)user_data); +} + +/* callback function to write data to a file stream */ +size_t file_write(const void * in_buffer, size_t size, void * user_data) { + return fwrite(in_buffer, sizeof(uint8_t), size, (FILE *)user_data); +} diff --git a/src/mod/endpoints/mod_rtmp/libamf/src/io.h b/src/mod/endpoints/mod_rtmp/libamf/src/io.h new file mode 100644 index 0000000000..5d284e5026 --- /dev/null +++ b/src/mod/endpoints/mod_rtmp/libamf/src/io.h @@ -0,0 +1,33 @@ +#ifndef __IO_H__ +#define __IO_H__ + +#include "amf.h" + +/* structure used to mimic a stream with a memory buffer */ +typedef struct __buffer_context { + uint8_t * start_address; + uint8_t * current_address; + size_t buffer_size; +} buffer_context; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* callback function to mimic fread using a memory buffer */ +size_t buffer_read(void * out_buffer, size_t size, void * user_data); + +/* callback function to mimic fwrite using a memory buffer */ +size_t buffer_write(const void * in_buffer, size_t size, void * user_data); + +/* callback function to read data from a file stream */ +size_t file_read(void * out_buffer, size_t size, void * user_data); + +/* callback function to write data to a file stream */ +size_t file_write(const void * in_buffer, size_t size, void * user_data); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __IO_H__ */ diff --git a/src/mod/endpoints/mod_rtmp/libamf/src/ptrarray.c b/src/mod/endpoints/mod_rtmp/libamf/src/ptrarray.c new file mode 100644 index 0000000000..ac73f663bd --- /dev/null +++ b/src/mod/endpoints/mod_rtmp/libamf/src/ptrarray.c @@ -0,0 +1,172 @@ +#include "ptrarray.h" +#include + +/** + Customize default capacity +*/ +#ifndef PTRARRAY_DEFAULT_CAPACITY +# define PTRARRAY_DEFAULT_CAPACITY 5 +#endif + +/** + Enable array security checking +*/ +/* #define PTRARRAY_SECURITY_CHECKS */ + +/** + Customize memory allocation routines +*/ +#ifndef PTRARRAY_MALLOC +# define PTRARRAY_MALLOC malloc +#endif + +#ifndef PTRARRAY_FREE +# define PTRARRAY_FREE free +#endif + +#ifndef PTRARRAY_REALLOC +# define PTRARRAY_REALLOC realloc +#endif + +/** + This function doubles the current capacity + of the given dynamic array. + */ +static int ptrarray_grow(ptrarray * array) { + void * new_mem; + size_t new_capacity; +#ifdef PTRARRAY_SECURITY_CHECKS + if (array == NULL) + return; +#endif + new_capacity = array->capacity * 2; + new_mem = PTRARRAY_REALLOC(array->data, new_capacity * sizeof(void*)); + if (new_mem != NULL) { + array->data = new_mem; + array->capacity = new_capacity; + return 1; + } + else { + return 0; + } +} + +void ptrarray_init(ptrarray * array, size_t initial_capacity, data_free_proc free_proc) { + if (free_proc == NULL) { + free_proc = PTRARRAY_FREE; + } + array->data_free = free_proc; + if (initial_capacity <= 0) { + initial_capacity = PTRARRAY_DEFAULT_CAPACITY; + } + array->capacity = initial_capacity; + array->data = PTRARRAY_MALLOC(initial_capacity * sizeof(void*)); + array->size = 0; +} + +/*size_t ptrarray_capacity(ptrarray * array) { + return array->capacity; +}*/ + +/*size_t ptrarray_size(ptrarray * array) { + return array->size; +}*/ + +/*int ptrarray_empty(ptrarray * array) { + return !((array)->size); +}*/ + +void ptrarray_reserve(ptrarray * array, size_t new_capacity) { + void * new_mem; +#ifdef PTRARRAY_SECURITY_CHECKS + if (array == NULL) + return; +#endif + if (new_capacity > array->capacity) { + new_mem = PTRARRAY_REALLOC(array->data, new_capacity * sizeof(void*)); + if (new_mem != NULL) { + array->data = new_mem; + array->capacity = new_capacity; + } + } + else if (new_capacity < array->capacity) { + new_capacity = (new_capacity < array->size) ? array->size : new_capacity; + new_capacity = (new_capacity < PTRARRAY_DEFAULT_CAPACITY) ? PTRARRAY_DEFAULT_CAPACITY : new_capacity; + new_mem = PTRARRAY_REALLOC(array->data, new_capacity * sizeof(void*)); + if (new_mem != NULL) { + array->data = new_mem; + array->capacity = new_capacity; + } + } +} + +void ptrarray_compact(ptrarray * array) { + size_t new_capacity; + void * new_mem; +#ifdef PTRARRAY_SECURITY_CHECKS + if (array == NULL) + return; +#endif + new_capacity = (array->size < PTRARRAY_DEFAULT_CAPACITY) ? PTRARRAY_DEFAULT_CAPACITY : array->size; + new_mem = PTRARRAY_REALLOC(array->data, new_capacity * sizeof(void*)); + if (new_mem != NULL) { + array->data = new_mem; + array->capacity = new_capacity; + } +} + +void ptrarray_push(ptrarray * array, void * data) { +#ifdef PTRARRAY_SECURITY_CHECKS + if (array == NULL) + return; +#endif + if (array->size == array->capacity) { + if (!ptrarray_grow(array)) { + return; + } + } + array->data[array->size++] = data; +} + +void * ptrarray_pop(ptrarray * array) { +#ifdef PTRARRAY_SECURITY_CHECKS + if (array == NULL) + return NULL; +#endif + if (ptrarray_empty(array)) { + return NULL; + } + return array->data[array->size--]; +} + +void ptrarray_insert(ptrarray * array, size_t position, void * data) { + void ** src_pos; + +#ifdef PTRARRAY_SECURITY_CHECKS + if (array == NULL) + return; +#endif + if (array->size > position) { + if (array->size == array->capacity) { + if (!ptrarray_grow(array)) { + return; + } + } + src_pos = array->data + position; + memmove(src_pos + 1, src_pos, array->size - position); + *src_pos = data; + } +} + +void ptrarray_prepend(ptrarray * array, void * data); +void * ptrarray_replace(ptrarray * array, size_t position, void * data); + +void * ptrarray_remove(ptrarray * array, size_t position); +void ptrarray_clear(ptrarray * array); + +void * ptrarray_first(ptrarray * array); +void * ptrarray_last(ptrarray * array); +void * ptrarray_get(size_t position); + +void ptrarray_destroy(ptrarray * array) { +} diff --git a/src/mod/endpoints/mod_rtmp/libamf/src/ptrarray.h b/src/mod/endpoints/mod_rtmp/libamf/src/ptrarray.h new file mode 100644 index 0000000000..d4eb2990a3 --- /dev/null +++ b/src/mod/endpoints/mod_rtmp/libamf/src/ptrarray.h @@ -0,0 +1,52 @@ +#ifndef __PTRARRAY_H__ +#define __PTRARRAY_H__ + +#include + +typedef void (*data_free_proc)(void *); + +typedef struct __ptrarray { + size_t capacity; + size_t size; + void ** data; + data_free_proc data_free; +} ptrarray; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void ptrarray_init(ptrarray * array, size_t initial_capacity, data_free_proc free_proc); + +/*size_t ptrarray_capacity(ptrarray * array);*/ +#define ptrarray_capacity(a) ((a)->capacity) + +/*size_t ptrarray_size(ptrarray * array);*/ +#define ptrarray_size(a) ((a)->size) + +/*int ptrarray_empty(ptrarray * array);*/ +#define ptrarray_empty(a) (!((a)->size)) + +void ptrarray_reserve(ptrarray * array, size_t new_capacity); +void ptrarray_compact(ptrarray * array); + +void ptrarray_push(ptrarray * array, void * data); +void * ptrarray_pop(ptrarray * array); +void ptrarray_insert(ptrarray * array, size_t position, void * data); +void ptrarray_prepend(ptrarray * array, void * data); +void * ptrarray_replace(ptrarray * array, size_t position, void * data); + +void * ptrarray_remove(ptrarray * array, size_t position); +void ptrarray_clear(ptrarray * array); + +void * ptrarray_first(ptrarray * array); +void * ptrarray_last(ptrarray * array); +void * ptrarray_get(size_t position); + +void ptrarray_destroy(ptrarray * array); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __PTRARRAY_H__ */ diff --git a/src/mod/endpoints/mod_rtmp/libamf/src/types.c b/src/mod/endpoints/mod_rtmp/libamf/src/types.c new file mode 100644 index 0000000000..006571aa9c --- /dev/null +++ b/src/mod/endpoints/mod_rtmp/libamf/src/types.c @@ -0,0 +1,48 @@ +/* + $Id: types.c 1 2009-11-13 00:04:24Z noirotm $ + + FLV Metadata updater + + Copyright (C) 2007, 2008 Marc Noirot + + This file is part of FLVMeta. + + FLVMeta is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + FLVMeta is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with FLVMeta; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "types.h" + +#ifndef WORDS_BIGENDIAN + +/* swap 64 bits doubles */ +typedef union __convert_u { + uint64_t i; + number64_t f; +} convert_u; + +number64_t swap_number64(number64_t n) { + convert_u c; + c.f = n; + c.i = (((c.i & 0x00000000000000FFULL) << 56) | + ((c.i & 0x000000000000FF00ULL) << 40) | + ((c.i & 0x0000000000FF0000ULL) << 24) | + ((c.i & 0x00000000FF000000ULL) << 8) | + ((c.i & 0x000000FF00000000ULL) >> 8) | + ((c.i & 0x0000FF0000000000ULL) >> 24) | + ((c.i & 0x00FF000000000000ULL) >> 40) | + ((c.i & 0xFF00000000000000ULL) >> 56)); + return c.f; +} +#endif /* !WORDS_BIGENDIAN */ diff --git a/src/mod/endpoints/mod_rtmp/libamf/src/types.h b/src/mod/endpoints/mod_rtmp/libamf/src/types.h new file mode 100644 index 0000000000..535ae2e18e --- /dev/null +++ b/src/mod/endpoints/mod_rtmp/libamf/src/types.h @@ -0,0 +1,60 @@ +/* + $Id: types.h 1 2009-11-13 00:04:24Z noirotm $ + + FLV Metadata updater + + Copyright (C) 2007, 2008 Marc Noirot + + This file is part of FLVMeta. + + FLVMeta is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + FLVMeta is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with FLVMeta; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +#ifndef __TYPES_H__ +#define __TYPES_H__ + +#include "amf.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#ifdef WORDS_BIGENDIAN + +# define swap_uint16(x) (x) +# define swap_int16(x) (x) +# define swap_uint32(x) (x) +# define swap_number64(x) (x) + +#else /* WORDS_BIGENDIAN */ +/* swap 16 bits integers */ +# define swap_uint16(x) ((((x) & 0x00FFU) << 8) | (((x) & 0xFF00U) >> 8)) +# define swap_sint16(x) ((((x) & 0x00FF) << 8) | (((x) & 0xFF00) >> 8)) + +/* swap 32 bits integers */ +# define swap_uint32(x) ((((x) & 0x000000FFU) << 24) | \ + (((x) & 0x0000FF00U) << 8) | \ + (((x) & 0x00FF0000U) >> 8) | \ + (((x) & 0xFF000000U) >> 24)) + +/* swap 64 bits doubles */ +number64_t swap_number64(number64_t); + +#endif /* WORDS_BIGENDIAN */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __TYPES_H__ */ diff --git a/src/mod/endpoints/mod_rtmp/libamf/tests/CMakeLists.txt b/src/mod/endpoints/mod_rtmp/libamf/tests/CMakeLists.txt new file mode 100644 index 0000000000..95e3b6674b --- /dev/null +++ b/src/mod/endpoints/mod_rtmp/libamf/tests/CMakeLists.txt @@ -0,0 +1,5 @@ +include_directories(${CMAKE_SOURCE_DIR}/src) + +add_executable(amf0_demo amf0_demo.c) +add_dependencies(amf0_demo amf) + diff --git a/src/mod/endpoints/mod_rtmp/libamf/tests/amf0 b/src/mod/endpoints/mod_rtmp/libamf/tests/amf0 new file mode 100755 index 0000000000..ce0e5c3fdc Binary files /dev/null and b/src/mod/endpoints/mod_rtmp/libamf/tests/amf0 differ diff --git a/src/mod/endpoints/mod_rtmp/libamf/tests/amf0_demo.c b/src/mod/endpoints/mod_rtmp/libamf/tests/amf0_demo.c new file mode 100644 index 0000000000..06af985103 --- /dev/null +++ b/src/mod/endpoints/mod_rtmp/libamf/tests/amf0_demo.c @@ -0,0 +1,17 @@ +#include + +#include "amf0.h" +#include "io.h" +#include "types.h" + +int main() { + amf0_data * test; + + test = amf0_object_new(); + amf0_object_add(test, "toto", amf0_str("une chaine de caracteres")); + amf0_object_add(test, "test_bool", amf0_boolean_new(1)); + + amf0_data_dump(stdout, test, 0); + + amf0_data_free(test); +} diff --git a/src/mod/endpoints/mod_rtmp/mod_rtmp.c b/src/mod/endpoints/mod_rtmp/mod_rtmp.c new file mode 100644 index 0000000000..aced950d67 --- /dev/null +++ b/src/mod/endpoints/mod_rtmp/mod_rtmp.c @@ -0,0 +1,1821 @@ +/* + * mod_rtmp for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2011, Barracuda Networks Inc. + * + * Version: MPL 1.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mod_rtmp for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * + * The Initial Developer of the Original Code is Barracuda Networks Inc. + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Mathieu Rene + * + * mod_rtmp.c -- RTMP Endpoint Module + * + */ + +#define BEEN_PAID + +#ifdef BEEN_PAID +/* Thanks to Barracuda Networks Inc. for sponsoring this work */ +#endif + +#include "mod_rtmp.h" + +SWITCH_MODULE_LOAD_FUNCTION(mod_rtmp_load); +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_rtmp_shutdown); +SWITCH_MODULE_DEFINITION(mod_rtmp, mod_rtmp_load, mod_rtmp_shutdown, NULL); + +static switch_status_t config_profile(rtmp_profile_t *profile, switch_bool_t reload); +static switch_xml_config_item_t *get_instructions(rtmp_profile_t *profile); + +switch_state_handler_table_t rtmp_state_handlers = { + /*.on_init */ rtmp_on_init, + /*.on_routing */ rtmp_on_routing, + /*.on_execute */ rtmp_on_execute, + /*.on_hangup */ rtmp_on_hangup, + /*.on_exchange_media */ rtmp_on_exchange_media, + /*.on_soft_execute */ rtmp_on_soft_execute, + /*.on_consume_media */ NULL, + /*.on_hibernate */ NULL, + /*.on_reset */ NULL, + /*.on_park */ NULL, + /*.on_reporting */ NULL, + /*.on_destroy */ rtmp_on_destroy +}; + +switch_io_routines_t rtmp_io_routines = { + /*.outgoing_channel */ rtmp_outgoing_channel, + /*.read_frame */ rtmp_read_frame, + /*.write_frame */ rtmp_write_frame, + /*.kill_channel */ rtmp_kill_channel, + /*.send_dtmf */ rtmp_send_dtmf, + /*.receive_message */ rtmp_receive_message, + /*.receive_event */ rtmp_receive_event +}; + +struct mod_rtmp_globals rtmp_globals; + +static void rtmp_set_channel_variables(switch_core_session_t *session) +{ + switch_channel_t *channel = switch_core_session_get_channel(session); + rtmp_private_t *tech_pvt = switch_core_session_get_private(session); + rtmp_session_t *rsession = tech_pvt->rtmp_session; + + switch_channel_set_variable(channel, "rtmp_profile", rsession->profile->name); + switch_channel_set_variable(channel, "rtmp_session", rsession->uuid); + switch_channel_set_variable(channel, "rtmp_flash_version", rsession->flashVer); + switch_channel_set_variable(channel, "rtmp_swf_url", rsession->swfUrl); + switch_channel_set_variable(channel, "rtmp_tc_url", rsession->tcUrl); + switch_channel_set_variable(channel, "rtmp_page_url", rsession->pageUrl); + switch_channel_set_variable(channel, "rtmp_remote_address", rsession->remote_address); + switch_channel_set_variable_printf(channel, "rtmp_remote_port", "%d", rsession->remote_port); +} + +switch_status_t rtmp_tech_init(rtmp_private_t *tech_pvt, rtmp_session_t *rtmp_session, switch_core_session_t *session) +{ + switch_assert(rtmp_session && session && tech_pvt); + + tech_pvt->read_frame.data = tech_pvt->databuf; + tech_pvt->read_frame.buflen = sizeof(tech_pvt->databuf); + switch_mutex_init(&tech_pvt->mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(session)); + switch_mutex_init(&tech_pvt->flag_mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(session)); + switch_mutex_init(&tech_pvt->readbuf_mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(session)); + + switch_buffer_create_dynamic(&tech_pvt->readbuf, 512, 512, 1024000); + //switch_buffer_add_mutex(tech_pvt->readbuf, tech_pvt->readbuf_mutex); + + switch_core_timer_init(&tech_pvt->timer, "soft", 20, (16000 / (1000 / 20)), switch_core_session_get_pool(session)); + + tech_pvt->session = session; + tech_pvt->rtmp_session = rtmp_session; + tech_pvt->channel = switch_core_session_get_channel(session); + + /* Initialize read & write codecs */ + if (switch_core_codec_init(&tech_pvt->read_codec, /* name */ "SPEEX", + /* fmtp */ NULL, /* rate */ 16000, /* ms */ 20, /* channels */ 1, + /* flags */ SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE, + /* codec settings */ NULL, switch_core_session_get_pool(session)) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Can't initialize read codec\n"); + + return SWITCH_STATUS_FALSE; + } + + if (switch_core_codec_init(&tech_pvt->write_codec, /* name */ "SPEEX", + /* fmtp */ NULL, /* rate */ 16000, /* ms */ 20, /* channels */ 1, + /* flags */ SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE, + /* codec settings */ NULL, switch_core_session_get_pool(session)) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Can't initialize write codec\n"); + + return SWITCH_STATUS_FALSE; + } + + switch_core_session_set_read_codec(session, &tech_pvt->read_codec); + switch_core_session_set_write_codec(session, &tech_pvt->write_codec); + + //static inline uint8_t rtmp_audio_codec(int channels, int bits, int rate, rtmp_audio_format_t format) { + tech_pvt->audio_codec = 0xB2; //rtmp_audio_codec(1, 16, 0 /* speex is always 8000 */, RTMP_AUDIO_SPEEX); + + switch_core_session_set_private(session, tech_pvt); + + return SWITCH_STATUS_SUCCESS; +} + + +/* + State methods they get called when the state changes to the specific state + returning SWITCH_STATUS_SUCCESS tells the core to execute the standard state method next + so if you fully implement the state you can return SWITCH_STATUS_FALSE to skip it. +*/ +switch_status_t rtmp_on_init(switch_core_session_t *session) +{ + switch_channel_t *channel; + rtmp_private_t *tech_pvt = NULL; + + tech_pvt = switch_core_session_get_private(session); + assert(tech_pvt != NULL); + + channel = switch_core_session_get_channel(session); + assert(channel != NULL); + + rtmp_notify_call_state(session); + + switch_set_flag_locked(tech_pvt, TFLAG_IO); + + /* Move channel's state machine to ROUTING. This means the call is trying + to get from the initial start where the call because, to the point + where a destination has been identified. If the channel is simply + left in the initial state, nothing will happen. */ + switch_channel_set_state(channel, CS_ROUTING); + + + switch_mutex_lock(tech_pvt->rtmp_session->profile->mutex); + tech_pvt->rtmp_session->profile->calls++; + switch_mutex_unlock(tech_pvt->rtmp_session->profile->mutex); + + switch_mutex_lock(tech_pvt->rtmp_session->count_mutex); + tech_pvt->rtmp_session->active_sessions++; + switch_mutex_unlock(tech_pvt->rtmp_session->count_mutex); + return SWITCH_STATUS_SUCCESS; +} + +switch_status_t rtmp_on_routing(switch_core_session_t *session) +{ + switch_channel_t *channel = NULL; + rtmp_private_t *tech_pvt = NULL; + + channel = switch_core_session_get_channel(session); + assert(channel != NULL); + + tech_pvt = switch_core_session_get_private(session); + assert(tech_pvt != NULL); + + rtmp_notify_call_state(session); + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s CHANNEL ROUTING\n", switch_channel_get_name(channel)); + + return SWITCH_STATUS_SUCCESS; +} + +switch_status_t rtmp_on_execute(switch_core_session_t *session) +{ + + switch_channel_t *channel = NULL; + rtmp_private_t *tech_pvt = NULL; + + channel = switch_core_session_get_channel(session); + assert(channel != NULL); + + tech_pvt = switch_core_session_get_private(session); + assert(tech_pvt != NULL); + + rtmp_notify_call_state(session); + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s CHANNEL EXECUTE\n", switch_channel_get_name(channel)); + + + return SWITCH_STATUS_SUCCESS; +} + +switch_status_t rtmp_on_destroy(switch_core_session_t *session) +{ + switch_channel_t *channel = NULL; + rtmp_private_t *tech_pvt = NULL; + + channel = switch_core_session_get_channel(session); + assert(channel != NULL); + + tech_pvt = switch_core_session_get_private(session); + + if (tech_pvt) { + if (switch_core_codec_ready(&tech_pvt->read_codec)) { + switch_core_codec_destroy(&tech_pvt->read_codec); + } + + if (switch_core_codec_ready(&tech_pvt->write_codec)) { + switch_core_codec_destroy(&tech_pvt->write_codec); + } + + switch_buffer_destroy(&tech_pvt->readbuf); + switch_core_timer_destroy(&tech_pvt->timer); + } + + return SWITCH_STATUS_SUCCESS; +} + + +switch_status_t rtmp_on_hangup(switch_core_session_t *session) +{ + switch_channel_t *channel = NULL; + rtmp_private_t *tech_pvt = NULL; + + channel = switch_core_session_get_channel(session); + assert(channel != NULL); + + tech_pvt = switch_core_session_get_private(session); + assert(tech_pvt != NULL); + + switch_clear_flag_locked(tech_pvt, TFLAG_IO); + //switch_thread_cond_signal(tech_pvt->cond); + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s CHANNEL HANGUP\n", switch_channel_get_name(channel)); + + if (tech_pvt->rtmp_session->tech_pvt == tech_pvt) { + rtmp_private_t *other_tech_pvt = NULL; + const char *s; + if ((s = switch_channel_get_variable(channel, RTMP_ATTACH_ON_HANGUP_VARIABLE)) && !zstr(s)) { + other_tech_pvt = rtmp_locate_private(tech_pvt->rtmp_session, s); + } + rtmp_attach_private(tech_pvt->rtmp_session, other_tech_pvt); + } + + rtmp_notify_call_state(session); + rtmp_send_onhangup(session); + + switch_mutex_lock(tech_pvt->rtmp_session->count_mutex); + tech_pvt->rtmp_session->active_sessions--; + switch_mutex_unlock(tech_pvt->rtmp_session->count_mutex); + + switch_core_hash_delete_wrlock(tech_pvt->rtmp_session->session_hash, switch_core_session_get_uuid(session), tech_pvt->rtmp_session->session_rwlock); + + switch_mutex_lock(tech_pvt->rtmp_session->profile->mutex); + tech_pvt->rtmp_session->profile->calls--; + if (tech_pvt->rtmp_session->profile->calls < 0) { + tech_pvt->rtmp_session->profile->calls = 0; + } + switch_mutex_unlock(tech_pvt->rtmp_session->profile->mutex); + +#ifndef RTMP_DONT_HOLD + if (switch_channel_test_flag(channel, CF_HOLD)) { + switch_channel_mark_hold(channel, SWITCH_FALSE); + switch_ivr_unhold(session); + } +#endif + + return SWITCH_STATUS_SUCCESS; +} + + +switch_status_t rtmp_kill_channel(switch_core_session_t *session, int sig) +{ + switch_channel_t *channel = NULL; + rtmp_private_t *tech_pvt = NULL; + + channel = switch_core_session_get_channel(session); + assert(channel != NULL); + + tech_pvt = switch_core_session_get_private(session); + assert(tech_pvt != NULL); + + switch (sig) { + case SWITCH_SIG_KILL: + switch_clear_flag_locked(tech_pvt, TFLAG_IO); + switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING); + break; + case SWITCH_SIG_BREAK: + switch_set_flag_locked(tech_pvt, TFLAG_BREAK); + break; + default: + break; + } + + return SWITCH_STATUS_SUCCESS; +} + +switch_status_t rtmp_on_exchange_media(switch_core_session_t *session) +{ + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "CHANNEL LOOPBACK\n"); + rtmp_notify_call_state(session); + return SWITCH_STATUS_SUCCESS; +} + +switch_status_t rtmp_on_soft_execute(switch_core_session_t *session) +{ + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "CHANNEL TRANSMIT\n"); + rtmp_notify_call_state(session); + return SWITCH_STATUS_SUCCESS; +} + +switch_status_t rtmp_send_dtmf(switch_core_session_t *session, const switch_dtmf_t *dtmf) +{ + rtmp_private_t *tech_pvt = switch_core_session_get_private(session); + switch_assert(tech_pvt != NULL); + + return SWITCH_STATUS_SUCCESS; +} + +switch_status_t rtmp_read_frame(switch_core_session_t *session, switch_frame_t **frame, switch_io_flag_t flags, int stream_id) +{ + switch_channel_t *channel = NULL; + rtmp_private_t *tech_pvt = NULL; + //switch_time_t started = switch_time_now(); + //unsigned int elapsed; + switch_byte_t *data; + uint16_t len; + + channel = switch_core_session_get_channel(session); + assert(channel != NULL); + + tech_pvt = switch_core_session_get_private(session); + assert(tech_pvt != NULL); + + if (tech_pvt->rtmp_session->state >= RS_DESTROY) { + return SWITCH_STATUS_FALSE; + } + + if (switch_test_flag(tech_pvt, TFLAG_DETACHED)) { + switch_core_timer_next(&tech_pvt->timer); + goto cng; + } + + tech_pvt->read_frame.flags = SFF_NONE; + tech_pvt->read_frame.codec = &tech_pvt->read_codec; + + switch_core_timer_next(&tech_pvt->timer); + + if (switch_buffer_inuse(tech_pvt->readbuf) < 2) { + /* Not enough data in buffer, return CNG frame */ + goto cng; + } else { + switch_mutex_lock(tech_pvt->readbuf_mutex); + switch_buffer_peek(tech_pvt->readbuf, &len, 2); + if (switch_buffer_inuse(tech_pvt->readbuf) >= len) { + if (len == 0) { + switch_mutex_unlock(tech_pvt->readbuf_mutex); + goto cng; + } else { + uint8_t codec; + + if (tech_pvt->read_frame.buflen < len) { + switch_mutex_unlock(tech_pvt->readbuf_mutex); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Packet of size %u is bigger that the buffer length %u.\n", + len, tech_pvt->read_frame.buflen); + return SWITCH_STATUS_FALSE; + } + + switch_buffer_toss(tech_pvt->readbuf, 2); + switch_buffer_read(tech_pvt->readbuf, &codec, 1); + switch_buffer_read(tech_pvt->readbuf, tech_pvt->read_frame.data, len-1); + tech_pvt->read_frame.datalen = len-1; + + if (codec != tech_pvt->audio_codec) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Received codec 0x%x instead of 0x%x\n", codec, tech_pvt->audio_codec); + switch_mutex_unlock(tech_pvt->readbuf_mutex); + goto cng; + } + } + } + switch_mutex_unlock(tech_pvt->readbuf_mutex); + } + + *frame = &tech_pvt->read_frame; + + return SWITCH_STATUS_SUCCESS; + +cng: + data = (switch_byte_t *) tech_pvt->read_frame.data; + data[0] = 65; + data[1] = 0; + tech_pvt->read_frame.datalen = 2; + tech_pvt->read_frame.flags = SFF_CNG; + + switch_core_timer_sync(&tech_pvt->timer); + + *frame = &tech_pvt->read_frame; + + return SWITCH_STATUS_SUCCESS; + +} + +switch_status_t rtmp_write_frame(switch_core_session_t *session, switch_frame_t *frame, switch_io_flag_t flags, int stream_id) +{ + switch_channel_t *channel = NULL; + rtmp_private_t *tech_pvt = NULL; + //switch_frame_t *pframe; + unsigned char buf[AMF_MAX_SIZE]; + switch_time_t ts; + + channel = switch_core_session_get_channel(session); + assert(channel != NULL); + + tech_pvt = switch_core_session_get_private(session); + assert(tech_pvt != NULL); + + + if (!switch_test_flag(tech_pvt, TFLAG_IO)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "TFLAG_IO not set\n"); + return SWITCH_STATUS_FALSE; + } + + if (switch_test_flag(tech_pvt, TFLAG_DETACHED) || !switch_test_flag(tech_pvt->rtmp_session, SFLAG_AUDIO)) { + return SWITCH_STATUS_SUCCESS; + } + + if (!tech_pvt->rtmp_session || !tech_pvt->audio_codec || !tech_pvt->write_channel) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Missing mandatory value\n"); + return SWITCH_STATUS_FALSE; + } + + if (tech_pvt->rtmp_session->state >= RS_DESTROY) { + return SWITCH_STATUS_FALSE; + } + + if (frame->datalen+1 > frame->buflen) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Datalen too big\n"); + return SWITCH_STATUS_FALSE; + } + + if (frame->flags & SFF_CNG) { + return SWITCH_STATUS_SUCCESS; + } + + /* Build message */ + buf[0] = tech_pvt->audio_codec; + memcpy(buf+1, frame->data, frame->datalen); + + /* Send it down the socket */ + if (!tech_pvt->stream_start_ts) { + tech_pvt->stream_start_ts = switch_micro_time_now() / 1000; + ts = 0; + } else { + ts = (switch_micro_time_now() / 1000) - tech_pvt->stream_start_ts; + } + + rtmp_send_message(tech_pvt->rtmp_session, RTMP_DEFAULT_STREAM_AUDIO, ts, RTMP_TYPE_AUDIO, tech_pvt->rtmp_session->media_streamid, buf, frame->datalen + 1, 0); + return SWITCH_STATUS_SUCCESS; +} + + +switch_status_t rtmp_receive_message(switch_core_session_t *session, switch_core_session_message_t *msg) +{ + switch_channel_t *channel; + rtmp_private_t *tech_pvt; + + channel = switch_core_session_get_channel(session); + assert(channel != NULL); + + tech_pvt = (rtmp_private_t *) switch_core_session_get_private(session); + assert(tech_pvt != NULL); + + switch (msg->message_id) { + case SWITCH_MESSAGE_INDICATE_ANSWER: + switch_channel_mark_answered(channel); + rtmp_notify_call_state(session); + break; + case SWITCH_MESSAGE_INDICATE_RINGING: + switch_channel_mark_ring_ready(channel); + rtmp_notify_call_state(session); + break; + case SWITCH_MESSAGE_INDICATE_PROGRESS: + switch_channel_mark_pre_answered(channel); + rtmp_notify_call_state(session); + break; + case SWITCH_MESSAGE_INDICATE_HOLD: + case SWITCH_MESSAGE_INDICATE_UNHOLD: + rtmp_notify_call_state(session); + break; + + case SWITCH_MESSAGE_INDICATE_DISPLAY: + { + const char *name = msg->string_array_arg[0], *number = msg->string_array_arg[1]; + char *arg = NULL; + char *argv[2] = { 0 }; + int argc; + + if (zstr(name) && !zstr(msg->string_arg)) { + arg = strdup(msg->string_arg); + switch_assert(arg); + + argc = switch_separate_string(arg, '|', argv, (sizeof(argv) / sizeof(argv[0]))); + name = argv[0]; + number = argv[1]; + + } + + if (!zstr(name)) { + if (zstr(number)) { + switch_caller_profile_t *caller_profile = switch_channel_get_caller_profile(channel); + number = caller_profile->destination_number; + } + + if (zstr(tech_pvt->display_callee_id_name) || strcmp(tech_pvt->display_callee_id_name, name)) { + tech_pvt->display_callee_id_name = switch_core_session_strdup(session, name); + } + + if (zstr(tech_pvt->display_callee_id_number) || strcmp(tech_pvt->display_callee_id_number, number)) { + tech_pvt->display_callee_id_number = switch_core_session_strdup(session, number); + } + + rtmp_send_display_update(session); + } + + switch_safe_free(arg); + } + break; + default: + break; + } + + return SWITCH_STATUS_SUCCESS; +} + +/* Make sure when you have 2 sessions in the same scope that you pass the appropriate one to the routines + that allocate memory or you will have 1 channel with memory allocated from another channel's pool! +*/ +switch_call_cause_t rtmp_outgoing_channel(switch_core_session_t *session, switch_event_t *var_event, + switch_caller_profile_t *outbound_profile, + switch_core_session_t **newsession, switch_memory_pool_t **inpool, switch_originate_flag_t flags, + switch_call_cause_t *cancel_cause) +{ + rtmp_private_t *tech_pvt; + switch_caller_profile_t *caller_profile; + switch_channel_t *channel; + switch_call_cause_t cause = SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER; + rtmp_session_t *rsession = NULL; + switch_memory_pool_t *pool; + char *destination = NULL, *auth, *user, *domain; + *newsession = NULL; + + if (zstr(outbound_profile->destination_number)) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "No destination\n"); + goto fail; + } + + destination = strdup(outbound_profile->destination_number); + + if ((auth = strchr(destination, '/'))) { + *auth++ = '\0'; + } + + /* Locate the user to be called */ + if (!(rsession = rtmp_session_locate(destination))) { + cause = SWITCH_CAUSE_NO_ROUTE_DESTINATION; + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "No such session id: %s\n", outbound_profile->destination_number); + goto fail; + } + + if (!(*newsession = switch_core_session_request(rtmp_globals.rtmp_endpoint_interface, flags, SWITCH_CALL_DIRECTION_OUTBOUND, inpool))) { + goto fail; + } + + pool = switch_core_session_get_pool(*newsession); + + channel = switch_core_session_get_channel(*newsession); + switch_channel_set_name(channel, switch_core_session_sprintf(*newsession, "rtmp/%s/%s", rsession->profile->name, outbound_profile->destination_number)); + + caller_profile = switch_caller_profile_dup(pool, outbound_profile); + switch_channel_set_caller_profile(channel, caller_profile); + + tech_pvt = switch_core_alloc(pool, sizeof(rtmp_private_t)); + tech_pvt->rtmp_session = rsession; + tech_pvt->write_channel = RTMP_DEFAULT_STREAM_AUDIO; + tech_pvt->session = *newsession; + tech_pvt->caller_profile = caller_profile; + switch_core_session_add_stream(*newsession, NULL); + + if (rtmp_tech_init(tech_pvt, rsession, *newsession) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(*newsession), SWITCH_LOG_ERROR, "tech_init failed\n"); + cause = SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER; + goto fail; + } + + if (!zstr(auth)) { + tech_pvt->auth = switch_core_session_strdup(*newsession, auth); + switch_split_user_domain(auth, &user, &domain); + tech_pvt->auth_user = switch_core_session_strdup(*newsession, user); + tech_pvt->auth_domain = switch_core_session_strdup(*newsession, domain); + } + + /*switch_channel_mark_pre_answered(channel);*/ + + switch_channel_ring_ready(channel); + rtmp_send_incoming_call(*newsession); + + switch_channel_set_state(channel, CS_INIT); + switch_set_flag_locked(tech_pvt, TFLAG_IO); + + rtmp_set_channel_variables(*newsession); + + switch_core_hash_insert_wrlock(rsession->session_hash, switch_core_session_get_uuid(*newsession), tech_pvt, rsession->session_rwlock); + + if (switch_core_session_thread_launch(tech_pvt->session) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't spawn thread\n"); + goto fail; + } + + if (rsession) { + rtmp_session_rwunlock(rsession); + } + + return SWITCH_CAUSE_SUCCESS; + +fail: + if (*newsession) { + switch_core_session_destroy(newsession); + } + if (rsession) { + rtmp_session_rwunlock(rsession); + } + switch_safe_free(destination); + return cause; + +} + +switch_status_t rtmp_receive_event(switch_core_session_t *session, switch_event_t *event) +{ + rtmp_private_t *tech_pvt = switch_core_session_get_private(session); + switch_assert(tech_pvt != NULL); + + /* Deliver the event as a custom message to the target rtmp session */ + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Session", switch_core_session_get_uuid(session)); + + rtmp_send_event(tech_pvt->rtmp_session, event); + + return SWITCH_STATUS_SUCCESS; +} + + +rtmp_profile_t *rtmp_profile_locate(const char *name) +{ + rtmp_profile_t *profile = switch_core_hash_find_rdlock(rtmp_globals.profile_hash, name, rtmp_globals.profile_rwlock); + + if (profile) { + if (switch_thread_rwlock_tryrdlock(profile->rwlock) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Profile %s is locked\n", name); + profile = NULL; + } + } + + return profile; +} + +void rtmp_profile_release(rtmp_profile_t *profile) +{ + switch_thread_rwlock_unlock(profile->rwlock); +} + +rtmp_session_t *rtmp_session_locate(const char *uuid) +{ + rtmp_session_t *rsession = switch_core_hash_find_rdlock(rtmp_globals.session_hash, uuid, rtmp_globals.session_rwlock); + + if (!rsession || rsession->state == RS_DESTROY) { + return NULL; + } + + switch_thread_rwlock_rdlock(rsession->rwlock); + + return rsession; +} + +void rtmp_session_rwunlock(rtmp_session_t *rsession) +{ + switch_thread_rwlock_unlock(rsession->rwlock); +} + +void rtmp_event_fill(rtmp_session_t *rsession, switch_event_t *event) +{ + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "RTMP-Session-ID", rsession->uuid); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "RTMP-Flash-Version", rsession->flashVer); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "RTMP-SWF-URL", rsession->swfUrl); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "RTMP-TC-URL", rsession->tcUrl); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "RTMP-Page-URL", rsession->pageUrl); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "RTMP-Profile", rsession->profile->name); + switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Network-Port", "%d", rsession->remote_port); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Network-IP", rsession->remote_address); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "RTMP-Profile", rsession->profile->name); +} + +switch_status_t rtmp_session_request(rtmp_profile_t *profile, rtmp_session_t **newsession) +{ + switch_memory_pool_t *pool; + switch_uuid_t uuid; + switch_core_new_memory_pool(&pool); + *newsession = switch_core_alloc(pool, sizeof(rtmp_session_t)); + + (*newsession)->pool = pool; + (*newsession)->profile = profile; + (*newsession)->in_chunksize = (*newsession)->out_chunksize = RTMP_DEFAULT_CHUNKSIZE; + (*newsession)->recv_ack_window = RTMP_DEFAULT_ACK_WINDOW; + (*newsession)->next_streamid = 1; + + switch_uuid_get(&uuid); + switch_uuid_format((*newsession)->uuid, &uuid); + switch_mutex_init(&((*newsession)->socket_mutex), SWITCH_MUTEX_NESTED, pool); + switch_mutex_init(&((*newsession)->count_mutex), SWITCH_MUTEX_NESTED, pool); + switch_thread_rwlock_create(&((*newsession)->rwlock), pool); + switch_thread_rwlock_create(&((*newsession)->account_rwlock), pool); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "New RTMP session [%s]\n", (*newsession)->uuid); + switch_core_hash_insert_wrlock(rtmp_globals.session_hash, (*newsession)->uuid, *newsession, rtmp_globals.session_rwlock); + switch_core_hash_insert_wrlock(profile->session_hash, (*newsession)->uuid, *newsession, profile->session_rwlock); + + switch_core_hash_init(&(*newsession)->session_hash, pool); + switch_thread_rwlock_create(&(*newsession)->session_rwlock, pool); + +#ifdef RTMP_DEBUG_IO + { + char buf[1024]; + snprintf(buf, sizeof(buf), "/tmp/rtmp-%s-in.txt", (*newsession)->uuid); + (*newsession)->io_debug_in = fopen(buf, "w"); + snprintf(buf, sizeof(buf), "/tmp/rtmp-%s-out.txt", (*newsession)->uuid); + (*newsession)->io_debug_out = fopen(buf, "w"); + } +#endif + + switch_mutex_lock(profile->mutex); + profile->clients++; + switch_mutex_unlock(profile->mutex); + + { + switch_event_t *event; + if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, RTMP_EVENT_CONNECT) == SWITCH_STATUS_SUCCESS) { + rtmp_event_fill(*newsession, event); + switch_event_fire(&event); + } + } + + return SWITCH_STATUS_SUCCESS; +} + +switch_status_t rtmp_session_destroy(rtmp_session_t **session) +{ + switch_hash_index_t *hi; + switch_event_t *event; + + if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, RTMP_EVENT_DISCONNECT) == SWITCH_STATUS_SUCCESS) { + rtmp_event_fill(*session, event); + switch_event_fire(&event); + } + + switch_core_hash_delete_wrlock(rtmp_globals.session_hash, (*session)->uuid, rtmp_globals.session_rwlock); + switch_core_hash_delete_wrlock((*session)->profile->session_hash, (*session)->uuid, (*session)->profile->session_rwlock); + rtmp_clear_registration(*session, NULL, NULL); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "RTMP session ended [%s]\n", (*session)->uuid); + + (*session)->state = RS_DESTROY; + + switch_thread_rwlock_rdlock((*session)->session_rwlock); + for (hi = switch_hash_first(NULL, (*session)->session_hash); hi; hi = switch_hash_next(hi)) { + void *val; + const void *key; + switch_ssize_t keylen; + rtmp_private_t *item; + switch_channel_t *channel; + switch_hash_this(hi, &key, &keylen, &val); + item = (rtmp_private_t *)val; + + channel = switch_core_session_get_channel(item->session); + switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); + } + switch_thread_rwlock_unlock((*session)->session_rwlock); + + + while ((*session)->active_sessions > 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Still have %d sessions, waiting\n", (*session)->active_sessions); + switch_yield(500000); + } + + + switch_thread_rwlock_wrlock((*session)->rwlock); + switch_thread_rwlock_unlock((*session)->rwlock); + + (*session)->profile->io->close(*session); + +#ifdef RTMP_DEBUG_IO + fclose((*session)->io_debug_in); + fclose((*session)->io_debug_out); +#endif + + switch_mutex_lock((*session)->profile->mutex); + (*session)->profile->clients--; + switch_mutex_unlock((*session)->profile->mutex); + + switch_core_hash_destroy(&(*session)->session_hash); + + switch_core_destroy_memory_pool(&(*session)->pool); + + *session = NULL; + + return SWITCH_STATUS_SUCCESS; +} + +switch_call_cause_t rtmp_session_create_call(rtmp_session_t *rsession, switch_core_session_t **newsession, int read_channel, int write_channel, const char *number, const char *auth_user, const char *auth_domain, switch_event_t *event) +{ + switch_memory_pool_t *pool; + rtmp_private_t *tech_pvt; + switch_caller_profile_t *caller_profile; + switch_channel_t *channel; + const char *dialplan, *context; + + if (!(*newsession = switch_core_session_request(rtmp_globals.rtmp_endpoint_interface, SOF_NONE, SWITCH_CALL_DIRECTION_INBOUND, NULL))) { + return SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER; + } + + pool = switch_core_session_get_pool(*newsession); + channel = switch_core_session_get_channel(*newsession); + switch_channel_set_name(channel, switch_core_session_sprintf(*newsession, "rtmp/%s/%s", rsession->profile->name, number)); + + if (!zstr(auth_user) && !zstr(auth_domain)) { + const char *s = switch_core_session_sprintf(*newsession, "%s@%s", auth_user, auth_domain); + switch_ivr_set_user(*newsession, s); + switch_channel_set_variable(channel, "rtmp_authorized", "true"); + } + + if (!(context = switch_channel_get_variable(channel, "user_context"))) { + if (!(context = rsession->profile->context)) { + context = "public"; + } + } + + if (!(dialplan = switch_channel_get_variable(channel, "inbound_dialplan"))) { + if (!(dialplan = rsession->profile->dialplan)) { + dialplan = "XML"; + } + } + + caller_profile = switch_caller_profile_new(pool, switch_str_nil(auth_user), dialplan, + SWITCH_DEFAULT_CLID_NAME, + !zstr(auth_user) ? auth_user : "0000000000", + rsession->remote_address /* net addr */, + NULL /* ani */, + NULL /* anii */, + NULL /* rdnis */, + "mod_rtmp", context, number); + + switch_channel_set_caller_profile(channel, caller_profile); + + tech_pvt = switch_core_alloc(pool, sizeof(rtmp_private_t)); + tech_pvt->rtmp_session = rsession; + tech_pvt->write_channel = RTMP_DEFAULT_STREAM_AUDIO; + tech_pvt->session = *newsession; + tech_pvt->caller_profile = caller_profile; + switch_core_session_add_stream(*newsession, NULL); + + if (rtmp_tech_init(tech_pvt, rsession, *newsession) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "tech_init failed\n"); + goto fail; + } + + if (!zstr(auth_user) && !zstr(auth_domain)) { + tech_pvt->auth_user = switch_core_session_strdup(*newsession, auth_user); + tech_pvt->auth_domain = switch_core_session_strdup(*newsession, auth_domain); + tech_pvt->auth = switch_core_session_sprintf(*newsession, "%s@%s", auth_user, auth_domain); + } + + switch_channel_set_state(channel, CS_INIT); + switch_set_flag_locked(tech_pvt, TFLAG_IO); + switch_set_flag_locked(tech_pvt, TFLAG_DETACHED); + rtmp_set_channel_variables(*newsession); + + switch_core_hash_insert_wrlock(rsession->session_hash, switch_core_session_get_uuid(*newsession), tech_pvt, rsession->session_rwlock); + + if (switch_core_session_thread_launch(tech_pvt->session) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't spawn thread\n"); + goto fail; + } + + return SWITCH_CAUSE_NONE; + +fail: + switch_core_session_destroy(newsession); + return SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER; +} + +switch_status_t rtmp_profile_start(const char *profilename) +{ + switch_memory_pool_t *pool; + rtmp_profile_t *profile; + + switch_assert(profilename); + + switch_core_new_memory_pool(&pool); + profile = switch_core_alloc(pool, sizeof(*profile)); + profile->pool = pool; + profile->name = switch_core_strdup(pool, profilename); + + if (config_profile(profile, SWITCH_FALSE) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Config failed\n"); + goto fail; + } + + switch_thread_rwlock_create(&profile->rwlock, pool); + switch_mutex_init(&profile->mutex, SWITCH_MUTEX_NESTED, pool); + switch_core_hash_init(&profile->session_hash, pool); + switch_thread_rwlock_create(&profile->session_rwlock, pool); + switch_thread_rwlock_create(&profile->reg_rwlock, pool); + switch_core_hash_init(&profile->reg_hash, pool); + + if (!strcmp(profile->io_name, "tcp")) { + if (rtmp_tcp_init(profile, profile->bind_address, &profile->io, pool) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't initialize I/O layer\n"); + goto fail; + } + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "No such I/O module [%s]\n", profile->io_name); + goto fail; + } + + switch_core_hash_insert_wrlock(rtmp_globals.profile_hash, profile->name, profile, rtmp_globals.profile_rwlock); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Started profile %s\n", profile->name); + + return SWITCH_STATUS_SUCCESS; +fail: + switch_core_destroy_memory_pool(&pool); + return SWITCH_STATUS_FALSE; +} + +switch_status_t rtmp_profile_destroy(rtmp_profile_t **profile) { + int sanity = 0; + switch_hash_index_t *hi; + switch_xml_config_item_t *instructions = get_instructions(*profile); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Stopping profile: %s\n", (*profile)->name); + + switch_core_hash_delete_wrlock(rtmp_globals.profile_hash, (*profile)->name, rtmp_globals.profile_rwlock); + + switch_thread_rwlock_wrlock((*profile)->rwlock); + + /* Kill all sessions */ + while ((hi = switch_hash_first(NULL, (*profile)->session_hash))) { + void *val; + rtmp_session_t *session; + const void *key; + switch_ssize_t keylen; + switch_hash_this(hi, &key, &keylen, &val); + + session = val; + + rtmp_session_destroy(&session); + } + + if ((*profile)->io->running > 0) { + (*profile)->io->running = 0; + + while (sanity++ < 100 && (*profile)->io->running == 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Waiting for thread to end\n"); + switch_yield(500000); + } + } + + switch_thread_rwlock_unlock((*profile)->rwlock); + + switch_xml_config_cleanup(instructions); + + switch_core_hash_destroy(&(*profile)->session_hash); + switch_core_hash_destroy(&(*profile)->reg_hash); + + switch_core_destroy_memory_pool(&(*profile)->pool); + + free(instructions); + + return SWITCH_STATUS_SUCCESS; +} + + +void rtmp_add_registration(rtmp_session_t *rsession, const char *auth, const char *nickname) +{ + rtmp_reg_t *current_reg; + rtmp_reg_t *reg; + switch_event_t *event; + + if (zstr(auth)) { + return; + } + + reg = switch_core_alloc(rsession->pool, sizeof(*reg)); + reg->uuid = rsession->uuid; + + if (!zstr(nickname)) { + reg->nickname = switch_core_strdup(rsession->pool, nickname); + } + + switch_thread_rwlock_wrlock(rsession->profile->reg_rwlock); + if ((current_reg = switch_core_hash_find(rsession->profile->reg_hash, auth))) { + for (;current_reg && current_reg->next; current_reg = current_reg->next); + current_reg->next = reg; + } else { + switch_core_hash_insert(rsession->profile->reg_hash, auth, reg); + } + switch_thread_rwlock_unlock(rsession->profile->reg_rwlock); + + if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, RTMP_EVENT_REGISTER) == SWITCH_STATUS_SUCCESS) { + char *user, *domain, *dup; + rtmp_event_fill(rsession, event); + + dup = strdup(auth); + switch_split_user_domain(dup, &user, &domain); + + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "User", user); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Domain", domain); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Nickname", switch_str_nil(nickname)); + switch_event_fire(&event); + free(dup); + } + +} + +static void rtmp_clear_reg_auth(rtmp_session_t *rsession, const char *auth, const char *nickname) +{ + rtmp_reg_t *reg, *prev = NULL; + switch_thread_rwlock_wrlock(rsession->profile->reg_rwlock); + if ((reg = switch_core_hash_find(rsession->profile->reg_hash, auth))) { + for (; reg; reg = reg->next) { + if (!strcmp(reg->uuid, rsession->uuid) && (zstr(nickname) || !strcmp(reg->nickname, nickname))) { + switch_event_t *event; + if (prev) { + prev->next = reg->next; + } else { + /* Replace hash entry by its next ptr */ + switch_core_hash_insert(rsession->profile->reg_hash, auth, reg->next); + } + + if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, RTMP_EVENT_UNREGISTER) == SWITCH_STATUS_SUCCESS) { + rtmp_event_fill(rsession, event); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Nickname", switch_str_nil(reg->nickname)); + switch_event_fire(&event); + } + } + prev = reg; + } + } + switch_thread_rwlock_unlock(rsession->profile->reg_rwlock); +} + + +void rtmp_clear_registration(rtmp_session_t *rsession, const char *auth, const char *nickname) +{ + rtmp_account_t *account; + + if (zstr(auth)) { + /* Reg data is pool-allocated, no need to free them */ + switch_thread_rwlock_rdlock(rsession->account_rwlock); + for (account = rsession->account; account; account = account->next) { + char buf[1024]; + snprintf(buf, sizeof(buf), "%s@%s", account->user, account->domain); + rtmp_clear_reg_auth(rsession, buf, nickname); + } + switch_thread_rwlock_unlock(rsession->account_rwlock); + } else { + rtmp_clear_reg_auth(rsession, auth, nickname); + } + +} + +switch_status_t rtmp_session_login(rtmp_session_t *rsession, const char *user, const char *domain) +{ + rtmp_account_t *account = switch_core_alloc(rsession->pool, sizeof(*account)); + switch_event_t *event; + + account->user = switch_core_strdup(rsession->pool, user); + account->domain = switch_core_strdup(rsession->pool, domain); + + switch_thread_rwlock_wrlock(rsession->account_rwlock); + account->next = rsession->account; + rsession->account = account; + switch_thread_rwlock_unlock(rsession->account_rwlock); + + rtmp_send_invoke_free(rsession, 3, 0, 0, + amf0_str("onLogin"), + amf0_number_new(0), + amf0_null_new(), + amf0_str("success"), + amf0_str(user), + amf0_str(domain), NULL); + + if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, RTMP_EVENT_LOGIN) == SWITCH_STATUS_SUCCESS) { + rtmp_event_fill(rsession, event); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "User", user); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Domain", domain); + switch_event_fire(&event); + } + + switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_INFO, "RTMP Session [%s] is now logged into %s@%s\n", rsession->uuid, user, domain); + + return SWITCH_STATUS_SUCCESS; +} + +switch_status_t rtmp_session_logout(rtmp_session_t *rsession, const char *user, const char *domain) +{ + rtmp_account_t *account, *prev = NULL; + switch_event_t *event; + + switch_thread_rwlock_wrlock(rsession->account_rwlock); + for (account = rsession->account; account; account = account->next) { + if (!strcmp(account->user, user) && !strcmp(account->domain, domain)) { + if (prev) { + prev->next = account->next; + } else { + rsession->account = account->next; + } + } + } + switch_thread_rwlock_unlock(rsession->account_rwlock); + + rtmp_send_invoke_free(rsession, 3, 0, 0, + amf0_str("onLogout"), + amf0_number_new(0), + amf0_null_new(), + amf0_str(user), + amf0_str(domain), NULL); + + if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, RTMP_EVENT_LOGOUT) == SWITCH_STATUS_SUCCESS) { + rtmp_event_fill(rsession, event); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "User", user); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Domain", domain); + switch_event_fire(&event); + } + + switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_INFO, "RTMP Session [%s] is now logged out of %s@%s\n", rsession->uuid, user, domain); + + return SWITCH_STATUS_SUCCESS; +} + +switch_status_t rtmp_session_check_user(rtmp_session_t *rsession, const char *user, const char *domain) +{ + rtmp_account_t *account; + switch_status_t status = SWITCH_STATUS_FALSE; + + switch_thread_rwlock_rdlock(rsession->account_rwlock); + for (account = rsession->account; account; account = account->next) { + if (!strcmp(account->user, user) && !strcmp(account->domain, domain)) { + status = SWITCH_STATUS_SUCCESS; + break; + } + } + switch_thread_rwlock_unlock(rsession->account_rwlock); + + return status; +} + +void rtmp_attach_private(rtmp_session_t *rsession, rtmp_private_t *tech_pvt) +{ + switch_event_t *event; + + if (rsession->tech_pvt) { + /* Detach current call */ + switch_set_flag_locked(rsession->tech_pvt, TFLAG_DETACHED); +#ifndef RTMP_DONT_HOLD + switch_ivr_hold(rsession->tech_pvt->session, NULL, SWITCH_TRUE); + switch_channel_mark_hold(rsession->tech_pvt->channel, SWITCH_FALSE); +#endif + if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, RTMP_EVENT_DETACH) == SWITCH_STATUS_SUCCESS) { + rtmp_event_fill(rsession, event); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Call-ID", + switch_core_session_get_uuid(rsession->tech_pvt->session)); + switch_event_fire(&event); + } + + rsession->tech_pvt = NULL; + } + + if (tech_pvt && switch_test_flag(tech_pvt, TFLAG_THREE_WAY)) { + const char *s = switch_channel_get_variable(tech_pvt->channel, RTMP_THREE_WAY_UUID_VARIABLE); + /* 2nd call of a three-way: attach to other call instead */ + if (!zstr(s)) { + tech_pvt = rtmp_locate_private(rsession, s); + } else { + tech_pvt = NULL; + } + } + + rsession->tech_pvt = tech_pvt; + + if (tech_pvt) { + /* Attach new call */ + switch_clear_flag_locked(tech_pvt, TFLAG_DETACHED); + +#ifndef RTMP_DONT_HOLD + if (switch_channel_test_flag(tech_pvt->channel, CF_HOLD)) { + switch_channel_mark_hold(tech_pvt->channel, SWITCH_FALSE); + switch_ivr_unhold(tech_pvt->session); + } +#endif + if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, RTMP_EVENT_ATTACH) == SWITCH_STATUS_SUCCESS) { + rtmp_event_fill(rsession, event); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Call-ID", switch_core_session_get_uuid(tech_pvt->session)); + switch_event_fire(&event); + } + } + + /* Let the UI know to which call it has connected */ + rtmp_session_send_onattach(rsession); +} + +rtmp_private_t *rtmp_locate_private(rtmp_session_t *rsession, const char *uuid) +{ + return switch_core_hash_find_rdlock(rsession->session_hash, uuid, rsession->session_rwlock); +} + +static switch_xml_config_item_t *get_instructions(rtmp_profile_t *profile) { + switch_xml_config_item_t *dup; + static switch_xml_config_int_options_t opt_chunksize = { + SWITCH_TRUE, /* enforce min */ + 128, + SWITCH_TRUE, /* Enforce Max */ + 65536 + }; + static switch_xml_config_int_options_t opt_bufferlen = { + SWITCH_FALSE, + 0, + SWITCH_TRUE, + UINT32_MAX + }; + switch_xml_config_item_t instructions[] = { + /* parameter name type reloadable pointer default value options structure */ + SWITCH_CONFIG_ITEM("context", SWITCH_CONFIG_STRING, CONFIG_RELOADABLE, &profile->context, "public", &switch_config_string_strdup, + "", "The dialplan context to use for inbound calls"), + SWITCH_CONFIG_ITEM("dialplan", SWITCH_CONFIG_STRING, CONFIG_RELOADABLE, &profile->dialplan, "XML", &switch_config_string_strdup, + "", "The dialplan to use for inbound calls"), + SWITCH_CONFIG_ITEM("bind-address", SWITCH_CONFIG_STRING, 0, &profile->bind_address, "0.0.0.0:1935", &switch_config_string_strdup, + "ip:port", "IP and port to bind"), + SWITCH_CONFIG_ITEM("io", SWITCH_CONFIG_STRING, 0, &profile->io_name, "tcp", &switch_config_string_strdup, + "io module", "I/O module to use (if unsure use tcp)"), + SWITCH_CONFIG_ITEM("auth-calls", SWITCH_CONFIG_BOOL, CONFIG_RELOADABLE, &profile->auth_calls, SWITCH_FALSE, NULL, "true|false", "Set to true in order to reject unauthenticated calls"), + SWITCH_CONFIG_ITEM("chunksize", SWITCH_CONFIG_INT, CONFIG_RELOADABLE, &profile->chunksize, 128, &opt_chunksize, "", "RTMP Sending chunksize"), + SWITCH_CONFIG_ITEM("buffer-len", SWITCH_CONFIG_INT, CONFIG_RELOADABLE, &profile->buffer_len, 500, &opt_bufferlen, "", "Length of the receiving buffer to be used by the flash clients, in miliseconds"), + SWITCH_CONFIG_ITEM_END() + }; + + dup = malloc(sizeof(instructions)); + memcpy(dup, instructions, sizeof(instructions)); + return dup; +} + +static switch_status_t config_profile(rtmp_profile_t *profile, switch_bool_t reload) +{ + switch_xml_t cfg, xml, x_profiles, x_profile, x_settings; + switch_status_t status = SWITCH_STATUS_FALSE; + switch_xml_config_item_t *instructions = (profile ? get_instructions(profile) : NULL); + switch_event_t *event = NULL; + int count; + const char *file = "rtmp.conf"; + + if (!(xml = switch_xml_open_cfg(file, &cfg, NULL))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Could not open %s\n", file); + goto done; + } + + if (!(x_profiles = switch_xml_child(cfg, "profiles"))) { + goto done; + } + + for (x_profile = switch_xml_child(x_profiles, "profile"); x_profile; x_profile = x_profile->next) { + const char *name = switch_xml_attr_soft(x_profile, "name"); + if (strcmp(name, profile->name)) { + continue; + } + + if (!(x_settings = switch_xml_child(x_profile, "settings"))) { + goto done; + } + + + count = switch_event_import_xml(switch_xml_child(x_settings, "param"), "name", "value", &event); + status = switch_xml_config_parse_event(event, count, reload, instructions); + } + + +done: + if (xml) { + switch_xml_free(xml); + } + switch_safe_free(instructions); + if (event) { + switch_event_destroy(&event); + } + return status; +} + +static void rtmp_event_handler(switch_event_t *event) +{ + rtmp_session_t *rsession; + const char *uuid; + + if (!event) { + return; + } + + uuid = switch_event_get_header(event, "RTMP-Session-ID"); + if (zstr(uuid)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "RTMP Custom event without RTMP-Session-ID\n"); + return; + } + + if ((rsession = rtmp_session_locate(uuid))) { + rtmp_send_event(rsession, event); + rtmp_session_rwunlock(rsession); + } +} + +#define RTMP_CONTACT_FUNCTION_SYNTAX "profile/user@domain[/[!]nickname]" +SWITCH_STANDARD_API(rtmp_contact_function) +{ + int argc; + char *argv[5]; + char *dup = NULL; + char *szprofile = NULL, *user = NULL; + const char *nickname = NULL; + rtmp_profile_t *profile = NULL; + rtmp_reg_t *reg; + switch_bool_t first = SWITCH_TRUE; + + if (zstr(cmd)) { + goto usage; + } + + dup = strdup(cmd); + argc = switch_split(dup, '/', argv); + + if (argc < 2 || zstr(argv[0]) || zstr(argv[1])) { + goto usage; + } + + szprofile = argv[0]; + if (!strchr(argv[1], '@')) { + goto usage; + } + + user = argv[1]; + nickname = argv[2]; + + if (!(profile = rtmp_profile_locate(szprofile))) { + stream->write_function(stream, "-ERR No such profile\n"); + goto done; + } + + switch_thread_rwlock_rdlock(profile->reg_rwlock); + if ((reg = switch_core_hash_find(profile->reg_hash, user))) { + for (; reg; reg = reg->next) { + if (zstr(nickname) || + (nickname[0] == '!' && (zstr(reg->nickname) || strcmp(reg->nickname, nickname+1))) || + (!zstr(reg->nickname) && !strcmp(reg->nickname, nickname))) { + if (!first) { + stream->write_function(stream, ","); + } else { + first = SWITCH_FALSE; + } + stream->write_function(stream, "rtmp/%s/%s", reg->uuid, user); + } + } + } else { + stream->write_function(stream, "error/user_not_registered"); + } + switch_thread_rwlock_unlock(profile->reg_rwlock); + goto done; + +usage: + stream->write_function(stream, "Usage: rtmp_contact "RTMP_CONTACT_FUNCTION_SYNTAX"\n"); + +done: + if (profile) { + rtmp_profile_release(profile); + } + switch_safe_free(dup); + return SWITCH_STATUS_SUCCESS; +} + +#define RTMP_FUNCTION_SYNTAX "profile [profilename] [start | stop | rescan | restart]\nstatus profile [profilename]\nstatus profile [profilename] [reg | sessions]\nsession [session_id] [kill | login [user@domain] | logout [user@domain]]" +SWITCH_STANDARD_API(rtmp_function) +{ + int argc; + char *argv[10]; + char *dup = NULL; + + if (zstr(cmd)) { + goto usage; + } + + dup = strdup(cmd); + argc = switch_split(dup, ' ', argv); + + if (argc < 1 || zstr(argv[0])) { + goto usage; + } + + if (!strcmp(argv[0], "profile")) { + if (zstr(argv[1]) || zstr(argv[2])) { + goto usage; + } + if (!strcmp(argv[2], "start")) { + rtmp_profile_t *profile = rtmp_profile_locate(argv[1]); + if (profile) { + rtmp_profile_release(profile); + stream->write_function(stream, "-ERR Profile %s is already started\n", argv[2]); + } else { + rtmp_profile_start(argv[1]); + stream->write_function(stream, "+OK\n"); + } + } else if (!strcmp(argv[2], "stop")) { + rtmp_profile_t *profile = rtmp_profile_locate(argv[1]); + if (profile) { + rtmp_profile_release(profile); + rtmp_profile_destroy(&profile); + stream->write_function(stream, "+OK\n"); + } else { + stream->write_function(stream, "-ERR No such profile\n"); + } + } else if (!strcmp(argv[2], "rescan")) { + rtmp_profile_t *profile = rtmp_profile_locate(argv[1]); + if (config_profile(profile, SWITCH_TRUE) == SWITCH_STATUS_SUCCESS) { + stream->write_function(stream, "+OK\n"); + } else { + stream->write_function(stream, "-ERR Config error\n"); + } + rtmp_profile_release(profile); + } else if (!strcmp(argv[2], "restart")) { + rtmp_profile_t *profile = rtmp_profile_locate(argv[1]); + if (profile) { + rtmp_profile_release(profile); + rtmp_profile_destroy(&profile); + rtmp_profile_start(argv[1]); + stream->write_function(stream, "+OK\n"); + } else { + rtmp_profile_start(argv[1]); + stream->write_function(stream, "-OK (wasn't started, started anyways)\n"); + } + } else { + goto usage; + } + } else if (!strcmp(argv[0], "status")) { + if (!zstr(argv[1]) && !strcmp(argv[1], "profile") && !zstr(argv[2])) { + rtmp_profile_t *profile; + + if ((profile = rtmp_profile_locate(argv[2]))) { + stream->write_function(stream, "Profile: %s\n", profile->name); + stream->write_function(stream, "I/O Backend: %s\n", profile->io->name); + stream->write_function(stream, "Bind address: %s\n", profile->io->address); + stream->write_function(stream, "Active calls: %d\n", profile->calls); + + if (!zstr(argv[3]) && !strcmp(argv[3], "sessions")) + { + switch_hash_index_t *hi; + stream->write_function(stream, "\nSessions:\n"); + stream->write_function(stream, "uuid,address,user,domain,flashVer\n"); + switch_thread_rwlock_rdlock(profile->session_rwlock); + for (hi = switch_hash_first(NULL, profile->session_hash); hi; hi = switch_hash_next(hi)) { + void *val; + const void *key; + switch_ssize_t keylen; + rtmp_session_t *item; + switch_hash_this(hi, &key, &keylen, &val); + + item = (rtmp_session_t *)val; + stream->write_function(stream, "%s,%s:%d,%s,%s,%s\n", + item->uuid, item->remote_address, item->remote_port, + item->account ? item->account->user : NULL, + item->account ? item->account->domain : NULL, + item->flashVer); + + } + switch_thread_rwlock_unlock(profile->session_rwlock); + } else if (!zstr(argv[3]) && !strcmp(argv[3], "reg")) { + switch_hash_index_t *hi; + stream->write_function(stream, "\nRegistrations:\n"); + stream->write_function(stream, "user,nickname,uuid\n"); + + switch_thread_rwlock_rdlock(profile->reg_rwlock); + for (hi = switch_hash_first(NULL, profile->reg_hash); hi; hi = switch_hash_next(hi)) { + void *val; + const void *key; + switch_ssize_t keylen; + rtmp_reg_t *item; + switch_hash_this(hi, &key, &keylen, &val); + + item = (rtmp_reg_t *)val; + for (;item;item = item->next) { + stream->write_function(stream, "%s,%s,%s\n", + key, switch_str_nil(item->nickname), item->uuid); + } + } + switch_thread_rwlock_unlock(profile->reg_rwlock); + } else { + stream->write_function(stream, "Profile: %s\n", profile->name); + stream->write_function(stream, "I/O Backend: %s\n", profile->io->name); + stream->write_function(stream, "Bind address: %s\n", profile->io->address); + stream->write_function(stream, "Active calls: %d\n", profile->calls); + stream->write_function(stream, "Dialplan: %s\n", profile->dialplan); + stream->write_function(stream, "Context: %s\n", profile->context); + } + + rtmp_profile_release(profile); + } else { + stream->write_function(stream, "-ERR No such profile [%s]\n", argv[2]); + } + } else { + switch_hash_index_t *hi; + switch_thread_rwlock_rdlock(rtmp_globals.profile_rwlock); + for (hi = switch_hash_first(NULL, rtmp_globals.profile_hash); hi; hi = switch_hash_next(hi)) { + void *val; + const void *key; + switch_ssize_t keylen; + rtmp_profile_t *item; + switch_hash_this(hi, &key, &keylen, &val); + + item = (rtmp_profile_t *)val; + stream->write_function(stream, "%s\t%s:%s\tprofile\n", item->name, item->io->name, item->io->address); + + } + switch_thread_rwlock_unlock(rtmp_globals.profile_rwlock); + } + + } else if (!strcmp(argv[0], "session")) { + rtmp_session_t *rsession; + + if (zstr(argv[1]) || zstr(argv[2])) { + goto usage; + } + + rsession = rtmp_session_locate(argv[1]); + if (!rsession) { + stream->write_function(stream, "-ERR No such session\n"); + goto done; + } + + if (!strcmp(argv[2], "login")) { + char *user, *domain; + if (zstr(argv[3])) { + goto usage; + } + switch_split_user_domain(argv[3], &user, &domain); + + if (!zstr(user) && !zstr(domain)) { + rtmp_session_login(rsession, user, domain); + stream->write_function(stream, "+OK\n"); + } else { + stream->write_function(stream, "-ERR I need user@domain\n"); + } + } else if (!strcmp(argv[2], "logout")) { + char *user, *domain; + if (zstr(argv[3])) { + goto usage; + } + switch_split_user_domain(argv[3], &user, &domain); + + if (!zstr(user) && !zstr(domain)) { + rtmp_session_logout(rsession, user, domain); + stream->write_function(stream, "+OK\n"); + } else { + stream->write_function(stream, "-ERR I need user@domain\n"); + } + } else if (!strcmp(argv[2], "kill")) { + rtmp_session_rwunlock(rsession); + rtmp_session_destroy(&rsession); + stream->write_function(stream, "+OK\n"); + } else if (!strcmp(argv[2], "call")) { + switch_core_session_t *newsession = NULL; + char *dest = argv[3]; + char *user = argv[4]; + char *domain = NULL; + + if (!zstr(user) && (domain = strchr(user, '@'))) { + *domain++ = '\0'; + } + + if (!zstr(dest)) { + if (rtmp_session_create_call(rsession, &newsession, 0, RTMP_DEFAULT_STREAM_AUDIO, dest, user, domain, NULL) != SWITCH_STATUS_SUCCESS) { + stream->write_function(stream, "-ERR Couldn't create new call\n"); + } else { + rtmp_private_t *new_pvt = switch_core_session_get_private(newsession); + rtmp_send_invoke_free(rsession, 3, 0, 0, + amf0_str("onMakeCall"), + amf0_number_new(0), + amf0_null_new(), + amf0_str(switch_core_session_get_uuid(newsession)), + amf0_str(switch_str_nil(dest)), + amf0_str(switch_str_nil(new_pvt->auth)), + NULL); + + rtmp_attach_private(rsession, switch_core_session_get_private(newsession)); + stream->write_function(stream, "+OK\n"); + } + } else { + stream->write_function(stream, "-ERR Missing destination number\n"); + } + } else if (!strcmp(argv[2], "ping")) { + rtmp_ping(rsession); + stream->write_function(stream, "+OK\n"); + } else { + stream->write_function(stream, "-ERR No such session action [%s]\n", argv[2]); + } + + if (rsession) { + rtmp_session_rwunlock(rsession); + } + } else { + goto usage; + } + + goto done; + +usage: + stream->write_function(stream, "-ERR Usage: "RTMP_FUNCTION_SYNTAX"\n"); + +done: + switch_safe_free(dup); + return SWITCH_STATUS_SUCCESS; +} + +static inline void rtmp_register_invoke_function(const char *name, rtmp_invoke_function_t func) +{ + switch_core_hash_insert(rtmp_globals.invoke_hash, name, (void*)(intptr_t)func); +} + +static switch_status_t console_complete_hashtable(switch_hash_t *hash, const char *line, const char *cursor, switch_console_callback_match_t **matches) +{ + switch_hash_index_t *hi; + void *val; + const void *vvar; + switch_console_callback_match_t *my_matches = NULL; + switch_status_t status = SWITCH_STATUS_FALSE; + + for (hi = switch_hash_first(NULL, hash); hi; hi = switch_hash_next(hi)) { + switch_hash_this(hi, &vvar, NULL, &val); + switch_console_push_match(&my_matches, (const char *) vvar); + } + + if (my_matches) { + *matches = my_matches; + status = SWITCH_STATUS_SUCCESS; + } + + return status; +} + +static switch_status_t list_sessions(const char *line, const char *cursor, switch_console_callback_match_t **matches) +{ + switch_status_t status; + switch_thread_rwlock_rdlock(rtmp_globals.session_rwlock); + status = console_complete_hashtable(rtmp_globals.session_hash, line, cursor, matches); + switch_thread_rwlock_unlock(rtmp_globals.session_rwlock); + return status; +} + + +static switch_status_t list_profiles(const char *line, const char *cursor, switch_console_callback_match_t **matches) +{ + switch_status_t status; + switch_thread_rwlock_rdlock(rtmp_globals.profile_rwlock); + status = console_complete_hashtable(rtmp_globals.profile_hash, line, cursor, matches); + switch_thread_rwlock_unlock(rtmp_globals.profile_rwlock); + return status; +} + +SWITCH_MODULE_LOAD_FUNCTION(mod_rtmp_load) +{ + switch_api_interface_t *api_interface; + rtmp_globals.pool = pool; + + memset(&rtmp_globals, 0, sizeof(rtmp_globals)); + + switch_mutex_init(&rtmp_globals.mutex, SWITCH_MUTEX_NESTED, pool); + switch_core_hash_init(&rtmp_globals.profile_hash, pool); + switch_core_hash_init(&rtmp_globals.session_hash, pool); + switch_core_hash_init(&rtmp_globals.invoke_hash, pool); + switch_thread_rwlock_create(&rtmp_globals.profile_rwlock, pool); + switch_thread_rwlock_create(&rtmp_globals.session_rwlock, pool); + + rtmp_register_invoke_function("connect", rtmp_i_connect); + rtmp_register_invoke_function("createStream", rtmp_i_createStream); + rtmp_register_invoke_function("closeStream", rtmp_i_noop); + rtmp_register_invoke_function("deleteStream", rtmp_i_noop); + rtmp_register_invoke_function("play", rtmp_i_play); + rtmp_register_invoke_function("publish", rtmp_i_publish); + rtmp_register_invoke_function("makeCall", rtmp_i_makeCall); + rtmp_register_invoke_function("login", rtmp_i_login); + rtmp_register_invoke_function("logout", rtmp_i_logout); + rtmp_register_invoke_function("sendDTMF", rtmp_i_sendDTMF); + rtmp_register_invoke_function("register", rtmp_i_register); + rtmp_register_invoke_function("unregister", rtmp_i_unregister); + rtmp_register_invoke_function("answer", rtmp_i_answer); + rtmp_register_invoke_function("attach", rtmp_i_attach); + rtmp_register_invoke_function("hangup", rtmp_i_hangup); + rtmp_register_invoke_function("transfer", rtmp_i_transfer); + rtmp_register_invoke_function("three_way", rtmp_i_three_way); + rtmp_register_invoke_function("join", rtmp_i_join); + rtmp_register_invoke_function("sendevent", rtmp_i_sendevent); + rtmp_register_invoke_function("receiveAudio", rtmp_i_receiveaudio); + rtmp_register_invoke_function("receiveVideo", rtmp_i_receivevideo); + rtmp_register_invoke_function("log", rtmp_i_log); + + *module_interface = switch_loadable_module_create_module_interface(pool, modname); + rtmp_globals.rtmp_endpoint_interface = switch_loadable_module_create_interface(*module_interface, SWITCH_ENDPOINT_INTERFACE); + rtmp_globals.rtmp_endpoint_interface->interface_name = "rtmp"; + rtmp_globals.rtmp_endpoint_interface->io_routines = &rtmp_io_routines; + rtmp_globals.rtmp_endpoint_interface->state_handler = &rtmp_state_handlers; + + SWITCH_ADD_API(api_interface, "rtmp", "rtmp management", rtmp_function, RTMP_FUNCTION_SYNTAX); + SWITCH_ADD_API(api_interface, "rtmp_contact", "rtmp contact", rtmp_contact_function, RTMP_CONTACT_FUNCTION_SYNTAX); + + switch_console_set_complete("add rtmp status"); + switch_console_set_complete("add rtmp status profile ::rtmp::list_profiles"); + switch_console_set_complete("add rtmp status profile ::rtmp::list_profiles sessions"); + switch_console_set_complete("add rtmp status profile ::rtmp::list_profiles reg"); + switch_console_set_complete("add rtmp profile ::rtmp::list_profiles start"); + switch_console_set_complete("add rtmp profile ::rtmp::list_profiles stop"); + switch_console_set_complete("add rtmp profile ::rtmp::list_profiles restart"); + switch_console_set_complete("add rtmp profile ::rtmp::list_profiles rescan"); + switch_console_set_complete("add rtmp session ::rtmp::list_sessions kill"); + switch_console_set_complete("add rtmp session ::rtmp::list_sessions login"); + switch_console_set_complete("add rtmp session ::rtmp::list_sessions logout"); + + switch_console_add_complete_func("::rtmp::list_profiles", list_profiles); + switch_console_add_complete_func("::rtmp::list_sessions", list_sessions); + + switch_event_bind("mod_rtmp", SWITCH_EVENT_CUSTOM, RTMP_EVENT_CUSTOM, rtmp_event_handler, NULL); + + { + switch_xml_t cfg, xml, x_profiles, x_profile; + const char *file = "rtmp.conf"; + + if (!(xml = switch_xml_open_cfg(file, &cfg, NULL))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Could not open %s\n", file); + goto done; + } + + if (!(x_profiles = switch_xml_child(cfg, "profiles"))) { + goto done; + } + + for (x_profile = switch_xml_child(x_profiles, "profile"); x_profile; x_profile = x_profile->next) { + const char *name = switch_xml_attr_soft(x_profile, "name"); + rtmp_profile_start(name); + } + done: + if (xml) { + switch_xml_free(xml); + } + } + + return SWITCH_STATUS_SUCCESS; +} + +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_rtmp_shutdown) +{ + switch_hash_index_t *hi; + + switch_mutex_lock(rtmp_globals.mutex); + while ((hi = switch_hash_first(NULL, rtmp_globals.profile_hash))) { + void *val; + const void *key; + switch_ssize_t keylen; + rtmp_profile_t *item; + switch_hash_this(hi, &key, &keylen, &val); + + item = (rtmp_profile_t *)val; + + switch_mutex_unlock(rtmp_globals.mutex); + rtmp_profile_destroy(&item); + switch_mutex_lock(rtmp_globals.mutex); + } + switch_mutex_unlock(rtmp_globals.mutex); + + switch_event_unbind_callback(rtmp_event_handler); + + switch_core_hash_destroy(&rtmp_globals.profile_hash); + switch_core_hash_destroy(&rtmp_globals.session_hash); + switch_core_hash_destroy(&rtmp_globals.invoke_hash); + + return SWITCH_STATUS_SUCCESS; +} + +/* For Emacs: + * Local Variables: + * mode:c + * indent-tabs-mode:t + * tab-width:4 + * c-basic-offset:4 + * End: + * For VIM: + * vim:set softtabstop=4 shiftwidth=4 tabstop=4: + */ diff --git a/src/mod/endpoints/mod_rtmp/mod_rtmp.h b/src/mod/endpoints/mod_rtmp/mod_rtmp.h new file mode 100644 index 0000000000..5d0f853335 --- /dev/null +++ b/src/mod/endpoints/mod_rtmp/mod_rtmp.h @@ -0,0 +1,638 @@ +/* + * mod_rtmp for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2011, Barracuda Networks Inc. + * + * Version: MPL 1.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mod_rtmp for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * + * The Initial Developer of the Original Code is Barracuda Networks Inc. + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Mathieu Rene + * + * mod_rtmp.h -- RTMP Endpoint Module + * + */ + +#ifndef MOD_RTMP_H +#define MOD_RTMP_H +#include + +/* AMF */ +#include "amf0.h" +#include "io.h" +#include "types.h" + +//#define RTMP_DEBUG_IO +#define RTMP_DONT_HOLD + +#define RTMP_THREE_WAY_UUID_VARIABLE "rtmp_three_way_uuid" +#define RTMP_ATTACH_ON_HANGUP_VARIABLE "rtmp_attach_on_hangup" +#define RTMP_USER_VARIABLE_PREFIX "rtmp_u_" + +#define RTMP_DEFAULT_PORT 1935 +#define RTMP_TCP_READ_BUF 2048 +#define AMF_MAX_SIZE 2048 + +#define SUPPORT_SND_NONE 0x0000 +#define SUPPORT_SND_ADPCM 0x0002 +#define SUPPORT_SND_MP3 0x0004 +#define SUPPORT_SND_INTEL 0x0008 +#define SUPPORT_SND_UNUSED 0x0010 +#define SUPPORT_SND_NELLY8 0x0020 +#define SUPPORT_SND_NELLY 0x0040 +#define SUPPORT_SND_G711A 0x0080 +#define SUPPORT_SND_G711U 0x0100 +#define SUPPORT_SND_NELLY16 0x0200 +#define SUPPORT_SND_AAC 0x0400 +#define SUPPORT_SND_SPEEX 0x0800 +#define SUPPORT_SND_ALL 0x0FFF + +#define SUPPORT_VID_UNUSED 0x0001 +#define SUPPORT_VID_JPEG 0x0002 +#define SUPPORT_VID_SORENSON 0x0004 +#define SUPPORT_VID_HOMEBREW 0x0008 +#define SUPPORT_VID_VP6 0x0010 +#define SUPPORT_VID_VP6ALPHA 0x0020 +#define SUPPORT_VID_HOMEBREWV 0x0040 +#define SUPPORT_VID_H264 0x0080 +#define SUPPORT_VID_ALL 0x00FF + +#define SUPPORT_VID_CLIENT_SEEK 1 + +#define kAMF0 0 +#define kAMF3 3 + +#define RTMP_DEFAULT_ACK_WINDOW 0x20000 + +#define RTMP_TYPE_CHUNKSIZE 0x01 +#define RTMP_TYPE_ABORT 0x2 +#define RTMP_TYPE_ACK 0x3 +#define RTMP_TYPE_USERCTRL 0x04 +#define RTMP_TYPE_WINDOW_ACK_SIZE 0x5 +#define RTMP_TYPE_SET_PEER_BW 0x6 +#define RTMP_TYPE_AUDIO 0x08 +#define RTMP_TYPE_VIDEO 0x09 +#define RTMP_TYPE_METADATA 0x12 +#define RTMP_TYPE_INVOKE 0x14 +#define RTMP_TYPE_NOTIFY 0x12 + +#define RTMP_CTRL_STREAM_BEGIN 0x00 +#define RTMP_CTRL_STREAM_EOF 0x01 +#define RTMP_CTRL_STREAM_DRY 0x02 +#define RTMP_CTRL_SET_BUFFER_LENGTH 0x03 +#define RTMP_CTRL_STREAM_IS_RECORDED 0x04 +#define RTMP_CTRL_PING_REQUEST 0x06 +#define RTMP_CTRL_PING_RESPONSE 0x07 + +#define RTMP_DEFAULT_STREAM_CONTROL 0x02 +#define RTMP_DEFAULT_STREAM_INVOKE 0x03 +#define RTMP_DEFAULT_STREAM_NOTIFY 0x05 +#define RTMP_DEFAULT_STREAM_VIDEO 0x07 +#define RTMP_DEFAULT_STREAM_AUDIO 0x06 + + +#define RTMP_MSGSTREAM_DEFAULT 0x0 +/* It seems everything media-related (play/onStatus and the actual audio data are using this stream) */ +#define RTMP_MSGSTREAM_MEDIA 0x01 + +#define RTMP_EVENT_CONNECT "rtmp::connect" +#define RTMP_EVENT_DISCONNECT "rtmp::disconnect" +#define RTMP_EVENT_REGISTER "rtmp::register" +#define RTMP_EVENT_UNREGISTER "rtmp::unregister" +#define RTMP_EVENT_LOGIN "rtmp::login" +#define RTMP_EVENT_LOGOUT "rtmp::logout" +#define RTMP_EVENT_ATTACH "rtmp::attach" +#define RTMP_EVENT_DETACH "rtmp::detach" +#define RTMP_EVENT_CUSTOM "rtmp::custom" +#define RTMP_EVENT_CLIENTCUSTOM "rtmp::clientcustom" + +#define INT32_LE(x) (x) & 0xFF, ((x) >> 8) & 0xFF, ((x) >> 16) & 0xFF, ((x) >> 24) & 0xFF +#define INT32(x) ((x) >> 24) & 0xFF, ((x) >> 16) & 0xFF, ((x) >> 8) & 0xFF, (x) & 0xFF +#define INT24(x) ((x) >> 16) & 0xFF, ((x) >> 8) & 0xFF, (x) & 0xFF +#define INT16(x) ((x) >> 8) & 0xFF, (x) & 0xFF + + +typedef enum { + RTMP_AUDIO_PCM = 0, + RTMP_AUDIO_ADPCM = 1, + RTMP_AUDIO_MP3 = 2, + RTMP_AUDIO_NELLYMOSER_8K_MONO= 5, + RTMP_AUDIO_NELLYMOSER = 6, + RTMP_AUDIO_SPEEX = 11 +} rtmp_audio_format_t; + +/* + +From: http://osflash.org/flv + +0x08: AUDIO +The first byte of an audio packet contains bitflags that describe the codec used, with the following layout: + + + +Name Expression Description +soundType (byte & 0×01) » 0 0: mono, 1: stereo +soundSize (byte & 0×02) » 1 0: 8-bit, 1: 16-bit +soundRate (byte & 0x0C) » 2 0: 5.5 kHz, 1: 11 kHz, 2: 22 kHz, 3: 44 kHz +soundFormat (byte & 0xf0) » 4 0: Uncompressed, 1: ADPCM, 2: MP3, 5: Nellymoser 8kHz mono, 6: Nellymoser, 11: Speex + +0x09: VIDEO +The first byte of a video packet describes contains bitflags that describe the codec used, and the type of frame + +Name Expression Description +codecID (byte & 0x0f) » 0 2: Sorensen H.263, 3: Screen video, 4: On2 VP6, 5: On2 VP6 Alpha, 6: ScreenVideo 2 +frameType (byte & 0xf0) » 4 1: keyframe, 2: inter frame, 3: disposable inter frame + +0x12: META +The contents of a meta packet are two AMF packets. +The first is almost always a short uint16_be length-prefixed UTF-8 string (AMF type 0×02), +and the second is typically a mixed array (AMF type 0×08). However, the second chunk typically contains a variety of types, +so a full AMF parser should be used. +*/ + + +static inline int rtmp_audio_codec_get_channels(uint8_t codec) { + return (codec & 0x01) ? 2 : 1; +} + +static inline int rtmp_audio_codec_get_sample_size(uint8_t codec) { + return (codec & 0x02) ? 16 : 8; +} + +static inline int rtmp_audio_codec_get_rate(uint8_t codec) { + switch(codec & 0x0C) { + case 0: + return 5500; + case 1: + return 11000; + case 2: + return 22000; + case 3: + return 44000; + default: + return 0; + } +} + +static inline rtmp_audio_format_t rtmp_audio_codec_get_format(uint8_t codec) { + return (rtmp_audio_format_t)(codec & 0xf0); +} + +static inline uint8_t rtmp_audio_codec(int channels, int bits, int rate, rtmp_audio_format_t format) { + uint8_t codec = 0; + + switch (channels) { + case 1: + break; + case 2: + codec |= 1; + default: + return 0; + } + + switch (bits) { + case 8: + break; + case 16: + codec |= 2; + default: + return 0; + } + + switch (rate) { + case 0: + case 5500: + break; + case 11000: + codec |= 0x4; + break; + case 22000: + codec |= 0x8; + break; + case 44000: + codec |= 0xC; + default: + return 0; + } + + switch(format) { + case RTMP_AUDIO_PCM: + break; + case RTMP_AUDIO_ADPCM: + codec |= 0x10; + break; + case RTMP_AUDIO_MP3: + codec |= 0x20; + break; + case RTMP_AUDIO_NELLYMOSER_8K_MONO: + codec |= 0x50; + break; + case RTMP_AUDIO_NELLYMOSER: + codec |= 0x60; + break; + case RTMP_AUDIO_SPEEX: + codec |= 0x80; + break; + default: + return 0; + } + + return codec; +} + + +struct rtmp_session; +typedef struct rtmp_session rtmp_session_t; + +struct rtmp_profile; +typedef struct rtmp_profile rtmp_profile_t; + +typedef struct rtmp_state rtmp_state_t; + +#define RTMP_INVOKE_FUNCTION_ARGS rtmp_session_t *rsession, rtmp_state_t *state, int amfnumber, int transaction_id, int argc, amf0_data *argv[] + +typedef switch_status_t (*rtmp_invoke_function_t)(RTMP_INVOKE_FUNCTION_ARGS); + +#define RTMP_INVOKE_FUNCTION(_x) switch_status_t _x (RTMP_INVOKE_FUNCTION_ARGS) + +/* AMF Helpers */ + +#define amf0_is_string(_x) (_x && (_x)->type == AMF0_TYPE_STRING) +#define amf0_is_number(_x) (_x && (_x)->type == AMF0_TYPE_NUMBER) +#define amf0_is_boolean(_x) (_x && (_x)->type == AMF0_TYPE_BOOLEAN) +#define amf0_is_object(_x) (_x && (_x)->type == AMF0_TYPE_OBJECT) + +static inline char *amf0_get_string(amf0_data *x) +{ + return (amf0_is_string(x) ? (char*)amf0_string_get_uint8_ts(x) : NULL); +} + +static inline int amf0_get_number(amf0_data *x) +{ + return (amf0_is_number(x) ? amf0_number_get_value(x) : 0); +} + +static inline switch_bool_t amf0_get_boolean(amf0_data *x) +{ + return (amf0_is_boolean(x) ? amf0_boolean_get_value(x) : SWITCH_FALSE); +} + +struct rtmp_io { + switch_status_t (*read)(rtmp_session_t *rsession, unsigned char *buf, switch_size_t *len); + switch_status_t (*write)(rtmp_session_t *rsession, const unsigned char *buf, switch_size_t *len); + switch_status_t (*close)(rtmp_session_t *rsession); + rtmp_profile_t *profile; + switch_memory_pool_t *pool; + int running; + const char *name; + const char *address; +}; + +typedef struct rtmp_io rtmp_io_t; + +typedef enum { + TFLAG_IO = (1 << 0), + TFLAG_DETACHED = (1 << 1), /* Call isn't the current active call */ + TFLAG_BREAK = (1 << 2), + TFLAG_THREE_WAY = (1 << 3), /* In a three-way call */ + TFLAG_VID_WAIT_KEYFRAME = (1 << 4) /* Wait for video keyframe */ +} TFLAGS; + + +/* Session flags */ +typedef enum { + SFLAG_AUDIO = (1 << 0), /* < Send audio */ + SFLAG_VIDEO = (1 << 1) /* < Send video */ +} SFLAGS; + +typedef enum { + PFLAG_RUNNING = (1 << 0) +} PFLAGS; + +struct mod_rtmp_globals { + switch_endpoint_interface_t *rtmp_endpoint_interface; + switch_memory_pool_t *pool; + switch_mutex_t *mutex; + switch_hash_t *profile_hash; + switch_thread_rwlock_t *profile_rwlock; + switch_hash_t *session_hash; + switch_thread_rwlock_t *session_rwlock; + switch_hash_t *invoke_hash; +}; + +extern struct mod_rtmp_globals rtmp_globals; + +struct rtmp_profile { + char *name; /* < Profile name */ + switch_memory_pool_t *pool; /* < Memory pool */ + rtmp_io_t *io; /* < IO Module instance */ + switch_thread_rwlock_t *rwlock; /* < Rwlock for reference counting */ + uint32_t flags; /* < PFLAGS */ + switch_mutex_t *mutex; /* < Mutex for call count */ + int calls; /* < Active calls count */ + int clients; /* < Number of connected clients */ + switch_hash_t *session_hash; /* < Active rtmp sessions */ + switch_thread_rwlock_t *session_rwlock; /* < rwlock for session hashtable */ + const char *context; /* < Default dialplan name */ + const char *dialplan; /* < Default dialplan context */ + const char *bind_address; /* < Bind address */ + const char *io_name; /* < Name of I/O module (from config) */ + int chunksize; /* < Override default chunksize (from config) */ + int buffer_len; /* < Receive buffer length the flash clients should use */ + + switch_hash_t *reg_hash; /* < Registration hashtable */ + switch_thread_rwlock_t *reg_rwlock; /* < Registration hash rwlock */ + + switch_bool_t auth_calls; /* < Require authentiation */ +}; + +typedef struct { + unsigned ts:24; + unsigned len:24; + unsigned type:8; + unsigned src:16; + unsigned dst:16; +} rtmp_hdr_t; + +#define RTMP_DEFAULT_CHUNKSIZE 128 + +struct rtmp_state { + union { + char sz[12]; + rtmp_hdr_t packed; + } header; + int remainlen; + int origlen; + + uint32_t ts; /* 24 bits max */ + uint32_t ts_delta; /* 24 bits max */ + uint8_t type; + uint32_t stream_id; + unsigned char buf[AMF_MAX_SIZE]; + switch_size_t buf_pos; +}; + + +typedef enum { + RS_HANDSHAKE = 0, + RS_HANDSHAKE2 = 1, + RS_ESTABLISHED = 2, + RS_DESTROY = 3 +} rtmp_session_state_t; + +struct rtmp_private; +typedef struct rtmp_private rtmp_private_t; + + +struct rtmp_account; +typedef struct rtmp_account rtmp_account_t; + +struct rtmp_account { + const char *user; + const char *domain; + rtmp_account_t *next; +}; + +struct rtmp_session { + switch_memory_pool_t *pool; + rtmp_profile_t *profile; + char uuid[SWITCH_UUID_FORMATTED_LENGTH+1]; + void *io_private; + + rtmp_session_state_t state; + int parse_state; + uint16_t parse_remain; /* < Remaining bytes required before changing parse state */ + + int hdrsize; /* < The current header size */ + int amfnumber; /* < The current AMF number */ + + rtmp_state_t amfstate[64]; + rtmp_state_t amfstate_out[64]; + + switch_mutex_t *socket_mutex; + switch_mutex_t *count_mutex; + int active_sessions; + + unsigned char hsbuf[2048]; + int hspos; + uint16_t in_chunksize; + uint16_t out_chunksize; + + /* Connect params */ + const char *flashVer; + const char *swfUrl; + const char *tcUrl; + const char *app; + const char *pageUrl; + + uint32_t capabilities; + uint32_t audioCodecs; + uint32_t videoCodecs; + uint32_t videoFunction; + + switch_thread_rwlock_t *rwlock; + + rtmp_private_t *tech_pvt; /* < Active call's tech_pvt */ +#ifdef RTMP_DEBUG_IO + FILE *io_debug_in; + FILE *io_debug_out; +#endif + + const char *remote_address; + switch_port_t remote_port; + + switch_hash_t *session_hash; /* < Hash of call uuids and tech_pvt */ + switch_thread_rwlock_t *session_rwlock; /* < RWLock protecting session_hash */ + + rtmp_account_t *account; + switch_thread_rwlock_t *account_rwlock; + uint_least32_t flags; + + int8_t sendAudio, sendVideo; + uint64_t recv_ack_window; /* < ACK Window */ + uint64_t recv_ack_sent; /* < Bytes ack'd */ + uint64_t recv; /* < Bytes received */ + + uint32_t send_ack_window; + uint32_t send_ack; + uint32_t send; + switch_time_t send_ack_ts; + + uint32_t send_bw; /* < Current send bandwidth (in bytes/sec) */ + + uint32_t next_streamid; /* < The next stream id that will be used */ + uint32_t active_streamid; /* < The stream id returned by the last call to createStream */ + + uint32_t media_streamid; /* < The stream id that was used for the last "play" command, + where we should send media */ +}; + +struct rtmp_private { + unsigned int flags; + switch_codec_t read_codec; + switch_codec_t write_codec; + + switch_frame_t read_frame; + unsigned char databuf[SWITCH_RECOMMENDED_BUFFER_SIZE]; /* < Buffer for read_frame */ + + switch_caller_profile_t *caller_profile; + + switch_mutex_t *mutex; + switch_mutex_t *flag_mutex; + + switch_core_session_t *session; + switch_channel_t *channel; + rtmp_session_t *rtmp_session; + + int read_channel; /* RTMP channel #s for read and write */ + int write_channel; + uint8_t audio_codec; + uint8_t video_codec; + + switch_time_t stream_start_ts; + switch_timer_t timer; + switch_buffer_t *readbuf; + switch_mutex_t *readbuf_mutex; + + const char *display_callee_id_name; + const char *display_callee_id_number; + + const char *auth_user; + const char *auth_domain; + const char *auth; +}; + +struct rtmp_reg; +typedef struct rtmp_reg rtmp_reg_t; + +struct rtmp_reg { + const char *uuid; /* < The rtmp session id */ + const char *nickname; /* < This instance's nickname, optional */ + rtmp_reg_t *next; /* < Next entry */ +}; + + +typedef enum { + MSG_FULLHEADER = 1 +} rtmp_message_send_flag_t; + + +/* Invokable functions from flash */ +RTMP_INVOKE_FUNCTION(rtmp_i_connect); +RTMP_INVOKE_FUNCTION(rtmp_i_createStream); +RTMP_INVOKE_FUNCTION(rtmp_i_noop); +RTMP_INVOKE_FUNCTION(rtmp_i_play); +RTMP_INVOKE_FUNCTION(rtmp_i_publish); +RTMP_INVOKE_FUNCTION(rtmp_i_makeCall); +RTMP_INVOKE_FUNCTION(rtmp_i_sendDTMF); +RTMP_INVOKE_FUNCTION(rtmp_i_login); +RTMP_INVOKE_FUNCTION(rtmp_i_logout); +RTMP_INVOKE_FUNCTION(rtmp_i_register); +RTMP_INVOKE_FUNCTION(rtmp_i_unregister); +RTMP_INVOKE_FUNCTION(rtmp_i_answer); +RTMP_INVOKE_FUNCTION(rtmp_i_attach); +RTMP_INVOKE_FUNCTION(rtmp_i_hangup); +RTMP_INVOKE_FUNCTION(rtmp_i_transfer); +RTMP_INVOKE_FUNCTION(rtmp_i_three_way); +RTMP_INVOKE_FUNCTION(rtmp_i_join); +RTMP_INVOKE_FUNCTION(rtmp_i_sendevent); +RTMP_INVOKE_FUNCTION(rtmp_i_receiveaudio); +RTMP_INVOKE_FUNCTION(rtmp_i_receivevideo); +RTMP_INVOKE_FUNCTION(rtmp_i_log); + +/*** RTMP Sessions ***/ +rtmp_session_t *rtmp_session_locate(const char *uuid); +void rtmp_session_rwunlock(rtmp_session_t *rsession); + +switch_status_t rtmp_session_login(rtmp_session_t *rsession, const char *user, const char *domain); +switch_status_t rtmp_session_logout(rtmp_session_t *rsession, const char *user, const char *domain); +switch_status_t rtmp_session_check_user(rtmp_session_t *rsession, const char *user, const char *domain); + +switch_status_t rtmp_check_auth(rtmp_session_t *rsession, const char *user, const char *domain, const char *authmd5); +void rtmp_event_fill(rtmp_session_t *rsession, switch_event_t *event); +switch_status_t amf_object_to_event(amf0_data *obj, switch_event_t **event); +switch_status_t amf_event_to_object(amf0_data **obj, switch_event_t *event); + +/*** Endpoint interface ***/ +switch_call_cause_t rtmp_session_create_call(rtmp_session_t *rsession, switch_core_session_t **newsession, int read_channel, int write_channel, const char *number, const char *auth_user, const char *auth_domain, switch_event_t *event); + +switch_status_t rtmp_on_execute(switch_core_session_t *session); +switch_status_t rtmp_send_dtmf(switch_core_session_t *session, const switch_dtmf_t *dtmf); +switch_status_t rtmp_receive_message(switch_core_session_t *session, switch_core_session_message_t *msg); +switch_status_t rtmp_receive_event(switch_core_session_t *session, switch_event_t *event); +switch_status_t rtmp_on_init(switch_core_session_t *session); +switch_status_t rtmp_on_hangup(switch_core_session_t *session); +switch_status_t rtmp_on_destroy(switch_core_session_t *session); +switch_status_t rtmp_on_routing(switch_core_session_t *session); +switch_status_t rtmp_on_exchange_media(switch_core_session_t *session); +switch_status_t rtmp_on_soft_execute(switch_core_session_t *session); +switch_call_cause_t rtmp_outgoing_channel(switch_core_session_t *session, switch_event_t *var_event, + switch_caller_profile_t *outbound_profile, + switch_core_session_t **new_session, switch_memory_pool_t **pool, switch_originate_flag_t flags, + switch_call_cause_t *cancel_cause); +switch_status_t rtmp_read_frame(switch_core_session_t *session, switch_frame_t **frame, switch_io_flag_t flags, int stream_id); +switch_status_t rtmp_write_frame(switch_core_session_t *session, switch_frame_t *frame, switch_io_flag_t flags, int stream_id); +switch_status_t rtmp_kill_channel(switch_core_session_t *session, int sig); + +switch_status_t rtmp_tech_init(rtmp_private_t *tech_pvt, rtmp_session_t *rtmp_session, switch_core_session_t *session); +rtmp_profile_t *rtmp_profile_locate(const char *name); +void rtmp_profile_release(rtmp_profile_t *profile); + +/**** I/O ****/ +switch_status_t rtmp_tcp_init(rtmp_profile_t *profile, const char *bindaddr, rtmp_io_t **new_io, switch_memory_pool_t *pool); +switch_status_t rtmp_session_request(rtmp_profile_t *profile, rtmp_session_t **newsession); +switch_status_t rtmp_session_destroy(rtmp_session_t **session); + +/**** Protocol ****/ +void rtmp_set_chunksize(rtmp_session_t *rsession, uint32_t chunksize); +switch_status_t rtmp_send_invoke(rtmp_session_t *rsession, uint8_t amfnumber, uint32_t timestamp, uint32_t stream_id, ...); +switch_status_t rtmp_send_invoke_free(rtmp_session_t *rsession, uint8_t amfnumber, uint32_t timestamp, uint32_t stream_id, ...); +switch_status_t rtmp_send_notify(rtmp_session_t *rsession, uint8_t amfnumber, uint32_t timestamp, uint32_t stream_id, ...); +switch_status_t rtmp_send_notify_free(rtmp_session_t *rsession, uint8_t amfnumber, uint32_t timestamp, uint32_t stream_id, ...); +switch_status_t rtmp_send_invoke_v(rtmp_session_t *rsession, uint8_t amfnumber, uint8_t type, uint32_t timestamp, uint32_t stream_id, va_list list, switch_bool_t freethem); +switch_status_t rtmp_send_message(rtmp_session_t *rsession, uint8_t amfnumber, uint32_t timestamp, uint8_t type, uint32_t stream_id, const unsigned char *message, switch_size_t len, uint32_t flags); + +void rtmp_send_event(rtmp_session_t *rsession, switch_event_t *event); +void rtmp_notify_call_state(switch_core_session_t *session); +void rtmp_send_display_update(switch_core_session_t *session); +void rtmp_send_incoming_call(switch_core_session_t *session); +void rtmp_send_onhangup(switch_core_session_t *session); +void rtmp_add_registration(rtmp_session_t *rsession, const char *auth, const char *nickname); +void rtmp_clear_registration(rtmp_session_t *rsession, const char *auth, const char *nickname); +/* Attaches an rtmp session to one of its calls, use NULL to hold everything */ +void rtmp_attach_private(rtmp_session_t *rsession, rtmp_private_t *tech_pvt); +rtmp_private_t *rtmp_locate_private(rtmp_session_t *rsession, const char *uuid); +void rtmp_ping(rtmp_session_t *rsession); + +void rtmp_session_send_onattach(rtmp_session_t *rsession); + +/* Protocol handler */ +switch_status_t rtmp_handle_data(rtmp_session_t *rsession); + +#endif /* defined(MOD_RTMP_H) */ + +/* For Emacs: + * Local Variables: + * mode:c + * indent-tabs-mode:t + * tab-width:4 + * c-basic-offset:4 + * End: + * For VIM: + * vim:set softtabstop=4 shiftwidth=4 tabstop=4: + */ diff --git a/src/mod/endpoints/mod_rtmp/rtmp.c b/src/mod/endpoints/mod_rtmp/rtmp.c new file mode 100644 index 0000000000..f4fa2e6e1f --- /dev/null +++ b/src/mod/endpoints/mod_rtmp/rtmp.c @@ -0,0 +1,913 @@ +/* + * mod_rtmp for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2011, Barracuda Networks Inc. + * + * Version: MPL 1.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mod_rtmp for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * + * The Initial Developer of the Original Code is Barracuda Networks Inc. + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Mathieu Rene + * + * rtmp.c -- RTMP Protocol Handler + * + */ + +#include "mod_rtmp.h" + +typedef struct { + unsigned char *buf; + size_t pos; + size_t len; +} buffer_helper_t; + +size_t my_buffer_read(void * out_buffer, size_t size, void * user_data) +{ + buffer_helper_t *helper = (buffer_helper_t*)user_data; + size_t len = (helper->len - helper->pos) < size ? (helper->len - helper->pos) : size; + if (len <= 0) { + return 0; + } + memcpy(out_buffer, helper->buf + helper->pos, len); + helper->pos += len; + return len; +} + +size_t my_buffer_write(const void *buffer, size_t size, void * user_data) +{ + buffer_helper_t *helper = (buffer_helper_t*)user_data; + size_t len = (helper->len - helper->pos) < size ? (helper->len - helper->pos) : size; + if (len <= 0) { + return 0; + } + memcpy(helper->buf + helper->pos, buffer, len); + helper->pos += len; + return len; +} + +void rtmp_handle_control(rtmp_session_t *rsession, int amfnumber) +{ + rtmp_state_t *state = &rsession->amfstate[amfnumber]; + char buf[200] = { 0 }; + char *p = buf; + int type = state->buf[0] << 8 | state->buf[1]; + int i; + + for (i = 2; i < state->origlen; i++) { + p += sprintf(p, "%02x ", state->buf[i] & 0xFF); + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Control (%d): %s\n", type, buf); + + switch(type) { + case RTMP_CTRL_STREAM_BEGIN: + break; + case RTMP_CTRL_PING_REQUEST: + { + unsigned char buf[] = { + INT16(RTMP_CTRL_PING_RESPONSE), + state->buf[2], state->buf[3], state->buf[4], state->buf[5] + }; + rtmp_send_message(rsession, amfnumber, 0, RTMP_TYPE_USERCTRL, 0, buf, sizeof(buf), 0); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Ping request\n"); + } + break; + case RTMP_CTRL_PING_RESPONSE: + { + uint32_t now = ((switch_micro_time_now()/1000) & 0xFFFFFFFF); + uint32_t sent = state->buf[2] << 24 | state->buf[3] << 16 | state->buf[4] << 8 | state->buf[5]; + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Ping reply: %d ms\n", (int)(now - sent)); + } + break; + default: + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "[amfnumber=%d] Unhandled control packet (type=0x%x)\n", + amfnumber, type); + } +} + +void rtmp_handle_invoke(rtmp_session_t *rsession, int amfnumber) +{ + rtmp_state_t *state = &rsession->amfstate[amfnumber]; + //amf0_data *dump; + int i = 0; + buffer_helper_t helper = { state->buf, 0, state->origlen }; + int64_t transaction_id; + const char *command; + int argc = 0; + amf0_data *argv[100] = { 0 }; + rtmp_invoke_function_t function; + +#if 0 + printf(">>>>> BEGIN INVOKE MSG (num=0x%02x, type=0x%02x, stream_id=0x%x)\n", amfnumber, state->type, state->stream_id); + while((dump = amf0_data_read(my_buffer_read, &helper))) { + amf0_data *dump2; + printf("ELM> "); + amf0_data_dump(stdout, dump, 0); + printf("\n"); + while ((dump2 = amf0_data_read(my_buffer_read, &helper))) { + printf("ELM> "); + amf0_data_dump(stdout, dump2, 0); + printf("\n"); + amf0_data_free(dump2); + } + amf0_data_free(dump); + } + printf("<<<<< END AMF MSG\n"); +#endif + +#ifdef RTMP_DEBUG_IO + { + helper.pos = 0; + + fprintf(rsession->io_debug_in, ">>>>> BEGIN INVOKE MSG (chunk_stream=0x%02x, type=0x%02x, stream_id=0x%x)\n", amfnumber, state->type, state->stream_id); + while((dump = amf0_data_read(my_buffer_read, &helper))) { + amf0_data *dump2; + fprintf(rsession->io_debug_in, "ELM> "); + amf0_data_dump(rsession->io_debug_in, dump, 0); + fprintf(rsession->io_debug_in, "\n"); + while ((dump2 = amf0_data_read(my_buffer_read, &helper))) { + fprintf(rsession->io_debug_in, "ELM> "); + amf0_data_dump(rsession->io_debug_in, dump2, 0); + fprintf(rsession->io_debug_in, "\n"); + amf0_data_free(dump2); + } + amf0_data_free(dump); + } + fprintf(rsession->io_debug_in, "<<<<< END AMF MSG\n"); + fflush(rsession->io_debug_in); + } +#endif + + helper.pos = 0; + while (argc < switch_arraylen(argv) && (argv[argc++] = amf0_data_read(my_buffer_read, &helper))); + + if (!(command = amf0_get_string(argv[i++]))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Bogus INVOKE request\n"); + return; + } + + transaction_id = amf0_get_number(argv[i++]); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[amfnumber=%d] Got INVOKE for %s\n", amfnumber, + command); + + if ((function = (rtmp_invoke_function_t)(intptr_t)switch_core_hash_find(rtmp_globals.invoke_hash, command))) { + function(rsession, state, amfnumber, transaction_id, argc - 2, argv + 2); + } else { + switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_WARNING, "Unhandled invoke for \"%s\"\n", + command); + } + + /* Free all the AMF data we've read */ + for (i = 0; i < argc; i++) { + amf0_data_free(argv[i]); + } +} + +switch_status_t rtmp_check_auth(rtmp_session_t *rsession, const char *user, const char *domain, const char *authmd5) +{ + switch_status_t status = SWITCH_STATUS_FALSE; + char *auth; + char md5[SWITCH_MD5_DIGEST_STRING_SIZE]; + switch_xml_t xml = NULL, x_param, x_params; + switch_bool_t allow_empty_password = SWITCH_FALSE; + const char *passwd = NULL; + + /* Locate user */ + if (switch_xml_locate_user_merged("id", user, domain, NULL, &xml, NULL) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_WARNING, "Authentication failed. No such user %s@%s\n", user, domain); + goto done; + } + + if ((x_params = switch_xml_child(xml, "params"))) { + for (x_param = switch_xml_child(x_params, "param"); x_param; x_param = x_param->next) { + const char *var = switch_xml_attr_soft(x_param, "name"); + const char *val = switch_xml_attr_soft(x_param, "value"); + + if (!strcasecmp(var, "password")) { + passwd = val; + } + if (!strcasecmp(var, "allow-empty-password")) { + allow_empty_password = switch_true(val); + } + } + } + + if (zstr(passwd)) { + if (allow_empty_password) { + status = SWITCH_STATUS_SUCCESS; + } else { + switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_WARNING, "Authentication failed for %s@%s: empty password not allowed\n", user, switch_str_nil(domain)); + } + goto done; + } + + auth = switch_core_sprintf(rsession->pool, "%s:%s@%s:%s", rsession->uuid, user, domain, passwd); + switch_md5_string(md5, auth, strlen(auth)); + + if (!strncmp(md5, authmd5, SWITCH_MD5_DIGEST_STRING_SIZE)) { + status = SWITCH_STATUS_SUCCESS; + } else { + switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_WARNING, "Authentication failed for %s@%s\n", user, domain); + } + +done: + if (xml) { + switch_xml_free(xml); + } + return status; +} + +switch_status_t amf_object_to_event(amf0_data *obj, switch_event_t **event) +{ + switch_status_t status = SWITCH_STATUS_SUCCESS; + + if (obj && obj->type == AMF0_TYPE_OBJECT) { + amf0_node *node; + if (!event) { + if ((status = switch_event_create(event, SWITCH_EVENT_CUSTOM)) != SWITCH_STATUS_SUCCESS) { + return status; + } + } + + for (node = amf0_object_first(obj); node; node = amf0_object_next(node)) { + const char *name = amf0_get_string(amf0_object_get_name(node)); + const char *value = amf0_get_string(amf0_object_get_data(node)); + + if (!zstr(name) && !zstr(value)) { + if (!strcmp(name, "_body")) { + switch_event_add_body(*event, "%s", value); + } else { + switch_event_add_header_string(*event, SWITCH_STACK_BOTTOM, name, value); + } + } + } + } else { + status = SWITCH_STATUS_FALSE; + } + + return status; +} + +switch_status_t amf_event_to_object(amf0_data **obj, switch_event_t *event) +{ + switch_event_header_t *hp; + const char *body; + + switch_assert(event); + switch_assert(obj); + + if (!*obj) { + *obj = amf0_object_new(); + } + + for (hp = event->headers; hp; hp = hp->next) { + amf0_object_add(*obj, hp->name, amf0_str(hp->value)); + } + + body = switch_event_get_body(event); + if (!zstr(body)) { + amf0_object_add(*obj, "_body", amf0_str(body)); + } + + return SWITCH_STATUS_SUCCESS; +} + +void rtmp_set_chunksize(rtmp_session_t *rsession, uint32_t chunksize) +{ + if (rsession->out_chunksize != chunksize) { + unsigned char buf[] = { + INT32(chunksize) + }; + + rtmp_send_message(rsession, 2 /*amfnumber*/, 0, RTMP_TYPE_CHUNKSIZE, 0, buf, sizeof(buf), MSG_FULLHEADER); + rsession->out_chunksize = chunksize; + } +} + +void rtmp_get_user_variables(switch_event_t **event, switch_core_session_t *session) +{ + switch_channel_t *channel = switch_core_session_get_channel(session); + switch_event_header_t *he; + + if (!*event && switch_event_create(event, SWITCH_EVENT_CLONE) != SWITCH_STATUS_SUCCESS) { + return; + } + + if ((he = switch_channel_variable_first(channel))) { + for (; he; he = he->next) { + if (!strncmp(he->name, RTMP_USER_VARIABLE_PREFIX, strlen(RTMP_USER_VARIABLE_PREFIX))) { + switch_event_add_header_string(*event, SWITCH_STACK_BOTTOM, he->name, he->value); + } + } + switch_channel_variable_last(channel); + } +} + +void rtmp_session_send_onattach(rtmp_session_t *rsession) +{ + const char *uuid = ""; + + if (rsession->tech_pvt) { + uuid = switch_core_session_get_uuid(rsession->tech_pvt->session); + } + + rtmp_send_invoke_free(rsession, 3, 0, 0, + amf0_str("onAttach"), + amf0_number_new(0), + amf0_null_new(), + amf0_str(uuid), NULL); + +} + +void rtmp_send_display_update(switch_core_session_t *session) +{ + rtmp_private_t *tech_pvt = switch_core_session_get_private(session); + + rtmp_send_invoke_free(tech_pvt->rtmp_session, 3, 0, 0, + amf0_str("displayUpdate"), + amf0_number_new(0), + amf0_null_new(), + amf0_str(switch_core_session_get_uuid(session)), + amf0_str(switch_str_nil(tech_pvt->display_callee_id_name)), + amf0_str(switch_str_nil(tech_pvt->display_callee_id_number)), NULL); +} + +void rtmp_send_incoming_call(switch_core_session_t *session) +{ + rtmp_private_t *tech_pvt = switch_core_session_get_private(session); + switch_channel_t *channel = switch_core_session_get_channel(session); + switch_caller_profile_t *caller_profile = switch_channel_get_caller_profile(channel); + switch_event_t *event = NULL; + amf0_data *obj = NULL; + + rtmp_get_user_variables(&event, session); + + if (event) { + amf_event_to_object(&obj, event); + switch_event_destroy(&event); + } + + rtmp_send_invoke_free(tech_pvt->rtmp_session, 3, 0, 0, + amf0_str("incomingCall"), + amf0_number_new(0), + amf0_null_new(), + amf0_str(switch_core_session_get_uuid(session)), + amf0_str(switch_str_nil(caller_profile->caller_id_name)), + amf0_str(switch_str_nil(caller_profile->caller_id_number)), + !zstr(tech_pvt->auth) ? amf0_str(tech_pvt->auth) : amf0_null_new(), + obj ? obj : amf0_null_new(), NULL); +} + +void rtmp_send_onhangup(switch_core_session_t *session) +{ + rtmp_private_t *tech_pvt = switch_core_session_get_private(session); + switch_channel_t *channel = switch_core_session_get_channel(session); + + rtmp_send_invoke_free(tech_pvt->rtmp_session, 3, 0, 0, + amf0_str("onHangup"), + amf0_number_new(0), + amf0_null_new(), + amf0_str(switch_core_session_get_uuid(session)), + amf0_str(switch_channel_cause2str(switch_channel_get_cause(channel))), NULL); +} + +void rtmp_send_event(rtmp_session_t *rsession, switch_event_t *event) +{ + amf0_data *obj = NULL; + + switch_assert(event != NULL); + switch_assert(rsession != NULL); + + if (amf_event_to_object(&obj, event) == SWITCH_STATUS_SUCCESS) { + rtmp_send_invoke_free(rsession, 3, 0, 0, amf0_str("event"), amf0_number_new(0), amf0_null_new(), obj, NULL); + } +} + +void rtmp_ping(rtmp_session_t *rsession) +{ + uint32_t now = (uint32_t)((switch_micro_time_now() / 1000) & 0xFFFFFFFF); + unsigned char buf[] = { + INT16(RTMP_CTRL_PING_REQUEST), + INT32(now) + }; + rtmp_send_message(rsession, 2, 0, RTMP_TYPE_USERCTRL, 0, buf, sizeof(buf), 0); +} + +void rtmp_notify_call_state(switch_core_session_t *session) +{ + switch_channel_t *channel = switch_core_session_get_channel(session); + const char *state = switch_channel_callstate2str(switch_channel_get_callstate(channel)); + rtmp_private_t *tech_pvt = switch_core_session_get_private(session); + + rtmp_send_invoke_free(tech_pvt->rtmp_session, 3, 0, 0, + amf0_str("callState"), + amf0_number_new(0), + amf0_null_new(), + amf0_str(switch_core_session_get_uuid(session)), + amf0_str(state), NULL); +} + +switch_status_t rtmp_send_invoke(rtmp_session_t *rsession, uint8_t amfnumber, uint32_t timestamp, uint32_t stream_id, ...) +{ + switch_status_t s; + va_list list; + va_start(list, stream_id); + s = rtmp_send_invoke_v(rsession, amfnumber, RTMP_TYPE_INVOKE, timestamp, stream_id, list, SWITCH_FALSE); + va_end(list); + return s; +} + +switch_status_t rtmp_send_invoke_free(rtmp_session_t *rsession, uint8_t amfnumber, uint32_t timestamp, uint32_t stream_id, ...) +{ + switch_status_t s; + va_list list; + va_start(list, stream_id); + s = rtmp_send_invoke_v(rsession, amfnumber, RTMP_TYPE_INVOKE, timestamp, stream_id, list, SWITCH_TRUE); + va_end(list); + return s; +} + +switch_status_t rtmp_send_notify(rtmp_session_t *rsession, uint8_t amfnumber, uint32_t timestamp, uint32_t stream_id, ...) +{ + switch_status_t s; + va_list list; + va_start(list, stream_id); + s = rtmp_send_invoke_v(rsession, amfnumber, RTMP_TYPE_NOTIFY, timestamp, stream_id, list, SWITCH_FALSE); + va_end(list); + return s; +} + +switch_status_t rtmp_send_notify_free(rtmp_session_t *rsession, uint8_t amfnumber, uint32_t timestamp, uint32_t stream_id, ...) +{ + switch_status_t s; + va_list list; + va_start(list, stream_id); + s = rtmp_send_invoke_v(rsession, amfnumber, RTMP_TYPE_NOTIFY, timestamp, stream_id, list, SWITCH_TRUE); + va_end(list); + return s; +} + + + +switch_status_t rtmp_send_invoke_v(rtmp_session_t *rsession, uint8_t amfnumber, uint8_t type, uint32_t timestamp, uint32_t stream_id, va_list list, switch_bool_t freethem) +{ + amf0_data *data; + unsigned char buf[AMF_MAX_SIZE]; + buffer_helper_t helper = { buf, 0, AMF_MAX_SIZE }; + + while ((data = va_arg(list, amf0_data*))) { + //amf0_data_dump(stdout, data, 0); + //printf("\n"); + amf0_data_write(data, my_buffer_write, &helper); + if (freethem) { + amf0_data_free(data); + } + } + return rtmp_send_message(rsession, amfnumber, timestamp, type, stream_id, buf, helper.pos, 0); +} + +/* Break message down into 128 bytes chunks, add the appropriate headers and send it out */ +switch_status_t rtmp_send_message(rtmp_session_t *rsession, uint8_t amfnumber, uint32_t timestamp, uint8_t type, uint32_t stream_id, const unsigned char *message, switch_size_t len, uint32_t flags) +{ + switch_size_t pos = 0; + uint8_t header[12] = { amfnumber & 0x3F, INT24(0), INT24(len), type, INT32_LE(stream_id) }; + switch_size_t chunksize; + uint8_t microhdr = (3 << 6) | amfnumber; + switch_size_t hdrsize = 1; + switch_status_t status = SWITCH_STATUS_SUCCESS; + rtmp_state_t *state = &rsession->amfstate_out[amfnumber]; + + if ((rsession->send_ack + rsession->send_ack_window) < rsession->send && + (type == RTMP_TYPE_VIDEO || type == RTMP_TYPE_AUDIO)) { + /* We're sending too fast, drop the frame */ + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "DROP %s FRAME [amfnumber=%d type=0x%x stream_id=0x%x] len=%"SWITCH_SIZE_T_FMT" \n", + type == RTMP_TYPE_AUDIO ? "AUDIO" : "VIDEO", amfnumber, type, stream_id, len); + return SWITCH_STATUS_SUCCESS; + } + + if (type != RTMP_TYPE_AUDIO && type != RTMP_TYPE_VIDEO && type != RTMP_TYPE_ACK) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[amfnumber=%d type=0x%x stream_id=0x%x] len=%"SWITCH_SIZE_T_FMT" \n", amfnumber, type, stream_id, len); + } + +#ifdef RTMP_DEBUG_IO + { + fprintf(rsession->io_debug_out, "[amfnumber=%d type=0x%x stream_id=0x%x] len=%"SWITCH_SIZE_T_FMT" \n", amfnumber, type, stream_id, len); + if (type == RTMP_TYPE_INVOKE || type == RTMP_TYPE_NOTIFY) { + buffer_helper_t helper = { (unsigned char*)message, 0, len }; + amf0_data *dump; + while((dump = amf0_data_read(my_buffer_read, &helper))) { + amf0_data *dump2; + fprintf(rsession->io_debug_out, "ELM> "); + amf0_data_dump(rsession->io_debug_out, dump, 0); + fprintf(rsession->io_debug_out, "\n"); + while ((dump2 = amf0_data_read(my_buffer_read, &helper))) { + fprintf(rsession->io_debug_out, "ELM> "); + amf0_data_dump(rsession->io_debug_out, dump2, 0); + fprintf(rsession->io_debug_out, "\n"); + amf0_data_free(dump2); + } + amf0_data_free(dump); + } + fprintf(rsession->io_debug_out, "<<<<< END AMF MSG\n"); + } + fflush(rsession->io_debug_out); + + } +#endif + + /* Find out what is the smallest header we can use */ + if (!(flags & MSG_FULLHEADER) && stream_id > 0 && state->stream_id == stream_id && timestamp >= state->ts) { + if (state->type == type && state->origlen == len) { + if (state->ts == timestamp) { + /* Type 3: no header! */ + hdrsize = 1; + header[0] |= 3 << 6; + } else { + uint32_t delta = timestamp - state->ts; + /* Type 2: timestamp delta */ + hdrsize = 4; + header[0] |= 2 << 6; + header[1] = (delta >> 16) & 0xFF; + header[2] = (delta >> 8) & 0xFF; + header[3] = delta & 0xFF; + } + } else { + /* Type 1: ts delta + msg len + type */ + uint32_t delta = timestamp - state->ts; + hdrsize = 8; + header[0] |= 1 << 6; + header[1] = (delta >> 16) & 0xFF; + header[2] = (delta >> 8) & 0xFF; + header[3] = delta & 0xFF; + } + } else { + hdrsize = 12; /* Type 0, full header */ + header[1] = (timestamp >> 16) & 0xFF; + header[2] = (timestamp >> 8) & 0xFF; + header[3] = timestamp & 0xFF; + } + + state->ts = timestamp; + state->type = type; + state->origlen = len; + state->stream_id = stream_id; + + switch_mutex_lock(rsession->socket_mutex); + chunksize = (len - pos) < rsession->out_chunksize ? (len - pos) : rsession->out_chunksize; + if (rsession->profile->io->write(rsession, (unsigned char*)header, &hdrsize) != SWITCH_STATUS_SUCCESS) { + switch_goto_status(SWITCH_STATUS_FALSE, end); + } + rsession->send += hdrsize; + + /* Write one chunk of data */ + if (rsession->profile->io->write(rsession, (unsigned char*)message, &chunksize) != SWITCH_STATUS_SUCCESS) { + switch_goto_status(SWITCH_STATUS_FALSE, end); + } + rsession->send += chunksize; + pos += chunksize; + + /* Send more chunks if we need to */ + while (((signed)len - (signed)pos) > 0) { + switch_mutex_unlock(rsession->socket_mutex); + /* Let other threads send data on the socket */ + switch_mutex_lock(rsession->socket_mutex); + hdrsize = 1; + if (rsession->profile->io->write(rsession, (unsigned char*)µhdr, &hdrsize) != SWITCH_STATUS_SUCCESS) { + switch_goto_status(SWITCH_STATUS_FALSE, end); + } + rsession->send += hdrsize; + + chunksize = (len - pos) < rsession->out_chunksize ? (len - pos) : rsession->out_chunksize; + + if (rsession->profile->io->write(rsession, message + pos, &chunksize) != SWITCH_STATUS_SUCCESS) { + switch_goto_status(SWITCH_STATUS_FALSE, end); + } + rsession->send += chunksize; + pos += chunksize; + } +end: + switch_mutex_unlock(rsession->socket_mutex); + return SWITCH_STATUS_SUCCESS; +} + +/* Returns SWITCH_STATUS_SUCCESS of the connection is still active or SWITCH_STATUS_FALSE to tear it down */ +switch_status_t rtmp_handle_data(rtmp_session_t *rsession) +{ + uint8_t buf[RTMP_TCP_READ_BUF]; + switch_size_t s = RTMP_TCP_READ_BUF; + + if (rsession->state == RS_HANDSHAKE) { + s = 1537 - rsession->hspos; + + if (rsession->profile->io->read(rsession, rsession->hsbuf + rsession->hspos, &s) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Read error\n"); + return SWITCH_STATUS_FALSE; + } + + rsession->hspos += s; + + /* Receive C0 and C1 */ + if (rsession->hspos < 1537) { + /* Not quite there yet */ + return SWITCH_STATUS_SUCCESS; + } + + /* Send reply (S0 + S1) */ + memset(buf, 0, sizeof(buf)); + *buf = '\x03'; + s = 1537; + rsession->profile->io->write(rsession, (unsigned char*)buf, &s); + + /* Send S2 */ + s = 1536; + rsession->profile->io->write(rsession, rsession->hsbuf, &s); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Sent handshake response\n"); + + rsession->state++; + rsession->hspos = 0; + } else if (rsession->state == RS_HANDSHAKE2) { + s = 1536 - rsession->hspos; + + /* Receive C2 */ + if (rsession->profile->io->read(rsession, rsession->hsbuf + rsession->hspos, &s) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Read error\n"); + return SWITCH_STATUS_FALSE; + } + + rsession->hspos += s; + + if (rsession->hspos < 1536) { + /* Not quite there yet */ + return SWITCH_STATUS_SUCCESS; + } + + rsession->state++; + + //s = 1536; + //rsession->profile->io->write(rsession, (char*)buf, &s); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Done with handshake\n"); + + + return SWITCH_STATUS_SUCCESS; + } else if (rsession->state == RS_ESTABLISHED) { + /* Process RTMP packet */ + switch(rsession->parse_state) { + case 0: + // Read the header's first byte + s = 1; + if (rsession->profile->io->read(rsession, (unsigned char*)buf, &s) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Read error\n"); + return SWITCH_STATUS_FALSE; + } + + rsession->recv += s; + + switch(buf[0] >> 6) { + case 0: + rsession->hdrsize = 12; + break; + case 1: + rsession->hdrsize = 8; + break; + case 2: + rsession->hdrsize = 4; + break; + case 3: + rsession->hdrsize = 1; + break; + default: + rsession->hdrsize = 0; + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "WTF hdrsize 0x%02x %d\n", *buf, *buf >> 6); + return SWITCH_STATUS_FALSE; + } + rsession->amfnumber = buf[0] & 0x3F; /* Get rid of the 2 first bits */ + if (rsession->amfnumber > 64) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Protocol error\n"); + return SWITCH_STATUS_FALSE; + } + //switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Header size: %d AMF Number: %d\n", rsession->hdrsize, rsession->amfnumber); + rsession->parse_state++; + if (rsession->hdrsize == 1) { + /* Skip header fetch on one-byte headers since we have it already */ + rsession->parse_state++; + } + rsession->parse_remain = 0; + break; + + case 1: + { + /* Read full header and decode */ + rtmp_state_t *state = &rsession->amfstate[rsession->amfnumber]; + uint8_t *hdr = (uint8_t*)state->header.sz; + unsigned char *readbuf = (unsigned char*)hdr; + + if (!rsession->parse_remain) { + rsession->parse_remain = s = rsession->hdrsize - 1; + } else { + s = rsession->parse_remain; + readbuf += (rsession->hdrsize - 1) - s; + } + + switch_assert(s < 12 && s > 0); /** XXX **/ + + if (rsession->profile->io->read(rsession, readbuf, &s) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Read error\n"); + return SWITCH_STATUS_FALSE; + } + + rsession->parse_remain -= s; + if (rsession->parse_remain > 0) { + /* More data please */ + return SWITCH_STATUS_SUCCESS; + } + + rsession->recv += s; + + if (rsession->hdrsize == 12) { + state->ts = (hdr[0] << 16) | (hdr[1] << 8) | (hdr[2]); + state->ts_delta = 0; + } else if (rsession->hdrsize >= 4) { + /* Save the timestamp delta since we have to re-use it with type 3 headers */ + state->ts_delta = (hdr[0] << 16) | (hdr[1] << 8) | (hdr[2]); + state->ts += state->ts_delta; + } else if (rsession->hdrsize == 1) { + /* Type 3: Re-use timestamp delta if we have one */ + state->ts += state->ts_delta; + } + + if (rsession->hdrsize >= 8) { + /* Reset length counter since its included in the header */ + state->remainlen = state->origlen = (hdr[3] << 16) | (hdr[4] << 8) | (hdr[5]); + state->buf_pos = 0; + state->type = hdr[6]; + } + if (rsession->hdrsize == 12) { + state->stream_id = (hdr[10] << 24) | (hdr[9] << 16) | (hdr[8] << 8) | hdr[7]; + } + + if (rsession->hdrsize >= 8 && state->origlen == 0) { + /* Happens we sometimes get a 0 length packet */ + rsession->parse_state = 0; + return SWITCH_STATUS_SUCCESS; + } + + /* FIXME: Handle extended timestamps */ + if (state->ts == 0x00ffffff) { + return SWITCH_STATUS_FALSE; + } + + rsession->parse_state++; + } + case 2: + { + rtmp_state_t *state = &rsession->amfstate[rsession->amfnumber]; + + if (rsession->parse_remain > 0) { + s = rsession->parse_remain; + } else { + s = state->remainlen < rsession->in_chunksize ? state->remainlen : rsession->in_chunksize; + rsession->parse_remain = s; + } + + if (!s) { + /* Restart from beginning */ + s = state->remainlen = state->origlen; + rsession->parse_remain = s; + if (!s) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Protocol error, forcing big read\n"); + s = sizeof(state->buf); + rsession->profile->io->read(rsession, state->buf, &s); + return SWITCH_STATUS_FALSE; + } + } + + /* Sanity check */ + if ((state->buf_pos + s) > AMF_MAX_SIZE) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "WTF %"SWITCH_SIZE_T_FMT" %"SWITCH_SIZE_T_FMT"\n", + state->buf_pos, s); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Protocol error: exceeding max AMF packet size\n"); + return SWITCH_STATUS_FALSE; + } + + switch_assert(s <= rsession->in_chunksize); + + if (rsession->profile->io->read(rsession, state->buf + state->buf_pos, &s) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Read error\n"); + return SWITCH_STATUS_FALSE; + } + rsession->recv += s; + + state->remainlen -= s; + rsession->parse_remain -= s; + state->buf_pos += s; + + if (rsession->parse_remain > 0) { + /* Need more data */ + return SWITCH_STATUS_SUCCESS; + } + + if (state->remainlen == 0) { + + if (state->type != RTMP_TYPE_AUDIO && state->type != RTMP_TYPE_VIDEO && state->type != RTMP_TYPE_ACK) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[chunk_stream=%d type=0x%x ts=%d stream_id=0x%x] len=%d\n", rsession->amfnumber, state->type, (int)state->ts, state->stream_id, state->origlen); + } +#ifdef RTMP_DEBUG_IO + fprintf(rsession->io_debug_in, "[chunk_stream=%d type=0x%x ts=%d stream_id=0x%x] len=%d\n", rsession->amfnumber, state->type, (int)state->ts, state->stream_id, state->origlen); +#endif + switch(state->type) { + case RTMP_TYPE_CHUNKSIZE: + rsession->in_chunksize = state->buf[0] << 24 | state->buf[1] << 16 | state->buf[2] << 8 | state->buf[3]; + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "SET CHUNKSIZE=%d\n", (int)rsession->in_chunksize); + break; + case RTMP_TYPE_USERCTRL: + rtmp_handle_control(rsession, rsession->amfnumber); + break; + case RTMP_TYPE_INVOKE: + rtmp_handle_invoke(rsession, rsession->amfnumber); + break; + case RTMP_TYPE_AUDIO: /* Audio data */ + if (rsession->tech_pvt) { + uint16_t len = state->origlen; + switch_mutex_lock(rsession->tech_pvt->readbuf_mutex); + switch_buffer_write(rsession->tech_pvt->readbuf, &len, 2); + switch_buffer_write(rsession->tech_pvt->readbuf, state->buf, len); + switch_mutex_unlock(rsession->tech_pvt->readbuf_mutex); + } + break; + case RTMP_TYPE_VIDEO: /* Video data */ + case RTMP_TYPE_METADATA: /* Metadata */ + break; + case RTMP_TYPE_WINDOW_ACK_SIZE: + rsession->send_ack_window = (state->buf[0] << 24) | (state->buf[1] << 16) | (state->buf[2] << 8) | (state->buf[3]); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Set window size: %lu bytes\n", (long unsigned int)rsession->send_ack_window); + break; + case RTMP_TYPE_ACK: + { + switch_time_t now = switch_micro_time_now(); + uint32_t ack = (state->buf[0] << 24) | (state->buf[1] << 16) | (state->buf[2] << 8) | (state->buf[3]); + uint32_t delta = rsession->send_ack_ts == 0 ? 0 : now - rsession->send_ack_ts; + + delta /= 1000000; /* microseconds -> seconds */ + + if (delta) { + rsession->send_bw = (ack - rsession->send_ack) / delta; + } + + rsession->send_ack = ack; + rsession->send_ack_ts = switch_micro_time_now(); + break; + } + default: + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Cannot handle message type 0x%x\n", state->type); + break; + } + state->buf_pos = 0; + } + + rsession->parse_state = 0; + + /* Send an ACK if we need to */ + if (rsession->recv - rsession->recv_ack_sent >= rsession->recv_ack_window) { + unsigned char ackbuf[] = { INT32(rsession->recv) }; + + rtmp_send_message(rsession, 2/*chunkstream*/, 0/*ts*/, RTMP_TYPE_ACK, 0/*msg stream id */, ackbuf, sizeof(ackbuf), 0 /*flags*/); + rsession->recv_ack_sent = rsession->recv; + } + + } + } + } + + return SWITCH_STATUS_SUCCESS; +} + +/* For Emacs: + * Local Variables: + * mode:c + * indent-tabs-mode:t + * tab-width:4 + * c-basic-offset:4 + * End: + * For VIM: + * vim:set softtabstop=4 shiftwidth=4 tabstop=4: + */ diff --git a/src/mod/endpoints/mod_rtmp/rtmp_sig.c b/src/mod/endpoints/mod_rtmp/rtmp_sig.c new file mode 100644 index 0000000000..ece43bc050 --- /dev/null +++ b/src/mod/endpoints/mod_rtmp/rtmp_sig.c @@ -0,0 +1,841 @@ +/* + * mod_rtmp for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2011, Barracuda Networks Inc. + * + * Version: MPL 1.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mod_rtmp for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * + * The Initial Developer of the Original Code is Barracuda Networks Inc. + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Mathieu Rene + * + * rtmp.c -- RTMP Signalling functions + * + */ + +#include "mod_rtmp.h" + +/* AMF */ +#include "amf0.h" +#include "io.h" +#include "types.h" + +/* RTMP_INVOKE_FUNCTION is a macro that expands to: +switch_status_t function(rtmp_session_t *rsession, rtmp_state_t *state, int amfnumber, int transaction_id, int argc, amf0_data *argv[]) +*/ + +RTMP_INVOKE_FUNCTION(rtmp_i_connect) +{ + amf0_data *object1 = amf0_object_new(), *object2 = amf0_object_new(), *params = argv[0], *d; + const char *s; + + if ((d = amf0_object_get(params, "app")) && (s = amf0_get_string(d))) { + rsession->app = switch_core_strdup(rsession->pool, s); + } + + if ((d = amf0_object_get(params, "flashVer")) && (s = amf0_get_string(d))) { + rsession->flashVer = switch_core_strdup(rsession->pool, s); + } + if ((d = amf0_object_get(params, "swfUrl")) && (s = amf0_get_string(d))) { + rsession->swfUrl = switch_core_strdup(rsession->pool, s); + } + if ((d = amf0_object_get(params, "tcUrl")) && (s = amf0_get_string(d))) { + rsession->tcUrl = switch_core_strdup(rsession->pool, s); + } + if ((d = amf0_object_get(params, "pageUrl")) && (s = amf0_get_string(d))) { + rsession->pageUrl = switch_core_strdup(rsession->pool, s); + } + + if ((d = amf0_object_get(params, "capabilities"))) { + rsession->capabilities = amf0_get_number(d); + } + if ((d = amf0_object_get(params, "audioCodecs"))) { + rsession->audioCodecs = amf0_get_number(d); + } + if ((d = amf0_object_get(params, "videoCodecs"))) { + rsession->videoCodecs = amf0_get_number(d); + } + if ((d = amf0_object_get(params, "videoFunction"))) { + rsession->videoFunction = amf0_get_number(d); + } + + amf0_object_add(object1, "fmsVer", amf0_number_new(1)); + amf0_object_add(object1, "capabilities", amf0_number_new(31)); + + amf0_object_add(object2, "level", amf0_str("status")); + amf0_object_add(object2, "code", amf0_str("NetConnection.Connect.Success")); + amf0_object_add(object2, "description", amf0_str("Connection succeeded")); + amf0_object_add(object2, "clientId", amf0_number_new(217834719)); + amf0_object_add(object2, "objectEncoding", amf0_number_new(0)); + + rtmp_set_chunksize(rsession, rsession->profile->chunksize); + + { + unsigned char ackbuf[] = { INT32(RTMP_DEFAULT_ACK_WINDOW) }; + rtmp_send_message(rsession, 2, 0, RTMP_TYPE_WINDOW_ACK_SIZE, 0, ackbuf, sizeof(ackbuf), MSG_FULLHEADER); + } + + { + unsigned char ackbuf[] = { INT32(RTMP_DEFAULT_ACK_WINDOW), 0x1 /* Soft limit */}; + rtmp_send_message(rsession, 2, 0, RTMP_TYPE_SET_PEER_BW, 0, ackbuf, sizeof(ackbuf), MSG_FULLHEADER); + } + + { + unsigned char buf[] = { + INT16(RTMP_CTRL_STREAM_BEGIN), + INT32(0) + }; + + rtmp_send_message(rsession, 2, 0, RTMP_TYPE_USERCTRL, 0, buf, sizeof(buf), 0); + } + + /* respond with a success message */ + rtmp_send_invoke_free(rsession, amfnumber, 0, 0, + amf0_str("_result"), + amf0_number_new(1), + object1, + object2, + NULL); + + rtmp_send_invoke_free(rsession, 3, 0, 0, + amf0_str("connected"), + amf0_number_new(0), + amf0_null_new(), + amf0_str(rsession->uuid), NULL); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Sent connect reply\n"); + + return SWITCH_STATUS_SUCCESS; +} + + +RTMP_INVOKE_FUNCTION(rtmp_i_createStream) +{ + rtmp_send_invoke_free(rsession, amfnumber, 0, 0, + amf0_str("_result"), + amf0_number_new(transaction_id), + amf0_null_new(), + amf0_number_new(rsession->next_streamid), + NULL); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Replied to createStream (%u)\n", rsession->next_streamid); + + rsession->next_streamid++; + + return SWITCH_STATUS_SUCCESS; +} + +RTMP_INVOKE_FUNCTION(rtmp_i_noop) +{ + return SWITCH_STATUS_SUCCESS; +} + + +RTMP_INVOKE_FUNCTION(rtmp_i_receiveaudio) +{ + switch_bool_t enabled = argv[1] ? amf0_boolean_get_value(argv[1]) : SWITCH_FALSE; + + if (enabled) { + switch_set_flag(rsession, SFLAG_AUDIO); + } else { + switch_clear_flag(rsession, SFLAG_AUDIO); + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "%sending audio\n", enabled ? "S" : "Not s"); + + return SWITCH_STATUS_SUCCESS; +} + +RTMP_INVOKE_FUNCTION(rtmp_i_receivevideo) +{ + switch_bool_t enabled = argv[1] ? amf0_boolean_get_value(argv[1]) : SWITCH_FALSE; + + if (enabled) { + switch_set_flag(rsession, SFLAG_VIDEO); + if (rsession->tech_pvt) { + switch_set_flag(rsession->tech_pvt, TFLAG_VID_WAIT_KEYFRAME); + } + } else { + switch_clear_flag(rsession, SFLAG_VIDEO); + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "%sending video\n", enabled ? "S" : "Not s"); + + return SWITCH_STATUS_SUCCESS; +} + + +RTMP_INVOKE_FUNCTION(rtmp_i_play) +{ + amf0_data *obj = amf0_object_new(); + amf0_data *object = amf0_object_new(); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Got play for %s on stream %d\n", switch_str_nil(amf0_get_string(argv[1])), + state->stream_id); + + /* Set outgoing chunk size to 1024 bytes */ + rtmp_set_chunksize(rsession, 1024); + + rsession->media_streamid = state->stream_id; + + /* Send StreamBegin on the current stream */ + { + unsigned char buf[] = { + INT16(RTMP_CTRL_STREAM_BEGIN), + INT32(rsession->media_streamid) + }; + rtmp_send_message(rsession, 2, 0, RTMP_TYPE_USERCTRL, 0, buf, sizeof(buf), 0); + } + + + { + unsigned char buf[] = { + INT16(RTMP_CTRL_SET_BUFFER_LENGTH), + INT32(rsession->media_streamid), + INT32(rsession->profile->buffer_len) + }; + rtmp_send_message(rsession, 2, 0, RTMP_TYPE_USERCTRL, 0, buf, sizeof(buf), 0); + } + + /* Send onStatus */ + amf0_object_add(object, "level", amf0_str("status")); + amf0_object_add(object, "code", amf0_str("NetStream.Play.Reset")); + amf0_object_add(object, "description", amf0_str("description")); + amf0_object_add(object, "details", amf0_str("details")); + amf0_object_add(object, "clientid", amf0_number_new(217834719)); + + rtmp_send_invoke_free(rsession, RTMP_DEFAULT_STREAM_NOTIFY, 0, rsession->media_streamid, + amf0_str("onStatus"), + amf0_number_new(1), + amf0_null_new(), + object, NULL); + + object = amf0_object_new(); + + amf0_object_add(object, "level", amf0_str("status")); + amf0_object_add(object, "code", amf0_str("NetStream.Play.Start")); + amf0_object_add(object, "description", amf0_str("description")); + amf0_object_add(object, "details", amf0_str("details")); + amf0_object_add(object, "clientid", amf0_number_new(217834719)); + + rtmp_send_invoke_free(rsession, RTMP_DEFAULT_STREAM_NOTIFY, 0, rsession->media_streamid, + amf0_str("onStatus"), + amf0_number_new(1), + amf0_null_new(), + object, NULL); + + amf0_object_add(obj, "code", amf0_str("NetStream.Data.Start")); + + rtmp_send_notify_free(rsession, RTMP_DEFAULT_STREAM_NOTIFY, 0, rsession->media_streamid, + amf0_str("onStatus"), + obj, NULL); + + rtmp_send_notify_free(rsession, RTMP_DEFAULT_STREAM_NOTIFY, 0, rsession->media_streamid, + amf0_str("|RtmpSampleAccess"), + amf0_boolean_new(1), + amf0_boolean_new(1), NULL); + + return SWITCH_STATUS_SUCCESS; +} + +RTMP_INVOKE_FUNCTION(rtmp_i_publish) +{ + + unsigned char buf[] = { + INT16(RTMP_CTRL_STREAM_BEGIN), + INT32(state->stream_id) + }; + + rtmp_send_message(rsession, 2, 0, RTMP_TYPE_USERCTRL, 0, buf, sizeof(buf), 0); + + rtmp_send_invoke_free(rsession, amfnumber, 0, 0, + amf0_str("_result"), + amf0_number_new(transaction_id), + amf0_null_new(), + amf0_null_new(), + NULL); + + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Got publish on stream %u.\n", state->stream_id); + + return SWITCH_STATUS_SUCCESS; +} + +RTMP_INVOKE_FUNCTION(rtmp_i_makeCall) +{ + switch_core_session_t *newsession = NULL; + char *number = NULL; + + if ((number = amf0_get_string(argv[1]))) { + switch_event_t *event = NULL; + char *auth, *user = NULL, *domain = NULL; + + if ((auth = amf0_get_string(argv[2])) && !zstr(auth)) { + switch_split_user_domain(auth, &user, &domain); + if (rtmp_session_check_user(rsession, user, domain) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_WARNING, "Unauthorized call to %s, client is not logged in account [%s@%s]\n", + number, switch_str_nil(user), switch_str_nil(domain)); + return SWITCH_STATUS_FALSE; + } + } else if (rsession->profile->auth_calls && !rsession->account) { + switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_WARNING, "Unauthorized call to %s, client is not logged in\n", number); + return SWITCH_STATUS_FALSE; + } + + if (amf0_is_object(argv[3])) { + amf_object_to_event(argv[3], &event); + } + + if (rtmp_session_create_call(rsession, &newsession, 0, RTMP_DEFAULT_STREAM_AUDIO, number, user, domain, event) != SWITCH_CAUSE_NONE) { + switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_ERROR, "Couldn't create call.\n"); + } + + if (event) { + switch_event_destroy(&event); + } + } + + if (newsession) { + rtmp_private_t *new_pvt = switch_core_session_get_private(newsession); + rtmp_send_invoke_free(rsession, 3, 0, 0, + amf0_str("onMakeCall"), + amf0_number_new(transaction_id), + amf0_null_new(), + amf0_str(switch_core_session_get_uuid(newsession)), + amf0_str(switch_str_nil(number)), + amf0_str(switch_str_nil(new_pvt->auth)), + NULL); + + rtmp_attach_private(rsession, switch_core_session_get_private(newsession)); + } + + return SWITCH_STATUS_SUCCESS; +} + +RTMP_INVOKE_FUNCTION(rtmp_i_sendDTMF) +{ + /* Send DTMFs on the active channel */ + switch_dtmf_t dtmf = { 0 }; + switch_channel_t *channel; + char *digits; + + if (!rsession->tech_pvt) { + return SWITCH_STATUS_FALSE; + } + + channel = switch_core_session_get_channel(rsession->tech_pvt->session); + + if (amf0_is_number(argv[2])) { + dtmf.duration = amf0_get_number(argv[2]); + } else if (!zstr(amf0_get_string(argv[2]))) { + dtmf.duration = atoi(amf0_get_string(argv[2])); + } + + if ((digits = amf0_get_string(argv[1]))) { + size_t len = strlen(digits); + size_t j; + for (j = 0; j < len; j++) { + dtmf.digit = digits[j]; + switch_channel_queue_dtmf(channel, &dtmf); + } + } + + return SWITCH_STATUS_SUCCESS; +} + + +RTMP_INVOKE_FUNCTION(rtmp_i_login) +{ + char *user, *auth, *domain, *ddomain = NULL; + + + user = amf0_get_string(argv[1]); + auth = amf0_get_string(argv[2]); + + if (zstr(user) || zstr(auth)) { + return SWITCH_STATUS_FALSE; + } + + if ((domain = strchr(user, '@'))) { + *domain++ = '\0'; + } + + if (zstr(domain)) { + ddomain = switch_core_get_variable_dup("domain"); + domain = ddomain; + } + + + if (rtmp_check_auth(rsession, user, domain, auth) == SWITCH_STATUS_SUCCESS) { + rtmp_session_login(rsession, user, domain); + } else { + rtmp_send_invoke_free(rsession, 3, 0, 0, + amf0_str("onLogin"), + amf0_number_new(0), + amf0_null_new(), + amf0_str("failure"), + amf0_null_new(), + amf0_null_new(), NULL); + } + + + switch_safe_free(ddomain); + + return SWITCH_STATUS_SUCCESS; +} + +RTMP_INVOKE_FUNCTION(rtmp_i_logout) +{ + char *auth = amf0_get_string(argv[1]); + char *user = NULL, *domain = NULL; + + /* Unregister from that user */ + rtmp_clear_registration(rsession, auth, NULL); + + switch_split_user_domain(auth, &user, &domain); + + if (!zstr(user) && !zstr(domain)) { + rtmp_session_logout(rsession, user, domain); + } + + return SWITCH_STATUS_SUCCESS; +} + +RTMP_INVOKE_FUNCTION(rtmp_i_register) +{ + char *auth = amf0_get_string(argv[1]); + const char *user = NULL, *domain = NULL; + char *dup = NULL; + switch_status_t status; + + if (!rsession->account) { + return SWITCH_STATUS_FALSE; + } + + if (!zstr(auth)) { + dup = strdup(auth); + switch_split_user_domain(dup, (char**)&user, (char**)&domain); + } else { + dup = auth = switch_mprintf("%s@%s", rsession->account->user, rsession->account->domain); + user = rsession->account->user; + domain = rsession->account->domain; + } + + if (rtmp_session_check_user(rsession, user, domain) == SWITCH_STATUS_SUCCESS) { + rtmp_add_registration(rsession, auth, amf0_get_string(argv[2])); + status = SWITCH_STATUS_SUCCESS; + } else { + status = SWITCH_STATUS_FALSE; + } + + switch_safe_free(dup); + + return status; +} + +RTMP_INVOKE_FUNCTION(rtmp_i_unregister) +{ + rtmp_clear_registration(rsession, amf0_get_string(argv[1]), amf0_get_string(argv[2])); + + return SWITCH_STATUS_SUCCESS; +} + +RTMP_INVOKE_FUNCTION(rtmp_i_answer) +{ + switch_channel_t *channel = NULL; + char *uuid = amf0_get_string(argv[1]); + + if (!zstr(uuid)) { + rtmp_private_t *new_tech_pvt = rtmp_locate_private(rsession, uuid); + if (new_tech_pvt) { + switch_channel_mark_answered(switch_core_session_get_channel(new_tech_pvt->session)); + rtmp_attach_private(rsession, new_tech_pvt); + } + return SWITCH_STATUS_FALSE; + } + + if (!rsession->tech_pvt) { + return SWITCH_STATUS_FALSE; + } + + /* No UUID specified but we're attached to a channel, mark it as answered */ + channel = switch_core_session_get_channel(rsession->tech_pvt->session); + switch_channel_mark_answered(channel); + rtmp_attach_private(rsession, rsession->tech_pvt); + + return SWITCH_STATUS_SUCCESS; +} + +RTMP_INVOKE_FUNCTION(rtmp_i_attach) +{ + rtmp_private_t *tech_pvt = NULL; + char *uuid = amf0_get_string(argv[1]); + + if (!zstr(uuid)) { + tech_pvt = rtmp_locate_private(rsession, uuid); + } + /* Will detach if an empty (or invalid) uuid is received */ + rtmp_attach_private(rsession, tech_pvt); + + return SWITCH_STATUS_SUCCESS; +} + +RTMP_INVOKE_FUNCTION(rtmp_i_hangup) +{ + /* CallID (or null/nothing to hangup the current call) */ + char *uuid = amf0_get_string(argv[1]); + char *scause; + switch_channel_t *channel = NULL; + switch_call_cause_t cause = SWITCH_CAUSE_NORMAL_CLEARING; + + if (!zstr(uuid)) { + rtmp_private_t *tech_pvt = rtmp_locate_private(rsession, uuid); + if (tech_pvt) { + channel = switch_core_session_get_channel(tech_pvt->session); + } + } + + if (!channel) { + if (!rsession->tech_pvt) { + return SWITCH_STATUS_FALSE; + } + channel = switch_core_session_get_channel(rsession->tech_pvt->session); + } + + if (amf0_is_number(argv[2])) { + cause = amf0_get_number(argv[2]); + } else if ((scause = amf0_get_string(argv[2])) && !zstr(scause)) { + cause = switch_channel_str2cause(scause); + } + + switch_channel_hangup(channel, cause); + + return SWITCH_STATUS_SUCCESS; +} + +RTMP_INVOKE_FUNCTION(rtmp_i_transfer) +{ + char *uuid = amf0_get_string(argv[1]); + char *dest = amf0_get_string(argv[2]); + rtmp_private_t *tech_pvt; + + if (zstr(uuid) || zstr(dest)) { + return SWITCH_STATUS_FALSE; + } + + if ((tech_pvt = rtmp_locate_private(rsession, uuid))) { + const char *other_uuid = switch_channel_get_variable(tech_pvt->channel, SWITCH_SIGNAL_BOND_VARIABLE); + switch_core_session_t *session; + + if (!zstr(other_uuid) && (session = switch_core_session_locate(other_uuid))) { + switch_ivr_session_transfer(session, dest, NULL, NULL); + switch_core_session_rwunlock(session); + } + } + + return SWITCH_STATUS_SUCCESS; +} + +RTMP_INVOKE_FUNCTION(rtmp_i_join) +{ + char *uuid[] = { amf0_get_string(argv[1]), amf0_get_string(argv[2]) }; + const char *other_uuid[2]; + rtmp_private_t *tech_pvt[2]; + + if (zstr(uuid[0]) || zstr(uuid[1])) { + return SWITCH_STATUS_SUCCESS; + } + + if (!(tech_pvt[0] = rtmp_locate_private(rsession, uuid[0])) || + !(tech_pvt[1] = rtmp_locate_private(rsession, uuid[1]))) { + return SWITCH_STATUS_FALSE; + } + + if (tech_pvt[0] == tech_pvt[1]) { + return SWITCH_STATUS_FALSE; + } + + if ((other_uuid[0] = switch_channel_get_variable(tech_pvt[0]->channel, SWITCH_SIGNAL_BOND_VARIABLE)) && + (other_uuid[1] = switch_channel_get_variable(tech_pvt[1]->channel, SWITCH_SIGNAL_BOND_VARIABLE))) { + +#ifndef RTMP_DONT_HOLD + if (switch_test_flag(tech_pvt[0], TFLAG_DETACHED)) { + switch_ivr_unhold(tech_pvt[0]->session); + } + if (switch_test_flag(tech_pvt[1], TFLAG_DETACHED)) { + switch_ivr_unhold(tech_pvt[1]->session); + } +#endif + + switch_ivr_uuid_bridge(other_uuid[0], other_uuid[1]); + } + + return SWITCH_STATUS_SUCCESS; +} + +/* + +3-way: + +[0] is always the current active call +[1] is the call to be brought into the call, and that will be running the three_way application + +- Set the current app of other[1] to three_way with other_uuid[0] +- Put tech_pvt[0] to sleep: set state to CS_HIBERNATE +- set CF_TRANSFER, set state CS_EXECUTE (do we need CF_TRANSFER here?) + +- setup a state handler in other[1] to detect when it hangs up + +Check list: +tech_pvt[0] or other[0] hangs up + If we were attached to the call, switch the active call to tech_pvt[1] +tech_pvt[1] or other[1] hangs up + Clear up any 3-way indications on the tech_pvt[0] + +*/ + +static switch_status_t three_way_on_soft_execute(switch_core_session_t *session); +#if 0 +static switch_status_t three_way_on_hangup(switch_core_session_t *session); +#endif + +static const switch_state_handler_table_t three_way_state_handlers_remote = { + /*.on_init */ NULL, + /*.on_routing */ NULL, + /*.on_execute */ NULL, + /*.on_hangup */ NULL, + /*.on_exchange_media */ NULL, + /*.on_soft_execute */ three_way_on_soft_execute, + /*.on_consume_media */ NULL, + /*.on_hibernate */ NULL +}; + +/* runs on other_session[1] */ +static switch_status_t three_way_on_soft_execute(switch_core_session_t *other_session) +{ + switch_channel_t *other_channel = switch_core_session_get_channel(other_session); + const char *uuid = switch_channel_get_variable(other_channel, RTMP_THREE_WAY_UUID_VARIABLE); + const char *my_uuid = switch_channel_get_variable(other_channel, SWITCH_SOFT_HOLDING_UUID_VARIABLE); + switch_core_session_t *my_session; + switch_channel_t *my_channel; + rtmp_private_t *tech_pvt; + + if (zstr(uuid) || zstr(my_uuid)) { + return SWITCH_STATUS_SUCCESS; + } + + if (zstr(my_uuid) || !(my_session = switch_core_session_locate(my_uuid))) { + return SWITCH_STATUS_SUCCESS; + } + + if (!switch_core_session_check_interface(my_session, rtmp_globals.rtmp_endpoint_interface)) { + /* In case someone tempers with my variables, since we get tech_pvt from there */ + switch_core_session_rwunlock(my_session); + return SWITCH_STATUS_SUCCESS; + } + + my_channel = switch_core_session_get_channel(my_session); + tech_pvt = switch_core_session_get_private(my_session); + + switch_ivr_eavesdrop_session(other_session, uuid, NULL, ED_MUX_READ | ED_MUX_WRITE); + + /* 3-way call ended, whatever the reason + * We need to go back to our original state. */ + if (!switch_channel_up(other_channel)) { + /* channel[1] hung up, check if we have special post-bridge actions, and hangup otherwise */ + /* if my_channel isn't ready, it means something else has control of it, leave it alone */ + if (switch_channel_ready(my_channel)) { + const char *s; + if ((s = switch_channel_get_variable(my_channel, SWITCH_PARK_AFTER_BRIDGE_VARIABLE)) && switch_true(s)) { + switch_ivr_park_session(my_session); + } else if ((s = switch_channel_get_variable(my_channel, SWITCH_TRANSFER_AFTER_BRIDGE_VARIABLE)) && !zstr(s)) { + int argc; + char *argv[4] = { 0 }; + char *mydata = switch_core_session_strdup(my_session, s); + + switch_channel_set_variable(my_channel, SWITCH_TRANSFER_AFTER_BRIDGE_VARIABLE, NULL); + + if ((argc = switch_split(mydata, ':', argv)) >= 1) { + switch_ivr_session_transfer(my_session, argv[0], argv[1], argv[2]); + } else { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(my_session), SWITCH_LOG_ERROR, "No extension specified.\n"); + } + } else { + switch_channel_hangup(my_channel, SWITCH_CAUSE_NORMAL_CLEARING); + } + } + } else if (switch_channel_ready(other_channel)) { + /* channel[1] didn't hangup, must be channel[0] then, rebridge this one with its original partner */ + switch_ivr_uuid_bridge(switch_core_session_get_uuid(other_session), my_uuid); + } else { + /* channel[1] being taken out of our control, take the other leg out of CS_HIBERNATE if its ready, or else leave it alone */ + if (switch_channel_ready(my_channel)) { + switch_channel_set_state(my_channel, CS_EXECUTE); + } + } + + switch_channel_clear_state_handler(other_channel, &three_way_state_handlers_remote); + + switch_channel_set_variable(other_channel, SWITCH_SOFT_HOLDING_UUID_VARIABLE, NULL); + switch_channel_set_variable(my_channel, SWITCH_SOFT_HOLDING_UUID_VARIABLE, NULL); + switch_channel_set_variable(other_channel, RTMP_THREE_WAY_UUID_VARIABLE, NULL); + + switch_clear_flag(tech_pvt, TFLAG_THREE_WAY); + + if (my_session) { + switch_core_session_rwunlock(my_session); + } + + return SWITCH_STATUS_SUCCESS; +} + +RTMP_INVOKE_FUNCTION(rtmp_i_three_way) +{ + /* The first uuid is the (local) uuid of the current call, the 2nd one should be already detached */ + char *uuid[] = { amf0_get_string(argv[1]), amf0_get_string(argv[2]) }; + rtmp_private_t *tech_pvt[2]; + const char *other_uuid[2]; + switch_core_session_t *other_session[2] = { 0 }; + switch_channel_t *other_channel[2] = { 0 }; + + if (zstr(uuid[0]) || zstr(uuid[1]) || + !(tech_pvt[0] = rtmp_locate_private(rsession, uuid[0])) || + !(tech_pvt[1] = rtmp_locate_private(rsession, uuid[1]))) { + return SWITCH_STATUS_FALSE; + } + + /* Make sure we don't 3-way with the same call, and that it doesnt turn into a 4-way, we aren't that permissive */ + if (tech_pvt[0] == tech_pvt[1] || switch_test_flag(tech_pvt[0], TFLAG_THREE_WAY) || + switch_test_flag(tech_pvt[1], TFLAG_THREE_WAY)) { + return SWITCH_STATUS_FALSE; + } + + if (!(other_uuid[0] = switch_channel_get_variable(tech_pvt[0]->channel, SWITCH_SIGNAL_BOND_VARIABLE)) || + !(other_uuid[1] = switch_channel_get_variable(tech_pvt[1]->channel, SWITCH_SIGNAL_BOND_VARIABLE))) { + return SWITCH_STATUS_FALSE; /* Both calls aren't bridged */ + } + + if (!(other_session[0] = switch_core_session_locate(other_uuid[0])) || + !(other_session[1] = switch_core_session_locate(other_uuid[1]))) { + goto done; + } + + other_channel[0] = switch_core_session_get_channel(other_session[0]); + other_channel[1] = switch_core_session_get_channel(other_session[1]); + + /* Save which uuid is the 3-way target */ + switch_channel_set_variable(other_channel[1], RTMP_THREE_WAY_UUID_VARIABLE, uuid[0]); + switch_channel_set_variable(tech_pvt[1]->channel, RTMP_THREE_WAY_UUID_VARIABLE, uuid[0]); + + /* Attach redirect */ + switch_set_flag(tech_pvt[1], TFLAG_THREE_WAY); + + /* Set soft_holding_uuid to the uuid of the other matching channel, so they can can be bridged back when the 3-way is over */ + switch_channel_set_variable(tech_pvt[1]->channel, SWITCH_SOFT_HOLDING_UUID_VARIABLE, other_uuid[1]); + switch_channel_set_variable(other_channel[1], SWITCH_SOFT_HOLDING_UUID_VARIABLE, uuid[1]); + + /* Start the 3-way on the 2nd channel using a media bug */ + switch_channel_add_state_handler(other_channel[1], &three_way_state_handlers_remote); + + switch_channel_set_flag(tech_pvt[1]->channel, CF_TRANSFER); + switch_channel_set_state(tech_pvt[1]->channel, CS_HIBERNATE); + switch_channel_set_flag(other_channel[1], CF_TRANSFER); + switch_channel_set_state(other_channel[1], CS_SOFT_EXECUTE); + +done: + + if (other_session[0]) { + switch_core_session_rwunlock(other_session[0]); + } + + if (other_session[1]) { + switch_core_session_rwunlock(other_session[1]); + } + + return SWITCH_STATUS_SUCCESS; +} + +RTMP_INVOKE_FUNCTION(rtmp_i_sendevent) +{ + amf0_data *obj = NULL; + switch_event_t *event = NULL; + switch_status_t status = SWITCH_STATUS_SUCCESS; + const char *uuid = NULL; + + if (argv[1] && argv[1]->type == AMF0_TYPE_OBJECT) { + obj = argv[1]; + } else if (argv[2] && argv[2]->type == AMF0_TYPE_OBJECT) { + uuid = amf0_get_string(argv[1]); + obj = argv[2]; + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Bad argument for sendevent"); + return SWITCH_STATUS_FALSE; + } + + + if (switch_event_create_subclass(&event, zstr(uuid) ? SWITCH_EVENT_CUSTOM : SWITCH_EVENT_MESSAGE, + zstr(uuid) ? RTMP_EVENT_CLIENTCUSTOM : NULL) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_ERROR, "Couldn't create event\n"); + return SWITCH_STATUS_FALSE; + } + + rtmp_event_fill(rsession, event); + + /* Build event using amf array */ + if ((status = amf_object_to_event(obj, &event)) != SWITCH_STATUS_SUCCESS) { + switch_event_destroy(&event); + return SWITCH_STATUS_FALSE; + } + + if (!zstr(uuid)) { + rtmp_private_t *session_pvt = rtmp_locate_private(rsession, uuid); + if (session_pvt) { + if (switch_core_session_queue_event(session_pvt->session, &event) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session_pvt->session), SWITCH_LOG_ERROR, "Couldn't queue event to session\n"); + switch_event_destroy(&event); + status = SWITCH_STATUS_FALSE; + } else { + status = SWITCH_STATUS_SUCCESS; + } + } + } + + switch_event_fire(&event); + + return SWITCH_STATUS_SUCCESS; +} + +RTMP_INVOKE_FUNCTION(rtmp_i_log) +{ + const char *data = amf0_get_string(argv[1]); + + switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_INFO, "Log: %s\n", data); + + return SWITCH_STATUS_SUCCESS; +} + +/* For Emacs: + * Local Variables: + * mode:c + * indent-tabs-mode:t + * tab-width:4 + * c-basic-offset:4 + * End: + * For VIM: + * vim:set softtabstop=4 shiftwidth=4 tabstop=4: + */ diff --git a/src/mod/endpoints/mod_rtmp/rtmp_tcp.c b/src/mod/endpoints/mod_rtmp/rtmp_tcp.c new file mode 100644 index 0000000000..6ed2b49e67 --- /dev/null +++ b/src/mod/endpoints/mod_rtmp/rtmp_tcp.c @@ -0,0 +1,363 @@ +/* + * mod_rtmp for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2011, Barracuda Networks Inc. + * + * Version: MPL 1.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mod_rtmp for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * + * The Initial Developer of the Original Code is Barracuda Networks Inc. + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Mathieu Rene + * + * rtmp_tcp.c -- RTMP TCP I/O module + * + */ + +#include "mod_rtmp.h" + +/* Locally-extended version of rtmp_io_t */ +struct rtmp_io_tcp { + rtmp_io_t base; + + switch_pollset_t *pollset; + switch_pollfd_t *listen_pollfd; + switch_socket_t *listen_socket; + const char *ip; + switch_port_t port; + switch_thread_t *thread; + switch_mutex_t *mutex; +}; + +typedef struct rtmp_io_tcp rtmp_io_tcp_t; + +struct rtmp_tcp_io_private { + switch_pollfd_t *pollfd; + switch_socket_t *socket; + switch_buffer_t *sendq; + switch_bool_t poll_send; +}; + +typedef struct rtmp_tcp_io_private rtmp_tcp_io_private_t; + +static void rtmp_tcp_alter_pollfd(rtmp_session_t *rsession, switch_bool_t pollout) +{ + rtmp_tcp_io_private_t *io_pvt = rsession->io_private; + rtmp_io_tcp_t *io = (rtmp_io_tcp_t*)rsession->profile->io; + + if (pollout && (io_pvt->pollfd->reqevents & SWITCH_POLLOUT)) { + return; + } else if (!pollout && !(io_pvt->pollfd->reqevents & SWITCH_POLLOUT)) { + return; + } + + switch_pollset_remove(io->pollset, io_pvt->pollfd); + io_pvt->pollfd->reqevents = SWITCH_POLLIN | SWITCH_POLLERR; + if (pollout) { + io_pvt->pollfd->reqevents |= SWITCH_POLLOUT; + } + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Pollout: %s\n", + pollout ? "true" : "false"); + + switch_pollset_add(io->pollset, io_pvt->pollfd); +} + +static switch_status_t rtmp_tcp_read(rtmp_session_t *rsession, unsigned char *buf, switch_size_t *len) +{ + //rtmp_io_tcp_t *io = (rtmp_io_tcp_t*)rsession->profile->io; + rtmp_tcp_io_private_t *io_pvt = rsession->io_private; + switch_status_t status = SWITCH_STATUS_SUCCESS; +#ifdef RTMP_DEBUG_IO + switch_size_t olen = *len; +#endif + switch_assert(*len > 0 && *len < 1024000); + status = switch_socket_recv(io_pvt->socket, (char*)buf, len); + +#ifdef RTMP_DEBUG_IO + { + int i; + + fprintf(rsession->io_debug_in, "recv %p max=%"SWITCH_SIZE_T_FMT" got=%"SWITCH_SIZE_T_FMT"\n< ", (void*)buf, olen, *len); + + for (i = 0; i < *len; i++) { + + fprintf(rsession->io_debug_in, "%02X ", (uint8_t)buf[i]); + + if (i != 0 && i % 32 == 0) { + fprintf(rsession->io_debug_in, "\n> "); + } + } + fprintf(rsession->io_debug_in, "\n\n"); + fflush(rsession->io_debug_in); + } +#endif + + return status; +} + +static switch_status_t rtmp_tcp_write(rtmp_session_t *rsession, const unsigned char *buf, switch_size_t *len) +{ + //rtmp_io_tcp_t *io = (rtmp_io_tcp_t*)rsession->profile->io; + rtmp_tcp_io_private_t *io_pvt = rsession->io_private; + switch_status_t status; + switch_size_t orig_len = *len; + +#ifdef RTMP_DEBUG_IO + { + int i; + fprintf(rsession->io_debug_out, + "SEND %"SWITCH_SIZE_T_FMT" bytes\n> ", *len); + + for (i = 0; i < *len; i++) { + fprintf(rsession->io_debug_out, "%02X ", (uint8_t)buf[i]); + + if (i != 0 && i % 32 == 0) { + fprintf(rsession->io_debug_out, "\n> "); + } + } + fprintf(rsession->io_debug_out, "\n\n "); + + fflush(rsession->io_debug_out); + } +#endif + + if (io_pvt->sendq && switch_buffer_inuse(io_pvt->sendq) > 0) { + /* We already have queued data, append it to the sendq */ + switch_buffer_write(io_pvt->sendq, buf, *len); + return SWITCH_STATUS_SUCCESS; + } + + status = switch_socket_send_nonblock(io_pvt->socket, (char*)buf, len); + + if (*len < orig_len) { + + if (rsession->state >= RS_DESTROY) { + return SWITCH_STATUS_FALSE; + } + + /* We didnt send it all... add it to the sendq*/ + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%"SWITCH_SIZE_T_FMT" bytes added to sendq.\n", (orig_len - *len)); + + switch_buffer_write(io_pvt->sendq, (buf + *len), orig_len - *len); + + /* Make sure we poll-write */ + rtmp_tcp_alter_pollfd(rsession, SWITCH_TRUE); + } + + return status; +} + +static switch_status_t rtmp_tcp_close(rtmp_session_t *rsession) +{ + rtmp_io_tcp_t *io = (rtmp_io_tcp_t*)rsession->profile->io; + rtmp_tcp_io_private_t *io_pvt = rsession->io_private; + + if (io_pvt->socket) { + switch_mutex_lock(io->mutex); + switch_pollset_remove(io->pollset, io_pvt->pollfd); + switch_mutex_unlock(io->mutex); + + switch_socket_close(io_pvt->socket); + io_pvt->socket = NULL; + } + return SWITCH_STATUS_SUCCESS; +} + + +void *SWITCH_THREAD_FUNC rtmp_io_tcp_thread(switch_thread_t *thread, void *obj) +{ + rtmp_io_tcp_t *io = (rtmp_io_tcp_t*)obj; + io->base.running = 1; + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s: I/O Thread starting\n", io->base.profile->name); + + + while(io->base.running) { + const switch_pollfd_t *fds; + int32_t numfds; + int32_t i; + switch_status_t status; + + switch_mutex_lock(io->mutex); + status = switch_pollset_poll(io->pollset, 500000, &numfds, &fds); + switch_mutex_unlock(io->mutex); + + if (status != SWITCH_STATUS_SUCCESS && status != SWITCH_STATUS_TIMEOUT) { + //switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "pollset_poll failed\n"); + continue; + } else if (status == SWITCH_STATUS_TIMEOUT) { + switch_yield(1); + } + + for (i = 0; i < numfds; i++) { + if (!fds[i].client_data) { + switch_socket_t *newsocket; + if (switch_socket_accept(&newsocket, io->listen_socket, io->base.pool) != SWITCH_STATUS_SUCCESS) { + if (io->base.running) { + /* Don't spam the logs if we are shutting down */ + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Socket Error [%s]\n", strerror(errno)); + } else { + return NULL; + } + } else { + rtmp_session_t *newsession; + if (rtmp_session_request(io->base.profile, &newsession) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "RTMP session request failed\n"); + switch_socket_close(newsocket); + } else { + switch_sockaddr_t *addr = NULL; + char ipbuf[200]; + + /* Create out private data and attach it to the rtmp session structure */ + rtmp_tcp_io_private_t *pvt = switch_core_alloc(newsession->pool, sizeof(*pvt)); + newsession->io_private = pvt; + pvt->socket = newsocket; + switch_socket_create_pollfd(&pvt->pollfd, newsocket, SWITCH_POLLIN | SWITCH_POLLERR, newsession, newsession->pool); + switch_pollset_add(io->pollset, pvt->pollfd); + switch_buffer_create_dynamic(&pvt->sendq, 512, 1024, 0); + + /* Get the remote address/port info */ + switch_socket_addr_get(&addr, SWITCH_TRUE, newsocket); + switch_get_addr(ipbuf, sizeof(ipbuf), addr); + newsession->remote_address = switch_core_strdup(newsession->pool, ipbuf); + newsession->remote_port = switch_sockaddr_get_port(addr); + } + } + } else { + rtmp_session_t *rsession = (rtmp_session_t*)fds[i].client_data; + rtmp_tcp_io_private_t *io_pvt = (rtmp_tcp_io_private_t*)rsession->io_private; + + if (fds[i].rtnevents & SWITCH_POLLOUT && switch_buffer_inuse(io_pvt->sendq) > 0) { + /* Send as much remaining data as possible */ + switch_size_t sendlen; + const void *ptr; + sendlen = switch_buffer_peek_zerocopy(io_pvt->sendq, &ptr); + switch_socket_send_nonblock(io_pvt->socket, ptr, &sendlen); + switch_buffer_toss(io_pvt->sendq, sendlen); + if (switch_buffer_inuse(io_pvt->sendq) == 0) { + /* Remove our fd from OUT polling */ + rtmp_tcp_alter_pollfd(rsession, SWITCH_FALSE); + } + } else if (fds[i].rtnevents & SWITCH_POLLIN && rtmp_handle_data(rsession) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_DEBUG, "Closing socket\n"); + + switch_mutex_lock(io->mutex); + switch_pollset_remove(io->pollset, io_pvt->pollfd); + switch_mutex_unlock(io->mutex); + + switch_socket_close(io_pvt->socket); + io_pvt->socket = NULL; + + rtmp_session_destroy(&rsession); + } + } + } + } + + io->base.running = -1; + switch_socket_close(io->listen_socket); + + return NULL; +} + +switch_status_t rtmp_tcp_init(rtmp_profile_t *profile, const char *bindaddr, rtmp_io_t **new_io, switch_memory_pool_t *pool) +{ + char *szport; + switch_sockaddr_t *sa; + switch_threadattr_t *thd_attr = NULL; + rtmp_io_tcp_t *io_tcp; + + io_tcp = (rtmp_io_tcp_t*)switch_core_alloc(pool, sizeof(rtmp_io_tcp_t)); + io_tcp->base.pool = pool; + io_tcp->ip = switch_core_strdup(pool, bindaddr); + + *new_io = (rtmp_io_t*)io_tcp; + io_tcp->base.profile = profile; + io_tcp->base.read = rtmp_tcp_read; + io_tcp->base.write = rtmp_tcp_write; + io_tcp->base.close = rtmp_tcp_close; + io_tcp->base.name = "tcp"; + io_tcp->base.address = switch_core_strdup(pool, io_tcp->ip); + + if ((szport = strchr(io_tcp->ip, ':'))) { + *szport++ = '\0'; + io_tcp->port = atoi(szport); + } else { + io_tcp->port = RTMP_DEFAULT_PORT; + } + + if (switch_sockaddr_info_get(&sa, io_tcp->ip, SWITCH_INET, io_tcp->port, 0, pool)) { + goto fail; + } + if (switch_socket_create(&io_tcp->listen_socket, switch_sockaddr_get_family(sa), SOCK_STREAM, SWITCH_PROTO_TCP, pool)) { + goto fail; + } + if (switch_socket_opt_set(io_tcp->listen_socket, SWITCH_SO_REUSEADDR, 1)) { + goto fail; + } + if (switch_socket_bind(io_tcp->listen_socket, sa)) { + goto fail; + } + if (switch_socket_listen(io_tcp->listen_socket, 10)) { + goto fail; + } + if (switch_socket_opt_set(io_tcp->listen_socket, SWITCH_SO_NONBLOCK, TRUE)) { + goto fail; + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Listening on %s:%u (tcp)\n", io_tcp->ip, io_tcp->port); + + io_tcp->base.running = 1; + + if (switch_pollset_create(&io_tcp->pollset, 1000 /* max poll fds */, pool, 0) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "pollset_create failed\n"); + goto fail; + } + + switch_socket_create_pollfd(&(io_tcp->listen_pollfd), io_tcp->listen_socket, SWITCH_POLLIN | SWITCH_POLLERR, NULL, pool); + if (switch_pollset_add(io_tcp->pollset, io_tcp->listen_pollfd) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "pollset_add failed\n"); + goto fail; + } + + switch_mutex_init(&io_tcp->mutex, SWITCH_MUTEX_NESTED, pool); + + switch_threadattr_create(&thd_attr, pool); + switch_threadattr_detach_set(thd_attr, 1); + switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE); + switch_thread_create(&io_tcp->thread, thd_attr, rtmp_io_tcp_thread, *new_io, pool); + + return SWITCH_STATUS_SUCCESS; +fail: + if (io_tcp->listen_socket) { + switch_socket_close(io_tcp->listen_socket); + } + *new_io = NULL; + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Socket error. Couldn't listen on %s:%u\n", io_tcp->ip, io_tcp->port); + return SWITCH_STATUS_FALSE; +} + +/* For Emacs: + * Local Variables: + * mode:c + * indent-tabs-mode:t + * tab-width:4 + * c-basic-offset:4 + * End: + * For VIM: + * vim:set softtabstop=4 shiftwidth=4 tabstop=4: + */