summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzhuzhenjun <[email protected]>2024-11-03 16:55:14 +0000
committerzhuzhenjun <[email protected]>2024-11-06 10:06:51 +0000
commit54837dbc1e9ed341610f3ac7409299f91b6ef55f (patch)
tree082a7c6e1f0a09cc1777956bb729a641410c4b44
parent8ddef31bb58c529763d565674ba5036584323921 (diff)
sip decoder initialdev-sip-decoder
-rw-r--r--decoders/CMakeLists.txt3
-rw-r--r--decoders/sip/CMakeLists.txt13
-rw-r--r--decoders/sip/sip.c1493
-rw-r--r--decoders/sip/sip_internal.h193
-rw-r--r--decoders/sip/version.map13
-rw-r--r--include/stellar/sip.h114
-rw-r--r--test/CMakeLists.txt5
-rw-r--r--test/decoders/sip/CMakeLists.txt151
-rw-r--r--test/decoders/sip/conf/spec.toml11
-rw-r--r--test/decoders/sip/pcap/01-complete-dialog.pcapbin0 -> 9624 bytes
-rw-r--r--test/decoders/sip/pcap/02-complete-call.pcapbin0 -> 5456 bytes
-rw-r--r--test/decoders/sip/pcap/03-complete-call-with-empty-line.pcapbin0 -> 5580 bytes
-rw-r--r--test/decoders/sip/pcap/04-complete-call-with-proxy.pcapbin0 -> 29495 bytes
-rw-r--r--test/decoders/sip/pcap/05-complete-call-with-two-dir-invite.pcapbin0 -> 12647 bytes
-rw-r--r--test/decoders/sip/pcap/06-complete-call-on-tcp.pcapbin0 -> 5395 bytes
-rw-r--r--test/decoders/sip/pcap/07-c2s-complete-dialog.pcapbin0 -> 5171 bytes
-rw-r--r--test/decoders/sip/pcap/08-c2s-complete-call.pcapbin0 -> 2705 bytes
-rw-r--r--test/decoders/sip/pcap/09-c2s-complete-call-with-proxy.pcapbin0 -> 17426 bytes
-rw-r--r--test/decoders/sip/pcap/10-s2c-complete-dialog.pcapbin0 -> 4477 bytes
-rw-r--r--test/decoders/sip/pcap/11-s2c-complete-call.pcapbin0 -> 2775 bytes
-rw-r--r--test/decoders/sip/pcap/12-s2c-complete-call-with-proxy.pcapbin0 -> 12093 bytes
-rw-r--r--test/decoders/sip/result/01-complete-dialog.json82
-rw-r--r--test/decoders/sip/result/02-complete-call.json40
-rw-r--r--test/decoders/sip/result/03-complete-call-with-empty-line.json40
-rw-r--r--test/decoders/sip/result/04-complete-call-with-proxy.json169
-rw-r--r--test/decoders/sip/result/05-complete-call-with-two-dir-invite.json77
-rw-r--r--test/decoders/sip/result/06-complete-call-on-tcp.json38
-rw-r--r--test/decoders/sip/result/07-c2s-complete-dialog.json62
-rw-r--r--test/decoders/sip/result/08-c2s-complete-call.json28
-rw-r--r--test/decoders/sip/result/09-c2s-complete-call-with-proxy.json120
-rw-r--r--test/decoders/sip/result/10-s2c-complete-dialog.json62
-rw-r--r--test/decoders/sip/result/11-s2c-complete-call.json28
-rw-r--r--test/decoders/sip/result/12-s2c-complete-call-with-proxy.json120
-rw-r--r--test/decoders/sip/result/13-complete-call-with-limit-1.json40
-rw-r--r--test/decoders/sip/result/14-complete-call-with-timeout-10s.json40
-rw-r--r--test/decoders/sip/result/15-complete-call-with-timeout-30s.json40
-rw-r--r--test/decoders/sip/sip_test_main.cpp79
-rw-r--r--test/decoders/sip/sip_test_module.cpp721
38 files changed, 3779 insertions, 3 deletions
diff --git a/decoders/CMakeLists.txt b/decoders/CMakeLists.txt
index efad779..aae7874 100644
--- a/decoders/CMakeLists.txt
+++ b/decoders/CMakeLists.txt
@@ -3,4 +3,5 @@ add_subdirectory(lpi_plus)
#add_subdirectory(http)
#add_subdirectory(socks)
#add_subdirectory(stratum)
-#add_subdirectory(session_flags) \ No newline at end of file
+#add_subdirectory(session_flags)
+add_subdirectory(sip)
diff --git a/decoders/sip/CMakeLists.txt b/decoders/sip/CMakeLists.txt
new file mode 100644
index 0000000..8e50eb3
--- /dev/null
+++ b/decoders/sip/CMakeLists.txt
@@ -0,0 +1,13 @@
+set(DECODER_NAME sip)
+
+file(GLOB DECODER_SRC "${CMAKE_CURRENT_SOURCE_DIR}/${DECODER_NAME}*.c")
+
+add_library(
+ ${DECODER_NAME}
+ ${DECODER_SRC}
+)
+
+set_target_properties(
+ ${DECODER_NAME} PROPERTIES
+ LINK_FLAGS "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/version.map"
+)
diff --git a/decoders/sip/sip.c b/decoders/sip/sip.c
new file mode 100644
index 0000000..830ac7c
--- /dev/null
+++ b/decoders/sip/sip.c
@@ -0,0 +1,1493 @@
+#include <stdio.h>
+#include <limits.h>
+#include <ctype.h>
+#include <assert.h>
+#include <sys/time.h>
+
+#include "stellar/packet.h"
+#include "stellar/utils.h"
+#include "stellar/mq.h"
+#include "stellar/session.h"
+#include "stellar/sip.h"
+
+#include "sip_internal.h"
+
+const char* g_sip_version[] = {
+ "unknown",
+ "sip/1.0",
+ "sip/1.1",
+ "sip/2.0"
+};
+
+
+const char* g_sip_method[] = {
+ "unknown",
+ "invite",
+ "ack",
+ "options",
+ "register",
+ "bye",
+ "cancel",
+ "do",
+ "info",
+ "message",
+ "notify",
+ "prack",
+ "qauth",
+ "refer",
+ "sprack",
+ "subscribe",
+ "update",
+ "publish"
+};
+
+static int strntoi(char *buff, size_t len) {
+ if (buff == NULL || len == 0) {
+ return 0;
+ }
+
+ int result = 0;
+ int sign = 1;
+ size_t i = 0;
+
+ while (i < len && isspace(buff[i])) {
+ i++;
+ }
+
+ if (i < len && buff[i] == '-') {
+ sign = -1;
+ i++;
+ } else if (i < len && buff[i] == '+') {
+ i++;
+ }
+
+ for (; i < len; i++) {
+ if (!isdigit(buff[i])) {
+ break;
+ }
+
+ int digit = buff[i] - '0';
+
+ if (result > (INT_MAX - digit) / 10) {
+ return (sign == 1) ? INT_MAX : INT_MIN;
+ }
+
+ result = result * 10 + digit;
+ }
+
+ return result * sign;
+}
+
+static enum sip_version sip_strn2version(const char *data, size_t len)
+{
+ int i;
+ for(i = 1; i < SIP_VERSION_NUM; i++) {
+ if(0 == SAFE_STRNCASECMP(data, len, g_sip_version[i], strlen(g_sip_version[i]))) {
+ break;
+ }
+ }
+ if (i == SIP_VERSION_NUM) {
+ return SIP_VERSION_UNKNOWN;
+ }
+ return (enum sip_version)i;
+}
+
+enum sip_method sip_strn2method(const char *data, size_t len)
+{
+ int i;
+ if (data == NULL || len == 0) {
+ return SIP_METHOD_UNKNOWN;
+ }
+ for(i = 1; i < SIP_METHOD_NUM; i++) {
+ if(0 == SAFE_STRNCASECMP(data, len, g_sip_method[i], strlen(g_sip_method[i]))) {
+ break;
+ }
+ }
+ if (i == SIP_METHOD_NUM) {
+ return SIP_METHOD_UNKNOWN;
+ }
+ return (enum sip_method)i;
+}
+
+static char* strntrim_sp(char *str, size_t *len) {
+ char *end;
+
+ while (*str == ' ' && *len > 0) {
+ str++;
+ (*len)--;
+ }
+
+ if (*len == 0) {
+ return str;
+ }
+
+ end = str + *len - 1;
+ while (end > str && *end == ' ') {
+ end--;
+ (*len)--;
+ }
+
+ return str;
+}
+
+static int strnsplit(struct iovec *out, size_t n_out, char delimiter, const char *data, size_t data_len)
+{
+ size_t i;
+ size_t offset;
+ size_t part_len;
+ const char *data_end;
+ const char *part_start;
+ const char *part_end;
+
+ if (out == NULL || n_out == 0 || data == NULL || data_len == 0) {
+ return -1;
+ }
+
+ offset = 0;
+ data_end = data + data_len;
+
+ for (i = 0; i < n_out - 1; i++) {
+ part_start = data + offset;
+ part_end = memchr(part_start, delimiter, data_end - part_start);
+
+ if (part_end != NULL) {
+ // Found a delimiter
+ part_len = part_end - part_start;
+ out[i].iov_base = strntrim_sp((char*)part_start, &part_len);
+ out[i].iov_len = part_len;
+ offset += part_len + 1;
+ } else {
+ // Last part, no delimiter found
+ part_len = data_end - part_start;
+ out[i].iov_base = strntrim_sp((char*)part_start, &part_len);
+ out[i].iov_len = part_len;
+ offset += part_len;
+ return i + 1;
+ }
+
+ if (offset >= data_len) {
+ return i + 1;
+ }
+ }
+
+ // last part
+ part_start = data + offset;
+ part_len = data_end - part_start;
+ out[i].iov_base = strntrim_sp((char*)part_start, &part_len);
+ out[i].iov_len = part_len;
+
+ return n_out;
+}
+
+static inline struct sip_header_field_pool* field_pool_new(size_t pool_size)
+{
+ struct sip_header_field_pool *pool;
+
+ pool = calloc(1, sizeof(struct sip_header_field_pool));
+ pool->arr = calloc(pool_size, sizeof(struct sip_header_field));
+ pool->size = pool_size;
+ pool->used = 0;
+
+ return pool;
+}
+
+static inline void field_pool_free(struct sip_header_field_pool *pool)
+{
+ if (pool) {
+ if (pool->arr) {
+ free(pool->arr);
+ }
+ free(pool);
+ }
+}
+
+static inline void field_pool_reset(struct sip_header_field_pool *pool)
+{
+ if (pool && pool->arr && pool->used != 0) {
+ pool->used = 0;
+ }
+}
+
+static inline struct sip_header_field *field_pool_series_get(struct sip_header_field_pool *pool)
+{
+ struct sip_header_field *header_field;
+ if (pool && pool ->arr && pool->used < pool->size) {
+ header_field = &pool->arr[pool->used++];
+ memset(header_field, 0, sizeof(struct sip_header_field));
+ return header_field;
+ }
+ return NULL;
+}
+static inline void stream_buffer_deinit(struct stream_buffer *sb)
+{
+ sb->buf_size = 0;
+ sb->buf_off = 0;
+ if (sb->buf) {
+ free(sb->buf);
+ }
+}
+
+static inline void stream_buffer_init(struct stream_buffer *sb, size_t size)
+{
+ sb->buf_size = size;
+ sb->buf_off = 0;
+ sb->buf = NULL;
+}
+
+static inline int stream_buffer_is_full(struct stream_buffer *sb)
+{
+ if (sb->buf && sb->buf_off == sb->buf_size) {
+ return 1;
+ }
+ return 0;
+}
+
+static inline int stream_buffer_is_empty(struct stream_buffer *sb)
+{
+ if (sb->buf == NULL || sb->buf_off == 0) {
+ return 1;
+ }
+ return 0;
+}
+
+static inline void stream_buffer_reset(struct stream_buffer *sb)
+{
+ if (sb->buf && sb->buf_off > 0) {
+ sb->buf_off = 0;
+ }
+}
+
+static inline void stream_buffer_append(struct stream_buffer *sb, const char *data, size_t data_len)
+{
+ if (sb->buf == NULL && sb->buf_size > 0) {
+ sb->buf = (char *)malloc(sb->buf_size);
+ sb->buf_off = 0;
+ }
+ if (sb->buf_off + data_len > sb->buf_size) {
+ data_len = sb->buf_size - sb->buf_off;
+ }
+ memcpy(sb->buf + sb->buf_off, data, data_len);
+}
+
+static inline void stream_buffer_slide(struct stream_buffer *sb, size_t offset)
+{
+ if (sb->buf) {
+ if (sb->buf_off > offset) {
+ memmove(sb->buf, sb->buf + offset, sb->buf_off - offset);
+ sb->buf_off = sb->buf_off - offset;
+ } else {
+ stream_buffer_reset(sb);
+ }
+ }
+}
+
+static inline void stream_buffer_get_data(struct stream_buffer *sb, const char **data, size_t *data_len, size_t offset)
+{
+ *data = NULL;
+ *data_len = 0;
+ if (sb->buf && sb->buf_off > offset) {
+ *data = sb->buf + offset;
+ *data_len = sb->buf_off - offset;
+ }
+}
+
+static void sip_message_free(struct sip_message *msg)
+{
+ if (msg) {
+ free(msg);
+ }
+}
+
+static struct sip_message* sip_message_new(void)
+{
+ struct sip_message *msg;
+ msg = (struct sip_message *)calloc(1, sizeof(struct sip_message));
+ return msg;
+}
+
+void sip_message_print(struct sip_message *msg)
+{
+ if (msg == NULL) {
+ printf("NULL SIP message\n");
+ return;
+ }
+
+ printf("SIP Message Type: %d\n", msg->type);
+ printf("Call State: %d\n", msg->call_state);
+ printf("Transaction Sequence: %d\n", msg->transaction_seq);
+
+ if (msg->type == SIP_MESSAGE_TYPE_REQUEST) {
+ printf("Request Line:\n");
+ printf(" Method: %d\n", msg->request_line.method);
+ printf(" URI: %.*s\n", (int)msg->request_line.uri_len, msg->request_line.uri);
+ printf(" Version: %.*s\n", (int)msg->request_line.version_len, msg->request_line.version);
+ } else if (msg->type == SIP_MESSAGE_TYPE_RESPONSE) {
+ printf("Status Line:\n");
+ printf(" Version: %.*s\n", (int)msg->status_line.version_len, msg->status_line.version);
+ printf(" Code: %.*s\n", (int)msg->status_line.code_len, msg->status_line.code);
+ printf(" Reason: %.*s\n", (int)msg->status_line.reason_len, msg->status_line.reason);
+ }
+
+ printf("Headers:\n");
+ for (size_t i = 0; i < msg->header.n_header_fields; i++) {
+ struct sip_header_field *field = &msg->header.header_fields[i];
+ printf(" %.*s: %.*s\n", (int)field->field_name_len, field->field_name,
+ (int)field->field_value_len, field->field_value);
+ }
+
+ if (msg->body.body_len > 0) {
+ printf("Body:\n");
+ printf(" Media IP: %.*s\n", (int)msg->body.media_ip_len, msg->body.media_ip);
+ printf(" Media Audio Port: %u\n", msg->body.media_audio_port);
+ printf(" Media Video Port: %u\n", msg->body.media_video_port);
+ printf(" Body: %.*s\n", (int)msg->body.body_len, msg->body.body);
+ } else {
+ printf("Body: Empty\n");
+ }
+}
+
+int sip_message_publish(struct sip_decoder *decoder, struct sip_message *msg, void *arg)
+{
+ UNUSED(arg);
+ int topic_id;
+ struct mq_runtime *runtime;
+
+ runtime = module_manager_get_mq_runtime(decoder->mod_mgr);
+ if (runtime == NULL) {
+ return -1;
+ }
+
+ switch (msg->type) {
+ case SIP_MESSAGE_TYPE_REQUEST:
+ topic_id = decoder->request_message_topic_id;
+ break;
+ case SIP_MESSAGE_TYPE_RESPONSE:
+ topic_id = decoder->response_message_topic_id;
+ break;
+ default:
+ return -1;
+ }
+
+ //sip_message_print(msg);
+
+ return mq_runtime_publish_message(runtime, topic_id, msg);
+}
+
+static void sip_message_dispatch(int topic, void *msg, on_msg_cb_func *msg_cb, void *arg, void *dispatch_arg)
+{
+ struct sip_message *sip_msg;
+ struct sip_decoder *decoder;
+
+ sip_msg = (struct sip_message *)msg;
+ decoder = (struct sip_decoder *)dispatch_arg;
+
+ if (topic == decoder->request_message_topic_id) {
+ sip_request_message_callback_func *req_cb = (sip_request_message_callback_func *)(void *)msg_cb;
+ req_cb(sip_msg->sess_ref, &sip_msg->request_line, &sip_msg->header, &sip_msg->body, arg);
+ return;
+ }
+
+ if (topic == decoder->response_message_topic_id) {
+ sip_response_message_callback_func *res_cb = (sip_response_message_callback_func *)(void *)msg_cb;
+ res_cb(sip_msg->sess_ref, &sip_msg->status_line, &sip_msg->header, &sip_msg->body, arg);
+ return;
+ }
+}
+
+static int sip_message_subscribe(struct module_manager *mod_mgr, const char *topic_name, on_msg_cb_func *cb, void *arg)
+{
+ int topic;
+ struct mq_schema *schema;
+
+ if (mod_mgr == NULL) {
+ return -1;
+ }
+
+ schema = module_manager_get_mq_schema(mod_mgr);
+ if (schema == NULL) {
+ return -1;
+ }
+
+ topic = mq_schema_get_topic_id(schema, topic_name);
+ if (topic < 0) {
+ return -1;
+ }
+
+ return mq_schema_subscribe(schema, topic, (on_msg_cb_func *)(void *)cb, arg);
+}
+
+static int sip_transaction_call_state_check(struct sip_transaction *transaction, enum sip_call_state call_state)
+{
+ return transaction->call_state == call_state;
+}
+
+static void sip_transaction_call_state_set(struct sip_transaction *transaction, enum sip_call_state call_state)
+{
+ transaction->call_state = call_state;
+}
+
+static void sip_transaction_call_state_update(struct sip_transaction *transaction, struct sip_message *msg)
+{
+ if (sip_transaction_call_state_check(transaction, SIP_CALL_STATE_OPENING)) {
+ sip_transaction_call_state_set(transaction, SIP_CALL_STATE_WAITING);
+ }
+
+ if (msg->type == SIP_MESSAGE_TYPE_REQUEST) {
+ enum sip_method method = msg->request_line.method;
+ switch (method) {
+ case SIP_METHOD_INVITE:
+ if (sip_transaction_call_state_check(transaction, SIP_CALL_STATE_WAITING)) {
+ sip_transaction_call_state_set(transaction, SIP_CALL_STATE_CALLING);
+ }
+ break;
+ case SIP_METHOD_BYE:
+ if (transaction->dir != SIP_DIR_DOUBLE) {
+ sip_transaction_call_state_set(transaction, SIP_CALL_STATE_DISCONNECTED);
+ } else {
+ sip_transaction_call_state_set(transaction, SIP_CALL_STATE_DISCONNECTING);
+ }
+ break;
+ case SIP_METHOD_ACK:
+ if (sip_transaction_call_state_check(transaction, SIP_CALL_STATE_CONNECTING)) {
+ sip_transaction_call_state_set(transaction, SIP_CALL_STATE_CONFIRMED);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (msg->type == SIP_MESSAGE_TYPE_RESPONSE) {
+ int stauts_code = strntoi((char*)msg->status_line.code, msg->status_line.code_len);
+ enum sip_method cseq_method = msg->header.cseq_method;
+
+ // rfc3261#section-7.2
+ switch (stauts_code/100) {
+ // status code 1xx
+ case 1:
+ if (cseq_method == SIP_METHOD_INVITE) {
+ if (sip_transaction_call_state_check(transaction, SIP_CALL_STATE_CALLING)) {
+ sip_transaction_call_state_set(transaction, SIP_CALL_STATE_EARLY);
+ }
+ if (transaction->dir != SIP_DIR_DOUBLE &&
+ sip_transaction_call_state_check(transaction, SIP_CALL_STATE_WAITING)) {
+
+ sip_transaction_call_state_set(transaction, SIP_CALL_STATE_EARLY);
+ }
+ }
+ break;
+ case 2:
+ if (stauts_code == 200) {
+ if (cseq_method == SIP_METHOD_INVITE) {
+
+ if (sip_transaction_call_state_check(transaction, SIP_CALL_STATE_EARLY) ||
+ sip_transaction_call_state_check(transaction, SIP_CALL_STATE_CALLING)) {
+
+ sip_transaction_call_state_set(transaction, SIP_CALL_STATE_CONNECTING);
+ }
+ }
+ if (cseq_method == SIP_METHOD_BYE) {
+ if (sip_transaction_call_state_check(transaction, SIP_CALL_STATE_DISCONNECTING)) {
+ sip_transaction_call_state_set(transaction, SIP_CALL_STATE_DISCONNECTED);
+ }
+
+ if (transaction->dir != SIP_DIR_DOUBLE &&
+ !sip_transaction_call_state_check(transaction, SIP_CALL_STATE_WAITING)) {
+
+ sip_transaction_call_state_set(transaction, SIP_CALL_STATE_DISCONNECTED);
+ }
+ }
+ }
+ break;
+ case 5:
+ if (!sip_transaction_call_state_check(transaction, SIP_CALL_STATE_WAITING)) {
+ sip_transaction_call_state_set(transaction, SIP_CALL_STATE_DISCONNECTED);
+ }
+ break;
+ case 3:
+ case 4:
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+static void sip_transaction_dir_update(struct sip_transaction *transaction, struct sip_message *msg)
+{
+ if (transaction->dir != SIP_DIR_DOUBLE) {
+ switch (transaction->dir) {
+ case SIP_DIR_UNKNOWN:
+ if (msg->type == SIP_MESSAGE_TYPE_REQUEST) {
+ transaction->dir = SIP_DIR_REQUEST;
+ }
+ if (msg->type == SIP_MESSAGE_TYPE_RESPONSE){
+ transaction->dir = SIP_DIR_RESPONSE;
+ }
+ break;
+ case SIP_DIR_REQUEST:
+ if (msg->type == SIP_MESSAGE_TYPE_RESPONSE) {
+ transaction->dir = SIP_DIR_DOUBLE;
+ }
+ break;
+ case SIP_DIR_RESPONSE:
+ if (msg->type == SIP_MESSAGE_TYPE_REQUEST) {
+ transaction->dir = SIP_DIR_DOUBLE;
+ }
+ break;
+ case SIP_DIR_DOUBLE:
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+static void sip_transaction_free(struct sip_transaction *transaction)
+{
+ if (transaction) {
+ if (transaction->key) {
+ free(transaction->key);
+ }
+ free(transaction);
+ }
+}
+
+static struct sip_transaction* sip_transaction_new(void)
+{
+ return (struct sip_transaction *)calloc(1, sizeof(struct sip_transaction));
+}
+
+static void sip_transaction_close(struct sip_decoder *decoder, struct sip_exdata *exdata, struct sip_transaction *transaction)
+{
+ UNUSED(decoder);
+ UNUSED(exdata);
+// int ret;
+// struct sip_message *msg;
+
+ sip_transaction_call_state_set(transaction, SIP_CALL_STATE_CLOSING);
+
+// msg = sip_message_new();
+// msg->sess_ref = exdata->sess_ref;
+// msg->transaction_seq = transaction->seq;
+// msg->call_state = SIP_CALL_STATE_CLOSING;
+// msg->type = SIP_MESSAGE_TYPE_REQUEST;
+// ret = sip_message_publish(decoder, msg, NULL);
+// if (ret < 0) {
+// sip_message_free(msg);
+// }
+//
+// msg = sip_message_new();
+// msg->sess_ref = exdata->sess_ref;
+// msg->transaction_seq = transaction->seq;
+// msg->call_state = SIP_CALL_STATE_CLOSING;
+// msg->type = SIP_MESSAGE_TYPE_RESPONSE;
+// ret = sip_message_publish(decoder, msg, NULL);
+// if (ret < 0) {
+// sip_message_free(msg);
+// }
+
+ sip_transaction_free(transaction);
+}
+
+static struct sip_transaction *sip_transaction_open(struct sip_decoder *decoder, struct sip_exdata *exdata, const char *call_id, size_t call_id_len)
+{
+ UNUSED(decoder);
+ UNUSED(exdata);
+ // int ret;
+// struct sip_message *msg;
+ struct sip_transaction *transaction;
+
+ transaction = sip_transaction_new();
+ transaction->seq = exdata->transaction_seq++;
+ transaction->call_state = SIP_CALL_STATE_OPENING;
+ transaction->key = malloc(call_id_len);
+ transaction->key_len = call_id_len;
+ memcpy(transaction->key, call_id, call_id_len);
+
+// msg = sip_message_new();
+// msg->sess_ref = exdata->sess_ref;
+// msg->transaction_seq = transaction->seq;
+// msg->call_state = SIP_CALL_STATE_OPENING;
+// msg->type = SIP_MESSAGE_TYPE_REQUEST;
+// ret = sip_message_publish(decoder, msg, NULL);
+// if (ret < 0) {
+// sip_message_free(msg);
+// }
+// msg = sip_message_new();
+// msg->sess_ref = exdata->sess_ref;
+// msg->transaction_seq = transaction->seq;
+// msg->call_state = SIP_CALL_STATE_OPENING;
+// msg->type = SIP_MESSAGE_TYPE_RESPONSE;
+// ret = sip_message_publish(decoder, msg, NULL);
+// if (ret < 0) {
+// sip_message_free(msg);
+// }
+
+ return transaction;
+}
+
+static void sip_transaction_prune(struct sip_decoder *decoder, struct sip_exdata *exdata, struct timeval ts)
+{
+ struct sip_transaction *transaction = NULL, *tmp = NULL, *oldest;
+
+ if (exdata->transaction_table == NULL || decoder->transaction_timeout_ms == 0) {
+ return;
+ }
+
+ if (decoder->transaction_limit_count > 0 &&
+ decoder->transaction_limit_count <= (int)HASH_CNT(hh, exdata->transaction_table)) {
+ oldest = exdata->transaction_table;
+ HASH_DELETE(hh, exdata->transaction_table, oldest);
+ sip_transaction_close(decoder, exdata, oldest);
+ }
+
+ HASH_ITER(hh, exdata->transaction_table, transaction, tmp) {
+ if (timeval_delta_ms(transaction->last_update, ts) < decoder->transaction_timeout_ms) {
+ break;
+ }
+
+ HASH_DELETE(hh, exdata->transaction_table, transaction);
+ sip_transaction_close(decoder, exdata, transaction);
+ }
+}
+
+static void sip_transaction_process(struct sip_decoder *decoder, struct sip_exdata *exdata, struct sip_message *msgs[], size_t n_msgs)
+{
+ int ret;
+ size_t i;
+ struct timeval ts;
+ struct sip_message *msg;
+ struct sip_transaction *transaction;
+ struct sip_header_field *call_id;
+
+ gettimeofday(&ts, NULL);
+
+ for (i = 0 ; i < n_msgs; i++) {
+ msg = msgs[i];
+ call_id = msg->header.call_id;
+
+ if (call_id == NULL || call_id->field_value == NULL || call_id->field_value_len == 0) {
+ continue;
+ }
+
+ // get transaction
+ HASH_FIND(hh, exdata->transaction_table, call_id->field_value, call_id->field_value_len, transaction);
+ if (transaction == NULL) {
+ transaction = sip_transaction_open(decoder, exdata, call_id->field_value, call_id->field_value_len);
+ } else {
+ HASH_DELETE(hh, exdata->transaction_table, transaction);
+ }
+ HASH_ADD_KEYPTR(hh, exdata->transaction_table, transaction->key, transaction->key_len, transaction);
+
+ // update transaction
+ transaction->last_update = ts;
+ sip_transaction_dir_update(transaction, msg);
+ sip_transaction_call_state_update(transaction, msg);
+
+ // publish message
+ msg->transaction_seq = transaction->seq;
+ msg->call_state = transaction->call_state;
+ ret = sip_message_publish(decoder, msg, NULL);
+ if (ret < 0) {
+ sip_message_free(msg);
+ }
+
+ // transaction that call state is disconnected, should be closed immediately
+ if (sip_transaction_call_state_check(transaction, SIP_CALL_STATE_DISCONNECTED)) {
+ HASH_DELETE(hh, exdata->transaction_table, transaction);
+ sip_transaction_close(decoder, exdata, transaction);
+ }
+
+ // timeout transaction
+ sip_transaction_prune(decoder, exdata, ts);
+ }
+}
+
+int sip_decode_sdp_media(struct sip_message *msg, const char *line_start, size_t line_len)
+{
+ int ret;
+ struct iovec parts[SDP_MEDIA_PART_NUM];
+ const char *media;
+ size_t media_len;
+ const char *port;
+ size_t port_len;
+
+ if (line_start == NULL || line_len <= 2) {
+ return -1;
+ }
+
+ // skip "m="
+ line_start += strlen("m=");
+ line_len -= strlen("m=");
+
+ ret = strnsplit(parts, SIP_DIM(parts), ' ', line_start, line_len);
+ if (ret != SIP_DIM(parts)) {
+ return -1;
+ }
+
+ media = (const char *)parts[SDP_MEDIA_PART_MEDIA].iov_base;
+ media_len = parts[SDP_MEDIA_PART_MEDIA].iov_len;
+ port = (const char *)parts[SDP_MEDIA_PART_PORT].iov_base;
+ port_len = parts[SDP_MEDIA_PART_PORT].iov_len;
+
+ if (0 == SAFE_STRNCASECMP(media, media_len, "audio", strlen("audio"))) {
+ msg->body.media_audio_port = strntoi((char*)port, port_len);
+ return 0;
+ }
+ if (0 == SAFE_STRNCASECMP(media, media_len, "video", strlen("video"))) {
+ msg->body.media_video_port = strntoi((char*)port, port_len);
+ return 0;
+ }
+
+ return 0;
+}
+
+int sip_decode_sdp_connection(struct sip_message *msg, const char *line_start, size_t line_len)
+{
+ int ret;
+ struct iovec parts[SDP_CONNECTION_PART_NUM];
+ const char *addr;
+ size_t addr_len;
+ const char *nettype;
+ size_t nettype_len;
+
+ if (line_start == NULL || line_len <= 2) {
+ return -1;
+ }
+
+ // skip "c="
+ line_start += strlen("c=");
+ line_len -= strlen("c=");
+
+ ret = strnsplit(parts, SIP_DIM(parts), ' ', line_start, line_len);
+ if (ret != SIP_DIM(parts)) {
+ return -1;
+ }
+
+ addr = (const char *)parts[SDP_CONNECTION_PART_ADDRESS].iov_base;
+ addr_len = parts[SDP_CONNECTION_PART_ADDRESS].iov_len;
+ nettype = (const char *)parts[SDP_CONNECTION_PART_NETTYPE].iov_base;
+ nettype_len = parts[SDP_CONNECTION_PART_NETTYPE].iov_len;
+
+ if (0 == SAFE_STRNCASECMP(nettype, nettype_len, "IN", strlen("IN"))) {
+ msg->body.media_ip = addr;
+ msg->body.media_ip_len = addr_len;
+ return 0;
+ }
+
+ return 0;
+}
+
+static int sip_decode_body(struct sip_message *msg, const char *body, size_t body_len)
+{
+ int ret;
+ size_t remain, offset;
+ struct sip_header_field *content_type;
+
+ if (body == NULL || body_len == 0) {
+ return -1;
+ }
+
+ msg->body.body = body;
+ msg->body.body_len = body_len;
+
+ content_type = msg->header.content_type;
+ if (content_type == NULL) {
+ return 0;
+ }
+
+ // parse sdp media
+ if (0 != SAFE_STRNCASECMP(content_type->field_value, content_type->field_value_len, "application/sdp", strlen("application/sdp"))) {
+ return 0;
+ }
+
+ msg->body.sdp_content = body;
+ msg->body.sdp_content_len = body_len;
+
+ offset = 0;
+ remain = body_len;
+
+ // body lines
+ while (remain > 0) {
+ const char *line_start, *line_end;
+ size_t line_len;
+
+ // line
+ line_start = body + offset;
+ line_end = (const char *)memmem(line_start, remain, SIP_LINE_END, strlen(SIP_LINE_END));
+ if (line_end == NULL || line_end == line_start) {
+ break;
+ }
+
+ line_len = line_end - line_start + strlen(SIP_LINE_END);
+ switch (line_start[0]) {
+ case 'c':
+ ret = sip_decode_sdp_connection(msg, line_start, line_len - strlen(SIP_LINE_END));
+ if (ret != 0) {
+ return -1;
+ }
+ break;
+ case 'm':
+ ret = sip_decode_sdp_media(msg, line_start, line_len - strlen(SIP_LINE_END));
+ if (ret != 0) {
+ return -1;
+ }
+ break;
+ default:
+ break;
+ }
+
+ offset += line_len;
+ remain -= line_len;
+ }
+ return 0;
+}
+
+static int sip_decode_header_field(struct sip_message *msg, const char *line_start, size_t line_len)
+{
+ int ret;
+ struct iovec field_parts[SIP_HEADER_FIELD_PART_NUM];
+ struct sip_header_field *header_field;
+ struct sip_header *header = &msg->header;
+
+ if (line_start == NULL || line_len == 0) {
+ return -1;
+ }
+
+ ret = strnsplit(field_parts, SIP_DIM(field_parts), ':', line_start, line_len);
+ if (ret != SIP_DIM(field_parts)) {
+ return -1;
+ }
+
+ header_field = field_pool_series_get(msg->header_field_pool);
+ if (header_field == NULL) {
+ return -1;
+ }
+
+ if (header->header_fields == NULL) {
+ header->header_fields = header_field;
+ }
+ header->n_header_fields++;
+ header_field->field_name = (const char*)field_parts[SIP_HEADER_FIELD_PART_NAME].iov_base;
+ header_field->field_name_len = field_parts[SIP_HEADER_FIELD_PART_NAME].iov_len;
+ header_field->field_value = (const char*)field_parts[SIP_HEADER_FIELD_PART_VALUE].iov_base;
+ header_field->field_value_len = field_parts[SIP_HEADER_FIELD_PART_VALUE].iov_len;
+
+ if (0 == SAFE_STRNCASECMP(header_field->field_name, header_field->field_name_len, "Call-ID", strlen("Call-ID"))) {
+ msg->header.call_id = header_field;
+ return 0;
+ }
+
+ if (0 == SAFE_STRNCASECMP(header_field->field_name, header_field->field_name_len, "From", strlen("From"))) {
+ msg->header.from = header_field;
+ return 0;
+ }
+
+ if (0 == SAFE_STRNCASECMP(header_field->field_name, header_field->field_name_len, "To", strlen("To"))) {
+ msg->header.to = header_field;
+ }
+
+ if (0 == SAFE_STRNCASECMP(header_field->field_name, header_field->field_name_len, "CSeq", strlen("CSeq"))) {
+ msg->header.cseq = header_field;
+
+ struct iovec cseq_parts[SIP_CSEQ_PART_NUM];
+ ret = strnsplit(cseq_parts, SIP_DIM(cseq_parts), ' ', header_field->field_value, header_field->field_value_len);
+ if (ret != SIP_DIM(cseq_parts)) {
+ return 0;
+ }
+ msg->header.cseq_method = sip_strn2method((const char *)cseq_parts[SIP_CSEQ_PART_METHOD].iov_base,
+ cseq_parts[SIP_CSEQ_PART_METHOD].iov_len);
+ return 0;
+ }
+
+ if (0 == SAFE_STRNCASECMP(header_field->field_name, header_field->field_name_len, "Via", strlen("Via"))) {
+ msg->header.via = header_field;
+ return 0;
+ }
+
+ if (0 == SAFE_STRNCASECMP(header_field->field_name, header_field->field_name_len, "Server", strlen("Server"))) {
+ msg->header.server = header_field;
+ return 0;
+ }
+
+ if (0 == SAFE_STRNCASECMP(header_field->field_name, header_field->field_name_len, "Reason", strlen("Reason"))) {
+ msg->header.reason = header_field;
+ return 0;
+ }
+
+ if (0 == SAFE_STRNCASECMP(header_field->field_name, header_field->field_name_len, "User-Agent", strlen("User-Agent"))) {
+ msg->header.user_agent = header_field;
+ return 0;
+ }
+
+ if (0 == SAFE_STRNCASECMP(header_field->field_name, header_field->field_name_len, "Content-Type", strlen("Content-Type"))) {
+ msg->header.content_type = header_field;
+ return 0;
+ }
+
+ if (0 == SAFE_STRNCASECMP(header_field->field_name, header_field->field_name_len, "Content-Length", strlen("Content-Length"))) {
+ msg->header.content_length = header_field;
+ return 0;
+ }
+
+ return 0;
+}
+
+int sip_decode_header(struct sip_message *msg, const char *header_start, size_t header_len)
+{
+ int ret = 0;
+ size_t remain, offset;
+
+ offset = 0;
+ remain = header_len;
+
+ // header lines
+ while (remain > 0) {
+ const char *line_start, *line_end;
+ size_t line_len;
+
+ // line
+ line_start = header_start + offset;
+ line_end = (const char *)memmem(line_start, remain, SIP_LINE_END, strlen(SIP_LINE_END));
+ if (line_end == NULL || line_end == line_start) {
+ break;
+ }
+
+ line_len = line_end - line_start + strlen(SIP_LINE_END);
+ ret = sip_decode_header_field(msg, line_start, line_len - strlen(SIP_LINE_END));
+ if (ret != 0) {
+ break;
+ }
+
+ offset += line_len;
+ remain -= line_len;
+ }
+
+ return ret;
+}
+
+int sip_decode_start_line(struct sip_message *msg, const char *payload, size_t payload_len)
+{
+ int ret;
+ enum sip_method method;
+ enum sip_version version;
+ struct iovec parts[SIP_START_LINE_PART_NUM];
+
+ ret = strnsplit(parts, SIP_DIM(parts), ' ', payload, payload_len);
+ if (ret != SIP_DIM(parts)) {
+ return -1;
+ }
+
+ // try to decode as request line
+ method = sip_strn2method((const char *)parts[SIP_REQUEST_LINE_PART_METHOD].iov_base, parts[SIP_REQUEST_LINE_PART_METHOD].iov_len);
+ if (method != SIP_METHOD_UNKNOWN) {
+ msg->request_line.method = method;
+ msg->request_line.uri = (const char *)parts[SIP_REQUEST_LINE_PART_URI].iov_base;
+ msg->request_line.uri_len = parts[SIP_REQUEST_LINE_PART_URI].iov_len;
+ msg->request_line.version = (const char *)parts[SIP_REQUEST_LINE_PART_VERSION].iov_base;
+ msg->request_line.version_len = parts[SIP_REQUEST_LINE_PART_VERSION].iov_len;
+ msg->type = SIP_MESSAGE_TYPE_REQUEST;
+ return 0;
+ }
+
+ // try to decode as status line
+ version = sip_strn2version((const char *)parts[SIP_STATUS_LINE_PART_VERSION].iov_base, parts[SIP_STATUS_LINE_PART_VERSION].iov_len);
+ if (version != SIP_VERSION_UNKNOWN) {
+ msg->status_line.version = (const char *)parts[SIP_STATUS_LINE_PART_VERSION].iov_base;
+ msg->status_line.version_len = parts[SIP_STATUS_LINE_PART_VERSION].iov_len;
+ msg->status_line.code = (const char *)parts[SIP_STATUS_LINE_PART_CODE].iov_base;
+ msg->status_line.code_len = parts[SIP_STATUS_LINE_PART_CODE].iov_len;
+ msg->status_line.reason = (const char *)parts[SIP_STATUS_LINE_PART_REASON].iov_base;
+ msg->status_line.reason_len = parts[SIP_STATUS_LINE_PART_REASON].iov_len;
+ msg->type = SIP_MESSAGE_TYPE_RESPONSE;
+ return 0;
+ }
+
+ return -1;
+}
+
+int sip_decode(struct sip_message *msg, const char *payload, size_t payload_len)
+{
+ int ret = 0;
+ int content_length;
+ size_t offset;
+ size_t line_len = 0;
+ size_t header_len = 0;
+ size_t body_len = 0;
+ const char *line_start = NULL;
+ const char *header_start = NULL;
+ const char *body_start = NULL;
+ const char *line_end;
+ const char *header_end;
+
+ offset = 0;
+
+ // check start-line complete
+ line_start = payload + offset;
+ line_end = (const char *)memmem(line_start, payload_len - offset, SIP_LINE_END, strlen(SIP_LINE_END));
+ if (line_end == NULL) {
+ goto exit;
+ }
+
+ line_len = line_end - line_start + strlen(SIP_LINE_END);
+ offset += line_len;
+
+ if (offset > payload_len) {
+ goto exit;
+ }
+
+ // decode start line
+ ret = sip_decode_start_line(msg, line_start, line_len);
+ if (ret != 0) {
+ ret = -1;
+ goto exit;
+ }
+
+ // check message-header complete
+ header_start = payload + offset;
+ header_end = (const char *)memmem(header_start, payload_len - offset, SIP_HEADER_END, strlen(SIP_HEADER_END));
+ if (header_end == NULL) {
+ goto exit;
+ }
+
+ header_len = header_end - header_start + strlen(SIP_HEADER_END);
+ offset += header_len;
+
+ if (offset > payload_len) {
+ goto exit;
+ }
+
+ // decode header
+ ret = sip_decode_header(msg, header_start, header_len);
+ if (ret != 0) {
+ ret = -1;
+ goto exit;
+ }
+
+ // check body complete
+ body_start = NULL;
+ body_len = 0;
+ if (msg->header.content_length &&
+ msg->header.content_length->field_value && msg->header.content_length->field_value_len > 0) {
+ content_length = strntoi((char*)msg->header.content_length->field_value, msg->header.content_length->field_value_len);
+ if (content_length > 0) {
+ body_start = payload + offset;
+ body_len = content_length;
+
+ offset += body_len;
+ if (offset > payload_len) {
+ goto exit;
+ }
+
+ ret = sip_decode_body(msg, body_start, body_len);
+ if (ret != 0) {
+ goto exit;
+ }
+ }
+ }
+
+ ret = offset;
+exit:
+ return ret;
+}
+
+static enum sip_identify_state sip_identify(struct sip_decoder *decoder, struct sip_exdata *exdata, const char *payload, size_t payload_len)
+{
+ UNUSED(decoder);
+ int ret;
+ size_t i;
+ size_t start_line_len = 0;
+ const char *start_line = NULL;
+ struct iovec parts[SIP_START_LINE_PART_NUM];
+ enum sip_method method;
+ enum sip_version version;
+
+ if (exdata->identify_state == SIP_IDENTIFY_STATE_FALSE ||
+ exdata->identify_state == SIP_IDENTIFY_STATE_TRUE) {
+ goto exit;
+ }
+
+ if (exdata->identify_times++ > SIP_IDENTIFY_TIMES_MAX) {
+ exdata->identify_state = SIP_IDENTIFY_STATE_FALSE;
+ goto exit;
+ }
+
+ // skip first '\r\n'
+ for (i = 0; i < payload_len; i++) {
+ if (payload[i] != '\r' && payload[i] != '\n') {
+ payload += i;
+ payload_len -= i;
+ break;
+ }
+ }
+
+ if (payload_len == 0) {
+ exdata->identify_state = SIP_IDENTIFY_STATE_HALF_TRUE;
+ goto exit;
+ }
+
+ start_line = payload;
+ for (i = 0; i < payload_len; i++) {
+ if (payload[i] == '\r' || payload[i] == '\n') {
+ start_line_len = i;
+ break;
+ }
+ }
+
+ if (start_line_len == 0) {
+ exdata->identify_state = SIP_IDENTIFY_STATE_FALSE;
+ goto exit;
+ }
+
+ // split start line
+ ret = strnsplit(parts, SIP_DIM(parts), ' ', (char *)start_line, start_line_len);
+ if (ret != SIP_START_LINE_PART_NUM) {
+ exdata->identify_state = SIP_IDENTIFY_STATE_FALSE;
+ goto exit;
+ }
+
+ // it's sip request ?
+ method = sip_strn2method((const char *)parts[SIP_REQUEST_LINE_PART_METHOD].iov_base,
+ parts[SIP_REQUEST_LINE_PART_METHOD].iov_len);
+ version = sip_strn2version((const char *)parts[SIP_REQUEST_LINE_PART_VERSION].iov_base,
+ parts[SIP_REQUEST_LINE_PART_VERSION].iov_len);
+ if (method != SIP_METHOD_UNKNOWN && version != SIP_VERSION_UNKNOWN) {
+ exdata->identify_state = SIP_IDENTIFY_STATE_TRUE;
+ goto exit;
+ }
+
+ // it's sip response ?
+ version = sip_strn2version((const char *)parts[SIP_STATUS_LINE_PART_VERSION].iov_base,
+ parts[SIP_STATUS_LINE_PART_VERSION].iov_len);
+ if (version != SIP_VERSION_UNKNOWN && parts[SIP_STATUS_LINE_PART_CODE].iov_len == SIP_STATUS_CODE_LEN) {
+ exdata->identify_state = SIP_IDENTIFY_STATE_TRUE;
+ goto exit;
+ }
+
+ exdata->identify_state = SIP_IDENTIFY_STATE_FALSE;
+exit:
+ return exdata->identify_state;
+}
+
+static void sip_exdata_free(struct sip_exdata *exdata)
+{
+ struct sip_transaction *transaction = NULL, *tmp = NULL;
+
+ if (exdata) {
+ if (exdata->transaction_table) {
+ HASH_ITER(hh, exdata->transaction_table, transaction, tmp) {
+ HASH_DELETE(hh, exdata->transaction_table, transaction);
+
+ sip_transaction_close(exdata->decoder_ref, exdata, transaction);
+ }
+ }
+
+ if (exdata->streambuffer.buf) {
+ free(exdata->streambuffer.buf);
+ exdata->streambuffer.buf = NULL;
+ }
+ free(exdata);
+ }
+}
+
+static struct sip_exdata *sip_exdata_new(void)
+{
+ struct sip_exdata *exdata;
+ exdata = (struct sip_exdata *)calloc(1, sizeof(struct sip_exdata));
+ stream_buffer_init(&exdata->streambuffer, SIP_STREAM_BUFFER_DEFAULT_SIZE);
+ return exdata;
+}
+
+static void sip_on_payload(struct session *sess, enum session_state state, const char *payload, size_t payload_len, void *arg)
+{
+ UNUSED(state);
+ struct sip_exdata *exdata;
+ struct sip_decoder *decoder;
+ enum sip_identify_state identify_state;
+
+ // session is closing?
+ if (payload == NULL) {
+ return;
+ }
+
+ decoder = (struct sip_decoder *)arg;
+
+ exdata = (struct sip_exdata *)session_get_exdata(sess, decoder->exdata_id);
+ if (exdata == NULL) {
+ exdata = sip_exdata_new();
+ exdata->decoder_ref = decoder;
+ exdata->sess_ref = sess;
+ session_set_exdata(sess, decoder->exdata_id, exdata);
+ }
+
+ if (exdata->sess_ignored) {
+ return;
+ }
+
+ identify_state = sip_identify(decoder, exdata, payload, payload_len);
+ if (identify_state == SIP_IDENTIFY_STATE_FALSE) {
+ exdata->sess_ignored = 1;
+ return;
+ }
+ if (identify_state != SIP_IDENTIFY_STATE_TRUE) {
+ return;
+ }
+
+ /*
+ * We need both remaining data from last sip message and
+ * current sip message
+ */
+ if (!stream_buffer_is_empty(&exdata->streambuffer)) {
+ stream_buffer_append(&exdata->streambuffer, payload, payload_len);
+ if (stream_buffer_is_full(&exdata->streambuffer)) {
+ stream_buffer_reset(&exdata->streambuffer);
+ return;
+ }
+
+ stream_buffer_get_data(&exdata->streambuffer, &payload, &payload_len, 0);
+ }
+
+ int tid, msg_cnt = 0;
+ int decoded = 0;
+ int offset;
+ int remain;
+ struct sip_message *msg;
+ struct sip_message* msg_list[SIP_PER_PAYLOAD_MESSAGE_MAX];
+ struct sip_header_field_pool *field_pool;
+
+ tid = module_manager_get_thread_id(decoder->mod_mgr);
+ field_pool = decoder->field_pools[tid];
+ field_pool_reset(field_pool);
+
+ /*
+ * There may be multiple SIP messages in the payload. We should parse all of them
+ * and cache the last incomplete one, if it exists.
+ */
+ offset = 0;
+ remain = payload_len;
+ while (remain > 0 && msg_cnt < (int)sizeof(msg_list)) {
+ msg= sip_message_new();
+ msg->sess_ref = sess;
+ msg->header_field_pool = field_pool;
+
+ decoded = sip_decode(msg, payload + offset, remain);
+ // decode failed
+ if (decoded < 0) {
+ sip_message_free(msg);
+ return;
+ }
+
+ // incomplete SIP message, break and cache it
+ if (decoded == 0) {
+ sip_message_free(msg);
+ break;
+ }
+
+ msg_list[msg_cnt++] = msg;
+
+ offset += decoded;
+ remain -= decoded;
+ }
+
+ // messages should be published or freed in sip_transaction_process
+ if (msg_cnt > 0) {
+ sip_transaction_process(decoder, exdata, msg_list, msg_cnt);
+ }
+
+ // remove decoded data
+ if (!stream_buffer_is_empty(&exdata->streambuffer)) {
+ stream_buffer_slide(&exdata->streambuffer, offset);
+ }
+
+ // cache remaining data
+ if (remain > 0) {
+ stream_buffer_append(&exdata->streambuffer, payload + offset, remain);
+ if (stream_buffer_is_full(&exdata->streambuffer)) {
+ // reach streambuffer limit, just reset
+ stream_buffer_reset(&exdata->streambuffer);
+ }
+ }
+}
+
+static void sip_on_tcp_payload(struct session *sess, enum session_state state, const char *payload, uint32_t payload_len, void *arg)
+{
+ sip_on_payload(sess, state, payload, (size_t)payload_len, arg);
+}
+
+static void sip_on_udp_packet(struct session *sess, enum session_state state, struct packet *pkt, void *arg)
+{
+ const char *payload;
+ size_t payload_len;
+
+ if (pkt == NULL) {
+ payload = NULL;
+ payload_len = 0;
+ } else {
+ payload = packet_get_payload_data(pkt);
+ payload_len = packet_get_payload_len(pkt);
+ }
+
+ sip_on_payload(sess, state, payload, payload_len, arg);
+}
+
+void sip_on_exdata_free(int idx, void *ex_ptr, void *arg)
+{
+ UNUSED(idx);
+ UNUSED(arg);
+ struct sip_exdata *exdata = (struct sip_exdata *)ex_ptr;
+
+ if (exdata) {
+ sip_exdata_free(exdata);
+ }
+}
+
+static void sip_on_message_free(void *msg, void *arg)
+{
+ UNUSED(arg);
+ if (msg) {
+ free(msg);
+ }
+}
+
+long long sip_get_call_duration_ms(struct sip_decoder *decoder, struct session *sess, const char *call_id, size_t call_id_len)
+{
+ //struct module *mod;
+ //struct sip_decoder *decoder;
+ struct sip_exdata *exdata;
+ struct sip_transaction *transaction = NULL;
+
+ //mod = module_manager_get_module(mod_mgr, SIP_MODULE_NAME);
+ //if (mod == NULL) {
+ // return -1;
+ //}
+
+ //decoder = module_get_ctx(mod);
+ //if (decoder == NULL) {
+ // return -1;
+ //}
+
+ exdata = (struct sip_exdata *)session_get_exdata(sess, decoder->exdata_id);
+ if (exdata == NULL) {
+ return -1;
+ }
+
+ HASH_FIND(hh, exdata->transaction_table, call_id, call_id_len, transaction);
+ if (transaction == NULL) {
+ return -1;
+ }
+
+ if (transaction->call_duration_ms == 0) {
+ return -1;
+ }
+
+ return transaction->call_duration_ms;
+}
+
+struct sip_decoder *module_to_sip_decoder(struct module *mod)
+{
+ assert(mod);
+ assert(strcmp(module_get_name(mod), SIP_MODULE_NAME) == 0);
+ return module_get_ctx(mod);
+}
+
+int sip_subscribe(struct sip_decoder *decoder,
+ sip_request_message_callback_func *request_cb,
+ sip_response_message_callback_func *response_cb,
+ void *arg)
+{
+ int ret;
+ ret = sip_message_subscribe(decoder->mod_mgr, SIP_REQUEST_TOPIC_NAME, (on_msg_cb_func *)(void *)request_cb, arg);
+ if (ret < 0) {
+ return ret;
+ }
+ ret = sip_message_subscribe(decoder->mod_mgr, SIP_RESPONSE_TOPIC_NAME, (on_msg_cb_func *)(void *)response_cb, arg);
+ if (ret < 0) {
+ return ret;
+ }
+ return 0;
+}
+
+void sip_exit(struct module_manager *mod_mgr, struct module *mod)
+{
+ (void)(mod_mgr);
+ int i;
+ struct sip_decoder *decoder;
+ struct mq_schema *schema;
+
+ if (mod) {
+ decoder = (struct sip_decoder *)module_get_ctx(mod);
+ if (decoder) {
+ schema = module_manager_get_mq_schema(decoder->mod_mgr);
+ mq_schema_destroy_topic(schema, decoder->request_message_topic_id);
+ mq_schema_destroy_topic(schema, decoder->response_message_topic_id);
+
+ for (i = 0; i < decoder->thread_num; i++) {
+ field_pool_free(decoder->field_pools[i]);
+ }
+ free(decoder->field_pools);
+ free(decoder);
+ }
+
+ module_free(mod);
+ }
+}
+
+struct module* sip_init(struct module_manager *mod_mgr)
+{
+ int i, ret, thread_num;
+ struct mq_schema *schema;
+ struct session_manager *sess_mgr;
+ struct module *mod;
+ struct sip_decoder *decoder;
+
+ decoder = (struct sip_decoder *)calloc(1, sizeof(struct sip_decoder));
+ decoder->mod_mgr = mod_mgr;
+ mod = module_new(SIP_MODULE_NAME, decoder);
+ sess_mgr = module_to_session_manager(module_manager_get_module(mod_mgr, SESSION_MANAGER_MODULE_NAME));
+ schema = module_manager_get_mq_schema(mod_mgr);
+
+ if (sess_mgr == NULL || schema == NULL) {
+ goto exit;
+ }
+
+ decoder->exdata_id = session_manager_new_session_exdata_index(sess_mgr, SIP_EXDATA_NAME, sip_on_exdata_free, NULL);
+ if (decoder->exdata_id < 0) {
+ goto exit;
+ }
+
+ ret = session_manager_subscribe_udp(sess_mgr, sip_on_udp_packet, decoder);
+ if (ret < 0) {
+ goto exit;
+ }
+
+ ret = session_manager_subscribe_tcp_stream(sess_mgr, sip_on_tcp_payload, decoder);
+ if (ret < 0) {
+ goto exit;
+ }
+
+ decoder->request_message_topic_id = mq_schema_create_topic(schema, SIP_REQUEST_TOPIC_NAME, sip_message_dispatch, decoder, sip_on_message_free, NULL);
+ if (decoder->request_message_topic_id < 0) {
+ goto exit;
+ }
+
+ decoder->response_message_topic_id = mq_schema_create_topic(schema, SIP_RESPONSE_TOPIC_NAME, sip_message_dispatch, decoder, sip_on_message_free, NULL);
+ if (decoder->response_message_topic_id < 0) {
+ goto exit;
+ }
+
+ // init per thread sip header field pools
+ thread_num = module_manager_get_max_thread_num(mod_mgr);
+ decoder->thread_num = thread_num;
+ decoder->field_pools = (struct sip_header_field_pool **)calloc(thread_num, sizeof(struct sip_header_field_pool *));
+ for (i = 0; i < thread_num; i++) {
+ decoder->field_pools[i] = field_pool_new(SIP_HEADER_FIELD_POOL_DEFAULT_SIZE);
+ }
+
+ return mod;
+exit:
+ sip_exit(mod_mgr, mod);
+ return NULL;
+}
+
+
diff --git a/decoders/sip/sip_internal.h b/decoders/sip/sip_internal.h
new file mode 100644
index 0000000..4acd653
--- /dev/null
+++ b/decoders/sip/sip_internal.h
@@ -0,0 +1,193 @@
+#pragma once
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include "uthash/uthash.h"
+
+#ifndef UNUSED
+#define UNUSED(x) (void)(x)
+#endif
+#define SIP_DIM(a) (sizeof (a) / sizeof ((a)[0]))
+
+#define timeval_usec(t) ((t).tv_usec)
+#define timeval_sec(t) ((t).tv_sec)
+#define timeval_add(a, b, r) \
+ do { \
+ (r).tv_sec = (a).tv_sec + (b).tv_sec; \
+ (r).tv_usec = (a).tv_usec + (b).tv_usec; \
+ } while (0)
+#define timeval_cmp(a, b, CMP) \
+ ((timeval_sec(a) == timeval_sec(b)) ? (timeval_usec(a) CMP timeval_usec(b)) \
+ : (timeval_sec(a) CMP timeval_sec(b)))
+#define timeval_cmp_gte(a, b) timeval_cmp((a), (b), >=)
+#define timeval_cmp_gt(a, b) timeval_cmp((a), (b), >)
+#define timeval_cmp_lt(a, b) timeval_cmp((a), (b), <)
+#define timeval_cmp_lte(a, b) timeval_cmp((a), (b), <=)
+#define timeval_cmp_neq(a, b) timeval_cmp((a), (b), !=)
+
+#define timeval_delta_ms(start, end) (((end).tv_sec-(start).tv_sec)*1000 + ((end).tv_usec-(start).tv_usec)/1000)
+#define timeval_delta_us(start, end) (((end).tv_sec-(start).tv_sec)*1000*1000 + ((end).tv_usec-(start).tv_usec))
+#define timeval_to_ms(t) ((t).tv_sec*1000+(t).tv_usec/1000)
+
+#define SAFE_STRNCASECMP(s1, s1_len, s2, s2_len) ((s1_len) >= (s2_len) ? strncasecmp(s1, s2, s2_len) : -1)
+
+#define SIP_EXDATA_NAME "SIP_EXDATA"
+#define SIP_REQUEST_TOPIC_NAME "SIP_REQUEST"
+#define SIP_RESPONSE_TOPIC_NAME "SIP_RESPONSE"
+#define SIP_TRANSACTION_TOPIC_NAME "SIP_TRANSACTION"
+#define SIP_IDENTIFY_TIMES_MAX 128
+
+#define SIP_REQUEST_LINE_PART_METHOD 0
+#define SIP_REQUEST_LINE_PART_URI 1
+#define SIP_REQUEST_LINE_PART_VERSION 2
+#define SIP_STATUS_LINE_PART_VERSION 0
+#define SIP_STATUS_LINE_PART_CODE 1
+#define SIP_STATUS_LINE_PART_REASON 2
+#define SIP_START_LINE_PART_NUM 3
+#define SIP_HEADER_FIELD_PART_NAME 0
+#define SIP_HEADER_FIELD_PART_VALUE 1
+#define SIP_HEADER_FIELD_PART_NUM 2
+#define SDP_MEDIA_PART_MEDIA 0
+#define SDP_MEDIA_PART_PORT 1
+#define SDP_MEDIA_PART_PROTO 2
+#define SDP_MEDIA_PART_FMT 3
+#define SDP_MEDIA_PART_NUM 4
+#define SDP_CONNECTION_PART_NETTYPE 0
+#define SDP_CONNECTION_PART_ADDRTYPE 1
+#define SDP_CONNECTION_PART_ADDRESS 2
+#define SDP_CONNECTION_PART_NUM 3
+
+#define SIP_FROM_PART_ADDR 0
+#define SIP_FROM_PART_TAG 1
+#define SIP_FROM_PART_NUM 2
+#define SIP_TO_PART_ADDR 0
+#define SIP_TO_PART_TAG 1
+#define SIP_TO_PART_NUM 2
+#define SIP_CSEQ_PART_SEQ 0
+#define SIP_CSEQ_PART_METHOD 1
+#define SIP_CSEQ_PART_NUM 2
+
+#define SIP_HEADER_FIELD_POOL_DEFAULT_SIZE 256
+#define SIP_STREAM_BUFFER_DEFAULT_SIZE 4096
+#define SIP_LINE_END "\r\n"
+#define SIP_HEADER_END "\r\n\r\n"
+#define SIP_STATUS_CODE_LEN 3
+#define SIP_PER_PAYLOAD_MESSAGE_MAX 8
+
+enum sip_call_state {
+ SIP_CALL_STATE_OPENING,
+ SIP_CALL_STATE_WAITING, /* before seeing INVITE*/
+ SIP_CALL_STATE_CALLING, /* after seeing INVITE */
+ SIP_CALL_STATE_EARLY, /* provisional response (1xx status code) */
+ SIP_CALL_STATE_CONNECTING, /* after seeing 200/OK response */
+ SIP_CALL_STATE_CONFIRMED, /* after seeing ACK */
+ SIP_CALL_STATE_DISCONNECTING, /* after seeing BYE */
+ SIP_CALL_STATE_DISCONNECTED, /* call is disconnected */
+ SIP_CALL_STATE_CLOSING,
+};
+
+enum sip_identify_state {
+ SIP_IDENTIFY_STATE_UNKNOWN,
+ SIP_IDENTIFY_STATE_HALF_TRUE,
+ SIP_IDENTIFY_STATE_TRUE,
+ SIP_IDENTIFY_STATE_FALSE,
+};
+
+enum sip_message_type {
+ SIP_MESSAGE_TYPE_UNKNOWN,
+ SIP_MESSAGE_TYPE_REQUEST,
+ SIP_MESSAGE_TYPE_RESPONSE,
+ SIP_MESSAGE_TYPE_MAX,
+};
+
+enum sip_dir {
+ SIP_DIR_UNKNOWN,
+ SIP_DIR_REQUEST,
+ SIP_DIR_RESPONSE,
+ SIP_DIR_DOUBLE,
+};
+
+enum sip_version {
+ SIP_VERSION_UNKNOWN,
+ SIP_VERSION_1_0,
+ SIP_VERSION_1_1,
+ SIP_VERSION_2_0,
+
+ SIP_VERSION_NUM,
+};
+
+struct stream_buffer {
+ char *buf;
+ size_t buf_size;
+ size_t buf_off;
+};
+
+struct sip_header_field_pool {
+ struct sip_header_field *arr;
+ unsigned int used;
+ unsigned int size;
+} __attribute__((aligned(64)));
+
+struct sip_transaction {
+ char *key; // use sip Call-ID string for uthash key
+ size_t key_len;
+ int seq;
+ enum sip_dir dir;
+ enum sip_call_state call_state;
+ long long call_duration_ms;
+ struct timeval last_update;
+ UT_hash_handle hh;
+};
+
+struct sip_message {
+ enum sip_message_type type;
+ enum sip_call_state call_state;
+ int transaction_seq;
+
+ union {
+ struct sip_request_line request_line;
+ struct sip_status_line status_line;
+ };
+ struct sip_header header;
+ struct sip_body body;
+
+ struct sip_header_field_pool *header_field_pool;
+ struct session *sess_ref;
+};
+
+struct sip_exdata {
+ struct sip_decoder *decoder_ref;
+ struct session *sess_ref;
+
+ int sess_ignored;
+
+ enum sip_identify_state identify_state;
+ int identify_times;
+
+ struct sip_transaction *transaction_table; // sip transaction uthash head (key: call_id)
+ int transaction_seq;
+ enum sip_dir dir; // request only/response only/ double
+ enum sip_call_state call_state; // sip call state
+
+ struct stream_buffer streambuffer; // temporary buffer for incomplete sip message
+};
+
+struct sip_decoder {
+ int request_message_topic_id;
+ int response_message_topic_id;
+ int exdata_id;
+
+ int transaction_limit_count;
+ int transaction_timeout_ms;
+
+ int thread_num;
+ struct sip_header_field_pool **field_pools;// per thread field pool
+ struct module_manager *mod_mgr;
+};
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/decoders/sip/version.map b/decoders/sip/version.map
new file mode 100644
index 0000000..afa02ed
--- /dev/null
+++ b/decoders/sip/version.map
@@ -0,0 +1,13 @@
+VERS_2.4{
+global:
+extern "C" {
+ sip_init;
+ sip_exit;
+ sip_subscribe;
+ sip_get_call_duration_ms;
+ module_to_sip_decoder;
+ GIT_VERSION_*;
+};
+
+local: *;
+};
diff --git a/include/stellar/sip.h b/include/stellar/sip.h
new file mode 100644
index 0000000..26ef908
--- /dev/null
+++ b/include/stellar/sip.h
@@ -0,0 +1,114 @@
+#pragma once
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include "stellar/session.h"
+#include "stellar/module.h"
+
+#define SIP_MODULE_NAME "SIP_MODULE"
+
+enum sip_method {
+ SIP_METHOD_UNKNOWN,
+ SIP_METHOD_INVITE,
+ SIP_METHOD_ACK,
+ SIP_METHOD_OPTIONS,
+ SIP_METHOD_REGISTER,
+ SIP_METHOD_BYE,
+ SIP_METHOD_CANCEL,
+ SIP_METHOD_DO,
+ SIP_METHOD_INFO,
+ SIP_METHOD_MESSAGE,
+ SIP_METHOD_NOTIFY,
+ SIP_METHOD_PRACK,
+ SIP_METHOD_QAUTH,
+ SIP_METHOD_REFER,
+ SIP_METHOD_SPRACK,
+ SIP_METHOD_SUBSCRIBE,
+ SIP_METHOD_UPDATE,
+ SIP_METHOD_PUBLISH,
+
+ SIP_METHOD_NUM,
+};
+
+struct sip_request_line {
+ const char *uri;
+ size_t uri_len;
+ const char *version;
+ size_t version_len;
+
+ enum sip_method method;
+};
+
+struct sip_status_line {
+ const char *version;
+ size_t version_len;
+ const char *code;
+ size_t code_len;
+ const char *reason;
+ size_t reason_len;
+};
+
+struct sip_header_field {
+ const char *field_name;
+ size_t field_name_len;
+ const char *field_value;
+ size_t field_value_len;
+};
+
+struct sip_body {
+ const char *sdp_content;
+ size_t sdp_content_len;
+ const char *media_ip;
+ size_t media_ip_len;
+ unsigned short media_audio_port;
+ unsigned short media_video_port;
+
+ const char *body;
+ size_t body_len;
+};
+
+struct sip_header {
+ struct sip_header_field *call_id;
+ struct sip_header_field *from;
+ struct sip_header_field *to;
+ struct sip_header_field *cseq;
+ struct sip_header_field *via;
+ struct sip_header_field *server;
+ struct sip_header_field *reason;
+ struct sip_header_field *user_agent;
+ struct sip_header_field *content_type;
+ struct sip_header_field *content_length;
+
+ enum sip_method cseq_method;
+
+ struct sip_header_field *header_fields;
+ size_t n_header_fields;
+};
+
+struct sip_decoder;
+
+typedef void sip_request_message_callback_func(struct session *sess,
+ struct sip_request_line *request_line,
+ struct sip_header *header,
+ struct sip_body *body,
+ void *arg);
+typedef void sip_response_message_callback_func(struct session *sess,
+ struct sip_status_line *status_line,
+ struct sip_header *header,
+ struct sip_body *body,
+ void *arg);
+
+struct sip_decoder *module_to_sip_decoder(struct module *mod);
+int sip_subscribe(struct sip_decoder *decoder,
+ sip_request_message_callback_func *request_cb,
+ sip_response_message_callback_func *response_cb,
+ void *arg);
+
+// return -1 or duration_ms
+long long sip_get_call_duration_ms(struct sip_decoder *decoder, struct session *sess, const char *call_id, size_t call_id_len);
+#ifdef __cplusplus
+}
+#endif
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index a402bf4..06fc5fa 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -1,8 +1,9 @@
#add_subdirectory(packet_inject)
add_subdirectory(packet_tool)
add_subdirectory(session_debugger)
-add_subdirectory(lpi_plus)
+#add_subdirectory(lpi_plus)
#add_subdirectory(decoders/http)
#add_subdirectory(decoders/socks)
#add_subdirectory(decoders/stratum)
-#add_subdirectory(decoders/session_flags) \ No newline at end of file
+#add_subdirectory(decoders/session_flags)
+add_subdirectory(decoders/sip)
diff --git a/test/decoders/sip/CMakeLists.txt b/test/decoders/sip/CMakeLists.txt
new file mode 100644
index 0000000..54236bd
--- /dev/null
+++ b/test/decoders/sip/CMakeLists.txt
@@ -0,0 +1,151 @@
+set(TEST_NAME sip_test)
+set(TEST_MAIN ${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME})
+file(GLOB TEST_SRC "${TEST_NAME}*.cpp")
+
+add_executable(
+ ${TEST_NAME}
+ ${TEST_SRC}
+)
+
+target_include_directories(
+ ${TEST_NAME} PRIVATE
+ ${CMAKE_SOURCE_DIR}/deps/
+ ${CMAKE_SOURCE_DIR}/decoders/
+)
+
+target_link_libraries(
+ ${TEST_NAME}
+ sip
+ stellar_lib
+ cjson-static
+ dl "-rdynamic"
+ gtest
+ gmock
+)
+
+add_test(
+ NAME ${TEST_NAME}.SETUP
+ COMMAND sh -c "
+ mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/result/ &&
+
+ mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/log/ &&
+ mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/conf/ &&
+ cat ${CMAKE_SOURCE_DIR}/conf/stellar.toml > ${CMAKE_CURRENT_BINARY_DIR}/conf/stellar.toml &&
+ cat ${CMAKE_CURRENT_SOURCE_DIR}/conf/spec.toml >> ${CMAKE_CURRENT_BINARY_DIR}/conf/stellar.toml &&
+ sed -i 's/mode = \"pcapfile\"/mode = \"pcaplist\"/g' ${CMAKE_CURRENT_BINARY_DIR}/conf/stellar.toml &&
+ sed -i 's/pcap_path = \"\\\/tmp\\\/test.pcap\"/pcap_path = \"pcaplist.txt\"/g' ${CMAKE_CURRENT_BINARY_DIR}/conf/stellar.toml &&
+ find ${CMAKE_CURRENT_SOURCE_DIR}/result/ -type f | xargs -i cp {} ${CMAKE_CURRENT_BINARY_DIR}/result/ &&
+ find ${CMAKE_CURRENT_SOURCE_DIR}/pcap/ -type f > ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.all
+ "
+)
+add_test(
+ NAME ${TEST_NAME}.01-complete-dialog
+ COMMAND sh -c "
+ cat ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.all | grep '01-complete-dialog.pcap' > ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.txt &&
+ ${TEST_MAIN} -m -c ./conf/stellar.toml -f ${CMAKE_CURRENT_BINARY_DIR}/result/01-complete-dialog.json
+ "
+)
+add_test(
+ NAME ${TEST_NAME}.02-complete-call
+ COMMAND sh -c "
+ cat ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.all | grep '02-complete-call.pcap' > ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.txt &&
+ ${TEST_MAIN} -m -c ./conf/stellar.toml -f ${CMAKE_CURRENT_BINARY_DIR}/result/02-complete-call.json
+ "
+)
+add_test(
+ NAME ${TEST_NAME}.03-complete-call-with-empty-line
+ COMMAND sh -c "
+ cat ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.all | grep '03-complete-call-with-empty-line.pcap' > ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.txt &
+ ${TEST_MAIN} -m -c ./conf/stellar.toml -f ${CMAKE_CURRENT_BINARY_DIR}/result/03-complete-call-with-empty-line.json
+ "
+)
+add_test(
+ NAME ${TEST_NAME}.04-complete-call-with-proxy
+ COMMAND sh -c "
+ cat ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.all | grep '04-complete-call-with-proxy.pcap' > ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.txt &&
+ ${TEST_MAIN} -m -c ./conf/stellar.toml -f ${CMAKE_CURRENT_BINARY_DIR}/result/04-complete-call-with-proxy.json
+ "
+)
+add_test(
+ NAME ${TEST_NAME}.05-complete-call-with-two-dir-invite
+ COMMAND sh -c "
+ cat ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.all | grep '05-complete-call-with-two-dir-invite.pcap' > ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.txt &&
+ ${TEST_MAIN} -m -c ./conf/stellar.toml -f ${CMAKE_CURRENT_BINARY_DIR}/result/05-complete-call-with-two-dir-invite.json
+ "
+)
+add_test(
+ NAME ${TEST_NAME}.06-complete-call-on-tcp
+ COMMAND sh -c "
+ cat ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.all | grep '06-complete-call-on-tcp.pcap' > ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.txt &&
+ ${TEST_MAIN} -m -c ./conf/stellar.toml -f ${CMAKE_CURRENT_BINARY_DIR}/result/06-complete-call-on-tcp.json
+ "
+)
+add_test(
+ NAME ${TEST_NAME}.07-c2s-complete-dialog
+ COMMAND sh -c "
+ cat ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.all | grep '07-c2s-complete-dialog.pcap' > ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.txt &&
+ ${TEST_MAIN} -m -c ./conf/stellar.toml -f ${CMAKE_CURRENT_BINARY_DIR}/result/07-c2s-complete-dialog.json
+ "
+)
+add_test(
+ NAME ${TEST_NAME}.08-c2s-complete-call
+ COMMAND sh -c "
+ cat ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.all | grep '08-c2s-complete-call.pcap' > ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.txt &&
+ ${TEST_MAIN} -m -c ./conf/stellar.toml -f ${CMAKE_CURRENT_BINARY_DIR}/result/08-c2s-complete-call.json
+ "
+)
+add_test(
+ NAME ${TEST_NAME}.09-c2s-complete-call-with-proxy
+ COMMAND sh -c "
+ cat ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.all | grep '09-c2s-complete-call-with-proxy.pcap' > ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.txt &&
+ ${TEST_MAIN} -m -c ./conf/stellar.toml -f ${CMAKE_CURRENT_BINARY_DIR}/result/09-c2s-complete-call-with-proxy.json
+ "
+)
+add_test(
+ NAME ${TEST_NAME}.10-s2c-complete-dialog
+ COMMAND sh -c "
+ cat ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.all | grep '10-s2c-complete-dialog.pcap' > ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.txt &&
+ ${TEST_MAIN} -m -c ./conf/stellar.toml -f ${CMAKE_CURRENT_BINARY_DIR}/result/10-s2c-complete-dialog.json
+ "
+)
+add_test(
+ NAME ${TEST_NAME}.11-s2c-complete-call
+ COMMAND sh -c "
+ cat ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.all | grep '11-s2c-complete-call.pcap' > ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.txt &&
+ ${TEST_MAIN} -m -c ./conf/stellar.toml -f ${CMAKE_CURRENT_BINARY_DIR}/result/11-s2c-complete-call.json
+ "
+)
+add_test(
+ NAME ${TEST_NAME}.12-s2c-complete-call-with-proxy
+ COMMAND sh -c "
+ cat ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.all | grep '12-s2c-complete-call-with-proxy.pcap' > ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.txt &&
+ ${TEST_MAIN} -m -c ./conf/stellar.toml -f ${CMAKE_CURRENT_BINARY_DIR}/result/12-s2c-complete-call-with-proxy.json
+ "
+)
+add_test(
+ NAME ${TEST_NAME}.13-complete-call-with-limit-1
+ COMMAND sh -c "
+ cat ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.all | grep '02-complete-call.pcap' > ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.txt &&
+ ${TEST_MAIN} -m -c ./conf/stellar.toml -f ${CMAKE_CURRENT_BINARY_DIR}/result/13-complete-call-with-limit-1.json
+ "
+)
+add_test(
+ NAME ${TEST_NAME}.14-complete-call-with-timeout-10s
+ COMMAND sh -c "
+ cat ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.all | grep '02-complete-call.pcap' > ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.txt &&
+ ${TEST_MAIN} -m -c ./conf/stellar.toml -f ${CMAKE_CURRENT_BINARY_DIR}/result/14-complete-call-with-timeout-10s.json
+ "
+)
+add_test(
+ NAME ${TEST_NAME}.15-complete-call-with-timeout-30s
+ COMMAND sh -c "
+ cat ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.all | grep '02-complete-call.pcap' > ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.txt &&
+ ${TEST_MAIN} -m -c ./conf/stellar.toml -f ${CMAKE_CURRENT_BINARY_DIR}/result/15-complete-call-with-timeout-30s.json
+ "
+)
+
+set_tests_properties(
+ ${TEST_NAME}.01-complete-dialog
+ PROPERTIES
+ FIXTURES_REQUIRED ${TEST_NAME}.SETUP
+)
diff --git a/test/decoders/sip/conf/spec.toml b/test/decoders/sip/conf/spec.toml
new file mode 100644
index 0000000..9bf9049
--- /dev/null
+++ b/test/decoders/sip/conf/spec.toml
@@ -0,0 +1,11 @@
+# stellar_plugin.toml
+#
+[[module]]
+path = ""
+init = "sip_init"
+exit = "sip_exit"
+
+[[module]]
+path = ""
+init = "sip_test_init"
+exit = "sip_test_exit"
diff --git a/test/decoders/sip/pcap/01-complete-dialog.pcap b/test/decoders/sip/pcap/01-complete-dialog.pcap
new file mode 100644
index 0000000..fef0252
--- /dev/null
+++ b/test/decoders/sip/pcap/01-complete-dialog.pcap
Binary files differ
diff --git a/test/decoders/sip/pcap/02-complete-call.pcap b/test/decoders/sip/pcap/02-complete-call.pcap
new file mode 100644
index 0000000..27dcd5a
--- /dev/null
+++ b/test/decoders/sip/pcap/02-complete-call.pcap
Binary files differ
diff --git a/test/decoders/sip/pcap/03-complete-call-with-empty-line.pcap b/test/decoders/sip/pcap/03-complete-call-with-empty-line.pcap
new file mode 100644
index 0000000..bd1d096
--- /dev/null
+++ b/test/decoders/sip/pcap/03-complete-call-with-empty-line.pcap
Binary files differ
diff --git a/test/decoders/sip/pcap/04-complete-call-with-proxy.pcap b/test/decoders/sip/pcap/04-complete-call-with-proxy.pcap
new file mode 100644
index 0000000..3875e73
--- /dev/null
+++ b/test/decoders/sip/pcap/04-complete-call-with-proxy.pcap
Binary files differ
diff --git a/test/decoders/sip/pcap/05-complete-call-with-two-dir-invite.pcap b/test/decoders/sip/pcap/05-complete-call-with-two-dir-invite.pcap
new file mode 100644
index 0000000..038c81d
--- /dev/null
+++ b/test/decoders/sip/pcap/05-complete-call-with-two-dir-invite.pcap
Binary files differ
diff --git a/test/decoders/sip/pcap/06-complete-call-on-tcp.pcap b/test/decoders/sip/pcap/06-complete-call-on-tcp.pcap
new file mode 100644
index 0000000..f51f64e
--- /dev/null
+++ b/test/decoders/sip/pcap/06-complete-call-on-tcp.pcap
Binary files differ
diff --git a/test/decoders/sip/pcap/07-c2s-complete-dialog.pcap b/test/decoders/sip/pcap/07-c2s-complete-dialog.pcap
new file mode 100644
index 0000000..ac83bfb
--- /dev/null
+++ b/test/decoders/sip/pcap/07-c2s-complete-dialog.pcap
Binary files differ
diff --git a/test/decoders/sip/pcap/08-c2s-complete-call.pcap b/test/decoders/sip/pcap/08-c2s-complete-call.pcap
new file mode 100644
index 0000000..081ca40
--- /dev/null
+++ b/test/decoders/sip/pcap/08-c2s-complete-call.pcap
Binary files differ
diff --git a/test/decoders/sip/pcap/09-c2s-complete-call-with-proxy.pcap b/test/decoders/sip/pcap/09-c2s-complete-call-with-proxy.pcap
new file mode 100644
index 0000000..66bce10
--- /dev/null
+++ b/test/decoders/sip/pcap/09-c2s-complete-call-with-proxy.pcap
Binary files differ
diff --git a/test/decoders/sip/pcap/10-s2c-complete-dialog.pcap b/test/decoders/sip/pcap/10-s2c-complete-dialog.pcap
new file mode 100644
index 0000000..6d2c992
--- /dev/null
+++ b/test/decoders/sip/pcap/10-s2c-complete-dialog.pcap
Binary files differ
diff --git a/test/decoders/sip/pcap/11-s2c-complete-call.pcap b/test/decoders/sip/pcap/11-s2c-complete-call.pcap
new file mode 100644
index 0000000..28319a6
--- /dev/null
+++ b/test/decoders/sip/pcap/11-s2c-complete-call.pcap
Binary files differ
diff --git a/test/decoders/sip/pcap/12-s2c-complete-call-with-proxy.pcap b/test/decoders/sip/pcap/12-s2c-complete-call-with-proxy.pcap
new file mode 100644
index 0000000..beecb8a
--- /dev/null
+++ b/test/decoders/sip/pcap/12-s2c-complete-call-with-proxy.pcap
Binary files differ
diff --git a/test/decoders/sip/result/01-complete-dialog.json b/test/decoders/sip/result/01-complete-dialog.json
new file mode 100644
index 0000000..a804264
--- /dev/null
+++ b/test/decoders/sip/result/01-complete-dialog.json
@@ -0,0 +1,82 @@
+[
+ {
+ "bye": "originator",
+ "callid": "0seuMoGmE.gmmU16GkVbxuFRPNvkZK1m",
+ "cseq": "58 INVITE",
+ "method_cseq_array": [
+ "invite",
+ "invite",
+ "invite",
+ "invite",
+ "ack",
+ "bye",
+ "bye"
+ ],
+ "method_rescode_array": [
+ "invite",
+ "100",
+ "180",
+ "200",
+ "ack",
+ "bye",
+ "200"
+ ],
+ "originator_description": "\"13520407511\" <sip:[email protected]>;tag=aNoEhdOkv50IiTC2XG-ckdvPFl8Eglzv",
+ "originator_sdp": "v=0\r\no=- 3703802843 3703802843 IN IP4 202.43.148.166\r\ns=pjmedia\r\nb=AS:84\r\nt=0 0\r\na=X-nat:1\r\nm=audio 4000 RTP/AVP 0 8 104 96\r\nc=IN IP4 202.43.148.166\r\nb=TIAS:64000\r\na=rtcp:4001 IN IP4 202.43.148.166\r\na=sendrecv\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:104 iLBC/8000\r\na=fmtp:104 mode=30\r\na=rtpmap:96 telephone-event/8000\r\na=fmtp:96 0-16\r\n",
+ "originator_sdp_media_audio_port": 4000,
+ "originator_sdp_media_ip": "202.43.148.166",
+ "responder_description": "sip:[email protected]",
+ "responder_sdp": "v=0\r\no=- 3419 3419 IN IP4 139.129.211.227\r\ns=VOS3000\r\nc=IN IP4 139.129.211.227\r\nt=0 0\r\nm=audio 30790 RTP/AVP 0 101\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:101 telephone-event/8000\r\na=fmtp:101 0-15\r\na=sendrecv\r\n",
+ "responder_sdp_media_audio_port": 30790,
+ "responder_sdp_media_ip": "139.129.211.227",
+ "server": "VOS3000 V2.1.6.00",
+ "test_result": 1,
+ "tuple6": "202.43.148.166:5121-139.129.211.227:5060-17-0",
+ "user_agent": "MythCall iOS v2.5.5/armv7-apple-darwin_ios",
+ "via": "SIP/2.0/UDP 202.43.148.166:5121;rport;branch=z9hG4bKPjh06y0MA4Ef17KHi6coyNt.oYo6-A4MyU"
+ },
+ {
+ "callid": "K2OSZB7cd5OTowdHoHTzzWyYX3fVqht8",
+ "cseq": "25461 SUBSCRIBE",
+ "method_cseq_array": [
+ "subscribe",
+ "subscribe"
+ ],
+ "method_rescode_array": [
+ "subscribe",
+ "405"
+ ],
+ "originator_description": "\"13520407511\" <sip:[email protected]>;tag=7-fTlqp68THG740NBxM8OLkda47k16qu",
+ "responder_description": "\"13520407511\" <sip:[email protected]>",
+ "test_result": 2,
+ "tuple6": "202.43.148.166:5121-139.129.211.227:5060-17-0",
+ "user_agent": "MythCall iOS v2.5.5/armv7-apple-darwin_ios",
+ "via": "SIP/2.0/UDP 202.43.148.166:5121;rport;branch=z9hG4bKPjXIeWmm7lDGeT3UHsc.p3fCuxl4G27jl8"
+ },
+ {
+ "callid": "ylZwkyZFxejabfyj4X7G1b-KCCgKtdY.",
+ "cseq": "35307 REGISTER",
+ "method_cseq_array": [
+ "register",
+ "register",
+ "register",
+ "register",
+ "register",
+ "register"
+ ],
+ "method_rescode_array": [
+ "register",
+ "401",
+ "register",
+ "200",
+ "register",
+ "200"
+ ],
+ "originator_description": "\"13520407511\" <sip:[email protected]>;tag=.rmiME17Bwt1ATrRYlIcijQD4LN6g5xl",
+ "responder_description": "\"13520407511\" <sip:[email protected]>",
+ "test_result": 3,
+ "tuple6": "202.43.148.166:5121-139.129.211.227:5060-17-0",
+ "user_agent": "MythCall iOS v2.5.5/armv7-apple-darwin_ios",
+ "via": "SIP/2.0/UDP 202.43.148.166:5121;rport;branch=z9hG4bKPjgHErPqZEEe93p.FELmghVDsVzJwTLy3p"
+ }
+]
diff --git a/test/decoders/sip/result/02-complete-call.json b/test/decoders/sip/result/02-complete-call.json
new file mode 100644
index 0000000..a377d56
--- /dev/null
+++ b/test/decoders/sip/result/02-complete-call.json
@@ -0,0 +1,40 @@
+[
+ {
+ "bye": "responder",
+ "callid": "OGIzMzVkMDY0YTVmNzJmOWRmMGFjZWU4YjFlN2VlZGI.",
+ "cseq": "1 INVITE",
+ "method_cseq_array": [
+ "invite",
+ "invite",
+ "invite",
+ "ack",
+ "invite",
+ "ack",
+ "bye",
+ "bye"
+ ],
+ "method_rescode_array": [
+ "invite",
+ "100",
+ "200",
+ "ack",
+ "200",
+ "ack",
+ "bye",
+ "200"
+ ],
+ "originator_description": "\"test1\"<sip:[email protected]>;tag=fd34fa7d",
+ "originator_sdp": "v=0\r\no=- 6 2 IN IP4 192.168.36.97\r\ns=CounterPath eyeBeam 1.5\r\nc=IN IP4 192.168.36.97\r\nt=0 0\r\nm=audio 47782 RTP/AVP 0 8 18 101\r\na=alt:1 3 : 8rYKJy7Q suxCbgXp 192.168.48.1 47782\r\na=alt:2 2 : emfnqGoq LfzkM/Ar 192.168.237.1 47782\r\na=alt:3 1 : mE7OGFW5 0sJEvxvE 192.168.36.97 47782\r\na=fmtp:18 annexb=no\r\na=fmtp:101 0-15\r\na=rtpmap:18 G729/8000\r\na=rtpmap:101 telephone-event/8000\r\na=sendrecv\r\na=x-rtp-session-id:BF8A71898AD64337A82CB51A63150F00\r\n",
+ "originator_sdp_media_audio_port": 47782,
+ "originator_sdp_media_ip": "192.168.36.97",
+ "responder_description": "\"[email protected]\"<sip:[email protected]>",
+ "responder_sdp": "v=0\r\no=CARRIER 1614051872 1614051872 IN IP4 80.239.235.113\r\ns=SIP Call\r\nc=IN IP4 80.239.235.113\r\nt=0 0\r\nm=audio 11446 RTP/AVP 0 101\r\na=rtpmap:0 pcmu/8000\r\na=rtpmap:101 telephone-event/8000\r\na=ptime:20\r\na=sendrecv\r\n",
+ "responder_sdp_media_audio_port": 11446,
+ "responder_sdp_media_ip": "80.239.235.113",
+ "server": "(Very nice Sip Registrar/Proxy Server)",
+ "test_result": 1,
+ "tuple6": "192.168.36.97:57326-77.72.169.134:5060-17-0",
+ "user_agent": "eyeBeam release 1011d stamp 40820",
+ "via": "SIP/2.0/UDP 192.168.36.97:57326;branch=z9hG4bK-d87543-1407230f4943213a-1--d87543-;rport"
+ }
+]
diff --git a/test/decoders/sip/result/03-complete-call-with-empty-line.json b/test/decoders/sip/result/03-complete-call-with-empty-line.json
new file mode 100644
index 0000000..a377d56
--- /dev/null
+++ b/test/decoders/sip/result/03-complete-call-with-empty-line.json
@@ -0,0 +1,40 @@
+[
+ {
+ "bye": "responder",
+ "callid": "OGIzMzVkMDY0YTVmNzJmOWRmMGFjZWU4YjFlN2VlZGI.",
+ "cseq": "1 INVITE",
+ "method_cseq_array": [
+ "invite",
+ "invite",
+ "invite",
+ "ack",
+ "invite",
+ "ack",
+ "bye",
+ "bye"
+ ],
+ "method_rescode_array": [
+ "invite",
+ "100",
+ "200",
+ "ack",
+ "200",
+ "ack",
+ "bye",
+ "200"
+ ],
+ "originator_description": "\"test1\"<sip:[email protected]>;tag=fd34fa7d",
+ "originator_sdp": "v=0\r\no=- 6 2 IN IP4 192.168.36.97\r\ns=CounterPath eyeBeam 1.5\r\nc=IN IP4 192.168.36.97\r\nt=0 0\r\nm=audio 47782 RTP/AVP 0 8 18 101\r\na=alt:1 3 : 8rYKJy7Q suxCbgXp 192.168.48.1 47782\r\na=alt:2 2 : emfnqGoq LfzkM/Ar 192.168.237.1 47782\r\na=alt:3 1 : mE7OGFW5 0sJEvxvE 192.168.36.97 47782\r\na=fmtp:18 annexb=no\r\na=fmtp:101 0-15\r\na=rtpmap:18 G729/8000\r\na=rtpmap:101 telephone-event/8000\r\na=sendrecv\r\na=x-rtp-session-id:BF8A71898AD64337A82CB51A63150F00\r\n",
+ "originator_sdp_media_audio_port": 47782,
+ "originator_sdp_media_ip": "192.168.36.97",
+ "responder_description": "\"[email protected]\"<sip:[email protected]>",
+ "responder_sdp": "v=0\r\no=CARRIER 1614051872 1614051872 IN IP4 80.239.235.113\r\ns=SIP Call\r\nc=IN IP4 80.239.235.113\r\nt=0 0\r\nm=audio 11446 RTP/AVP 0 101\r\na=rtpmap:0 pcmu/8000\r\na=rtpmap:101 telephone-event/8000\r\na=ptime:20\r\na=sendrecv\r\n",
+ "responder_sdp_media_audio_port": 11446,
+ "responder_sdp_media_ip": "80.239.235.113",
+ "server": "(Very nice Sip Registrar/Proxy Server)",
+ "test_result": 1,
+ "tuple6": "192.168.36.97:57326-77.72.169.134:5060-17-0",
+ "user_agent": "eyeBeam release 1011d stamp 40820",
+ "via": "SIP/2.0/UDP 192.168.36.97:57326;branch=z9hG4bK-d87543-1407230f4943213a-1--d87543-;rport"
+ }
+]
diff --git a/test/decoders/sip/result/04-complete-call-with-proxy.json b/test/decoders/sip/result/04-complete-call-with-proxy.json
new file mode 100644
index 0000000..06aafdb
--- /dev/null
+++ b/test/decoders/sip/result/04-complete-call-with-proxy.json
@@ -0,0 +1,169 @@
+[
+ {
+ "bye": "originator",
+ "callid": "ZDRjYWNhMzA5NDdmYzYzMGRmYTYwNmZmZGRmNzc3NTE.",
+ "cseq": "1 INVITE",
+ "method_cseq_array": [
+ "invite",
+ "invite",
+ "ack",
+ "invite",
+ "invite",
+ "invite",
+ "invite",
+ "ack",
+ "bye",
+ "bye"
+ ],
+ "method_rescode_array": [
+ "invite",
+ "407",
+ "ack",
+ "invite",
+ "100",
+ "180",
+ "200",
+ "ack",
+ "bye",
+ "200"
+ ],
+ "originator_description": "\"1032\"<sip:[email protected]:5060>;tag=c42cde23",
+ "originator_sdp": "v=0\r\no=- 9 2 IN IP4 192.168.50.68\r\ns=CounterPath eyeBeam 1.5\r\nc=IN IP4 192.168.50.68\r\nt=0 0\r\nm=audio 49738 RTP/AVP 0 8 18 101\r\na=alt:1 3 : Ysbkrqav oetTILx7 169.254.7.194 49738\r\na=alt:2 2 : q9OOjmX8 Xlhn1oYR 169.254.48.65 49738\r\na=alt:3 1 : syl0IXfv afOOYd57 192.168.50.68 49738\r\na=fmtp:18 annexb=no\r\na=fmtp:101 0-15\r\na=rtpmap:18 G729/8000\r\na=rtpmap:101 telephone-event/8000\r\na=sendrecv\r\na=x-rtp-session-id:354215C61E6B42BD809C4D740324B1F3\r\nm=video 57826 RTP/AVP 115 34\r\na=alt:1 3 : Yf1yx/VS PdYNr16U 169.254.7.194 57826\r\na=alt:2 2 : qsSkMBe/ IdJSK1LS 169.254.48.65 57826\r\na=alt:3 1 : liL1EzaT DfjzhxEr 192.168.50.68 57826\r\na=fmtp:115 QCIF=2 I=1 J=1 K=1 MaxBR=1960\r\na=fmtp:34 QCIF=2 MaxBR=1960\r\na=rtpmap:115 H263-1998/90000\r\na=rtpmap:34 H263/90000\r\na=sendrecv\r\na=x-rtp-session-id:D5988637AE9C4053A8E6E887BC3D270D\r\n",
+ "originator_sdp_media_audio_port": 49738,
+ "originator_sdp_media_ip": "192.168.50.68",
+ "originator_sdp_media_video_port": 57826,
+ "responder_description": "\"1038\"<sip:[email protected]:5060>",
+ "responder_sdp": "v=0\r\no=- 7 2 IN IP4 192.168.36.64\r\ns=CounterPath eyeBeam 1.5\r\nc=IN IP4 192.168.36.64\r\nt=0 0\r\nm=audio 52188 RTP/AVP 0 8 18 101\r\na=alt:1 1 : 4JPpFDvE mn5GwW8z 192.168.36.64 52188\r\na=fmtp:18 annexb=no\r\na=fmtp:101 0-15\r\na=rtpmap:18 G729/8000\r\na=rtpmap:101 telephone-event/8000\r\na=sendrecv\r\na=x-rtp-session-id:70F70193B63E45C6A473B0D8B8B46665\r\nm=video 30090 RTP/AVP 115 34\r\na=alt:1 1 : x54MkCBj gaoyAbUY 192.168.36.64 30090\r\na=fmtp:115 QCIF=1 I=1 J=1 K=1 MaxBR=1960\r\na=fmtp:34 QCIF=1 MaxBR=1960\r\na=rtpmap:115 H263-1998/90000\r\na=rtpmap:34 H263/90000\r\na=sendrecv\r\na=x-rtp-session-id:F36E8B538BDB4748895486E45A9B9789\r\n",
+ "responder_sdp_media_audio_port": 52188,
+ "responder_sdp_media_ip": "192.168.36.64",
+ "responder_sdp_media_video_port": 30090,
+ "server": "OpenSIPS (2.4.9 (x86_64/linux))",
+ "test_result": 1,
+ "tuple6": "192.168.50.68:46442-192.168.40.158:5060-17-0",
+ "user_agent": "eyeBeam release 1011d stamp 40820",
+ "via": "SIP/2.0/UDP 192.168.50.68:46442;branch=z9hG4bK-d87543-8e418f41137fcc2c-1--d87543-;rport"
+ },
+ {
+ "bye": "originator",
+ "callid": "NzE3YzI0Y2U1NDFiZDRjYzk3NzgzOTEwODk4NWMyZTM.",
+ "cseq": "1 INVITE",
+ "method_cseq_array": [
+ "invite",
+ "invite",
+ "invite",
+ "ack",
+ "invite",
+ "invite",
+ "ack",
+ "invite",
+ "invite",
+ "invite",
+ "ack",
+ "invite",
+ "ack",
+ "bye",
+ "bye"
+ ],
+ "method_rescode_array": [
+ "invite",
+ "invite",
+ "407",
+ "ack",
+ "invite",
+ "407",
+ "ack",
+ "100",
+ "180",
+ "200",
+ "ack",
+ "200",
+ "ack",
+ "bye",
+ "200"
+ ],
+ "originator_description": "\"1032\"<sip:[email protected]:5060>;tag=ae361d01",
+ "originator_sdp": "v=0\r\no=- 2 2 IN IP4 192.168.50.68\r\ns=CounterPath eyeBeam 1.5\r\nc=IN IP4 192.168.50.68\r\nt=0 0\r\nm=audio 58256 RTP/AVP 0 8 18 101\r\na=alt:1 3 : Ukq6IWE+ sPXc/kpP 169.254.7.194 58256\r\na=alt:2 2 : 9Zzg2VLF Zk9CWytB 169.254.48.65 58256\r\na=alt:3 1 : 1PAMvtIf 4tJHWela 192.168.50.68 58256\r\na=fmtp:18 annexb=no\r\na=fmtp:101 0-15\r\na=rtpmap:18 G729/8000\r\na=rtpmap:101 telephone-event/8000\r\na=sendrecv\r\na=x-rtp-session-id:96915E7B514C46E2AE550ED690392D51\r\nm=video 9582 RTP/AVP 115 34\r\na=alt:1 3 : jZqjg8B7 NAw32Thx 169.254.7.194 9582\r\na=alt:2 2 : rDZLEnot JbntDk1h 169.254.48.65 9582\r\na=alt:3 1 : k/ZVxfWw /zS8chaA 192.168.50.68 9582\r\na=fmtp:115 QCIF=2 I=1 J=1 K=1 MaxBR=1960\r\na=fmtp:34 QCIF=2 MaxBR=1960\r\na=rtpmap:115 H263-1998/90000\r\na=rtpmap:34 H263/90000\r\na=sendrecv\r\na=x-rtp-session-id:E673AB2155EF4EB98F107C828ECB14A1\r\n",
+ "originator_sdp_media_audio_port": 58256,
+ "originator_sdp_media_ip": "192.168.50.68",
+ "originator_sdp_media_video_port": 9582,
+ "responder_description": "\"1038\"<sip:[email protected]:5060>",
+ "responder_sdp": "v=0\r\no=- 9 2 IN IP4 192.168.36.64\r\ns=CounterPath eyeBeam 1.5\r\nc=IN IP4 192.168.36.64\r\nt=0 0\r\nm=audio 5998 RTP/AVP 0 8 18 101\r\na=alt:1 1 : YO/bnCGP kAri1zsb 192.168.36.64 5998\r\na=fmtp:18 annexb=no\r\na=fmtp:101 0-15\r\na=rtpmap:18 G729/8000\r\na=rtpmap:101 telephone-event/8000\r\na=sendrecv\r\na=x-rtp-session-id:37C21754CB9D472EAA684A680B74553D\r\nm=video 53206 RTP/AVP 115 34\r\na=alt:1 1 : tPK4TpGl Mg6nG3cl 192.168.36.64 53206\r\na=fmtp:115 QCIF=1 I=1 J=1 K=1 MaxBR=1960\r\na=fmtp:34 QCIF=1 MaxBR=1960\r\na=rtpmap:115 H263-1998/90000\r\na=rtpmap:34 H263/90000\r\na=sendrecv\r\na=x-rtp-session-id:48CBA905D47E495096455E3778CE42AE\r\n",
+ "responder_sdp_media_audio_port": 5998,
+ "responder_sdp_media_ip": "192.168.36.64",
+ "responder_sdp_media_video_port": 53206,
+ "server": "OpenSIPS (2.4.9 (x86_64/linux))",
+ "test_result": 2,
+ "tuple6": "192.168.50.68:46442-192.168.40.158:5060-17-0",
+ "user_agent": "eyeBeam release 1011d stamp 40820",
+ "via": "SIP/2.0/UDP 192.168.50.68:46442;branch=z9hG4bK-d87543-a07f874b220a3b05-1--d87543-;rport"
+ },
+ {
+ "callid": "M2NhN2I0NzBkZjFhMTkwMDIxODk1YjllN2ZiZTk5ZDI.",
+ "cseq": "1 SUBSCRIBE",
+ "method_cseq_array": [
+ "subscribe",
+ "subscribe",
+ "subscribe",
+ "subscribe"
+ ],
+ "method_rescode_array": [
+ "subscribe",
+ "407",
+ "subscribe",
+ "503"
+ ],
+ "originator_description": "\"1032\"<sip:[email protected]:5060>;tag=8542a020",
+ "responder_description": "\"1032\"<sip:[email protected]:5060>",
+ "server": "OpenSIPS (2.4.9 (x86_64/linux))",
+ "test_result": 3,
+ "tuple6": "192.168.50.68:46442-192.168.40.158:5060-17-0",
+ "user_agent": "eyeBeam release 1011d stamp 40820",
+ "via": "SIP/2.0/UDP 192.168.50.68:46442;branch=z9hG4bK-d87543-823a494ec8173d67-1--d87543-;rport"
+ },
+ {
+ "callid": "ZDU0NGVmN2UyZWU2ZjFkN2E2NzZkMzczZDE4NTQ1OGQ.",
+ "cseq": "1 SUBSCRIBE",
+ "method_cseq_array": [
+ "subscribe",
+ "subscribe",
+ "subscribe",
+ "subscribe"
+ ],
+ "method_rescode_array": [
+ "subscribe",
+ "407",
+ "subscribe",
+ "503"
+ ],
+ "originator_description": "\"1032\"<sip:[email protected]:5060>;tag=d13ff607",
+ "responder_description": "\"1032\"<sip:[email protected]:5060>",
+ "server": "OpenSIPS (2.4.9 (x86_64/linux))",
+ "test_result": 4,
+ "tuple6": "192.168.50.68:46442-192.168.40.158:5060-17-0",
+ "user_agent": "eyeBeam release 1011d stamp 40820",
+ "via": "SIP/2.0/UDP 192.168.50.68:46442;branch=z9hG4bK-d87543-a93d19660e74c136-1--d87543-;rport"
+ },
+ {
+ "callid": "NTIwM2RmMjQyNmU1NTNiODZhYWU0MjRhN2JhMTc0NTU.",
+ "cseq": "1 SUBSCRIBE",
+ "method_cseq_array": [
+ "subscribe",
+ "subscribe",
+ "subscribe",
+ "subscribe"
+ ],
+ "method_rescode_array": [
+ "subscribe",
+ "407",
+ "subscribe",
+ "503"
+ ],
+ "originator_description": "\"1032\"<sip:[email protected]:5060>;tag=2721a84d",
+ "responder_description": "\"1032\"<sip:[email protected]:5060>",
+ "server": "OpenSIPS (2.4.9 (x86_64/linux))",
+ "test_result": 5,
+ "tuple6": "192.168.50.68:46442-192.168.40.158:5060-17-0",
+ "user_agent": "eyeBeam release 1011d stamp 40820",
+ "via": "SIP/2.0/UDP 192.168.50.68:46442;branch=z9hG4bK-d87543-e6750a38d852cc14-1--d87543-;rport"
+ }
+]
diff --git a/test/decoders/sip/result/05-complete-call-with-two-dir-invite.json b/test/decoders/sip/result/05-complete-call-with-two-dir-invite.json
new file mode 100644
index 0000000..aa6adb4
--- /dev/null
+++ b/test/decoders/sip/result/05-complete-call-with-two-dir-invite.json
@@ -0,0 +1,77 @@
+[
+ {
+ "bye": "responder",
+ "callid": "YTcwNjg2OTQ3MWFhZTI2MDg1MDRkMzQ5NmMyMjJmOWE.",
+ "cseq": "1 INVITE",
+ "method_cseq_array": [
+ "invite",
+ "invite",
+ "ack",
+ "invite",
+ "invite",
+ "invite",
+ "invite",
+ "ack",
+ "bye",
+ "bye"
+ ],
+ "method_rescode_array": [
+ "invite",
+ "407",
+ "ack",
+ "invite",
+ "100",
+ "180",
+ "200",
+ "ack",
+ "bye",
+ "200"
+ ],
+ "originator_description": "\"1030\"<sip:[email protected]:5060>;tag=98737c0c",
+ "originator_sdp": "v=0\r\no=- 4 2 IN IP4 192.168.36.97\r\ns=CounterPath eyeBeam 1.5\r\nc=IN IP4 192.168.36.97\r\nt=0 0\r\nm=audio 52056 RTP/AVP 0 8 18 101\r\na=alt:1 3 : opd9/sgt Vr8Ttk83 192.168.48.1 52056\r\na=alt:2 2 : iugt0GCV dN9UXXXx 192.168.237.1 52056\r\na=alt:3 1 : JautfEUw 92B0OZ+j 192.168.36.97 52056\r\na=fmtp:18 annexb=no\r\na=fmtp:101 0-15\r\na=rtpmap:18 G729/8000\r\na=rtpmap:101 telephone-event/8000\r\na=sendrecv\r\na=x-rtp-session-id:FDCC9EBB233B4AC4B793D4D25490D806\r\n",
+ "originator_sdp_media_audio_port": 52056,
+ "originator_sdp_media_ip": "192.168.36.97",
+ "responder_description": "\"1032\"<sip:[email protected]:5060>",
+ "responder_sdp": "v=0\r\no=- 4 2 IN IP4 192.168.50.68\r\ns=CounterPath eyeBeam 1.5\r\nc=IN IP4 192.168.50.68\r\nt=0 0\r\nm=audio 4944 RTP/AVP 0 8 18 101\r\na=alt:1 3 : lb0y6UiS dvaqGd2D 169.254.7.194 4944\r\na=alt:2 2 : IQWKr5N7 P24M3b1e 169.254.48.65 4944\r\na=alt:3 1 : qby+cteH PvMOdORy 192.168.50.68 4944\r\na=fmtp:18 annexb=no\r\na=fmtp:101 0-15\r\na=rtpmap:18 G729/8000\r\na=rtpmap:101 telephone-event/8000\r\na=sendrecv\r\na=x-rtp-session-id:38935D9859634191BBD2008170E8F73C\r\n",
+ "responder_sdp_media_audio_port": 4944,
+ "responder_sdp_media_ip": "192.168.50.68",
+ "server": "OpenSIPS (2.4.9 (x86_64/linux))",
+ "test_result": 1,
+ "tuple6": "192.168.36.97:47381-192.168.40.158:5060-17-0",
+ "user_agent": "eyeBeam release 1011d stamp 40820",
+ "via": "SIP/2.0/UDP 192.168.36.97:47381;branch=z9hG4bK-d87543-f56d9c03de4b2227-1--d87543-;rport"
+ },
+ {
+ "bye": "responder",
+ "callid": "NGNkNjE0MjQ1MjI4NjdjNmE3Y2QwZDY4NjI2MDEzZjQ.",
+ "cseq": "2 INVITE",
+ "method_cseq_array": [
+ "invite",
+ "invite",
+ "invite",
+ "ack",
+ "bye",
+ "bye"
+ ],
+ "method_rescode_array": [
+ "invite",
+ "180",
+ "200",
+ "ack",
+ "bye",
+ "200"
+ ],
+ "originator_description": "\"1032\"<sip:[email protected]:5060>;tag=324c8154",
+ "originator_sdp": "v=0\r\no=- 4 2 IN IP4 192.168.50.68\r\ns=CounterPath eyeBeam 1.5\r\nc=IN IP4 192.168.50.68\r\nt=0 0\r\nm=audio 16160 RTP/AVP 0 8 18 101\r\na=alt:1 3 : VCBMZzkP KIWmfyDw 169.254.7.194 16160\r\na=alt:2 2 : z92C5YbS M5WgnYQj 169.254.48.65 16160\r\na=alt:3 1 : gfcoHYdt OJI5tsZv 192.168.50.68 16160\r\na=fmtp:18 annexb=no\r\na=fmtp:101 0-15\r\na=rtpmap:18 G729/8000\r\na=rtpmap:101 telephone-event/8000\r\na=sendrecv\r\na=x-rtp-session-id:699FA8B13B374E1993D24CCD808EF1AA\r\n",
+ "originator_sdp_media_audio_port": 16160,
+ "originator_sdp_media_ip": "192.168.50.68",
+ "responder_description": "\"1030\"<sip:[email protected]:5060>",
+ "responder_sdp": "v=0\r\no=- 4 2 IN IP4 192.168.36.97\r\ns=CounterPath eyeBeam 1.5\r\nc=IN IP4 192.168.36.97\r\nt=0 0\r\nm=audio 11208 RTP/AVP 0 8 18 101\r\na=alt:1 3 : p4Bf9q5H usALR/9g 192.168.48.1 11208\r\na=alt:2 2 : YUymdXru IDtVTg0Q 192.168.237.1 11208\r\na=alt:3 1 : C4MpNfxr ommU+2Le 192.168.36.97 11208\r\na=fmtp:18 annexb=no\r\na=fmtp:101 0-15\r\na=rtpmap:18 G729/8000\r\na=rtpmap:101 telephone-event/8000\r\na=sendrecv\r\na=x-rtp-session-id:91B98646E59C4F679B4A971700660509\r\n",
+ "responder_sdp_media_audio_port": 11208,
+ "responder_sdp_media_ip": "192.168.36.97",
+ "test_result": 2,
+ "tuple6": "192.168.36.97:47381-192.168.40.158:5060-17-0",
+ "user_agent": "eyeBeam release 1011d stamp 40820",
+ "via": "SIP/2.0/UDP 192.168.50.68:7537;received=192.168.50.68;branch=z9hG4bK-d87543-0f67d1484d39436e-1--d87543-;rport=7537"
+ }
+]
diff --git a/test/decoders/sip/result/06-complete-call-on-tcp.json b/test/decoders/sip/result/06-complete-call-on-tcp.json
new file mode 100644
index 0000000..dcfcfb9
--- /dev/null
+++ b/test/decoders/sip/result/06-complete-call-on-tcp.json
@@ -0,0 +1,38 @@
+[
+ {
+ "bye": "responder",
+ "callid": "[email protected]",
+ "cseq": "1 INVITE",
+ "method_cseq_array": [
+ "invite",
+ "invite",
+ "invite",
+ "invite",
+ "ack",
+ "bye",
+ "bye"
+ ],
+ "method_rescode_array": [
+ "invite",
+ "100",
+ "180",
+ "200",
+ "ack",
+ "bye",
+ "200"
+ ],
+ "originator_description": "<sip:[email protected]>;tag=1c1589367133",
+ "originator_sdp": "v=0\r\no=IPP 1589356486 1589356361 IN IP4 10.33.6.100\r\ns=Phone-Call\r\nc=IN IP4 10.33.6.100\r\nt=0 0\r\nm=audio 6000 RTP/AVP 8 13 101\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:101 telephone-event/8000\r\na=fmtp:101 0-15,16\r\na=ptime:20\r\na=sendrecv\r\n",
+ "originator_sdp_media_audio_port": 6000,
+ "originator_sdp_media_ip": "10.33.6.100",
+ "responder_description": "<sip:[email protected];user=phone>",
+ "responder_sdp": "v=0\r\no=GW 343007640 343007510 IN IP4 10.33.6.101\r\ns=Phone-Call\r\nc=IN IP4 10.33.6.101\r\nt=0 0\r\nm=audio 6050 RTP/AVP 8 13 101\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:101 telephone-event/8000\r\na=fmtp:101 0-15,16\r\na=ptime:20\r\na=sendrecv\r\n",
+ "responder_sdp_media_audio_port": 6050,
+ "responder_sdp_media_ip": "10.33.6.101",
+ "server": "GW/v.6.20A.027.012",
+ "test_result": 1,
+ "tuple6": "10.33.6.100:64802-10.33.6.101:5060-6-0",
+ "user_agent": "IPP/v.6.20A.027.012",
+ "via": "SIP/2.0/TCP 10.33.6.100;branch=z9hG4bKac1589375893;alias"
+ }
+]
diff --git a/test/decoders/sip/result/07-c2s-complete-dialog.json b/test/decoders/sip/result/07-c2s-complete-dialog.json
new file mode 100644
index 0000000..62aa1e5
--- /dev/null
+++ b/test/decoders/sip/result/07-c2s-complete-dialog.json
@@ -0,0 +1,62 @@
+[
+ {
+ "bye": "originator",
+ "callid": "0seuMoGmE.gmmU16GkVbxuFRPNvkZK1m",
+ "cseq": "58 INVITE",
+ "method_cseq_array": [
+ "invite",
+ "ack",
+ "bye"
+ ],
+ "method_rescode_array": [
+ "invite",
+ "ack",
+ "bye"
+ ],
+ "originator_description": "\"13520407511\" <sip:[email protected]>;tag=aNoEhdOkv50IiTC2XG-ckdvPFl8Eglzv",
+ "originator_sdp": "v=0\r\no=- 3703802843 3703802843 IN IP4 202.43.148.166\r\ns=pjmedia\r\nb=AS:84\r\nt=0 0\r\na=X-nat:1\r\nm=audio 4000 RTP/AVP 0 8 104 96\r\nc=IN IP4 202.43.148.166\r\nb=TIAS:64000\r\na=rtcp:4001 IN IP4 202.43.148.166\r\na=sendrecv\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:104 iLBC/8000\r\na=fmtp:104 mode=30\r\na=rtpmap:96 telephone-event/8000\r\na=fmtp:96 0-16\r\n",
+ "originator_sdp_media_audio_port": 4000,
+ "originator_sdp_media_ip": "202.43.148.166",
+ "responder_description": "sip:[email protected]",
+ "test_result": 1,
+ "tuple6": "202.43.148.166:5121-139.129.211.227:5060-17-0",
+ "user_agent": "MythCall iOS v2.5.5/armv7-apple-darwin_ios",
+ "via": "SIP/2.0/UDP 202.43.148.166:5121;rport;branch=z9hG4bKPjh06y0MA4Ef17KHi6coyNt.oYo6-A4MyU"
+ },
+ {
+ "callid": "K2OSZB7cd5OTowdHoHTzzWyYX3fVqht8",
+ "cseq": "25461 SUBSCRIBE",
+ "method_cseq_array": [
+ "subscribe"
+ ],
+ "method_rescode_array": [
+ "subscribe"
+ ],
+ "originator_description": "\"13520407511\" <sip:[email protected]>;tag=7-fTlqp68THG740NBxM8OLkda47k16qu",
+ "responder_description": "\"13520407511\" <sip:[email protected]>",
+ "test_result": 2,
+ "tuple6": "202.43.148.166:5121-139.129.211.227:5060-17-0",
+ "user_agent": "MythCall iOS v2.5.5/armv7-apple-darwin_ios",
+ "via": "SIP/2.0/UDP 202.43.148.166:5121;rport;branch=z9hG4bKPjXIeWmm7lDGeT3UHsc.p3fCuxl4G27jl8"
+ },
+ {
+ "callid": "ylZwkyZFxejabfyj4X7G1b-KCCgKtdY.",
+ "cseq": "35307 REGISTER",
+ "method_cseq_array": [
+ "register",
+ "register",
+ "register"
+ ],
+ "method_rescode_array": [
+ "register",
+ "register",
+ "register"
+ ],
+ "originator_description": "\"13520407511\" <sip:[email protected]>;tag=.rmiME17Bwt1ATrRYlIcijQD4LN6g5xl",
+ "responder_description": "\"13520407511\" <sip:[email protected]>",
+ "test_result": 3,
+ "tuple6": "202.43.148.166:5121-139.129.211.227:5060-17-0",
+ "user_agent": "MythCall iOS v2.5.5/armv7-apple-darwin_ios",
+ "via": "SIP/2.0/UDP 202.43.148.166:5121;rport;branch=z9hG4bKPjgHErPqZEEe93p.FELmghVDsVzJwTLy3p"
+ }
+]
diff --git a/test/decoders/sip/result/08-c2s-complete-call.json b/test/decoders/sip/result/08-c2s-complete-call.json
new file mode 100644
index 0000000..f95dbc5
--- /dev/null
+++ b/test/decoders/sip/result/08-c2s-complete-call.json
@@ -0,0 +1,28 @@
+[
+ {
+ "bye": "responder",
+ "callid": "OGIzMzVkMDY0YTVmNzJmOWRmMGFjZWU4YjFlN2VlZGI.",
+ "cseq": "1 INVITE",
+ "method_cseq_array": [
+ "invite",
+ "ack",
+ "ack",
+ "bye"
+ ],
+ "method_rescode_array": [
+ "invite",
+ "ack",
+ "ack",
+ "200"
+ ],
+ "originator_description": "\"test1\"<sip:[email protected]>;tag=fd34fa7d",
+ "originator_sdp": "v=0\r\no=- 6 2 IN IP4 192.168.36.97\r\ns=CounterPath eyeBeam 1.5\r\nc=IN IP4 192.168.36.97\r\nt=0 0\r\nm=audio 47782 RTP/AVP 0 8 18 101\r\na=alt:1 3 : 8rYKJy7Q suxCbgXp 192.168.48.1 47782\r\na=alt:2 2 : emfnqGoq LfzkM/Ar 192.168.237.1 47782\r\na=alt:3 1 : mE7OGFW5 0sJEvxvE 192.168.36.97 47782\r\na=fmtp:18 annexb=no\r\na=fmtp:101 0-15\r\na=rtpmap:18 G729/8000\r\na=rtpmap:101 telephone-event/8000\r\na=sendrecv\r\na=x-rtp-session-id:BF8A71898AD64337A82CB51A63150F00\r\n",
+ "originator_sdp_media_audio_port": 47782,
+ "originator_sdp_media_ip": "192.168.36.97",
+ "responder_description": "\"[email protected]\"<sip:[email protected]>",
+ "test_result": 1,
+ "tuple6": "192.168.36.97:57326-77.72.169.134:5060-17-0",
+ "user_agent": "eyeBeam release 1011d stamp 40820",
+ "via": "SIP/2.0/UDP 192.168.36.97:57326;branch=z9hG4bK-d87543-1407230f4943213a-1--d87543-;rport"
+ }
+]
diff --git a/test/decoders/sip/result/09-c2s-complete-call-with-proxy.json b/test/decoders/sip/result/09-c2s-complete-call-with-proxy.json
new file mode 100644
index 0000000..a3bc498
--- /dev/null
+++ b/test/decoders/sip/result/09-c2s-complete-call-with-proxy.json
@@ -0,0 +1,120 @@
+[
+ {
+ "bye": "originator",
+ "callid": "ZDRjYWNhMzA5NDdmYzYzMGRmYTYwNmZmZGRmNzc3NTE.",
+ "cseq": "1 INVITE",
+ "method_cseq_array": [
+ "invite",
+ "ack",
+ "invite",
+ "ack",
+ "bye"
+ ],
+ "method_rescode_array": [
+ "invite",
+ "ack",
+ "invite",
+ "ack",
+ "bye"
+ ],
+ "originator_description": "\"1032\"<sip:[email protected]:5060>;tag=c42cde23",
+ "originator_sdp": "v=0\r\no=- 9 2 IN IP4 192.168.50.68\r\ns=CounterPath eyeBeam 1.5\r\nc=IN IP4 192.168.50.68\r\nt=0 0\r\nm=audio 49738 RTP/AVP 0 8 18 101\r\na=alt:1 3 : Ysbkrqav oetTILx7 169.254.7.194 49738\r\na=alt:2 2 : q9OOjmX8 Xlhn1oYR 169.254.48.65 49738\r\na=alt:3 1 : syl0IXfv afOOYd57 192.168.50.68 49738\r\na=fmtp:18 annexb=no\r\na=fmtp:101 0-15\r\na=rtpmap:18 G729/8000\r\na=rtpmap:101 telephone-event/8000\r\na=sendrecv\r\na=x-rtp-session-id:354215C61E6B42BD809C4D740324B1F3\r\nm=video 57826 RTP/AVP 115 34\r\na=alt:1 3 : Yf1yx/VS PdYNr16U 169.254.7.194 57826\r\na=alt:2 2 : qsSkMBe/ IdJSK1LS 169.254.48.65 57826\r\na=alt:3 1 : liL1EzaT DfjzhxEr 192.168.50.68 57826\r\na=fmtp:115 QCIF=2 I=1 J=1 K=1 MaxBR=1960\r\na=fmtp:34 QCIF=2 MaxBR=1960\r\na=rtpmap:115 H263-1998/90000\r\na=rtpmap:34 H263/90000\r\na=sendrecv\r\na=x-rtp-session-id:D5988637AE9C4053A8E6E887BC3D270D\r\n",
+ "originator_sdp_media_audio_port": 49738,
+ "originator_sdp_media_ip": "192.168.50.68",
+ "originator_sdp_media_video_port": 57826,
+ "responder_description": "\"1038\"<sip:[email protected]:5060>",
+ "test_result": 1,
+ "tuple6": "192.168.50.68:46442-192.168.40.158:5060-17-0",
+ "user_agent": "eyeBeam release 1011d stamp 40820",
+ "via": "SIP/2.0/UDP 192.168.50.68:46442;branch=z9hG4bK-d87543-8e418f41137fcc2c-1--d87543-;rport"
+ },
+ {
+ "bye": "originator",
+ "callid": "NzE3YzI0Y2U1NDFiZDRjYzk3NzgzOTEwODk4NWMyZTM.",
+ "cseq": "1 INVITE",
+ "method_cseq_array": [
+ "invite",
+ "invite",
+ "ack",
+ "invite",
+ "ack",
+ "ack",
+ "ack",
+ "bye"
+ ],
+ "method_rescode_array": [
+ "invite",
+ "invite",
+ "ack",
+ "invite",
+ "ack",
+ "ack",
+ "ack",
+ "bye"
+ ],
+ "originator_description": "\"1032\"<sip:[email protected]:5060>;tag=ae361d01",
+ "originator_sdp": "v=0\r\no=- 2 2 IN IP4 192.168.50.68\r\ns=CounterPath eyeBeam 1.5\r\nc=IN IP4 192.168.50.68\r\nt=0 0\r\nm=audio 58256 RTP/AVP 0 8 18 101\r\na=alt:1 3 : Ukq6IWE+ sPXc/kpP 169.254.7.194 58256\r\na=alt:2 2 : 9Zzg2VLF Zk9CWytB 169.254.48.65 58256\r\na=alt:3 1 : 1PAMvtIf 4tJHWela 192.168.50.68 58256\r\na=fmtp:18 annexb=no\r\na=fmtp:101 0-15\r\na=rtpmap:18 G729/8000\r\na=rtpmap:101 telephone-event/8000\r\na=sendrecv\r\na=x-rtp-session-id:96915E7B514C46E2AE550ED690392D51\r\nm=video 9582 RTP/AVP 115 34\r\na=alt:1 3 : jZqjg8B7 NAw32Thx 169.254.7.194 9582\r\na=alt:2 2 : rDZLEnot JbntDk1h 169.254.48.65 9582\r\na=alt:3 1 : k/ZVxfWw /zS8chaA 192.168.50.68 9582\r\na=fmtp:115 QCIF=2 I=1 J=1 K=1 MaxBR=1960\r\na=fmtp:34 QCIF=2 MaxBR=1960\r\na=rtpmap:115 H263-1998/90000\r\na=rtpmap:34 H263/90000\r\na=sendrecv\r\na=x-rtp-session-id:E673AB2155EF4EB98F107C828ECB14A1\r\n",
+ "originator_sdp_media_audio_port": 58256,
+ "originator_sdp_media_ip": "192.168.50.68",
+ "originator_sdp_media_video_port": 9582,
+ "responder_description": "\"1038\"<sip:[email protected]:5060>",
+ "test_result": 2,
+ "tuple6": "192.168.50.68:46442-192.168.40.158:5060-17-0",
+ "user_agent": "eyeBeam release 1011d stamp 40820",
+ "via": "SIP/2.0/UDP 192.168.50.68:46442;branch=z9hG4bK-d87543-a07f874b220a3b05-1--d87543-;rport"
+ },
+ {
+ "callid": "M2NhN2I0NzBkZjFhMTkwMDIxODk1YjllN2ZiZTk5ZDI.",
+ "cseq": "1 SUBSCRIBE",
+ "method_cseq_array": [
+ "subscribe",
+ "subscribe"
+ ],
+ "method_rescode_array": [
+ "subscribe",
+ "subscribe"
+ ],
+ "originator_description": "\"1032\"<sip:[email protected]:5060>;tag=8542a020",
+ "responder_description": "\"1032\"<sip:[email protected]:5060>",
+ "test_result": 3,
+ "tuple6": "192.168.50.68:46442-192.168.40.158:5060-17-0",
+ "user_agent": "eyeBeam release 1011d stamp 40820",
+ "via": "SIP/2.0/UDP 192.168.50.68:46442;branch=z9hG4bK-d87543-823a494ec8173d67-1--d87543-;rport"
+ },
+ {
+ "callid": "ZDU0NGVmN2UyZWU2ZjFkN2E2NzZkMzczZDE4NTQ1OGQ.",
+ "cseq": "1 SUBSCRIBE",
+ "method_cseq_array": [
+ "subscribe",
+ "subscribe"
+ ],
+ "method_rescode_array": [
+ "subscribe",
+ "subscribe"
+ ],
+ "originator_description": "\"1032\"<sip:[email protected]:5060>;tag=d13ff607",
+ "responder_description": "\"1032\"<sip:[email protected]:5060>",
+ "test_result": 4,
+ "tuple6": "192.168.50.68:46442-192.168.40.158:5060-17-0",
+ "user_agent": "eyeBeam release 1011d stamp 40820",
+ "via": "SIP/2.0/UDP 192.168.50.68:46442;branch=z9hG4bK-d87543-a93d19660e74c136-1--d87543-;rport"
+ },
+ {
+ "callid": "NTIwM2RmMjQyNmU1NTNiODZhYWU0MjRhN2JhMTc0NTU.",
+ "cseq": "1 SUBSCRIBE",
+ "method_cseq_array": [
+ "subscribe",
+ "subscribe"
+ ],
+ "method_rescode_array": [
+ "subscribe",
+ "subscribe"
+ ],
+ "originator_description": "\"1032\"<sip:[email protected]:5060>;tag=2721a84d",
+ "responder_description": "\"1032\"<sip:[email protected]:5060>",
+ "test_result": 5,
+ "tuple6": "192.168.50.68:46442-192.168.40.158:5060-17-0",
+ "user_agent": "eyeBeam release 1011d stamp 40820",
+ "via": "SIP/2.0/UDP 192.168.50.68:46442;branch=z9hG4bK-d87543-e6750a38d852cc14-1--d87543-;rport"
+ }
+]
diff --git a/test/decoders/sip/result/10-s2c-complete-dialog.json b/test/decoders/sip/result/10-s2c-complete-dialog.json
new file mode 100644
index 0000000..d3ca04e
--- /dev/null
+++ b/test/decoders/sip/result/10-s2c-complete-dialog.json
@@ -0,0 +1,62 @@
+[
+ {
+ "bye": "originator",
+ "callid": "0seuMoGmE.gmmU16GkVbxuFRPNvkZK1m",
+ "cseq": "58 INVITE",
+ "method_cseq_array": [
+ "invite",
+ "invite",
+ "invite",
+ "bye"
+ ],
+ "method_rescode_array": [
+ "100",
+ "180",
+ "200",
+ "200"
+ ],
+ "originator_description": "\"13520407511\" <sip:[email protected]>;tag=aNoEhdOkv50IiTC2XG-ckdvPFl8Eglzv",
+ "responder_description": "sip:[email protected];tag=4d6ca6e64d83babb",
+ "responder_sdp": "v=0\r\no=- 3419 3419 IN IP4 139.129.211.227\r\ns=VOS3000\r\nc=IN IP4 139.129.211.227\r\nt=0 0\r\nm=audio 30790 RTP/AVP 0 101\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:101 telephone-event/8000\r\na=fmtp:101 0-15\r\na=sendrecv\r\n",
+ "responder_sdp_media_audio_port": 30790,
+ "responder_sdp_media_ip": "139.129.211.227",
+ "server": "VOS3000 V2.1.6.00",
+ "test_result": 1,
+ "tuple6": "202.43.148.166:5121-139.129.211.227:5060-17-0",
+ "via": "SIP/2.0/UDP 202.43.148.166:5121;received=202.43.148.166;rport=5121;branch=z9hG4bKPjh06y0MA4Ef17KHi6coyNt.oYo6-A4MyU"
+ },
+ {
+ "callid": "K2OSZB7cd5OTowdHoHTzzWyYX3fVqht8",
+ "cseq": "25461 SUBSCRIBE",
+ "method_cseq_array": [
+ "subscribe"
+ ],
+ "method_rescode_array": [
+ "405"
+ ],
+ "originator_description": "\"13520407511\" <sip:[email protected]>;tag=7-fTlqp68THG740NBxM8OLkda47k16qu",
+ "responder_description": "\"13520407511\" <sip:[email protected]>",
+ "test_result": 2,
+ "tuple6": "202.43.148.166:5121-139.129.211.227:5060-17-0",
+ "via": "SIP/2.0/UDP 202.43.148.166:5121;received=202.43.148.166;rport=5121;branch=z9hG4bKPjXIeWmm7lDGeT3UHsc.p3fCuxl4G27jl8"
+ },
+ {
+ "callid": "ylZwkyZFxejabfyj4X7G1b-KCCgKtdY.",
+ "cseq": "35307 REGISTER",
+ "method_cseq_array": [
+ "register",
+ "register",
+ "register"
+ ],
+ "method_rescode_array": [
+ "401",
+ "200",
+ "200"
+ ],
+ "originator_description": "\"13520407511\" <sip:[email protected]>;tag=.rmiME17Bwt1ATrRYlIcijQD4LN6g5xl",
+ "responder_description": "\"13520407511\" <sip:[email protected]>",
+ "test_result": 3,
+ "tuple6": "202.43.148.166:5121-139.129.211.227:5060-17-0",
+ "via": "SIP/2.0/UDP 202.43.148.166:5121;received=202.43.148.166;rport=5121;branch=z9hG4bKPjgHErPqZEEe93p.FELmghVDsVzJwTLy3p"
+ }
+]
diff --git a/test/decoders/sip/result/11-s2c-complete-call.json b/test/decoders/sip/result/11-s2c-complete-call.json
new file mode 100644
index 0000000..c885337
--- /dev/null
+++ b/test/decoders/sip/result/11-s2c-complete-call.json
@@ -0,0 +1,28 @@
+[
+ {
+ "bye": "responder",
+ "callid": "OGIzMzVkMDY0YTVmNzJmOWRmMGFjZWU4YjFlN2VlZGI.",
+ "cseq": "1 INVITE",
+ "method_cseq_array": [
+ "invite",
+ "invite",
+ "invite",
+ "bye"
+ ],
+ "method_rescode_array": [
+ "100",
+ "200",
+ "200",
+ "bye"
+ ],
+ "originator_description": "\"test1\" <sip:[email protected]>;tag=fd34fa7d",
+ "responder_description": "\"[email protected]\" <sip:[email protected]>",
+ "responder_sdp": "v=0\r\no=CARRIER 1614051872 1614051872 IN IP4 80.239.235.113\r\ns=SIP Call\r\nc=IN IP4 80.239.235.113\r\nt=0 0\r\nm=audio 11446 RTP/AVP 0 101\r\na=rtpmap:0 pcmu/8000\r\na=rtpmap:101 telephone-event/8000\r\na=ptime:20\r\na=sendrecv\r\n",
+ "responder_sdp_media_audio_port": 11446,
+ "responder_sdp_media_ip": "80.239.235.113",
+ "server": "(Very nice Sip Registrar/Proxy Server)",
+ "test_result": 1,
+ "tuple6": "192.168.36.97:57326-77.72.169.134:5060-17-0",
+ "via": "SIP/2.0/UDP 192.168.36.97:57326;branch=z9hG4bK-d87543-1407230f4943213a-1--d87543-;rport"
+ }
+]
diff --git a/test/decoders/sip/result/12-s2c-complete-call-with-proxy.json b/test/decoders/sip/result/12-s2c-complete-call-with-proxy.json
new file mode 100644
index 0000000..aef3ca9
--- /dev/null
+++ b/test/decoders/sip/result/12-s2c-complete-call-with-proxy.json
@@ -0,0 +1,120 @@
+[
+ {
+ "bye": "originator",
+ "callid": "ZDRjYWNhMzA5NDdmYzYzMGRmYTYwNmZmZGRmNzc3NTE.",
+ "cseq": "1 INVITE",
+ "method_cseq_array": [
+ "invite",
+ "invite",
+ "invite",
+ "invite",
+ "bye"
+ ],
+ "method_rescode_array": [
+ "407",
+ "100",
+ "180",
+ "200",
+ "200"
+ ],
+ "originator_description": "\"1032\"<sip:[email protected]:5060>;tag=c42cde23",
+ "responder_description": "\"1038\"<sip:[email protected]:5060>;tag=846d.8319acc87f795d6fad5326ce040af11e",
+ "responder_sdp": "v=0\r\no=- 7 2 IN IP4 192.168.36.64\r\ns=CounterPath eyeBeam 1.5\r\nc=IN IP4 192.168.36.64\r\nt=0 0\r\nm=audio 52188 RTP/AVP 0 8 18 101\r\na=alt:1 1 : 4JPpFDvE mn5GwW8z 192.168.36.64 52188\r\na=fmtp:18 annexb=no\r\na=fmtp:101 0-15\r\na=rtpmap:18 G729/8000\r\na=rtpmap:101 telephone-event/8000\r\na=sendrecv\r\na=x-rtp-session-id:70F70193B63E45C6A473B0D8B8B46665\r\nm=video 30090 RTP/AVP 115 34\r\na=alt:1 1 : x54MkCBj gaoyAbUY 192.168.36.64 30090\r\na=fmtp:115 QCIF=1 I=1 J=1 K=1 MaxBR=1960\r\na=fmtp:34 QCIF=1 MaxBR=1960\r\na=rtpmap:115 H263-1998/90000\r\na=rtpmap:34 H263/90000\r\na=sendrecv\r\na=x-rtp-session-id:F36E8B538BDB4748895486E45A9B9789\r\n",
+ "responder_sdp_media_audio_port": 52188,
+ "responder_sdp_media_ip": "192.168.36.64",
+ "responder_sdp_media_video_port": 30090,
+ "server": "OpenSIPS (2.4.9 (x86_64/linux))",
+ "test_result": 1,
+ "tuple6": "192.168.50.68:46442-192.168.40.158:5060-17-0",
+ "user_agent": "eyeBeam release 1011d stamp 40820",
+ "via": "SIP/2.0/UDP 192.168.50.68:46442;received=192.168.50.68;branch=z9hG4bK-d87543-8e418f41137fcc2c-1--d87543-;rport=46442"
+ },
+ {
+ "bye": "originator",
+ "callid": "NzE3YzI0Y2U1NDFiZDRjYzk3NzgzOTEwODk4NWMyZTM.",
+ "cseq": "1 INVITE",
+ "method_cseq_array": [
+ "invite",
+ "invite",
+ "invite",
+ "invite",
+ "invite",
+ "invite",
+ "bye"
+ ],
+ "method_rescode_array": [
+ "407",
+ "407",
+ "100",
+ "180",
+ "200",
+ "200",
+ "200"
+ ],
+ "originator_description": "\"1032\"<sip:[email protected]:5060>;tag=ae361d01",
+ "responder_description": "\"1038\"<sip:[email protected]:5060>;tag=846d.c35a95d5b0841928748f572e5080cd90",
+ "responder_sdp": "v=0\r\no=- 9 2 IN IP4 192.168.36.64\r\ns=CounterPath eyeBeam 1.5\r\nc=IN IP4 192.168.36.64\r\nt=0 0\r\nm=audio 5998 RTP/AVP 0 8 18 101\r\na=alt:1 1 : YO/bnCGP kAri1zsb 192.168.36.64 5998\r\na=fmtp:18 annexb=no\r\na=fmtp:101 0-15\r\na=rtpmap:18 G729/8000\r\na=rtpmap:101 telephone-event/8000\r\na=sendrecv\r\na=x-rtp-session-id:37C21754CB9D472EAA684A680B74553D\r\nm=video 53206 RTP/AVP 115 34\r\na=alt:1 1 : tPK4TpGl Mg6nG3cl 192.168.36.64 53206\r\na=fmtp:115 QCIF=1 I=1 J=1 K=1 MaxBR=1960\r\na=fmtp:34 QCIF=1 MaxBR=1960\r\na=rtpmap:115 H263-1998/90000\r\na=rtpmap:34 H263/90000\r\na=sendrecv\r\na=x-rtp-session-id:48CBA905D47E495096455E3778CE42AE\r\n",
+ "responder_sdp_media_audio_port": 5998,
+ "responder_sdp_media_ip": "192.168.36.64",
+ "responder_sdp_media_video_port": 53206,
+ "server": "OpenSIPS (2.4.9 (x86_64/linux))",
+ "test_result": 2,
+ "tuple6": "192.168.50.68:46442-192.168.40.158:5060-17-0",
+ "user_agent": "eyeBeam release 1011d stamp 40820",
+ "via": "SIP/2.0/UDP 192.168.50.68:46442;received=192.168.50.68;branch=z9hG4bK-d87543-a07f874b220a3b05-1--d87543-;rport=46442"
+ },
+ {
+ "callid": "M2NhN2I0NzBkZjFhMTkwMDIxODk1YjllN2ZiZTk5ZDI.",
+ "cseq": "1 SUBSCRIBE",
+ "method_cseq_array": [
+ "subscribe",
+ "subscribe"
+ ],
+ "method_rescode_array": [
+ "407",
+ "503"
+ ],
+ "originator_description": "\"1032\"<sip:[email protected]:5060>;tag=8542a020",
+ "responder_description": "\"1032\"<sip:[email protected]:5060>;tag=846d.d4c07ec1dcdf1c8bd9f604e4c7a80ae7",
+ "server": "OpenSIPS (2.4.9 (x86_64/linux))",
+ "test_result": 3,
+ "tuple6": "192.168.50.68:46442-192.168.40.158:5060-17-0",
+ "via": "SIP/2.0/UDP 192.168.50.68:46442;received=192.168.50.68;branch=z9hG4bK-d87543-823a494ec8173d67-1--d87543-;rport=46442"
+ },
+ {
+ "callid": "ZDU0NGVmN2UyZWU2ZjFkN2E2NzZkMzczZDE4NTQ1OGQ.",
+ "cseq": "1 SUBSCRIBE",
+ "method_cseq_array": [
+ "subscribe",
+ "subscribe"
+ ],
+ "method_rescode_array": [
+ "407",
+ "503"
+ ],
+ "originator_description": "\"1032\"<sip:[email protected]:5060>;tag=d13ff607",
+ "responder_description": "\"1032\"<sip:[email protected]:5060>;tag=846d.392735082489933dfe0d5b688a48d6f3",
+ "server": "OpenSIPS (2.4.9 (x86_64/linux))",
+ "test_result": 4,
+ "tuple6": "192.168.50.68:46442-192.168.40.158:5060-17-0",
+ "via": "SIP/2.0/UDP 192.168.50.68:46442;received=192.168.50.68;branch=z9hG4bK-d87543-a93d19660e74c136-1--d87543-;rport=46442"
+ },
+ {
+ "callid": "NTIwM2RmMjQyNmU1NTNiODZhYWU0MjRhN2JhMTc0NTU.",
+ "cseq": "1 SUBSCRIBE",
+ "method_cseq_array": [
+ "subscribe",
+ "subscribe"
+ ],
+ "method_rescode_array": [
+ "407",
+ "503"
+ ],
+ "originator_description": "\"1032\"<sip:[email protected]:5060>;tag=2721a84d",
+ "responder_description": "\"1032\"<sip:[email protected]:5060>;tag=846d.ecc91ea49edd16acd81aeb2a61bc3233",
+ "server": "OpenSIPS (2.4.9 (x86_64/linux))",
+ "test_result": 5,
+ "tuple6": "192.168.50.68:46442-192.168.40.158:5060-17-0",
+ "via": "SIP/2.0/UDP 192.168.50.68:46442;received=192.168.50.68;branch=z9hG4bK-d87543-e6750a38d852cc14-1--d87543-;rport=46442"
+ }
+]
diff --git a/test/decoders/sip/result/13-complete-call-with-limit-1.json b/test/decoders/sip/result/13-complete-call-with-limit-1.json
new file mode 100644
index 0000000..a377d56
--- /dev/null
+++ b/test/decoders/sip/result/13-complete-call-with-limit-1.json
@@ -0,0 +1,40 @@
+[
+ {
+ "bye": "responder",
+ "callid": "OGIzMzVkMDY0YTVmNzJmOWRmMGFjZWU4YjFlN2VlZGI.",
+ "cseq": "1 INVITE",
+ "method_cseq_array": [
+ "invite",
+ "invite",
+ "invite",
+ "ack",
+ "invite",
+ "ack",
+ "bye",
+ "bye"
+ ],
+ "method_rescode_array": [
+ "invite",
+ "100",
+ "200",
+ "ack",
+ "200",
+ "ack",
+ "bye",
+ "200"
+ ],
+ "originator_description": "\"test1\"<sip:[email protected]>;tag=fd34fa7d",
+ "originator_sdp": "v=0\r\no=- 6 2 IN IP4 192.168.36.97\r\ns=CounterPath eyeBeam 1.5\r\nc=IN IP4 192.168.36.97\r\nt=0 0\r\nm=audio 47782 RTP/AVP 0 8 18 101\r\na=alt:1 3 : 8rYKJy7Q suxCbgXp 192.168.48.1 47782\r\na=alt:2 2 : emfnqGoq LfzkM/Ar 192.168.237.1 47782\r\na=alt:3 1 : mE7OGFW5 0sJEvxvE 192.168.36.97 47782\r\na=fmtp:18 annexb=no\r\na=fmtp:101 0-15\r\na=rtpmap:18 G729/8000\r\na=rtpmap:101 telephone-event/8000\r\na=sendrecv\r\na=x-rtp-session-id:BF8A71898AD64337A82CB51A63150F00\r\n",
+ "originator_sdp_media_audio_port": 47782,
+ "originator_sdp_media_ip": "192.168.36.97",
+ "responder_description": "\"[email protected]\"<sip:[email protected]>",
+ "responder_sdp": "v=0\r\no=CARRIER 1614051872 1614051872 IN IP4 80.239.235.113\r\ns=SIP Call\r\nc=IN IP4 80.239.235.113\r\nt=0 0\r\nm=audio 11446 RTP/AVP 0 101\r\na=rtpmap:0 pcmu/8000\r\na=rtpmap:101 telephone-event/8000\r\na=ptime:20\r\na=sendrecv\r\n",
+ "responder_sdp_media_audio_port": 11446,
+ "responder_sdp_media_ip": "80.239.235.113",
+ "server": "(Very nice Sip Registrar/Proxy Server)",
+ "test_result": 1,
+ "tuple6": "192.168.36.97:57326-77.72.169.134:5060-17-0",
+ "user_agent": "eyeBeam release 1011d stamp 40820",
+ "via": "SIP/2.0/UDP 192.168.36.97:57326;branch=z9hG4bK-d87543-1407230f4943213a-1--d87543-;rport"
+ }
+]
diff --git a/test/decoders/sip/result/14-complete-call-with-timeout-10s.json b/test/decoders/sip/result/14-complete-call-with-timeout-10s.json
new file mode 100644
index 0000000..a377d56
--- /dev/null
+++ b/test/decoders/sip/result/14-complete-call-with-timeout-10s.json
@@ -0,0 +1,40 @@
+[
+ {
+ "bye": "responder",
+ "callid": "OGIzMzVkMDY0YTVmNzJmOWRmMGFjZWU4YjFlN2VlZGI.",
+ "cseq": "1 INVITE",
+ "method_cseq_array": [
+ "invite",
+ "invite",
+ "invite",
+ "ack",
+ "invite",
+ "ack",
+ "bye",
+ "bye"
+ ],
+ "method_rescode_array": [
+ "invite",
+ "100",
+ "200",
+ "ack",
+ "200",
+ "ack",
+ "bye",
+ "200"
+ ],
+ "originator_description": "\"test1\"<sip:[email protected]>;tag=fd34fa7d",
+ "originator_sdp": "v=0\r\no=- 6 2 IN IP4 192.168.36.97\r\ns=CounterPath eyeBeam 1.5\r\nc=IN IP4 192.168.36.97\r\nt=0 0\r\nm=audio 47782 RTP/AVP 0 8 18 101\r\na=alt:1 3 : 8rYKJy7Q suxCbgXp 192.168.48.1 47782\r\na=alt:2 2 : emfnqGoq LfzkM/Ar 192.168.237.1 47782\r\na=alt:3 1 : mE7OGFW5 0sJEvxvE 192.168.36.97 47782\r\na=fmtp:18 annexb=no\r\na=fmtp:101 0-15\r\na=rtpmap:18 G729/8000\r\na=rtpmap:101 telephone-event/8000\r\na=sendrecv\r\na=x-rtp-session-id:BF8A71898AD64337A82CB51A63150F00\r\n",
+ "originator_sdp_media_audio_port": 47782,
+ "originator_sdp_media_ip": "192.168.36.97",
+ "responder_description": "\"[email protected]\"<sip:[email protected]>",
+ "responder_sdp": "v=0\r\no=CARRIER 1614051872 1614051872 IN IP4 80.239.235.113\r\ns=SIP Call\r\nc=IN IP4 80.239.235.113\r\nt=0 0\r\nm=audio 11446 RTP/AVP 0 101\r\na=rtpmap:0 pcmu/8000\r\na=rtpmap:101 telephone-event/8000\r\na=ptime:20\r\na=sendrecv\r\n",
+ "responder_sdp_media_audio_port": 11446,
+ "responder_sdp_media_ip": "80.239.235.113",
+ "server": "(Very nice Sip Registrar/Proxy Server)",
+ "test_result": 1,
+ "tuple6": "192.168.36.97:57326-77.72.169.134:5060-17-0",
+ "user_agent": "eyeBeam release 1011d stamp 40820",
+ "via": "SIP/2.0/UDP 192.168.36.97:57326;branch=z9hG4bK-d87543-1407230f4943213a-1--d87543-;rport"
+ }
+]
diff --git a/test/decoders/sip/result/15-complete-call-with-timeout-30s.json b/test/decoders/sip/result/15-complete-call-with-timeout-30s.json
new file mode 100644
index 0000000..a377d56
--- /dev/null
+++ b/test/decoders/sip/result/15-complete-call-with-timeout-30s.json
@@ -0,0 +1,40 @@
+[
+ {
+ "bye": "responder",
+ "callid": "OGIzMzVkMDY0YTVmNzJmOWRmMGFjZWU4YjFlN2VlZGI.",
+ "cseq": "1 INVITE",
+ "method_cseq_array": [
+ "invite",
+ "invite",
+ "invite",
+ "ack",
+ "invite",
+ "ack",
+ "bye",
+ "bye"
+ ],
+ "method_rescode_array": [
+ "invite",
+ "100",
+ "200",
+ "ack",
+ "200",
+ "ack",
+ "bye",
+ "200"
+ ],
+ "originator_description": "\"test1\"<sip:[email protected]>;tag=fd34fa7d",
+ "originator_sdp": "v=0\r\no=- 6 2 IN IP4 192.168.36.97\r\ns=CounterPath eyeBeam 1.5\r\nc=IN IP4 192.168.36.97\r\nt=0 0\r\nm=audio 47782 RTP/AVP 0 8 18 101\r\na=alt:1 3 : 8rYKJy7Q suxCbgXp 192.168.48.1 47782\r\na=alt:2 2 : emfnqGoq LfzkM/Ar 192.168.237.1 47782\r\na=alt:3 1 : mE7OGFW5 0sJEvxvE 192.168.36.97 47782\r\na=fmtp:18 annexb=no\r\na=fmtp:101 0-15\r\na=rtpmap:18 G729/8000\r\na=rtpmap:101 telephone-event/8000\r\na=sendrecv\r\na=x-rtp-session-id:BF8A71898AD64337A82CB51A63150F00\r\n",
+ "originator_sdp_media_audio_port": 47782,
+ "originator_sdp_media_ip": "192.168.36.97",
+ "responder_description": "\"[email protected]\"<sip:[email protected]>",
+ "responder_sdp": "v=0\r\no=CARRIER 1614051872 1614051872 IN IP4 80.239.235.113\r\ns=SIP Call\r\nc=IN IP4 80.239.235.113\r\nt=0 0\r\nm=audio 11446 RTP/AVP 0 101\r\na=rtpmap:0 pcmu/8000\r\na=rtpmap:101 telephone-event/8000\r\na=ptime:20\r\na=sendrecv\r\n",
+ "responder_sdp_media_audio_port": 11446,
+ "responder_sdp_media_ip": "80.239.235.113",
+ "server": "(Very nice Sip Registrar/Proxy Server)",
+ "test_result": 1,
+ "tuple6": "192.168.36.97:57326-77.72.169.134:5060-17-0",
+ "user_agent": "eyeBeam release 1011d stamp 40820",
+ "via": "SIP/2.0/UDP 192.168.36.97:57326;branch=z9hG4bK-d87543-1407230f4943213a-1--d87543-;rport"
+ }
+]
diff --git a/test/decoders/sip/sip_test_main.cpp b/test/decoders/sip/sip_test_main.cpp
new file mode 100644
index 0000000..91c7016
--- /dev/null
+++ b/test/decoders/sip/sip_test_main.cpp
@@ -0,0 +1,79 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "stellar/stellar.h"
+
+#include <gtest/gtest.h>
+
+const char *usage = "Usage: %s [-m] [-u test_filter] [-c config_file] [-f result_file]\n";
+
+int main(int argc, char **argv) {
+ int opt;
+ int run_unittest = 0;
+ int run_stellar = 0;
+ const char *test_filter = NULL;
+ const char *result_filename = NULL;
+ const char *conf_filename = NULL;
+
+ if (argc == 1) {
+ fprintf(stderr, usage, argv[0]);
+ exit(-1);
+ }
+
+ while ((opt = getopt(argc, argv, "hmu:c:f:")) != -1) {
+ switch (opt) {
+ case 'u':
+ run_unittest = 1;
+ test_filter = optarg;
+ break;
+ case 'm':
+ run_stellar = 1;
+ break;
+ case 'f':
+ result_filename = optarg;
+ break;
+ case 'c':
+ conf_filename = optarg;
+ break;
+ case 'h':
+ default:
+ fprintf(stderr, usage, argv[0]);
+ exit(-1);
+ }
+ }
+
+ ::testing::InitGoogleTest(&argc, argv);
+
+ if (run_unittest) {
+ testing::GTEST_FLAG(filter) = test_filter ? test_filter : "";
+ int test_ret = RUN_ALL_TESTS();
+ if (test_ret != 0) {
+ fprintf(stderr, "Tests failed with return code %d\n", test_ret);
+ return test_ret;
+ }
+ }
+
+ if (run_stellar) {
+ if (result_filename == NULL) {
+ result_filename = "./sip_result.json";
+ }
+ // setenv for sip test module
+ setenv("SIP_TEST_RESULT_EXPECT", result_filename, 1);
+
+ if (conf_filename == NULL) {
+ conf_filename = "./conf/stellar.toml";
+ }
+
+ struct stellar *st = stellar_new(conf_filename);
+ if (st == NULL) {
+ fprintf(stderr, "Failed to create stellar instance.\n");
+ return -1;
+ }
+
+ stellar_run(st);
+ stellar_free(st);
+ }
+
+ return ::testing::Test::HasFailure() ? -1 : 0;
+}
diff --git a/test/decoders/sip/sip_test_module.cpp b/test/decoders/sip/sip_test_module.cpp
new file mode 100644
index 0000000..f42e40b
--- /dev/null
+++ b/test/decoders/sip/sip_test_module.cpp
@@ -0,0 +1,721 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/time.h>
+
+#include "stellar/stellar.h"
+#include "stellar/module.h"
+#include "stellar/session.h"
+#include "stellar/utils.h"
+#include "stellar/sip.h"
+
+#include <gtest/gtest.h>
+#include <uthash/uthash.h>
+#include "cjson/cJSON.h"
+
+#define SIP_TEST_MODULE_NAME "SIP_TEST_MODULE"
+#define SIP_TEST_RESULT_EXPECT_ENV "SIP_TEST_RESULT_EXPECT"
+#define SIP_TEST_EXDATA_NAME "SIP_TEST_EXDATA"
+
+#define SIP_TEST_TRANSACTION_SEQ_MAX 64
+
+#define timeval_delta_ms(start, end) (((end).tv_sec-(start).tv_sec)*1000 + ((end).tv_usec-(start).tv_usec)/1000)
+#define timeval_delta_us(start, end) (((end).tv_sec-(start).tv_sec)*1000*1000 + ((end).tv_usec-(start).tv_usec))
+#define timeval_to_ms(t) ((t).tv_sec*1000+(t).tv_usec/1000)
+
+struct sip_test_sockaddr_in {
+ int family;
+ union {
+ struct sockaddr_in ipv4;
+ struct sockaddr_in6 ipv6;
+ } sockaddr;
+};
+
+struct sip_test_transaction {
+ char *tuple6;
+ char *callid;
+ char *via;
+ char *cseq;
+ char *server;
+ char *user_agent;
+ char *reason;
+ char *bye;
+ char *originator_description;
+ char *originator_description_tag;
+ char *originator_sdp;
+ char *originator_sdp_media_ip;
+ char *responder_description;
+ char *responder_description_tag;
+ char *responder_sdp;
+ char *responder_sdp_media_ip;
+ unsigned short originator_sdp_media_audio_port;
+ unsigned short originator_sdp_media_video_port;
+ unsigned short responder_sdp_media_audio_port;
+ unsigned short responder_sdp_media_video_port;
+ cJSON *method_rescode_array;
+ cJSON *method_cseq_array;
+
+ int is_log_done;
+ int request_seen;
+ int response_seen;
+ long long invite_timestamp_ms;
+ long long bye_timestamp_ms;
+ struct sip_test_sockaddr_in sockaddr;
+
+ char *call_id; // hash key
+ size_t call_id_len;
+ UT_hash_handle hh;
+};
+
+struct sip_test_exdata {
+ int callback_times;
+ struct sip_test_transaction* transactions;
+ struct sip_test_module_ctx *mod_ctx_ref;
+};
+
+struct sip_test_result {
+ cJSON *test_json;
+ cJSON *expect_json;
+ int count;
+};
+
+struct sip_test_module_ctx {
+ int exdata_id;
+ struct sip_test_result *result;
+
+ // depends a single session pcap
+ int callback_times;
+ struct module_manager *mod_mgr_ref;
+};
+
+const char* g_sip_test_method_name[] = {
+ "unknown",
+ "invite",
+ "ack",
+ "options",
+ "register",
+ "bye",
+ "cancel",
+ "do",
+ "info",
+ "message",
+ "notify",
+ "prack",
+ "qauth",
+ "refer",
+ "sprack",
+ "subscribe",
+ "update",
+ "publish"
+};
+
+static void sip_test_result_commit(struct sip_test_result *result, cJSON *json)
+{
+ cJSON_AddNumberToObject(json, "test_result", ++result->count);
+ cJSON_AddItemToArray(result->test_json, json);
+}
+
+static void sip_test_result_compare(struct sip_test_result *result)
+{
+ EXPECT_TRUE(result->expect_json != NULL);
+ EXPECT_TRUE(result->test_json != NULL);
+
+ int i, json_compare;
+ int test_result_count, expect_result_count;
+ char *test_str, *expect_str;
+ cJSON *tmp_test, *tmp_expect;
+
+ //expect_str = cJSON_Print(result->expect_json);
+ //test_str = cJSON_Print(result->test_json);
+ //printf("LOAD Raw:\n%s\n", expect_str);
+ //printf("TEST Raw:\n%s\n", test_str);
+
+ test_result_count = cJSON_GetArraySize(result->test_json);
+ expect_result_count = cJSON_GetArraySize(result->expect_json);
+
+ EXPECT_EQ(test_result_count, expect_result_count);
+
+ for (i = 0; i < MIN(test_result_count, expect_result_count); i++) {
+ tmp_test = cJSON_GetArrayItem(result->test_json, i);
+ tmp_expect = cJSON_GetArrayItem(result->expect_json, i);
+ expect_str = cJSON_Print(tmp_expect);
+ test_str = cJSON_Print(tmp_test);
+
+ json_compare = cJSON_Compare(tmp_expect, tmp_test, 0);
+ if (json_compare != 1) {
+ printf("LOAD Diff:\n%s\n", expect_str);
+ printf("TEST Diff:\n%s\n", test_str);
+ }
+
+ free(expect_str);
+ free(test_str);
+
+ EXPECT_EQ(json_compare, 1);
+ break;
+ }
+}
+
+static void sip_test_result_exit(struct sip_test_result *result)
+{
+ if (result->expect_json) {
+ cJSON_Delete(result->expect_json);
+ }
+ if (result->test_json) {
+ cJSON_Delete(result->test_json);
+ }
+ free(result);
+}
+
+static struct sip_test_result * sip_test_result_init(const char *filename)
+{
+ long filesize;
+ char *buffer;
+ FILE *file;
+ struct sip_test_result *result;
+
+ result = (struct sip_test_result *)calloc(1, sizeof(struct sip_test_result));
+
+ file = fopen(filename, "rb");
+ if (file) {
+ fseek(file, 0, SEEK_END);
+ filesize = ftell(file);
+ rewind(file);
+ buffer = (char *)calloc(filesize + 1, 1);
+ fread(buffer, 1, filesize, file);
+
+ result->expect_json = cJSON_Parse(buffer);
+
+ free(buffer);
+ fclose(file);
+ }
+
+ result->test_json = cJSON_CreateArray();
+
+ printf("sip test result expect: %s\n", filename);
+ return result;
+}
+
+static void sip_test_store_packet_dst(struct sip_test_sockaddr_in *sockaddr, const struct packet *pkt) {
+ const struct layer *layer;
+
+ if (sockaddr == NULL) {
+ return;
+ }
+
+ memset(sockaddr, 0, sizeof(struct sip_test_sockaddr_in));
+
+ layer = packet_get_layer_by_idx(pkt, packet_get_layer_count(pkt) - 2);
+ if (layer->proto == LAYER_PROTO_IPV4) {
+ sockaddr->family = AF_INET;
+ sockaddr->sockaddr.ipv4.sin_family = AF_INET;
+ sockaddr->sockaddr.ipv4.sin_addr = layer->hdr.ip4->ip_dst;
+
+ layer = packet_get_layer_by_idx(pkt, packet_get_layer_count(pkt) - 1);
+ if (layer->proto == LAYER_PROTO_TCP) {
+ sockaddr->sockaddr.ipv4.sin_port = htons(layer->hdr.tcp->dest);
+ }
+ if (layer->proto == LAYER_PROTO_UDP) {
+ sockaddr->sockaddr.ipv4.sin_port = htons(layer->hdr.udp->dest);
+ }
+ }
+ if (layer->proto == LAYER_PROTO_IPV6) {
+ sockaddr->family = AF_INET6;
+ sockaddr->sockaddr.ipv6.sin6_family = AF_INET6;
+ memcpy(&sockaddr->sockaddr.ipv6.sin6_addr, &layer->hdr.ip6->ip6_dst, sizeof(struct in6_addr));
+
+ layer = packet_get_layer_by_idx(pkt, packet_get_layer_count(pkt) - 2);
+ if (layer->proto == LAYER_PROTO_TCP) {
+ sockaddr->sockaddr.ipv6.sin6_port = htons(layer->hdr.tcp->dest);
+ }
+ if (layer->proto == LAYER_PROTO_UDP) {
+ sockaddr->sockaddr.ipv6.sin6_port = htons(layer->hdr.udp->dest);
+ }
+ }
+}
+
+static void sip_test_store_packet_src(struct sip_test_sockaddr_in *sockaddr, const struct packet *pkt) {
+ const struct layer *layer;
+
+ if (sockaddr == NULL) {
+ return;
+ }
+
+ memset(sockaddr, 0, sizeof(struct sip_test_sockaddr_in));
+
+ layer = packet_get_layer_by_idx(pkt, packet_get_layer_count(pkt) - 2);
+ if (layer->proto == LAYER_PROTO_IPV4) {
+ sockaddr->family = AF_INET;
+ sockaddr->sockaddr.ipv4.sin_family = AF_INET;
+ sockaddr->sockaddr.ipv4.sin_addr = layer->hdr.ip4->ip_src;
+
+ layer = packet_get_layer_by_idx(pkt, packet_get_layer_count(pkt) - 1);
+ if (layer->proto == LAYER_PROTO_TCP) {
+ sockaddr->sockaddr.ipv4.sin_port = htons(layer->hdr.tcp->source);
+ }
+ if (layer->proto == LAYER_PROTO_UDP) {
+ sockaddr->sockaddr.ipv4.sin_port = htons(layer->hdr.udp->source);
+ }
+ }
+ if (layer->proto == LAYER_PROTO_IPV6) {
+ sockaddr->family = AF_INET6;
+ sockaddr->sockaddr.ipv6.sin6_family = AF_INET6;
+ memcpy(&sockaddr->sockaddr.ipv6.sin6_addr, &layer->hdr.ip6->ip6_src, sizeof(struct in6_addr));
+
+ layer = packet_get_layer_by_idx(pkt, packet_get_layer_count(pkt) - 2);
+ if (layer->proto == LAYER_PROTO_TCP) {
+ sockaddr->sockaddr.ipv6.sin6_port = htons(layer->hdr.tcp->source);
+ }
+ if (layer->proto == LAYER_PROTO_UDP) {
+ sockaddr->sockaddr.ipv6.sin6_port = htons(layer->hdr.udp->source);
+ }
+ }
+}
+
+static int sip_test_compare_packet_src(struct sip_test_sockaddr_in *sockaddr, const struct packet *pkt)
+{
+ struct sip_test_sockaddr_in pkt_src;
+ sip_test_store_packet_src(&pkt_src, pkt);
+ return 0 == memcmp(sockaddr, &pkt_src, sizeof(pkt_src));
+}
+
+void sip_test_transaction_free(struct sip_test_transaction *transaction)
+{
+ if (transaction == NULL) return;
+ if (transaction->tuple6 ) free(transaction->tuple6);
+ if (transaction->callid ) free(transaction->callid);
+ if (transaction->via ) free(transaction->via);
+ if (transaction->cseq ) free(transaction->cseq);
+ if (transaction->server ) free(transaction->server);
+ if (transaction->user_agent ) free(transaction->user_agent);
+ if (transaction->bye ) free(transaction->bye);
+ if (transaction->originator_description ) free(transaction->originator_description);
+ if (transaction->originator_description_tag ) free(transaction->originator_description_tag);
+ if (transaction->originator_sdp ) free(transaction->originator_sdp);
+ if (transaction->originator_sdp_media_ip ) free(transaction->originator_sdp_media_ip);
+ if (transaction->responder_description ) free(transaction->responder_description);
+ if (transaction->responder_description_tag ) free(transaction->responder_description_tag);
+ if (transaction->responder_sdp ) free(transaction->responder_sdp);
+ if (transaction->responder_sdp_media_ip ) free(transaction->responder_sdp_media_ip);
+
+ if (transaction->call_id) free(transaction->call_id);
+ free(transaction);
+}
+
+struct sip_test_transaction *sip_test_transaction_new(void)
+{
+ return (struct sip_test_transaction *)calloc(1, sizeof(struct sip_test_transaction));
+}
+
+static void sip_test_transaction_log(struct sip_test_module_ctx *mod_ctx, struct sip_test_transaction *transaction)
+{
+ if (transaction->is_log_done) {
+ return;
+ }
+ transaction->is_log_done = 1;
+
+ cJSON *json = cJSON_CreateObject();
+
+ if (transaction->tuple6) {
+ cJSON_AddStringToObject(json, "tuple6",transaction->tuple6);
+ }
+ if (transaction->callid) {
+ cJSON_AddStringToObject(json, "callid",transaction->callid);
+ }
+ if (transaction->via ) {
+ cJSON_AddStringToObject(json, "via",transaction->via);
+ }
+ if (transaction->cseq ) {
+ cJSON_AddStringToObject(json, "cseq",transaction->cseq);
+ }
+ if (transaction->server) {
+ cJSON_AddStringToObject(json, "server",transaction->server);
+ }
+ if (transaction->user_agent) {
+ cJSON_AddStringToObject(json, "user_agent",transaction->user_agent);
+ }
+ if (transaction->bye) {
+ cJSON_AddStringToObject(json, "bye", transaction->bye);
+ }
+ if (transaction->originator_description) {
+ cJSON_AddStringToObject(json, "originator_description",transaction->originator_description);
+ }
+ if (transaction->originator_description_tag) {
+ cJSON_AddStringToObject(json, "originator_description_tag",transaction->originator_description_tag);
+ }
+ if (transaction->originator_sdp) {
+ cJSON_AddStringToObject(json, "originator_sdp",transaction->originator_sdp);
+ }
+ if (transaction->originator_sdp_media_ip ) {
+ cJSON_AddStringToObject(json, "originator_sdp_media_ip",transaction->originator_sdp_media_ip);
+ }
+ if (transaction->originator_sdp_media_audio_port) {
+ cJSON_AddNumberToObject(json, "originator_sdp_media_audio_port",transaction->originator_sdp_media_audio_port);
+ }
+ if (transaction->originator_sdp_media_video_port) {
+ cJSON_AddNumberToObject(json, "originator_sdp_media_video_port",transaction->originator_sdp_media_video_port);
+ }
+ if (transaction->responder_description ) {
+ cJSON_AddStringToObject(json, "responder_description",transaction->responder_description);
+ }
+ if (transaction->responder_description_tag ) {
+ cJSON_AddStringToObject(json, "responder_description_tag",transaction->responder_description_tag);
+ }
+ if (transaction->responder_sdp ) {
+ cJSON_AddStringToObject(json, "responder_sdp",transaction->responder_sdp);
+ }
+ if (transaction->responder_sdp_media_ip) {
+ cJSON_AddStringToObject(json, "responder_sdp_media_ip",transaction->responder_sdp_media_ip);
+ }
+ if (transaction->responder_sdp_media_audio_port) {
+ cJSON_AddNumberToObject(json, "responder_sdp_media_audio_port", transaction->responder_sdp_media_audio_port);
+ }
+ if (transaction->responder_sdp_media_video_port) {
+ cJSON_AddNumberToObject(json, "responder_sdp_media_video_port", transaction->responder_sdp_media_video_port);
+ }
+
+ if (transaction->method_rescode_array) {
+ cJSON_AddItemToObject(json, "method_rescode_array", transaction->method_rescode_array);
+ transaction->method_rescode_array = NULL;
+ }
+ if (transaction->method_cseq_array) {
+ cJSON_AddItemToObject(json, "method_cseq_array", transaction->method_cseq_array);
+ transaction->method_cseq_array = NULL;
+ }
+
+ sip_test_result_commit(mod_ctx->result, json);
+}
+
+struct sip_test_transaction * sip_test_transaction_get(struct sip_test_exdata *exdata, char *call_id, size_t call_id_len)
+{
+ struct sip_test_transaction *transaction = NULL;
+
+ HASH_FIND(hh, exdata->transactions, call_id, call_id_len, transaction);
+ if (transaction == NULL) {
+ transaction = sip_test_transaction_new();
+ transaction->method_rescode_array = cJSON_CreateArray();
+ transaction->method_cseq_array = cJSON_CreateArray();
+ transaction->call_id = (char *)malloc(call_id_len);
+ transaction->call_id_len = call_id_len;
+ memcpy(transaction->call_id, call_id, call_id_len);
+ } else {
+ HASH_DELETE(hh, exdata->transactions, transaction);
+ }
+ HASH_ADD_KEYPTR(hh, exdata->transactions, transaction->call_id, transaction->call_id_len, transaction);
+ return transaction;
+}
+
+static void sip_test_transaction_fill_body(struct sip_test_transaction *transaction, struct sip_body *body, int is_request)
+{
+ if (is_request) {
+ // originator media
+ if (transaction->originator_sdp == NULL && body->sdp_content && body->sdp_content_len > 0) {
+ transaction->originator_sdp = strndup(body->sdp_content, body->sdp_content_len);
+ }
+ if (transaction->originator_sdp_media_ip == NULL && body->media_ip && body->media_ip_len > 0) {
+ transaction->originator_sdp_media_ip = strndup(body->media_ip, body->media_ip_len);
+ }
+ if (transaction->originator_sdp_media_audio_port == 0) {
+ transaction->originator_sdp_media_audio_port = body->media_audio_port;
+ }
+ if (transaction->originator_sdp_media_video_port == 0) {
+ transaction->originator_sdp_media_video_port = body->media_video_port;
+ }
+ } else {
+ // responder media
+ if (transaction->responder_sdp == NULL && body->sdp_content && body->sdp_content_len > 0) {
+ transaction->responder_sdp = strndup(body->sdp_content, body->sdp_content_len);
+ }
+ if (transaction->responder_sdp_media_ip == NULL && body->media_ip && body->media_ip_len > 0) {
+ transaction->responder_sdp_media_ip = strndup(body->media_ip, body->media_ip_len);
+ }
+ if (transaction->responder_sdp_media_audio_port == 0) {
+ transaction->responder_sdp_media_audio_port = body->media_audio_port;
+ }
+ if (transaction->responder_sdp_media_video_port == 0) {
+ transaction->responder_sdp_media_video_port = body->media_video_port;
+ }
+ }
+}
+
+static void sip_test_transaction_fill_header(struct sip_test_transaction *transaction, struct sip_header *header)
+{
+ struct sip_header_field *field;
+
+ field = header->call_id;
+ if (transaction->callid == NULL && field && field->field_value && field->field_value_len > 0) {
+ transaction->callid = strndup(field->field_value, field->field_value_len);
+ }
+ field = header->from;
+ if (transaction->originator_description == NULL && field && field->field_value && field->field_value_len > 0) {
+ transaction->originator_description = strndup(field->field_value, field->field_value_len);
+ }
+ field = header->to;
+ if (transaction->responder_description == NULL && field && field->field_value && field->field_value_len > 0) {
+ transaction->responder_description = strndup(field->field_value, field->field_value_len);
+ }
+ field = header->cseq;
+ if (transaction->cseq == NULL && field && field->field_value && field->field_value_len > 0) {
+ transaction->cseq = strndup(field->field_value, field->field_value_len);
+ }
+ field = header->via;
+ if (transaction->via == NULL && field && field->field_value && field->field_value_len > 0) {
+ transaction->via = strndup(field->field_value, field->field_value_len);
+ }
+ field = header->server;
+ if (transaction->server == NULL && field && field->field_value && field->field_value_len > 0) {
+ transaction->server = strndup(field->field_value, field->field_value_len);
+ }
+ field = header->user_agent;
+ if (transaction->user_agent == NULL && field && field->field_value && field->field_value_len > 0) {
+ transaction->user_agent = strndup(field->field_value, field->field_value_len);
+ }
+}
+
+static void sip_test_transaction_fill_method(struct sip_test_transaction *transaction, enum sip_method method)
+{
+ cJSON_AddItemToArray(transaction->method_rescode_array, cJSON_CreateString(g_sip_test_method_name[method]));
+ cJSON_AddItemToArray(transaction->method_cseq_array, cJSON_CreateString(g_sip_test_method_name[method]));
+}
+
+static void sip_test_transaction_fill_cseq_method(struct sip_test_transaction *transaction, enum sip_method cseq_method)
+{
+ cJSON_AddItemToArray(transaction->method_cseq_array, cJSON_CreateString(g_sip_test_method_name[cseq_method]));
+}
+
+static void sip_test_transaction_fill_status_code(struct sip_test_transaction *transaction, char *status_code, size_t status_code_len)
+{
+ char *status_code_str = strndup(status_code, status_code_len);
+ cJSON_AddItemToArray(transaction->method_rescode_array, cJSON_CreateString(status_code_str));
+ free(status_code_str);
+}
+
+void sip_test_exdata_free(int idx, void *ex_ptr, void *arg)
+{
+ (void)(idx);
+ struct sip_test_module_ctx *mod_ctx = (struct sip_test_module_ctx *)arg;
+ struct sip_test_exdata *exdata = (struct sip_test_exdata *)ex_ptr;
+
+ if (exdata) {
+ struct sip_test_transaction *transaction = NULL, *tmp = NULL;
+ HASH_ITER(hh, exdata->transactions, transaction, tmp) {
+ HASH_DELETE(hh, exdata->transactions, transaction);
+ sip_test_transaction_log(mod_ctx, transaction);
+ sip_test_transaction_free(transaction);
+ }
+
+ free(exdata);
+ }
+}
+
+static void sip_test_request_callback(struct session *sess,
+ struct sip_request_line *request_line,
+ struct sip_header *header,
+ struct sip_body *body,
+ void *arg)
+{
+ (void)(request_line);
+ (void)(header);
+ (void)(body);
+ (void)(arg);
+ struct timeval ts;
+ struct sip_test_exdata *exdata;
+ struct sip_test_transaction *transaction;
+ struct sip_test_module_ctx *mod_ctx = (struct sip_test_module_ctx *)arg;
+
+ exdata = (struct sip_test_exdata *)session_get_exdata(sess, mod_ctx->exdata_id);
+ if (exdata == NULL) {
+ exdata = (struct sip_test_exdata *)calloc(1, sizeof(struct sip_test_exdata));
+ session_set_exdata(sess, mod_ctx->exdata_id, exdata);
+ }
+
+ transaction = sip_test_transaction_get(exdata, (char *)header->call_id->field_value, header->call_id->field_value_len);
+ if (transaction == NULL) {
+ return;
+ }
+ if (transaction->request_seen == 0) {
+ transaction->request_seen = 1;
+ }
+ if (transaction->tuple6 == NULL) {
+ transaction->tuple6 = strdup(session_get_readable_addr(sess));
+ }
+ sip_test_transaction_fill_method(transaction, request_line->method);
+ sip_test_transaction_fill_header(transaction, header);
+
+ const struct packet *pkt = session_get_current_packet(sess);
+ switch (request_line->method) {
+ case SIP_METHOD_INVITE:
+ if (body->body) {
+ sip_test_transaction_fill_body(transaction, body, 1);
+ }
+
+ // record originator session sockaddr
+ sip_test_store_packet_src(&transaction->sockaddr, pkt);
+
+ // record invite timestamp
+ if (transaction->invite_timestamp_ms == 0) {
+ gettimeofday(&ts, NULL);
+ transaction->invite_timestamp_ms = timeval_to_ms(ts);
+ }
+ break;
+ case SIP_METHOD_BYE:
+ if (transaction->bye == NULL) {
+ // check originator session sockaddr
+ if (sip_test_compare_packet_src(&transaction->sockaddr, pkt)) {
+ transaction->bye = strdup("originator");
+ } else {
+ transaction->bye = strdup("responder");
+ }
+
+ // record bye timestamp
+ gettimeofday(&ts, NULL);
+ transaction->bye_timestamp_ms = timeval_to_ms(ts);
+
+ // transaction in symmetric session should be delete now
+ if (! (transaction->request_seen && transaction->response_seen)) {
+ sip_test_transaction_log(mod_ctx, transaction);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return;
+}
+static void sip_test_response_callback(struct session *sess,
+ struct sip_status_line *status_line,
+ struct sip_header *header,
+ struct sip_body *body,
+ void *arg)
+{
+ (void)(status_line);
+ (void)(header);
+ (void)(body);
+ (void)(arg);
+ struct timeval ts;
+ struct sip_header_field *field;
+ struct sip_test_exdata *exdata;
+ struct sip_test_transaction *transaction;
+ struct sip_test_module_ctx *mod_ctx = (struct sip_test_module_ctx *)arg;
+
+ exdata = (struct sip_test_exdata *)session_get_exdata(sess, mod_ctx->exdata_id);
+ if (exdata == NULL) {
+ exdata = (struct sip_test_exdata *)calloc(1, sizeof(struct sip_test_exdata));
+ session_set_exdata(sess, mod_ctx->exdata_id, exdata);
+ }
+
+ transaction = sip_test_transaction_get(exdata, (char*)header->call_id->field_value, header->call_id->field_value_len);
+ if (transaction == NULL) {
+ return;
+ }
+ if (transaction->response_seen == 0) {
+ transaction->response_seen = 1;
+ }
+ if (transaction->tuple6 == NULL) {
+ transaction->tuple6 = strdup(session_get_readable_addr(sess));
+ }
+ sip_test_transaction_fill_status_code(transaction, (char*)status_line->code, status_line->code_len);
+ sip_test_transaction_fill_cseq_method(transaction, header->cseq_method);
+ sip_test_transaction_fill_header(transaction, header);
+
+ const struct packet *pkt = session_get_current_packet(sess);
+ switch (header->cseq_method) {
+ case SIP_METHOD_INVITE:
+ if (body->body) {
+ sip_test_transaction_fill_body(transaction, body, 0);
+ }
+
+ // originator session sockaddr
+ sip_test_store_packet_dst(&transaction->sockaddr, pkt);
+
+ // record invite timestamp
+ if (transaction->invite_timestamp_ms == 0) {
+ gettimeofday(&ts, NULL);
+ transaction->invite_timestamp_ms = timeval_to_ms(ts);
+ }
+ break;
+ case SIP_METHOD_BYE:
+ // check originator session sockaddr
+ if (transaction->bye == NULL) {
+ if (sip_test_compare_packet_src(&transaction->sockaddr, pkt)) {
+ transaction->bye = strdup("responder");
+ } else {
+ transaction->bye = strdup("originator");
+ }
+ }
+
+ // record bye timestamp
+ if (transaction->bye_timestamp_ms == 0) {
+ gettimeofday(&ts, NULL);
+ transaction->bye_timestamp_ms = timeval_to_ms(ts);
+ }
+
+ // record bye reason
+ field = header->reason;
+ if (transaction->reason == NULL && field && field->field_value && field->field_value_len > 0) {
+ transaction->reason = strndup(field->field_value, field->field_value_len);
+ }
+
+ sip_test_transaction_log(mod_ctx, transaction);
+ break;
+ default:
+ break;
+ }
+
+ return;
+}
+
+extern "C" void sip_test_exit(struct module_manager *mod_mgr, struct module *mod)
+{
+ (void)(mod_mgr);
+ struct sip_test_module_ctx *mod_ctx;
+
+ if (mod) {
+ mod_ctx = (struct sip_test_module_ctx *)module_get_ctx(mod);
+ if (mod_ctx) {
+ sip_test_result_compare(mod_ctx->result);
+ sip_test_result_exit(mod_ctx->result);
+ free(mod_ctx);
+ }
+ module_free(mod);
+ }
+}
+
+extern "C" struct module *sip_test_init(struct module_manager *mod_mgr)
+{
+ int ret;
+ struct module *mod;
+ struct sip_test_module_ctx *mod_ctx;
+ struct session_manager *sess_mgr;
+ struct sip_decoder *decoder;
+
+ mod_ctx = (struct sip_test_module_ctx *)calloc(1, sizeof(struct sip_test_module_ctx));
+ mod_ctx->mod_mgr_ref = mod_mgr;
+ mod = module_new(SIP_TEST_MODULE_NAME, mod_ctx);
+ sess_mgr = module_to_session_manager(module_manager_get_module(mod_mgr, SESSION_MANAGER_MODULE_NAME));
+
+ if (mod_mgr == NULL || sess_mgr == NULL) {
+ goto exit;
+ }
+
+ mod_ctx->exdata_id = session_manager_new_session_exdata_index(sess_mgr, SIP_TEST_EXDATA_NAME, sip_test_exdata_free, mod_ctx);
+ if (mod_ctx->exdata_id < 0) {
+ goto exit;
+ }
+
+ mod_ctx->result = sip_test_result_init(getenv(SIP_TEST_RESULT_EXPECT_ENV));
+ if (mod_ctx->result == NULL) {
+ goto exit;
+ }
+
+ decoder = module_to_sip_decoder(module_manager_get_module(mod_mgr, SIP_MODULE_NAME));
+ ret = sip_subscribe(decoder, sip_test_request_callback, sip_test_response_callback, mod_ctx);
+ if (ret < 0) {
+ goto exit;
+ }
+
+ return mod;
+exit:
+ printf("sip_test module init failed!\n");
+ sip_test_exit(mod_mgr, mod);
+ return NULL;
+}