Merged revisions 206487 via svnmerge from
[asterisk/asterisk.git] / channels / chan_misdn.c
index 5be536a..3a5a5b4 100644 (file)
@@ -305,15 +305,8 @@ enum misdn_chan_state {
        MISDN_ALERTING,            /*!< when Alerting */
        MISDN_BUSY,                /*!< when BUSY */
        MISDN_CONNECTED,           /*!< when connected */
-       MISDN_PRECONNECTED,        /*!< when connected (Noone sets this state) */
        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,              /*!< when on hold */
-       MISDN_HOLD_DISCONNECT,     /*!< when on hold */
 };
 
 /*! Asterisk created the channel (outgoing call) */
@@ -321,15 +314,25 @@ enum misdn_chan_state {
 /*! mISDN created the channel (incoming call) */
 #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
+        * \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;
@@ -501,13 +504,13 @@ struct chan_list {
 #endif /* defined(AST_MISDN_ENHANCEMENTS) */
 
        /*!
-        * \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.
+        * \note Used to find the HELD channel call record when retrieving a call.
         */
        unsigned int l3id;
 
@@ -662,7 +665,6 @@ static void chan_misdn_log(int level, int port, char *tmpl, ...)
 static struct ast_channel *misdn_new(struct chan_list *cl, int state,  char *exten, char *callerid, int format, const char *linkedid, 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
@@ -702,12 +704,13 @@ static struct chan_list *find_chan_by_bc(struct chan_list *list, struct misdn_bc
 static struct chan_list *find_chan_by_pid(struct chan_list *list, int pid);
 
 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);
 
 #if defined(AST_MISDN_ENHANCEMENTS)
 static const char misdn_command_name[] = "misdn_command";
@@ -4039,15 +4042,8 @@ static const struct state_struct state_array[] = {
        { 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 */
 /* *INDENT-ON* */
 };
 
@@ -4218,8 +4214,8 @@ static char *handle_cli_misdn_show_channels(struct ast_cli_entry *e, int cmd, st
                if (bc) {
                        print_bc_info(a->fd, help, bc);
                } else {
-                       if (help->state == MISDN_HOLDED) {
-                               ast_cli(a->fd, "ITS A HOLDED BC:\n");
+                       if (help->hold.state != MISDN_HOLD_IDLE) {
+                               ast_cli(a->fd, "ITS A HELD CALL BC:\n");
                                ast_cli(a->fd, " --> l3_id: %x\n"
                                        " --> dialed:%s\n"
                                        " --> caller:\"%s\" <%s>\n"
@@ -4229,8 +4225,8 @@ static char *handle_cli_misdn_show_channels(struct ast_cli_entry *e, int cmd, st
                                        ast->exten,
                                        ast->cid.cid_name ? ast->cid.cid_name : "",
                                        ast->cid.cid_num ? ast->cid.cid_num : "",
-                                       help->hold_info.port,
-                                       help->hold_info.channel
+                                       help->hold.port,
+                                       help->hold.channel
                                        );
                        } else {
                                ast_cli(a->fd, "* Channel in unknown STATE !!! Exten:%s, Callerid:%s\n", ast->exten, ast->cid.cid_num);
@@ -6662,12 +6658,18 @@ static int misdn_indication(struct ast_channel *ast, int cond, const void *data,
        }
 
        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->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:
@@ -6778,7 +6780,7 @@ static int misdn_indication(struct ast_channel *ast, int cond, const void *data,
                break;
        default:
                chan_misdn_log(1, p->bc->port, " --> * Unknown Indication:%d pid:%d\n", cond, p->bc->pid);
-               break;
+               return -1;
        }
 
        return 0;
@@ -6787,91 +6789,79 @@ static int misdn_indication(struct ast_channel *ast, int cond, const void *data,
 static int misdn_hangup(struct ast_channel *ast)
 {
        struct chan_list *p;
-       struct misdn_bchannel *bc = NULL;
-       const char *varcause = NULL;
-
-       ast_debug(1, "misdn_hangup(%s)\n", ast->name);
+       struct misdn_bchannel *bc;
+       const char *var;
 
        if (!ast || !(p = MISDN_ASTERISK_TECH_PVT(ast))) {
                return -1;
        }
+       MISDN_ASTERISK_TECH_PVT(ast) = NULL;
 
-       if (!p) {
-               chan_misdn_log(3, 0, "misdn_hangup called, without chan_list obj.\n");
-               return 0 ;
-       }
-
-       bc = p->bc;
-
-       if (bc) {
-               const char *tmp;
+       ast_debug(1, "misdn_hangup(%s)\n", ast->name);
 
-               ast_channel_lock(ast);
-               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;
                }
-               ast_channel_unlock(ast);
        }
 
-       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_debug(1, "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]);
-               ast_free(p);
-               ast_mutex_unlock(&release_lock);
-
+               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;
 
        ast_channel_lock(ast);
-       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;
        }
+
+       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);
+       }
        ast_channel_unlock(ast);
 
        chan_misdn_log(1, bc->port,
                "* IND : HANGUP\tpid:%d context:%s dialed:%s caller:\"%s\" <%s> State:%s\n",
-               p->bc ? p->bc->pid : -1,
+               bc->pid,
                ast->context,
                ast->exten,
                ast->cid.cid_name ? ast->cid.cid_name : "",
@@ -6883,27 +6873,29 @@ CLEAN_CH:
 
        switch (p->state) {
        case MISDN_INCOMING_SETUP:
-               p->state = MISDN_CLEANING;
-               /* 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);
                misdn_lib_send_event(bc, EVENT_RELEASE_COMPLETE);
-               break;
-       case MISDN_HOLDED:
+               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);
+               }
 
-               p->state = MISDN_CLEANING;
                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);
@@ -6914,79 +6906,53 @@ CLEAN_CH:
        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);
                }
                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;*/
                break;
        case MISDN_DISCONNECTED:
                if (bc->need_release) {
                        misdn_lib_send_event(bc, EVENT_RELEASE);
                }
-               p->state = MISDN_CLEANING; /* MISDN_HUNGUP_FROM_AST; */
                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) {
                                misdn_lib_send_event(bc, EVENT_RELEASE);
                        }
-                       p->state = MISDN_CLEANING;
                } else {
                        if (bc->need_disconnect) {
                                misdn_lib_send_event(bc, EVENT_DISCONNECT);
                        }
                }
+               break;
        }
 
        p->state = MISDN_CLEANING;
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-       if (p->peer) {
-               ao2_ref(p->peer, -1);
-               p->peer = NULL;
-       }
-#endif /* AST_MISDN_ENHANCEMENTS */
-
-       chan_misdn_log(3, bc->port, " --> Channel: %s hanguped new state:%s\n", ast->name, misdn_get_ch_state(p));
+       chan_misdn_log(3, bc->port, " --> Channel: %s hungup new state:%s\n", ast->name,
+               misdn_get_ch_state(p));
 
        return 0;
 }
@@ -7082,7 +7048,7 @@ static struct ast_frame *misdn_read(struct ast_channel *ast)
                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;
        }
@@ -7164,8 +7130,8 @@ static int misdn_write(struct ast_channel *ast, struct ast_frame *frame)
                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;
        }
 
@@ -7398,10 +7364,9 @@ static int dialtone_indicate(struct chan_list *cl)
        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)
@@ -7903,7 +7868,7 @@ static struct chan_list *find_chan_by_pid(struct chan_list *list, int pid)
        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;
 
@@ -7911,20 +7876,19 @@ static struct chan_list *find_holded(struct chan_list *list, struct misdn_bchann
                return NULL;
        }
 
-       chan_misdn_log(6, bc->port, "$$$ find_holded: channel:%d dialed:%s caller:\"%s\" <%s>\n",
+       chan_misdn_log(6, bc->port, "$$$ find_hold_call: channel:%d dialed:%s caller:\"%s\" <%s>\n",
                bc->channel,
                bc->dialed.number,
                bc->caller.name,
                bc->caller.number);
        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_holded: No channel found for dialed:%s caller:\"%s\" <%s>\n",
+               "$$$ find_hold_call: No channel found for dialed:%s caller:\"%s\" <%s>\n",
                bc->dialed.number,
                bc->caller.name,
                bc->caller.number);
@@ -7933,13 +7897,12 @@ static struct chan_list *find_holded(struct chan_list *list, struct misdn_bchann
 }
 
 
-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;
                }
        }
@@ -7947,6 +7910,42 @@ static struct chan_list *find_holded_l3(struct chan_list *list, unsigned long l3
        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);
@@ -8009,7 +8008,7 @@ static int pbx_start_chan(struct chan_list *ch)
        return ret;
 }
 
