2022-10-02 06:16:23 +00:00
|
|
|
/* 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 */
|
2023-02-05 09:55:44 +00:00
|
|
|
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)
|
2022-10-02 06:16:23 +00:00
|
|
|
{
|
|
|
|
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;
|
2023-01-02 20:34:34 +00:00
|
|
|
pstn->clip_date = clip_date;
|
2022-10-02 06:16:23 +00:00
|
|
|
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;
|
|
|
|
|
2023-01-02 20:34:34 +00:00
|
|
|
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);
|
2022-10-02 06:16:23 +00:00
|
|
|
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)
|
|
|
|
{
|
2023-02-05 09:55:44 +00:00
|
|
|
int dtas = 0;
|
|
|
|
|
2022-10-02 06:16:23 +00:00
|
|
|
/* 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) {
|
2023-02-05 09:55:44 +00:00
|
|
|
dtas = 1;
|
2022-10-02 06:16:23 +00:00
|
|
|
pstn->callerid_state = PSTN_CID_STATE_WAIT1;
|
|
|
|
/* add delay when cw */
|
|
|
|
pstn->callerid_wait1 = 8000;
|
|
|
|
pstn->callerid_wait2 = 0;
|
|
|
|
} else {
|
2023-02-05 09:55:44 +00:00
|
|
|
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;
|
|
|
|
}
|
2022-10-02 06:16:23 +00:00
|
|
|
}
|
|
|
|
|
2023-02-05 09:55:44 +00:00
|
|
|
/* add DT_AS on waitng call */
|
|
|
|
callerid_set(&pstn->callerid, cw, dtas, callerid, caller_type, pstn->clip_date);
|
|
|
|
|
2022-10-02 06:16:23 +00:00
|
|
|
/* 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) {
|
2023-02-05 09:55:44 +00:00
|
|
|
if (pstn->clip == CID_METHOD_STOP && pstn->call[PSTN_CALL_ACTIVE]->state == CALL_STATE_ALERTING_SUB) {
|
2022-10-02 06:16:23 +00:00
|
|
|
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) {
|
2023-02-05 09:55:44 +00:00
|
|
|
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");
|
2022-10-02 06:16:23 +00:00
|
|
|
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 */
|
2023-02-05 09:55:44 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2022-10-02 06:16:23 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2023-01-02 20:34:15 +00:00
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-02 06:16:23 +00:00
|
|
|
static void v5_sig_ind(pstn_t *pstn, uint8_t *data, int len)
|
|
|
|
{
|
|
|
|
osmo_cc_msg_t *new_msg;
|
2023-01-02 20:34:15 +00:00
|
|
|
int pulses;
|
2022-10-02 06:16:23 +00:00
|
|
|
|
|
|
|
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) {
|
2023-01-02 20:34:15 +00:00
|
|
|
PDEBUG(DTEL, DEBUG_INFO, "Performing hookflash, because on-hook was too short for hangup.\n");
|
2022-10-02 06:16:23 +00:00
|
|
|
/* stop timer */
|
|
|
|
timer_off(pstn);
|
2023-01-02 20:34:15 +00:00
|
|
|
hookflash(pstn);
|
2022-10-02 06:16:23 +00:00
|
|
|
}
|
|
|
|
/* 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");
|
2023-01-02 20:34:15 +00:00
|
|
|
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);
|
2022-10-02 06:16:23 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case PSTN_V5_IE_DIGIT_SIGNAL:
|
2023-01-02 20:34:15 +00:00
|
|
|
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;
|
|
|
|
}
|
2022-10-02 06:16:23 +00:00
|
|
|
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' };
|
2023-01-02 20:34:15 +00:00
|
|
|
switch (pulses) {
|
2022-10-02 06:16:23 +00:00
|
|
|
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 */
|
2023-01-03 10:23:21 +00:00
|
|
|
if (pstn->state == PSTN_STATE_EST_LE) {
|
2023-02-05 09:55:44 +00:00
|
|
|
PDEBUG(DTEL, DEBUG_NOTICE, "Handle establish collision, send ACK. A possible ACK from AN will be ignored.\n");
|
2023-01-03 10:23:21 +00:00
|
|
|
/* 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) {
|
2022-10-02 06:16:23 +00:00
|
|
|
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);
|
|
|
|
|
2023-02-05 09:55:44 +00:00
|
|
|
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);
|
2022-10-02 06:16:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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);
|
|
|
|
}
|
|
|
|
|