/* SS5 process * * (C) 2020 by Andreas Eversberg * All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #define CHAN ss5->name #include #include #include #include #include #include #include #include #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); }