/* POTS/PSTN FXS implementation * * (C) 2022 by Andreas Eversberg * All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include "../libdebug/debug.h" #include "../libsample/sample.h" #include "../libg711/g711.h" #include "pstn.h" #include "../libosmocc/helper.h" #define db2level(db) pow(10, (double)(db) / 20.0) #define DIALTONE_TO 60.0 #define DIALING_TO 20.0 #define RELEASE_TO 60.0 #define HOOKFLASH_TO 1.1 /* 1100 ms */ #define TONE_CW (pstn->tones_type != TONES_TYPE_AMERICAN) ? TONE_GERMAN_CW : TONE_AMERICAN_CW #define TONE_DIALTONE (pstn->tones_type != TONES_TYPE_AMERICAN) ? ((pstn->tones_type != TONES_TYPE_OLDGERMAN) ? TONE_GERMAN_DIALTONE : TONE_GERMAN_OLDDIALTONE) : TONE_AMERICAN_DIALTONE #define TONE_BUSY (pstn->tones_type != TONES_TYPE_AMERICAN) ? ((pstn->tones_type != TONES_TYPE_OLDGERMAN) ? TONE_GERMAN_BUSY : TONE_GERMAN_OLDBUSY) : TONE_AMERICAN_BUSY #define TONE_HANGUP (pstn->tones_type != TONES_TYPE_AMERICAN) ? ((pstn->tones_type != TONES_TYPE_OLDGERMAN) ? TONE_GERMAN_HANGUP : TONE_GERMAN_OLDHANGUP) : TONE_AMERICAN_HANGUP #define TONE_GASSENBESETZT (pstn->tones_type != TONES_TYPE_AMERICAN) ? ((pstn->tones_type != TONES_TYPE_OLDGERMAN) ? TONE_GERMAN_GASSENBESETZT : TONE_GERMAN_OLDBUSY) : TONE_AMERICAN_BUSY #define TONE_RINGING (pstn->tones_type != TONES_TYPE_AMERICAN) ? ((pstn->tones_type != TONES_TYPE_OLDGERMAN) ? TONE_GERMAN_RINGING : TONE_GERMAN_OLDRINGING) : TONE_AMERICAN_RINGING /* Uncomment this, to hear caller ID even after answering: */ //#define TEST_CALLERID static void v5_sig_ind(pstn_t *pstn, uint8_t *data, int len); static void v5_est_ack_req(pstn_t *pstn); static void v5_sig_req(pstn_t *pstn, uint8_t *ie, int length); static void v5_disc_req_and_cleanup(pstn_t *pstn); void __attribute__((noinline)) safe_strcpy(char *dst, const char *src, size_t size) { strncpy(dst, src, size - 1); } static struct osmo_cc_helper_audio_codecs codecs_alaw_ulaw[] = { { "PCMA", 8000, 1, g711_transcode_flipped, g711_transcode_flipped }, { "PCMU", 8000, 1, g711_transcode_alaw_flipped_to_ulaw, g711_transcode_ulaw_to_alaw_flipped }, { NULL, 0, 0, NULL, NULL}, }; static struct osmo_cc_helper_audio_codecs codecs_ulaw_alaw[] = { { "PCMU", 8000, 1, g711_transcode_flipped, g711_transcode_flipped }, { "PCMA", 8000, 1, g711_transcode_ulaw_flipped_to_alaw, g711_transcode_alaw_to_ulaw_flipped }, { NULL, 0, 0, NULL, NULL}, }; static const char *pstn_state_name(enum pstn_state state) { switch (state) { case PSTN_STATE_OOS: return "OUT-OF-SERVICE"; case PSTN_STATE_NULL: return "NULL"; case PSTN_STATE_EST_LE: return "ESTABLISH-LE"; case PSTN_STATE_EST_AN: return "ESTABLISH-AN"; case PSTN_STATE_ACTIVE: return "ACTIVE"; case PSTN_STATE_DISC_REQ: return "DISC-REQ"; case PSTN_STATE_BLOCKED: return "BLOCKED"; default: return ""; } }; static const char *pstn_event_name(enum pstn_event event) { switch (event) { case PSTN_EVENT_EST_REQ: return "FE-establish_request"; case PSTN_EVENT_EST_ACK_IND: return "FE-establish_acknowlege_indication"; case PSTN_EVENT_EST_IND: return "FE-establish_indication"; case PSTN_EVENT_EST_ACK_REQ: return "FE-establish_scknowlege_request"; case PSTN_EVENT_SIG_REQ: return "FE-line_signal_request"; case PSTN_EVENT_SIG_IND: return "FE-line_signal_indication"; case PSTN_EVENT_PARAM_REQ: return "FE-protocol_parameter_request"; case PSTN_EVENT_DISC_REQ: return "FE-disconnect_request"; case PSTN_EVENT_DISC_CPL_REQ: return "FE-disconnect_complete_request"; case PSTN_EVENT_DISC_CPL_IND: return "FE-disconnect_complete_inidcation"; default: return ""; } }; static const char *pstn_call_state_name(enum pstn_call_state state) { switch (state) { case CALL_STATE_NULL: return "NULL"; case CALL_STATE_ENBLOCK: return "ENBLOCK"; case CALL_STATE_ALERTING_SUB: return "ALERTING-SUB"; case CALL_STATE_OVERLAP_NET: return "OVERLAP-NET"; case CALL_STATE_PROCEEDING_NET: return "PROCEEDING-NET"; case CALL_STATE_ALERTING_NET: return "ALERTING-NET"; case CALL_STATE_ACTIVE: return "ACTIVE"; case CALL_STATE_HOLD: return "HOLD"; case CALL_STATE_DISCONNECT_NET: return "DISCONNECT-NET"; default: return ""; } }; static const char *timer_ident_name(enum timer_ident ident) { switch (ident) { case TIMER_IDENT_DIALING: return "DIALING"; case TIMER_IDENT_RELEASE: return "RELEASE"; case TIMER_IDENT_HOOKFLASH: return "HOOKFLASH"; default: return ""; } }; /* * dial hints */ static struct dial_hint *dial_hints = NULL; int add_dial_hint(const char *arg) { struct dial_hint *hint; int i, dash; hint = calloc(1, sizeof(*hint)); if (!hint) return -ENOMEM; for (i = 0, dash = -1; arg[i]; i++) { if (arg[i] == '-') { /* only single dash is allowed */ if (dash >= 0) goto error; dash = i; continue; } /* only digits 0..9 are allowd */ if (arg[i] < '0' || arg[i] > '9') goto error; } if (dash < 0) { /* number must have at least one digit */ if (i < 1) goto error; hint->length = i; hint->from = calloc(1, i + 1); if (!hint->from) goto error; strcpy(hint->from, arg); PDEBUG(DTEL, DEBUG_INFO, "Added dial hint '%s'\n", hint->from); } else { /* both numbers must have same lenth */ if (dash != i - dash - 1) goto error; /* there must be digits at all */ if (dash < 1) goto error; hint->length = dash; hint->from = calloc(1, dash + 1); if (!hint->from) goto error; strncpy(hint->from, arg, dash); hint->to = calloc(1, dash + 1); if (!hint->to) goto error; strcpy(hint->to, arg + dash + 1); /* second string must be greater than first string */ if (strcmp(hint->from, hint->to) > 0) goto error; PDEBUG(DTEL, DEBUG_INFO, "Added dial hint '%s' - '%s'\n", hint->from, hint->to); } hint->next = dial_hints; dial_hints = hint; return 0; error: free(hint->from); free(hint->to); free(hint); return -EINVAL; } static int check_dial_hint(const char *number) { struct dial_hint *hint; size_t length = strlen(number); for (hint = dial_hints; hint; hint = hint->next) { if (hint->length != length) continue; if (hint->from && !hint->to) { if (strncmp(number, hint->from, hint->length) == 0) return 1; } if (hint->from && hint->to) { if (strncmp(number, hint->from, hint->length) >= 0 && strncmp(number, hint->to, hint->length) <= 0) return 1; } } return 0; } void purge_dial_hints(void) { struct dial_hint *hint; while ((hint = dial_hints)) { dial_hints = hint->next; free(hint->from); free(hint->to); free(hint); } } /* * Endpoint instance */ static void pstn_call_state(struct call *pstn_call, enum pstn_call_state state) { PDEBUG(DTEL, DEBUG_DEBUG, "PSTN CALL state '%s' -> '%s'\n", pstn_call_state_name(pstn_call->state), pstn_call_state_name(state)); pstn_call->state = state; } void ph_socket_rx_msg(ph_socket_t *s, int channel, uint8_t prim, uint8_t *data, int length); static void dtmf_off(pstn_t *pstn); /* create interface instance */ pstn_t *pstn_create(void) { pstn_t *pstn; int i; pstn = calloc(1, sizeof(*pstn)); if (!pstn) { PDEBUG(DTEL, DEBUG_ERROR, "No memory!\n"); abort(); } for (i = 0; i < 2; i++) { pstn->call[i] = calloc(1, sizeof(struct call)); if (!pstn->call[i]) { PDEBUG(DTEL, DEBUG_ERROR, "No memory!\n"); abort(); } pstn->call[i]->pstn = pstn; } PDEBUG(DTEL, DEBUG_DEBUG, "PSTN instance created\n"); return pstn; } static void pulse_on(pstn_t *pstn) { if (pstn->pulse_on) return; PDEBUG(DTEL, DEBUG_DEBUG, "Enable reception of pulse dialing.\n"); pstn->pulse_on = 1; } static void pulse_off(pstn_t *pstn) { if (!pstn->pulse_on) return; PDEBUG(DTEL, DEBUG_DEBUG, "Disable reception of pulse dialing.\n"); pstn->pulse_on = 0; } /* release call towards osmo-cc, if still connected and remove association with pstn instance */ static void release_call(pstn_t *pstn, int hold, uint8_t isdn_cause) { osmo_cc_call_t *cc_call; osmo_cc_msg_t *new_msg; struct call *pstn_call = pstn->call[hold]; PDEBUG(DTEL, DEBUG_INFO, "Release %s towards Osmo-CC with ISDN cause %d, if exists.\n", (hold) ? "call on hold" : "active call", isdn_cause); /* get call state */ cc_call = osmo_cc_call_by_callref(&pstn->cc_ep, pstn_call->cc_callref); if (cc_call) { /* on release request, we confirm */ /* create osmo-cc message */ if (cc_call->state == OSMO_CC_STATE_RELEASING_OUT) new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_CNF); else if (cc_call->state == OSMO_CC_STATE_INIT_OUT) new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REJ_IND); else new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND); /* cause */ osmo_cc_add_ie_cause(new_msg, pstn->serving_location, isdn_cause, 0, 0); /* send message to osmo-cc */ osmo_cc_ll_msg(&pstn->cc_ep, pstn_call->cc_callref, new_msg); /* unlink callref */ pstn_call->cc_callref = 0; } /* free session description */ if (pstn_call->cc_session) { osmo_cc_free_session(pstn_call->cc_session); pstn_call->cc_session = NULL; pstn_call->codec = NULL; } /* free sdp */ if (pstn_call->sdp) { free((char *)pstn_call->sdp); pstn_call->sdp = NULL; } /* reset state */ pstn_call_state(pstn_call, CALL_STATE_NULL); } /* destroy interface instance and free all resource */ void pstn_destroy(pstn_t *pstn) { /* stop DTMF */ dtmf_off(pstn); /* stop pulse */ pulse_off(pstn); /* release association with osmo-cc */ release_call(pstn, PSTN_CALL_ACTIVE, OSMO_CC_ISDN_CAUSE_DEST_OOO); release_call(pstn, PSTN_CALL_HOLD, OSMO_CC_ISDN_CAUSE_DEST_OOO); /* exit callerid */ callerid_exit(&pstn->callerid); /* exit dtmf detector */ dtmf_decode_exit(&pstn->dtmf_dec); /* close ph socket */ ph_socket_exit(&pstn->ph_socket); /* free jitter buffer */ jitter_destroy(&pstn->tx_dejitter); /* destroy timer */ timer_exit(&pstn->timer); free(pstn->call[0]); free(pstn->call[1]); free((char *)pstn->name); free(pstn); PDEBUG(DTEL, DEBUG_DEBUG, "PSTN instance destroyed\n"); } static void pstn_timeout(void *data); void recv_dtmf(void *priv, char digit, dtmf_meas_t __attribute__((unused)) *meas); static void pstn_new_state(pstn_t *pstn, enum pstn_state state) { PDEBUG(DTEL, DEBUG_DEBUG, "PSTN state '%s' -> '%s'\n", pstn_state_name(pstn->state), pstn_state_name(state)); pstn->state = state; } /* initialization and configuration of interface instance */ int pstn_init(pstn_t *pstn, const char *name, const char *socketname, const char **subscribers, int subscriber_num, uint8_t serving_location, int tx_delay, enum pstn_cid_method clip, int cid_bell, int cid_dtmf, int clip_date, int enblock, int recall, int *ringing_types_incoming, int ringing_type_hold, enum tones_type tones_type, char law, int sweden) { int i; int rc; pstn->law = law; pstn->name = strdup(name); pstn->subscribers = subscribers; pstn->subscriber_num = subscriber_num; pstn->serving_location = serving_location; pstn->tx_delay = tx_delay; pstn->clip = clip; pstn->cid_bell = cid_bell; pstn->cid_dtmf = cid_dtmf; pstn->clip_date = clip_date; pstn->enblock = enblock; pstn->recall = recall; pstn->ringing_types_incoming = ringing_types_incoming; pstn->ringing_type_hold = ringing_type_hold; pstn->tones_type = tones_type; pstn->sweden = sweden; /* init DTMF detector */ rc = dtmf_decode_init(&pstn->dtmf_dec, pstn, recv_dtmf, 8000, db2level(6.0), db2level(-30.0)); if (rc < 0) return rc; /* create callerid generator */ rc = callerid_init(&pstn->callerid, 8000, cid_bell, cid_dtmf); if (rc < 0) return rc; /* create timer */ timer_init(&pstn->timer, pstn_timeout, pstn); /* allocate jitter buffer */ if (pstn->tx_delay) rc = jitter_create(&pstn->tx_dejitter, "tx", 8000, sizeof(uint8_t), (double)pstn->tx_delay / 1000.0, (double)pstn->tx_delay / 1000.0 * 2.0, JITTER_FLAG_NONE); else rc = jitter_create(&pstn->tx_dejitter, "tx", 8000, sizeof(uint8_t), JITTER_AUDIO); if (rc < 0) return rc; PDEBUG(DTEL, DEBUG_DEBUG, "PSTN instance initialized (name=%s socketname=%s subscribers=%d, serving_location=%d, tx_delay=%d, clip=%d, cid_bell=%d, cid_dtmf=%d, clip_date=%d, enblock=%d, recall=%d, ringing_type_hold=%d\n", name, socketname, subscriber_num, serving_location, tx_delay, clip, cid_bell, cid_dtmf, clip_date, enblock, recall, ringing_type_hold); for (i = 0; i < subscriber_num; i++) PDEBUG(DTEL, DEBUG_DEBUG, " -> subscriber '%s', ringing_type_incoming %d\n", subscribers[i], ringing_types_incoming[i]); /* bring into service */ pstn_new_state(pstn, PSTN_STATE_BLOCKED); /* create ph socket here because we get a reply from the function call */ rc = ph_socket_init(&pstn->ph_socket, ph_socket_rx_msg, pstn, socketname, 0); if (rc < 0) return rc; return 0; } /* * audio handling */ /* take audio from CC (this is alaw or ulaw as specified) and store in jitter buffer */ void rtp_receive(struct osmo_cc_session_codec *codec, uint8_t __attribute__((unused)) marker, uint16_t sequence, uint32_t timestamp, uint32_t ssrc, uint8_t *data, int len) { struct call *pstn_call = codec->media->session->priv; pstn_t *pstn = pstn_call->pstn; /* not the active call, drop audio */ if (pstn_call != pstn->call[PSTN_CALL_ACTIVE]) return; jitter_save(&pstn->tx_dejitter, data, len, 1, sequence, timestamp, ssrc); } static void dtmf_on(pstn_t *pstn) { if (pstn->dtmf_on) return; PDEBUG(DTEL, DEBUG_DEBUG, "Turn DTMF detection on.\n"); /* our working level is 0dBm */ dtmf_decode_reset(&pstn->dtmf_dec); pstn->dtmf_on = 1; } static void dtmf_off(pstn_t *pstn) { if (!pstn->dtmf_on) return; PDEBUG(DTEL, DEBUG_DEBUG, "Turn DTMF detection off.\n"); pstn->dtmf_on = 0; } static void timer_on(pstn_t *pstn, double timeout, enum timer_ident ident) { PDEBUG(DTEL, DEBUG_DEBUG, "Start %s timer with %.1f seconds.\n", timer_ident_name(ident), timeout); timer_start(&pstn->timer, timeout); pstn->timer_ident = ident; } static void timer_off(pstn_t *pstn) { if (timer_running(&pstn->timer)) { PDEBUG(DTEL, DEBUG_DEBUG, "Stop %s timer.\n", timer_ident_name(pstn->timer_ident)); timer_stop(&pstn->timer); } } static void callerid_on(pstn_t *pstn, int cw, const char *callerid, uint8_t caller_type) { int dtas = 0; /* DTMF on CW not supported */ if (cw && pstn->cid_dtmf) { PDEBUG(DTEL, DEBUG_INFO, "DTMF CID is not allowed for waiting call, don't sending CID.\n"); return; } PDEBUG(DTEL, DEBUG_DEBUG, "Schedule caller ID transmission. (cw=%d, callerid=%s)\n", cw, callerid); if (cw) { dtas = 1; pstn->callerid_state = PSTN_CID_STATE_WAIT1; /* add delay when cw */ pstn->callerid_wait1 = 8000; pstn->callerid_wait2 = 0; } else { if (pstn->clip == CID_METHOD_DTAS || pstn->clip == CID_METHOD_DTAS_LR) { dtas = 1; pstn->callerid_state = PSTN_CID_STATE_WAIT1; /* wait for channel and do not wait to ring afterwards */ pstn->callerid_wait1 = 4000; pstn->callerid_wait2 = 0; } else { pstn->callerid_state = PSTN_CID_STATE_WAIT1; /* wait to stop ringing and wait to continue ringing */ pstn->callerid_wait1 = 8000; pstn->callerid_wait2 = 16000; } } /* add DT_AS on waitng call */ callerid_set(&pstn->callerid, cw, dtas, callerid, caller_type, pstn->clip_date); /* turn DTMF on, to detect TE-ACK */ if (cw) { /* start DTMF */ dtmf_on(pstn); } } static void callerid_off(pstn_t *pstn) { if (pstn->callerid_state == PSTN_CID_STATE_OFF) return; PDEBUG(DTEL, DEBUG_DEBUG, "Cancel caller ID transmission.\n"); pstn->callerid_state = PSTN_CID_STATE_OFF; /* stop DTMF */ dtmf_off(pstn); } static void tone_off(pstn_t *pstn) { if (pstn->isdn_tone.tone != TONE_OFF) { /* stop tone */ PDEBUG(DTEL, DEBUG_DEBUG, "Stop tone.\n"); isdn_tone_set(&pstn->isdn_tone, TONE_OFF); } } static void tone_on(pstn_t *pstn, int tone, const char *name) { if (pstn->isdn_tone.tone != tone) { PDEBUG(DTEL, DEBUG_DEBUG, "Set %s tone.\n", name); isdn_tone_set(&pstn->isdn_tone, tone); } } static void setup_ind(pstn_t *pstn, int hold, const char *called, int complete); void recv_dtmf(void *priv, char digit, dtmf_meas_t __attribute__((unused)) *meas) { pstn_t *pstn = (pstn_t *)priv; struct call *pstn_call = pstn->call[PSTN_CALL_ACTIVE]; osmo_cc_msg_t *new_msg; PDEBUG(DTEL, DEBUG_INFO, "Received DTMF digit '%c'.\n", digit); /* send DTMF tones to caller ID process */ if (pstn->callerid_state == PSTN_CID_STATE_SEND) { callerid_te_ack(&pstn->callerid, digit); return; } /* stop pulse */ pulse_off(pstn); /* stop tone */ tone_off(pstn); /* if we are receiving digits en block */ if (pstn->call[PSTN_CALL_ACTIVE]->state == CALL_STATE_ENBLOCK) { /* stop timer */ timer_off(pstn); if (digit == '#') { PDEBUG(DTEL, DEBUG_DEBUG, "Digit '#' received, number is complete, send setup\n"); /* setup (en block) */ setup_ind(pstn, PSTN_CALL_ACTIVE, pstn->dialing, 1); return; } PDEBUG(DTEL, DEBUG_DEBUG, "Storing digit, because we perform enblock dialing.\n"); /* append digit */ char called[2] = { digit, '\0' }; strncat(pstn->dialing, called, sizeof(pstn->dialing) - 1); PDEBUG(DTEL, DEBUG_DEBUG, "Appending digit, so dial string is now: '%s'.\n", pstn->dialing); if (check_dial_hint(pstn->dialing)) { PDEBUG(DTEL, DEBUG_DEBUG, "Number in list of dial hints received, number is complete, send setup\n"); /* setup (en block) */ setup_ind(pstn, PSTN_CALL_ACTIVE, pstn->dialing, 1); return; } /* start dial timer */ timer_on(pstn, (double)pstn->enblock, TIMER_IDENT_DIALING); return; } PDEBUG(DTEL, DEBUG_INFO, "Sending INFO-IND with DTMF digit '%c' towards Osmo-CC\n", digit); /* create osmo-cc message */ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_INFO_IND); /* called */ char called[2] = { digit, '\0' }; osmo_cc_add_ie_called(new_msg, OSMO_CC_TYPE_UNKNOWN, OSMO_CC_PLAN_TELEPHONY, called); /* send message to osmo-cc */ osmo_cc_ll_msg(&pstn->cc_ep, pstn_call->cc_callref, new_msg); } #define COMFORT_NOISE (0.02 * SPEECH_LEVEL) /* audio level of comfort noise (relative to ISDN level) */ static void send_noise(struct call *pstn_call, int len) { int16_t noise[len], r; uint8_t *law_noise; int len_noise; int i; for (i = 0; i < len; i++) { r = random(); noise[i] = (double)r * COMFORT_NOISE; } if (pstn_call->pstn->law == 'a') g711_encode_alaw_flipped((uint8_t *)noise, 160 * 2, &law_noise, &len_noise, NULL); else g711_encode_ulaw_flipped((uint8_t *)noise, 160 * 2, &law_noise, &len_noise, NULL); osmo_cc_rtp_send(pstn_call->codec, law_noise, len_noise, 0, 1, len_noise, pstn_call); free(law_noise); } static void bchannel_rx_tx(pstn_t *pstn, uint8_t *data, int len) { uint8_t *buffer = pstn->tx_buffer; int *buffer_pos = &(pstn->tx_buffer_pos); int i; /* reception */ if (pstn->dtmf_on) { sample_t samples[len]; int16_t *spl; int spl_len; if (pstn->law == 'a') g711_decode_alaw_flipped(data, len, (uint8_t **)&spl, &spl_len, NULL); else g711_decode_ulaw_flipped(data, len, (uint8_t **)&spl, &spl_len, NULL); int16_to_samples_1mw(samples, spl, len); free(spl); dtmf_decode(&pstn->dtmf_dec, samples, len); } /* transmission */ /* mute when there is no active call OR when there is caller ID transmission */ if (pstn->call[PSTN_CALL_ACTIVE]->state != CALL_STATE_ACTIVE || pstn->callerid_state == PSTN_CID_STATE_SEND) memset(data, (pstn->law == 'a') ? 0x2a : 0xff, len); /* add to buffer and send via RTP */ for (i = 0; i < len; i++) { buffer[(*buffer_pos)++] = data[i]; if (*buffer_pos == 160) { *buffer_pos = 0; /* only send, if codec is negotiated, send comfort noise for call on hold or ringing call on hold */ if (pstn->call[PSTN_CALL_ACTIVE]->codec) { if (pstn->call[PSTN_CALL_ACTIVE]->state == CALL_STATE_HOLD) send_noise(pstn->call[PSTN_CALL_ACTIVE], 160); else osmo_cc_rtp_send(pstn->call[PSTN_CALL_ACTIVE]->codec, buffer, 160, 0, 1, 160, pstn->call[PSTN_CALL_ACTIVE]); } if (pstn->call[PSTN_CALL_HOLD]->codec && pstn->call[PSTN_CALL_HOLD]->state == CALL_STATE_HOLD) send_noise(pstn->call[PSTN_CALL_HOLD], 160); } } /* load from TX jitter buffer and optionally overload with tones an with caller ID */ jitter_load(&pstn->tx_dejitter, data, len); isdn_tone_copy(&pstn->isdn_tone, data, len); switch (pstn->callerid_state) { case PSTN_CID_STATE_OFF: break; case PSTN_CID_STATE_WAIT1: /* wait before sending callerid */ pstn->callerid_wait1 -= len; if (pstn->callerid_wait1 <= 0) { if (pstn->clip == CID_METHOD_STOP && pstn->call[PSTN_CALL_ACTIVE]->state == CALL_STATE_ALERTING_SUB) { PDEBUG(DTEL, DEBUG_DEBUG, "Stop ringing.\n"); uint8_t ie[3] = { PSTN_V5_IE_STEADY_SIGNAL, 1, 0x80 | 0x0e}; v5_sig_req(pstn, ie, sizeof(ie)); } PDEBUG(DTEL, DEBUG_DEBUG, "Start sending caller ID.\n"); pstn->callerid_state = PSTN_CID_STATE_SEND; } break; case PSTN_CID_STATE_SEND: { sample_t samples[len]; int16_t spl[len]; uint8_t *data_cid; int len_cid; int rc; rc = callerid_send(&pstn->callerid, samples, len); if (rc) { samples_to_int16_1mw(spl, samples, rc); if (pstn->law == 'a') g711_encode_alaw_flipped((uint8_t *)spl, rc * 2, &data_cid, &len_cid, NULL); else g711_encode_ulaw_flipped((uint8_t *)spl, rc * 2, &data_cid, &len_cid, NULL); memcpy(data, data_cid, len_cid); free(data_cid); } if (rc < len) { /* caller ID transmission has finished */ PDEBUG(DTEL, DEBUG_DEBUG, "Done sending caller ID.\n"); /* stop DTMF */ dtmf_off(pstn); pstn->callerid_state = PSTN_CID_STATE_WAIT2; } break; } case PSTN_CID_STATE_WAIT2: /* wait before sending callerid */ pstn->callerid_wait2 -= len; if (pstn->callerid_wait2 <= 0) { pstn->callerid_state = PSTN_CID_STATE_OFF; if (pstn->call[PSTN_CALL_ACTIVE]->state == CALL_STATE_ALERTING_SUB) { if (pstn->clip == CID_METHOD_DTAS_LR) { pstn->reversed = 0; PDEBUG(DTEL, DEBUG_DEBUG, "Set normal polarity.\n"); uint8_t ie[3] = { PSTN_V5_IE_STEADY_SIGNAL, 1, 0x80 | PSTN_V5_STEADY_SIGNAL_NORMAL}; v5_sig_req(pstn, ie, sizeof(ie)); } PDEBUG(DTEL, DEBUG_DEBUG, "Start ringing.\n"); uint8_t ie[3] = { PSTN_V5_IE_CADENCED_RINGING, 1, 0x80 | pstn->ringing_types_incoming[pstn->call[PSTN_CALL_ACTIVE]->subscriber_index] }; v5_sig_req(pstn, ie, sizeof(ie)); } } break; } if (!pstn->b_transmitting) { uint8_t init_data[len]; PDEBUG(DTEL, DEBUG_DEBUG, "First received b-channel data, sending two frames, to preload FIFO with %d extra bytes.\n", len); memset(init_data, 0xff, len); /* forward to interface */ ph_socket_tx_msg(&pstn->ph_socket, 1, PH_PRIM_DATA_REQ, init_data, len); pstn->b_transmitting = 1; } /* forward to interface */ ph_socket_tx_msg(&pstn->ph_socket, 1, PH_PRIM_DATA_REQ, data, len); } /* * handle message from CC */ static void reject_ind(pstn_t *pstn, uint32_t callref, uint8_t isdn_cause) { osmo_cc_msg_t *new_msg; PDEBUG(DTEL, DEBUG_INFO, "Sending REJ-IND with ISDN cause %d towards Osmo-CC.\n", isdn_cause); /* create osmo-cc message */ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REJ_IND); /* cause */ osmo_cc_add_ie_cause(new_msg, pstn->serving_location, isdn_cause, 0, 0); /* send message to osmo-cc */ osmo_cc_ll_msg(&pstn->cc_ep, callref, new_msg); } static void notify_ind(struct call *pstn_call, uint8_t notify) { osmo_cc_msg_t *new_msg; if (pstn_call->on_hold && notify == OSMO_CC_NOTIFY_REMOTE_HOLD) return; if (!pstn_call->on_hold && notify == OSMO_CC_NOTIFY_REMOTE_RETRIEVAL) return; /* create osmo-cc message */ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_NOTIFY_IND); /* notify the facility */ osmo_cc_add_ie_notify(new_msg, notify); /* send message to osmo-cc */ osmo_cc_ll_msg(&pstn_call->pstn->cc_ep, pstn_call->cc_callref, new_msg); if (notify == OSMO_CC_NOTIFY_REMOTE_HOLD) pstn_call->on_hold = 1; if (notify == OSMO_CC_NOTIFY_REMOTE_RETRIEVAL) pstn_call->on_hold = 0; } static void set_tone_cause(pstn_t *pstn, uint8_t isdn_cause) { switch (isdn_cause) { case OSMO_CC_ISDN_CAUSE_USER_BUSY: case OSMO_CC_ISDN_CAUSE_USER_NOTRESPOND: case OSMO_CC_ISDN_CAUSE_USER_ALERTING_NA: tone_on(pstn, TONE_BUSY, "busy"); break; case OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR: tone_on(pstn, TONE_HANGUP, "hangup"); break; case OSMO_CC_ISDN_CAUSE_NO_CIRCUIT_CHAN: tone_on(pstn, TONE_GASSENBESETZT, "congestion"); break; default: tone_on(pstn, TONE_SPECIAL_INFO, "SIT"); } } static void v5_est_req(pstn_t *pstn, uint8_t *ie, int length); void cc_message(osmo_cc_endpoint_t *ep, uint32_t callref, osmo_cc_msg_t *msg) { pstn_t *pstn = ep->priv; struct osmo_cc_helper_audio_codecs *codecs; osmo_cc_msg_t *new_msg; uint8_t caller_type, caller_plan, caller_present, caller_screen; uint8_t dialing_type, dialing_plan; uint8_t capability, mode; char callerid[128], dialing[128]; uint8_t coding, location, progress, socket_cause; uint8_t isdn_cause; uint16_t sip_cause; struct call *pstn_call = NULL; const char *sdp; int hold = 0; int i; int rc; /* hunt for callref */ if (pstn->call[PSTN_CALL_ACTIVE]->cc_callref == callref) { pstn_call = pstn->call[PSTN_CALL_ACTIVE]; hold = 0; } if (pstn->call[PSTN_CALL_HOLD]->cc_callref == callref) { pstn_call = pstn->call[PSTN_CALL_HOLD]; hold = 1; } /* process SETUP */ if (!pstn_call) { if (msg->type != OSMO_CC_MSG_SETUP_REQ) { PDEBUG(DTEL, DEBUG_ERROR, "received message without call instance, please fix!\n"); goto done; } PDEBUG(DTEL, DEBUG_INFO, "Received new SETUP-REQ from Osmo-CC\n"); /* called */ rc = osmo_cc_get_ie_called(msg, 0, &dialing_type, &dialing_plan, dialing, sizeof(dialing)); if (rc < 0) dialing[0] = '\0'; /* bearer capability */ rc = osmo_cc_get_ie_bearer(msg, 0, &coding, &capability, &mode); if (rc < 0) { PDEBUG(DTEL, DEBUG_DEBUG, "No bearer capability given, assuming audio.\n"); capability = OSMO_CC_CAPABILITY_AUDIO; } /* search for interface */ if (!pstn) { // this will never happen, unless we implement multiple PSTN interface support PDEBUG(DTEL, DEBUG_INFO, "No interface for subscriber '%s', rejecting call.\n", dialing); reject_ind(pstn, callref, OSMO_CC_ISDN_CAUSE_UNASSIGNED_NR); goto done; } /* if not a voice/speech call */ if (capability != OSMO_CC_CAPABILITY_AUDIO && capability != OSMO_CC_CAPABILITY_SPEECH) { PDEBUG(DTEL, DEBUG_INFO, "Bearer is not voice/speech, rejecting call.\n"); reject_ind(pstn, callref, OSMO_CC_ISDN_CAUSE_INCOMPAT_DEST); goto done; } /* reject if port is out of service */ if (pstn->state == PSTN_STATE_OOS) { PDEBUG(DTEL, DEBUG_INFO, "Interface is out of service, rejecting call.\n"); reject_ind(pstn, callref, OSMO_CC_ISDN_CAUSE_DEST_OOO); goto done; } /* reject if port is blocked */ if (pstn->state == PSTN_STATE_BLOCKED) { PDEBUG(DTEL, DEBUG_INFO, "Interface is blocked, rejecting call.\n"); reject_ind(pstn, callref, OSMO_CC_ISDN_CAUSE_DEST_OOO); goto done; } /* reject, if busy */ if (pstn->call[PSTN_CALL_ACTIVE]->cc_callref && (pstn->call[PSTN_CALL_HOLD]->cc_callref || !pstn->recall)) { PDEBUG(DTEL, DEBUG_INFO, "We are busy, rejecting call.\n"); reject_ind(pstn, callref, OSMO_CC_ISDN_CAUSE_USER_BUSY); goto done; } /* reject if calle not in NULL or ACTIVE state */ if (pstn->call[PSTN_CALL_ACTIVE]->state != CALL_STATE_NULL && pstn->call[PSTN_CALL_ACTIVE]->state != CALL_STATE_ACTIVE) { PDEBUG(DTEL, DEBUG_INFO, "Call is not in NULL or active state, rejecting call.\n"); reject_ind(pstn, callref, OSMO_CC_ISDN_CAUSE_USER_BUSY); goto done; } /* select call and link with cc */ if (!pstn->call[PSTN_CALL_ACTIVE]->cc_callref) { pstn_call = pstn->call[PSTN_CALL_ACTIVE]; hold = 0; } else { pstn_call = pstn->call[PSTN_CALL_HOLD]; hold = 1; } pstn_call->cc_callref = callref; pstn_call->on_hold = 0; } switch (msg->type) { case OSMO_CC_MSG_SETUP_REQ: /* dial-out command received from epoint */ /* calling */ rc = osmo_cc_get_ie_calling(msg, 0, &caller_type, &caller_plan, &caller_present, &caller_screen, callerid, sizeof(callerid)); if (rc < 0) callerid[0] = '\0'; /* called */ rc = osmo_cc_get_ie_called(msg, 0, &dialing_type, &dialing_plan, dialing, sizeof(dialing)); if (rc < 0) dialing[0] = '\0'; PDEBUG(DTEL, DEBUG_INFO, "Received SETUP-REQ call (from '%s' to '%s') from Osmo-CC.\n", callerid, dialing); /* now set subscriber index by looking at the dialed subscriber number. if not found, use index 0 */ /* note: digits after subscriber number are ignored, so it matches anyway */ for (i = 0; i < pstn->subscriber_num; i++) { if (!strncasecmp(dialing, pstn->subscribers[i], strlen(pstn->subscribers[i]))) break; } if (i == pstn->subscriber_num) i = 0; pstn_call->subscriber_index = i; PDEBUG(DTEL, DEBUG_INFO, "After screening, call (from '%s' to '%s') is performed.\n", callerid, pstn->subscribers[pstn_call->subscriber_index]); /* select codec */ if (pstn->law == 'a') codecs = codecs_alaw_ulaw; else codecs = codecs_ulaw_alaw; /* sdp accept */ sdp = osmo_cc_helper_audio_accept(&ep->session_config, pstn_call, codecs, rtp_receive, msg, &pstn_call->cc_session, &pstn_call->codec, 0); if (!sdp) { reject_ind(pstn, callref, OSMO_CC_ISDN_CAUSE_RESOURCE_UNAVAIL); break; } pstn_call->sdp = strdup(sdp); PDEBUG(DTEL, DEBUG_INFO, "Sending ALERT-IND towards Osmo-CC\n"); /* create osmo-cc message */ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_ALERT_IND); /* if call is waiting, send notify to caller */ if (hold) { /* CW tone */ tone_on(pstn, TONE_CW, "CW"); if (pstn->clip) { /* send callerid */ callerid_on(pstn, 1, callerid, caller_type); } /* add notify to waiting call */ PDEBUG(DTEL, DEBUG_INFO, "Adding call waiting notification towards Osmo-CC\n"); osmo_cc_add_ie_notify(new_msg, OSMO_CC_NOTIFY_CALL_IS_A_WAITING_CALL); } /* send message to osmo-cc */ osmo_cc_ll_msg(&pstn->cc_ep, pstn_call->cc_callref, new_msg); if (pstn->state == PSTN_STATE_NULL) { /* send message to V5 */ switch (pstn->clip) { case CID_METHOD_DTAS: { v5_est_req(pstn, NULL, 0); break; } case CID_METHOD_DTAS_LR: { pstn->reversed = 1; PDEBUG(DTEL, DEBUG_DEBUG, "Reversed polarity.\n"); uint8_t ie[3] = { PSTN_V5_IE_STEADY_SIGNAL, 1, 0x80 | PSTN_V5_STEADY_SIGNAL_REVERSED}; v5_est_req(pstn, ie, sizeof(ie)); break; } case CID_METHOD_PULSE: { PDEBUG(DTEL, DEBUG_DEBUG, "Start one ring pulse.\n"); uint8_t ie[5] = { PSTN_V5_IE_PULSED_SIGNAL, 3, 0x80 | PSTN_V5_PULSED_SIGNAL_INIT_RING, 0x60, 0x80 | 0x40 | 0x01}; v5_est_req(pstn, ie, sizeof(ie)); break; } case CID_METHOD_STOP: case CID_METHOD_NONE: { PDEBUG(DTEL, DEBUG_DEBUG, "Start ringing.\n"); uint8_t ie[3] = { PSTN_V5_IE_CADENCED_RINGING, 1, 0x80 | pstn->ringing_types_incoming[pstn_call->subscriber_index] }; v5_est_req(pstn, ie, sizeof(ie)); break; } } if (pstn->clip) { /* send callerid */ callerid_on(pstn, 0, callerid, caller_type); } /* change state */ pstn_new_state(pstn, PSTN_STATE_EST_LE); } /* change call state */ pstn_call_state(pstn_call, CALL_STATE_ALERTING_SUB); break; case OSMO_CC_MSG_SETUP_ACK_REQ: /* more information is needed */ PDEBUG(DTEL, DEBUG_INFO, "Received SETUP-ACK-REQ (overlap dialing) from Osmo-CC.\n"); /* stop tone */ tone_off(pstn); /* negotiate audio */ rc = osmo_cc_helper_audio_negotiate(msg, &pstn_call->cc_session, &pstn_call->codec); if (rc < 0) { release_call(pstn, hold, OSMO_CC_ISDN_CAUSE_RESOURCE_UNAVAIL); /* SIT tone */ tone_on(pstn, TONE_SPECIAL_INFO, "SIT"); break; } /* set audio path */ rc = osmo_cc_get_ie_progress(msg, 0, &coding, &location, &progress); if (!rc && coding == OSMO_CC_CODING_ITU_T && (progress == 1 || progress == 8)) pstn->audio_path = 1; /* if we do enblock dialing, number is already complete, so no dial tone required */ if (!pstn->enblock) { if (!pstn->audio_path) { /* dial tone */ tone_on(pstn, TONE_DIALTONE, "dial"); } } /* change call state */ pstn_call_state(pstn_call, CALL_STATE_OVERLAP_NET); break; case OSMO_CC_MSG_PROC_REQ: /* call of endpoint is proceeding */ PDEBUG(DTEL, DEBUG_INFO, "Received PROC-REQ (proceeding) from Osmo-CC.\n"); /* stop tone */ tone_off(pstn); /* stop timer */ timer_off(pstn); /* stop DTMF */ dtmf_off(pstn); /* stop pulse */ pulse_off(pstn); /* negotiate audio */ rc = osmo_cc_helper_audio_negotiate(msg, &pstn_call->cc_session, &pstn_call->codec); if (rc < 0) { release_call(pstn, hold, OSMO_CC_ISDN_CAUSE_RESOURCE_UNAVAIL); /* SIT tone */ tone_on(pstn, TONE_SPECIAL_INFO, "SIT"); break; } /* set audio path */ rc = osmo_cc_get_ie_progress(msg, 0, &coding, &location, &progress); if (!rc && coding == OSMO_CC_CODING_ITU_T && (progress == 1 || progress == 8)) pstn->audio_path = 1; /* change call state */ pstn_call_state(pstn_call, CALL_STATE_PROCEEDING_NET); break; case OSMO_CC_MSG_ALERT_REQ: /* call of endpoint is ringing */ PDEBUG(DTEL, DEBUG_INFO, "Received ALERT-REQ (alerting) from Osmo-CC.\n"); /* stop tone */ tone_off(pstn); /* stop timer */ timer_off(pstn); /* stop DTMF */ dtmf_off(pstn); /* stop pulse */ pulse_off(pstn); /* negotiate audio */ rc = osmo_cc_helper_audio_negotiate(msg, &pstn_call->cc_session, &pstn_call->codec); if (rc < 0) { release_call(pstn, hold, OSMO_CC_ISDN_CAUSE_RESOURCE_UNAVAIL); /* SIT tone */ tone_on(pstn, TONE_SPECIAL_INFO, "SIT"); break; } /* set audio path */ rc = osmo_cc_get_ie_progress(msg, 0, &coding, &location, &progress); if (!rc && coding == OSMO_CC_CODING_ITU_T && (progress == 1 || progress == 8)) pstn->audio_path = 1; if (!pstn->audio_path) { /* ringback tone */ tone_on(pstn, TONE_RINGING, "ringback"); } /* change call state */ pstn_call_state(pstn_call, CALL_STATE_ALERTING_NET); break; case OSMO_CC_MSG_SETUP_RSP: /* call of endpoint is connected */ PDEBUG(DTEL, DEBUG_INFO, "Received SETUP-RSP (answer) from Osmo-CC.\n"); /* set audio path */ pstn->audio_path = 1; /* stop tone */ tone_off(pstn); /* stop timer */ timer_off(pstn); /* stop DTMF */ dtmf_off(pstn); /* stop pulse */ pulse_off(pstn); /* negotiate audio */ rc = osmo_cc_helper_audio_negotiate(msg, &pstn_call->cc_session, &pstn_call->codec); if (rc < 0) { release_call(pstn, hold, OSMO_CC_ISDN_CAUSE_RESOURCE_UNAVAIL); /* SIT tone */ tone_on(pstn, TONE_SPECIAL_INFO, "SIT"); break; } PDEBUG(DTEL, DEBUG_INFO, "Sending SETUP-COMP-IND towards Osmo-CC\n"); /* create osmo-cc message */ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_COMP_IND); /* send message to osmo-cc */ osmo_cc_ll_msg(&pstn->cc_ep, pstn_call->cc_callref, new_msg); /* change call state */ pstn_call_state(pstn_call, CALL_STATE_ACTIVE); break; case OSMO_CC_MSG_SETUP_COMP_REQ: /* call of endpoint is connected */ break; case OSMO_CC_MSG_INFO_REQ: /* overlap dialing */ break; case OSMO_CC_MSG_PROGRESS_REQ: /* progress */ PDEBUG(DTEL, DEBUG_INFO, "Received PROGRESS-REQ from Osmo-CC.\n"); /* stop timer */ timer_off(pstn); /* negotiate audio */ rc = osmo_cc_helper_audio_negotiate(msg, &pstn_call->cc_session, &pstn_call->codec); if (rc < 0) { release_call(pstn, hold, OSMO_CC_ISDN_CAUSE_RESOURCE_UNAVAIL); /* SIT tone */ tone_on(pstn, TONE_SPECIAL_INFO, "SIT"); /* stop DTMF */ dtmf_off(pstn); /* stop pulse */ pulse_off(pstn); break; } /* set audio path */ rc = osmo_cc_get_ie_progress(msg, 0, &coding, &location, &progress); if (!rc && coding == OSMO_CC_CODING_ITU_T && (progress == 1 || progress == 8)) pstn->audio_path = 1; break; case OSMO_CC_MSG_NOTIFY_REQ: /* display and notifications */ break; case OSMO_CC_MSG_REJ_REQ: /* call has been rejected */ case OSMO_CC_MSG_REL_REQ: /* release call */ PDEBUG(DTEL, DEBUG_INFO, "Received REJ-REQ/REL-REQ from Osmo-CC.\n"); /* get cause */ rc = osmo_cc_get_ie_cause(msg, 0, &location, &isdn_cause, &sip_cause, &socket_cause); if (rc < 0) isdn_cause = OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR; else PDEBUG(DTEL, DEBUG_INFO, " -> ISDN cause = %d, SIP cause = %d, socket cause = %d\n", isdn_cause, sip_cause, socket_cause); /* if call is on hold, stop call waiting signal and release */ if (hold) { PDEBUG(DTEL, DEBUG_INFO, "Releasing call on hold.\n"); if (pstn->isdn_tone.tone == TONE_GERMAN_CW || pstn->isdn_tone.tone == TONE_AMERICAN_CW) { /* stop tone */ tone_off(pstn); } release_call(pstn, hold, isdn_cause); break; } /* keep hookflash timer running */ if (pstn->timer_ident != TIMER_IDENT_HOOKFLASH) { /* stop timer */ timer_off(pstn); } /* release ringing call (or call that rings because it is on hold) */ if (pstn_call->state == CALL_STATE_ALERTING_SUB || pstn_call->state == CALL_STATE_HOLD) { release_call(pstn, hold, isdn_cause); /* there is no more call, release V5 */ v5_disc_req_and_cleanup(pstn); break; } release_call(pstn, hold, isdn_cause); /* play cause tone */ set_tone_cause(pstn, isdn_cause); break; case OSMO_CC_MSG_DISC_REQ: /* call has been disconnected */ PDEBUG(DTEL, DEBUG_INFO, "Received DISC-REQ from Osmo-CC.\n"); /* get cause */ rc = osmo_cc_get_ie_cause(msg, 0, &location, &isdn_cause, &sip_cause, &socket_cause); if (rc < 0) isdn_cause = OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR; else PDEBUG(DTEL, DEBUG_INFO, " -> ISDN cause = %d, SIP cause = %d, socket cause = %d\n", isdn_cause, sip_cause, socket_cause); /* if call is on hold, stop call waiting signal and release */ if (hold) { PDEBUG(DTEL, DEBUG_INFO, "Releasing call on hold.\n"); if (pstn->isdn_tone.tone == TONE_GERMAN_CW || pstn->isdn_tone.tone == TONE_AMERICAN_CW) { /* stop tone */ tone_off(pstn); } release_call(pstn, hold, isdn_cause); break; } /* keep hookflash timer running */ if (pstn->timer_ident != TIMER_IDENT_HOOKFLASH) { /* stop timer */ timer_off(pstn); } /* negotiate audio */ rc = osmo_cc_helper_audio_negotiate(msg, &pstn_call->cc_session, &pstn_call->codec); if (rc < 0) { release_call(pstn, hold, OSMO_CC_ISDN_CAUSE_RESOURCE_UNAVAIL); /* SIT tone */ tone_on(pstn, TONE_SPECIAL_INFO, "SIT"); break; } /* release ringing call (or call that rings because it is on hold) */ if (pstn_call->state == CALL_STATE_ALERTING_SUB || pstn_call->state == CALL_STATE_HOLD) { release_call(pstn, hold, isdn_cause); /* there is no more call, release V5 */ v5_disc_req_and_cleanup(pstn); break; } /* set audio path */ rc = osmo_cc_get_ie_progress(msg, 0, &coding, &location, &progress); if (!rc && coding == OSMO_CC_CODING_ITU_T && (progress == 1 || progress == 8)) pstn->audio_path = 1; else pstn->audio_path = 0; /* release if no progress, also release, if call is on hold */ if (!pstn->audio_path) { PDEBUG(DTEL, DEBUG_INFO, "no audio after disconnect, releasing!\n"); release_call(pstn, hold, isdn_cause); /* play cause tone */ set_tone_cause(pstn, isdn_cause); break; } /* stop tone */ tone_off(pstn); /* stop DTMF */ dtmf_off(pstn); /* stop pulse */ pulse_off(pstn); /* start release timer */ timer_on(pstn, RELEASE_TO, TIMER_IDENT_RELEASE); /* change call state */ pstn_call_state(pstn_call, CALL_STATE_DISCONNECT_NET); break; default: PDEBUG(DTEL, DEBUG_ERROR, "Received an unsupported Osmo-CC message: %d\n", msg->type); } done: osmo_cc_free_msg(msg); } /* * handle message from V5-interface */ static void v5_est_ack_ind(pstn_t *pstn, uint8_t *data, int len) { /* change state */ pstn_new_state(pstn, PSTN_STATE_ACTIVE); /* activate b-channel */ ph_socket_tx_msg(&pstn->ph_socket, 1, PH_PRIM_ACT_REQ, NULL, 0); if (len) { v5_sig_ind(pstn, data, len); } } static void v5_est_ind(pstn_t *pstn, uint8_t *data, int len) { /* change state */ pstn_new_state(pstn, PSTN_STATE_ACTIVE); /* activate b-channel */ ph_socket_tx_msg(&pstn->ph_socket, 1, PH_PRIM_ACT_REQ, NULL, 0); /* send message to V5 */ v5_est_ack_req(pstn); if (len) { v5_sig_ind(pstn, data, len); } } static void setup_ind(pstn_t *pstn, int hold, const char *called, int complete) { struct call *pstn_call = pstn->call[hold]; osmo_cc_msg_t *new_msg; struct osmo_cc_helper_audio_codecs *codecs; /* always use index 0 for subscriber */ pstn_call->subscriber_index = 0; PDEBUG(DTEL, DEBUG_INFO, "Sending SETUP-IND (from '%s' to '%s) towards Osmo-CC\n", pstn->subscribers[0], called); /* setup message */ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_IND); /* network type */ osmo_cc_add_ie_calling_network(new_msg, OSMO_CC_NETWORK_POTS_NONE, ""); if (pstn->subscribers[pstn_call->subscriber_index][0]) { /* calling number, if subscriber is not a null string */ osmo_cc_add_ie_calling(new_msg, OSMO_CC_TYPE_UNKNOWN, OSMO_CC_PLAN_TELEPHONY, OSMO_CC_PRESENT_ALLOWED, OSMO_CC_SCREEN_NETWORK, pstn->subscribers[pstn_call->subscriber_index]); } if (called && called[0]) { /* called number */ osmo_cc_add_ie_called(new_msg, OSMO_CC_TYPE_UNKNOWN, OSMO_CC_PLAN_TELEPHONY, called); } /* select codec */ if (pstn->law == 'a') codecs = codecs_alaw_ulaw; else codecs = codecs_ulaw_alaw; /* dialing complete */ if (complete) osmo_cc_add_ie_complete(new_msg); /* sdp offer */ pstn_call->cc_session = osmo_cc_helper_audio_offer(&pstn->cc_ep.session_config, pstn_call, codecs, rtp_receive, new_msg, 1); if (!pstn_call->cc_session) { PDEBUG(DTEL, DEBUG_NOTICE, "Failed to offer audio, call aborted.\n"); osmo_cc_free_msg(new_msg); /* SIT tone */ tone_on(pstn, TONE_SPECIAL_INFO, "SIT"); /* stop DTMF */ dtmf_off(pstn); /* stop pulse */ pulse_off(pstn); return; } osmo_cc_call_t *cc_call = osmo_cc_call_new(&pstn->cc_ep); pstn_call->cc_callref = cc_call->callref; pstn_call->on_hold = 0; /* send message to CC */ osmo_cc_ll_msg(&pstn->cc_ep, pstn_call->cc_callref, new_msg); /* set initial audio path to off */ pstn->audio_path = 0; } static void setup_cnf(pstn_t *pstn, int hold) { struct call *pstn_call = pstn->call[hold]; osmo_cc_msg_t *new_msg; PDEBUG(DTEL, DEBUG_INFO, "Sending SETUP-CNF towards Osmo-CC\n"); /* create osmo-cc message */ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_CNF); /* sdp */ if (pstn_call->sdp) { osmo_cc_add_ie_sdp(new_msg, pstn_call->sdp); free((char *)pstn_call->sdp); pstn_call->sdp = NULL; } if (pstn->subscribers[pstn_call->subscriber_index][0]) { /* connected ID: use called subscriber, but only if not a null string */ osmo_cc_add_ie_calling(new_msg, OSMO_CC_TYPE_UNKNOWN, OSMO_CC_PLAN_TELEPHONY, OSMO_CC_PRESENT_ALLOWED, OSMO_CC_SCREEN_NETWORK, pstn->subscribers[pstn_call->subscriber_index]); } /* send message to osmo-cc */ osmo_cc_ll_msg(&pstn->cc_ep, pstn_call->cc_callref, new_msg); } static void swap_active_hold(pstn_t *pstn) { struct call *temp; /* 3-ways swap */ temp = pstn->call[0]; pstn->call[0] = pstn->call[1]; pstn->call[1] = temp; /* reset jitter buffer */ jitter_reset(&pstn->tx_dejitter); } /* process hookflash from various events: short hangup, off-hook-pulse, pulse digit 1 ... */ static void hookflash(pstn_t *pstn) { /* hookflash: no connected call, no call on hold */ if (pstn->call[PSTN_CALL_ACTIVE]->state != CALL_STATE_ACTIVE && !pstn->call[PSTN_CALL_HOLD]->cc_callref) { PDEBUG(DTEL, DEBUG_INFO, "There is an active call that is not connected, but no call on hold, ignoring hookflash.\n"); return; } /* stop timer */ timer_off(pstn); /* stop tone (CW tone) */ tone_off(pstn); /* clear dialing */ pstn->dialing[0] = '\0'; /* release incomplete call, if any */ if (pstn->call[PSTN_CALL_ACTIVE]->state != CALL_STATE_ACTIVE) { PDEBUG(DTEL, DEBUG_INFO, "There is an active call that is not connected, release it.\n"); /* release active call that is not connectd */ release_call(pstn, PSTN_CALL_ACTIVE, OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR); } /* if call on hold, swap calls */ if (pstn->call[PSTN_CALL_HOLD]->cc_callref) { PDEBUG(DTEL, DEBUG_INFO, "There is a call on hold, so swap both calls.\n"); /* swap ACTIVE call and call on HOLD */ swap_active_hold(pstn); if (pstn->call[PSTN_CALL_HOLD]->cc_callref) { /* send notify to call on hold */ notify_ind(pstn->call[PSTN_CALL_HOLD], OSMO_CC_NOTIFY_REMOTE_HOLD); } /* if waiting call, answer it */ if (pstn->call[PSTN_CALL_ACTIVE]->state != CALL_STATE_ACTIVE && pstn->call[PSTN_CALL_ACTIVE]->state != CALL_STATE_HOLD) { /* setup confirm */ setup_cnf(pstn, PSTN_CALL_ACTIVE); } /* change state */ pstn_call_state(pstn->call[PSTN_CALL_ACTIVE], CALL_STATE_ACTIVE); if (pstn->call[PSTN_CALL_HOLD]->cc_callref) pstn_call_state(pstn->call[PSTN_CALL_HOLD], CALL_STATE_HOLD); /* send notify to active call */ notify_ind(pstn->call[PSTN_CALL_ACTIVE], OSMO_CC_NOTIFY_REMOTE_RETRIEVAL); return; } /* if active call, put it on hold and setup new call, if not en-block dialing */ if (pstn->call[PSTN_CALL_ACTIVE]->cc_callref) { PDEBUG(DTEL, DEBUG_INFO, "There is an active call only, so put it on hold and start a new one.\n"); /* swap ACTIVE call and call on HOLD */ swap_active_hold(pstn); /* change state */ pstn_call_state(pstn->call[PSTN_CALL_HOLD], CALL_STATE_HOLD); /* send notify to call on hold */ notify_ind(pstn->call[PSTN_CALL_HOLD], OSMO_CC_NOTIFY_REMOTE_HOLD); /* start DTMF */ dtmf_on(pstn); /* start pulse */ pulse_on(pstn); if (!pstn->enblock) { /* setup */ setup_ind(pstn, PSTN_CALL_ACTIVE, "", 0); } else { /* start dial timer */ timer_on(pstn, DIALTONE_TO, TIMER_IDENT_DIALING); /* dial tone */ tone_on(pstn, TONE_DIALTONE, "dial"); /* change state */ pstn_call_state(pstn->call[PSTN_CALL_ACTIVE], CALL_STATE_ENBLOCK); } return; } } static void v5_sig_ind(pstn_t *pstn, uint8_t *data, int len) { osmo_cc_msg_t *new_msg; int pulses; if (len < 3 && data[1] < 1) { PDEBUG(DTEL, DEBUG_ERROR, "Received short V5 signal message, ignoring!\n"); return; } switch (data[0]) { case PSTN_V5_IE_STEADY_SIGNAL: switch ((data[2] & 0x7f)) { case PSTN_V5_STEADY_SIGNAL_OFF_HOOK: PDEBUG(DTEL, DEBUG_INFO, "Received steady off-kook signal.\n"); if (timer_running(&pstn->timer) && pstn->timer_ident == TIMER_IDENT_HOOKFLASH) { PDEBUG(DTEL, DEBUG_INFO, "Performing hookflash, because on-hook was too short for hangup.\n"); /* stop timer */ timer_off(pstn); hookflash(pstn); } /* clear dialing */ pstn->dialing[0] = '\0'; /* if we have ringing call, we answer it or notify that it is now active */ if (pstn->call[PSTN_CALL_ACTIVE]->state == CALL_STATE_ALERTING_SUB) { PDEBUG(DTEL, DEBUG_INFO, "Alerting subscriber answered.\n"); #ifndef TEST_CALLERID /* stop caller id process */ callerid_off(pstn); #endif /* setup confirm */ setup_cnf(pstn, PSTN_CALL_ACTIVE); /* change state */ pstn_call_state(pstn->call[PSTN_CALL_ACTIVE], CALL_STATE_ACTIVE); /* send notify to active call */ notify_ind(pstn->call[PSTN_CALL_ACTIVE], OSMO_CC_NOTIFY_REMOTE_RETRIEVAL); break; } if (pstn->call[PSTN_CALL_ACTIVE]->state == CALL_STATE_HOLD) { PDEBUG(DTEL, DEBUG_INFO, "Subscriber on hold is retrieved.\n"); #ifndef TEST_CALLERID /* stop caller id process */ callerid_off(pstn); #endif /* change state */ pstn_call_state(pstn->call[PSTN_CALL_ACTIVE], CALL_STATE_ACTIVE); /* send notify to active call */ notify_ind(pstn->call[PSTN_CALL_ACTIVE], OSMO_CC_NOTIFY_REMOTE_RETRIEVAL); break; } /* if there is no call, send setup, if not en block dialing */ if (!pstn->call[PSTN_CALL_ACTIVE]->cc_callref) { /* start DTMF */ dtmf_on(pstn); /* start pulse */ pulse_on(pstn); if (!pstn->enblock) { /* setup */ setup_ind(pstn, PSTN_CALL_ACTIVE, "", 0); } else { /* start dial timer */ timer_on(pstn, DIALTONE_TO, TIMER_IDENT_DIALING); /* dial tone */ tone_on(pstn, TONE_DIALTONE, "dial"); /* change state */ pstn_call_state(pstn->call[PSTN_CALL_ACTIVE], CALL_STATE_ENBLOCK); } } break; case PSTN_V5_STEADY_SIGNAL_ON_HOOK: PDEBUG(DTEL, DEBUG_INFO, "Received steady on-kook signal.\n"); /* start hookflash timer */ if (pstn->recall && pstn->call[PSTN_CALL_ACTIVE]->state == CALL_STATE_ACTIVE) timer_on(pstn, HOOKFLASH_TO, TIMER_IDENT_HOOKFLASH); else { PDEBUG(DTEL, DEBUG_DEBUG, "No call, so we set hookflash timer to 0.\n"); timer_on(pstn, 0.0, TIMER_IDENT_HOOKFLASH); } break; } break; case PSTN_V5_IE_PULSED_SIGNAL: switch ((data[2] & 0x7f)) { case PSTN_V5_PULSED_SIGNAL_REG_RECAL: PDEBUG(DTEL, DEBUG_INFO, "Received register recall signal.\n"); if (pstn->recall && (pstn->call[PSTN_CALL_ACTIVE]->state == CALL_STATE_ACTIVE || pstn->call[PSTN_CALL_ACTIVE]->state == CALL_STATE_ENBLOCK || pstn->call[PSTN_CALL_ACTIVE]->state == CALL_STATE_OVERLAP_NET)) { PDEBUG(DTEL, DEBUG_INFO, "Performing hookflash, register recall signal was received.\n"); hookflash(pstn); } break; case PSTN_V5_PULSED_SIGNAL_ON_HOOK: PDEBUG(DTEL, DEBUG_INFO, "Received pulsed on-kook signal.\n"); if (pstn->recall && (pstn->call[PSTN_CALL_ACTIVE]->state == CALL_STATE_ACTIVE || pstn->call[PSTN_CALL_ACTIVE]->state == CALL_STATE_ENBLOCK || pstn->call[PSTN_CALL_ACTIVE]->state == CALL_STATE_OVERLAP_NET)) { PDEBUG(DTEL, DEBUG_INFO, "Performing hookflash, on-hook pulse was received.\n"); hookflash(pstn); } break; } break; case PSTN_V5_IE_DIGIT_SIGNAL: pulses = data[2] & 0x0f; PDEBUG(DTEL, DEBUG_INFO, "Received digit signal: %d pulses\n", pulses); if (pulses == 1 && pstn->recall && pstn->call[PSTN_CALL_ACTIVE]->state == CALL_STATE_ACTIVE) { PDEBUG(DTEL, DEBUG_INFO, "Performing hookflash, because digit '1' was dialled. (short hookflash)\n"); hookflash(pstn); break; } if (!pstn->pulse_on) break; /* stop timer */ timer_off(pstn); /* stop DTMF */ dtmf_off(pstn); /* stop tone */ tone_off(pstn); /* convert pulses -> digit */ char called[2] = { '\0', '\0' }; switch (pulses) { case 0: PDEBUG(DTEL, DEBUG_ERROR, "Received 0 pulses, ignoring!\n"); break; case 11: called[0] = '*'; break; case 12: called[0] = '#'; break; case 13: called[0] = 'a'; break; case 14: called[0] = 'b'; break; case 15: called[0] = 'c'; break; default: if (pstn->sweden) called[0] = '0' + (((data[2] & 0x0f) + 9) % 10); else called[0] = '0' + ((data[2] & 0x0f) % 10); } /* if we are receiving digits en block */ if (pstn->call[PSTN_CALL_ACTIVE]->state == CALL_STATE_ENBLOCK) { PDEBUG(DTEL, DEBUG_DEBUG, "Storing digit, because we perform enblock dialing.\n"); /* append digit */ strncat(pstn->dialing, called, sizeof(pstn->dialing) - 1); PDEBUG(DTEL, DEBUG_DEBUG, "Appending digit, so dial string is now: '%s'.\n", pstn->dialing); if (check_dial_hint(pstn->dialing)) { PDEBUG(DTEL, DEBUG_DEBUG, "Number in list of dial hints received, number is complete, send setup\n"); /* setup (en block) */ setup_ind(pstn, PSTN_CALL_ACTIVE, pstn->dialing, 1); break; } /* start dial timer */ timer_on(pstn, (double)pstn->enblock, TIMER_IDENT_DIALING); break; } if (pstn->call[PSTN_CALL_ACTIVE]->state != CALL_STATE_OVERLAP_NET) { PDEBUG(DTEL, DEBUG_NOTICE, "Received pulse digit, but call is not in overlap state, ignoring!\n"); break; } /* create osmo-cc message */ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_INFO_IND); /* called number (digits) */ PDEBUG(DTEL, DEBUG_INFO, "Sending INFO-IND with digit '%s' towards Osmo-CC\n", called); osmo_cc_add_ie_called(new_msg, OSMO_CC_TYPE_UNKNOWN, OSMO_CC_PLAN_TELEPHONY, called); /* send message to osmo-cc */ osmo_cc_ll_msg(&pstn->cc_ep, pstn->call[PSTN_CALL_ACTIVE]->cc_callref, new_msg); break; default: PDEBUG(DTEL, DEBUG_INFO, "Received unknown signal 0x%02x, ignoring!\n", data[0]); } } static void v5_disc_cpl_ind_and_cleanup(pstn_t *pstn) { /* deactivate b-channel */ ph_socket_tx_msg(&pstn->ph_socket, 1, PH_PRIM_DACT_REQ, NULL, 0); /* stop tone */ tone_off(pstn); /* stop caller id process */ callerid_off(pstn); /* stop DTMF */ dtmf_off(pstn); /* release, if not already */ release_call(pstn, PSTN_CALL_ACTIVE, OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR); /* change state */ pstn_new_state(pstn, PSTN_STATE_NULL); } static void v5_block(pstn_t *pstn) { /* release and change state */ if (pstn->state != PSTN_STATE_BLOCKED) { v5_disc_cpl_ind_and_cleanup(pstn); pstn_new_state(pstn, PSTN_STATE_BLOCKED); } } static void v5_unblock(pstn_t *pstn) { /* change state */ if (pstn->state == PSTN_STATE_BLOCKED) pstn_new_state(pstn, PSTN_STATE_NULL); } /* receive V5 message */ static void v5_receive(pstn_t *pstn, uint8_t *data, int len) { uint8_t event; if (len < 1) { PDEBUG(DTEL, DEBUG_ERROR, "Received data indication message from PH socket is too short, please fix!\n"); return; } event = *data++; len--; PDEBUG(DTEL, DEBUG_INFO, "Received %s message from Osmo-V5.\n", pstn_event_name(event)); switch (event) { case PSTN_EVENT_EST_ACK_IND: if (pstn->state != PSTN_STATE_EST_LE) { PDEBUG(DTEL, DEBUG_NOTICE, "Received %s message in state %s, ignoring!\n", pstn_event_name(event), pstn_state_name(pstn->state)); break; } /* we got an establish acknowledgement from PSTN interface */ v5_est_ack_ind(pstn, data, len); break; case PSTN_EVENT_EST_IND: /* in case of collision, just treat as ACK and proceed outgoing call */ if (pstn->state == PSTN_STATE_EST_LE) { PDEBUG(DTEL, DEBUG_NOTICE, "Handle establish collision, send ACK. A possible ACK from AN will be ignored.\n"); /* send ack message to V5 */ v5_est_ack_req(pstn); /* treat as establish acknowledgement from PSTN interface */ v5_est_ack_ind(pstn, data, len); break; } if (pstn->state != PSTN_STATE_NULL) { PDEBUG(DTEL, DEBUG_NOTICE, "Received %s message in state %s, ignoring!\n", pstn_event_name(event), pstn_state_name(pstn->state)); break; } /* we got an establishment from PSTN interface */ v5_est_ind(pstn, data, len); break; case PSTN_EVENT_SIG_IND: if (pstn->state != PSTN_STATE_ACTIVE) { PDEBUG(DTEL, DEBUG_NOTICE, "Received %s message in state %s, ignoring!\n", pstn_event_name(event), pstn_state_name(pstn->state)); break; } /* we got a line signal from PSTN interface */ v5_sig_ind(pstn, data, len); break; case PSTN_EVENT_DISC_CPL_IND: /* in NULL state we may receive a DISC, because of disconnect collision or confirm, so we ignore it */ if (pstn->state == PSTN_STATE_NULL) return; if (pstn->state != PSTN_STATE_ACTIVE && pstn->state != PSTN_STATE_EST_LE && pstn->state != PSTN_STATE_EST_AN && pstn->state != PSTN_STATE_DISC_REQ) { PDEBUG(DTEL, DEBUG_NOTICE, "Received %s message in state %s, ignoring!\n", pstn_event_name(event), pstn_state_name(pstn->state)); break; } /* we got a disconnect (reply) from PSTN interface */ v5_disc_cpl_ind_and_cleanup(pstn); break; default: PDEBUG(DTEL, DEBUG_NOTICE, "Received unssuports %s message in state %s, ignoring!\n", pstn_event_name(event), pstn_state_name(pstn->state)); } } /* message from PH socket */ void ph_socket_rx_msg(ph_socket_t *s, int channel, uint8_t prim, uint8_t *data, int length) { pstn_t *pstn = (pstn_t *)s->priv; switch (prim) { case PH_PRIM_CTRL_IND: if (channel == 0 && length >= 1) { if (*data == PH_CTRL_BLOCK) { PDEBUG(DTEL, DEBUG_INFO, "Received blocking from V5 interface!\n"); v5_block(pstn); } if (*data == PH_CTRL_UNBLOCK) { PDEBUG(DTEL, DEBUG_INFO, "Received unblocking from V5 interface!\n"); v5_unblock(pstn); } } break; case PH_PRIM_DATA_IND: if (channel == 0) v5_receive(pstn, data, length); if (channel == 1) bchannel_rx_tx(pstn, data, length); break; case PH_PRIM_ACT_IND: if (channel == 1) { PDEBUG(DTEL, DEBUG_DEBUG, "Received b-channel activation from V5 interface!\n"); pstn->b_transmitting = 0; } break; case PH_PRIM_DACT_IND: break; } } /* send V5 message */ static void v5_send(pstn_t *pstn, uint8_t event, uint8_t *data, int length) { uint8_t buffer[length + 1]; buffer[0] = event; if (length) memcpy(buffer + 1, data, length); PDEBUG(DTEL, DEBUG_INFO, "Sending %s message to Osmo-V5.\n", pstn_event_name(event)); ph_socket_tx_msg(&pstn->ph_socket, 0, PH_PRIM_DATA_REQ, buffer, length + 1); } static void v5_est_req(pstn_t *pstn, uint8_t *ie, int length) { v5_send(pstn, PSTN_EVENT_EST_REQ, ie, length); } static void v5_est_ack_req(pstn_t *pstn) { v5_send(pstn, PSTN_EVENT_EST_ACK_REQ, NULL, 0); } static void v5_sig_req(pstn_t *pstn, uint8_t *ie, int length) { v5_send(pstn, PSTN_EVENT_SIG_REQ, ie, length); } static void v5_disc_req_and_cleanup(pstn_t *pstn) { /* deactivate b-channel */ ph_socket_tx_msg(&pstn->ph_socket, 1, PH_PRIM_DACT_REQ, NULL, 0); /* stop tone */ tone_off(pstn); /* stop caller id process */ callerid_off(pstn); /* stop DTMF */ dtmf_off(pstn); /* release, if not already */ release_call(pstn, PSTN_CALL_ACTIVE, OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR); /* change state */ pstn_new_state(pstn, PSTN_STATE_NULL); if (pstn->reversed) { pstn->reversed = 0; PDEBUG(DTEL, DEBUG_DEBUG, "Normal polarity.\n"); uint8_t ie[3] = { PSTN_V5_IE_STEADY_SIGNAL, 1, 0x80 | PSTN_V5_STEADY_SIGNAL_NORMAL}; v5_send(pstn, PSTN_EVENT_DISC_REQ, ie, sizeof(ie)); } else v5_send(pstn, PSTN_EVENT_DISC_REQ, NULL, 0); } /* * timeout */ static void pstn_timeout(void *data) { pstn_t *pstn = data; switch (pstn->timer_ident) { case TIMER_IDENT_DIALING: PDEBUG(DTEL, DEBUG_INFO, "Timeout while enblock-dialing.\n"); /* if no digit were dialed */ if (pstn->dialing[0] == '\0') { /* hangup tone */ tone_on(pstn, TONE_HANGUP, "hangup"); /* stop DTMF */ dtmf_off(pstn); /* stop pulse */ pulse_off(pstn); break; } /* setup (en block) */ setup_ind(pstn, PSTN_CALL_ACTIVE, pstn->dialing, 1); break; case TIMER_IDENT_RELEASE: PDEBUG(DTEL, DEBUG_INFO, "Timeout while in disconnect state, releasing.\n"); if (pstn->call[PSTN_CALL_ACTIVE]->cc_callref) { release_call(pstn, PSTN_CALL_ACTIVE, OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR); /* SIT tone */ tone_on(pstn, TONE_SPECIAL_INFO, "SIT"); } break; case TIMER_IDENT_HOOKFLASH: PDEBUG(DTEL, DEBUG_DEBUG, "Timeout while waiting for hook flash, releasing.\n"); /* stop tone */ tone_off(pstn); /* stop timer */ timer_off(pstn); /* if active call, release it */ if (pstn->call[PSTN_CALL_ACTIVE]->cc_callref) { /* release towards osmo-cc */ release_call(pstn, PSTN_CALL_ACTIVE, OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR); } /* if call on hold, ring the phone again */ if (pstn->call[PSTN_CALL_HOLD]->cc_callref) { /* swap ACTIVE call and call on HOLD */ swap_active_hold(pstn); /* send message to V5 */ uint8_t ie[3] = { PSTN_V5_IE_CADENCED_RINGING, 1, 0x80 | pstn->ringing_type_hold }; v5_sig_req(pstn, ie, sizeof(ie)); break; } /* there is no more call, release V5 */ v5_disc_req_and_cleanup(pstn); break; } }