BRI PTMP: Active channels not cleared when the interface goes down.

If the connection to the terminal is lost while there are open channels
on the interface, red alarm is reported, but the open channels are never
cleared.  Additionally, if you manually try to channel request hangup,
Asterisk crashes.

For PTMP, the T309 processing was not searching the call pool on the
master control record.  Additionally, for NT PTMP, the timeout events were
not passed to the upper layer because the events were not put on the
master control record where timer processing expects them.

(closes issue #17865)
Reported by: wimpy
Patches:
      issue17865_v1.4.patch uploaded by rmudgett (license 664)
Tested by: rmudgett, wimpy


git-svn-id: https://origsvn.digium.com/svn/libpri/branches/1.4@1982 2fbb986a-6c06-0410-b554-c9c1f0a7f128
This commit is contained in:
Richard Mudgett 2010-09-13 16:07:24 +00:00
parent 0c79b0a4b1
commit 05158ec5fb
6 changed files with 286 additions and 52 deletions

7
pri.c
View File

@ -1478,7 +1478,7 @@ char *pri_dump_info_str(struct pri *ctrl)
enum PRI_TIMERS_AND_COUNTERS tmr; enum PRI_TIMERS_AND_COUNTERS tmr;
tmr = pri_timer[idx].number; tmr = pri_timer[idx].number;
if (0 <= ctrl->timers[tmr] || tmr == PRI_TIMER_T309) { if (0 <= ctrl->timers[tmr]) {
used = pri_snprintf(buf, used, buf_size, " %s: %d\n", used = pri_snprintf(buf, used, buf_size, " %s: %d\n",
pri_timer[idx].name, ctrl->timers[tmr]); pri_timer[idx].name, ctrl->timers[tmr]);
} }
@ -1504,8 +1504,11 @@ int pri_set_crv(struct pri *pri, q931_call *call, int crv, int callmode)
void pri_enslave(struct pri *master, struct pri *slave) void pri_enslave(struct pri *master, struct pri *slave)
{ {
if (master && slave) if (master && slave) {
slave->callpool = &master->localpool; slave->callpool = &master->localpool;
slave->nfas = 1;
master->nfas = 1;
}
} }
struct pri_sr *pri_sr_new(void) struct pri_sr *pri_sr_new(void)

View File

@ -96,6 +96,7 @@ struct pri {
int sapi; int sapi;
int tei; int tei;
int protodisc; int protodisc;
unsigned int nfas:1;/* TRUE if this D channel is involved with an NFAS group */
unsigned int bri:1; unsigned int bri:1;
unsigned int acceptinbanddisconnect:1; /* Should we allow inband progress after DISCONNECT? */ unsigned int acceptinbanddisconnect:1; /* Should we allow inband progress after DISCONNECT? */
unsigned int sendfacility:1; unsigned int sendfacility:1;
@ -886,6 +887,7 @@ extern int pri_schedule_event(struct pri *pri, int ms, void (*function)(void *da
extern pri_event *pri_schedule_run(struct pri *pri); extern pri_event *pri_schedule_run(struct pri *pri);
extern void pri_schedule_del(struct pri *pri, int ev); extern void pri_schedule_del(struct pri *pri, int ev);
int pri_schedule_check(struct pri *ctrl, int id, void (*function)(void *data), void *data);
extern pri_event *pri_mkerror(struct pri *pri, char *errstr); extern pri_event *pri_mkerror(struct pri *pri, char *errstr);

View File

@ -502,7 +502,8 @@ void q931_dump(struct pri *ctrl, int tei, q931_h *h, int len, int txrx);
void q931_destroycall(struct pri *pri, q931_call *c); void q931_destroycall(struct pri *pri, q931_call *c);
extern void q931_dl_indication(struct pri *pri, int event); void q931_dl_tei_removal(struct pri *link);
void q931_dl_indication(struct pri *link, int event);
int q931_send_hold(struct pri *ctrl, struct q931_call *call); int q931_send_hold(struct pri *ctrl, struct q931_call *call);
int q931_send_hold_ack(struct pri *ctrl, struct q931_call *call); int q931_send_hold_ack(struct pri *ctrl, struct q931_call *call);

View File

@ -256,3 +256,32 @@ void pri_schedule_del(struct pri *ctrl, int id)
ctrl->sched.num_slots); ctrl->sched.num_slots);
} }
} }
/*!
* \brief Is the scheduled event this callback.
*
* \param ctrl D channel controller.
* \param id Scheduled event id to check.
* 0 is a disabled/unscheduled event id.
* 1 - MAX_SCHED is a valid event id.
* \param function Callback function to call when timeout.
* \param data Value to give callback function when timeout.
*
* \return TRUE if scheduled event has the callback.
*/
int pri_schedule_check(struct pri *ctrl, int id, void (*function)(void *data), void *data)
{
/* Scheduling runs on master channels only */
ctrl = PRI_MASTER(ctrl);
if (0 < id && id <= ctrl->sched.num_slots) {
if (ctrl->sched.timer[id - 1].callback == function
&& ctrl->sched.timer[id - 1].data == data) {
return 1;
}
} else if (id) {
pri_error(ctrl, "Asked to check sched id %d??? num_slots=%d\n", id,
ctrl->sched.num_slots);
}
return 0;
}