-static void hangup_chan(struct chan_list *ch)
+static void hangup_chan(struct chan_list *ch, struct misdn_bchannel *bc)
 {
        int port;
 
@@ -8018,15 +8017,15 @@ static void hangup_chan(struct chan_list *ch)
                return;
        }
 
-       port = ch->bc ? ch->bc->port : 0;
+       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) {
+                       send_cause2ast(ch->ast, bc, ch);
                        ast_hangup(ch->ast);
                }
                return;
@@ -8038,33 +8037,43 @@ static void hangup_chan(struct chan_list *ch)
 
        ch->need_queue_hangup = 0;
        if (ch->ast) {
-               send_cause2ast(ch->ast, ch->bc, ch);
-
-               if (ch->ast) {
-                       ast_queue_hangup_with_cause(ch->ast, ch->bc->cause);
-               }
+               send_cause2ast(ch->ast, bc, ch);
+               ast_queue_hangup_with_cause(ch->ast, bc->cause);
                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)
+/*!
+ * \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 chan_list *ch;
        struct ast_channel *ast;
 
+       ch->state = MISDN_CLEANING;
+
        ast_mutex_lock(&release_lock);
 
-       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 defined(AST_MISDN_ENHANCEMENTS)
+       if (ch->peer) {
+               ao2_ref(ch->peer, -1);
+               ch->peer = NULL;
        }
+#endif /* AST_MISDN_ENHANCEMENTS */
+
+       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) {
@@ -8094,17 +8103,15 @@ static void release_chan(struct misdn_bchannel *bc)
        close(ch->pipe[1]);
 
        ast = ch->ast;
-       if (ast && MISDN_ASTERISK_TECH_PVT(ast)) {
+       if (ast) {
+               MISDN_ASTERISK_TECH_PVT(ast) = NULL;
                chan_misdn_log(1, bc->port,
-                       "* RELEASING CHANNEL pid:%d context:%s dialed:%s caller:\"%s\" <%s> state: %s\n",
+                       "* RELEASING CHANNEL pid:%d context:%s dialed:%s caller:\"%s\" <%s>\n",
                        bc->pid,
                        ast->context,
                        ast->exten,
                        ast->cid.cid_name ? ast->cid.cid_name : "",
-                       ast->cid.cid_num ? 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->cid.cid_num ? ast->cid.cid_num : "");
 
                if (ast->_state != AST_STATE_RESERVED) {
                        chan_misdn_log(3, bc->port, " --> Setting AST State to down\n");
@@ -8112,25 +8119,118 @@ static void release_chan(struct misdn_bchannel *bc)
                }
        }
 
+       ast_free(ch);
+
+       ast_mutex_unlock(&release_lock);
+}
+
+/*!
+ * \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;
+
+       ast_mutex_lock(&release_lock);
+
+#if defined(AST_MISDN_ENHANCEMENTS)
+       if (ch->peer) {
+               ao2_ref(ch->peer, -1);
+               ch->peer = NULL;
+       }
+#endif /* AST_MISDN_ENHANCEMENTS */
+
        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 {
+                       --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);
+               }
+       }
+
        ast_free(ch);
 
        ast_mutex_unlock(&release_lock);
 }
 
