diff --git a/channels/Makefile b/channels/Makefile index b641173fe85304fb7c7388b3eed6a3ccb1b7d51e..5558ee546a0e6dcc08a22a22caffa3a11fa563be 100644 --- a/channels/Makefile +++ b/channels/Makefile @@ -36,6 +36,7 @@ chan_mgcp.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION) chan_unistim.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION) chan_phone.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION) chan_sip.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION) +chan_brcm.so: LIBS+=-lubus -lubox -lpicoevent chan_misdn.o: _ASTCFLAGS+=-Imisdn misdn_config.o: _ASTCFLAGS+=-Imisdn diff --git a/channels/chan_brcm.c b/channels/chan_brcm.c new file mode 100644 index 0000000000000000000000000000000000000000..4d2f615a3e7c0d036c3a6633f2e299fbdb0e9e73 --- /dev/null +++ b/channels/chan_brcm.c @@ -0,0 +1,3822 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * Mark Spencer <markster@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \author Benjamin Larsson <benjamin@southpole.se> + * \author Jonas Höglund <jonash@southpole.se> + * + * \ingroup channel_drivers + */ + +// #define BRCM_LOCK_DEBUG /* If defined we will log lock events to the asterisk debug channel */ + +/* TODO: + * Prefered codec order mulaw/alaw/g729/g723.1/g726_24/g726_32 + * Enable T38 support + * Enable V18 support + */ + + + +#include "asterisk.h" + +#include <math.h> +#include <ctype.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <arpa/inet.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <signal.h> +#include <semaphore.h> +#include <sys/types.h> +#include <sys/stat.h> // mkfifo() + +#include "asterisk/lock.h" +#include "asterisk/channel.h" +#include "asterisk/options.h" +#include "asterisk/cli.h" +#include "asterisk/config.h" +#include "asterisk/module.h" +#include "asterisk/pbx.h" +#include "asterisk/utils.h" +#include "asterisk/callerid.h" +#include "asterisk/causes.h" +#include "asterisk/stringfields.h" +#include "asterisk/musiconhold.h" +#include "asterisk/indications.h" +#include "asterisk/sched.h" +#include "asterisk/app.h" +#include "asterisk/format_cap.h" +#include "asterisk/format_compatibility.h" +#include "asterisk/format_cache.h" +#include "asterisk/logger.h" +#include "asterisk/bridge.h" + +#include "asterisk/stasis_system.h" +#include "asterisk/stasis_channels.h" + +#include <libubus.h> +#include <libpicoevent.h> + +#include "chan_brcm.h" + +#ifndef AST_MODULE +#define AST_MODULE "chan_brcm" +#endif + +static void brcm_dialtone_set(struct brcm_pvt *p, dialtone_state state); +static int brcm_signal_dialtone(struct brcm_pvt *p); +static int brcm_in_conference(const struct brcm_pvt *p); +static int cwtimeout_cb(const void *data); +static int r4hanguptimeout_cb(const void *data); +static void brcm_generate_rtp_packet(struct brcm_subchannel *p, uint8_t *packet_buf, int type, int marker, int dtmf_timestamp); +static int brcm_mute_connection(struct brcm_subchannel *p); +static int brcm_unmute_connection(struct brcm_subchannel *p); +static int brcm_close_connection(struct brcm_subchannel *p); +static int brcm_create_conference(struct brcm_pvt *p); +static int brcm_stop_conference(struct brcm_subchannel *p); +static int brcm_finish_transfer(struct ast_channel *owner, struct brcm_subchannel *p, int result); +static int brcm_call(struct ast_channel *ast, const char *dest, int timeout); +static int brcm_hangup(struct ast_channel *ast); +static int brcm_answer(struct ast_channel *ast); +static struct ast_frame *brcm_read(struct ast_channel *ast); +static int brcm_write(struct ast_channel *ast, struct ast_frame *frame); +static int brcm_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen); +static int brcm_transfer(struct ast_channel *ast, const char *newdest); +static int brcm_senddigit_begin(struct ast_channel *ast, char digit); +static int brcm_senddigit_end(struct ast_channel *ast, char digit, unsigned int duration); +static int brcm_in_call(const struct brcm_pvt *p); +static int brcm_in_callwaiting(const struct brcm_pvt *p); +static int brcm_in_onhold(const struct brcm_pvt *p); +static int brcm_subchannel_is_idle(const struct brcm_subchannel *sub); +static struct ast_channel *brcm_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *dest, int *cause); +static void brcm_lock_pvts(void); +static void brcm_unlock_pvts(void); +static struct brcm_pvt* brcm_get_next_pvt(struct brcm_pvt *p); +static void *pe_base_run(void *unused); +static int brcm_create_connection(struct brcm_subchannel *p); +static int brcm_signal_congestion(struct brcm_pvt *p); +static int brcm_stop_dialtone(struct brcm_pvt *p); +static int brcm_signal_ringing(struct brcm_pvt *p); +static int brcm_stop_ringing(struct brcm_pvt *p); +static int brcm_signal_ringing_callerid_pending(struct brcm_pvt *p); +static int brcm_stop_ringing_callerid_pending(struct brcm_pvt *p); +static int brcm_signal_callwaiting(const struct brcm_pvt *p); +static int brcm_stop_callwaiting(const struct brcm_pvt *p); +static int brcm_signal_callerid(struct ast_channel *chan, struct brcm_subchannel *sub); +static struct brcm_subchannel *brcm_get_idle_subchannel(const struct brcm_pvt *p); +static struct brcm_subchannel* brcm_get_active_subchannel(const struct brcm_pvt *p); +static struct brcm_subchannel *brcm_subchannel_get_peer(const struct brcm_subchannel *sub); +static struct brcm_pvt* brcm_get_pvt_from_lineid(struct brcm_pvt *p, int line_id); +static void handle_dtmf_calling(struct brcm_subchannel *sub); +static void brcm_cancel_dialing_timeouts(struct brcm_pvt *p); +static int brcm_should_relay_dtmf(const struct brcm_subchannel *sub); +static struct ast_channel *brcm_new(struct brcm_subchannel *subchan, int state, const char *ext, const char *context, + const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, + struct ast_format_cap *format); + +/* Global brcm channel parameters */ +static const char tdesc[] = "Broadcom SLIC Driver"; +static const char config[] = "chan_telephony.conf"; + +static line_settings line_config[MAX_NUM_LINEID]; +static int current_connection_id = 0; +static int num_fxs_endpoints = -1; +static int num_fxo_endpoints = -1; +static int num_dect_endpoints = -1; +static int num_endpoints = -1; +static int clip = 1; // Caller ID presentation +/* driver scheduler */ +static struct ast_sched_context *sched = NULL; + +/* Call waiting */ +static int cwtimeout = DEFAULT_CALL_WAITING_TIMEOUT; + +/* R4 transfer */ +static int r4hanguptimeout = DEFAULT_R4_HANGUP_TIMEOUT; + +/* Automatic call on hold hangup */ +static int onholdhanguptimeout = DEFAULT_ONHOLD_HANGUP_TIMEOUT; + +/* Boolean, controls whether the transferor puts the transfer target on-hold before sending + * REFER to the transferee */ +static int hold_target_before_refer = 1; + +/* Boolean, controls whether the conference call shall be terminated when the initiator hangs up. + * The conference call terminates until there is only one participant remaining by default. The + * option is not configurable for now */ +static int terminate_conference = 0; + +/* Global jitterbuffer configuration */ +static struct ast_jb_conf global_jbconf; + +//TODO change AST_MAX_EXTENSION to something shorter +/* Structure for feature access codes */ +struct feature_access_code { + AST_LIST_ENTRY(feature_access_code) list; + char code[AST_MAX_EXTENSION]; +}; + +/* List of configured feature access codes */ +static AST_LIST_HEAD_NOLOCK_STATIC(feature_access_codes, feature_access_code); + +/* Format a string of feature access codes */ +static const char *feature_access_code_string(char *buffer, unsigned int buffer_length); + +/* Add FAC to list */ +static int feature_access_code_add(const char *code); + +/* Clear list of FAC */ +static int feature_access_code_clear(void); + +/* Match dialed digits against feature access codes */ +static int feature_access_code_match(char *sequence); + +/* Send dialed DTMF codes during call */ +static void send_outgoing_dtmf(struct ast_channel *owner, char dtmf_button, int frametype); + +/* Boolean value whether the monitoring thread shall continue. */ +static unsigned int monitor; +static unsigned int packets; + +static pthread_t monitor_thread = AST_PTHREADT_NULL; +static pthread_t packet_thread = AST_PTHREADT_NULL; +static pthread_t ubus_thread = AST_PTHREADT_NULL; + +static struct ubus_context *ctx; +static struct ubus_event_handler ObjAddListener; // Event handler for new ubus object events +static struct ubus_event_handler ObjRmListener; // Event handler for removed ubus object events +static int base; +static volatile uint32_t endpt_id = 0; +static const char endpt_ubus_path[] = "endpt"; +static const char audio_rx_str[] = "/tmp/endpt_audio_tx"; +static const char audio_tx_str[] = "/tmp/endpt_audio_rx"; +static const char ubusStrObjAdd[] = "ubus.object.add"; // UBUS objects added to global context +static const char ubusStrObjRm[] = "ubus.object.remove"; // UBUS objects added removed from global context +static int audio_rx_fd = 0, audio_tx_fd = 0; +static pe_bus_t *audio_rx_bus, *audio_tx_bus; +static pe_stream_t *audio_rx_stream; + +#define RTP_HEADER_SIZE 12 +#define RTP_DTMF_SIZE 4 + +enum { + EVENT_TYPE, + EVENT_LINE_ID, + __EVENT_MAX, +}; + +enum { // New ubus objects in global context + OBJ_ID, // UBUS ID of object + OBJ_PATH, // String path of object +}; + +enum { + STATE_OFF = 0, + STATE_ON, + STATE_LAST, +}; + +struct endpt_event { + const char *name; + enum LINE_EVENT event; + int line; +}; + +typedef struct __attribute__((__packed__)) { + int32_t line; + int32_t cnx_id; + uint32_t rtp_size; + uint8_t rtp[1]; +} audio_packet_t; + +static struct endpt_event event_map[] = { + { .name = "DTMF0", .event = EVENT_DTMF0 }, + { .name = "DTMF1", .event = EVENT_DTMF1 }, + { .name = "DTMF2", .event = EVENT_DTMF2 }, + { .name = "DTMF3", .event = EVENT_DTMF3 }, + { .name = "DTMF4", .event = EVENT_DTMF4 }, + { .name = "DTMF5", .event = EVENT_DTMF5 }, + { .name = "DTMF6", .event = EVENT_DTMF6 }, + { .name = "DTMF7", .event = EVENT_DTMF7 }, + { .name = "DTMF8", .event = EVENT_DTMF8 }, + { .name = "DTMF9", .event = EVENT_DTMF9 }, + { .name = "DTMFA", .event = EVENT_DTMFA }, + { .name = "DTMFB", .event = EVENT_DTMFB }, + { .name = "DTMFC", .event = EVENT_DTMFC }, + { .name = "DTMFD", .event = EVENT_DTMFD }, + { .name = "DTMFS", .event = EVENT_DTMFS }, + { .name = "DTMFH", .event = EVENT_DTMFH }, + { .name = "OFFHOOK", .event = EVENT_OFFHOOK }, + { .name = "ONHOOK", .event = EVENT_ONHOOK }, + { .name = "EARLY_OFFHOOK", .event = EVENT_EARLY_OFFHOOK }, + { .name = "EARLY_ONHOOK", .event = EVENT_EARLY_ONHOOK }, + { .name = "FLASH", .event = EVENT_FLASH }, + { .name = "", .event = EVENT_LAST }, +}; + +static const struct blobmsg_policy asterisk_event_policy[] = { + [EVENT_TYPE] = { .name = "event", .type = BLOBMSG_TYPE_STRING }, + [EVENT_LINE_ID] = { .name = "line", .type = BLOBMSG_TYPE_INT32 }, +}; + +static const struct blobmsg_policy new_obj_policy[] = { + [OBJ_ID] = { .name = "id", .type = BLOBMSG_TYPE_INT32 }, // UBUS ID of object + [OBJ_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING }, // String path of object +}; + +static struct ast_channel_tech *cur_tech; + +static struct stasis_subscription **registration_change_sub = NULL; /*!< subscription id for SIP registration change events for every line*/ + +static const DTMF_CHARNAME_MAP dtmf_to_charname[] = +{ + {EVENT_DTMF0, "DTMF0", '0', 0}, + {EVENT_DTMF1, "DTMF1", '1', 1}, + {EVENT_DTMF2, "DTMF2", '2', 2}, + {EVENT_DTMF3, "DTMF3", '3', 3}, + {EVENT_DTMF4, "DTMF4", '4', 4}, + {EVENT_DTMF5, "DTMF5", '5', 5}, + {EVENT_DTMF6, "DTMF6", '6', 6}, + {EVENT_DTMF7, "DTMF7", '7', 7}, + {EVENT_DTMF8, "DTMF8", '8', 8}, + {EVENT_DTMF9, "DTMF9", '9', 9}, + {EVENT_DTMFA, "DTMFA", 'A', 12}, + {EVENT_DTMFB, "DTMFB", 'B', 13}, + {EVENT_DTMFC, "DTMFC", 'C', 14}, + {EVENT_DTMFD, "DTMFD", 'D', 15}, + {EVENT_DTMFH, "DTMFH", 0x23, 11}, //# + {EVENT_DTMFS, "DTMFS", 0x2A, 10}, //* + {EVENT_LAST, "", '-', -1} +}; + +/* Linked list of pvt:s */ +static struct brcm_pvt *iflist; + +/* Protect the interface list (of brcm_pvt's) */ +AST_MUTEX_DEFINE_STATIC(iflock); + +/* Protect the monitoring thread, so only one process can kill or start it, and not + when it's doing something critical. */ +AST_MUTEX_DEFINE_STATIC(monlock); + +static struct ast_format_cap *default_cap; + +static int load_common_settings(struct ast_config **cfg); +static void load_lines_settings(struct ast_config *cfg); +static char *state2str(enum channel_state state); + +/* exported capabilities */ +static struct ast_channel_tech brcm_tech = { + .type = "TELCHAN", + .description = tdesc, + .requester = brcm_request, //No lock held (no channel yet) + .call = brcm_call, //Channel is locked + .hangup = brcm_hangup, //Channel is locked + .answer = brcm_answer, //Channel is locked + .read = brcm_read, //Channel is locked + .write = brcm_write, //Channel is locked + .send_digit_begin = brcm_senddigit_begin, //Channel is NOT locked + .send_digit_end = brcm_senddigit_end, //Channel is NOT locked + .indicate = brcm_indicate, //Channel is locked + .transfer = brcm_transfer // Channel is locked +}; + +static struct brcm_channel_tech fxs_tech = { + .signal_ringing = brcm_signal_ringing, + .signal_ringing_callerid_pending = brcm_signal_ringing_callerid_pending, + .signal_callerid = brcm_signal_callerid, + .stop_ringing = brcm_stop_ringing, + .stop_ringing_callerid_pending = brcm_stop_ringing_callerid_pending, + .release = NULL, +}; + +/* Tries to lock 10 timees, then gives up */ +/* static int pvt_trylock(struct brcm_pvt *pvt, const char *reason) */ +/* { */ +/* int i = 10; */ +/* while (i--) { */ +/* if (!ast_mutex_trylock(&pvt->lock)) { */ +/* ast_debug(9, "----> Successfully locked pvt port %d - reason %s\n", pvt->line_id, reason); */ +/* return 1; */ +/* } */ +/* } */ +/* ast_debug(9, "----> Failed to lock port %d - %s\n", pvt->line_id, reason); */ +/* return 0; */ +/* } */ + +#ifdef BRCM_LOCK_DEBUG +static int pvt_lock(struct brcm_pvt *pvt, const char *reason) +{ + ast_debug(9, "----> Trying to lock port %d - %s\n", pvt->line_id, reason); + ast_mutex_lock(&pvt->lock); + ast_debug(9, "----> Locked pvt port %d - reason %s\n", pvt->line_id, reason); + return 1; +} + + +static int pvt_lock_silent(struct brcm_pvt *pvt) +{ + ast_mutex_lock(&pvt->lock); + return 1; +} + + +static int pvt_unlock(struct brcm_pvt *pvt) +{ + ast_mutex_unlock(&pvt->lock); + ast_debug(10, "----> Unlocking pvt port %d\n", pvt->line_id); + return 1; +} + +static int pvt_unlock_silent(struct brcm_pvt *pvt) +{ + ast_mutex_unlock(&pvt->lock); + return 1; +} + +#else +#define pvt_lock(pvt, reason) ast_mutex_lock(&(pvt)->lock) +#define pvt_lock_silent(pvt) ast_mutex_lock(&(pvt)->lock) +#define pvt_unlock(pvt) ast_mutex_unlock(&(pvt)->lock) +#define pvt_unlock_silent(pvt) ast_mutex_unlock(&(pvt)->lock) +#endif + +// Get the endptmngr ubus address. It's dynamically updated +// by another thread without locking. Thus we read it twice +// here to ensure we get it correct since it might have been +// written behind our back. +static uint32_t get_ubus_endpt_id(void) { + uint32_t local_endpt_id[2]; + + do { + local_endpt_id[0] = endpt_id; + local_endpt_id[1] = endpt_id; + } while(local_endpt_id[0] != local_endpt_id[1]); + + return local_endpt_id[0]; +} + +//------------------------------------------------------------- +// Callback for: a ubus call (invocation) has replied with some data +static void ubus_call_answer(struct ubus_request *req, int type, struct blob_attr *msg) +{ + ast_debug(3, "Got answer on ubus call.\n"); +} + + +//------------------------------------------------------------- +// Callback for: a ubus call (invocation) has finished +static void ubus_call_complete(struct ubus_request *req, int ret) +{ + ast_debug(3, "Ubus call completed.\n"); + free(req); +} + + +static void endpt_signal(int line, char *signal, char *state, char *data) { + struct blob_buf bb; + struct ubus_request *req; + uint32_t local_endpt_id; + int res; + + //ast_debug(5, "line = %d, signal = %s, state = %s, data = %s\n", line, signal, state, data); + + local_endpt_id = get_ubus_endpt_id(); + + if (local_endpt_id) { + memset(&bb, 0, sizeof(bb)); + blob_buf_init(&bb, 0); + + blobmsg_add_u32(&bb, "line", line); + blobmsg_add_string(&bb, "signal", signal); + blobmsg_add_string(&bb, "state", state); + if (data) + blobmsg_add_string(&bb, "data", data); + + req = calloc(1, sizeof(struct ubus_request)); + if (!req) return; + + res = ubus_invoke_async(ctx, local_endpt_id, "signal", bb.head, req); + + if(res != UBUS_STATUS_OK) { + ast_log(LOG_ERROR, "Error invoking method: %s %d\n", "signal", res); + free(req); + return; + } + + req->data_cb = ubus_call_answer; + req->complete_cb = ubus_call_complete; + req->priv = NULL; + ubus_complete_request_async(ctx, req); + } +} + +static void endpt_connection(int line, int id, char *action) { + struct blob_buf bb; + struct ubus_request *req; + uint32_t local_endpt_id; + int res; + + local_endpt_id = get_ubus_endpt_id(); + + if (local_endpt_id) { + memset(&bb, 0, sizeof(bb)); + blob_buf_init(&bb, 0); + + blobmsg_add_u32(&bb, "line", line); + blobmsg_add_u32(&bb, "id", line); + blobmsg_add_string(&bb, "action", action); + + req = calloc(1, sizeof(struct ubus_request)); + if (!req) return; + res = ubus_invoke_async(ctx, local_endpt_id, "connection", bb.head, req); + + if(res != UBUS_STATUS_OK) { + ast_log(LOG_ERROR, "Error invoking method: %s %d\n", "connection", res); + free(req); + return; + } + + req->data_cb = ubus_call_answer; + req->complete_cb = ubus_call_complete; + req->priv = NULL; + ubus_complete_request_async(ctx, req); + } +} + +static int brcm_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen) +{ + struct brcm_subchannel *sub = ast_channel_tech_pvt(ast); + struct ast_bridge_channel *play_bridge_channel; + struct ast_bridge *myBridge; + struct ast_frame astFrame; + int res = 0; + pvt_lock(sub->parent, "indicate"); + //ast_mutex_lock(&sub->parent->lock); + switch(condition) { + case AST_CONTROL_UNHOLD: + res = 0; //We still want asterisk core to play tone + brcm_stop_dialtone(sub->parent); + + // Play a beep when unholding. + ast_channel_lock(ast); + play_bridge_channel = ast_channel_get_bridge_channel(ast); + ast_channel_unlock(ast); + ast_bridge_channel_queue_playfile(play_bridge_channel, NULL, "beep", NULL); + + sub->channel_state = INCALL; + + // Tell all participants to re-sync RTP stream. + myBridge = ast_channel_internal_bridge(ast); + memset(&astFrame, 0, sizeof astFrame); + astFrame.frametype = AST_FRAME_CONTROL; + astFrame.subclass.integer = AST_CONTROL_SRCUPDATE; + ast_bridge_queue_everyone_else(myBridge, NULL, &astFrame); + break; + + case AST_CONTROL_UPDATE_RTP_PEER: + case AST_CONTROL_SRCUPDATE: + case AST_CONTROL_SRCCHANGE: + break; + case AST_CONTROL_RINGING: + sub->channel_state = RINGBACK; + endpt_signal(sub->parent->line_id, "ringback", "on", NULL); + res = 0; + break; + case AST_CONTROL_TRANSFER: + res = -1; + if (datalen != sizeof(enum ast_control_transfer)) { + ast_log(LOG_ERROR, "Invalid datalen for AST_CONTROL_TRANSFER. Expected %d, got %d\n", (int) sizeof(enum ast_control_transfer), (int) datalen); + } else { + enum ast_control_transfer *message = (enum ast_control_transfer *) data; + brcm_finish_transfer(ast, sub, *message); + } + break; + case AST_CONTROL_CONGESTION: + ast_debug(4, "Got CONGESTION on %s\n", ast_channel_name(ast)); + /* The other end is out of network resources */ + if (ast_channel_state(ast) != AST_STATE_UP) { + /* If state is UP, we can't do anything */ + ast_softhangup_nolock(ast, AST_SOFTHANGUP_DEV); + brcm_hangup(ast); + break; + } + res = -1; + break; + case AST_CONTROL_CONNECTED_LINE: + ast_debug(4, "Got CONNECTED LINE UPDATE on %s\n", ast_channel_name(ast)); + /* Update caller IDs on display - dect ? */ + //TODO Ronny: perhaps reverse loop current here? + res = -1; + break; + + case AST_CONTROL_BUSY: + ast_debug(4, "Got BUSY on %s\n", ast_channel_name(ast)); + /* The other end is busy */ + if (ast_channel_state(ast) != AST_STATE_UP) { + /* XXX We should play a busy tone here!! */ + ast_softhangup_nolock(ast, AST_SOFTHANGUP_DEV); + brcm_hangup(ast); + break; + } + res = -1; + break; + case AST_CONTROL_PROGRESS: + ast_debug(4, "Got PROGRESS on %s\n", ast_channel_name(ast)); + /* Early media is coming our way */ + /* What do we do with that? */ + res = -1; + break; + case AST_CONTROL_NORMAL_DIALTONE: + brcm_dialtone_set(sub->parent, DIALTONE_ON); + break; + case AST_CONTROL_SPECIAL_DIALTONE: + brcm_dialtone_set(sub->parent, DIALTONE_SPECIAL_CONDITION); + break; + case AST_CONTROL_PVT_CAUSE_CODE: { + const struct ast_control_pvt_cause_code *cause_code = data; + int ast_cause = cause_code->ast_cause; + ast_debug(2, "AST_CONTROL_PVT_CAUSE_CODE = %d, chan_name = %s\n", ast_cause, cause_code->chan_name); + + switch (ast_cause) { + case AST_CAUSE_NO_USER_RESPONSE: + case AST_CAUSE_NO_ANSWER: + if (ast_channel_state(ast) != AST_STATE_UP) { + endpt_signal(sub->parent->line_id, "congestion", "on", NULL); + break; + } + res = -1; + break; + case AST_CAUSE_NORMAL_CLEARING: + // This is just fine. + break; + case AST_CAUSE_USER_BUSY: + if (ast_channel_state(ast) != AST_STATE_UP) { + /* XXX We should play a busy tone here!! */ + endpt_signal(sub->parent->line_id, "busy", "on", NULL); + break; + } + res = -1; + default: + ast_debug(1, "Don't know how to handle cause code %d\n", ast_cause); + break; + } + } + break; + default: + res = -1; + ast_debug(1, "Don't know how to indicate condition %d\n", condition); + break; + } + pvt_unlock(sub->parent); + return res; +} + +static int brcm_transfer(struct ast_channel *ast, const char *newdest) +{ + struct brcm_pvt *pvt; + struct brcm_subchannel *subchan_active, *subchan_inactive; + struct ast_channel *chan_new; + char ext[strlen(newdest) + 1], *replaces; + + ast_debug(1, "Transfer the existing call to [%s]\n", newdest); + + // Get the other subchannel + subchan_active = ast_channel_tech_pvt(ast); + if (!subchan_active || !(pvt = subchan_active->parent) || + !(subchan_inactive = brcm_subchannel_get_peer(subchan_active)) || + !brcm_subchannel_is_idle(subchan_inactive)) { + return -1; + } + + strncpy(ext, newdest, sizeof(ext)); + replaces = strcasestr(ext, "?Replaces="); + if (replaces) { + *replaces = '\0'; + replaces += strlen("?Replaces="); + } + + if (!subchan_inactive->connection_init) { + subchan_inactive->connection_id = ast_atomic_fetchadd_int((int *)¤t_connection_id, +1); + brcm_create_connection(subchan_inactive); + } + + chan_new = brcm_new(subchan_inactive, AST_STATE_DOWN, ext, pvt->context, NULL, NULL, NULL); + if (!chan_new) { + ast_log(LOG_ERROR, "couldn't create channel\n"); + return -1; + } + + if (replaces) + pbx_builtin_setvar_helper(chan_new, "SIPTRANSFER_REPLACES", replaces); + + ast_setstate(chan_new, AST_STATE_RING); + subchan_inactive->channel_state = TRANSFERING; + + if (ast_pbx_start(chan_new)) { + ast_log(LOG_WARNING, "Unable to start PBX on %s\n", ast_channel_name(chan_new)); + ast_hangup(chan_new); + return -1; + } + + return 0; +} + +static int brcm_finish_transfer(struct ast_channel *owner, struct brcm_subchannel *p, int result) +{ + struct brcm_subchannel* peer_sub; + /* + * We have received the result of a transfer operation. + * This could be: + * - Result of a Transfer-On-Hangup (Remote Transfer), in which case + * we should hangup the subchannel, no matter the result + * - Result of a R4 Attended Transfer (Remote Transfer), in which case + * we should wait for hangup on both subchannels, or resume calls if failed + * Hangup should be received immediately, but we start a timer to hangup + * everything ourselves just to be sure. + * - Probably nothing else - the built-in transfer should never let this + * control frame propagate to here + */ + if (p->channel_state != TRANSFERING) { + ast_log(LOG_WARNING, "Received AST_CONTROL_TRANSFER while in state %s\n", state2str(p->channel_state)); + return -1; + } + + peer_sub = brcm_subchannel_get_peer(p); + if (!peer_sub) { + ast_log(LOG_ERROR, "Failed to get peer subchannel\n"); + return -1; + } + + if (brcm_subchannel_is_idle(peer_sub)) { + // In the case of Transfer-On-Hangup peer sub should be a idle + if (result == AST_TRANSFER_SUCCESS) { + ast_log(LOG_NOTICE, "Remote transfer completed successfully, hanging up\n"); + } + else { + ast_log(LOG_NOTICE, "Remote transfer failed, hanging up\n"); + } + ast_queue_control(owner, AST_CONTROL_HANGUP); + p->channel_state = CALLENDED; + } else if (peer_sub->channel_state == ONHOLD) { + // In the case of R4 transfer peer sub should be on hold + if (result == AST_TRANSFER_SUCCESS) { + ast_log(LOG_NOTICE, "Remote transfer completed successfully, wait for remote hangup\n"); + p->r4_hangup_timer_id = ast_sched_add(sched, r4hanguptimeout, r4hanguptimeout_cb, p); + } else { + //Do nothing. Let calls be up as they were before R4 was attempted (first call on hold, second call active) + ast_log(LOG_NOTICE, "Remote transfer failed\n"); + p->channel_state = INCALL; + } + } else { + ast_log(LOG_WARNING, "AST_CONTROL_TRANSFER received in unexpected state\n"); + return -1; + } + + return 0; +} + +/*! \brief Incoming DTMF begin event */ +static int brcm_senddigit_begin(struct ast_channel *ast, char digit) +{ + struct brcm_subchannel *sub; + char signal[10]; + + //ast_debug(5, "DTMF send begin: %c\n", digit); + + sub = ast_channel_tech_pvt(ast); + pvt_lock(sub->parent, "DTMF senddigit_begin"); + + snprintf(signal, sizeof(signal), "dtmf%c", digit); + endpt_signal(sub->parent->line_id, signal, "on", NULL); + + pvt_unlock(sub->parent); + return 0; +} + +/*! \brief Incoming DTMF end */ +static int brcm_senddigit_end(struct ast_channel *ast, char digit, unsigned int duration) +{ + struct brcm_subchannel *sub; + char signal[10]; + + //ast_debug(5, "DTMF send end: %c, %d ms\n", digit, duration); + + sub = ast_channel_tech_pvt(ast); + pvt_lock(sub->parent, "DTMF senddigit_end"); + + snprintf(signal, sizeof(signal), "dtmf%c", digit); + endpt_signal(sub->parent->line_id, signal, "off", NULL); + + pvt_unlock(sub->parent); + return 0; +} + + +static int brcm_call(struct ast_channel *chan, const char *dest, int timeout) +{ + struct brcm_pvt *p; + struct brcm_subchannel *sub; + struct brcm_subchannel *sub_peer; + + struct timeval UtcTime = ast_tvnow(); + struct ast_tm tm; + + sub = ast_channel_tech_pvt(chan); + + ast_debug(1, "%s(): line %d\n", __func__, sub->parent->line_id); + ast_localtime(&UtcTime, &tm, NULL); + + if ((ast_channel_state(chan) != AST_STATE_DOWN) && (ast_channel_state(chan) != AST_STATE_RESERVED)) { + ast_log(LOG_WARNING, "brcm_call called on %s, neither down nor reserved\n", ast_channel_name(chan)); + return -1; + } + + //ast_mutex_lock(&sub->parent->lock); + pvt_lock(sub->parent, "brcm_call"); + + p = sub->parent; + sub_peer = brcm_subchannel_get_peer(sub); + if (brcm_in_call(p) && // a call is established + line_config[p->line_id].callwaiting && // call waiting active + !sub_peer->cw_rejected) { // a previous call has not been rejected using R0 + ast_debug(1, "Call waiting\n"); + sub->channel_state = CALLWAITING; + brcm_signal_callwaiting(p); + int cwtimeout_ms = cwtimeout * 1000; + sub->cw_timer_id = ast_sched_add(sched, cwtimeout_ms, cwtimeout_cb, sub); + ast_setstate(chan, AST_STATE_RINGING); + ast_queue_control(chan, AST_CONTROL_RINGING); + } + else if (!brcm_subchannel_is_idle(sub_peer)) { + ast_debug(1, "Line is busy\n"); + ast_channel_hangupcause_set(chan, AST_CAUSE_USER_BUSY); + ast_queue_control(chan, AST_CONTROL_BUSY); + } + else { + ast_debug(1, "Not call waiting\n"); + sub->channel_state = RINGING; + if (!clip) { + p->tech->signal_ringing(p); + } else { + p->tech->signal_ringing_callerid_pending(p); + p->tech->signal_callerid(chan, sub); + } + ast_setstate(chan, AST_STATE_RINGING); + ast_queue_control(chan, AST_CONTROL_RINGING); + } + //ast_mutex_unlock(&sub->parent->lock); + pvt_unlock(sub->parent); + + return 0; +} + +static int brcm_hangup(struct ast_channel *ast) +{ + struct brcm_pvt *p; + struct brcm_subchannel *sub, *sub_peer; + sub = ast_channel_tech_pvt(ast); + + if (!ast_channel_tech_pvt(ast)) { + ast_log(LOG_WARNING, "Asked to hangup channel not connected\n"); + return 0; + } + pvt_lock(sub->parent, "TELCHAN hangup"); + + p = sub->parent; + sub_peer = brcm_subchannel_get_peer(sub); + ast_debug(1, "brcm_hangup(%s) line_id=%d connection_id=%d\n", ast_channel_name(ast), p->line_id, sub->connection_id); + + if (sub->channel_state == CALLWAITING) { + brcm_stop_callwaiting(p); + } else if (sub_peer->channel_state == ONHOLD && + sub_peer->onhold_hangup_timer_id != -1 && sub->channel_state == RINGING) { + ast_debug(2, "There is a forgotten onhold call, not releasing channel\n"); + endpt_signal(sub->parent->line_id, "ringback", "off", NULL); + } else if (sub->channel_state == RINGING || sub->onhold_hangup_timer_id != -1) { + //Stop ringing if other end hungup before we answered + if (!clip) { + p->tech->stop_ringing(p); + } else { + p->tech->stop_ringing_callerid_pending(p); + } + } else if (brcm_subchannel_is_idle(sub_peer) && p->tech->release) { + //No active subchannel left, release + p->tech->release(p); + } + + if (sub->cw_timer_id != -1) { + if (ast_sched_del(sched, sub->cw_timer_id)) { + ast_log(LOG_WARNING, "Failed to remove scheduled call waiting timer\n"); + } + sub->cw_timer_id = -1; + } + + if(sub->r4_hangup_timer_id != -1) { + if (ast_sched_del(sched, sub->r4_hangup_timer_id)) { + ast_log(LOG_WARNING, "Failed to remove scheduled r4 hangup timer\n"); + } + sub->r4_hangup_timer_id = -1; + } + + if(sub->onhold_hangup_timer_id != -1) { + if (ast_sched_del(sched, sub->onhold_hangup_timer_id)) { + ast_log(LOG_WARNING, "Failed to remove scheduled onhold hangup timer\n"); + } + sub->onhold_hangup_timer_id = -1; + } + ast_setstate(ast, AST_STATE_DOWN); + + p->lastformat = NULL; + p->lastinput = NULL; + p->hf_detected = 0; + sub->channel_state = CALLENDED; + if (terminate_conference && sub->conference_initiator && brcm_in_conference(p)) { + /* Switch still active call leg out of conference mode */ + brcm_stop_conference(sub); + brcm_stop_conference(sub_peer); + } + + memset(p->ext, 0, sizeof(p->ext)); + sub->owner = NULL; + sub->conference_initiator = 0; + free(sub->conference_id); + sub->conference_id = NULL; + sub->cw_rejected = 0; + ast_module_unref(ast_module_info->self); + ast_verb(3, "Hungup '%s'\n", ast_channel_name(ast)); + ast_channel_tech_pvt_set(ast, NULL); + brcm_close_connection(sub); + + pvt_unlock(p); + return 0; +} + +static int brcm_answer(struct ast_channel *ast) +{ + struct brcm_subchannel *sub = ast_channel_tech_pvt(ast); + struct brcm_pvt *pvt = sub->parent; + + ast_debug(1, "brcm_answer(%s)\n", ast_channel_name(ast)); + + pvt_lock(pvt, "TELCHAN answer"); + + /* For attended call transfer, if the transfer target has accepted the call initiated by + * us as the tranferee, we can terminate the original call with the transferor */ + if (sub->channel_state == TRANSFERING) { + struct brcm_subchannel *peer = brcm_subchannel_get_peer(sub); + + if (peer->owner) { + ast_debug(1, "Transferee terminates the original call with the transferor\n"); + ast_queue_control(peer->owner, AST_CONTROL_HANGUP); + peer->owner = NULL; + peer->channel_state = CALLENDED; + brcm_close_connection(peer); + } + } + + if (ast_channel_state(ast) != AST_STATE_UP) { + ast_setstate(ast, AST_STATE_UP); + ast_debug(2, "brcm_answer(%s) set state to up\n", ast_channel_name(ast)); + } + ast_channel_rings_set(ast, 0); + sub->channel_state = INCALL; + endpt_signal(pvt->line_id, "ringback", "off", NULL); + + pvt_unlock(pvt); + + return 0; +} + + +// Hangup ALL brcm lines currently ringing with busy casue. +static int brcm_busy_all_ringers(void) +{ + struct brcm_pvt *pvt; + int i; + + brcm_lock_pvts(); + pvt = iflist; + + while(pvt) { + for (i=0; i<NUM_SUBCHANNELS; i++) { + if (pvt->sub[i] && pvt->sub[i]->owner && pvt->sub[i]->channel_state == RINGING) { + ast_debug(4, "Hangup BUSY on %s\n", ast_channel_name(pvt->sub[i]->owner)); + ast_queue_control(pvt->sub[i]->owner, AST_CONTROL_BUSY); + ast_queue_hangup_with_cause(pvt->sub[i]->owner, AST_CAUSE_USER_BUSY); + } + } + + pvt = brcm_get_next_pvt(pvt); + } + + brcm_unlock_pvts(); + + return 0; +} + + +/* +* Map RTP data header value to a codec name +*/ +static char* brcm_get_codec_string(int id) { + switch (id) { + case PCMA: return "alaw"; break; + case PCMU: return "ulaw"; break; + case G723: return "g723.1"; break; + case G726: return "g726"; break; + case G729: return "g729"; break; + case G722: return "g722"; break; + case -1: return "none set"; break; + default: return "unknown id"; break; + } +} + +static int brcm_classify_rtp_packet(int id) { + + //ast_debug(5, "RTP Packet ID = %d\n", id); + + switch (id) { + case PCMU: return BRCM_AUDIO; + case G726: return BRCM_AUDIO; + case G723: return BRCM_AUDIO; + case PCMA: return BRCM_AUDIO; + case G729: return BRCM_AUDIO; + case G722: return BRCM_AUDIO; + case RTCP: return BRCM_UNKNOWN; // Ignored packet. + default: + ast_verbose("Unknown rtp packet id %d\n", id); + return BRCM_UNKNOWN; + } +} + +static int map_ast_codec_id_to_rtp(const struct ast_format *astcodec) +{ + if (ast_format_cmp(astcodec, ast_format_alaw) == AST_FORMAT_CMP_EQUAL) { + return PCMA; + } else if (ast_format_cmp(astcodec, ast_format_ulaw) == AST_FORMAT_CMP_EQUAL) { + return PCMU; + } else if (ast_format_cmp(astcodec, ast_format_g722) == AST_FORMAT_CMP_EQUAL) { + return G722; + } else if (ast_format_cmp(astcodec, ast_format_g723) == AST_FORMAT_CMP_EQUAL) { + return G723; + } else if (ast_format_cmp(astcodec, ast_format_g729) == AST_FORMAT_CMP_EQUAL) { + return G729; + } else if (ast_format_cmp(astcodec, ast_format_g726) == AST_FORMAT_CMP_EQUAL) { + return G726; + } else { + ast_verbose("Unknown asterisk format/codec id\n"); + return PCMA; + } +} + + +static struct ast_frame *brcm_read(struct ast_channel *ast) +{ + return &ast_null_frame; +} + +/* Handle stream events on audio_bus. Parses raw stream and calls + registered packet handlers. */ +static void audio_rx_stream_handler(pe_stream_t *stream __attribute__((unused)), pe_event_t *event __attribute__((unused))) { + if (pe_bus_receive(audio_rx_bus, event) < 0) { + printf("audio_bus rx buffer full"); + return; // Drop packets if we can't cope the pace + } + + pe_bus_dispatch(audio_rx_bus); +} + +static int brcm_write(struct ast_channel *ast, struct ast_frame *frame) +{ + struct brcm_subchannel *sub = ast_channel_tech_pvt(ast); + int packet_size; + audio_packet_t *ap; + + if (ast_channel_state(ast) != AST_STATE_UP && ast_channel_state(ast) != AST_STATE_RING) { + /* Silently ignore packets until channel is up */ + ast_debug(5, "error: channel not up\n"); + return 0; + } + + /* Ignore if on hold */ + if (sub->channel_state == ONHOLD) { + return 0; + } + + if(frame->frametype == AST_FRAME_VOICE) { + packet_size = sizeof(audio_packet_t) - 1 + RTP_HEADER_SIZE + frame->datalen; + ap = calloc(1, packet_size); + if (!ap) { + printf("Out of memory\n"); + return -1; + } + + ap->line = sub->parent->line_id; + ap->cnx_id = sub->parent->line_id; + ap->rtp_size = RTP_HEADER_SIZE + frame->datalen; + + /* copy frame data to audio packet */ + memcpy(ap->rtp + RTP_HEADER_SIZE, frame->data.ptr, frame->datalen); + + //ast_mutex_lock(&sub->parent->lock); + pvt_lock(sub->parent, "TELCHAN write frame"); + + /* generate the rtp header */ + brcm_generate_rtp_packet(sub, ap->rtp, map_ast_codec_id_to_rtp(frame->subclass.format), 0, 0); + + /* set rtp id sent to endpoint */ + sub->codec = map_ast_codec_id_to_rtp(frame->subclass.format); + + //ast_mutex_unlock(&sub->parent->lock); + pvt_unlock(sub->parent); + + pe_bus_send(audio_tx_bus, (uint8_t *)ap, packet_size); + free(ap); + } + + return 0; +} + +static void brcm_reset_dtmf_buffer(struct brcm_pvt *p) +{ + memset(p->dtmfbuf, 0, sizeof(p->dtmfbuf)); + p->dtmf_len = 0; + p->dtmf_first = -1; + p->dtmfbuf[p->dtmf_len] = '\0'; +} + +static char *state2str(enum channel_state state) +{ + switch (state) { + case ONHOOK: return "ONHOOK"; + case OFFHOOK: return "OFFHOOK"; + case DIALING: return "DIALING"; + case CALLING: return "CALLING"; + case INCALL: return "INCALL"; + case ANSWER: return "ANSWER"; + case CALLENDED: return "CALLENDED"; + case RINGING: return "RINGING"; + case CALLWAITING: return "CALLWAITING"; + case ONHOLD: return "ONHOLD"; + case TRANSFERING: return "TRANSFERING"; + case RINGBACK: return "RINGBACK"; + case AWAITONHOOK: return "AWAITONHOOK"; + default: return "UNKNOWN"; + } +} + +static int brcm_subchannel_is_idle(const struct brcm_subchannel *sub) +{ + if (sub->channel_state == ONHOOK || sub->channel_state == CALLENDED) { + return 1; + } + return 0; +} + +struct brcm_subchannel *brcm_subchannel_get_peer(const struct brcm_subchannel *sub) +{ + struct brcm_subchannel *peer_sub; + peer_sub = (sub->parent->sub[0] == sub) ? sub->parent->sub[1] : sub->parent->sub[0]; + return peer_sub; +} + +/* Tell endpoint to play country specific dialtone. */ +static int brcm_signal_dialtone(struct brcm_pvt *p) { + + switch (p->dialtone) { + case DIALTONE_OFF: + endpt_signal(p->line_id, "dial", "off", NULL); + endpt_signal(p->line_id, "stutter", "off", NULL); + break; + case DIALTONE_ON: + endpt_signal(p->line_id, "dial", "on", NULL); + break; + case DIALTONE_CONGESTION: + endpt_signal(p->line_id, "congestion", "on", NULL); + break; + case DIALTONE_SPECIAL_CONDITION: + endpt_signal(p->line_id, "stutter", "on", NULL); + break; + default: + ast_log(LOG_ERROR, "Requested to signal unknown dialtone\n"); + return -1; + } + return 0; +} + +int brcm_stop_dialtone(struct brcm_pvt *p) { + endpt_signal(p->line_id, "dial", "off", NULL); + endpt_signal(p->line_id, "stutter", "off", NULL); + + return 0; +} + +/* Tell endpoint to play country specific congestion tone */ +int brcm_signal_congestion(struct brcm_pvt *p) { + endpt_signal(p->line_id, "stutter", "on", NULL); + + return 0; +} + +static struct ast_channel *brcm_new(struct brcm_subchannel *subchan, int state, const char *ext, const char *context, + const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, + struct ast_format_cap *format) +{ + struct ast_channel *chan; + struct ast_format *fmt; + struct ast_format_cap *caps; + + caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + if (!caps) { + return NULL; + } + + chan = ast_channel_alloc(1, state, subchan->parent->cid_num, subchan->parent->cid_name, "", ext, context, + assignedids, requestor, 0, "TELCHAN/%d/%d", subchan->parent->line_id, subchan->connection_id); + if (chan) { + ast_channel_tech_set(chan, cur_tech); + + ast_format_cap_append_from_cap(caps, default_cap, AST_MEDIA_TYPE_UNKNOWN); + ast_channel_nativeformats_set(chan, caps); + ao2_ref(caps, -1); + fmt = ast_format_cap_get_format(ast_channel_nativeformats(chan), 0); + + line_settings *s = &line_config[subchan->parent->line_id]; + + /* set codecs */ + ast_channel_set_writeformat(chan, fmt); + ast_channel_set_rawwriteformat(chan, fmt); + ast_channel_set_readformat(chan, fmt); + ast_channel_set_rawreadformat(chan, fmt); + + /* no need to call ast_setstate: the channel_alloc already did its job */ + if (state == AST_STATE_RING) + ast_channel_rings_set(chan, 1); + ast_channel_tech_pvt_set(chan, subchan); + + /* Don't use ast_set_callerid() here because it will + * generate a NewCallerID event before the NewChannel event */ + if (!ast_strlen_zero(subchan->parent->cid_num)) { + ast_channel_caller(chan)->ani.number.valid = 1; + ast_channel_caller(chan)->ani.number.str = ast_strdup(subchan->parent->cid_num); + } + + ast_channel_caller(chan)->id.number.presentation = s->clir ? AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED : AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED; + ast_channel_caller(chan)->id.name.presentation = s->clir ? AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED : AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED; + + //Setup jitter buffer + ast_jb_configure(chan, &global_jbconf); + subchan->owner = chan; + + ast_module_ref(ast_module_info->self); + + if (state != AST_STATE_DOWN) { + if (ast_pbx_start(chan)) { + ast_log(LOG_WARNING, "Unable to start PBX on %s\n", ast_channel_name(chan)); + ast_hangup(chan); + return NULL; + } + } + } else + ast_log(LOG_WARNING, "Unable to allocate channel structure\n"); + + ast_channel_unlock(chan); + + return chan; +} + + +static struct brcm_pvt* brcm_get_next_pvt(struct brcm_pvt *p) { + if (p && p->next) + return p->next; + else + return NULL; +} + +struct brcm_pvt* brcm_get_pvt_from_lineid(struct brcm_pvt *p, int line_id) +{ + struct brcm_pvt *tmp = p; + if (p && p->line_id == line_id) return p; + tmp = brcm_get_next_pvt(tmp); + + while(tmp) { + if (tmp->line_id == line_id) return tmp; + tmp = brcm_get_next_pvt(tmp); + } + return NULL; +} + +struct brcm_subchannel* brcm_get_active_subchannel(const struct brcm_pvt *p) +{ + struct brcm_subchannel *sub = NULL; + int i; + + if(!p) return NULL; + + for (i=0; i<NUM_SUBCHANNELS; i++) { + switch (p->sub[i]->channel_state) { + case INCALL: + case DIALING: + case CALLING: + case OFFHOOK: + case AWAITONHOOK: + case RINGING: + case TRANSFERING: + case RINGBACK: + sub = p->sub[i]; + return sub; + case CALLWAITING: + case ONHOLD: + break; + case ONHOOK: + case ANSWER: //Remove this state + case CALLENDED: + if (!sub) { + sub = p->sub[i]; + } + break; + default: + ast_log(LOG_WARNING, "Unhandled channel state %d\n", sub->channel_state); + break; + } + } + + return sub; +} + +static struct brcm_subchannel *brcm_get_onhold_subchannel(const struct brcm_pvt *p) +{ + struct brcm_subchannel *sub; + int i; + for(i=0; i<NUM_SUBCHANNELS; i++) { + sub = p->sub[i]; + if (sub->channel_state == ONHOLD) { + return sub; + } + } + return NULL; +} + +/* Hangup incoming call after call waiting times out */ +static int cwtimeout_cb(const void *data) +{ + struct brcm_subchannel *sub; + struct ast_channel *owner = NULL; + + ast_debug(2, "No response to call waiting, hanging up\n"); + + sub = (struct brcm_subchannel *) data; + //ast_mutex_lock(&sub->parent->lock); + pvt_lock(sub->parent, "Cwtimeout callback"); + sub->cw_timer_id = -1; + if (sub->owner) { + ast_channel_ref(sub->owner); + owner = sub->owner; + } + //ast_mutex_unlock(&sub->parent->lock); + pvt_unlock(sub->parent); + + if (owner) { + ast_channel_lock(owner); + ast_channel_hangupcause_set(owner, AST_CAUSE_USER_BUSY); + ast_queue_control(owner, AST_CONTROL_BUSY); + ast_channel_unlock(owner); + ast_channel_unref(owner); + } + + return 0; +} + +/* Hangup calls if not done by remote after R4 transfer */ +static int r4hanguptimeout_cb(const void *data) +{ + struct brcm_subchannel *sub; + struct brcm_subchannel *peer_sub; + + struct ast_channel *sub_owner = NULL; + struct ast_channel *peer_sub_owner = NULL; + + ast_debug(2, "No hangup from remote after remote transfer using R4, hanging up\n"); + + sub = (struct brcm_subchannel *) data; + peer_sub = brcm_subchannel_get_peer(sub); + + //ast_mutex_lock(&sub->parent->lock); + pvt_lock(sub->parent, "r4hanguptimeout callback"); + + sub->r4_hangup_timer_id = -1; + peer_sub->channel_state = CALLENDED; + sub->channel_state = CALLENDED; + + if (sub->owner) { + ast_channel_ref(sub->owner); + sub_owner = sub->owner; + } + if (peer_sub->owner) { + ast_channel_ref(peer_sub->owner); + peer_sub_owner = peer_sub->owner; + } + //ast_mutex_unlock(&sub->parent->lock); + pvt_unlock(sub->parent); + + if (sub_owner) { + ast_queue_control(sub_owner, AST_CONTROL_HANGUP); + ast_channel_unref(sub_owner); + } + + if (peer_sub_owner) { + ast_queue_control(peer_sub_owner, AST_CONTROL_HANGUP); + ast_channel_unref(peer_sub_owner); + } + + return 0; +} + +/* Hangup call onhold if user does not pick up after reminder ringing */ +static int onholdhanguptimeout_cb(const void *data) +{ + struct brcm_subchannel *sub; + struct ast_channel *sub_owner = NULL; + + ast_debug(2, "No pickup after reminder ringing for call on hold, hanging up\n"); + sub = (struct brcm_subchannel *) data; + + //ast_mutex_lock(&sub->parent->lock); + pvt_lock(sub->parent, "onholdhanguptimeout callback"); + + sub->onhold_hangup_timer_id = -1; + + if (sub->owner) { + ast_channel_ref(sub->owner); + sub_owner = sub->owner; + } + //ast_mutex_unlock(&sub->parent->lock); + pvt_unlock(sub->parent); + + if (sub_owner) { + ast_queue_control(sub_owner, AST_CONTROL_HANGUP); + ast_channel_unref(sub_owner); + } + + return 0; +} + +/* + * Helper function that tells asterisk to start a call on the provided pvt/sub/context + * using the content of the dtmf buffer. + */ +static void brcm_start_calling(struct brcm_pvt *p, struct brcm_subchannel *sub, char* context) +{ + struct ast_channel *c = (struct ast_channel *)sub->parent; + + sub->channel_state = CALLING; + strncpy(p->ext, p->dtmfbuf, sizeof(p->dtmfbuf)); + ast_channel_exten_set(c, p->ext); + ast_channel_context_set(c, context); + ast_debug(1, "Calling %s@%s\n", ast_channel_exten(c), ast_channel_context(c)); + + /* Reset the dtmf buffer */ + brcm_reset_dtmf_buffer(p); + + /* Reset hook flash state */ + p->hf_detected = 0; + + /* Start the pbx */ + if (!sub->connection_init) { + sub->connection_id = ast_atomic_fetchadd_int((int *)¤t_connection_id, +1); + brcm_create_connection(sub); + } + + /* Changed state from AST_STATE_UP to AST_STATE_RING ito get the brcm_answer callback + * which is needed for call waiting. */ + brcm_new(sub, AST_STATE_RING, sub->parent->ext, sub->parent->context, NULL, NULL, NULL); +} + +/* + * Start calling if we have a (partial) match in asterisks dialplan after an interdigit timeout. + * Called on scheduler thread. + */ +static int handle_interdigit_timeout(const void *data) +{ + ast_debug(9, "Interdigit timeout\n"); + struct brcm_pvt *p = (struct brcm_pvt *) data; + //ast_mutex_lock(&p->lock); + pvt_lock(p, "interdigit callback"); + p->interdigit_timer_id = -1; + struct brcm_subchannel *sub = brcm_get_active_subchannel(p); + + if (ast_exists_extension(NULL, p->context, p->dtmfbuf, 1, p->cid_num)) + { + //We have at least one matching extension in "normal" context, + //and interdigit timeout has passed, so have asterisk start calling. + //Asterisk will select the best matching extension if there are more than one possibility. + ast_debug(9, "Interdigit timeout, extension(s) matching %s found\n", p->dtmfbuf); + brcm_start_calling(p, sub, p->context); + } + //ast_mutex_unlock(&p->lock); + pvt_unlock(p); + return 0; +} + +/* + * Reset hook flash state after an interdigit timeout. + * Called on scheduler thread. + */ +static int handle_hookflash_timeout(const void *data) +{ + ast_debug(9, "Hook flash timeout, clear hook flash\n"); + struct brcm_pvt *p = (struct brcm_pvt *) data; + + //ast_mutex_lock(&p->lock); + pvt_lock(p, "hookflash callback"); + p->interdigit_timer_id = -1; + p->hf_detected = 0; + //ast_mutex_unlock(&p->lock); + pvt_unlock(p); + + return 0; +} + +/* + * Start autodialing if we have an autodial extension. + * Called on scheduler thread. + */ +static int handle_autodial_timeout(const void *data) +{ + ast_debug(9, "Autodial timeout\n"); + struct brcm_pvt *p = (struct brcm_pvt *) data; + pvt_lock(p, "autodial timeout"); + //ast_mutex_lock(&p->lock); + p->autodial_timer_id = -1; + struct brcm_subchannel *sub = brcm_get_active_subchannel(p); + line_settings *s = &line_config[p->line_id]; + + if (ast_exists_extension(NULL, p->context, s->autodial_ext, 1, p->cid_num)) + { + brcm_stop_dialtone(p); + ast_copy_string(p->dtmfbuf, s->autodial_ext, sizeof(p->dtmfbuf)); + ast_debug(9, "Autodialing extension: %s\n", p->dtmfbuf); + brcm_start_calling(p, sub, p->context); + } + //ast_mutex_unlock(&p->lock); + pvt_unlock(p); + return 0; +} + +/* + * Dialtone expired, play congestion tone + * Called on scheduler thread. + */ +static int handle_dialtone_timeout(const void *data) +{ + ast_debug(9, "Dialtone timeout\n"); + struct brcm_pvt *p = (struct brcm_pvt *) data; + + pvt_lock(p, "dialtone timeout"); + //ast_mutex_lock(&p->lock); + p->dialtone_timeout_timer_id = -1; + + struct brcm_subchannel *sub = brcm_get_active_subchannel(p); + if (sub && sub->channel_state == OFFHOOK) { + /* Enter state where nothing else than ONHOOK is accepted and play congestion tone */ + sub->channel_state = AWAITONHOOK; + brcm_signal_congestion(p); + } + + //ast_mutex_unlock(&p->lock); + pvt_unlock(p); + return 0; +} + +/* + * Start calling if we have a match in asterisks dialplan. + * Called after each new DTMF event, from monitor_events thread, + * with the required locks already held. + */ +void handle_dtmf_calling(struct brcm_subchannel *sub) +{ + struct brcm_pvt *p = sub->parent; + int dtmfbuf_len = strlen(p->dtmfbuf); + char dtmf_last_char = p->dtmfbuf[(dtmfbuf_len - 1)]; + + if (ast_exists_extension(NULL, p->context_direct, p->dtmfbuf, 1, p->cid_num) && !ast_matchmore_extension(NULL, p->context_direct, p->dtmfbuf, 1, p->cid_num)) + { + //We have a full match in the "direct" context, so have asterisk place a call immediately + ast_debug(9, "Direct extension matching %s found\n", p->dtmfbuf); + brcm_start_calling(p, sub, p->context_direct); + } + else if (ast_exists_extension(NULL, p->context, p->dtmfbuf, 1, p->cid_num) && dtmf_last_char == 0x23 && feature_access_code_match(p->dtmfbuf) != 1) { + //We have a match in the "normal" context, and user ended the dialling sequence with a #, + //so have asterisk place a call immediately if sequence is not partially matching a feature access code + ast_debug(9, "Pound-key pressed during dialling, extension %s found\n", p->dtmfbuf); + brcm_start_calling(p, sub, p->context); + } + else if (ast_exists_extension(NULL, p->context, p->dtmfbuf, 1, p->cid_num) && !ast_matchmore_extension(NULL, p->context, p->dtmfbuf, 1, p->cid_num)) + { + //We have a full match in the "normal" context, so have asterisk place a call immediately, + //since no more digits can be added to the number + //(this is unlikely to happen since there is probably a "catch-all" extension) + ast_debug(9, "Unique extension matching %s found\n", p->dtmfbuf); + brcm_start_calling(p, sub, p->context); + } + else { + //No matches. We schedule a (new) interdigit timeout to occur + int timeoutmsec = line_config[p->line_id].timeoutmsec; + ast_debug(9, "Scheduling new interdigit timeout in %d msec\n", timeoutmsec); + p->interdigit_timer_id = ast_sched_add(sched, timeoutmsec, handle_interdigit_timeout, p); + } +} + +/* + * Perform actions for hook flash. + * Preconditions: One subchannel should be in CALLWAITING or ONHOLD, + * One subchannel should be in INCALL. + * channel locks are held + * brcm_pvt->lock is held + */ +static void handle_hookflash(struct brcm_subchannel *sub, struct brcm_subchannel *sub_peer, + struct ast_channel *owner, struct ast_channel *peer_owner) +{ + struct brcm_pvt *p = sub->parent; + + ast_log(LOG_DTMF, "Hook Flash detected for phone line %d\r\n", sub->parent->line_id); + + if (p->dtmf_first < 0) { + /* If current subchannel is in call and peer subchannel is idle, provide dialtone */ + if (sub->channel_state == INCALL && (sub_peer->channel_state == ONHOOK || sub_peer->channel_state == CALLENDED)) { + ast_debug(2, "R while in call and idle peer subchannel\n"); + + brcm_cancel_dialing_timeouts(p); + brcm_reset_dtmf_buffer(p); + p->hf_detected = 0; + + /* Put current call on hold */ + if (owner) { + brcm_mute_connection(sub); + sub->channel_state = ONHOLD; + ast_queue_hold(owner, NULL); + } + + /* Provide new line */ + brcm_signal_dialtone(p); + sub_peer->channel_state = OFFHOOK; + + /* If offhook/dialing/calling and peer subchannel is on hold, switch call */ + } else if ((sub->channel_state == DIALING || + sub->channel_state == OFFHOOK || + sub->channel_state == AWAITONHOOK || + sub->channel_state == CALLING || + sub->channel_state == RINGBACK) + && sub_peer->channel_state == ONHOLD) { + + ast_debug(2, "R while offhook/dialing and peer subchannel on hold\n"); + + brcm_cancel_dialing_timeouts(p); + brcm_reset_dtmf_buffer(p); + p->hf_detected = 0; + + if (sub->channel_state == OFFHOOK || sub->channel_state == AWAITONHOOK) { + brcm_stop_dialtone(p); + } + sub->channel_state = ONHOOK; + + /* Hang up current */ + if(owner) { + ast_queue_control(owner, AST_CONTROL_HANGUP); + } + + /* Pick up old */ + if (peer_owner) { + brcm_unmute_connection(sub_peer); + ast_queue_unhold(peer_owner); + sub_peer->channel_state = INCALL; + } + + /* Switch back to old call (remote hung up) */ + } else if ((sub->channel_state == ONHOOK || sub->channel_state == CALLENDED) + && sub_peer->channel_state == ONHOLD) { + + ast_debug(2, "R when idle and peer subchannel on hold\n"); + + brcm_cancel_dialing_timeouts(p); + p->hf_detected = 0; + + /* Hang up current */ + if (owner) { + ast_queue_control(owner, AST_CONTROL_HANGUP); + } + + /* Pick up old */ + if (peer_owner) { + brcm_unmute_connection(sub_peer); + ast_queue_unhold(peer_owner); + sub_peer->channel_state = INCALL; + } + } + + return; + } + + switch (p->dtmf_first) { + /* Force busy on waiting call or hang up call on hold */ + case '0': + if (sub->channel_state == INCALL && sub_peer->channel_state == CALLWAITING) { + ast_debug(2, "Sending busy to waiting call\n"); + + /* Immediately send busy next time someone calls us during this call */ + sub->cw_rejected = 1; + + if (ast_sched_del(sched, sub_peer->cw_timer_id)) { + ast_log(LOG_WARNING, "Failed to remove scheduled call waiting timer\n"); + } + sub_peer->cw_timer_id = -1; + + ast_queue_control(peer_owner, AST_CONTROL_BUSY); + brcm_busy_all_ringers(); + ast_indicate(owner, AST_CONTROL_UNHOLD); + } else if (sub->channel_state == INCALL && sub_peer->channel_state == ONHOLD) { + ast_debug(2, "Hanging up call on hold\n"); + + sub_peer = brcm_get_onhold_subchannel(p); + + ast_queue_control(peer_owner, AST_CONTROL_HANGUP); + sub_peer->channel_state = CALLENDED; + brcm_close_connection(sub_peer); + } + break; + + /* Hangup current call and answer waiting call */ + case '1': + if (sub->channel_state == INCALL && (sub_peer->channel_state == CALLWAITING || sub_peer->channel_state == ONHOLD)) { + + /* Close connection and hangup active subchannel */ + if (owner) { + ast_queue_control(owner, AST_CONTROL_HANGUP); + } + sub->channel_state = CALLENDED; + brcm_close_connection(sub); + + if (sub_peer->channel_state == CALLWAITING) { + ast_log(LOG_WARNING, "R1 call waiting\n"); + /* Stop call waiting tone on current call */ + brcm_stop_callwaiting(p); + + if (ast_sched_del(sched, sub_peer->cw_timer_id)) { + ast_log(LOG_WARNING, "Failed to remove scheduled call waiting timer\n"); + } + sub_peer->cw_timer_id = -1; + + /* Pick up call waiting */ + if (!sub_peer->connection_init) { + ast_debug(9, "create_connection()\n"); + brcm_create_connection(sub_peer); + } + if (peer_owner) { + ast_queue_control(peer_owner, AST_CONTROL_ANSWER); + sub_peer->channel_state = INCALL; + } + } else if (sub_peer->channel_state == ONHOLD) { + ast_log(LOG_WARNING, "R1 Unholding\n"); + + /* Unhold inactive subchannel */ + if (peer_owner) { + brcm_unmute_connection(sub_peer); + ast_queue_unhold(peer_owner); + sub_peer->channel_state = INCALL; + } + } + } + break; + + /* Answer waiting call and put other call on hold (switch calls) */ + case '2': + if (sub->channel_state == INCALL && (sub_peer->channel_state == CALLWAITING || sub_peer->channel_state == ONHOLD)) { + + brcm_mute_connection(sub); + if (owner) { + ast_queue_hold(owner, NULL); + } + + if (sub_peer->channel_state == CALLWAITING) { + ast_log(LOG_WARNING, "R2 Call waiting\n"); + + /* Stop call waiting tone on current call */ + brcm_stop_callwaiting(p); + + /* Cancel timer */ + if (ast_sched_del(sched, sub_peer->cw_timer_id)) { + ast_log(LOG_WARNING, "Failed to remove scheduled call waiting timer\n"); + } + sub_peer->cw_timer_id = -1; + + /* Pick up call waiting */ + if (!sub_peer->connection_init) { + ast_debug(9, "create_connection()\n"); + brcm_create_connection(sub_peer); + } + if (peer_owner) { + ast_queue_control(peer_owner, AST_CONTROL_ANSWER); + sub_peer->channel_state = INCALL; + } + } else if (sub_peer->channel_state == ONHOLD) { + ast_log(LOG_WARNING, "R2 on hold\n"); + + /* Unhold inactive subchannel */ + if (peer_owner) { + brcm_unmute_connection(sub_peer); + ast_queue_unhold(peer_owner); + sub_peer->channel_state = INCALL; + } + } + + sub->channel_state = ONHOLD; + } + break; + + /* Connect waiting call to existing call to create 3-way */ + case '3': + if (sub->channel_state == INCALL && sub_peer->channel_state == ONHOLD) { + ast_debug(2, "DTMF3 after HF\n"); + brcm_create_conference(p); + } + break; + + /* Remote transfer held call to active call */ + case '4': + ast_debug(2, "R4 Transfer\n"); + if (sub->channel_state == INCALL && sub_peer->channel_state == ONHOLD && + owner && peer_owner) { + struct ast_channel *bridged_chan_inactive = ast_channel_bridge_peer(peer_owner); + struct ast_channel *bridged_chan_active = ast_channel_bridge_peer(owner); + char dest[AST_CHANNEL_NAME * 2] = { '\0', }; + + if (bridged_chan_inactive && bridged_chan_active) { + // Hold the transfer target by default unless being configured no + if (hold_target_before_refer) + ast_queue_hold(owner, NULL); + + // Start the transfer by sending REFER to the transferee + snprintf(dest, sizeof(dest) - 1, "%s?Replaces=%s", + ast_channel_exten(owner), ast_channel_name(bridged_chan_active)); + ast_debug(1, "Start transfer to [%s] on channel %s\n", dest, ast_channel_name(bridged_chan_active)); + int res = -1; + ast_channel_lock(bridged_chan_inactive); + if (!ast_test_flag(ast_channel_flags(bridged_chan_inactive), AST_FLAG_ZOMBIE) && + !ast_check_hangup(bridged_chan_inactive)) { + if (ast_channel_tech(bridged_chan_inactive)->transfer) { + res = ast_channel_tech(bridged_chan_inactive)->transfer(bridged_chan_inactive, dest); + if (res == 0) + res = 1; + } else + res = 0; + } + ast_channel_unlock(bridged_chan_inactive); + if (res < 0) { + ast_log(LOG_ERROR, "ast_transfer() failed\n"); + } else if (res == 0) { + ast_log(LOG_ERROR, "ast_transfer() is not supported on the peer channel\n"); + } else { + sub->channel_state = TRANSFERING; + } + } else { + ast_log(LOG_ERROR, "can't get the peer channel\n"); + } + } + break; + + default: + ast_log(LOG_NOTICE, "Unhandled DTMF %c\n", p->dtmfbuf[0]); + break; + } + + brcm_reset_dtmf_buffer(p); +} + +static void handle_dtmf(enum LINE_EVENT event, + struct brcm_subchannel *sub, struct brcm_subchannel *sub_peer, + struct ast_channel *owner, struct ast_channel *peer_owner) +{ + struct brcm_pvt *p; + const DTMF_CHARNAME_MAP *dtmfMap = dtmf_to_charname; + struct timeval tim; + + /* Lookup event to find corresponding DTMF */ + while (dtmfMap->event != event) { + dtmfMap++; + if (dtmfMap->event == EVENT_LAST) { + /* DTMF not found. Should not be reached. */ + ast_log(LOG_WARNING, "Failed to handle DTMF. Event not found.\n"); + return; + } + } + + char dtmf_button = dtmfMap->c; + gettimeofday(&tim, NULL); + p = sub->parent; + + if (p->dtmf_first < 0) { + p->dtmf_first = dtmf_button; + ast_debug(9,"Pressed DTMF %s\n", dtmfMap->name); + if (owner && brcm_should_relay_dtmf(sub)) { + // INCALL + send_outgoing_dtmf(owner, dtmf_button, AST_FRAME_DTMF_BEGIN); + } + + /* Do not send AST_FRAME_DTMF_BEGIN to allow DSP-generated tone to pass through */ + } + else if (p->dtmf_first == dtmf_button) { + ast_debug(9,"Depressed DTMF %s\n", dtmfMap->name); + //ast_log(LOG_DTMF, "Detected DTMF %c, line %d\n", dtmfMap->c, sub->parent->line_id); + + if (p->hf_detected) { + ast_debug(2, "DTMF after HF\n"); + p->hf_detected = 0; + if (sub->channel_state == INCALL && + (brcm_in_callwaiting(p) || brcm_in_onhold(p) || brcm_in_conference(p))) { + handle_hookflash(sub, sub_peer, owner, peer_owner); + } else { + /* HF while not in a call doesn't make sense */ + ast_debug(2, "DTMF after HF while not in call. \ + state: %d, \ + callwaiting: %d, \ + onhold: %d, \ + conference: %d\n", + sub->channel_state, + brcm_in_callwaiting(p), + brcm_in_onhold(p), + brcm_in_conference(p)); + } + } else { + //ast_debug(5,"DTMF %s in state %s.\n", dtmfMap->name, state2str(sub->channel_state)); + p->dtmfbuf[p->dtmf_len] = dtmf_button; + p->dtmf_len++; + p->dtmfbuf[p->dtmf_len] = '\0'; + p->dtmf_first = -1; + if (sub->channel_state == OFFHOOK) { + sub->channel_state = DIALING; + //ast_debug(5,"Set to state %s.\n", state2str(sub->channel_state)); + } + else if (sub->channel_state != INCALL) { + struct ast_frame f = { 0, }; + f.subclass.integer = dtmf_button; + f.src = "TELCHAN"; + f.frametype = AST_FRAME_DTMF_END; + + //ast_debug(5,"DTMF %s not in call.\n", dtmfMap->name); + + if (owner) { + //ast_debug(5,"DTMF %s queuing frame.\n", dtmfMap->name); + ast_queue_frame(owner, &f); + } + } else { + if (owner) { + // INCALL + send_outgoing_dtmf(owner, dtmf_button, AST_FRAME_DTMF_END); + } + } + } + } + else { + p->dtmf_first = -1; + } +} + +static void send_outgoing_dtmf(struct ast_channel *owner, char dtmf_button, int frametype) { + struct ast_frame fr; + + if (!owner) return; + + if (frametype != AST_FRAME_DTMF_BEGIN && + frametype != AST_FRAME_DTMF_END) { + return; + } + + if (ast_channel_state(owner) != AST_STATE_UP && + ast_channel_state(owner) != AST_STATE_RING) { + return; + } + + memset(&fr, 0, sizeof fr); + ast_debug(2, "Sending DTMF %c %s\n", dtmf_button, ast_channel_name(owner)); + fr.src = brcm_tech.type; + fr.frametype = frametype; + fr.subclass.integer = dtmf_button; + + ast_queue_frame(owner, &fr); +} + +/* Handle audio packets from endptmngr. */ +static void audio_packet_handler(pe_packet_t *p) { + struct brcm_subchannel *sub; + int rtp_packet_type = BRCM_UNKNOWN, drop_frame = 0; + struct ast_frame fr = {0}; + audio_packet_t *ap = (audio_packet_t *)p->data; + struct brcm_pvt *pvt; + + fr.src = "TELCHAN"; + rtp_packet_type = brcm_classify_rtp_packet(ap->rtp[1]); + pvt = brcm_get_pvt_from_lineid(iflist, ap->line); + sub = brcm_get_active_subchannel(pvt); + if (!pvt || !sub) { + ast_log(LOG_ERROR, "Failed to find subchannel for %s/%d/%d\n", + fr.src, ap->line, ap->cnx_id); + endpt_connection(ap->line, ap->cnx_id, "destroy"); // Request line close + return; + } + + //pvt_lock(sub->parent, "brcm monitor packets"); + //ast_mutex_lock(&sub->parent->lock); + struct ast_channel *owner = NULL; + if (sub->owner) { + ast_channel_ref(sub->owner); + owner = sub->owner; + } + + /* We seem to get packets from DSP even if connection is muted (perhaps muting only affects packet callback). + * Drop packets if subchannel is on hold. */ + /* Handle rtp packet according to classification */ + if (sub->channel_state != ONHOLD && rtp_packet_type == BRCM_AUDIO && + (ap->rtp[0] & 0x80) && ap->rtp_size) { + fr.frametype = AST_FRAME_VOICE; + fr.offset = 0; + fr.data.ptr = ap->rtp + 12; + fr.datalen = ap->rtp_size - 12; + + switch (ap->rtp[1]) { + case PCMU: + fr.subclass.format = ast_format_ulaw; + fr.samples = 160; + break; + case PCMA: + fr.subclass.format = ast_format_alaw; + fr.samples = 160; + break; + case G726: + fr.subclass.format = ast_format_g726; + fr.samples = 160; //for 20 ms frame size + break; + case G723: + fr.subclass.format = ast_format_g723; + fr.samples = 240; + break; + case G729: + fr.subclass.format = ast_format_g729; + fr.samples = 80; //for 10 ms frame size + break; + case G722: + fr.subclass.format = ast_format_g722; + fr.samples = 160; + break; + default: + ast_log(LOG_WARNING, "Unknown rtp codec id [%d]\n", p->data[1]); + return; + } + } else { + //ast_debug(5, "Dropping RTP frame of type %d.\n", rtp_packet_type); + drop_frame=1; + //pvt_unlock(sub->parent); + } + //ast_mutex_unlock(&sub->parent->lock); + //pvt_unlock(sub->parent); + + if (owner) { + if (!drop_frame && (ast_channel_state(owner) == AST_STATE_UP || ast_channel_state(owner) == AST_STATE_RING)) { + struct ast_frame *cfr = NULL; + ast_queue_frame(owner, &fr); + if (cfr) { + ast_queue_frame(owner, cfr); + } + } + ast_channel_unref(owner); + } +} + +static void brcm_cancel_dialing_timeouts(struct brcm_pvt *p) +{ + //If we have interdigit timeout, cancel it + if (p->interdigit_timer_id > 0) { + p->interdigit_timer_id = ast_sched_del(sched, p->interdigit_timer_id); + } + + //If we have a autodial timeout, cancel it + if (p->autodial_timer_id > 0) { + p->autodial_timer_id = ast_sched_del(sched, p->autodial_timer_id); + } + + //If we have a dialtone timeout, cancel it + if (p->dialtone_timeout_timer_id > 0) { + p->dialtone_timeout_timer_id = ast_sched_del(sched, p->dialtone_timeout_timer_id); + } +} + +static int brcm_should_relay_dtmf(const struct brcm_subchannel *sub) +{ + if (sub->channel_state == INCALL && sub->parent->hf_detected == 0) { + return 1; + } + return 0; +} + +static int is_sip_account_registered(const char *sip_account) +{ + int res = 0; + const char *cmd = "sip show registry"; + char template[] = "/tmp/ast-brcm-XXXXXX"; /* template for temporary file */ + char line[160]; + int fd_temp = -1; + FILE *fp = NULL; + + if ((fd_temp = mkstemp(template)) < 0) { + ast_log(LOG_ERROR, "Failed to create temporary file for command \"%s\": %s\n", + cmd, strerror(errno)); + return res; + } + + if (ast_cli_command(fd_temp, cmd) == 0) { + close(fd_temp); + fd_temp = -1; + + fp = fopen(template, "r"); + if (fp) { + while (fgets(line, sizeof(line), fp) != NULL) { + /* The example output of the command is shown below. + * Some extra spaces are removed since the line is too long. + * sip0:5060 N 1313@10.0.2. 285 Registered Mon, 17 Jun 2019 17:12:58 */ + if (strstr(line, sip_account) == line && strstr(line, "Registered") != NULL) { + res = 1; + break; + } + } + } + } else { + ast_log(LOG_ERROR, "Execution CLI \"%s\" failed\n", cmd); + } + + if (fd_temp >= 0) + close(fd_temp); + if (fp) + fclose(fp); + unlink(template); + return res; +} + +static void *brcm_process_event(struct endpt_event *ev) { + struct brcm_pvt *p = NULL; + struct brcm_subchannel *sub = NULL; + + ast_debug(9, "Event %s detected\n", ev->name); + p = brcm_get_pvt_from_lineid(iflist, ev->line); + if (!p) { + ast_debug(3, "No pvt with the correct line_id %d found!\n", ev->line); + return NULL; + } + + /* Get locks in correct order */ + //ast_mutex_lock(&p->lock); + pvt_lock(p, "brcm monitor events"); + sub = brcm_get_active_subchannel(p); + struct brcm_subchannel *sub_peer = brcm_subchannel_get_peer(sub); + struct ast_channel *owner = NULL; + struct ast_channel *peer_owner = NULL; + if (sub->owner) { + ast_channel_ref(sub->owner); + owner = sub->owner; + } + if (sub_peer->owner) { + ast_channel_ref(sub_peer->owner); + peer_owner = sub_peer->owner; + } + pvt_unlock(p); + //ast_mutex_unlock(&p->lock); + + if (owner && peer_owner) { + if (owner < peer_owner) { + ast_channel_lock(owner); + ast_channel_lock(peer_owner); + } + else { + ast_channel_lock(peer_owner); + ast_channel_lock(owner); + } + } + else if (owner) { + ast_channel_lock(owner); + } + else if (peer_owner) { + ast_channel_lock(peer_owner); + } + pvt_lock(p, "brcm monitor events"); + //ast_mutex_lock(&p->lock); + + ast_debug(3, "me: got mutex\n"); + if (sub) { + + switch (ev->event) { + case EVENT_OFFHOOK: { + ast_debug(9, "EVENT_OFFHOOK detected\n"); + + /* Reset the dtmf buffer */ + memset(p->dtmfbuf, 0, sizeof(p->dtmfbuf)); + p->dtmf_len = 0; + p->dtmf_first = -1; + p->dtmfbuf[p->dtmf_len] = '\0'; + sub->channel_state = OFFHOOK; + + if (owner) { + if (!sub->connection_init) { + ast_debug(9, "create_connection()\n"); + brcm_stop_dialtone(p); + brcm_create_connection(sub); + } + + if (sub->cw_timer_id > -1) { + /* Picking up during reminder ringing for call waiting */ + if (ast_sched_del(sched, sub->cw_timer_id) < 0) + ast_debug(3, "Error deleting timer\n"); + sub->cw_timer_id = -1; + } + + sub->channel_state = INCALL; + ast_queue_control(owner, AST_CONTROL_ANSWER); + //TODO Ronny: ast_channel_queue_connected_line_update() ? + } + else if (sub_peer->channel_state == ONHOLD) { + + /* Picking up during reminder ringing for call on hold */ + if (ast_sched_del(sched, sub_peer->onhold_hangup_timer_id) < 0) + ast_debug(3, "Error deleting timer\n"); + sub_peer->onhold_hangup_timer_id = -1; + + sub->channel_state = CALLENDED; + sub_peer->channel_state = INCALL; + ast_queue_control(peer_owner, AST_CONTROL_ANSWER); + + brcm_unmute_connection(sub_peer); + + endpt_connection(sub_peer->parent->line_id, sub_peer->connection_id, "create"); + ast_queue_unhold(peer_owner); + } + else if (sub->channel_state == OFFHOOK) { + /* EVENT_OFFHOOK changed endpoint state to OFFHOOK, apply dialtone */ + ast_debug(9, "Resetting dial tones.\n"); + if ( p->context[0] != '\0' && is_sip_account_registered(p->context)) { + p->dialtone = DIALTONE_ON; + brcm_signal_dialtone(p); + } + + line_settings *s = &line_config[p->line_id]; + + if (strlen(s->autodial_ext)) { + /* Schedule autodial timeout if autodial extension is set */ + p->autodial_timer_id = ast_sched_add(sched, s->autodial_timeoutmsec, handle_autodial_timeout, p); + } + else { + /* No autodial, schedule dialtone timeout */ + p->dialtone_timeout_timer_id = ast_sched_add(sched, s->dialtone_timeoutmsec, handle_dialtone_timeout, p); + } + } + break; + } + case EVENT_ONHOOK: { + ast_debug(9, "EVENT_ONHOOK detected\n"); + if (sub->channel_state == OFFHOOK || sub->channel_state == AWAITONHOOK) { + /* Received EVENT_ONHOOK in state OFFHOOK/AWAITONHOOK, stop dial/congestion tone */ + brcm_stop_dialtone(p); + } + + sub->channel_state = ONHOOK; + brcm_cancel_dialing_timeouts(p); + + /* Reset the dtmf buffer */ + memset(p->dtmfbuf, 0, sizeof(p->dtmfbuf)); + p->dtmf_len = 0; + p->dtmf_first = -1; + p->dtmfbuf[p->dtmf_len] = '\0'; + + if (owner) { + ast_queue_control(owner, AST_CONTROL_HANGUP); + endpt_connection(sub->parent->line_id, sub->connection_id, "destroy"); + } + + //TODO: possible bug below - we don't change the channel_state when hanging up + if (sub_peer->channel_state == CALLWAITING) { + /* Remind user of waiting call */ + sub_peer->channel_state = RINGING; + p->tech->signal_ringing(p); //TODO: This should use CCSS "ringing signal" + } else if (sub_peer->channel_state == ONHOLD) { + /* Remind user of call on hold */ + sub_peer->onhold_hangup_timer_id = ast_sched_add(sched, onholdhanguptimeout * 1000, onholdhanguptimeout_cb, sub_peer); + ast_debug(2, "Forgotten call on hold for %s!\n", ast_channel_name(peer_owner)); + endpt_connection(sub_peer->parent->line_id, sub_peer->connection_id, "destroy"); + p->tech->signal_ringing_callerid_pending(p); + p->tech->signal_callerid(sub_peer->owner, sub_peer); //TODO: This should use CCSS "ringing signal" + sub->channel_state = RINGING; + ast_queue_control(peer_owner, AST_CONTROL_RINGING); + } else if (peer_owner && sub_peer->channel_state != TRANSFERING) { + /* Hangup peer subchannels in call or on hold */ + ast_debug(2, "Hanging up call (not transfering)\n"); + ast_queue_control(peer_owner, AST_CONTROL_HANGUP); + } + break; + } + case EVENT_DTMF0: + case EVENT_DTMF1: + case EVENT_DTMF2: + case EVENT_DTMF3: + case EVENT_DTMF4: + case EVENT_DTMF5: + case EVENT_DTMF6: + case EVENT_DTMF7: + case EVENT_DTMF8: + case EVENT_DTMF9: + case EVENT_DTMFA: + case EVENT_DTMFB: + case EVENT_DTMFC: + case EVENT_DTMFD: + case EVENT_DTMFS: + case EVENT_DTMFH: + { + brcm_cancel_dialing_timeouts(p); + + unsigned int old_state = sub->channel_state; + ast_debug(2, "====> GOT DTMF %d\n", ev->event); + //ast_debug(5, "State = %s\n", state2str(sub->channel_state)); + handle_dtmf(ev->event, sub, sub_peer, owner, peer_owner); + if (sub->channel_state == DIALING && old_state != sub->channel_state) { + /* DTMF event took channel state to DIALING. Stop dial tone. */ + ast_debug(2, "Dialing. Stop dialtone.\n"); + brcm_stop_dialtone(p); + } + + if (sub->channel_state == DIALING) { + ast_debug(2, "Handle DTMF calling\n"); + handle_dtmf_calling(sub); + } + break; + } + case EVENT_FLASH: + ast_debug(1, "EVENT_FLASH\n"); + p->hf_detected = 1; + + /* Schedule hook flash timeout. Until hook flash is handled or timeout expires, no + * dtmf will be relayed to asterisk. */ + int timeoutmsec = line_config[p->line_id].timeoutmsec; + p->interdigit_timer_id = ast_sched_add(sched, timeoutmsec, handle_hookflash_timeout, p); + + handle_hookflash(sub, sub_peer, owner, peer_owner); + break; + case EVENT_EARLY_OFFHOOK: + ast_debug(1, "EVENT_EARLY_OFFHOOK\n"); + break; + case EVENT_EARLY_ONHOOK: + ast_debug(1, "EVENT_EARLY_ONHOOK\n"); + break; + default: + /* ast_debug(1, "UNKNOWN event %d detected\n", tEventParm.event); */ + break; + } + } + + pvt_unlock(p); + ast_debug(9, "me: unlocked mutex\n"); + + if (owner) { + ast_channel_unlock(owner); + ast_channel_unref(owner); + } + + if (peer_owner) { + ast_channel_unlock(peer_owner); + ast_channel_unref(peer_owner); + } + + return NULL; +} + +/* Load settings for each line */ +static void brcm_initialize_pvt(struct brcm_pvt *p) +{ + line_settings *s = &line_config[p->line_id]; + + ast_copy_string(p->language, s->language, sizeof(p->language)); + ast_copy_string(p->context, s->context, sizeof(p->context)); + ast_copy_string(p->context_direct, s->context_direct, sizeof(p->context_direct)); + ast_copy_string(p->cid_num, s->cid_num, sizeof(p->cid_num)); + ast_copy_string(p->cid_name, s->cid_name, sizeof(p->cid_name)); +} + +static struct brcm_pvt *brcm_allocate_pvt(void) +{ + /* Make a brcm_pvt structure for this interface */ + struct brcm_pvt *tmp; + + tmp = ast_calloc(1, sizeof(*tmp)); + if (tmp) { + struct brcm_subchannel *sub; + int i; + + for (i=0; i<NUM_SUBCHANNELS; i++) { + sub = ast_calloc(1, sizeof(*sub)); + if (sub) { + sub->id = i; + sub->owner = NULL; + sub->connection_id = -1; + sub->connection_init = 0; + sub->channel_state = ONHOOK; + sub->time_stamp = 0; + sub->sequence_number = 0; + sub->ssrc = 0; + sub->codec = -1; + sub->parent = tmp; + sub->cw_timer_id = -1; + sub->r4_hangup_timer_id = -1; + sub->onhold_hangup_timer_id = -1; + sub->period = 20; // 20 ms + sub->conference_initiator = 0; + tmp->sub[i] = sub; + ast_debug(2, "subchannel created\n"); + } else { + ast_log(LOG_ERROR, "no subchannel created\n"); + } + } + tmp->line_id = -1; + tmp->dtmf_len = 0; + tmp->dtmf_first = -1; + tmp->lastformat = NULL; + tmp->lastinput = NULL; + memset(tmp->ext, 0, sizeof(tmp->ext)); + tmp->next = NULL; + tmp->dialtone = DIALTONE_UNKNOWN; + tmp->interdigit_timer_id = -1; + tmp->autodial_timer_id = -1; + ast_mutex_init(&tmp->lock); + tmp->tech = &fxs_tech; + } + return tmp; +} + + +static void brcm_create_pvts(struct brcm_pvt *p, int mode) { + int i; + struct brcm_pvt *tmp = iflist; + struct brcm_pvt *tmp_next; + + for (i=0; i<num_endpoints ; i++) { + tmp_next = brcm_allocate_pvt(); + if (tmp == NULL) { + iflist = tmp_next; //First loop round, set iflist to point at first pvt + tmp = tmp_next; + tmp->next = NULL; + } else { + tmp->next = tmp_next; + tmp_next->next = NULL; + tmp = tmp_next; + } + } +} + +static void brcm_assign_line_id(struct brcm_pvt *p) +{ + struct brcm_pvt *tmp = p; + int i; + /* Assign line_id's */ + for (i=0 ; i<num_endpoints ; i++) { + tmp->line_id = i; + brcm_initialize_pvt(tmp); + int j; + for (j=0; j<NUM_SUBCHANNELS; j++) { + tmp->sub[j]->channel_state = ONHOOK; + } + tmp = tmp->next; + } +} + + +static int brcm_in_call(const struct brcm_pvt *p) +{ + int i; + for (i=0; i<NUM_SUBCHANNELS; i++) { + if (p->sub[i]->channel_state == INCALL) { + return 1; + } + } + + return 0; +} + +static int brcm_in_callwaiting(const struct brcm_pvt *p) +{ + int i; + for (i=0; i<NUM_SUBCHANNELS; i++) { + if (p->sub[i]->channel_state == CALLWAITING) { + return 1; + } + } + + return 0; +} + +static int brcm_in_transferring(const struct brcm_pvt *p) +{ + int i; + for (i=0; i<NUM_SUBCHANNELS; i++) { + if (p->sub[i]->channel_state == TRANSFERING) { + return 1; + } + } + + return 0; +} + +static int brcm_in_onhold(const struct brcm_pvt *p) +{ + int i; + for (i=0; i<NUM_SUBCHANNELS; i++) { + if (p->sub[i]->channel_state == ONHOLD) { + return 1; + } + } + + return 0; +} + +static int brcm_in_conference(const struct brcm_pvt *p) +{ + return (p->sub[0]->channel_state == INCALL && + p->sub[1]->channel_state == INCALL) || + p->sub[0]->conference_initiator || + p->sub[1]->conference_initiator; +} + +/* + * Return idle subchannel + */ +struct brcm_subchannel *brcm_get_idle_subchannel(const struct brcm_pvt *p) +{ + int i; + for (i=0; i<NUM_SUBCHANNELS; i++) { + if (p->sub[i]->channel_state == ONHOOK || p->sub[i]->channel_state == CALLENDED) { + return p->sub[i]; + } + } + return NULL; +} + +static struct ast_channel *brcm_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *dest, int *cause) +{ + struct brcm_pvt *p; + struct brcm_subchannel *sub = NULL; + struct ast_channel *tmp = NULL; + struct ast_str * buf = ast_str_create(256); + int line_id = -1; + + ast_format_cap_get_names(cap, &buf); + ast_debug(1, "Asked to create a channel with formats: %s\n", ast_str_buffer(buf)); + + /* Search for an unowned channel */ + if (ast_mutex_lock(&iflock)) { + ast_log(LOG_ERROR, "Unable to lock interface list???\n"); + return NULL; + } + + + /* Get line id */ + line_id = atoi((char*)dest); + //ast_debug(1, "brcm_request = %s, line_id=%d, format %x\n", (char*) data, line_id, (unsigned int) format); + + /* Map id to the correct pvt */ + p = brcm_get_pvt_from_lineid(iflist, line_id); + + /* If the id doesn't exist (p==NULL) use 0 as default */ + if (!p) { + ast_log(LOG_ERROR, "Port id %s not found using default 0 instead.\n", (char*) dest); + p = iflist; + } + + pvt_lock(p, "brcm request"); + //ast_mutex_lock(&p->lock); + + sub = brcm_get_idle_subchannel(p); + + /* Check that the request has an allowed format */ + if (!(ast_format_cap_has_type(cap, AST_MEDIA_TYPE_AUDIO))) { + struct ast_str *codec_buf = ast_str_alloca(64); + ast_log(LOG_NOTICE, "Asked to get a channel of unsupported format '%s'\n", ast_format_cap_get_names(cap, &codec_buf)); + return NULL; + } else if (sub) { + sub->channel_state = ALLOCATED; + sub->connection_id = ast_atomic_fetchadd_int((int *)¤t_connection_id, +1); + tmp = brcm_new(sub, AST_STATE_DOWN, sub->parent->ext, sub->parent->context, assignedids, requestor ? requestor : NULL, cap); + } else { + *cause = AST_CAUSE_BUSY; + } + + //ast_mutex_unlock(&p->lock); + pvt_unlock(p); + ast_mutex_unlock(&iflock); + + return tmp; +} + + +static void brcm_lock_pvts(void) +{ + struct brcm_pvt *p = iflist; + while(p) { + pvt_lock(p, "brcm lock pvts"); + //ast_mutex_lock(&p->lock); + p = brcm_get_next_pvt(p); + } +} + +static void brcm_unlock_pvts(void) +{ + struct brcm_pvt *p = iflist; + while(p) { + pvt_unlock(p); + //ast_mutex_unlock(&p->lock); + p = brcm_get_next_pvt(p); + } +} + +static void brcm_show_subchannels(struct ast_cli_args *a, struct brcm_pvt *p) +{ + struct brcm_subchannel *sub; + + /* Output status for sub channels */ + int i; + for (i=0; i<NUM_SUBCHANNELS; i++) { + sub = p->sub[i]; + ast_cli(a->fd, "Subchannel: %d\n", sub->id); + ast_cli(a->fd, " Connection id : %d\n", sub->connection_id); + + ast_cli(a->fd, " Owner : %p\n", sub->owner); + ast_cli(a->fd, " Channel state : %s\n", state2str(sub->channel_state)); + ast_cli(a->fd, " Connection init : %d\n", sub->connection_init); + ast_cli(a->fd, " Codec used : %s\n", brcm_get_codec_string(sub->codec)); + ast_cli(a->fd, " RTP sequence number : %d\n", sub->sequence_number); + ast_cli(a->fd, " RTP SSRC : %d\n", sub->ssrc); + ast_cli(a->fd, " RTP timestamp : %d\n", sub->time_stamp); + ast_cli(a->fd, " CW Timer id : %d\n", sub->cw_timer_id); + ast_cli(a->fd, " CW Rejected : %d\n", sub->cw_rejected); + ast_cli(a->fd, " R4 Hangup Timer id : %d\n", sub->r4_hangup_timer_id); + ast_cli(a->fd, " Conference initiator: %d\n", sub->conference_initiator); + ast_cli(a->fd, " Onhold Hangup Timer id: %d\n", sub->onhold_hangup_timer_id); + } +} + +static void brcm_show_pvts(struct ast_cli_args *a) +{ + struct brcm_pvt *p = iflist; + int i = 0; + + while(p) { + pvt_lock(p, "brcm show pvts"); + //ast_mutex_lock(&p->lock); + ast_cli(a->fd, "\nPvt nr: %d\n",i); + ast_cli(a->fd, "Line id : %d\n", p->line_id); + ast_cli(a->fd, "Pvt next ptr : 0x%x\n", (unsigned int) p->next); + ast_cli(a->fd, "DTMF buffer : %s\n", p->dtmfbuf); + ast_cli(a->fd, "Default context : %s\n", p->context); + ast_cli(a->fd, "Direct context : %s\n", p->context_direct); + line_settings* s = &line_config[p->line_id]; + + ast_cli(a->fd, "Ringsignal : %s\n", s->ringsignal ? "on" : "off"); + ast_cli(a->fd, "Dialout msecs : %d\n", s->timeoutmsec); + ast_cli(a->fd, "Autodial extension : %s\n", s->autodial_ext); + ast_cli(a->fd, "Autodial msecs : %d\n", s->autodial_timeoutmsec); + ast_cli(a->fd, "Dialt. timeout msecs: %d\n", s->dialtone_timeoutmsec); + + ast_cli(a->fd, "Ast JitterBuf impl : %s\n", global_jbconf.impl); + ast_cli(a->fd, "Ast JitterBuf max : %ld\n", global_jbconf.max_size); + ast_cli(a->fd, "Call waiting : %s\n", s->callwaiting ? "on" : "off"); + ast_cli(a->fd, "CLIR : %s\n", s->clir ? "on" : "off"); + + ast_cli(a->fd, "Dialtone : "); + const DIALTONE_MAP *dialtone = dialtone_map; + while (dialtone->state != DIALTONE_LAST) { + if (dialtone->state == p->dialtone) { + break; + } + dialtone++; + } + ast_cli(a->fd, "%s\n", dialtone->str); + + /* Print status for subchannels */ + brcm_show_subchannels(a, p); + + ast_cli(a->fd, "\n"); + + i++; + pvt_unlock(p); + //ast_mutex_unlock(&p->lock); + p = brcm_get_next_pvt(p); + } +} + +/*! \brief CLI for showing brcm status. + * This is a new-style CLI handler so a single function contains + * the prototype for the function, the 'generator' to produce multiple + * entries in case it is required, and the actual handler for the command. + */ + +static char *brcm_show_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + char buffer[AST_MAX_EXTENSION]; + + if (cmd == CLI_INIT) { + e->command = "telephony show status"; + e->usage = + "Usage: telephony show status\n" + " Shows the current chan_brcm status.\n"; + return NULL; + } else if (cmd == CLI_GENERATE) + return NULL; + + /* print chan brcm status information */ + ast_cli(a->fd, "Channel version: %s\n\n", CHANNEL_VERSION); + ast_cli(a->fd, "Number of endpoints: %d\n", num_endpoints); + ast_cli(a->fd, "FAC list : %s\n", feature_access_code_string(buffer, AST_MAX_EXTENSION)); + + /* print status for individual pvts */ + brcm_show_pvts(a); + + return CLI_SUCCESS; +} + +/*! \brief CLI for showing brcm dialtone status. */ +static char *brcm_show_dialtone_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + if (cmd == CLI_INIT) { + e->command = "brcm show dialtone status"; + e->usage = + "Usage: brcm show dialtone status\n" + " Shows the current chan_brcm dialtone status.\n"; + return NULL; + } + else if (cmd == CLI_GENERATE) { + return NULL; + } + + struct brcm_pvt *p = iflist; + int i = 0; + + ast_cli(a->fd, "Pvt nr\tDialtone\n\n"); + while(p) { + const DIALTONE_MAP *dialtone = dialtone_map; + while (dialtone->state != DIALTONE_LAST) { + if (dialtone->state == p->dialtone) { + break; + } + dialtone++; + } + ast_cli(a->fd, "%d\t%s\n", i, dialtone->str); + + i++; + p = brcm_get_next_pvt(p); + } + + return CLI_SUCCESS; +} + +/*! \brief CLI for reloading brcm config. */ +static char *brcm_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct ast_config *cfg = NULL; + if (cmd == CLI_INIT) { + e->command = "telephony reload"; + e->usage = + "Usage: telephony reload\n" + " Reload chan_brcm configuration.\n"; + return NULL; + } else if (cmd == CLI_GENERATE) { + return NULL; + } + + ast_mutex_lock(&iflock); + + /* Acquire locks for all pvt:s to prevent nasty things from happening */ + brcm_lock_pvts(); + + feature_access_code_clear(); + + /* Reload configuration */ + if (load_common_settings(&cfg)) { + brcm_unlock_pvts(); + ast_mutex_unlock(&iflock); + return CLI_FAILURE; + } + + load_lines_settings(cfg); + struct brcm_pvt *p = iflist; + while(p) { + brcm_initialize_pvt(p); + p = brcm_get_next_pvt(p); + } + + brcm_unlock_pvts(); + ast_mutex_unlock(&iflock); + + ast_verbose("TELCHAN reload done\n"); + + return CLI_SUCCESS; +} + +static char *brcm_set_parameters_on_off(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + int on_off = 0; + + if (cmd == CLI_INIT) { + e->command = "brcm set {echocancel|ringsignal} {on|off}"; + e->usage = + "Usage: brcm set {echocancel|ringsignal} {on|off} PvtNr\n" + " echocancel, echocancel mode.\n" + " ringsignal, ring signal mode.\n" + " PvtNr, the Pvt to modify.\n"; + return NULL; + } else if (cmd == CLI_GENERATE) { + return NULL; + } + + if (a->argc <= 4) { + return CLI_SHOWUSAGE; //Too few arguments + } + + int pvt_id = atoi(a->argv[4]); + if (pvt_id >= num_endpoints || pvt_id < 0) { + return CLI_SHOWUSAGE; + } + line_settings *s = &line_config[pvt_id]; + + if (!strcasecmp(a->argv[3], "on")) { + on_off = 1; + } else { + on_off = 0; + } + + if (!strcasecmp(a->argv[2], "ringsignal")) { + s->ringsignal = on_off; + } + return CLI_SUCCESS; +} + +static char *brcm_set_parameters_value(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + if (cmd == CLI_INIT) { + e->command = "brcm set dialout_msecs"; + e->usage = + "Usage: brcm set dialout_msecs 4000 PvtNr\n" + " dialout_msecs, dialout delay in msecs.\n" + " PvtNr, the Pvt to modify.\n"; + return NULL; + } else if (cmd == CLI_GENERATE) + return NULL; + + if (a->argc <= 4) + return CLI_SHOWUSAGE; + + int pvt_id = atoi(a->argv[4]); + if (pvt_id >= num_endpoints || pvt_id < 0) { + return CLI_SHOWUSAGE; + } + line_settings *s = &line_config[pvt_id]; + + s->timeoutmsec = atoi(a->argv[3]); + + return CLI_SUCCESS; +} + +static char *brcm_set_autodial_extension(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct brcm_pvt *p; + + if (cmd == CLI_INIT) { + e->command = "brcm set autodial"; + e->usage = + "Usage: brcm set autodial 0 1234\n" + " brcm set autodial 0 \"\"\n" + " autodial, extension to autodial on of hook.\n"; + return NULL; + } else if (cmd == CLI_GENERATE) + return NULL; + + if (a->argc <= 4) + return CLI_SHOWUSAGE; + +// ast_verbose("%d %s",(a->argv[3][0] -'0'), a->argv[4]); + + p = iflist; + while(p) { + if (p->line_id == (a->argv[3][0]-'0')) { + line_settings *s = &line_config[p->line_id]; + ast_copy_string(s->autodial_ext, a->argv[4], sizeof(s->autodial_ext)); + break; + } + p = brcm_get_next_pvt(p); + } + + return CLI_SUCCESS; +} + + +/*! \brief Channel CLI commands definition */ +static struct ast_cli_entry cli_brcm[] = { + AST_CLI_DEFINE(brcm_show_status, "Show chan_brcm status"), + AST_CLI_DEFINE(brcm_show_dialtone_status, "Show chan_brcm dialtone status"), + AST_CLI_DEFINE(brcm_set_parameters_on_off, "Set chan_brcm parameters"), + AST_CLI_DEFINE(brcm_set_parameters_value, "Set chan_brcm dialout msecs"), + AST_CLI_DEFINE(brcm_set_autodial_extension, "Set chan_brcm autodial extension"), + AST_CLI_DEFINE(brcm_reload, "Reload chan_brcm configuration"), +}; + + +static int unload_module(void) +{ + struct brcm_pvt *p, *pl; + + //ast_sched_dump(sched); + + /* First, take us out of the channel loop */ + if (cur_tech) + ast_channel_unregister(cur_tech); + if (!ast_mutex_lock(&iflock)) { + /* Hangup all interfaces if they have an owner */ + p = iflist; + while(p) { + int i; + pvt_lock(p, "brcm unload module"); + //ast_mutex_lock(&p->lock); + for (i=0; i<NUM_SUBCHANNELS; i++) { + struct ast_channel *owner = p->sub[i]->owner; + if (owner) { + ast_channel_ref(owner); + ast_mutex_unlock(&p->lock); + ast_softhangup(owner, AST_SOFTHANGUP_APPUNLOAD); + ast_channel_unref(owner); + ast_mutex_lock(&p->lock); + } + } + free(registration_change_sub); + registration_change_sub = NULL; + pvt_unlock(p); + //ast_mutex_unlock(&p->lock); + p = p->next; + } + iflist = NULL; + ast_mutex_unlock(&iflock); + } else { + ast_log(LOG_WARNING, "Unable to lock the monitor\n"); + return -1; + } + if (!ast_mutex_lock(&monlock)) { + ast_debug(1, "Stopping threads...\n"); + if (monitor) { + monitor = 0; + while (pthread_kill(monitor_thread, SIGURG) == 0) + sched_yield(); + pthread_join(monitor_thread, NULL); + } + monitor_thread = AST_PTHREADT_STOP; + + if (packets) { + packets = 0; + while (pthread_kill(packet_thread, SIGURG) == 0) + sched_yield(); + pthread_join(packet_thread, NULL); + } + packet_thread = AST_PTHREADT_STOP; + + ast_mutex_unlock(&monlock); + } else { + ast_log(LOG_WARNING, "Unable to lock the monitor\n"); + return -1; + } + ast_debug(1, "[%d, %d,]\n",monitor, packets); + + if (!ast_mutex_lock(&iflock)) { + /* Destroy all the interfaces and free their memory */ + p = iflist; + while(p) { + /* Close the socket, assuming it's real */ + pl = p; + p = p->next; + /* Free associated memory */ + ast_free(pl); + } + iflist = NULL; + ast_mutex_unlock(&iflock); + } else { + ast_log(LOG_WARNING, "Unable to lock the monitor\n"); + return -1; + } + + /* Unregister CLI commands */ + ast_cli_unregister_multiple(cli_brcm, ARRAY_LEN(cli_brcm)); + + feature_access_code_clear(); + ast_sched_context_destroy(sched); + + return 0; +} + +/* + * Create a line_settings struct with default values. + */ +static line_settings line_settings_create(void) +{ + line_settings line_conf = (line_settings){ + .language = "", + .cid_num = "", + .cid_name = "", + .context_direct = "default-direct", + .context = "default", + .autodial_ext = "", + .ringsignal = 1, + .timeoutmsec = 4000, + .autodial_timeoutmsec = 60000, + .period = 20, + .hangup_xfer = 0, + .dialtone_timeoutmsec = 20000, + .callwaiting = 1, + .clir = 0, + }; + return line_conf; +} + +/* + * Load config file settings into the specified line_settings struct. + * Can be called multiple times in order to load from multiple ast_variables. + */ +static void line_settings_load(line_settings *line_config, struct ast_variable *v) +{ + while(v) { + if (!strcasecmp(v->name, "language")) { + ast_copy_string(line_config->language, v->value, sizeof(line_config->language)); + } else if (!strcasecmp(v->name, "callerid")) { + ast_callerid_split(v->value, line_config->cid_name, sizeof(line_config->cid_name), line_config->cid_num, sizeof(line_config->cid_num)); + } else if (!strcasecmp(v->name, "context")) { + ast_copy_string(line_config->context, v->value, sizeof(line_config->context)); + } else if (!strcasecmp(v->name, "context_direct")) { + ast_copy_string(line_config->context_direct, v->value, sizeof(line_config->context_direct)); + } else if (!strcasecmp(v->name, "autodial")) { + ast_copy_string(line_config->autodial_ext, v->value, sizeof(line_config->autodial_ext)); + } else if (!strcasecmp(v->name, "ringsignal")) { + line_config->ringsignal = ast_true(v->value)?1:0; + } else if (!strcasecmp(v->name, "dialoutmsec")) { + line_config->timeoutmsec = atoi(v->value); + } else if (!strcasecmp(v->name, "autodial_timeoutmsec")) { + line_config->autodial_timeoutmsec = atoi(v->value); + } else if (!strcasecmp(v->name, "dialtone_timeoutmsec")) { + line_config->dialtone_timeoutmsec = atoi(v->value); + } + else if (!strcasecmp(v->name, "hangup_xfer")) { + line_config->hangup_xfer = ast_true(v->value)?1:0; + } + else if (!strcasecmp(v->name, "callwaiting")) { + line_config->callwaiting = ast_true(v->value)?1:0; + } + else if (!strcasecmp(v->name, "clir")) { + line_config->clir = ast_true(v->value)?1:0; + } + + v = v->next; + } +} + +static int load_common_settings(struct ast_config **cfg) +{ + struct ast_flags config_flags = { 0 }; + + /* Set default values */ + hold_target_before_refer = 1; + + if ((*cfg = ast_config_load(config, config_flags)) == CONFIG_STATUS_FILEINVALID) { + ast_log(LOG_ERROR, "Config file %s is in an invalid format. Aborting.\n", config); + return AST_MODULE_LOAD_DECLINE; + } + + /* We *must* have a config file otherwise stop immediately */ + if (!(*cfg)) { + ast_log(LOG_ERROR, "Unable to load config %s\n", config); + return AST_MODULE_LOAD_DECLINE; + } + + /* Load jitterbuffer defaults, Copy the default jb config over global_jbconf */ + memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf)); + + /* Load global settings */ + struct ast_variable *v; + v = ast_variable_browse(*cfg, "default"); + + while(v) { + if (!ast_jb_read_conf(&global_jbconf, v->name, v->value)) { + ast_debug(2, "Loaded jitterbuffer settings '%s'\n", v->value); + v = v->next; + continue; + } + + if (!strcasecmp(v->name, "cwtimeout")) { + cwtimeout = atoi(v->value); + if (cwtimeout > 60 || cwtimeout < 0) { + cwtimeout = DEFAULT_CALL_WAITING_TIMEOUT; + ast_log(LOG_WARNING, "Incorrect cwtimeout '%s', defaulting to '%d'\n", v->value, cwtimeout); + } + } else if (!strcasecmp(v->name, "r4hanguptimeout")) { + r4hanguptimeout = atoi(v->value); + if (r4hanguptimeout > 30000 || r4hanguptimeout < 0) { + r4hanguptimeout = DEFAULT_R4_HANGUP_TIMEOUT; + ast_log(LOG_WARNING, "Incorrect r4hanguptimeout '%s', defaulting to '%d'\n", + v->value, r4hanguptimeout); + } + } else if (!strcasecmp(v->name, "onholdhanguptimeout")) { + onholdhanguptimeout = atoi(v->value); + if (onholdhanguptimeout > 60 || onholdhanguptimeout < 0) { + onholdhanguptimeout = DEFAULT_ONHOLD_HANGUP_TIMEOUT; + ast_log(LOG_WARNING, "Incorrect onholdhanguptimeout '%s', defaulting to '%d'\n", + v->value, onholdhanguptimeout); + } + } else if (!strcasecmp(v->name, "hold_target_before_refer")) { + if (!strcasecmp(v->value, "no")) { + ast_debug(1, "The transfer target will not be put on-hold before sending REFER\n"); + hold_target_before_refer = 0; + } + } else if (!strcasecmp(v->name, "featureaccesscodes")) { + char *tok; + + if (ast_strlen_zero(v->value)) { + ast_debug(1, "No value given for featureaccesscodes on line %d\n", v->lineno); + } + else { + tok = strtok(ast_strdupa(v->value), ","); + while (tok) { + char *code = ast_strdupa(tok); + code = ast_strip(code); + + feature_access_code_add(code); + + tok = strtok(NULL, ","); + } + } + } + + v = v->next; + } + + return 0; +} + +// Load settings fore all lines +static void load_lines_settings(struct ast_config *cfg) +{ + struct ast_variable *v; + + int i; + for (i = 0; i < num_endpoints; i++) { + // Create and init a new settings struct + line_config[i] = line_settings_create(); + // Load default settings + v = ast_variable_browse(cfg, "default"); + line_settings_load(&line_config[i], v); + // Load per line specific settings + char config_section[64]; + snprintf(config_section, 64, "telline%d", i); + v = ast_variable_browse(cfg, config_section); + if (!v) { + ast_log(LOG_WARNING, "Unable to load endpoint specific config (missing config section?): %s\n", config_section); + } + line_settings_load(&line_config[i], v); + } + +} + +enum { + NUM_ENDPOINTS, + NUM_FXO_ENDPOINTS, + NUM_FXS_ENDPOINTS, + NUM_DECT_ENDPOINTS, + __MAX_ENDPOINTS, +}; + +static const struct blobmsg_policy endpt_count_policy[__MAX_ENDPOINTS] = { + [NUM_ENDPOINTS] = { .name = "num_endpoints", .type = BLOBMSG_TYPE_INT32 }, + [NUM_FXO_ENDPOINTS] = { .name = "num_fxo_endpoints", .type = BLOBMSG_TYPE_INT32 }, + [NUM_FXS_ENDPOINTS] = { .name = "num_fxs_endpoints", .type = BLOBMSG_TYPE_INT32 }, + [NUM_DECT_ENDPOINTS] = { .name = "num_dect_endpoints", .type = BLOBMSG_TYPE_INT32 }, +}; + + +// Reception of event +// { "ubus.object.add": {"id":123, "path":"foo"} } +static void ubus_event_new_obj(struct ubus_context *ctx __attribute__((unused)), struct ubus_event_handler *ev __attribute__((unused)), const char *type, struct blob_attr *blob) +{ + struct blob_attr *keys[ARRAY_SIZE(new_obj_policy)]; + const char *objPath; + uint32_t objId; + + // Tokenize message key/value paris into an array + if(blobmsg_parse(new_obj_policy, ARRAY_SIZE(new_obj_policy), + keys, blob_data(blob), blob_len(blob))) { + return; + } + + if(!type || !keys[OBJ_ID] || !keys[OBJ_PATH]) return; // Did we get all arguments we need? + + objId = blobmsg_get_u32(keys[OBJ_ID]); + objPath = blobmsg_get_string(keys[OBJ_PATH]); + + if(strcmp(type, ubusStrObjAdd) == 0) { // Object added to global context + if(strcmp(objPath, endpt_ubus_path) == 0) endpt_id = objId; // Endptmngr has started up + } + else if(strcmp(type, ubusStrObjRm) == 0) { // Object removed from global context + if(strcmp(objPath, endpt_ubus_path) == 0) endpt_id = 0; // Endptmngr has closed down + } +} + + +static void endpt_get_count_cb(struct ubus_request *req, + int type, struct blob_attr *msg) { + struct blob_attr *tb[__MAX_ENDPOINTS]; + + blobmsg_parse(endpt_count_policy, __MAX_ENDPOINTS, tb, blob_data(msg), blob_len(msg)); + + if (tb[NUM_FXS_ENDPOINTS]) + num_fxs_endpoints = blobmsg_get_u32(tb[NUM_FXS_ENDPOINTS]); + if (tb[NUM_FXO_ENDPOINTS]) + num_fxo_endpoints = blobmsg_get_u32(tb[NUM_FXO_ENDPOINTS]); + if (tb[NUM_DECT_ENDPOINTS]) + num_dect_endpoints = blobmsg_get_u32(tb[NUM_DECT_ENDPOINTS]); + num_endpoints = num_fxs_endpoints + num_dect_endpoints; +} + +static int endpt_get_count(void) { + uint32_t local_endpt_id; + struct blob_buf bb; + + local_endpt_id = get_ubus_endpt_id(); + if(!local_endpt_id) return -1; + + memset(&bb, 0, sizeof(bb)); + blob_buf_init(&bb, 0); + blobmsg_add_u8(&bb, "effective", 0); + + return (ubus_invoke(ctx, local_endpt_id, "count", bb.head, + endpt_get_count_cb, NULL, 2000) == UBUS_STATUS_OK ? 0 : -1); +} + +// Reception of RPC call +// ubus call asterisk event '{ "line" : 1, "event" : "EVENT_DTMF0" }' +static int asterisk_event(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct blob_attr *tb[__EVENT_MAX]; + char *event_str; + struct endpt_event *ev; + int line; + + blobmsg_parse(asterisk_event_policy, __EVENT_MAX, + tb, blob_data(msg), blob_len(msg)); + + if (!tb[EVENT_LINE_ID] || !tb[EVENT_TYPE]) + return UBUS_STATUS_INVALID_ARGUMENT; + + line = blobmsg_get_u32(tb[EVENT_LINE_ID]); + + event_str = blobmsg_get_string(tb[EVENT_TYPE]); + + /* Check if event is valid */ + for (ev = event_map; ev->event != EVENT_LAST; ev++) { + if (strncmp(ev->name, event_str, strlen(ev->name)) == 0) + break; + } + if (ev->event == EVENT_LAST) { + printf("Unknown event: %s\n", event_str); + return UBUS_STATUS_INVALID_ARGUMENT; + } + ev->line = line; + + printf("event: %s, line: %d\n", ev->name, ev->line); + if(iflist && cur_tech) brcm_process_event(ev); + + return UBUS_STATUS_OK; +} + +static struct ubus_method asterisk_methods[] = { + UBUS_METHOD("event", asterisk_event, asterisk_event_policy), +}; + +static struct ubus_object_type asterisk_obj_type = UBUS_OBJECT_TYPE("asterisk", asterisk_methods); + +static struct ubus_object asterisk_obj = { + .name = "asterisk", + .type = &asterisk_obj_type, + .methods = asterisk_methods, + .n_methods = ARRAY_SIZE(asterisk_methods), +}; + +/* Process ubus events by ubus stack */ +static void ubus_stream_handler(pe_stream_t *stream __attribute__((unused)), pe_event_t *event __attribute__((unused))) { + ubus_handle_event(ctx); +} + +static int ubus_init(void) { + pe_stream_t *ubus_stream; + + base = pe_base_new(); + if (base < 0) + exit_failure("pe_base_new\n"); + + ctx = ubus_connect(NULL); + if (!ctx) return -1; + + /* Register event handlers (not calls) for: + * { "ubus.object.add": {"id":123, "path":"foo"} } */ + memset(&ObjAddListener, 0, sizeof(ObjAddListener)); + ObjAddListener.cb = ubus_event_new_obj; + if(ubus_register_event_handler(ctx, &ObjAddListener, + ubusStrObjAdd) != UBUS_STATUS_OK) { + printf("Error registering ubus event handler %s", ubusStrObjAdd); + return -1; + } + + memset(&ObjRmListener, 0, sizeof(ObjRmListener)); + ObjRmListener.cb = ubus_event_new_obj; + if(ubus_register_event_handler(ctx, &ObjRmListener, + ubusStrObjRm) != UBUS_STATUS_OK) { + printf("Error registering ubus event handler %s", ubusStrObjRm); + return -1; + } + + /* Lookup path to Endptmngr AFTER registration of ubus object + * event handler above. It's no error if lookup fails. */ + if(ubus_lookup_id(ctx, endpt_ubus_path, + (uint32_t*) &endpt_id) != UBUS_STATUS_OK) { + endpt_id = 0; + } + + if (ubus_add_object(ctx, &asterisk_obj) != UBUS_STATUS_OK) { + printf("Failed to register asterisk object"); + return -1; + } + + /* Listen for data on ubus socket in our main event loop. */ + ubus_stream = pe_stream_new(ctx->sock.fd); + pe_stream_add_handler(ubus_stream, 0, ubus_stream_handler); + pe_base_add_stream(base, ubus_stream); + + return 0; +} + +// pthread wrapper for lib picoevent dispatcher +static void *pe_base_run(void *unused) { + int delay; + + for(delay = 0; delay < 5 && (!iflist || !cur_tech); delay++) sleep(1); + + pe_base_dispatch(base); + return NULL; +} + +static int load_module(void) +{ + struct ast_config *cfg; + int result, try; + + if (!(default_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { + return AST_MODULE_LOAD_DECLINE; + } + + if (!(brcm_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { + ao2_ref(default_cap, -1); + return AST_MODULE_LOAD_DECLINE; + } + + ast_format_cap_append_by_type(brcm_tech.capabilities, AST_MEDIA_TYPE_AUDIO); + ast_format_cap_append(default_cap, ast_format_alaw, 0); + + // Init UBUS and wait for endptmngr to start + if(ubus_init()) goto err; + + /* Create audio fifos. Both ends open in R/W mode to become + * independent of daemon startup order and peer restart + * recovery. However, later each end use unidirectional flow. */ + result = mkfifo(audio_tx_str, 0666); + if ((result == -1) && (errno != EEXIST)) exit_failure("Failed to create tx pipe"); + audio_tx_fd = open(audio_tx_str, O_RDWR|O_LARGEFILE|O_NONBLOCK); + if(audio_tx_fd == -1) exit_failure("Failed to open tx pipe"); + audio_tx_bus = pe_bus_new(audio_tx_fd); + if (!audio_tx_bus) exit_failure("Failed to create audio_tx bus"); + result = mkfifo(audio_rx_str, 0666); + if ((result == -1) && (errno != EEXIST)) exit_failure("Failed to create rx pipe"); + audio_rx_fd = open(audio_rx_str, O_RDWR|O_LARGEFILE|O_NONBLOCK); + if(audio_rx_fd == -1) exit_failure("Failed to open rx pipe"); + audio_rx_stream = pe_stream_new(audio_rx_fd); + pe_stream_add_handler(audio_rx_stream, PE_MAX_EVENT_SIZE, audio_rx_stream_handler); + pe_base_add_stream(base, audio_rx_stream); + + audio_rx_bus = pe_bus_new(-1); + if (!audio_rx_bus) exit_failure("Failed to create audio_rx bus"); + pe_bus_add_handler(audio_rx_bus, audio_packet_handler); + + /* Run pe_base_dispatch in separate thread. */ + if (ast_pthread_create_background(&ubus_thread, NULL, pe_base_run, NULL) < 0) { + ast_log(LOG_ERROR, "Unable to start ubus thread.\n"); + goto err; + } + + for(try = 0; try < 20 && num_endpoints == -1; try++) { + if(endpt_get_count()) { + ast_log(LOG_ERROR, "Waiting for endptmngr...\n"); + sleep(1); + } + } + if(num_endpoints == -1) goto err; + + registration_change_sub = calloc(num_endpoints, sizeof(struct stasis_subscription *)); + + /* Setup scheduler thread */ + if (!(sched = ast_sched_context_create())) { + ast_log(LOG_ERROR, "Unable to create scheduler thread/context. Aborting.\n"); + goto err; + } + + if (ast_mutex_lock(&iflock)) { + /* It's a little silly to lock it, but we mind as well just to be sure */ + ast_log(LOG_ERROR, "Unable to lock interface list???\n"); + goto err; + } + + /* Load settings file and read default section */ + if ((result = load_common_settings(&cfg)) != 0) { + goto err; + } + + load_lines_settings(cfg); + brcm_create_pvts(iflist, 0); + brcm_assign_line_id(iflist); + + ast_mutex_unlock(&iflock); + + /* Make sure we can register our channel */ + cur_tech = (struct ast_channel_tech *) &brcm_tech; + if (ast_channel_register(cur_tech)) { + ast_log(LOG_ERROR, "Unable to register channel class 'Brcm'\n"); + ast_config_destroy(cfg); + unload_module(); + goto err; + } + + /* Register all channel CLI functions */ + ast_cli_register_multiple(cli_brcm, ARRAY_LEN(cli_brcm)); + ast_config_destroy(cfg); + + if (ast_sched_start_thread(sched)) { + ast_sched_context_destroy(sched); + sched = NULL; + goto err; + } + + ast_debug(3, "%s(): done\n", __func__); + + return AST_MODULE_LOAD_SUCCESS; + + err: + ao2_ref(default_cap, -1); + return AST_MODULE_LOAD_FAILURE; +} + +static int brcm_signal_callwaiting(const struct brcm_pvt *p) +{ + endpt_signal(p->line_id, "callwt", "on", NULL); + return 0; +} + +static int brcm_stop_callwaiting(const struct brcm_pvt *p) +{ + endpt_signal(p->line_id, "callwt", "off", NULL); + return 0; +} + +static int brcm_signal_ringing(struct brcm_pvt *p) +{ + if (line_config[p->line_id].ringsignal) { + endpt_signal(p->line_id, "ringing", "on", NULL); + } + return 0; +} + + +static int brcm_stop_ringing(struct brcm_pvt *p) +{ + if (line_config[p->line_id].ringsignal) { + endpt_signal(p->line_id, "ringing", "off", NULL); + } + + return 0; +} + +/* Prepare endpoint for ringing. Caller ID signal pending. */ +static int brcm_signal_ringing_callerid_pending(struct brcm_pvt *p) +{ + if (line_config[p->line_id].ringsignal) { + endpt_signal(p->line_id, "callid_ringing", "on", NULL); + } + + return 0; +} + +static int brcm_stop_ringing_callerid_pending(struct brcm_pvt *p) +{ + if (line_config[p->line_id].ringsignal) { + endpt_signal(p->line_id, "callid_ringing", "off", NULL); + } + + return 0; +} + +/* + * Send caller id message to endpoint. + * MMDDHHMM, number, name + * 'O' in number or name => not available + * 'P' in number or name => presentation not allowed + */ +static int brcm_signal_callerid(struct ast_channel *chan, struct brcm_subchannel *sub) +{ + if (line_config[sub->parent->line_id].ringsignal) { + CLID_STRING clid_string; + struct timeval utc_time; + struct ast_tm local_time; + char number[CLID_MAX_NUMBER]; + char name[CLID_MAX_NAME]; + + /* Add datetime to caller id string, format: MMDDHHMM */ + utc_time = ast_tvnow(); + ast_localtime(&utc_time, &local_time, NULL); + sprintf(clid_string.date, + "%02d%02d%02d%02d, ", + local_time.tm_mon + 1, + local_time.tm_mday, + local_time.tm_hour, + local_time.tm_min); + + /* Get connected line identity if valid and presentation is allowed */ + if (chan) { + if ((ast_party_id_presentation(&ast_channel_connected(chan)->id) & AST_PRES_RESTRICTION) == AST_PRES_ALLOWED) { + if (ast_channel_connected(chan)->id.number.valid) { + strncpy(number, ast_channel_connected(chan)->id.number.str, CLID_MAX_NAME); + number[CLID_MAX_NUMBER - 1] = '\0'; + } else { + strcpy(number, "O\0"); + } + + if (ast_channel_connected(chan)->id.name.valid) { + strncpy(name, ast_channel_connected(chan)->id.name.str, CLID_MAX_NAME); + name[CLID_MAX_NAME - 1] = '\0'; + } else { + strcpy(name, "O\0"); + } + } else { + /* Number and/or name available but presentation is not allowed */ + strcpy(number, "P\0"); + strcpy(name, "P\0"); + } + } else { + /* Name and number not available. Will probably not be reached */ + strcpy(number, "0\0"); + strcpy(name, "0\0"); + } + + /* Add number and name to caller id string, format: number,"name" */ + int str_length = 0; + strncpy(&clid_string.number_name[str_length], number, CLID_MAX_NUMBER); + str_length = strlen(number); + clid_string.number_name[str_length++] = ','; + clid_string.number_name[str_length++] = '"'; + strncpy(&clid_string.number_name[str_length], name, CLID_MAX_NAME); + str_length = strlen(clid_string.number_name); + clid_string.number_name[str_length++] = '"'; + clid_string.number_name[str_length++] = '\0'; + + ast_debug(2, "CLID string: %s\n", (char *) &clid_string); + endpt_signal(sub->parent->line_id, "callid", "on", (char *)&clid_string); + return 0; + } + + return 0; +} + +static int brcm_create_connection(struct brcm_subchannel *sub) { + if (!sub->connection_init) { + /* generate random nr for rtp header */ + sub->ssrc = rand(); + + ast_debug(1, "Creating virtual Asterisk connection for pvt line_id=%i connection_id=%d\n", sub->parent->line_id, sub->connection_id); + sub->connection_init = 1; + + if(!brcm_in_onhold(sub->parent) && !brcm_in_call(sub->parent)) { // Is there another connection already? + ast_debug(1, "Creating real endpoint connection for pvt line_id=%i\n", sub->parent->line_id); + endpt_connection(sub->parent->line_id, sub->connection_id, "create"); + } + } + + return 0; +} + +static int brcm_mute_connection(struct brcm_subchannel *sub) +{ + /* Workaround for AA. Unmuting is not working. Throw away packets in packets thread instead */ + return 0; +} + +static int brcm_unmute_connection(struct brcm_subchannel *sub) +{ + /* Workaround for AA. Unmuting is not working. Throw away packets in packets thread instead */ + return 0; +} + +/* Put all subchannels in conferencing mode */ +static int brcm_create_conference(struct brcm_pvt *p) +{ + struct brcm_subchannel *second, *onhold; + struct ast_bridge *onholdBridge, *secondBridge; + struct ast_channel *chanToKick[1]; + struct ast_frame astFrame; + int res; + + memset(&astFrame, 0, sizeof(astFrame)); + astFrame.src = "TELCHAN"; + + // Second call from initiator. + second = brcm_get_active_subchannel(p); + if(!second || !second->owner) return -1; + + // Second bridge. Initiator + second remote call. + secondBridge = ast_channel_internal_bridge(second->owner); + if(!secondBridge) return -1; + + // First call from initiator (is onhold). + onhold = brcm_get_onhold_subchannel(p); + if(!onhold || !onhold->owner) return -1; + + ast_debug(1, "Starting conference for pvt line_id=%i connection_id=%d\n", + onhold->parent->line_id, onhold->connection_id); + + /* First bridge. Initiator + first (active but + * waiting in background) remote call. */ + onholdBridge = ast_channel_internal_bridge(onhold->owner); + if(!onholdBridge) return -1; + + /* Put second initiator call onhold and unhold the first initiator + * call. The other way around would be better, but for some reason + * it doesn't work... If doing so, the second call get one way + * audio only. */ + second->conference_initiator = 0; + second->conference_id = strdup(secondBridge->uniqueid); + brcm_mute_connection(second); + ast_queue_hold(second->owner, NULL); + second->channel_state = ONHOLD; + onhold->conference_id = strdup(onholdBridge->uniqueid); + onhold->conference_initiator = 1; + brcm_unmute_connection(onhold); + ast_queue_unhold(second->owner); + onhold->channel_state = INCALL; + sched_yield(); + + // Move second call into first bridge and wait for it to finish. + chanToKick[0] = second->owner; + res = ast_bridge_merge(onholdBridge, secondBridge, 0, chanToKick, 1); + while(ast_bridge_find_by_id(second->conference_id)) sched_yield(); + + // SIP calls need unhold sent to the bridge as well. + astFrame.frametype = AST_FRAME_CONTROL; + astFrame.subclass.integer = AST_CONTROL_UNHOLD; + ast_bridge_queue_everyone_else(onholdBridge, NULL, &astFrame); + + return res; +} + +static int brcm_stop_conference(struct brcm_subchannel *p) +{ + struct ast_bridge *confBridge; + + if (p->connection_init && p->owner) { + ast_debug(1, "Ending conference for pvt line_id=%i connection_id=%d\n", + p->parent->line_id, p->connection_id); + + // Force end of the conference if it's still active. + confBridge = ast_bridge_find_by_id(p->conference_id); + if(!confBridge) return -1; + + if(ao2_ref(confBridge, +1) >= 0 && confBridge->uniqueid && + confBridge->technology) { + ast_bridge_destroy(confBridge, AST_CAUSE_NORMAL_CLEARING); + } + ao2_ref(confBridge, -1); + } + + return 0; +} + +static int brcm_close_connection(struct brcm_subchannel *sub) { + + if (sub->connection_init) { + if(!brcm_in_onhold(sub->parent) && !brcm_in_call(sub->parent) && + !brcm_in_callwaiting(sub->parent) && !brcm_in_transferring(sub->parent) ) { // Does line have another call? + ast_debug(1, "Closing real endpoint connection %d\n", sub->parent->line_id); + endpt_connection(sub->parent->line_id, sub->connection_id, "destroy"); + } + sub->connection_init = 0; + ast_debug(1, "Virtual Asterisk connection %d/%d destroyed\n", sub->parent->line_id, sub->connection_id); + } + + return 0; +} + + +/* Generate rtp payload, 12 bytes of header and 160 bytes of ulaw payload */ +static void brcm_generate_rtp_packet(struct brcm_subchannel *sub, uint8_t *packet_buf, int type, int marker, int dtmf_timestamp) { + unsigned short* packet_buf16 = (unsigned short*)packet_buf; + unsigned int* packet_buf32 = (unsigned int*)packet_buf; + + //Generate the rtp header, packet is zero from the start, that fact is used + packet_buf[0] |= 0x80; //Set version 2 of header + //Padding 0 + //Extension 0 + //CSRC count 0 + packet_buf[1] = type; + packet_buf[1] |= marker?0x80:0x00; + packet_buf16[1] = htons(sub->sequence_number++); //Add sequence number + if (sub->sequence_number > 0xFFFF) sub->sequence_number=0; + packet_buf32[1] = htonl(sub->time_stamp); //Add timestamp + sub->time_stamp += sub->period*8; + packet_buf32[2] = sub->ssrc; //Random SSRC +} + + +static void brcm_dialtone_set(struct brcm_pvt *p, dialtone_state state) +{ + ast_debug(3, "Old dialtone: %s, new dialtone: %s\n", + dialtone_map[p->dialtone].str, dialtone_map[state].str); + + if (state != p->dialtone) { + ast_debug(2, "Changing dialtone for pvt %d from '%s' to '%s'\n", + p->line_id, + dialtone_map[p->dialtone].str, + dialtone_map[state].str); + p->dialtone = state; + brcm_signal_dialtone(p); + } +} + +static const char *feature_access_code_string(char *buffer, unsigned int buffer_length) +{ + struct feature_access_code *current; + + if (AST_LIST_EMPTY(&feature_access_codes)) { + strncpy(buffer, "(empty)", buffer_length); + return buffer; + } + + buffer[0] = '\0'; + int write_length = 0; + AST_LIST_TRAVERSE(&feature_access_codes, current, list) { + int rv = snprintf(buffer + write_length, buffer_length - write_length, "%s ", current->code); + if (rv <= 0) { + break; + } + write_length += rv; + } + + return buffer; +} + +static int feature_access_code_add(const char *code) +{ + struct feature_access_code *fac; + + if (ast_strlen_zero(code)) { + ast_log(LOG_WARNING, "Zero length FAC\n"); + return 1; + } + + if (!(fac = ast_calloc(1, sizeof(*fac)))) { + ast_log(LOG_WARNING, "FAC alloc failed\n"); + return 1; + } + + ast_copy_string(fac->code, code, sizeof(fac->code)); + ast_log(LOG_DEBUG, "Adding FAC: [%s]\n", fac->code); + + AST_LIST_INSERT_TAIL(&feature_access_codes, fac, list); + return 0; +} + +static int feature_access_code_clear(void) +{ + struct feature_access_code *fac; + + while ((fac = AST_LIST_REMOVE_HEAD(&feature_access_codes, list))) { + ast_free(fac); + } + return 0; +} + +static int feature_access_code_match(char *sequence) +{ + struct feature_access_code *current; + int retval = -1; + + AST_LIST_TRAVERSE(&feature_access_codes, current, list) { + char *seq = sequence; + char *fac = current->code; + + int res = -1; + for (; *seq && *fac; seq++, fac++) { + if (*fac == '.') { + /* Perfect match */ + return 0; + } + else if (*seq == *fac) { + /* Partial match */ + res = 1; + } + else { + /* No match */ + res = -1; + break; + } + } + + if (res == 1 && *seq == *fac) { + /* Perfect match */ + return 0; + } + + if (res != -1) { + retval = res; + } + } + + return retval; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Broadcom SLIC channel"); diff --git a/channels/chan_brcm.h b/channels/chan_brcm.h new file mode 100644 index 0000000000000000000000000000000000000000..70b82aa0307365817afdd2aff6ba84fdf71a514e --- /dev/null +++ b/channels/chan_brcm.h @@ -0,0 +1,238 @@ +/* Inteno AB, Stockholm, Sweden + * Channel for Broadcom FXS ports + */ + +#ifndef CHAN_BRCM_H +#define CHAN_BRCM_H + +/* Change this value when needed */ +#define CHANNEL_VERSION "1.2" + +#define DEFAULT_CALLER_ID "Unknown" +#define PHONE_MAX_BUF 480 + +#define TIMEMSEC 1000 + +#define PCMU 0 +#define G726 2 +#define G723 4 +#define PCMA 8 +#define G729 18 +#define G722 9 +#define DTMF_PAYLOAD 101 +#define DTMF 128 +#define RTCP 200 + +#define NOT_INITIALIZED -1 +//#define EPSTATUS_DRIVER_ERROR -1 +#define MAX_NUM_LINEID 30 +#define PACKET_BUFFER_SIZE 1024 +#define NUM_SUBCHANNELS 2 + +#define BEGIN 0 +#define CONT 1 +#define END 2 + +enum channel_state { + ONHOOK, + OFFHOOK, + DIALING, + CALLING, + INCALL, + ANSWER, + CALLENDED, + RINGING, + CALLWAITING, + ONHOLD, + TRANSFERING, + RINGBACK, + AWAITONHOOK, + ALLOCATED, +}; + +enum LINE_EVENT { // Events from low level line (endpoint etc.) + EVENT_DTMF0, + EVENT_DTMF1, + EVENT_DTMF2, + EVENT_DTMF3, + EVENT_DTMF4, + EVENT_DTMF5, + EVENT_DTMF6, + EVENT_DTMF7, + EVENT_DTMF8, + EVENT_DTMF9, + EVENT_DTMFA, + EVENT_DTMFB, + EVENT_DTMFC, + EVENT_DTMFD, + EVENT_DTMFH, + EVENT_DTMFS, + EVENT_OFFHOOK, + EVENT_ONHOOK, + EVENT_EARLY_OFFHOOK, + EVENT_EARLY_ONHOOK, + EVENT_FLASH, + EVENT_LAST, +}; + +enum endpoint_type { + FXS, + FXO, + DECT, +}; + +typedef enum dialtone_state { + DIALTONE_OFF = 0, + DIALTONE_ON, + DIALTONE_CONGESTION, + DIALTONE_SPECIAL_CONDITION, + DIALTONE_UNKNOWN, + DIALTONE_LAST, +} dialtone_state; + +struct brcm_subchannel { + int id; + struct ast_channel *owner; /* Channel we belong to, possibly NULL */ + int connection_id; /* Current connection id, may be -1 */ + unsigned int channel_state; /* Channel states */ + unsigned int connection_init; /* State for endpoint id connection initialization */ + struct ast_frame fr; /* Frame */ + unsigned int sequence_number; /* Endpoint RTP sequence number state */ + unsigned int time_stamp; /* Endpoint RTP time stamp state */ + unsigned int period; /* Endpoint RTP period */ + unsigned int ssrc; /* Endpoint RTP synchronization source */ + int codec; /* Used codec */ + struct brcm_pvt *parent; /* brcm_line owning this subchannel */ + int cw_timer_id; /* Current call waiting timer id, -1 if no active timer */ + int cw_rejected; /* True if a previous waiting call has been rejected during current call */ + int r4_hangup_timer_id; /* Current R4 hangup timer id, -1 if no active timer */ + int onhold_hangup_timer_id; /* Current onhold hangup timer id, -1 if no active timer */ + int conference_initiator; /* True if this subchannel is the original leg in a 3-way conference */ + char *conference_id; /* uuid of the conference initiated by this subchannel */ +}; + + +struct brcm_channel_tech { + int (* signal_ringing)(struct brcm_pvt *p); + int (* signal_ringing_callerid_pending)(struct brcm_pvt *p); + int (* signal_callerid)(struct ast_channel *chan, struct brcm_subchannel *s); + int (* stop_ringing)(struct brcm_pvt *p); + int (* stop_ringing_callerid_pending)(struct brcm_pvt *p); + int (* release)(struct brcm_pvt *p); +}; + + +struct brcm_pvt { + ast_mutex_t lock; + int fd; /* Raw file descriptor for this device */ + int line_id; /* Maps to the correct port */ + char dtmfbuf[AST_MAX_EXTENSION];/* DTMF buffer per channel */ + int dtmf_len; /* Length of DTMF buffer */ + int dtmf_first; /* DTMF control state, button pushes generate 2 events, one on button down and one on button up */ + struct ast_format_cap * lastformat; /* Last output format */ + struct ast_format_cap * lastinput; /* Last input format */ + struct brcm_pvt *next; /* Next channel in list */ + char offset[AST_FRIENDLY_OFFSET]; + char buf[PHONE_MAX_BUF]; /* Static buffer for reading frames */ + char context_direct[AST_MAX_EXTENSION]; + char context[AST_MAX_EXTENSION]; + char obuf[PHONE_MAX_BUF * 2]; + char ext[AST_MAX_EXTENSION]; + char language[MAX_LANGUAGE]; + char cid_num[AST_MAX_EXTENSION]; + char cid_name[AST_MAX_EXTENSION]; + + struct brcm_subchannel *sub[NUM_SUBCHANNELS]; /* List of sub-channels, needed for callwaiting and 3-way support */ + int hf_detected; /* Hook flash detected */ + dialtone_state dialtone; /* Set by manager command */ + struct brcm_channel_tech *tech; + + int interdigit_timer_id; /* Id of timer that tracks interdigit timeout */ + int autodial_timer_id; /* Id of timer that tracks autodial timeout */ + int dialtone_timeout_timer_id; /* Id of timer that tracks dialtone timeout */ + int onhold_hangup_timer_id; /* Id of timer that tracks onhold hangup timeout */ +}; + +enum rtp_type { + BRCM_UNKNOWN, + BRCM_AUDIO, +}; + + + +/* Mapping of DTMF to char/name/intval */ +typedef struct DTMF_CHARNAME_MAP +{ + enum LINE_EVENT event; + char name[12]; + char c; + int i; +} DTMF_CHARNAME_MAP; + + + + +typedef struct DIALTONE_MAP +{ + dialtone_state state; + char str[11]; +} DIALTONE_MAP; + +static const DIALTONE_MAP dialtone_map[] = +{ + {DIALTONE_OFF, "off"}, + {DIALTONE_ON, "on"}, + {DIALTONE_CONGESTION, "congestion"}, + {DIALTONE_SPECIAL_CONDITION, "special"}, + {DIALTONE_UNKNOWN, "unknown"}, + {DIALTONE_LAST, "-"}, +}; + +/* Struct for individual endpoint settings */ +typedef struct { + char language[MAX_LANGUAGE]; + char cid_num[AST_MAX_EXTENSION]; + char cid_name[AST_MAX_EXTENSION]; + char context_direct[AST_MAX_EXTENSION]; //Context that will be checked for exact matches + char context[AST_MAX_EXTENSION]; //Default context for dialtone mode + char autodial_ext[AST_MAX_EXTENSION]; + int autodial_timeoutmsec; + int ringsignal; + int timeoutmsec; + int period; + int hangup_xfer; + int dialtone_timeoutmsec; + int callwaiting; + int clir; +} line_settings; + + +/* Caller ID */ +#define CLID_MAX_DATE 10 +#define CLID_MAX_NUMBER 16 +#define CLID_MAX_NAME 16 +typedef struct CLID_STRING +{ + char date[CLID_MAX_DATE]; + char number_name[CLID_MAX_NUMBER + CLID_MAX_NAME + 4]; // 4 = comma, quotation marks and null terminator +} CLID_STRING; + + +/* Global jitterbuffer configuration - by default, jb is disabled */ +static struct ast_jb_conf default_jbconf = +{ + .flags = 0, + .max_size = -1, + .resync_threshold = -1, + .impl = "", + .target_extra = -1, +}; + + +#define DEFAULT_CALL_WAITING_TIMEOUT 24 // In seconds, Telia uses 24s + +#define DEFAULT_R4_HANGUP_TIMEOUT 5000 // In milliseconds +#define DEFAULT_ONHOLD_HANGUP_TIMEOUT 20 // In seconds + + +#endif /* CHAN_BRCM_H */ diff --git a/configs/brcm.conf.sample b/configs/brcm.conf.sample new file mode 100644 index 0000000000000000000000000000000000000000..fb80497c1abb8737671599b1ec13c29e6c6a38de --- /dev/null +++ b/configs/brcm.conf.sample @@ -0,0 +1,50 @@ +; +; Linux Telephony Interface +; +; Configuration file +; +[interfaces] +; +; Select a mode, either the phone jack provides dialtone, reads digits, +; then starts PBX with the given extension (dialtone mode), or +; immediately provides the PBX without reading any digits or providing +; any dialtone (this is the immediate mode, the default). Also, you +; can set the mode to "fxo" if you have a linejack to make it operate +; properly. +; +mode=immediate +;mode=dialtone +;mode=fxo +; +; You can decide which format to use by default, "g723.1" or "slinear". +; XXX Be careful, sometimes the card causes kernel panics when running +; in signed linear mode for some reason... XXX +; +;format=slinear +format=g723.1 +; +; And set the echo cancellation to "off", "low", "medium", and "high". +; This is not supported on all phones. +; +echocancel=medium +; +; You can optionally use VAD/CNG silence supression +; +;silencesupression=yes +; +; List all devices we can use. Contexts may also be specified +; +;context=local +; +; You can set txgain and rxgain for each device in the same way as context. +; If you want to change default gain value (1.0 =~ 100%) for device, simple +; add txgain or rxgain line before device line. But rememeber, if you change +; volume all cards listed below will be affected by these values. You can +; use float values (1.0, 0.5, 2.0) or percentage values (100%, 150%, 50%). +; +;txgain=100% +;rxgain=1.0 +device => /dev/bcmendpoint0 + + +