2
q921.c
View File

@ -1433,6 +1433,8 @@ static void q921_mdl_remove(struct pri *ctrl)
return; return;
} }
q931_dl_tei_removal(ctrl);
/* /*
* Negate the TEI value so debug messages will display a * Negate the TEI value so debug messages will display a
* negated TEI when it is actually unassigned. * negated TEI when it is actually unassigned.

295
q931.c
View File

@ -4879,10 +4879,11 @@ static void pri_connect_timeout(void *data)
{ {
struct q931_call *c = data; struct q931_call *c = data;
struct pri *ctrl = c->pri; struct pri *ctrl = c->pri;
if (ctrl->debug & PRI_DEBUG_Q931_STATE) if (ctrl->debug & PRI_DEBUG_Q931_STATE)
pri_message(ctrl, "Timed out looking for connect acknowledge\n"); pri_message(ctrl, "Timed out looking for connect acknowledge\n");
c->retranstimer = 0;
q931_disconnect(ctrl, c, PRI_CAUSE_NORMAL_CLEARING); q931_disconnect(ctrl, c, PRI_CAUSE_NORMAL_CLEARING);
} }
/* T308 expiry, first time */ /* T308 expiry, first time */
@ -4890,9 +4891,11 @@ static void pri_release_timeout(void *data)
{ {
struct q931_call *c = data; struct q931_call *c = data;
struct pri *ctrl = c->pri; struct pri *ctrl = c->pri;
if (ctrl->debug & PRI_DEBUG_Q931_STATE) if (ctrl->debug & PRI_DEBUG_Q931_STATE)
pri_message(ctrl, "Timed out looking for release complete\n"); pri_message(ctrl, "Timed out looking for release complete\n");
c->t308_timedout++; c->t308_timedout++;
c->retranstimer = 0;
c->alive = 1; c->alive = 1;
/* The call to q931_release will re-schedule T308 */ /* The call to q931_release will re-schedule T308 */
@ -4904,6 +4907,8 @@ static void pri_release_finaltimeout(void *data)
{ {
struct q931_call *c = data; struct q931_call *c = data;
struct pri *ctrl = c->pri; struct pri *ctrl = c->pri;
c->retranstimer = 0;
c->alive = 1; c->alive = 1;
if (ctrl->debug & PRI_DEBUG_Q931_STATE) if (ctrl->debug & PRI_DEBUG_Q931_STATE)
pri_message(ctrl, "Final time-out looking for release complete\n"); pri_message(ctrl, "Final time-out looking for release complete\n");
@ -4930,8 +4935,10 @@ static void pri_disconnect_timeout(void *data)
{ {
struct q931_call *c = data; struct q931_call *c = data;
struct pri *ctrl = c->pri; struct pri *ctrl = c->pri;
if (ctrl->debug & PRI_DEBUG_Q931_STATE) if (ctrl->debug & PRI_DEBUG_Q931_STATE)
pri_message(ctrl, "Timed out looking for release\n"); pri_message(ctrl, "Timed out looking for release\n");
c->retranstimer = 0;
c->alive = 1; c->alive = 1;
q931_release(ctrl, c, PRI_CAUSE_NORMAL_CLEARING); q931_release(ctrl, c, PRI_CAUSE_NORMAL_CLEARING);
} }
@ -5266,6 +5273,9 @@ int q931_setup(struct pri *ctrl, q931_call *c, struct pri_sr *req)
c->cc.saved_ie_contents.length = 0; c->cc.saved_ie_contents.length = 0;
c->cc.saved_ie_flags = 0; c->cc.saved_ie_flags = 0;
if (BRI_NT_PTMP(ctrl)) {
c->outboundbroadcast = 1;
}
if (ctrl->subchannel && !ctrl->bri) if (ctrl->subchannel && !ctrl->bri)
res = send_message(ctrl, c, Q931_SETUP, gr303_setup_ies); res = send_message(ctrl, c, Q931_SETUP, gr303_setup_ies);
else if (c->cis_call) else if (c->cis_call)
@ -5279,13 +5289,9 @@ int q931_setup(struct pri *ctrl, q931_call *c, struct pri_sr *req)
UPDATE_OURCALLSTATE(ctrl, c, Q931_CALL_STATE_CALL_INITIATED); UPDATE_OURCALLSTATE(ctrl, c, Q931_CALL_STATE_CALL_INITIATED);
c->peercallstate = Q931_CALL_STATE_CALL_PRESENT; c->peercallstate = Q931_CALL_STATE_CALL_PRESENT;
c->t303_expirycnt = 0; c->t303_expirycnt = 0;
if (BRI_NT_PTMP(ctrl)) {
c->outboundbroadcast = 1;
}
start_t303(c); start_t303(c);
} }
return res; return res;
} }
static int register_ies[] = { Q931_IE_FACILITY, -1 }; static int register_ies[] = { Q931_IE_FACILITY, -1 };
@ -5845,6 +5851,12 @@ static void pri_fake_clearing(void *data)
struct q931_call *c = data; struct q931_call *c = data;
struct pri *ctrl = c->pri; struct pri *ctrl = c->pri;
/*
* We cannot clear the retranstimer id because we are called by t303_expiry also.
* Fortunately, it doesn't matter because pri_internal_clear() will stop it if
* it was actually running.
*/
//c->retranstimer = 0;
c->performing_fake_clearing = 1; c->performing_fake_clearing = 1;
if (pri_internal_clear(c) == Q931_RES_HAVEEVENT) if (pri_internal_clear(c) == Q931_RES_HAVEEVENT)
ctrl->schedev = 1; ctrl->schedev = 1;
@ -5852,6 +5864,7 @@ static void pri_fake_clearing(void *data)
static void pri_create_fake_clearing(struct q931_call *c, struct pri *master) static void pri_create_fake_clearing(struct q931_call *c, struct pri *master)
{ {
/* Point to the master so the timeout event can come out. */
c->pri = master; c->pri = master;
pri_schedule_del(master, c->retranstimer); pri_schedule_del(master, c->retranstimer);
@ -8279,6 +8292,18 @@ static int pri_internal_clear(void *data)
UPDATE_OURCALLSTATE(ctrl, c, Q931_CALL_STATE_NULL); UPDATE_OURCALLSTATE(ctrl, c, Q931_CALL_STATE_NULL);
c->peercallstate = Q931_CALL_STATE_NULL; c->peercallstate = Q931_CALL_STATE_NULL;
if (c->master_call->outboundbroadcast
&& c == q931_find_winning_call(c)) {
/* Pass the hangup cause to the master_call. */
c->master_call->cause = c->cause;
/* Declare this winning subcall to no longer be the winner and destroy it. */
c->master_call->pri_winner = -1;
q931_destroycall(ctrl, c);
return 0;
}
q931_clr_subcommands(ctrl); q931_clr_subcommands(ctrl);
ctrl->ev.hangup.subcmds = &ctrl->subcmds; ctrl->ev.hangup.subcmds = &ctrl->subcmds;
ctrl->ev.hangup.channel = q931_encode_channel(c); ctrl->ev.hangup.channel = q931_encode_channel(c);
@ -8320,9 +8345,15 @@ static void pri_dl_down_timeout(void *data)
{ {
struct q931_call *c = data; struct q931_call *c = data;
struct pri *ctrl = c->pri; struct pri *ctrl = c->pri;
if (ctrl->debug & PRI_DEBUG_Q931_STATE)
pri_message(ctrl, DBGHEAD "Timed out waiting for data link re-establishment\n", DBGINFO);
/* Point to the master so the timeout event can come out. */
ctrl = PRI_MASTER(ctrl);
c->pri = ctrl;
if (ctrl->debug & PRI_DEBUG_Q931_STATE)
pri_message(ctrl, "T309 timed out waiting for data link re-establishment\n");
c->retranstimer = 0;
c->cause = PRI_CAUSE_DESTINATION_OUT_OF_ORDER; c->cause = PRI_CAUSE_DESTINATION_OUT_OF_ORDER;
if (pri_internal_clear(c) == Q931_RES_HAVEEVENT) if (pri_internal_clear(c) == Q931_RES_HAVEEVENT)
ctrl->schedev = 1; ctrl->schedev = 1;
@ -8333,94 +8364,260 @@ static void pri_dl_down_cancelcall(void *data)
{ {
struct q931_call *c = data; struct q931_call *c = data;
struct pri *ctrl = c->pri; struct pri *ctrl = c->pri;
if (ctrl->debug & PRI_DEBUG_Q931_STATE)
pri_message(ctrl, DBGHEAD "Cancel non active call after data link failure\n", DBGINFO);
/* Point to the master so the timeout event can come out. */
ctrl = PRI_MASTER(ctrl);
c->pri = ctrl;
if (ctrl->debug & PRI_DEBUG_Q931_STATE)
pri_message(ctrl, "Cancel call after data link failure\n");
c->retranstimer = 0;
c->cause = PRI_CAUSE_DESTINATION_OUT_OF_ORDER; c->cause = PRI_CAUSE_DESTINATION_OUT_OF_ORDER;
if (pri_internal_clear(c) == Q931_RES_HAVEEVENT) if (pri_internal_clear(c) == Q931_RES_HAVEEVENT)
ctrl->schedev = 1; ctrl->schedev = 1;
} }
/* Receive an indication from Layer 2 */ /*!
void q931_dl_indication(struct pri *ctrl, int event) * \brief Layer 2 is removing the link's TEI.
*
* \param link Q.921 link losing it's TEI.
*
* \note
* For NT PTMP, this deviation from the specifications is needed
* because we have no way to re-associate any T309 calls on the
* removed TEI.
*
* \return Nothing
*/
void q931_dl_tei_removal(struct pri *link)
{ {
struct q931_call *cur; struct q931_call *cur;
struct q931_call *winner; struct q931_call *call;
struct pri *ctrl;
int idx;
/* Just return if T309 is not enabled. */ /* Find the master - He has the call pool */
if (!ctrl || ctrl->timers[PRI_TIMER_T309] < 0) ctrl = PRI_MASTER(link);
if (ctrl->debug & PRI_DEBUG_Q931_STATE) {
pri_message(ctrl, "DL TEI removal\n");
}
if (!BRI_NT_PTMP(ctrl)) {
/* Only NT PTMP has anything to worry about when the TEI is removed. */
return; return;
}
for (cur = *ctrl->callpool; cur; cur = cur->next) {
if (!(cur->cr & ~Q931_CALL_REFERENCE_FLAG)) {
/* Don't do anything on the global call reference call record. */
continue;
}
if (cur->outboundbroadcast) {
/* Does this master call have a subcall on the link that went down? */
call = NULL;
for (idx = 0; idx < ARRAY_LEN(cur->subcalls); ++idx) {
if (cur->subcalls[idx] && cur->subcalls[idx]->pri == link) {
/* This subcall is on the link that went down. */
call = cur->subcalls[idx];
break;
}
}
if (!call) {
/* No subcall is on the link that went down. */
continue;
}
} else if (cur->pri != link) {
/* This call is not on the link that went down. */
continue;
} else {
call = cur;
}
/*
* NOTE: We are gambling that no T309 timer's have had a chance
* to expire. They should not expire since we are either called
* immediately after the q931_dl_indication() or after a timeout
* of 0.
*/
if (ctrl->debug & PRI_DEBUG_Q931_STATE) {
pri_message(ctrl, "Cancel call cref=%d on channel %d in state %d (%s)\n",
call->cr, call->channelno, call->ourcallstate,
q931_call_state_str(call->ourcallstate));
}
call->pri = ctrl;/* Point to a safer place until the call is destroyed. */
pri_schedule_del(ctrl, call->retranstimer);
call->retranstimer = pri_schedule_event(ctrl, 0, pri_dl_down_cancelcall, call);
}
}
/* Receive an indication from Layer 2 */
void q931_dl_indication(struct pri *link, int event)
{
struct q931_call *cur;
struct q931_call *call;
struct pri *ctrl;
int idx;
if (!link) {
return;
}
/* Find the master - He has the call pool */
ctrl = PRI_MASTER(link);
if (BRI_TE_PTMP(ctrl)) {
/* The link is always the master */
link = ctrl;
}
switch (event) { switch (event) {
case PRI_EVENT_DCHAN_DOWN: case PRI_EVENT_DCHAN_DOWN:
if (ctrl->debug & PRI_DEBUG_Q931_STATE) { if (ctrl->debug & PRI_DEBUG_Q931_STATE) {
pri_message(ctrl, DBGHEAD "link is DOWN\n", DBGINFO); pri_message(ctrl, "DL-RELEASE indication (link is DOWN)\n");
} }
for (cur = *ctrl->callpool; cur; cur = cur->next) { for (cur = *ctrl->callpool; cur; cur = cur->next) {
if (!(cur->cr & ~Q931_CALL_REFERENCE_FLAG)) { if (!(cur->cr & ~Q931_CALL_REFERENCE_FLAG)) {
/* Don't do anything on the global call reference call record. */ /* Don't do anything on the global call reference call record. */
continue; continue;
} else if (cur->ourcallstate == Q931_CALL_STATE_ACTIVE) { }
/* For a call in Active state, activate T309 only if there is no timer already running. */ if (cur->outboundbroadcast) {
if (!cur->retranstimer) { /* Does this master call have a subcall on the link that went down? */
call = NULL;
for (idx = 0; idx < ARRAY_LEN(cur->subcalls); ++idx) {
if (cur->subcalls[idx] && cur->subcalls[idx]->pri == link) {
/* This subcall is on the link that went down. */
call = cur->subcalls[idx];
break;
}
}
if (!call) {
/* No subcall is on the link that went down. */
continue;
}
} else if (cur->pri != link) {
/* This call is not on the link that went down. */
continue;
} else {
call = cur;
}
switch (call->ourcallstate) {
case Q931_CALL_STATE_ACTIVE:
/* NOTE: Only a winning subcall can get to the active state. */
if (ctrl->nfas) {
/*
* The upper layer should handle T309 for NFAS since the calls
* could be maintained by a backup D channel if the B channel
* for the call did not go into alarm.
*/
break;
}
/*
* For a call in Active state, activate T309 only if there is
* no timer already running.
*
* NOTE: cur != call when we have a winning subcall.
*/
if (!cur->retranstimer || !call->retranstimer) {
if (ctrl->debug & PRI_DEBUG_Q931_STATE) {
pri_message(ctrl, "Start T309 for call cref=%d on channel %d\n",
call->cr, call->channelno);
}
call->retranstimer = pri_schedule_event(ctrl,
ctrl->timers[PRI_TIMER_T309], pri_dl_down_timeout, call);
}
break;
case Q931_CALL_STATE_NULL:
break;
default:
/*
* For a call that is not in Active state, schedule internal
* clearing of the call 'ASAP' (delay 0).
*
* NOTE: We are killing NFAS calls that are not connected yet
* because there are likely messages in flight when this link
* went down that could leave the call in an unknown/stuck state.
*/
if (ctrl->debug & PRI_DEBUG_Q931_STATE) { if (ctrl->debug & PRI_DEBUG_Q931_STATE) {
pri_message(ctrl, pri_message(ctrl,
DBGHEAD "activate T309 for call %d on channel %d\n", DBGINFO, "Cancel call cref=%d on channel %d in state %d (%s)\n",
cur->cr, cur->channelno); call->cr, call->channelno, call->ourcallstate,
q931_call_state_str(call->ourcallstate));
} }
cur->retranstimer = pri_schedule_event(ctrl, ctrl->timers[PRI_TIMER_T309], pri_dl_down_timeout, cur); if (cur->outboundbroadcast) {
/* Simply destroy non-winning subcalls. */
q931_destroycall(ctrl, call);
continue;
} }
} else if (cur->ourcallstate != Q931_CALL_STATE_NULL) { pri_schedule_del(ctrl, call->retranstimer);
/* For a call that is not in Active state, schedule internal clearing of the call 'ASAP' (delay 0). */ call->retranstimer = pri_schedule_event(ctrl, 0, pri_dl_down_cancelcall,
if (ctrl->debug & PRI_DEBUG_Q931_STATE) { call);
pri_message(ctrl, break;
DBGHEAD "cancel call %d on channel %d in state %d (%s)\n", DBGINFO,
cur->cr, cur->channelno, cur->ourcallstate,
q931_call_state_str(cur->ourcallstate));
}
pri_schedule_del(ctrl, cur->retranstimer);
cur->retranstimer = pri_schedule_event(ctrl, 0, pri_dl_down_cancelcall, cur);
} }
} }
break; break;
case PRI_EVENT_DCHAN_UP: case PRI_EVENT_DCHAN_UP:
if (ctrl->debug & PRI_DEBUG_Q931_STATE) { if (ctrl->debug & PRI_DEBUG_Q931_STATE) {
pri_message(ctrl, DBGHEAD "link is UP\n", DBGINFO); pri_message(ctrl, "DL-ESTABLISH indication (link is UP)\n");
} }
for (cur = *ctrl->callpool; cur; cur = cur->next) { for (cur = *ctrl->callpool; cur; cur = cur->next) {
if (!(cur->cr & ~Q931_CALL_REFERENCE_FLAG)) { if (!(cur->cr & ~Q931_CALL_REFERENCE_FLAG)) {
/* Don't do anything on the global call reference call record. */ /* Don't do anything on the global call reference call record. */
continue; continue;
} else if (cur->ourcallstate == Q931_CALL_STATE_ACTIVE && cur->retranstimer) { }
if (cur->outboundbroadcast) {
/* Does this master call have a subcall on the link that came up? */
call = NULL;
for (idx = 0; idx < ARRAY_LEN(cur->subcalls); ++idx) {
if (cur->subcalls[idx] && cur->subcalls[idx]->pri == link) {
/* This subcall is on the link that came up. */
call = cur->subcalls[idx];
break;
}
}
if (!call) {
/* No subcall is on the link that came up. */
continue;
}
} else if (cur->pri != link) {
/* This call is not on the link that came up. */
continue;
} else {
call = cur;
}
switch (call->ourcallstate) {
case Q931_CALL_STATE_ACTIVE:
/* NOTE: Only a winning subcall can get to the active state. */
if (pri_schedule_check(ctrl, call->retranstimer, pri_dl_down_timeout, call)) {
if (ctrl->debug & PRI_DEBUG_Q931_STATE) { if (ctrl->debug & PRI_DEBUG_Q931_STATE) {
pri_message(ctrl, pri_message(ctrl, "Stop T309 for call cref=%d on channel %d\n",
DBGHEAD "cancel T309 for call %d on channel %d\n", DBGINFO, call->cr, call->channelno);
cur->cr, cur->channelno);
} }
pri_schedule_del(ctrl, cur->retranstimer); pri_schedule_del(ctrl, call->retranstimer);
cur->retranstimer = 0; call->retranstimer = 0;
winner = q931_find_winning_call(cur);
if (winner) {
q931_status(ctrl, winner, PRI_CAUSE_NORMAL_UNSPECIFIED);
} }
} else if (cur->ourcallstate != Q931_CALL_STATE_NULL && q931_status(ctrl, call, PRI_CAUSE_NORMAL_UNSPECIFIED);
cur->ourcallstate != Q931_CALL_STATE_DISCONNECT_REQUEST && break;
cur->ourcallstate != Q931_CALL_STATE_DISCONNECT_INDICATION && case Q931_CALL_STATE_NULL:
cur->ourcallstate != Q931_CALL_STATE_RELEASE_REQUEST) { case Q931_CALL_STATE_DISCONNECT_REQUEST:
case Q931_CALL_STATE_DISCONNECT_INDICATION:
case Q931_CALL_STATE_RELEASE_REQUEST:
break;
default:
/* /*
* The STATUS message sent here is not required by Q.931, * The STATUS message sent here is not required by Q.931,
* but it may help anyway. * but it may help anyway.
* This looks like a new call started while the link was down. * This looks like a new call started while the link was down.
*/ */
winner = q931_find_winning_call(cur); q931_status(ctrl, call, PRI_CAUSE_NORMAL_UNSPECIFIED);
if (winner) { break;
q931_status(ctrl, winner, PRI_CAUSE_NORMAL_UNSPECIFIED);
}
} }
} }
break; break;
default: default:
pri_message(ctrl, DBGHEAD "unexpected event %d.\n", DBGINFO, event); pri_message(ctrl, DBGHEAD "unexpected event %d.\n", DBGINFO, event);
break;
} }
} }