-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;
 }
 
 
@@ -8169,7 +8269,7 @@ static void do_immediate_setup(struct misdn_bchannel *bc, struct chan_list *ch,
        if (!ast_canmatch_extension(ast, ast->context, ast->exten, 1, bc->caller.number) || pbx_start_chan(ch) < 0) {
                ast = NULL;
                bc->out_cause = AST_CAUSE_UNALLOCATED;
-               hangup_chan(ch);
+               hangup_chan(ch, bc);
                hanguptone_indicate(ch);
 
                misdn_lib_send_event(bc, bc->nt ? EVENT_RELEASE_COMPLETE : EVENT_DISCONNECT);
@@ -8351,7 +8451,7 @@ int add_out_calls(int port)
 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);
@@ -9299,6 +9399,7 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data)
 #if defined(AST_MISDN_ENHANCEMENTS)
        struct misdn_cc_record *cc_record;
 #endif /* defined(AST_MISDN_ENHANCEMENTS) */
+       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) {
@@ -9327,15 +9428,14 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data)
                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:
                case EVENT_REGISTER:
                        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:
@@ -9396,7 +9496,7 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data)
 
        case EVENT_NEW_BC:
                if (!ch) {
-                       ch = find_holded(cl_te,bc);
+                       ch = find_hold_call(cl_te,bc);
                }
 
                if (!ch) {
@@ -9458,10 +9558,10 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data)
                        /* 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 {
                                        ch->state = MISDN_CALLING_ACKNOWLEDGE;
-                                       hangup_chan(ch);
+                                       hangup_chan(ch, bc);
                                        ch->ast = NULL;
                                        break;
                                }
@@ -9681,10 +9781,10 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data)
                                ch->state = MISDN_INCOMING_SETUP;
                        }
                        if (ast_pickup_call(chan)) {
-                               hangup_chan(ch);
+                               hangup_chan(ch, bc);
                        } else {
                                ch->state = MISDN_CALLING_ACKNOWLEDGE;
-                               hangup_chan(ch);
+                               hangup_chan(ch, bc);
                                ch->ast = NULL;
                                break;
                        }
@@ -9988,8 +10088,6 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data)
        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);
-
                        if (bc->fac_in.Function != Fac_None) {
                                misdn_facility_ie_handler(event, bc, ch);
                        }
@@ -10016,23 +10114,40 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data)
                                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);
-                       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) {
+                               if (bc->fac_in.Function != Fac_None) {
+                                       misdn_facility_ie_handler(event, bc, held_ch);
+                               }
+
+                               if (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) {
@@ -10040,6 +10155,15 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data)
                }
                break;
        case EVENT_RELEASE:
+               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;
+                       }
+               }
                if (bc->fac_in.Function != Fac_None) {
                        misdn_facility_ie_handler(event, bc, ch);
                }
@@ -10047,12 +10171,12 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data)
                bc->need_disconnect = 0;
                bc->need_release = 0;
 
-               hangup_chan(ch);
-               release_chan(bc);
+               hangup_chan(ch, bc);
+               release_chan(ch, bc);
                break;
        case EVENT_RELEASE_COMPLETE:
-               if (bc->fac_in.Function != Fac_None) {
-                       misdn_facility_ie_handler(event, bc, ch);
+               if (!ch) {
+                       ch = find_hold_call_l3(cl_te, bc->l3_id);
                }
 
                bc->need_disconnect = 0;
@@ -10060,11 +10184,15 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data)
                bc->need_release_complete = 0;
 
                if (ch) {
+                       if (bc->fac_in.Function != Fac_None) {
+                               misdn_facility_ie_handler(event, bc, ch);
+                       }
+
                        stop_bc_tones(ch);
-                       hangup_chan(ch);
-                       ch->state = MISDN_CLEANING;
-#if defined(AST_MISDN_ENHANCEMENTS)
+                       hangup_chan(ch, bc);
+                       release_chan(ch, bc);
                } else {
+#if defined(AST_MISDN_ENHANCEMENTS)
                        /*
                         * A call-completion signaling link established with
                         * REGISTER does not have a struct chan_list record
@@ -10078,9 +10206,11 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data)
                        }
                        AST_LIST_UNLOCK(&misdn_cc_records_db);
 #endif /* defined(AST_MISDN_ENHANCEMENTS) */
