summaryrefslogtreecommitdiffstats
path: root/src/ss5/ss5.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ss5/ss5.c')
-rw-r--r--src/ss5/ss5.c1128
1 files changed, 1128 insertions, 0 deletions
diff --git a/src/ss5/ss5.c b/src/ss5/ss5.c
new file mode 100644
index 0000000..93bdd9f
--- /dev/null
+++ b/src/ss5/ss5.c
@@ -0,0 +1,1128 @@
+/* SS5 process
+ *
+ * (C) 2020 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/>.
+ */
+
+#define CHAN ss5->name
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <math.h>
+#include <errno.h>
+#include <sys/types.h>
+#include "../libdebug/debug.h"
+#include "../libg711/g711.h"
+#include "ss5.h"
+#include "display.h"
+
+/* names of all SS5 states */
+const char *ss5_state_names[] = {
+ "NULL",
+ /* idle line */
+ "IDLE",
+ /* outgoing call */
+ "SEND SEIZE",
+ "RECV PROCEED-TO-SEND",
+ "SEND DIGITS",
+ "OUT INACTIVE",
+ "SEND ACKNOWLEDGE (to answer)",
+ "SEND ACKNOWLEDGE (to busy-flash)",
+ "SEND ACKNOWLEDGE (to clear-back)",
+ "OUT ACTIVE",
+ "SEND CLEAR-FORWARD",
+ "RECV RELEASE-GUARD",
+ "SEND FORWARD-TRANSFER",
+ /* incoming call */
+ "SEND PROCEED-TO-SEND",
+ "RECV DIGIT",
+ "RECV SPACE",
+ "IN INACTIVE",
+ "SEND ANSWER",
+ "IN ACTIVE",
+ "SEND BUSY-FLASH",
+ "SEND CLEAR-BACK",
+ "SEND RELEASE-GUARD",
+ "SEND RELEASE-GUARD (waiting)",
+ /* seize collision */
+ "DOUBLE-SEIZURE",
+};
+
+/* timers and durations */
+#define SIGN_RECOGNITION_FAST 0.040 /* 40 ms for seize and proceed-to-send */
+#define SIGN_RECOGNITION_NORMAL 0.125 /* 125 ms for all other signals */
+#define MIN_RELEASE_GUARD 0.200 /* minimum 200 ms, in case we prevent blueboxing */
+#define MAX_SEIZE 10.0
+#define MAX_PROCEED_TO_SEND 4.0
+#define MAX_ANSWER 10.0
+#define MAX_BUSY_FLASH 10.0
+#define MAX_CLEAR_BACK 10.0
+#define MAX_ACKNOWLEDGE 4.0
+#define MAX_CLEAR_FORWARD 10.0
+#define MAX_RELEASE_GUARD 4.0
+#define DUR_DOUBLE_SEIZURE 0.850 /* 850 ms to be sure the other end recognizes the double seizure */
+#define DUR_FORWARD_TRANSFER 0.850 /* 850 ms forward-transfer */
+#define PAUSE_BEFORE_DIALING 0.080 /* pause before dialing after cease of tone */
+#define TO_DIALING 10.0 /* as defined in clause about releasing the incoming register when number is incomplete */
+
+static struct osmo_cc_helper_audio_codecs codecs[] = {
+ { "L16", 8000, 1, encode_l16, decode_l16 },
+ { "PCMA", 8000, 1, g711_encode_alaw, g711_decode_alaw },
+ { "PCMU", 8000, 1, g711_encode_ulaw, g711_decode_ulaw },
+ { NULL, 0, 0, NULL, NULL},
+};
+
+void refresh_status(void)
+{
+ osmo_cc_endpoint_t *ep;
+ ss5_endpoint_t *ss5_ep;
+ ss5_t *ss5;
+ int i;
+
+ display_status_start();
+
+ for (ep = osmo_cc_endpoint_list; ep; ep = ep->next) {
+ ss5_ep = ep->priv;
+ if (!ss5_ep->link_list)
+ display_status_line(ep->local_name, 0, NULL, NULL, 0);
+ for (i = 0, ss5 = ss5_ep->link_list; ss5; i++, ss5 = ss5->next)
+ display_status_line(ep->local_name, i, ss5->callerid, ss5->dialing, ss5->state);
+ }
+
+ display_status_end();
+}
+
+void ss5_new_state(ss5_t *ss5, enum ss5_state state)
+{
+ PDEBUG_CHAN(DSS5, DEBUG_DEBUG, "Changing state '%s' -> '%s'\n", ss5_state_names[ss5->state], ss5_state_names[state]);
+ ss5->state = state;
+
+ /* must update (new state) */
+ refresh_status();
+}
+
+/*
+ * endpoints & links
+ */
+
+/* reset ss5 link, but keep states, so audio generation/processing can continue */
+static void link_reset(ss5_t *ss5)
+{
+ /* unlink callref */
+ ss5->cc_callref = 0;
+
+ /* stop timer */
+ timer_stop(&ss5->timer);
+
+ /* free session description */
+ if (ss5->cc_session) {
+ osmo_cc_free_session(ss5->cc_session);
+ ss5->cc_session = NULL;
+ ss5->codec = NULL;
+ }
+
+ /* reset jitter buffer */
+ jitter_reset(&ss5->dejitter);
+
+ /* set recognition time */
+ set_sig_detect_duration(&ss5->dsp, SIGN_RECOGNITION_FAST, SIGN_RECOGNITION_NORMAL);
+
+ /* reset all other states */
+ ss5->callerid[0] = '\0';
+ ss5->dialing[0] = '\0';
+
+ /* must update (e.g. caller and dialing) */
+ refresh_status();
+}
+
+static void ss5_timeout(struct timer *timer);
+
+static ss5_t *link_create(ss5_endpoint_t *ss5_ep, const char *ep_name, int linkid)
+{
+ ss5_t *ss5, **ss5_p;
+ int rc;
+
+ ss5 = calloc(1, sizeof(*ss5));
+ if (!ss5) {
+ PDEBUG(DSS5, DEBUG_ERROR, "No memory!\n");
+ abort();
+ }
+ ss5->ss5_ep = ss5_ep;
+
+ ss5_p = &ss5_ep->link_list;
+ while (*ss5_p)
+ ss5_p = &((*ss5_p)->next);
+ *ss5_p = ss5;
+
+ /* debug name */
+ snprintf(ss5->name, sizeof(ss5->name) - 1, "%s/%d", ep_name, linkid);
+
+ /* init dsp instance */
+ dsp_init_inst(&ss5->dsp, ss5, ss5_ep->samplerate, ss5_ep->sense_db);
+
+ /* init timer */
+ timer_init(&ss5->timer, ss5_timeout, ss5);
+
+ /* allocate jitter buffer */
+ rc = jitter_create(&ss5->dejitter, 8000 / 10); // FIXME: size
+ if (rc < 0)
+ abort();
+
+ /* alloc delay buffer */
+ if (ss5_ep->delay_ms) {
+ ss5->delay_length = (int)(ss5_ep->samplerate * (double)ss5_ep->delay_ms / 1000.0);
+ ss5->delay_buffer = calloc(ss5->delay_length, sizeof(*ss5->delay_buffer));
+ }
+
+ /* reset instance */
+ link_reset(ss5);
+
+ /* state idle */
+ ss5_new_state(ss5, SS5_STATE_IDLE);
+
+ PDEBUG_CHAN(DSS5, DEBUG_DEBUG, "created ss5 instance\n");
+
+ return ss5;
+}
+
+static void link_destroy(ss5_t *ss5)
+{
+ ss5_t **ss5_p;
+
+ /* state idle */
+ ss5_new_state(ss5, SS5_STATE_IDLE);
+
+ /* reset instance */
+ link_reset(ss5);
+
+ /* exit timer */
+ timer_exit(&ss5->timer);
+
+ /* free jitter buffer */
+ jitter_destroy(&ss5->dejitter);
+
+ /* free delay buffer */
+ if (ss5->delay_buffer) {
+ free(ss5->delay_buffer);
+ ss5->delay_buffer = NULL;
+ }
+
+ /* cleanup dsp instance */
+ dsp_cleanup_inst(&ss5->dsp);
+
+ /* detach */
+ ss5_p = &ss5->ss5_ep->link_list;
+ while (*ss5_p) {
+ if (*ss5_p == ss5)
+ break;
+ ss5_p = &((*ss5_p)->next);
+ }
+ *ss5_p = ss5->next;
+
+ PDEBUG_CHAN(DSS5, DEBUG_DEBUG, "destroyed ss5 instance\n");
+
+ free(ss5);
+}
+
+ss5_endpoint_t *ss5_ep_create(const char *ep_name, int links, int prevent_blueboxing, int crosstalk, int delay_ms, int comfort_noise, int suppress_disconnect, double sense_db)
+{
+ ss5_endpoint_t *ss5_ep;
+ int i;
+
+ ss5_ep = calloc(1, sizeof(*ss5_ep));
+ if (!ss5_ep) {
+ PDEBUG(DSS5, DEBUG_ERROR, "No memory!\n");
+ abort();
+ }
+
+ ss5_ep->samplerate = 8000;
+ ss5_ep->prevent_blueboxing = prevent_blueboxing;
+ ss5_ep->crosstalk = crosstalk;
+ ss5_ep->delay_ms = delay_ms;
+ ss5_ep->comfort_noise = comfort_noise;
+ ss5_ep->suppress_disconnect = suppress_disconnect;
+ ss5_ep->sense_db = sense_db;
+
+ for (i = 0; i < links; i++)
+ link_create(ss5_ep, ep_name, i + 1);
+
+ PDEBUG(DSS5, DEBUG_DEBUG, "SS5 endpoint instance created\n");
+
+ return ss5_ep;
+}
+
+void ss5_ep_destroy(ss5_endpoint_t *ss5_ep)
+{
+ /* destroy all calls */
+ while (ss5_ep->link_list)
+ link_destroy(ss5_ep->link_list);
+
+ free(ss5_ep);
+
+ PDEBUG(DSS5, DEBUG_DEBUG, "SS5 endpoint instance destroyed\n");
+}
+
+/*
+ * several messages towards CC
+ */
+
+static void reject_call(ss5_endpoint_t *ss5_ep, uint32_t callref, uint8_t isdn_cause)
+
+{
+ osmo_cc_msg_t *new_msg;
+
+ /* create osmo-cc message */
+ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REJ_IND);
+ /* cause */
+ osmo_cc_add_ie_cause(new_msg, OSMO_CC_LOCATION_BEYOND_INTERWORKING, isdn_cause, 0, 0);
+ /* send message to osmo-cc */
+ osmo_cc_ll_msg(&ss5_ep->cc_ep, callref, new_msg);
+}
+
+static void release_call(ss5_t *ss5, uint8_t isdn_cause)
+{
+ osmo_cc_msg_t *new_msg;
+
+ /* create osmo-cc message */
+ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND);
+ /* cause */
+ osmo_cc_add_ie_cause(new_msg, OSMO_CC_LOCATION_BEYOND_INTERWORKING, isdn_cause, 0, 0);
+ /* send message to osmo-cc */
+ osmo_cc_ll_msg(&ss5->ss5_ep->cc_ep, ss5->cc_callref, new_msg);
+}
+
+static void disconnect_call(ss5_t *ss5, uint8_t isdn_cause)
+{
+ osmo_cc_msg_t *new_msg;
+
+ if (ss5->ss5_ep->suppress_disconnect)
+ return;
+ /* create osmo-cc message */
+ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_DISC_IND);
+ /* progress */
+ osmo_cc_add_ie_progress(new_msg, OSMO_CC_CODING_ITU_T, OSMO_CC_LOCATION_BEYOND_INTERWORKING, OSMO_CC_PROGRESS_INBAND_INFO_AVAILABLE);
+ /* cause */
+ osmo_cc_add_ie_cause(new_msg, OSMO_CC_LOCATION_BEYOND_INTERWORKING, isdn_cause, 0, 0);
+ /* send message to osmo-cc */
+ osmo_cc_ll_msg(&ss5->ss5_ep->cc_ep, ss5->cc_callref, new_msg);
+}
+
+static void proceed_call(ss5_t *ss5, const char *sdp)
+{
+ osmo_cc_msg_t *new_msg;
+
+ /* create osmo-cc message */
+ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_PROC_IND);
+ /* progress */
+ osmo_cc_add_ie_progress(new_msg, OSMO_CC_CODING_ITU_T, OSMO_CC_LOCATION_BEYOND_INTERWORKING, OSMO_CC_PROGRESS_INBAND_INFO_AVAILABLE);
+ /* sdp */
+ osmo_cc_add_ie_sdp(new_msg, sdp);
+ /* send message to osmo-cc */
+ osmo_cc_ll_msg(&ss5->ss5_ep->cc_ep, ss5->cc_callref, new_msg);
+}
+
+static void alert_call(ss5_t *ss5)
+{
+ osmo_cc_msg_t *new_msg;
+
+ /* create osmo-cc message */
+ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_ALERT_IND);
+ /* send message to osmo-cc */
+ osmo_cc_ll_msg(&ss5->ss5_ep->cc_ep, ss5->cc_callref, new_msg);
+}
+
+static void answer_call(ss5_t *ss5)
+{
+ osmo_cc_msg_t *new_msg;
+
+ /* create osmo-cc message */
+ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_CNF);
+ /* send message to osmo-cc */
+ osmo_cc_ll_msg(&ss5->ss5_ep->cc_ep, ss5->cc_callref, new_msg);
+}
+
+static void setup_ack_call(ss5_t *ss5)
+{
+ osmo_cc_msg_t *new_msg;
+
+ /* create osmo-cc message */
+ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_ACK_IND);
+ /* send message to osmo-cc */
+ osmo_cc_ll_msg(&ss5->ss5_ep->cc_ep, ss5->cc_callref, new_msg);
+}
+
+/*
+ * dial string generation and parsing
+ */
+
+static char *prefix_1_digit[] = { "1", "7", NULL };
+static char *prefix_2_digit[] = {
+ "20", "27", "28", "30", "31", "32", "33", "34", "36", "39", "40", "41",
+ "43", "44", "45", "46", "47", "48", "49", "51", "52", "53", "54", "55",
+ "56", "57", "58", "60", "61", "62", "63", "64", "65", "66", "81", "82",
+ "83", "84", "86", "89", "90", "91", "92", "93", "94", "95", "98",
+ NULL };
+
+/* use number and number type to generate an SS5 dial string
+ * the digits are checked if they can be dialed
+ * if the number is already in SS5 format, only digits are checked
+ */
+static int generate_dial_string(uint8_t type, const char *dialing, char *string, int string_size)
+{
+ int full_string_given = 0;
+ int i, ii;
+
+ if ((int)strlen(dialing) + 4 > string_size) {
+ PDEBUG(DSS5, DEBUG_NOTICE, "Dial string is too long for our digit register, call is rejected!\n");
+ return -EINVAL;
+ }
+
+ /* check for correct digits */
+ for (i = 0, ii = strlen(dialing); i < ii; i++) {
+ /* string may start with 'a' or 'b', but then 'c' must be the last digit */
+ if (dialing[i] == 'a' || dialing[i] == 'b') {
+ full_string_given = 1;
+ if (dialing[ii - 1] != 'c') {
+ PDEBUG(DSS5, DEBUG_NOTICE, "Number starts with 'a' (KP1) or 'b' (KP2) but missing 'c' (ST) at the end, call is rejected!\n");
+ return -EINVAL;
+ }
+ /* remove check of last digit 'c' */
+ --ii;
+ continue;
+ }
+ /* string must only consist of numerical digits and '*' (code 11) and '#' (code 12) */
+ if (!strchr("0123456789*#", dialing[i])) {
+ PDEBUG(DSS5, DEBUG_NOTICE, "Number has invalid digits, call is rejected!\n");
+ return -EINVAL;
+ }
+ }
+
+ /* if full string with 'a'/'b' and 'c' is given, we have complete dial string */
+ if (full_string_given) {
+ strcpy(string, dialing);
+ return 0;
+ }
+
+ /* if number is not of international type, create national dial string */
+ if (type != OSMO_CC_TYPE_INTERNATIONAL) {
+ // make GCC happy
+ strcpy(string, "a0");
+ strcat(string, dialing);
+ strcat(string, "c");
+ return 0;
+ }
+
+ /* check international prefix with length of 1 digit */
+ if ((int)strlen(dialing) < 1) {
+ PDEBUG(DSS5, DEBUG_NOTICE, "International number too short to get country code from, call is rejected!\n");
+ return -EINVAL;
+ }
+ for (i = 0; prefix_1_digit[i]; i++) {
+ if (prefix_1_digit[i][0] == dialing[0])
+ break;
+ }
+ /* if number is of international type, create international dial string */
+ if (prefix_1_digit[i]) {
+ sprintf(string, "b%c0%sc", dialing[0], dialing + 1);
+ return 0;
+ }
+
+ /* check international prefix with length of 2 digits */
+ if ((int)strlen(dialing) < 2) {
+ PDEBUG(DSS5, DEBUG_NOTICE, "International number too short to get country code from, call is rejected!\n");
+ return -EINVAL;
+ }
+ for (i = 0; prefix_2_digit[i]; i++) {
+ if (prefix_2_digit[i][0] == dialing[0]
+ && prefix_2_digit[i][1] == dialing[1])
+ break;
+ }
+ /* if number is of international type, create international dial string */
+ if (prefix_2_digit[i]) {
+ sprintf(string, "b%c%c0%sc", dialing[0], dialing[1], dialing + 2);
+ return 0;
+ }
+
+ /* check international prefix with length of 3 digits */
+ if ((int)strlen(dialing) < 3) {
+ PDEBUG(DSS5, DEBUG_NOTICE, "International number too short to get country code from, call is rejected!\n");
+ return -EINVAL;
+ }
+ /* if number is of international type, create international dial string */
+ sprintf(string, "b%c%c%c0%sc", dialing[0], dialing[1], dialing[2], dialing + 3);
+ return 0;
+}
+
+/* parse received SS5 dial string and convert it into a national or international number */
+static int parse_dial_string(uint8_t *type, char *dialing, int dialing_size, const char *string)
+{
+ char kp_digit;
+ const char *prefix;
+ int length;
+ int i;
+
+ /* remove start and stop digits, set string after start digit and set length to digits between start and stop */
+ if (string[0] != 'a' && string[0] != 'b') {
+ PDEBUG(DSS5, DEBUG_NOTICE, "Received digits do not start with 'a' (KP1) nor 'b' (KP2), call is rejected!\n");
+ return -EINVAL;
+ }
+ kp_digit = *string++;
+ length = strlen(string) - 1;
+ if (string[length] != 'c') {
+ PDEBUG(DSS5, DEBUG_NOTICE, "Received digits do end with 'c' (ST), call is rejected!\n");
+ return -EINVAL;
+ }
+ if (length > dialing_size - 1) {
+ PDEBUG(DSS5, DEBUG_NOTICE, "Received dial string is too long, call is rejected!\n");
+ return -EINVAL;
+ }
+
+ /* received national call */
+ if (kp_digit == 'a') {
+ /* remove discriminaing digit */
+ string++;
+ --length;
+ *type = OSMO_CC_TYPE_NATIONAL;
+ strncpy(dialing, string, length);
+ dialing[length] = '\0';
+ return 0;
+ }
+
+ /* check international prefix with length of 1 digit */
+ if (length < 2) {
+ PDEBUG(DSS5, DEBUG_NOTICE, "International number too short to get country code from, call is rejected!\n");
+ return -EINVAL;
+ }
+ for (i = 0; prefix_1_digit[i]; i++) {
+ if (prefix_1_digit[i][0] == string[0])
+ break;
+ }
+ /* if number is of international type, create international dial string */
+ if (prefix_1_digit[i]) {
+ prefix = string;
+ string += 1;
+ length -= 1;
+ /* remove discriminaing digit */
+ string++;
+ --length;
+ *type = OSMO_CC_TYPE_INTERNATIONAL;
+ dialing[0] = prefix[0];
+ strncpy(dialing + 1, string, length);
+ dialing[1 + length] = '\0';
+ return 0;
+ }
+
+ /* check international prefix with length of 2 digits */
+ if (length < 3) {
+ PDEBUG(DSS5, DEBUG_NOTICE, "International number too short to get country code from, call is rejected!\n");
+ return -EINVAL;
+ }
+ for (i = 0; prefix_2_digit[i]; i++) {
+ if (prefix_2_digit[i][0] == string[0]
+ && prefix_2_digit[i][1] == string[1])
+ break;
+ }
+ /* if number is of international type, create international dial string */
+ if (prefix_2_digit[i]) {
+ prefix = string;
+ string += 2;
+ length -= 2;
+ /* remove discriminaing digit */
+ string++;
+ --length;
+ *type = OSMO_CC_TYPE_INTERNATIONAL;
+ dialing[0] = prefix[0];
+ dialing[1] = prefix[1];
+ strncpy(dialing + 2, string, length);
+ dialing[2 + length] = '\0';
+ return 0;
+ }
+
+ /* check international prefix with length of 3 digits */
+ if (length < 4) {
+ PDEBUG(DSS5, DEBUG_NOTICE, "International number too short to get country code from, call is rejected!\n");
+ return -EINVAL;
+ }
+ /* if number is of international type, create international dial string */
+ prefix = string;
+ string += 3;
+ length -= 3;
+ /* remove discriminaing digit */
+ string++;
+ --length;
+ *type = OSMO_CC_TYPE_INTERNATIONAL;
+ dialing[0] = prefix[0];
+ dialing[1] = prefix[1];
+ dialing[2] = prefix[2];
+ strncpy(dialing + 3, string, length);
+ dialing[3 + length] = '\0';
+ return 0;
+}
+
+/*
+ * event handling
+ */
+
+/* function that receives the digit or the cease of it (' ' or different digit) */
+void receive_digit(void *priv, char digit, double dbm)
+{
+ ss5_t *ss5 = priv;
+ int i;
+ int rc;
+
+ if (digit > ' ')
+ PDEBUG_CHAN(DSS5, DEBUG_DEBUG, "Received digit '%c' in '%s' state. (%.1f dBm)\n", digit, ss5_state_names[ss5->state], dbm);
+ else
+ PDEBUG_CHAN(DSS5, DEBUG_DEBUG, "Received cease of digit in '%s' state.\n", ss5_state_names[ss5->state]);
+
+ /* a clear forward (not release guard) at any state (including idle state) */
+ if (ss5->state != SS5_STATE_SEND_CLR_FWD && digit == 'C') {
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "Received 'clear-forward' signal in '%s' state, sending 'release-guard' and clearing call.\n", ss5_state_names[ss5->state]);
+ /* release outgoing call */
+ if (ss5->cc_callref) {
+ /* send release indication towards CC */
+ release_call(ss5, OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR);
+ /* remove ref */
+ ss5->cc_callref = 0;
+ }
+ /* stop timer */
+ timer_stop(&ss5->timer);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_SEND_RELEASE);
+ /* unset dial string, if set */
+ set_dial_string(&ss5->dsp, "");
+ /* send release-guard */
+ set_tone(&ss5->dsp, 'C', 0);
+ /* to prevent blueboxing */
+ if (ss5->ss5_ep->prevent_blueboxing) {
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "Starting release-guard timer to prevent blueboxing.\n");
+ /* start timer */
+ timer_start(&ss5->timer, MIN_RELEASE_GUARD);
+ }
+ return;
+ }
+
+ switch (ss5->state) {
+ /* release guard */
+ case SS5_STATE_SEND_RELEASE:
+ if (digit != 'C') {
+ /* wait at least the minimum release-guard time, to prevent blueboxing */
+ if (timer_running(&ss5->timer)) {
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "'clear-forward' is ceased in '%s' state, must wait to prevent blueboxing.\n", ss5_state_names[ss5->state]);
+ /* state idle */
+ ss5_new_state(ss5, SS5_STATE_SEND_REL_WAIT);
+ break;
+ }
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "'clear-forward' is ceased in '%s' state, going idle.\n", ss5_state_names[ss5->state]);
+ /* cease */
+ set_tone(&ss5->dsp, 0, 0);
+ /* state idle */
+ ss5_new_state(ss5, SS5_STATE_IDLE);
+ /* reset instance */
+ link_reset(ss5);
+ }
+ break;
+ /* outgoing call sends seize */
+ case SS5_STATE_SEND_SEIZE:
+ if (digit == 'A') {
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "Received 'seize' signal in '%s' state, this is double seizure.\n", ss5_state_names[ss5->state]);
+ /* set timeout */
+ timer_start(&ss5->timer, DUR_DOUBLE_SEIZURE);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_DOUBLE_SEIZE);
+ }
+ if (digit == 'B') {
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "Received 'proceed-to-send' signal in '%s' state, ceasing 'seize' signal.\n", ss5_state_names[ss5->state]);
+ /* set recognition time to normal */
+ set_sig_detect_duration(&ss5->dsp, SIGN_RECOGNITION_NORMAL, SIGN_RECOGNITION_NORMAL);
+ /* cease */
+ set_tone(&ss5->dsp, 0, 0);
+ /* stop timer */
+ timer_stop(&ss5->timer);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_RECV_PROCEED);
+ }
+ break;
+ /* both ends send a seize, waiting for timeout */
+ case SS5_STATE_DOUBLE_SEIZE:
+ break;
+ /* outgoing call receives proceed-to-send */
+ case SS5_STATE_RECV_PROCEED:
+ if (digit != 'B') {
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "proceed-to-send' is ceased in '%s' state, sendig digits.\n", ss5_state_names[ss5->state]);
+ /* cease */
+ set_tone(&ss5->dsp, 0, 0);
+ /* dial */
+ set_tone(&ss5->dsp, ' ', PAUSE_BEFORE_DIALING);
+ set_dial_string(&ss5->dsp, ss5->dialing);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_SEND_DIGITS);
+ }
+ break;
+ /* outgoing call receives answer or busy-flash */
+ case SS5_STATE_OUT_INACTIVE:
+ if (digit == 'A') {
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "Received 'answer' signal in '%s' state, sending 'acknowledge'.\n", ss5_state_names[ss5->state]);
+ /* send acknowledge */
+ set_tone(&ss5->dsp, 'A', MAX_ACKNOWLEDGE);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_SEND_ACK_ANS);
+ /* indicate answer to upper layer */
+ answer_call(ss5);
+ }
+ if (digit == 'B') {
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "Received 'busy-flash' signal in '%s' state, sending 'acknowledge'.\n", ss5_state_names[ss5->state]);
+ /* send acknowledge */
+ set_tone(&ss5->dsp, 'A', MAX_ACKNOWLEDGE);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_SEND_ACK_BUS);
+ /* indicate disconnect w/tones to upper layer */
+ disconnect_call(ss5, OSMO_CC_ISDN_CAUSE_USER_BUSY);
+ }
+ break;
+ /* outgoing call receives clear-back */
+ case SS5_STATE_OUT_ACTIVE:
+ if (digit == 'A') {
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "Received 'answer' signal in '%s' state, sending 'acknowledge'.\n", ss5_state_names[ss5->state]);
+ /* send acknowledge */
+ set_tone(&ss5->dsp, 'A', MAX_ACKNOWLEDGE);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_SEND_ACK_ANS);
+ /* indicate answer to upper layer */
+ answer_call(ss5);
+ }
+ if (digit == 'B') {
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "Received 'clear-back' signal in '%s' state, sending 'acknowledge'.\n", ss5_state_names[ss5->state]);
+ /* send acknowledge */
+ set_tone(&ss5->dsp, 'A', MAX_ACKNOWLEDGE);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_SEND_ACK_CLR);
+ /* indicate disconnect w/tones to upper layer */
+ disconnect_call(ss5, OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR);
+ }
+ break;
+ /* outgoing call receives answer */
+ case SS5_STATE_SEND_ACK_ANS:
+ if (digit != 'A') {
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "'answer' is ceased in '%s' state, call is established.\n", ss5_state_names[ss5->state]);
+ /* stop timer */
+ timer_stop(&ss5->timer);
+ /* cease */
+ set_tone(&ss5->dsp, 0, 0);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_OUT_ACTIVE);
+ }
+ break;
+ /* outgoing call receives busy-flash */
+ case SS5_STATE_SEND_ACK_BUS:
+ case SS5_STATE_SEND_DIGITS:
+ if (digit != 'B') {
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "'busy-flash' is ceased in '%s' state, call is disconnected.\n", ss5_state_names[ss5->state]);
+ /* stop timer */
+ timer_stop(&ss5->timer);
+ /* unset dial string, if set */
+ set_dial_string(&ss5->dsp, "");
+ /* cease */
+ set_tone(&ss5->dsp, 0, 0);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_OUT_INACTIVE);
+ }
+ break;
+ /* outgoing call receives clear-back */
+ case SS5_STATE_SEND_ACK_CLR:
+ if (digit != 'B') {
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "'clear-back' is ceased in '%s' state, call is disconnected.\n", ss5_state_names[ss5->state]);
+ /* stop timer */
+ timer_stop(&ss5->timer);
+ /* cease */
+ set_tone(&ss5->dsp, 0, 0);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_OUT_INACTIVE);
+ }
+ break;
+ /* outgoing call sends clear forward */
+ case SS5_STATE_SEND_CLR_FWD:
+ if (digit == 'C') {
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "Received 'release-guard' signal in '%s' state, ceasing 'clear-forward' signal.\n", ss5_state_names[ss5->state]);
+ /* cease */
+ set_tone(&ss5->dsp, 0, 0);
+ /* stop timer */
+ timer_stop(&ss5->timer);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_RECV_RELEASE);
+ }
+ break;
+ /* outgoing call receives release guard */
+ case SS5_STATE_RECV_RELEASE:
+ if (digit != 'C') {
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "'release-guard' is ceased in '%s' state, going idle.\n", ss5_state_names[ss5->state]);
+ /* state idle */
+ ss5_new_state(ss5, SS5_STATE_IDLE);
+ /* reset instance */
+ link_reset(ss5);
+ }
+ break;
+ /* incoming call receives seize */
+ case SS5_STATE_IDLE:
+ if (digit == 'A') {
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "Received 'seize' signal in '%s' state, sending 'proceed-to-send'.\n", ss5_state_names[ss5->state]);
+ /* set recognition time to normal */
+ set_sig_detect_duration(&ss5->dsp, SIGN_RECOGNITION_NORMAL, SIGN_RECOGNITION_NORMAL);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_SEND_PROCEED);
+ /* send proceed-to-send */
+ set_tone(&ss5->dsp, 'B', MAX_PROCEED_TO_SEND);
+ }
+ break;
+ /* incoming call sends proceed-to-send */
+ case SS5_STATE_SEND_PROCEED:
+ if (digit != 'A') {
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "'seize' is ceased in '%s' state, receiving digits.\n", ss5_state_names[ss5->state]);
+ /* cease */
+ set_tone(&ss5->dsp, 0, 0);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_RECV_DIGIT);
+ /* start timer */
+ timer_start(&ss5->timer, TO_DIALING);
+ }
+ break;
+ /* incoming call receives digits */
+ case SS5_STATE_RECV_DIGIT:
+ if (!(digit >= '0' && digit <= '9') && !(digit >= 'a' && digit <= 'c')) {
+ break;
+ }
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "Digit '%c' is ceased in '%s' state.\n", digit, ss5_state_names[ss5->state]);
+ /* add digit */
+ i = strlen(ss5->dialing);
+ if (i + 1 == sizeof(ss5->dialing))
+ break;
+ ss5->dialing[i++] = digit;
+ ss5->dialing[i] = '\0';
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_RECV_SPACE);
+ /* restart timer */
+ timer_start(&ss5->timer, TO_DIALING);
+ break;
+ case SS5_STATE_RECV_SPACE:
+ if (digit != ' ')
+ break;
+ /* check for end of dialing */
+ i = strlen(ss5->dialing) - 1;
+ if (ss5->dialing[i] == 'c') {
+ osmo_cc_msg_t *msg;
+ uint8_t type;
+ char dialing[sizeof(ss5->dialing)];
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "Dialing '%s' is complete, sending setup message towards call control.\n", ss5->dialing);
+ /* stop timer */
+ timer_stop(&ss5->timer);
+ /* check dial string */
+ rc = parse_dial_string(&type, dialing, sizeof(dialing), ss5->dialing);
+ if (rc < 0) {
+ /* send clear-back */
+ set_tone(&ss5->dsp, 'B', MAX_CLEAR_BACK);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_SEND_CLR_BAK);
+ break;
+ }
+ /* setup message */
+ msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_IND);
+ /* network type */
+ osmo_cc_add_ie_calling_network(msg, OSMO_CC_NETWORK_SS5_NONE, "");
+ /* called number */
+ osmo_cc_add_ie_called(msg, type, OSMO_CC_PLAN_TELEPHONY, dialing);
+ /* bearer capability */
+ osmo_cc_add_ie_bearer(msg, OSMO_CC_CODING_ITU_T, OSMO_CC_CAPABILITY_AUDIO, OSMO_CC_MODE_CIRCUIT);
+ /* sdp offer */
+ ss5->cc_session = osmo_cc_helper_audio_offer(ss5, codecs, down_audio, msg, 1);
+ if (!ss5->cc_session) {
+ osmo_cc_free_msg(msg);
+ PDEBUG_CHAN(DSS5, DEBUG_NOTICE, "Failed to offer audio, sending 'clear-back'.\n");
+ /* send clear-back */
+ set_tone(&ss5->dsp, 'B', MAX_CLEAR_BACK);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_SEND_CLR_BAK);
+ break;
+ }
+ /* create new call */
+ osmo_cc_call_t *cc_call = osmo_cc_call_new(&ss5->ss5_ep->cc_ep);
+ ss5->cc_callref = cc_call->callref;
+ /* send message to CC */
+ osmo_cc_ll_msg(&ss5->ss5_ep->cc_ep, ss5->cc_callref, msg);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_IN_INACTIVE);
+ break;
+ }
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_RECV_DIGIT);
+ break;
+ /* incoming call sends answer */
+ case SS5_STATE_SEND_ANSWER:
+ if (digit == 'A') {
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "Received 'acknowledge' to 'answer' in '%s' state, call is now active.\n", ss5_state_names[ss5->state]);
+ /* stop timer */
+ timer_stop(&ss5->timer);
+ /* cease */
+ set_tone(&ss5->dsp, 0, 0);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_IN_ACTIVE);
+ }
+ break;
+ /* incoming call sends busy-flash */
+ case SS5_STATE_SEND_BUSY:
+ if (digit == 'A') {
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "Received 'acknowledge' to 'busy-flash' in '%s' state, call is now inactive.\n", ss5_state_names[ss5->state]);
+ /* stop timer */
+ timer_stop(&ss5->timer);
+ /* cease */
+ set_tone(&ss5->dsp, 0, 0);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_IN_INACTIVE);
+ }
+ break;
+ /* incoming call sends clear-back */
+ case SS5_STATE_SEND_CLR_BAK:
+ if (digit == 'A') {
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "Received 'acknowledge' to 'clear-back' in '%s' state, call is now inactive.\n", ss5_state_names[ss5->state]);
+ /* stop timer */
+ timer_stop(&ss5->timer);
+ /* cease */
+ set_tone(&ss5->dsp, 0, 0);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_IN_INACTIVE);
+ }
+ break;
+ default:
+ PDEBUG_CHAN(DSS5, DEBUG_ERROR, "Received digit '%c' in '%s' state is not handled, please fix!\n", digit, ss5_state_names[ss5->state]);
+ }
+}
+
+/* dialing was completed */
+void dialing_complete(void *priv)
+{
+ ss5_t *ss5 = priv;
+
+ if (ss5->state == SS5_STATE_SEND_DIGITS) {
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "Dialing is complete in '%s' state, waiting for remote party to answer.\n", ss5_state_names[ss5->state]);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_OUT_INACTIVE);
+ }
+
+ /* indicate alerting */
+ alert_call(ss5);
+}
+
+/* timeouts */
+static void ss5_timeout(struct timer *timer)
+{
+ ss5_t *ss5 = timer->priv;
+
+ switch (ss5->state) {
+ case SS5_STATE_RECV_DIGIT:
+ case SS5_STATE_RECV_SPACE:
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "Received timeout in '%s' state, sending 'clear-back'.\n", ss5_state_names[ss5->state]);
+ /* send clear-back */
+ set_tone(&ss5->dsp, 'B', MAX_CLEAR_BACK);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_SEND_CLR_BAK);
+ break;
+ case SS5_STATE_DOUBLE_SEIZE:
+ /* cease */
+ set_tone(&ss5->dsp, 0, 0);
+ /* state idle */
+ ss5_new_state(ss5, SS5_STATE_IDLE);
+ /* send release indication towards CC */
+ release_call(ss5, OSMO_CC_ISDN_CAUSE_NO_CIRCUIT_CHAN);
+ /* reset inst */
+ link_reset(ss5);
+ break;
+ case SS5_STATE_SEND_REL_WAIT:
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "'release-guard' timer expired, going idle.\n");
+ /* cease */
+ set_tone(&ss5->dsp, 0, 0);
+ /* state idle */
+ ss5_new_state(ss5, SS5_STATE_IDLE);
+ /* reset instance */
+ link_reset(ss5);
+ break;
+ default:
+ ;
+ }
+}
+
+/* message from call contol */
+void cc_message(osmo_cc_endpoint_t *ep, uint32_t callref, osmo_cc_msg_t *msg)
+{
+ ss5_endpoint_t *ss5_ep = ep->priv;
+ ss5_t *ss5;
+ osmo_cc_msg_t *new_msg;
+ uint8_t type, plan, present, screen;
+ char dialing[64];
+ const char *sdp;
+ int rc;
+
+ /* hunt for callref */
+ ss5 = ss5_ep->link_list;
+ while (ss5) {
+ if (ss5->cc_callref == callref)
+ break;
+ ss5 = ss5->next;
+ }
+
+ /* process SETUP */
+ if (!ss5) {
+ if (msg->type != OSMO_CC_MSG_SETUP_REQ) {
+ PDEBUG(DSS5, DEBUG_ERROR, "received message without ss5 instance, please fix!\n");
+ goto done;
+ }
+ /* hunt free ss5 instance */
+ ss5 = ss5_ep->link_list;
+ while (ss5) {
+ if (ss5->state == SS5_STATE_IDLE)
+ break;
+ ss5 = ss5->next;
+ }
+ if (!ss5) {
+ PDEBUG(DSS5, DEBUG_NOTICE, "No free ss5 instance, rejecting.\n");
+ reject_call(ss5_ep, callref, OSMO_CC_ISDN_CAUSE_NO_CIRCUIT_CHAN);
+ goto done;
+ }
+ /* link with cc */
+ ss5->cc_callref = callref;
+ }
+
+ switch (msg->type) {
+ case OSMO_CC_MSG_SETUP_REQ: /* dial-out command received from epoint */
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "Outgoing call in '%s' state, sending 'seize'.\n", ss5_state_names[ss5->state]);
+ /* caller id */
+ rc = osmo_cc_get_ie_calling(msg, 0, &type, &plan, &present, &screen, ss5->callerid, sizeof(ss5->callerid));
+ /* called number */
+ rc = osmo_cc_get_ie_called(msg, 0, &type, &plan, dialing, sizeof(dialing));
+ if (rc < 0 || !dialing[0]) {
+ PDEBUG_CHAN(DSS5, DEBUG_NOTICE, "No number given, call is rejected!\n");
+ inv_nr:
+ reject_call(ss5->ss5_ep, ss5->cc_callref, OSMO_CC_ISDN_CAUSE_INV_NR_FORMAT);
+ link_reset(ss5);
+ goto done;
+ }
+ rc = generate_dial_string(type, dialing, ss5->dialing, sizeof(ss5->dialing));
+ if (rc < 0)
+ goto inv_nr;
+ /* sdp accept */
+ sdp = osmo_cc_helper_audio_accept(ss5, codecs, down_audio, msg, &ss5->cc_session, &ss5->codec, 0);
+ if (!sdp) {
+ reject_call(ss5->ss5_ep, ss5->cc_callref, OSMO_CC_ISDN_CAUSE_RESOURCE_UNAVAIL);
+ link_reset(ss5);
+ goto done;
+ }
+ /* proceed */
+ proceed_call(ss5, sdp);
+ /* send seize */
+ set_tone(&ss5->dsp, 'A', MAX_SEIZE);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_SEND_SEIZE);
+ break;
+ case OSMO_CC_MSG_SETUP_ACK_REQ: /* more information is needed */
+ case OSMO_CC_MSG_PROC_REQ: /* call of endpoint is proceeding */
+ case OSMO_CC_MSG_ALERT_REQ: /* call of endpoint is ringing */
+ case OSMO_CC_MSG_PROGRESS_REQ: /* progress */
+ rc = osmo_cc_helper_audio_negotiate(msg, &ss5->cc_session, &ss5->codec);
+ if (rc < 0) {
+ codec_failed:
+ PDEBUG_CHAN(DSS5, DEBUG_NOTICE, "Releasing, because codec negotiation failed.\n");
+ /* send busy-flash */
+ set_tone(&ss5->dsp, 'B', MAX_BUSY_FLASH);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_SEND_BUSY);
+ /* release call */
+ release_call(ss5, OSMO_CC_ISDN_CAUSE_RESOURCE_UNAVAIL);
+ /* reset inst */
+ link_reset(ss5);
+ goto done;
+ }
+ break;
+ case OSMO_CC_MSG_SETUP_RSP: /* call of endpoint is connected */
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "Incoming call has answered in '%s' state, sending 'answer'.\n", ss5_state_names[ss5->state]);
+ rc = osmo_cc_helper_audio_negotiate(msg, &ss5->cc_session, &ss5->codec);
+ if (rc < 0)
+ goto codec_failed;
+ /* setup acknowledge */
+ setup_ack_call(ss5);
+ /* not in right state, which should never happen anyway */
+ if (ss5->state != SS5_STATE_IN_INACTIVE)
+ break;
+ /* send answer */
+ set_tone(&ss5->dsp, 'A', MAX_ANSWER);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_SEND_ANSWER);
+ break;
+ case OSMO_CC_MSG_REJ_REQ: /* call has been rejected */
+ case OSMO_CC_MSG_REL_REQ: /* call has been released */
+ case OSMO_CC_MSG_DISC_REQ: /* call has been disconnected */
+ rc = osmo_cc_helper_audio_negotiate(msg, &ss5->cc_session, &ss5->codec);
+ if (rc < 0)
+ goto codec_failed;
+ /* right state */
+ if (ss5->state == SS5_STATE_IN_INACTIVE) {
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "Incoming call has disconnected in '%s' state, sending 'busy-flash'.\n", ss5_state_names[ss5->state]);
+ /* send busy-flash */
+ set_tone(&ss5->dsp, 'B', MAX_BUSY_FLASH);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_SEND_BUSY);
+ } else
+ if (ss5->state == SS5_STATE_IN_ACTIVE) {
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "Incoming call has disconnected in '%s' state, sending 'clear-back'.\n", ss5_state_names[ss5->state]);
+ /* send clear-back */
+ set_tone(&ss5->dsp, 'B', MAX_CLEAR_BACK);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_SEND_CLR_BAK);
+ } else {
+ PDEBUG_CHAN(DSS5, DEBUG_INFO, "Outgoing call has disconnected in '%s' state, sending 'clear-forward'.\n", ss5_state_names[ss5->state]);
+ /* send clear-forward */
+ set_tone(&ss5->dsp, 'C', MAX_CLEAR_FORWARD);
+ /* change state */
+ ss5_new_state(ss5, SS5_STATE_SEND_CLR_FWD);
+ if (msg->type == OSMO_CC_MSG_DISC_REQ) {
+ /* clone osmo-cc message to preserve cause */
+ new_msg = osmo_cc_clone_msg(msg);
+ new_msg->type = OSMO_CC_MSG_REL_IND;
+ /* send message to osmo-cc */
+ osmo_cc_ll_msg(&ss5->ss5_ep->cc_ep, ss5->cc_callref, new_msg);
+ /* reset */
+ link_reset(ss5);
+ break;
+ }
+ }
+ /* on release, we confirm */
+ if (msg->type == OSMO_CC_MSG_REL_REQ) {
+ /* clone osmo-cc message to preserve cause */
+ new_msg = osmo_cc_clone_msg(msg);
+ new_msg->type = OSMO_CC_MSG_REL_CNF;
+ /* send message to osmo-cc */
+ osmo_cc_ll_msg(&ss5->ss5_ep->cc_ep, ss5->cc_callref, new_msg);
+ }
+ /* on reject/release we reset/unlink call */
+ if (msg->type != OSMO_CC_MSG_DISC_REQ)
+ link_reset(ss5);
+ break;
+ }
+
+done:
+ osmo_cc_free_msg(msg);
+}
+