Index: channels/chan_misdn.c =================================================================== --- channels/chan_misdn.c (revision 205839) +++ channels/chan_misdn.c (working copy) @@ -131,30 +131,32 @@ MISDN_ALERTING, /*!< when Alerting */ MISDN_BUSY, /*!< when BUSY */ MISDN_CONNECTED, /*!< when connected */ - MISDN_PRECONNECTED, /*!< when connected */ MISDN_DISCONNECTED, /*!< when connected */ - MISDN_RELEASED, /*!< when connected */ - MISDN_BRIDGED, /*!< when bridged */ MISDN_CLEANING, /*!< when hangup from * but we were connected before */ - MISDN_HUNGUP_FROM_MISDN, /*!< when DISCONNECT/RELEASE/REL_COMP came from misdn */ - MISDN_HUNGUP_FROM_AST, /*!< when DISCONNECT/RELEASE/REL_COMP came out of misdn_hangup */ - MISDN_HOLDED, /*!< if this chan is holded */ - MISDN_HOLD_DISCONNECT, /*!< if this chan is holded */ - }; #define ORG_AST 1 #define ORG_MISDN 2 +enum misdn_hold_state { + MISDN_HOLD_IDLE, /*!< HOLD not active */ + MISDN_HOLD_ACTIVE, /*!< Call is held */ + MISDN_HOLD_TRANSFER, /*!< Held call is being transferred */ + MISDN_HOLD_DISCONNECT, /*!< Held call is being disconnected */ +}; struct hold_info { /*! - * \brief Logical port the channel call record is HOLDED on - * because the B channel is no longer associated. + * \brief Call HOLD state. */ + enum misdn_hold_state state; + /*! + * \brief Logical port the channel call record is HELD on + * because the B channel is no longer associated. + */ int port; /*! - * \brief Original B channel number the HOLDED call was using. + * \brief Original B channel number the HELD call was using. * \note Used only for debug display messages. */ int channel; @@ -318,13 +320,13 @@ struct misdn_bchannel *bc; /*! - * \brief HOLDED channel information + * \brief HELD channel call information */ - struct hold_info hold_info; + struct hold_info hold; - /*! - * \brief From associated B channel: Layer 3 process ID - * \note Used to find the HOLDED channel call record when retrieving a call. + /*! + * \brief From associated B channel: Layer 3 process ID + * \note Used to find the HELD channel call record when retrieving a call. */ unsigned int l3id; @@ -485,7 +487,6 @@ static struct ast_channel *misdn_new(struct chan_list *cl, int state, char *exten, char *callerid, int format, int port, int c); static void send_digit_to_chan(struct chan_list *cl, char digit ); -static void hangup_chan(struct chan_list *ch); static int pbx_start_chan(struct chan_list *ch); #define MISDN_ASTERISK_TECH_PVT(ast) ast->tech_pvt @@ -531,12 +532,13 @@ static int dialtone_indicate(struct chan_list *cl); -static int hanguptone_indicate(struct chan_list *cl); +static void hanguptone_indicate(struct chan_list *cl); static int stop_indicate(struct chan_list *cl); static int start_bc_tones(struct chan_list *cl); static int stop_bc_tones(struct chan_list *cl); -static void release_chan(struct misdn_bchannel *bc); +static void release_chan_early(struct chan_list *ch); +static void release_chan(struct chan_list *ch, struct misdn_bchannel *bc); static int misdn_check_l2l1(struct ast_channel *chan, void *data); static int misdn_set_opt_exec(struct ast_channel *chan, void *data); @@ -1191,15 +1193,8 @@ {MISDN_ALERTING,"ALERTING"}, /* when Alerting */ {MISDN_BUSY,"BUSY"}, /* when BUSY */ {MISDN_CONNECTED,"CONNECTED"}, /* when connected */ - {MISDN_PRECONNECTED,"PRECONNECTED"}, /* when connected */ {MISDN_DISCONNECTED,"DISCONNECTED"}, /* when connected */ - {MISDN_RELEASED,"RELEASED"}, /* when connected */ - {MISDN_BRIDGED,"BRIDGED"}, /* when bridged */ {MISDN_CLEANING,"CLEANING"}, /* when hangup from * but we were connected before */ - {MISDN_HUNGUP_FROM_MISDN,"HUNGUP_FROM_MISDN"}, /* when DISCONNECT/RELEASE/REL_COMP came from misdn */ - {MISDN_HOLDED,"HOLDED"}, /* when DISCONNECT/RELEASE/REL_COMP came from misdn */ - {MISDN_HOLD_DISCONNECT,"HOLD_DISCONNECT"}, /* when DISCONNECT/RELEASE/REL_COMP came from misdn */ - {MISDN_HUNGUP_FROM_AST,"HUNGUP_FROM_AST"} /* when DISCONNECT/RELEASE/REL_COMP came out of misdn_hangup */ }; static const char *misdn_get_ch_state(struct chan_list *p) @@ -1329,8 +1324,8 @@ if (bc) { print_bc_info(fd, help, bc); } else { - if (help->state == MISDN_HOLDED) { - ast_cli(fd, "ITS A HOLDED BC:\n"); + if (help->hold.state != MISDN_HOLD_IDLE) { + ast_cli(fd, "ITS A HELD CALL BC:\n"); ast_cli(fd, " --> l3_id: %x\n" " --> dad:%s oad:%s\n" " --> hold_port: %d\n" @@ -1338,8 +1333,8 @@ help->l3id, ast->exten, ast->cid.cid_num, - help->hold_info.port, - help->hold_info.channel + help->hold.port, + help->hold.channel ); } else { ast_cli(fd, "* Channel in unknown STATE !!! Exten:%s, Callerid:%s\n", ast->exten, ast->cid.cid_num); @@ -2414,13 +2409,19 @@ return -1; } - if (!p->bc ) { - chan_misdn_log(1, 0, "* IND : Indication from %s\n", ast->exten); - ast_log(LOG_WARNING, "Private Pointer but no bc ?\n"); + if (!p->bc) { + if (p->hold.state == MISDN_HOLD_IDLE) { + chan_misdn_log(1, 0, "* IND : Indication [%d] ignored on %s\n", cond, + ast->name); + ast_log(LOG_WARNING, "Private Pointer but no bc ?\n"); + } else { + chan_misdn_log(1, 0, "* IND : Indication [%d] ignored on hold %s\n", + cond, ast->name); + } return -1; } - chan_misdn_log(5, p->bc->port, "* IND : Indication [%d] from %s\n", cond, ast->exten); + chan_misdn_log(5, p->bc->port, "* IND : Indication [%d] on %s\n\n", cond, ast->name); switch (cond) { case AST_CONTROL_BUSY: @@ -2521,6 +2522,7 @@ break; default: chan_misdn_log(1, p->bc->port, " --> * Unknown Indication:%d pid:%d\n", cond, p->bc ? p->bc->pid : -1); + return -1; } return 0; @@ -2529,175 +2531,171 @@ static int misdn_hangup(struct ast_channel *ast) { struct chan_list *p; - struct misdn_bchannel *bc = NULL; - const char *varcause = NULL; + struct misdn_bchannel *bc; + const char *var; - ast_log(LOG_DEBUG, "misdn_hangup(%s)\n", ast->name); - - if (!ast || ! (p=MISDN_ASTERISK_TECH_PVT(ast) ) ) return -1; - - if (!p) { - chan_misdn_log(3, 0, "misdn_hangup called, without chan_list obj.\n"); - return 0 ; + if (!ast || !(p = MISDN_ASTERISK_TECH_PVT(ast))) { + return -1; } + MISDN_ASTERISK_TECH_PVT(ast) = NULL; - bc = p->bc; + ast_log(LOG_DEBUG, "misdn_hangup(%s)\n", ast->name); - if (bc) { - const char *tmp=pbx_builtin_getvar_helper(ast,"MISDN_USERUSER"); - if (tmp) { - ast_log(LOG_NOTICE, "MISDN_USERUSER: %s\n", tmp); - strcpy(bc->uu, tmp); - bc->uulen=strlen(bc->uu); + if (p->hold.state == MISDN_HOLD_IDLE) { + bc = p->bc; + } else { + p->hold.state = MISDN_HOLD_DISCONNECT; + bc = misdn_lib_find_held_bc(p->hold.port, p->l3id); + if (!bc) { + chan_misdn_log(4, p->hold.port, + "misdn_hangup: Could not find held bc for (%s)\n", ast->name); + release_chan_early(p); + return 0; } } - MISDN_ASTERISK_TECH_PVT(ast) = NULL; - p->ast = NULL; - - if (ast->_state == AST_STATE_RESERVED || - p->state == MISDN_NOTHING || - p->state == MISDN_HOLDED || - p->state == MISDN_HOLD_DISCONNECT ) { - - CLEAN_CH: + if (ast->_state == AST_STATE_RESERVED || p->state == MISDN_NOTHING) { /* between request and call */ ast_log(LOG_DEBUG, "State Reserved (or nothing) => chanIsAvail\n"); - MISDN_ASTERISK_TECH_PVT(ast) = NULL; - - ast_mutex_lock(&release_lock); - cl_dequeue_chan(&cl_te, p); - close(p->pipe[0]); - close(p->pipe[1]); - free(p); - ast_mutex_unlock(&release_lock); - - if (bc) + release_chan_early(p); + if (bc) { misdn_lib_release(bc); - + } return 0; } - if (!bc) { - ast_log(LOG_WARNING, "Hangup with private but no bc ? state:%s l3id:%x\n", misdn_get_ch_state(p), p->l3id); - goto CLEAN_CH; + ast_log(LOG_WARNING, "Hangup with private but no bc ? state:%s l3id:%x\n", + misdn_get_ch_state(p), p->l3id); + release_chan_early(p); + return 0; } - + p->ast = NULL; p->need_hangup = 0; p->need_queue_hangup = 0; p->need_busy = 0; - - if (!p->bc->nt) + if (!bc->nt) { stop_bc_tones(p); + } bc->out_cause = ast->hangupcause ? ast->hangupcause : AST_CAUSE_NORMAL_CLEARING; - if ((varcause = pbx_builtin_getvar_helper(ast, "HANGUPCAUSE")) || - (varcause = pbx_builtin_getvar_helper(ast, "PRI_CAUSE"))) { - int tmpcause = atoi(varcause); + var = pbx_builtin_getvar_helper(ast, "HANGUPCAUSE"); + if (!var) { + var = pbx_builtin_getvar_helper(ast, "PRI_CAUSE"); + } + if (var) { + int tmpcause; + + tmpcause = atoi(var); bc->out_cause = tmpcause ? tmpcause : AST_CAUSE_NORMAL_CLEARING; } - chan_misdn_log(1, bc->port, "* IND : HANGUP\tpid:%d ctx:%s dad:%s oad:%s State:%s\n", p->bc ? p->bc->pid : -1, ast->context, ast->exten, ast->cid.cid_num, misdn_get_ch_state(p)); + var = pbx_builtin_getvar_helper(ast, "MISDN_USERUSER"); + if (var) { + ast_log(LOG_NOTICE, "MISDN_USERUSER: %s\n", var); + ast_copy_string(bc->uu, var, sizeof(bc->uu)); + bc->uulen = strlen(bc->uu); + } + + chan_misdn_log(1, bc->port, + "* IND : HANGUP\tpid:%d ctx:%s dad:%s oad:%s State:%s\n", + bc->pid, + ast->context, + ast->exten, + ast->cid.cid_num, + misdn_get_ch_state(p)); chan_misdn_log(3, bc->port, " --> l3id:%x\n", p->l3id); chan_misdn_log(3, bc->port, " --> cause:%d\n", bc->cause); chan_misdn_log(2, bc->port, " --> out_cause:%d\n", bc->out_cause); - chan_misdn_log(2, bc->port, " --> state:%s\n", misdn_get_ch_state(p)); switch (p->state) { case MISDN_INCOMING_SETUP: - /* This is the only place in misdn_hangup, where we - * can call release_chan, else it might create lot's of trouble - * */ + /* + * This is the only place in misdn_hangup, where we + * can call release_chan, else it might create a lot of trouble. + */ ast_log(LOG_NOTICE, "release channel, in INCOMING_SETUP state.. no other events happened\n"); - release_chan(bc); + release_chan(p, bc); - p->state = MISDN_CLEANING; - if (bc->need_release_complete) - misdn_lib_send_event( bc, EVENT_RELEASE_COMPLETE); - break; - case MISDN_HOLDED: + if (bc->need_release_complete) { + misdn_lib_send_event(bc, EVENT_RELEASE_COMPLETE); + } + return 0; case MISDN_DIALING: - start_bc_tones(p); - hanguptone_indicate(p); + if (p->hold.state == MISDN_HOLD_IDLE) { + start_bc_tones(p); + hanguptone_indicate(p); + } - if (bc->need_disconnect) - misdn_lib_send_event( bc, EVENT_DISCONNECT); + if (bc->need_disconnect) { + misdn_lib_send_event(bc, EVENT_DISCONNECT); + } break; case MISDN_CALLING_ACKNOWLEDGE: - start_bc_tones(p); - hanguptone_indicate(p); + if (p->hold.state == MISDN_HOLD_IDLE) { + start_bc_tones(p); + hanguptone_indicate(p); + } - if (bc->need_disconnect) - misdn_lib_send_event( bc, EVENT_DISCONNECT); + if (bc->need_disconnect) { + misdn_lib_send_event(bc, EVENT_DISCONNECT); + } break; case MISDN_CALLING: case MISDN_ALERTING: case MISDN_PROGRESS: case MISDN_PROCEEDING: - if (p->originator != ORG_AST) + if (p->originator != ORG_AST && p->hold.state == MISDN_HOLD_IDLE) { hanguptone_indicate(p); + } - /*p->state=MISDN_CLEANING;*/ - if (bc->need_disconnect) - misdn_lib_send_event( bc, EVENT_DISCONNECT); + if (bc->need_disconnect) { + misdn_lib_send_event(bc, EVENT_DISCONNECT); + } break; case MISDN_CONNECTED: - case MISDN_PRECONNECTED: /* Alerting or Disconnect */ - if (p->bc->nt) { + if (bc->nt && p->hold.state == MISDN_HOLD_IDLE) { start_bc_tones(p); hanguptone_indicate(p); - p->bc->progress_indicator = INFO_PI_INBAND_AVAILABLE; + bc->progress_indicator = INFO_PI_INBAND_AVAILABLE; } - if (bc->need_disconnect) - misdn_lib_send_event( bc, EVENT_DISCONNECT); - - /*p->state=MISDN_CLEANING;*/ + if (bc->need_disconnect) { + misdn_lib_send_event(bc, EVENT_DISCONNECT); + } break; case MISDN_DISCONNECTED: - if (bc->need_release) - misdn_lib_send_event( bc, EVENT_RELEASE); - p->state = MISDN_CLEANING; /* MISDN_HUNGUP_FROM_AST; */ + if (bc->need_release) { + misdn_lib_send_event(bc, EVENT_RELEASE); + } break; - case MISDN_RELEASED: case MISDN_CLEANING: - p->state = MISDN_CLEANING; - break; + return 0; case MISDN_BUSY: break; - - case MISDN_HOLD_DISCONNECT: - /* need to send release here */ - chan_misdn_log(1, bc->port, " --> cause %d\n", bc->cause); - chan_misdn_log(1, bc->port, " --> out_cause %d\n", bc->out_cause); - - bc->out_cause = -1; - if (bc->need_release) - misdn_lib_send_event(bc, EVENT_RELEASE); - p->state = MISDN_CLEANING; - break; default: if (bc->nt) { bc->out_cause = -1; - if (bc->need_release) + if (bc->need_release) { misdn_lib_send_event(bc, EVENT_RELEASE); - p->state = MISDN_CLEANING; + } } else { - if (bc->need_disconnect) + if (bc->need_disconnect) { misdn_lib_send_event(bc, EVENT_DISCONNECT); + } } + break; } p->state = MISDN_CLEANING; + chan_misdn_log(3, bc->port, " --> Channel: %s hungup new state:%s\n", ast->name, + misdn_get_ch_state(p)); - chan_misdn_log(3, bc->port, " --> Channel: %s hanguped new state:%s\n", ast->name, misdn_get_ch_state(p)); - return 0; } @@ -2789,7 +2787,7 @@ return NULL; } - if (!tmp->bc && !(tmp->state == MISDN_HOLDED)) { + if (!tmp->bc && tmp->hold.state == MISDN_HOLD_IDLE) { chan_misdn_log(1, 0, "misdn_read called without bc\n"); return NULL; } @@ -2873,8 +2871,8 @@ if (!ast || ! (ch = MISDN_ASTERISK_TECH_PVT(ast)) ) return -1; - if (ch->state == MISDN_HOLDED) { - chan_misdn_log(7, 0, "misdn_write: Returning because holded\n"); + if (ch->hold.state != MISDN_HOLD_IDLE) { + chan_misdn_log(7, 0, "misdn_write: Returning because hold active\n"); return 0; } @@ -3107,10 +3105,9 @@ return 0; } -static int hanguptone_indicate(struct chan_list *cl) +static void hanguptone_indicate(struct chan_list *cl) { misdn_lib_send_tone(cl->bc, TONE_HANGUP); - return 0; } static int stop_indicate(struct chan_list *cl) @@ -3538,39 +3535,72 @@ return NULL; } -static struct chan_list *find_holded(struct chan_list *list, struct misdn_bchannel *bc) +static struct chan_list *find_hold_call(struct chan_list *list, struct misdn_bchannel *bc) { struct chan_list *help = list; if (bc->pri) return NULL; - chan_misdn_log(6, bc->port, "$$$ find_holded: channel:%d oad:%s dad:%s\n", bc->channel, bc->oad, bc->dad); + chan_misdn_log(6, bc->port, "$$$ find_hold_call: channel:%d oad:%s dad:%s\n", bc->channel, bc->oad, bc->dad); for (;help; help = help->next) { - chan_misdn_log(4, bc->port, "$$$ find_holded: --> holded:%d channel:%d\n", help->state == MISDN_HOLDED, help->hold_info.channel); - if ((help->state == MISDN_HOLDED) && - (help->hold_info.port == bc->port)) + chan_misdn_log(4, bc->port, "$$$ find_hold_call: --> hold:%d channel:%d\n", help->hold.state, help->hold.channel); + if (help->hold.state == MISDN_HOLD_ACTIVE && help->hold.port == bc->port) return help; } - chan_misdn_log(6, bc->port, "$$$ find_chan: No channel found for oad:%s dad:%s\n", bc->oad, bc->dad); + chan_misdn_log(6, bc->port, "$$$ find_hold_call: No channel found for oad:%s dad:%s\n", bc->oad, bc->dad); return NULL; } -static struct chan_list *find_holded_l3(struct chan_list *list, unsigned long l3_id, int w) +static struct chan_list *find_hold_call_l3(struct chan_list *list, unsigned long l3_id) { struct chan_list *help = list; for (; help; help = help->next) { - if ( (help->state == MISDN_HOLDED) && - (help->l3id == l3_id) - ) + if (help->hold.state != MISDN_HOLD_IDLE && help->l3id == l3_id) return help; } return NULL; } +#define TRANSFER_ON_HELD_CALL_HANGUP 1 +#if defined(TRANSFER_ON_HELD_CALL_HANGUP) +/*! + * \internal + * \brief Find a suitable active call to go with a held call so we could try a transfer. + * + * \param list Channel list. + * \param bc B channel record. + * + * \return Found call record or NULL. + * + * \note There could be a possibility where we find the wrong active call to transfer. + * This concern is mitigated by the fact that there could be at most one other call + * on a PTMP BRI link to another device. Maybe the l3_id could help in locating an + * active call on the same TEI? + */ +static struct chan_list *find_hold_active_call(struct chan_list *list, struct misdn_bchannel *bc) +{ + for (; list; list = list->next) { + if (list->hold.state == MISDN_HOLD_IDLE && list->bc && list->bc->port == bc->port + && list->ast) { + switch (list->state) { + case MISDN_PROCEEDING: + case MISDN_PROGRESS: + case MISDN_ALERTING: + case MISDN_CONNECTED: + return list; + default: + break; + } + } + } + return NULL; +} +#endif /* defined(TRANSFER_ON_HELD_CALL_HANGUP) */ + static void cl_queue_chan(struct chan_list **list, struct chan_list *chan) { chan_misdn_log(4, chan->bc ? chan->bc->port : 0, "* Queuing chan %p\n", chan); @@ -3634,23 +3664,26 @@ return ret; } -static void hangup_chan(struct chan_list *ch) +static void hangup_chan(struct chan_list *ch, struct misdn_bchannel *bc) { - int port = ch ? (ch->bc ? ch->bc->port : 0) : 0; + int port; + if (!ch) { cb_log(1, 0, "Cannot hangup chan, no ch\n"); return; } + port = bc->port; cb_log(5, port, "hangup_chan called\n"); if (ch->need_hangup) { cb_log(2, port, " --> hangup\n"); - send_cause2ast(ch->ast, ch->bc, ch); ch->need_hangup = 0; ch->need_queue_hangup = 0; - if (ch->ast) + if (ch->ast) { + send_cause2ast(ch->ast, bc, ch); ast_hangup(ch->ast); + } return; } @@ -3660,96 +3693,185 @@ ch->need_queue_hangup = 0; if (ch->ast) { - send_cause2ast(ch->ast, ch->bc, ch); - - if (ch->ast) - ast_queue_hangup(ch->ast); + send_cause2ast(ch->ast, bc, ch); + ast_queue_hangup(ch->ast); cb_log(2, port, " --> queue_hangup\n"); } else { cb_log(1, port, "Cannot hangup chan, no ast\n"); } } -/** Isdn asks us to release channel, pendant to misdn_hangup **/ -static void release_chan(struct misdn_bchannel *bc) { - struct ast_channel *ast = NULL; +/*! + * \internal + * \brief ISDN asked us to release channel, pendant to misdn_hangup. + * + * \param ch Call channel record to release. + * \param bc Current B channel record associated with ch. + * + * \return Nothing + * + * \note ch must not be referenced after calling. + */ +static void release_chan(struct chan_list *ch, struct misdn_bchannel *bc) +{ + struct ast_channel *ast; + ch->state = MISDN_CLEANING; + ast_mutex_lock(&release_lock); - { - struct chan_list *ch=find_chan_by_bc(cl_te, bc); - if (!ch) { - chan_misdn_log(1, bc->port, "release_chan: Ch not found!\n"); - ast_mutex_unlock(&release_lock); - return; - } - if (ch->ast) { - ast = ch->ast; - } + cl_dequeue_chan(&cl_te, ch); - chan_misdn_log(5, bc->port, "release_chan: bc with l3id: %x\n", bc->l3_id); + chan_misdn_log(5, bc->port, "release_chan: bc with pid:%d l3id: %x\n", bc->pid, bc->l3_id); - /*releasing jitterbuffer*/ - if (ch->jb ) { - misdn_jb_destroy(ch->jb); - ch->jb = NULL; - } else { - if (!bc->nojitter) - chan_misdn_log(5, bc->port, "Jitterbuffer already destroyed.\n"); + /* releasing jitterbuffer */ + if (ch->jb) { + misdn_jb_destroy(ch->jb); + ch->jb = NULL; + } else { + if (!bc->nojitter) { + chan_misdn_log(5, bc->port, "Jitterbuffer already destroyed.\n"); } + } - if (ch->overlap_dial) { - if (ch->overlap_dial_task != -1) { - misdn_tasks_remove(ch->overlap_dial_task); - ch->overlap_dial_task = -1; - } - ast_mutex_destroy(&ch->overlap_tv_lock); + if (ch->overlap_dial) { + if (ch->overlap_dial_task != -1) { + misdn_tasks_remove(ch->overlap_dial_task); + ch->overlap_dial_task = -1; } + ast_mutex_destroy(&ch->overlap_tv_lock); + } - if (ch->originator == ORG_AST) { - misdn_out_calls[bc->port]--; - } else { - misdn_in_calls[bc->port]--; + if (ch->originator == ORG_AST) { + --misdn_out_calls[bc->port]; + } else { + --misdn_in_calls[bc->port]; + } + + close(ch->pipe[0]); + close(ch->pipe[1]); + + ast = ch->ast; + if (ast) { + MISDN_ASTERISK_TECH_PVT(ast) = NULL; + chan_misdn_log(1, bc->port, + "* RELEASING CHANNEL pid:%d ctx:%s dad:%s oad:%s\n", + bc->pid, + ast->context, + ast->exten, + ast->cid.cid_num); + + if (ast->_state != AST_STATE_RESERVED) { + chan_misdn_log(3, bc->port, " --> Setting AST State to down\n"); + ast_setstate(ast, AST_STATE_DOWN); } + } - if (ch) { - close(ch->pipe[0]); - close(ch->pipe[1]); + free(ch); - if (ast && MISDN_ASTERISK_TECH_PVT(ast)) { - chan_misdn_log(1, bc->port, "* RELEASING CHANNEL pid:%d ctx:%s dad:%s oad:%s state: %s\n", bc ? bc->pid : -1, ast->context, ast->exten, ast->cid.cid_num, misdn_get_ch_state(ch)); - chan_misdn_log(3, bc->port, " --> * State Down\n"); - MISDN_ASTERISK_TECH_PVT(ast) = NULL; + ast_mutex_unlock(&release_lock); +} - if (ast->_state != AST_STATE_RESERVED) { - chan_misdn_log(3, bc->port, " --> Setting AST State to down\n"); - ast_setstate(ast, AST_STATE_DOWN); - } - } +/*! + * \internal + * \brief Do everything in release_chan() that makes sense without a bc. + * + * \param ch Call channel record to release. + * + * \return Nothing + * + * \note ch must not be referenced after calling. + */ +static void release_chan_early(struct chan_list *ch) +{ + struct ast_channel *ast; - ch->state = MISDN_CLEANING; - cl_dequeue_chan(&cl_te, ch); + ch->state = MISDN_CLEANING; - free(ch); + ast_mutex_lock(&release_lock); + + cl_dequeue_chan(&cl_te, ch); + + /* releasing jitterbuffer */ + if (ch->jb) { + misdn_jb_destroy(ch->jb); + ch->jb = NULL; + } + + if (ch->overlap_dial) { + if (ch->overlap_dial_task != -1) { + misdn_tasks_remove(ch->overlap_dial_task); + ch->overlap_dial_task = -1; + } + ast_mutex_destroy(&ch->overlap_tv_lock); + } + + if (ch->hold.state != MISDN_HOLD_IDLE) { + if (ch->originator == ORG_AST) { + --misdn_out_calls[ch->hold.port]; } else { - /* chan is already cleaned, so exiting */ + --misdn_in_calls[ch->hold.port]; } } + + close(ch->pipe[0]); + close(ch->pipe[1]); + + ast = ch->ast; + if (ast) { + MISDN_ASTERISK_TECH_PVT(ast) = NULL; + if (ast->_state != AST_STATE_RESERVED) { + ast_setstate(ast, AST_STATE_DOWN); + } + } + + free(ch); + ast_mutex_unlock(&release_lock); -/*** release end **/ } -static void misdn_transfer_bc(struct chan_list *tmp_ch, struct chan_list *holded_chan) +/*! + * \internal + * \brief Attempt to transfer the active channel party to the held channel party. + * + * \param active_ch Channel currently connected. + * \param held_ch Channel currently on hold. + * + * \retval 0 on success. + * \retval -1 on error. + */ +static int misdn_attempt_transfer(struct chan_list *active_ch, struct chan_list *held_ch) { - chan_misdn_log(4, 0, "TRANSFERRING %s to %s\n", holded_chan->ast->name, tmp_ch->ast->name); + int retval; + struct ast_channel *bridged; - tmp_ch->state = MISDN_HOLD_DISCONNECT; + switch (active_ch->state) { + case MISDN_PROCEEDING: + case MISDN_PROGRESS: + case MISDN_ALERTING: + case MISDN_CONNECTED: + break; + default: + return -1; + } - ast_moh_stop(ast_bridged_channel(holded_chan->ast)); + bridged = ast_bridged_channel(held_ch->ast); + if (bridged) { + ast_queue_control(held_ch->ast, AST_CONTROL_UNHOLD); + held_ch->hold.state = MISDN_HOLD_TRANSFER; - holded_chan->state=MISDN_CONNECTED; - /* misdn_lib_transfer(holded_chan->bc); */ - ast_channel_masquerade(holded_chan->ast, ast_bridged_channel(tmp_ch->ast)); + chan_misdn_log(1, held_ch->hold.port, "TRANSFERRING %s to %s\n", + held_ch->ast->name, active_ch->ast->name); + retval = ast_channel_masquerade(active_ch->ast, bridged); + } else { + /* + * Could not transfer. Held channel is not bridged anymore. + * Held party probably got tired of waiting and hung up. + */ + retval = -1; + } + + return retval; } @@ -3787,7 +3909,7 @@ if (!ast_canmatch_extension(ast, ast->context, ast->exten, 1, bc->oad) || pbx_start_chan(ch) < 0) { ast = NULL; bc->out_cause = AST_CAUSE_UNALLOCATED; - hangup_chan(ch); + hangup_chan(ch, bc); hanguptone_indicate(ch); if (bc->nt) @@ -3967,7 +4089,7 @@ static void start_pbx(struct chan_list *ch, struct misdn_bchannel *bc, struct ast_channel *chan) { if (pbx_start_chan(ch) < 0) { - hangup_chan(ch); + hangup_chan(ch, bc); chan_misdn_log(-1, bc->port, "ast_pbx_start returned <0 in SETUP\n"); if (bc->nt) { hanguptone_indicate(ch); @@ -3992,6 +4114,7 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data) { int msn_valid; + struct chan_list *held_ch; struct chan_list *ch = find_chan_by_bc(cl_te, bc); if (event != EVENT_BCHAN_DATA && event != EVENT_TONE_GENERATE) { /* Debug Only Non-Bchan */ @@ -4010,14 +4133,13 @@ switch(event) { case EVENT_SETUP: case EVENT_DISCONNECT: + case EVENT_RELEASE: + case EVENT_RELEASE_COMPLETE: case EVENT_PORT_ALARM: case EVENT_RETRIEVE: case EVENT_NEW_BC: case EVENT_FACILITY: break; - case EVENT_RELEASE_COMPLETE: - chan_misdn_log(1, bc->port, " --> no Ch, so we've already released.\n"); - break; case EVENT_CLEANUP: case EVENT_TONE_GENERATE: case EVENT_BCHAN_DATA: @@ -4075,7 +4197,7 @@ case EVENT_NEW_BC: if (!ch) { - ch = find_holded(cl_te,bc); + ch = find_hold_call(cl_te,bc); } if (!ch) { @@ -4135,12 +4257,12 @@ /* Check for Pickup Request first */ if (!strcmp(ch->ast->exten, ast_pickup_ext())) { if (ast_pickup_call(ch->ast)) { - hangup_chan(ch); + hangup_chan(ch, bc); } else { struct ast_channel *chan = ch->ast; ch->state = MISDN_CALLING_ACKNOWLEDGE; ast_setstate(chan, AST_STATE_DOWN); - hangup_chan(ch); + hangup_chan(ch, bc); ch->ast = NULL; break; } @@ -4374,11 +4496,11 @@ ch->state = MISDN_INCOMING_SETUP; } if (ast_pickup_call(chan)) { - hangup_chan(ch); + hangup_chan(ch, bc); } else { ch->state = MISDN_CALLING_ACKNOWLEDGE; ast_setstate(chan, AST_STATE_DOWN); - hangup_chan(ch); + hangup_chan(ch, bc); ch->ast = NULL; break; } @@ -4630,8 +4752,6 @@ case EVENT_DISCONNECT: /*we might not have an ch->ast ptr here anymore*/ if (ch) { - struct chan_list *holded_ch = find_holded(cl_te, bc); - chan_misdn_log(3, bc->port, " --> org:%d nt:%d, inbandavail:%d state:%d\n", ch->originator, bc->nt, misdn_inband_avail(bc), ch->state); if (ch->originator == ORG_AST && !bc->nt && misdn_inband_avail(bc) && ch->state != MISDN_CONNECTED) { /* If there's inband information available (e.g. a @@ -4653,23 +4773,34 @@ break; } - /*Check for holded channel, to implement transfer*/ - if (holded_ch && holded_ch != ch && ch->ast && ch->state == MISDN_CONNECTED) { - cb_log(1, bc->port, " --> found holded ch\n"); - misdn_transfer_bc(ch, holded_ch) ; - } - bc->need_disconnect = 0; + stop_bc_tones(ch); - stop_bc_tones(ch); - hangup_chan(ch); -#if 0 + /* Check for held channel, to implement transfer */ + held_ch = find_hold_call(cl_te, bc); + if (!held_ch || !ch->ast || misdn_attempt_transfer(ch, held_ch)) { + hangup_chan(ch, bc); + } } else { - ch = find_holded_l3(cl_te, bc->l3_id,1); - if (ch) { - hangup_chan(ch); + held_ch = find_hold_call_l3(cl_te, bc->l3_id); + if (held_ch && held_ch->hold.state == MISDN_HOLD_ACTIVE) { + bc->need_disconnect = 0; + +#if defined(TRANSFER_ON_HELD_CALL_HANGUP) + /* + * Some phones disconnect the held call and the active call at the + * same time to do the transfer. Unfortunately, either call could + * be disconnected first. + */ + ch = find_hold_active_call(cl_te, bc); + if (!ch || misdn_attempt_transfer(ch, held_ch)) { + held_ch->hold.state = MISDN_HOLD_DISCONNECT; + hangup_chan(held_ch, bc); + } +#else + hangup_chan(held_ch, bc); +#endif /* defined(TRANSFER_ON_HELD_CALL_HANGUP) */ } -#endif } bc->out_cause = -1; if (bc->need_release) @@ -4677,29 +4808,41 @@ break; case EVENT_RELEASE: - { - bc->need_disconnect = 0; - bc->need_release = 0; + if (!ch) { + ch = find_hold_call_l3(cl_te, bc->l3_id); + if (!ch) { + chan_misdn_log(1, bc->port, + " --> no Ch, so we've already released. (%s)\n", + manager_isdn_get_info(event)); + return -1; + } + } - hangup_chan(ch); - release_chan(bc); - } + bc->need_disconnect = 0; + bc->need_release = 0; + + hangup_chan(ch, bc); + release_chan(ch, bc); break; case EVENT_RELEASE_COMPLETE: - { + if (!ch) { + ch = find_hold_call_l3(cl_te, bc->l3_id); + if (!ch) { + chan_misdn_log(1, bc->port, + " --> no Ch, so we've already released. (%s)\n", + manager_isdn_get_info(event)); + break; + } + } + bc->need_disconnect = 0; bc->need_release = 0; bc->need_release_complete = 0; stop_bc_tones(ch); - hangup_chan(ch); - - if (ch) - ch->state = MISDN_CLEANING; - - release_chan(bc); - } - break; + hangup_chan(ch, bc); + release_chan(ch, bc); + break; case EVENT_BCHAN_ERROR: case EVENT_CLEANUP: { @@ -4713,8 +4856,8 @@ break; } - hangup_chan(ch); - release_chan(bc); + hangup_chan(ch, bc); + release_chan(ch, bc); } break; @@ -4798,8 +4941,8 @@ chan_misdn_log(0, bc->port, "Write returned <=0 (err=%s) --> hanging up channel\n", strerror(errno)); stop_bc_tones(ch); - hangup_chan(ch); - release_chan(bc); + hangup_chan(ch, bc); + release_chan(ch, bc); } } else { chan_misdn_log(1, bc->port, "Write Pipe full!\n"); @@ -4859,39 +5002,30 @@ /** Supplementary Services **/ /****************************/ case EVENT_RETRIEVE: - { - struct ast_channel *hold_ast; - if (!ch) { - chan_misdn_log(4, bc->port, " --> no CH, searching in holded\n"); - ch = find_holded_l3(cl_te, bc->l3_id, 1); + chan_misdn_log(4, bc->port, " --> no CH, searching for held call\n"); + ch = find_hold_call_l3(cl_te, bc->l3_id); + if (!ch || ch->hold.state != MISDN_HOLD_ACTIVE) { + ast_log(LOG_WARNING, "No held call found, cannot Retrieve\n"); + misdn_lib_send_event(bc, EVENT_RETRIEVE_REJECT); + break; + } } - if (!ch) { - ast_log(LOG_WARNING, "Found no Holded channel, cannot Retrieve\n"); - misdn_lib_send_event(bc, EVENT_RETRIEVE_REJECT); - break; - } - - /*remember the channel again*/ + /* remember the channel again */ ch->bc = bc; - ch->state = MISDN_CONNECTED; - ch->hold_info.port = 0; - ch->hold_info.channel = 0; + ch->hold.state = MISDN_HOLD_IDLE; + ch->hold.port = 0; + ch->hold.channel = 0; - hold_ast = ast_bridged_channel(ch->ast); - - if (hold_ast) { - ast_moh_stop(hold_ast); - } + ast_queue_control(ch->ast, AST_CONTROL_UNHOLD); if (misdn_lib_send_event(bc, EVENT_RETRIEVE_ACKNOWLEDGE) < 0) { chan_misdn_log(4, bc->port, " --> RETRIEVE_ACK failed\n"); misdn_lib_send_event(bc, EVENT_RETRIEVE_REJECT); } - } - break; + break; case EVENT_HOLD: { @@ -4910,20 +5044,17 @@ bridged = ast_bridged_channel(ch->ast); if (bridged) { chan_misdn_log(2, bc->port, "Bridge Partner is of type: %s\n", bridged->tech->type); - ch->state = MISDN_HOLDED; ch->l3id = bc->l3_id; - - misdn_lib_send_event(bc, EVENT_HOLD_ACKNOWLEDGE); - /* XXX This should queue an AST_CONTROL_HOLD frame on this channel - * instead of starting moh on the bridged channel directly */ - ast_moh_start(bridged, NULL, NULL); - - /*forget the channel now*/ + /* forget the channel now */ ch->bc = NULL; - ch->hold_info.port = bc->port; - ch->hold_info.channel = bc->channel; + ch->hold.state = MISDN_HOLD_ACTIVE; + ch->hold.port = bc->port; + ch->hold.channel = bc->channel; + ast_queue_control(ch->ast, AST_CONTROL_HOLD); + + misdn_lib_send_event(bc, EVENT_HOLD_ACKNOWLEDGE); } else { misdn_lib_send_event(bc, EVENT_HOLD_REJECT); chan_misdn_log(0, bc->port, "We aren't bridged to anybody\n"); @@ -4984,7 +5115,7 @@ if (!bc->dummy) { stop_bc_tones(ch); - release_chan(bc); + release_chan(ch, bc); } break; Index: channels/misdn/isdn_lib.c =================================================================== --- channels/misdn/isdn_lib.c (revision 205839) +++ channels/misdn/isdn_lib.c (working copy) @@ -215,8 +215,6 @@ struct misdn_bchannel *find_bc_by_confid(unsigned long confid); -struct misdn_bchannel *stack_holder_find_bychan(struct misdn_stack *stack, int chan); - int setup_bc(struct misdn_bchannel *bc); int manager_isdn_handler(iframe_t *frm ,msg_t *msg); @@ -3099,13 +3097,6 @@ cb_log(4, 0, "midev closed\n"); } - - -void misdn_lib_transfer(struct misdn_bchannel* holded_bc) -{ - holded_bc->holded=0; -} - struct misdn_bchannel *manager_find_bc_by_pid(int pid) { struct misdn_stack *stack; @@ -3346,6 +3337,7 @@ msg_t *msg; int retval=0; struct misdn_stack *stack; + struct misdn_bchannel *held_bc; if (!bc) RETURN(-1,OUT_POST_UNLOCK); @@ -3432,21 +3424,22 @@ break; case EVENT_HOLD_ACKNOWLEDGE: - { - struct misdn_bchannel *holded_bc=malloc(sizeof(struct misdn_bchannel)); - if (!holded_bc) { - cb_log(0,bc->port, "Could not allocate holded_bc!!!\n"); + held_bc = malloc(sizeof(struct misdn_bchannel)); + if (!held_bc) { + cb_log(0, bc->port, "Could not allocate held_bc!!!\n"); RETURN(-1,OUT); } - /*backup the bc*/ - memcpy(holded_bc,bc,sizeof(struct misdn_bchannel)); - holded_bc->holded=1; - bc_state_change(holded_bc,BCHAN_CLEANED); - - stack_holder_add(stack,holded_bc); + /* backup the bc and put it in storage */ + *held_bc = *bc; + held_bc->holded = 1; + held_bc->channel = 0;/* A held call does not have a channel anymore. */ + held_bc->channel_preselected = 0; + held_bc->channel_found = 0; + bc_state_change(held_bc, BCHAN_CLEANED); + stack_holder_add(stack, held_bc); - /*kill the bridge and clean the bchannel*/ + /* kill the bridge and clean the real b-channel record */ if (stack->nt) { int channel; if (bc->bc_state == BCHAN_BRIDGED) { @@ -3471,9 +3464,7 @@ bc->in_use=0; } - - } - break; + break; /* finishing the channel eh ? */ case EVENT_DISCONNECT: @@ -4393,28 +4384,6 @@ } } -struct misdn_bchannel *stack_holder_find_bychan(struct misdn_stack *stack, int chan) -{ - struct misdn_bchannel *help; - - cb_log(4,stack?stack->port:0, "*HOLDER: find_bychan %c\n", chan); - - if (!stack) return NULL; - - for (help=stack->holding; - help; - help=help->next) { - if (help->channel == chan) { - cb_log(4,stack->port, "*HOLDER: found_bychan bc\n"); - return help; - } - } - - cb_log(4,stack->port, "*HOLDER: find_bychan nothing\n"); - return NULL; - -} - struct misdn_bchannel *stack_holder_find(struct misdn_stack *stack, unsigned long l3id) { struct misdn_bchannel *help; @@ -4436,8 +4405,30 @@ return NULL; } +/*! + * \brief Find a held call's B channel record. + * + * \param port Port the call is on. + * \param l3_id mISDN Layer 3 ID of held call. + * + * \return Found bc-record or NULL. + */ +struct misdn_bchannel *misdn_lib_find_held_bc(int port, int l3_id) +{ + struct misdn_bchannel *bc; + struct misdn_stack *stack; + bc = NULL; + for (stack = get_misdn_stack(); stack; stack = stack->next) { + if (stack->port == port) { + bc = stack_holder_find(stack, l3_id); + break; + } + } + return bc; +} + void misdn_lib_send_tone(struct misdn_bchannel *bc, enum tone_e tone) { char buf[mISDN_HEADER_LEN + 128] = ""; Index: channels/misdn/isdn_lib.h =================================================================== --- channels/misdn/isdn_lib.h (revision 205839) +++ channels/misdn/isdn_lib.h (working copy) @@ -593,8 +593,6 @@ char *manager_isdn_get_info(enum event_e event); -void misdn_lib_transfer(struct misdn_bchannel* holded_bc); - struct misdn_bchannel* misdn_lib_get_free_bc(int port, int channel, int inout, int dec); void manager_bchannel_activate(struct misdn_bchannel *bc); @@ -629,6 +627,7 @@ int misdn_lib_maxports_get(void) ; +struct misdn_bchannel *misdn_lib_find_held_bc(int port, int l3_id); void misdn_lib_release(struct misdn_bchannel *bc); int misdn_cap_is_speech(int cap);