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/conf/autoload_configs/rtmp.conf.xml b/conf/autoload_configs/rtmp.conf.xml
new file mode 100644
index 0000000000..92afbc60b3
--- /dev/null
+++ b/conf/autoload_configs/rtmp.conf.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn.2008.vcproj b/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn.2008.vcproj
index 01395cb1db..f6263907e1 100644
--- a/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn.2008.vcproj
+++ b/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn.2008.vcproj
@@ -238,6 +238,10 @@
RelativePath=".\ftmod_sangoma_isdn_trace.c"
>
+
+
diff --git a/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn.2010.vcxproj b/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn.2010.vcxproj
index d7b75d8df4..7ec8fd3346 100644
--- a/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn.2010.vcxproj
+++ b/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn.2010.vcxproj
@@ -189,6 +189,7 @@
+
@@ -199,4 +200,4 @@
-
\ No newline at end of file
+
diff --git a/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn.c b/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn.c
index 56a10e8690..d74a67b483 100644
--- a/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn.c
+++ b/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn.c
@@ -342,7 +342,7 @@ static void ftdm_sangoma_isdn_wakeup_phy(ftdm_channel_t *dchan)
static void *ftdm_sangoma_isdn_io_run(ftdm_thread_t *me, void *obj)
{
uint8_t data[1000];
- unsigned i;
+ unsigned i = 0;
ftdm_status_t status = FTDM_SUCCESS;
ftdm_wait_flag_t wflags = FTDM_READ;
ftdm_span_t *span = (ftdm_span_t*) obj;
diff --git a/src/mod/codecs/mod_sangoma_codec/mod_sangoma_codec.c b/src/mod/codecs/mod_sangoma_codec/mod_sangoma_codec.c
index 6da0ca0b17..6971995f50 100644
--- a/src/mod/codecs/mod_sangoma_codec/mod_sangoma_codec.c
+++ b/src/mod/codecs/mod_sangoma_codec/mod_sangoma_codec.c
@@ -89,7 +89,7 @@ vocallo_codec_t g_codec_map[] =
{ SNGTC_CODEC_PCMA, IANA_PCMA_A_8000_1, "PCMA", "Sangoma PCMA", 40, 64000, 10000, 80, 160, 80, 8000, 8000, 1 },
{ SNGTC_CODEC_L16_1, IANA_L16_A_8000_1, "L16", "Sangoma L16", 40, 120000, 10000, 80, 160, 160, 8000, 8000, 0 },
{ SNGTC_CODEC_L16_2, IANA_L16_A_16000_1, "L16", "Sangoma L16 2", 40, 320000, 10000, 160, 320, 320, 16000, 16000, 0 },
- { SNGTC_CODEC_G729AB, IANA_G729_AB_8000_1, "G729", "Sangoma G729", 200, 8000, 10000, 80, 160, 10, 8000, 8000, 1 },
+ { SNGTC_CODEC_G729AB, IANA_G729_AB_8000_1, "G729", "Sangoma G729", 50, 8000, 10000, 80, 160, 10, 8000, 8000, 1 },
{ SNGTC_CODEC_G726_32, IANA_G726_32_8000_1, "G726-32", "Sangoma G.726 32k", 40, 32000, 10000, 80, 160, 40, 8000, 8000, 1 },
{ SNGTC_CODEC_G722, IANA_G722_A_8000_1, "G722", "Sangoma G722", 20, 64000, 10000, 80, 160, 80, 8000, 8000, 1 },
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..576cddafba
--- /dev/null
+++ b/src/mod/endpoints/mod_rtmp/mod_rtmp.c
@@ -0,0 +1,1826 @@
+/*
+ * 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
+ * Anthony Minessale II
+ *
+ * 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);
+
+ if (event) {
+ switch_event_header_t *hp;
+
+ for (hp = event->headers; hp; hp = hp->next) {
+ switch_channel_set_variable_name_printf(channel, hp->value, RTMP_USER_VARIABLE_PREFIX "_%s", hp->name);
+ }
+ }
+
+ 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, "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..724e1b064f
--- /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:
+ */
diff --git a/src/mod/endpoints/mod_sofia/sofia.c b/src/mod/endpoints/mod_sofia/sofia.c
index 6b8cf8be58..d9a2a3af0d 100644
--- a/src/mod/endpoints/mod_sofia/sofia.c
+++ b/src/mod/endpoints/mod_sofia/sofia.c
@@ -4723,7 +4723,7 @@ static void sofia_handle_sip_i_state(switch_core_session_t *session, int status,
const char *uuid;
switch_core_session_t *other_session = NULL;
switch_channel_t *other_channel = NULL;
- private_object_t *other_tech_pvt = NULL;
+ //private_object_t *other_tech_pvt = NULL;
char st[80] = "";
int is_dup_sdp = 0;
switch_event_t *s_event = NULL;
@@ -4930,7 +4930,7 @@ static void sofia_handle_sip_i_state(switch_core_session_t *session, int status,
if ((uuid = switch_channel_get_variable(channel, SWITCH_SIGNAL_BOND_VARIABLE))
&& (other_session = switch_core_session_locate(uuid))) {
other_channel = switch_core_session_get_channel(other_session);
- other_tech_pvt = switch_core_session_get_private(other_session);
+ //other_tech_pvt = switch_core_session_get_private(other_session);
if (!switch_channel_get_variable(other_channel, SWITCH_B_SDP_VARIABLE)) {
switch_channel_set_variable(other_channel, SWITCH_B_SDP_VARIABLE, r_sdp);