/* * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application * Copyright (C) 2005/2006, Anthony Minessale II * * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application * * The Initial Developer of the Original Code is * Anthony Minessale II * Portions created by the Initial Developer are Copyright (C) * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Anthony Minessale II * * * mod_woomerachan.c -- Woomera Endpoint Module * */ #include #define WOOMERA_STRLEN 256 #define WOOMERA_ARRAY_LEN 50 #define WOOMERA_MIN_PORT 9900 #define WOOMERA_MAX_PORT 9999 #define WOOMERA_BODYLEN 2048 #define WOOMERA_LINE_SEPERATOR "\r\n" #define WOOMERA_RECORD_SEPERATOR "\r\n\r\n" #define WOOMERA_DEBUG_PREFIX "**[DEBUG]** " #define WOOMERA_DEBUG_LINE "--------------------------------------------------------------------------------" #define WOOMERA_HARD_TIMEOUT -10000 #define WOOMERA_QLEN 10 #define WOOMERA_RECONNECT_TIME 5000000 #define MEDIA_ANSWER "ANSWER" // THE ONE ABOVE OR THE 2 BELOW BUT NOT BOTH //#define MEDIA_ANSWER "ANSWER" //#define USE_ANSWER 1 static const char modname[] = "mod_woomera"; static switch_memory_pool *module_pool; #define STRLEN 15 #define FRAME_LEN 480 //static int WFORMAT = AST_FORMAT_SLINEAR; typedef enum { WFLAG_EXISTS = (1 << 0), WFLAG_EVENT = (1 << 1), WFLAG_CONTENT = (1 << 2), } WFLAGS; typedef enum { WCFLAG_NOWAIT = (1 << 0) } WCFLAGS; typedef enum { PFLAG_INBOUND = (1 << 0), PFLAG_OUTBOUND = (1 << 1), PFLAG_DYNAMIC = (1 << 2), PFLAG_DISABLED = (1 << 3) } PFLAGS; typedef enum { TFLAG_MEDIA = (1 << 0), TFLAG_INBOUND = (1 << 1), TFLAG_OUTBOUND = (1 << 2), TFLAG_INCOMING = (1 << 3), TFLAG_PARSE_INCOMING = (1 << 4), TFLAG_ACTIVATE = (1 << 5), TFLAG_DTMF = (1 << 6), TFLAG_DESTROY = (1 << 7), TFLAG_ABORT = (1 << 8), TFLAG_SWITCH = (1 << 9), TFLAG_ANSWER = (1 << 10), } TFLAGS; struct woomera_message { char callid[WOOMERA_STRLEN]; int mval; char command[WOOMERA_STRLEN]; char command_args[WOOMERA_STRLEN]; char names[WOOMERA_STRLEN][WOOMERA_ARRAY_LEN]; char values[WOOMERA_STRLEN][WOOMERA_ARRAY_LEN]; char body[WOOMERA_BODYLEN]; unsigned int flags; int last; struct woomera_message *next; }; static struct { int next_woomera_port; int debug; int panic; int rtpmode; } globals; struct woomera_event_queue { struct woomera_message *head; }; struct woomera_profile { char *name; switch_socket_t *woomera_socket; apr_thread_mutex_t *iolock; char woomera_host[WOOMERA_STRLEN]; int woomera_port; char audio_ip[WOOMERA_STRLEN]; char dialplan[WOOMERA_STRLEN]; // pthread_t thread; unsigned int flags; int thread_running; struct woomera_event_queue event_queue; }; struct private_object { char *name; switch_frame frame; switch_codec read_codec; switch_codec write_codec; switch_core_session *session; switch_pollfd_t read_poll; switch_pollfd_t write_poll; switch_pollfd_t command_poll; unsigned char databuf[SWITCH_RECCOMMENDED_BUFFER_SIZE]; switch_mutex_t *iolock; switch_sockaddr_t *udpread; switch_sockaddr_t *udpwrite; switch_socket_t *command_channel; switch_socket_t *udp_socket; unsigned int flags; short fdata[FRAME_LEN]; struct woomera_message call_info; struct woomera_profile *profile; char dest[WOOMERA_STRLEN]; int port; switch_time_t started; int timeout; char dtmfbuf[WOOMERA_STRLEN]; switch_caller_profile *caller_profile; struct woomera_event_queue event_queue; }; typedef struct private_object private_object; typedef struct woomera_message woomera_message; typedef struct woomera_profile woomera_profile; typedef struct woomera_event_queue woomera_event_queue; static woomera_profile default_profile; static const switch_endpoint_interface woomerachan_endpoint_interface; static switch_status woomerachan_on_init(switch_core_session *session); static switch_status woomerachan_on_hangup(switch_core_session *session); static switch_status woomerachan_on_ring(switch_core_session *session); static switch_status woomerachan_on_loopback(switch_core_session *session); static switch_status woomerachan_on_transmit(switch_core_session *session); static switch_status woomerachan_outgoing_channel(switch_core_session *session, switch_caller_profile *outbound_profile, switch_core_session **new_session); static switch_status woomerachan_read_frame(switch_core_session *session, switch_frame **frame, int timeout, switch_io_flag flags, int stream_id); static switch_status woomerachan_write_frame(switch_core_session *session, switch_frame *frame, int timeout, switch_io_flag flags, int stream_id); static switch_status woomerachan_kill_channel(switch_core_session *session, int sig); static void tech_destroy(private_object * tech_pvt); static void woomera_printf(woomera_profile * profile, switch_socket_t *socket, char *fmt, ...); static char *woomera_message_header(woomera_message * wmsg, char *key); static int woomera_enqueue_event(woomera_event_queue * event_queue, woomera_message * wmsg); static int woomera_dequeue_event(woomera_event_queue * event_queue, woomera_message * wmsg); static int woomera_message_parse(switch_socket_t *fd, woomera_message * wmsg, int timeout, woomera_profile * profile, woomera_event_queue * event_queue); static int connect_woomera(switch_socket_t **new_sock, woomera_profile * profile, int flags); static int woomera_profile_thread_running(woomera_profile * profile, int set, int new); static int woomera_locate_socket(woomera_profile * profile, switch_socket_t **woomera_socket); static int tech_create_read_socket(private_object * tech_pvt); static void *woomera_channel_thread_run(switch_thread *thread, void *obj); static void *woomera_thread_run(void *obj); static int tech_activate(private_object * tech_pvt); /* State methods they get called when the state changes to the specific state returning SWITCH_STATUS_SUCCESS tells the core to execute the standard state method next so if you fully implement the state you can return SWITCH_STATUS_FALSE to skip it. */ static switch_status woomerachan_on_init(switch_core_session *session) { switch_channel *channel; struct private_object *tech_pvt = NULL; int rate = 8000; tech_pvt = switch_core_session_get_private(session); assert(tech_pvt != NULL); channel = switch_core_session_get_channel(session); assert(channel != NULL); tech_pvt->frame.data = tech_pvt->databuf; if (switch_core_codec_init (&tech_pvt->read_codec, "L16", rate, 30, 1, SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE, NULL, switch_core_session_get_pool(session)) != SWITCH_STATUS_SUCCESS) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "%s Cannot set read codec\n", switch_channel_get_name(channel)); switch_channel_hangup(channel); return SWITCH_STATUS_FALSE; } if (switch_core_codec_init (&tech_pvt->write_codec, "L16", rate, 30, 1, SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE, NULL, switch_core_session_get_pool(session)) != SWITCH_STATUS_SUCCESS) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "%s Cannot set read codec\n", switch_channel_get_name(channel)); switch_channel_hangup(channel); return SWITCH_STATUS_FALSE; } tech_pvt->frame.rate = rate; tech_pvt->frame.codec = &tech_pvt->read_codec; switch_core_session_set_read_codec(session, &tech_pvt->read_codec); switch_core_session_set_write_codec(session, &tech_pvt->write_codec); switch_set_flag(tech_pvt, TFLAG_ACTIVATE); switch_core_session_launch_thread(session, woomera_channel_thread_run, session); switch_console_printf(SWITCH_CHANNEL_CONSOLE, "%s WOOMERACHAN INIT\n", switch_channel_get_name(channel)); return SWITCH_STATUS_SUCCESS; } static switch_status woomerachan_on_ring(switch_core_session *session) { switch_channel *channel = NULL; struct private_object *tech_pvt = NULL; channel = switch_core_session_get_channel(session); assert(channel != NULL); tech_pvt = switch_core_session_get_private(session); assert(tech_pvt != NULL); switch_console_printf(SWITCH_CHANNEL_CONSOLE, "%s WOOMERACHAN RING\n", switch_channel_get_name(channel)); return SWITCH_STATUS_SUCCESS; } static switch_status woomerachan_on_execute(switch_core_session *session) { switch_channel *channel = NULL; struct private_object *tech_pvt = NULL; channel = switch_core_session_get_channel(session); assert(channel != NULL); tech_pvt = switch_core_session_get_private(session); assert(tech_pvt != NULL); switch_console_printf(SWITCH_CHANNEL_CONSOLE, "%s WOOMERACHAN EXECUTE\n", switch_channel_get_name(channel)); return SWITCH_STATUS_SUCCESS; } static switch_status woomerachan_on_hangup(switch_core_session *session) { switch_channel *channel = NULL; struct private_object *tech_pvt = NULL; channel = switch_core_session_get_channel(session); assert(channel != NULL); tech_pvt = switch_core_session_get_private(session); assert(tech_pvt != NULL); switch_console_printf(SWITCH_CHANNEL_CONSOLE, "%s WOOMERACHAN HANGUP\n", switch_channel_get_name(channel)); tech_destroy(tech_pvt); return SWITCH_STATUS_SUCCESS; } static void woomera_socket_close(switch_socket_t **socket) { if (*socket) { switch_socket_close(*socket); *socket = NULL; } } static void udp_socket_close(struct private_object *tech_pvt) { if (tech_pvt->udp_socket) { apr_socket_shutdown(tech_pvt->udp_socket, APR_SHUTDOWN_READWRITE); woomera_socket_close(&tech_pvt->udp_socket); } } static switch_status woomerachan_kill_channel(switch_core_session *session, int sig) { switch_channel *channel = NULL; struct private_object *tech_pvt = NULL; channel = switch_core_session_get_channel(session); assert(channel != NULL); tech_pvt = switch_core_session_get_private(session); assert(tech_pvt != NULL); if (!tech_pvt->udp_socket) { return SWITCH_STATUS_FALSE; } udp_socket_close(tech_pvt); switch_channel_hangup(channel); switch_console_printf(SWITCH_CHANNEL_CONSOLE, "%s WOOMERACHAN KILL %d\n", switch_channel_get_name(channel), tech_pvt->udp_socket); return SWITCH_STATUS_SUCCESS; } static switch_status woomerachan_on_loopback(switch_core_session *session) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "WOOMERACHAN LOOPBACK\n"); return SWITCH_STATUS_SUCCESS; } static switch_status woomerachan_on_transmit(switch_core_session *session) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "WOOMERACHAN TRANSMIT\n"); return SWITCH_STATUS_SUCCESS; } /* Make sure when you have 2 sessions in the same scope that you pass the appropriate one to the routines that allocate memory or you will have 1 channel with memory allocated from another channel's pool! */ static switch_status woomerachan_outgoing_channel(switch_core_session *session, switch_caller_profile *outbound_profile, switch_core_session **new_session) { if ((*new_session = switch_core_session_request(&woomerachan_endpoint_interface, NULL))) { struct private_object *tech_pvt; switch_channel *channel; switch_core_session_add_stream(*new_session, NULL); if ((tech_pvt = (struct private_object *) switch_core_session_alloc(*new_session, sizeof(struct private_object)))) { memset(tech_pvt, 0, sizeof(*tech_pvt)); tech_pvt->profile = &default_profile; channel = switch_core_session_get_channel(*new_session); switch_core_session_set_private(*new_session, tech_pvt); tech_pvt->session = *new_session; } else { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "Hey where is my memory pool?\n"); switch_core_session_destroy(new_session); return SWITCH_STATUS_GENERR; } if (outbound_profile) { char name[128]; switch_caller_profile *caller_profile; caller_profile = switch_caller_profile_clone(*new_session, outbound_profile); switch_channel_set_caller_profile(channel, caller_profile); tech_pvt->caller_profile = caller_profile; snprintf(name, sizeof(name), "Woomera/%s-%04x", caller_profile->destination_number, rand() & 0xffff); switch_channel_set_name(channel, name); } else { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "Doh! no caller profile\n"); switch_core_session_destroy(new_session); return SWITCH_STATUS_GENERR; } switch_channel_set_flag(channel, CF_OUTBOUND); switch_set_flag(tech_pvt, TFLAG_OUTBOUND); switch_channel_set_state(channel, CS_INIT); return SWITCH_STATUS_SUCCESS; } return SWITCH_STATUS_GENERR; } static switch_status woomerachan_waitfor_read(switch_core_session *session, int ms, int stream_id) { struct private_object *tech_pvt = NULL; tech_pvt = switch_core_session_get_private(session); assert(tech_pvt != NULL); return switch_socket_waitfor(&tech_pvt->read_poll, ms); } static switch_status woomerachan_waitfor_write(switch_core_session *session, int ms, int stream_id) { struct private_object *tech_pvt = NULL; tech_pvt = switch_core_session_get_private(session); assert(tech_pvt != NULL); return SWITCH_STATUS_SUCCESS; return switch_socket_waitfor(&tech_pvt->write_poll, ms); } static switch_status woomerachan_read_frame(switch_core_session *session, switch_frame **frame, int timeout, switch_io_flag flags, int stream_id) { switch_channel *channel = NULL; struct private_object *tech_pvt = NULL; switch_frame *pframe; switch_status status; channel = switch_core_session_get_channel(session); assert(channel != NULL); tech_pvt = switch_core_session_get_private(session); assert(tech_pvt != NULL); if (!tech_pvt->udp_socket) { return SWITCH_STATUS_GENERR; } /* if ((status = woomerachan_waitfor_read(session, -1)) != SWITCH_STATUS_SUCCESS) { return status; }1< */ pframe = &tech_pvt->frame; *frame = pframe; pframe->datalen = sizeof(tech_pvt->databuf); if ((status = switch_socket_recvfrom(tech_pvt->udpread, tech_pvt->udp_socket, 0, tech_pvt->databuf, &pframe->datalen)) == SWITCH_STATUS_SUCCESS) { pframe->samples = (int) pframe->datalen / 2; } return status; } static switch_status woomerachan_write_frame(switch_core_session *session, switch_frame *frame, int timeout, switch_io_flag flags, int stream_id) { switch_channel *channel = NULL; struct private_object *tech_pvt = NULL; switch_frame *pframe; channel = switch_core_session_get_channel(session); assert(channel != NULL); tech_pvt = switch_core_session_get_private(session); assert(tech_pvt != NULL); if (!tech_pvt->udp_socket) { return SWITCH_STATUS_GENERR; } pframe = &tech_pvt->frame; return switch_socket_sendto(tech_pvt->udp_socket, tech_pvt->udpwrite, 0, frame->data, &frame->datalen); } static const switch_event_handler_table woomerachan_event_handlers = { /*.on_init */ woomerachan_on_init, /*.on_ring */ woomerachan_on_ring, /*.on_execute */ woomerachan_on_execute, /*.on_hangup */ woomerachan_on_hangup, /*.on_loopback */ woomerachan_on_loopback, /*.on_transmit */ woomerachan_on_transmit }; static const switch_io_routines woomerachan_io_routines = { /*.outgoing_channel */ woomerachan_outgoing_channel, /*.answer_channel */ NULL, /*.read_frame */ woomerachan_read_frame, /*.write_frame */ woomerachan_write_frame, /*.kill_channel */ woomerachan_kill_channel, /*.waitfor_read */ woomerachan_waitfor_read, /*.waitfor_write */ woomerachan_waitfor_write }; static const switch_endpoint_interface woomerachan_endpoint_interface = { /*.interface_name */ "woomera", /*.io_routines */ &woomerachan_io_routines, /*.event_handlers */ &woomerachan_event_handlers, /*.private */ NULL, /*.next */ NULL }; static const switch_loadable_module_interface woomerachan_module_interface = { /*.module_name */ modname, /*.endpoint_interface */ &woomerachan_endpoint_interface, /*.timer_interface */ NULL, /*.dialplan_interface */ NULL, /*.codec_interface */ NULL, /*.application_interface */ NULL }; static void tech_destroy(private_object * tech_pvt) { woomera_message wmsg; if (globals.debug > 1) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, WOOMERA_DEBUG_PREFIX "+++DESTROY\n"); } woomera_printf(tech_pvt->profile, tech_pvt->command_channel, "hangup %s%s", tech_pvt->call_info.callid, WOOMERA_RECORD_SEPERATOR); if (woomera_message_parse (tech_pvt->command_channel, &wmsg, WOOMERA_HARD_TIMEOUT, tech_pvt->profile, &tech_pvt->event_queue) < 0) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "{%s} Already Disconnected\n", tech_pvt->profile->name); } woomera_printf(tech_pvt->profile, tech_pvt->command_channel, "bye%s", WOOMERA_RECORD_SEPERATOR); woomera_socket_close(&tech_pvt->command_channel); udp_socket_close(tech_pvt); } static void woomera_printf(woomera_profile * profile, switch_socket_t *socket, char *fmt, ...) { char *stuff; size_t res = 0, len = 0; va_list ap; va_start(ap, fmt); #ifndef vasprintf stuff = (char *) malloc(10240); vsnprintf(stuff, 10240, fmt, ap); #else res = vasprintf(&stuff, fmt, ap); #endif va_end(ap); if (res == -1) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "Out of memory\n"); } else { if (profile && globals.debug) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "Send Message: {%s} [%s/%d]\n%s\n%s", profile->name, profile->woomera_host, profile->woomera_port, WOOMERA_DEBUG_LINE, stuff); } len = strlen(stuff); switch_socket_send(socket, stuff, &len); free(stuff); } } static char *woomera_message_header(woomera_message * wmsg, char *key) { int x = 0; char *value = NULL; for (x = 0; x < wmsg->last; x++) { if (!strcasecmp(wmsg->names[x], key)) { value = wmsg->values[x]; break; } } return value; } static int woomera_enqueue_event(woomera_event_queue * event_queue, woomera_message * wmsg) { woomera_message *new, *mptr; if ((new = malloc(sizeof(woomera_message)))) { memcpy(new, wmsg, sizeof(woomera_message)); new->next = NULL; if (!event_queue->head) { event_queue->head = new; } else { for (mptr = event_queue->head; mptr && mptr->next; mptr = mptr->next); mptr->next = new; } return 1; } else { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "Memory Allocation Error!\n"); } return 0; } static int woomera_dequeue_event(woomera_event_queue * event_queue, woomera_message * wmsg) { woomera_message *mptr = NULL; if (event_queue->head) { mptr = event_queue->head; event_queue->head = mptr->next; } if (mptr) { memcpy(wmsg, mptr, sizeof(woomera_message)); free(mptr); return 1; } else { memset(wmsg, 0, sizeof(woomera_message)); } return 0; } static int woomera_message_parse(switch_socket_t *fd, woomera_message * wmsg, int timeout, woomera_profile * profile, woomera_event_queue * event_queue) { char *cur, *cr, *next = NULL, *eor = NULL; char buf[2048] = "", *ptr; int bytes = 0; int failto = 0; memset(wmsg, 0, sizeof(woomera_message)); if (fd < 0) { return -1; } if (timeout < 0) { timeout = abs(timeout); failto = 1; } else if (timeout == 0) { timeout = -1; } ptr = buf; bytes = 0; while (!(eor = strstr(buf, WOOMERA_RECORD_SEPERATOR))) { size_t len = 1; if (!profile->thread_running) { return -1; } if (switch_socket_recv(fd, ptr, &len) != SWITCH_STATUS_SUCCESS) { return -1; } ptr++; bytes++; } //*eor = '\0'; next = buf; if (globals.debug) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "Receive Message: {%s} [%s/%d]\n%s\n%s", profile->name, profile->woomera_host, profile->woomera_port, WOOMERA_DEBUG_LINE, buf); } while ((cur = next)) { if ((cr = strstr(cur, WOOMERA_LINE_SEPERATOR))) { *cr = '\0'; next = cr + (sizeof(WOOMERA_LINE_SEPERATOR) - 1); if (!strcmp(next, WOOMERA_RECORD_SEPERATOR)) { break; } } if (!cur || !cur[0]) { break; } if (!wmsg->last) { switch_set_flag(wmsg, WFLAG_EXISTS); if (!strncasecmp(cur, "EVENT", 5)) { cur += 6; switch_set_flag(wmsg, WFLAG_EVENT); if (cur && (cr = strchr(cur, ' '))) { char *id; *cr = '\0'; cr++; id = cr; if (cr && (cr = strchr(cr, ' '))) { *cr = '\0'; cr++; strncpy(wmsg->command_args, cr, WOOMERA_STRLEN); } if (id) { strncpy(wmsg->callid, id, sizeof(wmsg->callid) - 1); } } } else { if (cur && (cur = strchr(cur, ' '))) { *cur = '\0'; cur++; wmsg->mval = atoi(buf); } else { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "Malformed Message!\n"); break; } } if (cur) { strncpy(wmsg->command, cur, WOOMERA_STRLEN); } else { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "Malformed Message!\n"); break; } } else { char *name, *val; name = cur; if ((val = strchr(name, ':'))) { *val = '\0'; val++; while (*val == ' ') { *val = '\0'; val++; } strncpy(wmsg->values[wmsg->last - 1], val, WOOMERA_STRLEN); } strncpy(wmsg->names[wmsg->last - 1], name, WOOMERA_STRLEN); if (name && val && !strcasecmp(name, "content-type")) { switch_set_flag(wmsg, WFLAG_CONTENT); bytes = atoi(val); } } wmsg->last++; } wmsg->last--; if (bytes && switch_test_flag(wmsg, WFLAG_CONTENT)) { size_t len = (bytes > sizeof(wmsg->body)) ? sizeof(wmsg->body) : bytes; switch_socket_recv(fd, wmsg->body, &len); if (globals.debug) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "%s\n", wmsg->body); } } if (event_queue && switch_test_flag(wmsg, WFLAG_EVENT)) { if (globals.debug) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "Queue Event: {%s} [%s]\n", profile->name, wmsg->command); } /* we don't want events we want a reply so we will stash them for later */ woomera_enqueue_event(event_queue, wmsg); /* call ourself recursively to find the reply. we'll keep doing this as long we get events. * wmsg will be overwritten but it's ok we just queued it. */ return woomera_message_parse(fd, wmsg, timeout, profile, event_queue); } else if (wmsg->mval > 99 && wmsg->mval < 200) { /* reply in the 100's are nice but we need to wait for another reply call ourself recursively to find the reply > 199 and forget this reply. */ return woomera_message_parse(fd, wmsg, timeout, profile, event_queue); } else { return switch_test_flag(wmsg, WFLAG_EXISTS); } } static int connect_woomera(switch_socket_t **new_sock, woomera_profile * profile, int flags) { switch_sockaddr_t *sa; switch_status status; status = switch_sockaddr_info_get(&sa, profile->woomera_host, AF_INET, profile->woomera_port, 0, module_pool); if (status != SWITCH_STATUS_SUCCESS) { return -1; } status = switch_socket_create(new_sock, AF_INET, SOCK_STREAM, 0, module_pool); if (status != SWITCH_STATUS_SUCCESS) { return -1; } /* status = switch_socket_bind((*new_sock), sa); if (0 && status != SWITCH_STATUS_SUCCESS) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "Can't Bind to %s:%d!\n", profile->woomera_host, profile->woomera_port); return -1; } */ status = switch_socket_connect((*new_sock), sa); if (status != SWITCH_STATUS_SUCCESS) { return -1; } return 1; } static int woomera_profile_thread_running(woomera_profile * profile, int set, int new) { int running = 0; switch_mutex_lock(profile->iolock); if (set) { profile->thread_running = new; } running = profile->thread_running; switch_mutex_unlock(profile->iolock); return running; } static int woomera_locate_socket(woomera_profile * profile, switch_socket_t **woomera_socket) { woomera_message wmsg; for (;;) { while (connect_woomera(woomera_socket, profile, 0) < 0) { if (!woomera_profile_thread_running(profile, 0, 0)) { break; } switch_console_printf(SWITCH_CHANNEL_CONSOLE, "{%s} Cannot Reconnect to Woomera! retry in 5 seconds\n", profile->name); switch_sleep(WOOMERA_RECONNECT_TIME); } if (*woomera_socket) { if (switch_test_flag(profile, PFLAG_INBOUND)) { woomera_printf(profile, *woomera_socket, "LISTEN%s", WOOMERA_RECORD_SEPERATOR); if (woomera_message_parse(*woomera_socket, &wmsg, WOOMERA_HARD_TIMEOUT, profile, &profile->event_queue) < 0) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "{%s} HELP! Woomera is broken!\n", profile->name); globals.panic = 1; woomera_profile_thread_running(&default_profile, 1, 0); switch_sleep(WOOMERA_RECONNECT_TIME); if (*woomera_socket) { woomera_socket_close(woomera_socket); } continue; } } } switch_sleep(100); break; } return *woomera_socket ? 1 : 0; } static int tech_create_read_socket(private_object * tech_pvt) { switch_memory_pool *pool = switch_core_session_get_pool(tech_pvt->session); if ((tech_pvt->port = globals.next_woomera_port++) >= WOOMERA_MAX_PORT) { tech_pvt->port = globals.next_woomera_port = WOOMERA_MIN_PORT; } switch_console_printf(SWITCH_CHANNEL_CONSOLE, "connect %s:%d\n", tech_pvt->profile->audio_ip, tech_pvt->port); //tech_pvt->udp_socket = create_udp_socket(tech_pvt->profile->audio_ip, tech_pvt->port, &tech_pvt->udpread, 0); switch_sockaddr_info_get(&tech_pvt->udpread, tech_pvt->profile->audio_ip, SWITCH_UNSPEC, tech_pvt->port, 0, pool); if (switch_socket_create(&tech_pvt->udp_socket, AF_INET, SOCK_DGRAM, 0, pool) == SWITCH_STATUS_SUCCESS) { switch_socket_bind(tech_pvt->udp_socket, tech_pvt->udpread); switch_socket_create_pollfd(&tech_pvt->read_poll, tech_pvt->udp_socket, SWITCH_POLLIN | SWITCH_POLLERR, pool); switch_socket_create_pollfd(&tech_pvt->write_poll, tech_pvt->udp_socket, SWITCH_POLLOUT | SWITCH_POLLERR, pool); } return 0; } static int tech_activate(private_object * tech_pvt) { woomera_message wmsg; if (tech_pvt) { if ((connect_woomera(&tech_pvt->command_channel, tech_pvt->profile, 0))) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "connected to woomera!\n"); } else { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "Can't connect to woomera!\n"); switch_sleep(WOOMERA_RECONNECT_TIME); return -1; } if (switch_test_flag(tech_pvt, TFLAG_OUTBOUND)) { woomera_printf(tech_pvt->profile, tech_pvt->command_channel, "CALL %s%sRaw-Audio: %s/%d%sLocal-Name: %s!%s%s", tech_pvt->caller_profile->destination_number, WOOMERA_LINE_SEPERATOR, tech_pvt->profile->audio_ip, tech_pvt->port, WOOMERA_LINE_SEPERATOR, tech_pvt->caller_profile->caller_id_name, tech_pvt->caller_profile->caller_id_number, WOOMERA_RECORD_SEPERATOR); woomera_message_parse(tech_pvt->command_channel, &wmsg, WOOMERA_HARD_TIMEOUT, tech_pvt->profile, &tech_pvt->event_queue); } else { switch_set_flag(tech_pvt, TFLAG_PARSE_INCOMING); woomera_printf(tech_pvt->profile, tech_pvt->command_channel, "LISTEN%s", WOOMERA_RECORD_SEPERATOR); if (woomera_message_parse(tech_pvt->command_channel, &wmsg, WOOMERA_HARD_TIMEOUT, tech_pvt->profile, &tech_pvt->event_queue) < 0) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "{%s} HELP! Woomera is broken!\n", tech_pvt->profile->name); switch_set_flag(tech_pvt, TFLAG_ABORT); globals.panic = 1; } } } else { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "Where's my tech_pvt?\n"); } return 0; } static void *woomera_channel_thread_run(switch_thread *thread, void *obj) { switch_core_session *session = obj; switch_channel *channel = NULL; struct private_object *tech_pvt = NULL; woomera_message wmsg; int res = 0; assert(session != NULL); channel = switch_core_session_get_channel(session); assert(channel != NULL); tech_pvt = switch_core_session_get_private(session); assert(tech_pvt != NULL); if (!tech_pvt->udp_socket) { tech_create_read_socket(tech_pvt); } for (;;) { if (globals.panic) { switch_set_flag(tech_pvt, TFLAG_ABORT); } if (switch_test_flag(tech_pvt, TFLAG_ABORT)) { if (switch_channel_get_state(channel) < CS_HANGUP) { switch_channel_set_state(channel, CS_HANGUP); } udp_socket_close(tech_pvt); break; } if (switch_test_flag(tech_pvt, TFLAG_ACTIVATE)) { switch_clear_flag(tech_pvt, TFLAG_ACTIVATE); tech_activate(tech_pvt); } if (switch_test_flag(tech_pvt, TFLAG_ANSWER)) { switch_clear_flag(tech_pvt, TFLAG_ANSWER); #ifdef USE_ANSWER woomera_printf(tech_pvt->profile, tech_pvt->command_channel, "ANSWER %s%s", tech_pvt->call_info.callid, WOOMERA_RECORD_SEPERATOR); if (woomera_message_parse (tech_pvt->command_channel, &wmsg, WOOMERA_HARD_TIMEOUT, tech_pvt->profile, &tech_pvt->event_queue) < 0) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "{%s} HELP! Woomera is broken!\n", tech_pvt->profile->name); switch_set_flag(tech_pvt, TFLAG_ABORT); globals.panic = 1; continue; } #endif } if (switch_test_flag(tech_pvt, TFLAG_DTMF)) { switch_mutex_lock(tech_pvt->iolock); woomera_printf(tech_pvt->profile, tech_pvt->command_channel, "DTMF %s %s%s", tech_pvt->call_info.callid, tech_pvt->dtmfbuf, WOOMERA_RECORD_SEPERATOR); if (woomera_message_parse (tech_pvt->command_channel, &wmsg, WOOMERA_HARD_TIMEOUT, tech_pvt->profile, &tech_pvt->event_queue) < 0) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "{%s} HELP! Woomera is broken!\n", tech_pvt->profile->name); switch_set_flag(tech_pvt, TFLAG_ABORT); globals.panic = 1; continue; } switch_clear_flag(tech_pvt, TFLAG_DTMF); memset(tech_pvt->dtmfbuf, 0, sizeof(tech_pvt->dtmfbuf)); switch_mutex_unlock(tech_pvt->iolock); } #if 1==0 /*convert to use switch_time_now */ if (tech_pvt->timeout) { struct timeval now; int elapsed; gettimeofday(&now, NULL); elapsed = (((now.tv_sec * 1000) + now.tv_usec / 1000) - ((tech_pvt->started.tv_sec * 1000) + tech_pvt->started.tv_usec / 1000)); if (elapsed > tech_pvt->timeout) { /* call timed out! */ switch_set_flag(tech_pvt, TFLAG_ABORT); } } #endif if (!tech_pvt->command_channel) { break; } /* Check for events */ if ((res = woomera_dequeue_event(&tech_pvt->event_queue, &wmsg)) || (res = woomera_message_parse(tech_pvt->command_channel, &wmsg, 100, tech_pvt->profile, NULL))) { if (res < 0 || !strcasecmp(wmsg.command, "HANGUP")) { switch_set_flag(tech_pvt, TFLAG_ABORT); continue; } else if (!strcasecmp(wmsg.command, "DTMF")) { /* struct ast_frame dtmf_frame = {AST_FRAME_DTMF}; int x = 0; for (x = 0; x < strlen(wmsg.command_args); x++) { dtmf_frame.subclass = wmsg.command_args[x]; ast_queue_frame(tech_pvt->owner, ast_frdup(&dtmf_frame)); if (globals.debug > 1) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, WOOMERA_DEBUG_PREFIX "SEND DTMF [%c] to %s\n", dtmf_frame.subclass, tech_pvt->owner->name); } } */ } else if (!strcasecmp(wmsg.command, "PROCEED")) { /* This packet has lots of info so well keep it */ tech_pvt->call_info = wmsg; } else if (switch_test_flag(tech_pvt, TFLAG_PARSE_INCOMING) && !strcasecmp(wmsg.command, "INCOMING")) { char *exten; char cid_name[512]; char *cid_num; char *ip; char *p; switch_clear_flag(tech_pvt, TFLAG_PARSE_INCOMING); switch_set_flag(tech_pvt, TFLAG_INCOMING); tech_pvt->call_info = wmsg; exten = woomera_message_header(&wmsg, "Local-Number"); if (switch_strlen_zero(exten)) { exten = "s"; } if ((p = woomera_message_header(&wmsg, "Remote-Name"))) { strncpy(cid_name, p, sizeof(cid_name)); } if ((cid_num = strchr(cid_name, '!'))) { *cid_num = '\0'; cid_num++; } else { cid_num = woomera_message_header(&wmsg, "Remote-Number"); } ip = woomera_message_header(&wmsg, "Remote-Address"); if ((tech_pvt->caller_profile = switch_caller_profile_new(session, tech_pvt->profile->dialplan, cid_name, cid_num, ip, NULL, NULL, exten))) { char name[128]; switch_channel_set_caller_profile(channel, tech_pvt->caller_profile); snprintf(name, sizeof(name), "Woomera/%s-%04x", tech_pvt->caller_profile->destination_number, rand() & 0xffff); switch_channel_set_name(channel, name); } woomera_printf(tech_pvt->profile, tech_pvt->command_channel, "%s %s%s" "Raw-Audio: %s/%d%s", MEDIA_ANSWER, wmsg.callid, WOOMERA_LINE_SEPERATOR, tech_pvt->profile->audio_ip, tech_pvt->port, WOOMERA_RECORD_SEPERATOR); if (woomera_message_parse(tech_pvt->command_channel, &wmsg, WOOMERA_HARD_TIMEOUT, tech_pvt->profile, &tech_pvt->event_queue) < 0) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "{%s} HELP! Woomera is broken!\n", tech_pvt->profile->name); switch_set_flag(tech_pvt, TFLAG_ABORT); globals.panic = 1; continue; } } else if (!strcasecmp(wmsg.command, "CONNECT")) { } else if (!strcasecmp(wmsg.command, "MEDIA")) { char *raw_audio_header; if ((raw_audio_header = woomera_message_header(&wmsg, "Raw-Audio"))) { char ip[25]; char *ptr; int port = 0; strncpy(ip, raw_audio_header, sizeof(ip) - 1); if ((ptr = strchr(ip, '/'))) { *ptr = '\0'; ptr++; port = atoi(ptr); } /* Move Channel's State Machine to RING */ switch_channel_answer(channel); switch_channel_set_state(channel, CS_RING); if (switch_sockaddr_info_get(&tech_pvt->udpwrite, ip, SWITCH_UNSPEC, port, 0, switch_core_session_get_pool(tech_pvt->session)) != SWITCH_STATUS_SUCCESS) { if (globals.debug) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, WOOMERA_DEBUG_PREFIX "{%s} Cannot resolve %s\n", tech_pvt->profile->name, ip); } switch_channel_hangup(channel); } } } } if (globals.debug > 2) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, WOOMERA_DEBUG_PREFIX "CHECK {%s}(%d)\n", tech_pvt->profile->name, res); } } if (globals.debug > 1) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, WOOMERA_DEBUG_PREFIX "Monitor thread for %s done.\n", tech_pvt->profile->name); } return NULL; } static void *woomera_thread_run(void *obj) { int res = 0; woomera_message wmsg; woomera_profile *profile; profile = obj; switch_console_printf(SWITCH_CHANNEL_CONSOLE, "Started Woomera Thread {%s}.\n", profile->name); profile->thread_running = 1; profile->woomera_socket = NULL; while (woomera_profile_thread_running(profile, 0, 0)) { /* listen on socket and handle events */ if (globals.panic == 2) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "Woomera is disabled!\n"); switch_sleep(WOOMERA_RECONNECT_TIME); continue; } if (!profile->woomera_socket) { if (woomera_locate_socket(profile, &profile->woomera_socket)) { globals.panic = 0; } if (!woomera_profile_thread_running(profile, 0, 0)) { break; } switch_console_printf(SWITCH_CHANNEL_CONSOLE, "Woomera Thread Up {%s} %s/%d\n", profile->name, profile->woomera_host, profile->woomera_port); } if (globals.panic) { if (globals.panic != 2) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "Help I'm in a state of panic!\n"); globals.panic = 0; //fix } woomera_socket_close(&profile->woomera_socket); continue; } if ((res = woomera_dequeue_event(&profile->event_queue, &wmsg) || (res = woomera_message_parse(profile->woomera_socket, &wmsg, /* if we are not stingy with threads we can block forever */ 0, profile, NULL)))) { if (res < 0) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "{%s} HELP! I lost my connection to woomera!\n", profile->name); woomera_socket_close(&profile->woomera_socket); //global_set_flag(TFLAG_ABORT); globals.panic = 1; continue; if (profile->woomera_socket) { if (switch_test_flag(profile, PFLAG_INBOUND)) { woomera_printf(profile, profile->woomera_socket, "LISTEN%s", WOOMERA_RECORD_SEPERATOR); if (woomera_message_parse(profile->woomera_socket, &wmsg, WOOMERA_HARD_TIMEOUT, profile, &profile->event_queue) < 0) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "{%s} HELP! Woomera is broken!\n", profile->name); globals.panic = 1; woomera_socket_close(&profile->woomera_socket); } } if (profile->woomera_socket) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "Woomera Thread Up {%s} %s/%d\n", profile->name, profile->woomera_host, profile->woomera_port); } } continue; } if (!strcasecmp(wmsg.command, "INCOMING")) { char *name; switch_core_session *session; if (!(name = woomera_message_header(&wmsg, "Remote-Address"))) { name = woomera_message_header(&wmsg, "Channel-Name"); } switch_console_printf(SWITCH_CHANNEL_CONSOLE, "New Inbound Channel %s!\n", name); if ((session = switch_core_session_request(&woomerachan_endpoint_interface, NULL))) { struct private_object *tech_pvt; switch_channel *channel; switch_core_session_add_stream(session, NULL); if ((tech_pvt = (struct private_object *) switch_core_session_alloc(session, sizeof(struct private_object)))) { memset(tech_pvt, 0, sizeof(*tech_pvt)); tech_pvt->profile = &default_profile; channel = switch_core_session_get_channel(session); switch_core_session_set_private(session, tech_pvt); tech_pvt->session = session; } else { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "Hey where is my memory pool?\n"); switch_core_session_destroy(&session); break; } switch_channel_set_state(channel, CS_INIT); switch_core_session_thread_launch(session); } } } if (globals.debug > 2) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "Main Thread {%s} Select Return %d\n", profile->name, res); } switch_yield(100); } if (profile->woomera_socket) { woomera_printf(profile, profile->woomera_socket, "BYE%s", WOOMERA_RECORD_SEPERATOR); woomera_socket_close(&profile->woomera_socket); } switch_console_printf(SWITCH_CHANNEL_CONSOLE, "Ended Woomera Thread {%s}.\n", profile->name); woomera_profile_thread_running(profile, 1, -1); return NULL; } SWITCH_MOD_DECLARE(switch_status) switch_module_runtime(void) { woomera_thread_run(&default_profile); return SWITCH_STATUS_TERM; } SWITCH_MOD_DECLARE(switch_status) switch_module_shutdown(void) { int x = 0; woomera_profile_thread_running(&default_profile, 1, 0); while (!woomera_profile_thread_running(&default_profile, 0, 0)) { woomera_socket_close(&default_profile.woomera_socket); if (x++ > 10) { break; } switch_yield(1); } return SWITCH_STATUS_SUCCESS; } SWITCH_MOD_DECLARE(switch_status) switch_module_load(const switch_loadable_module_interface **interface, char *filename) { switch_config cfg; char *var, *val; struct woomera_profile *profile = &default_profile; char *cf = "woomera.conf"; memset(&globals, 0, sizeof(globals)); globals.next_woomera_port = WOOMERA_MIN_PORT; if (!switch_config_open_file(&cfg, cf)) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "open of %s failed\n", cf); return SWITCH_STATUS_TERM; } switch_set_flag(profile, PFLAG_INBOUND | PFLAG_OUTBOUND); profile->name = "main"; strncpy(profile->dialplan, "default", sizeof(profile->dialplan) - 1); while (switch_config_next_pair(&cfg, &var, &val)) { if (!strcasecmp(cfg.category, "settings")) { if (!strcmp(var, "noload") && atoi(val)) { return SWITCH_STATUS_TERM; } if (!strcmp(var, "debug")) { globals.debug = atoi(val); } } else if (!strcasecmp(cfg.category, "profile")) { if (!strcmp(var, "audio_ip")) { strncpy(profile->audio_ip, val, sizeof(profile->audio_ip) - 1); } else if (!strcmp(var, "host")) { strncpy(profile->woomera_host, val, sizeof(profile->woomera_host) - 1); } else if (!strcmp(var, "port")) { profile->woomera_port = atoi(val); } else if (!strcmp(var, "disabled")) { if (atoi(val) > 0) { switch_set_flag(profile, PFLAG_DISABLED); } } else if (!strcmp(var, "inbound")) { if (atoi(val) < 1) { switch_clear_flag(profile, PFLAG_INBOUND); } } else if (!strcmp(var, "outbound")) { if (atoi(val) < 1) { switch_clear_flag(profile, PFLAG_OUTBOUND); } } else if (!strcmp(var, "dialplan")) { strncpy(profile->dialplan, val, sizeof(profile->dialplan) - 1); } } } switch_config_close_file(&cfg); if (switch_core_new_memory_pool(&module_pool) != SWITCH_STATUS_SUCCESS) { //switch_console_printf(SWITCH_CHANNEL_CONSOLE, "OH OH no pool\n"); if (switch_core_new_memory_pool(&module_pool) != SWITCH_STATUS_SUCCESS) { //switch_console_printf(SWITCH_CHANNEL_CONSOLE, "OH OH no pool\n"); return SWITCH_STATUS_MEMERR; } return SWITCH_STATUS_MEMERR; } if (switch_mutex_init(&default_profile.iolock, SWITCH_MUTEX_NESTED, module_pool) != SWITCH_STATUS_SUCCESS) { switch_console_printf(SWITCH_CHANNEL_CONSOLE, "OH OH no lock\n"); return SWITCH_STATUS_TERM; } /* connect my internal structure to the blank pointer passed to me */ *interface = &woomerachan_module_interface; /* indicate that the module should continue to be loaded */ return SWITCH_STATUS_SUCCESS; }