osmo-cc-pstn-endpoint/src/pstn/pstn.c

1742 lines
53 KiB
C

/* POTS/PSTN FXS implementation
*
* (C) 2022 by Andreas Eversberg <jolly@eversberg.eu>
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stddef.h>
#include <math.h>
#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 "<unknown state>";
}
};
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 "<unknown event>";
}
};
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 "<unknown state>";
}
};
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 "<unknown timer>";
}
};
/*
* 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(struct timer *timer);
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, int 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 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;
/* create ph socket */
rc = ph_socket_init(&pstn->ph_socket, ph_socket_rx_msg, pstn, socketname, 0);
if (rc < 0)
return rc;
/* 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);
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);
}
void rtp_work(pstn_t *pstn)
{
int i;
for (i = 0; i < 2; i++) {
if (pstn->call[i]->cc_session)
osmo_cc_session_handle(pstn->call[i]->cc_session, pstn->call[i]);
}
}
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)
{
/* 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);
/* add DT_AS on waitng call */
callerid_set(&pstn->callerid, cw, (cw) ? 1 : 0, callerid, caller_type, pstn->clip_date);
if (cw) {
pstn->callerid_state = PSTN_CID_STATE_WAIT1;
/* add delay when cw */
pstn->callerid_wait1 = 8000;
pstn->callerid_wait2 = 0;
} else {
pstn->callerid_state = PSTN_CID_STATE_WAIT1;
/* add delay when ringing */
pstn->callerid_wait1 = 8000;
pstn->callerid_wait2 = 16000;
}
/* 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);
/* stop timer */
timer_off(pstn);
/* if we are receiving digits en block */
if (pstn->call[PSTN_CALL_ACTIVE]->state == CALL_STATE_ENBLOCK) {
if (digit == '#') {
PDEBUG(DTEL, DEBUG_DEBUG, "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);
/* 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);
}
}
uint8_t init_data[len * 2];
int offset = 0;
if (!pstn->b_transmitting) {
PDEBUG(DTEL, DEBUG_DEBUG, "First received b-channel data, filling FIFO with double data of %d bytes.\n", len * 2);
memset(init_data, 0xff, len);
data = init_data;
offset = len;
}
/* load from TX jitter buffer and optionally overload with tones an with caller ID */
jitter_load(&pstn->tx_dejitter, data + offset, len);
isdn_tone_copy(&pstn->isdn_tone, data + offset, 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->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 + offset, 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) {
PDEBUG(DTEL, DEBUG_DEBUG, "Continue with 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;
}
/* forward to interface */
ph_socket_tx_msg(&pstn->ph_socket, 1, PH_PRIM_DATA_REQ, data, len + offset);
pstn->b_transmitting = 1;
}
/*
* 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 = 1;
}
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 */
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));
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;
}
/* 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;
}
/* 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)
{
/* stop tone (CW tone) */
tone_off(pstn);
/* clear dialing */
pstn->dialing[0] = '\0';
/* 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);
/* 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);
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_ON_HOOK:
PDEBUG(DTEL, DEBUG_INFO, "Received pulsed on-kook signal.\n");
if (pstn->recall && pstn->call[PSTN_CALL_ACTIVE]->state == CALL_STATE_ACTIVE) {
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 10:
called[0] = '0';
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:
called[0] = '0' + (data[2] & 0x0f);
}
/* 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);
/* 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_INFO, " -> Handle establish collision, send ACK.\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);
v5_send(pstn, PSTN_EVENT_DISC_REQ, NULL, 0);
}
/*
* timeout
*/
static void pstn_timeout(struct timer *timer)
{
pstn_t *pstn = timer->priv;
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;
}
}
/*
* work
*/
void pstn_work(pstn_t *pstn)
{
ph_socket_work(&pstn->ph_socket);
}