diff options
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 Binary files differnew file mode 100644 index 0000000..fef0252 --- /dev/null +++ b/test/decoders/sip/pcap/01-complete-dialog.pcap diff --git a/test/decoders/sip/pcap/02-complete-call.pcap b/test/decoders/sip/pcap/02-complete-call.pcap Binary files differnew file mode 100644 index 0000000..27dcd5a --- /dev/null +++ b/test/decoders/sip/pcap/02-complete-call.pcap 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 Binary files differnew file mode 100644 index 0000000..bd1d096 --- /dev/null +++ b/test/decoders/sip/pcap/03-complete-call-with-empty-line.pcap diff --git a/test/decoders/sip/pcap/04-complete-call-with-proxy.pcap b/test/decoders/sip/pcap/04-complete-call-with-proxy.pcap Binary files differnew file mode 100644 index 0000000..3875e73 --- /dev/null +++ b/test/decoders/sip/pcap/04-complete-call-with-proxy.pcap 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 Binary files differnew file mode 100644 index 0000000..038c81d --- /dev/null +++ b/test/decoders/sip/pcap/05-complete-call-with-two-dir-invite.pcap diff --git a/test/decoders/sip/pcap/06-complete-call-on-tcp.pcap b/test/decoders/sip/pcap/06-complete-call-on-tcp.pcap Binary files differnew file mode 100644 index 0000000..f51f64e --- /dev/null +++ b/test/decoders/sip/pcap/06-complete-call-on-tcp.pcap diff --git a/test/decoders/sip/pcap/07-c2s-complete-dialog.pcap b/test/decoders/sip/pcap/07-c2s-complete-dialog.pcap Binary files differnew file mode 100644 index 0000000..ac83bfb --- /dev/null +++ b/test/decoders/sip/pcap/07-c2s-complete-dialog.pcap diff --git a/test/decoders/sip/pcap/08-c2s-complete-call.pcap b/test/decoders/sip/pcap/08-c2s-complete-call.pcap Binary files differnew file mode 100644 index 0000000..081ca40 --- /dev/null +++ b/test/decoders/sip/pcap/08-c2s-complete-call.pcap 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 Binary files differnew file mode 100644 index 0000000..66bce10 --- /dev/null +++ b/test/decoders/sip/pcap/09-c2s-complete-call-with-proxy.pcap diff --git a/test/decoders/sip/pcap/10-s2c-complete-dialog.pcap b/test/decoders/sip/pcap/10-s2c-complete-dialog.pcap Binary files differnew file mode 100644 index 0000000..6d2c992 --- /dev/null +++ b/test/decoders/sip/pcap/10-s2c-complete-dialog.pcap diff --git a/test/decoders/sip/pcap/11-s2c-complete-call.pcap b/test/decoders/sip/pcap/11-s2c-complete-call.pcap Binary files differnew file mode 100644 index 0000000..28319a6 --- /dev/null +++ b/test/decoders/sip/pcap/11-s2c-complete-call.pcap 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 Binary files differnew file mode 100644 index 0000000..beecb8a --- /dev/null +++ b/test/decoders/sip/pcap/12-s2c-complete-call-with-proxy.pcap 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; +} |