-               }
 
-               release_chan(bc);
+                       chan_misdn_log(1, bc->port,
+                               " --> no Ch, so we've already released. (%s)\n",
+                               manager_isdn_get_info(event));
+               }
                break;
        case EVENT_BCHAN_ERROR:
        case EVENT_CLEANUP:
@@ -10094,8 +10224,8 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data)
                        break;
                }
 
-               hangup_chan(ch);
-               release_chan(bc);
+               hangup_chan(ch, bc);
+               release_chan(ch, bc);
                break;
        case EVENT_TONE_GENERATE:
        {
@@ -10181,8 +10311,8 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data)
                                        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");
@@ -10237,67 +10367,56 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data)
        /** 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);
-               }
-
                if (!ch) {
-                       ast_log(LOG_WARNING, "Found no Holded channel, cannot Retrieve\n");
-                       misdn_lib_send_event(bc, EVENT_RETRIEVE_REJECT);
-                       break;
+                       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;
+                       }
                }
 
-               /*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;
 
-               hold_ast = ast_bridged_channel(ch->ast);
+               ch->hold.state = MISDN_HOLD_IDLE;
+               ch->hold.port = 0;
+               ch->hold.channel = 0;
 
-               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;
-       }
        case EVENT_HOLD:
        {
                int hold_allowed;
-               struct ast_channel *bridged = ast_bridged_channel(ch->ast);
+               struct ast_channel *bridged;
 
                misdn_cfg_get(bc->port, MISDN_CFG_HOLD_ALLOWED, &hold_allowed, sizeof(hold_allowed));
-
                if (!hold_allowed) {
                        chan_misdn_log(-1, bc->port, "Hold not allowed this port.\n");
                        misdn_lib_send_event(bc, EVENT_HOLD_REJECT);
                        break;
                }
 
+               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);
+                       /* forget the channel now */
+                       ch->bc = NULL;
+                       ch->hold.state = MISDN_HOLD_ACTIVE;
+                       ch->hold.port = bc->port;
+                       ch->hold.channel = bc->channel;
 
-                       /* 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);
+                       ast_queue_control(ch->ast, AST_CONTROL_HOLD);
 
-                       /*forget the channel now*/
-                       ch->bc = NULL;
-                       ch->hold_info.port = bc->port;
-                       ch->hold_info.channel = bc->channel;
+                       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");
@@ -10379,7 +10498,7 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data)
        case EVENT_RESTART:
                if (!bc->dummy) {
                        stop_bc_tones(ch);
-                       release_chan(bc);
+                       release_chan(ch, bc);
                }
                break;
        default: