/* AMPS protocol handling * * (C) 2016 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 . */ /* * Notes on frames and scheduling: * * If the amps->dsp_mode is set to transmit frames, fsk_frame() at dsp.c code * requests frames, modulates them and forwards them to sound device. Whenever * the dsp.c code requests frame (if no frame exists or frame had been sent), * it calls amps_encode_frame_focc() or amps_encode_frame_fvc() of frame.c. * There it generates a sequence of frames (message train). If no sequence is * transmitted or a new sequence starts, amps_tx_frame_focc() or * amps_tx_frame_fvc() of amps.c is called. There it sets message data and * other states according to the current trans->state. * * If a frame is received by dsp.c code, amps_decode_frame() at frame.c is * called. There the bits are decoded and messages are assembled from multiple * frames. Then amps_rx_recc() at amps.c is called, so the received messages * are processed. */ #define CHAN amps->sender.kanal #include #include #include #include #include #include "../common/debug.h" #include "../common/timer.h" #include "../common/call.h" #include "../common/cause.h" #include "amps.h" #include "dsp.h" #include "frame.h" /* Uncomment this to test SAT via loopback */ //#define DEBUG_VC #define SAT_TO1 5.0 /* 5 sec to detect after setup */ #define SAT_TO2 5.0 /* 5 sec lost until abort (specs say 5) */ #define PAGE_TRIES 2 /* how many times to page the phone */ #define PAGE_TO1 8.0 /* max time to wait for paging reply */ #define PAGE_TO2 4.0 /* max time to wait for last paging reply */ #define ALERT_TO 60.0 /* max time to wait for answer */ #define RELEASE_TIMER 5.0 /* max time to send release messages */ /* Call reference for calls from mobile station to network This offset of 0x400000000 is required for MNCC interface. */ static int new_callref = 0x40000000; /* Convert channel number to frequency number of base station. Set 'uplink' to 1 to get frequency of mobile station. */ double amps_channel2freq(int channel, int uplink) { double freq; /* 832 channels, 990 not used, see TIA/EIA-136-110 */ if (channel < 1 || channel > 1023 || (channel > 799 && channel < 991)) return 0; if (channel >= 990) // 990 is not used channel -= 1023; freq = 870.030 + (channel - 1) * 0.030; if (uplink) freq -= 45.000; return freq * 1e6; } enum amps_chan_type amps_channel2type(int channel) { if (channel >= 313 && channel <= 354) return CHAN_TYPE_CC; return CHAN_TYPE_VC; } char amps_channel2band(int channel) { if (channel >= 334 && channel <= 666) return 'B'; if (channel >= 717 && channel <= 799) return 'B'; return 'A'; } static inline int digit2binary(int digit) { if (digit == '0') return 10; return digit - '0'; } static inline int binary2digit(int binary) { if (binary == 10) return '0'; return binary + '0'; } /* convert NPA-NXX-XXXX to MIN1 and MIN2 * NPA = numbering plan area (MIN2) * NXX = mobile exchange code * XXXX = telephone number within the exchange */ void amps_number2min(const char *number, uint32_t *min1, uint16_t *min2) { int nlen = strlen(number); int i; if (nlen != 10) { fprintf(stderr, "illegal lenght %d. Must be 10, aborting!", nlen); abort(); } for (i = 0; i < nlen; i++) { if (number[i] < '0' || number[i] > '9') { fprintf(stderr, "illegal number %s. Must consists only of digits 0..9, aborting!", number); abort(); } } /* MIN2 */ if (nlen == 10) { *min2 = digit2binary(number[0]) * 100 + digit2binary(number[1]) * 10 + digit2binary(number[2]) - 111; number += 3; nlen -= 3; } /* MIN1 */ *min1 = ((uint32_t)(digit2binary(number[0]) * 100 + digit2binary(number[1]) * 10 + digit2binary(number[2]) - 111)) << 14; *min1 |= digit2binary(number[3]) << 10; *min1 |= digit2binary(number[4]) * 100 + digit2binary(number[5]) * 10 + digit2binary(number[6]) - 111; } /* convert MIN1 and MIN2 to NPA-NXX-XXXX */ const char *amps_min22number(uint16_t min2) { static char number[4]; /* MIN2 */ if (min2 > 999) strcpy(number, "???"); else { number[0] = binary2digit((min2 / 100) + 1); number[1] = binary2digit(((min2 / 10) % 10) + 1); number[2] = binary2digit((min2 % 10) + 1); } number[3] = '\0'; return number; } const char *amps_min12number(uint32_t min1) { static char number[8]; /* MIN1 */ if ((min1 >> 14) > 999) strcpy(number, "???"); else { number[0] = binary2digit(((min1 >> 14) / 100) + 1); number[1] = binary2digit((((min1 >> 14) / 10) % 10) + 1); number[2] = binary2digit(((min1 >> 14) % 10) + 1); } if (((min1 >> 10) & 0xf) < 1 || ((min1 >> 10) & 0xf) > 10) number[3] = '?'; else number[3] = binary2digit((min1 >> 10) & 0xf); if ((min1 & 0x3ff) > 999) strcpy(number + 4, "???"); else { number[4] = binary2digit(((min1 & 0x3ff) / 100) + 1); number[5] = binary2digit((((min1 & 0x3ff) / 10) % 10) + 1); number[6] = binary2digit(((min1 & 0x3ff) % 10) + 1); } number[7] = '\0'; return number; } const char *amps_min2number(uint32_t min1, uint16_t min2) { static char number[11]; sprintf(number, "%s%s", amps_min22number(min2), amps_min12number(min1)); return number; } /* encode ESN */ void amps_encode_esn(uint32_t *esn, uint8_t mfr, uint32_t serial) { *esn = (((uint32_t)mfr) << 24) | (serial & 0xffffff); } /* decode ESN */ void amps_decode_esn(uint32_t esn, uint8_t *mfr, uint32_t *serial) { *mfr = esn >> 24; *serial = esn & 0xffffff; } const char *amps_scm(uint8_t scm) { static char text[64]; sprintf(text, "Class %d / %sontinuous / %d MHz", (scm >> 2) + (scm & 3) + 1, (scm & 4) ? "Disc" : "C", (scm & 8) ? 25 : 20); return text; } const char *amps_state_name(enum amps_state state) { static char invalid[16]; switch (state) { case STATE_NULL: return "(NULL)"; case STATE_IDLE: return "IDLE"; case STATE_BUSY: return "BUSY"; } sprintf(invalid, "invalid(%d)", state); return invalid; } static void amps_new_state(amps_t *amps, enum amps_state new_state) { if (amps->state == new_state) return; PDEBUG_CHAN(DAMPS, DEBUG_DEBUG, "State change: %s -> %s\n", amps_state_name(amps->state), amps_state_name(new_state)); amps->state = new_state; } static struct amps_channels { enum amps_chan_type chan_type; const char *short_name; const char *long_name; } amps_channels[] = { { CHAN_TYPE_CC, "CC", "control channel" }, { CHAN_TYPE_CC, "PC", "paging channel" }, { CHAN_TYPE_CC_PC, "CC/PC","combined control & paging channel" }, { CHAN_TYPE_VC, "VC", "voice channel" }, { CHAN_TYPE_CC_PC_VC, "CC/PC/VC","combined control & paging & voice channel" }, { 0, NULL, NULL } }; void amps_channel_list(void) { int i; printf("Type\t\tDescription\n"); printf("------------------------------------------------------------------------\n"); for (i = 0; amps_channels[i].long_name; i++) printf("%s%s\t%s\n", amps_channels[i].short_name, (strlen(amps_channels[i].short_name) >= 8) ? "" : "\t", amps_channels[i].long_name); } int amps_channel_by_short_name(const char *short_name) { int i; for (i = 0; amps_channels[i].short_name; i++) { if (!strcasecmp(amps_channels[i].short_name, short_name)) { PDEBUG(DAMPS, DEBUG_INFO, "Selecting channel '%s' = %s\n", amps_channels[i].short_name, amps_channels[i].long_name); return amps_channels[i].chan_type; } } return -1; } const char *chan_type_short_name(enum amps_chan_type chan_type) { int i; for (i = 0; amps_channels[i].short_name; i++) { if (amps_channels[i].chan_type == chan_type) return amps_channels[i].short_name; } return "invalid"; } const char *chan_type_long_name(enum amps_chan_type chan_type) { int i; for (i = 0; amps_channels[i].long_name; i++) { if (amps_channels[i].chan_type == chan_type) return amps_channels[i].long_name; } return "invalid"; } static amps_t *search_channel(int channel) { sender_t *sender; amps_t *amps; for (sender = sender_head; sender; sender = sender->next) { if (sender->kanal != channel) continue; amps = (amps_t *) sender; if (amps->state == STATE_IDLE) return amps; } return NULL; } static amps_t *search_free_vc(void) { sender_t *sender; amps_t *amps, *cc_pc_vc = NULL; for (sender = sender_head; sender; sender = sender->next) { amps = (amps_t *) sender; if (amps->state != STATE_IDLE) continue; /* return first free voice channel */ if (amps->chan_type == CHAN_TYPE_VC) return amps; /* remember combined voice/control/paging channel as second alternative */ if (amps->chan_type == CHAN_TYPE_CC_PC_VC) cc_pc_vc = amps; } return cc_pc_vc; } static amps_t *search_pc(void) { sender_t *sender; amps_t *amps; for (sender = sender_head; sender; sender = sender->next) { amps = (amps_t *) sender; if (amps->state != STATE_IDLE) continue; if (amps->chan_type == CHAN_TYPE_PC) return amps; if (amps->chan_type == CHAN_TYPE_CC_PC) return amps; if (amps->chan_type == CHAN_TYPE_CC_PC_VC) return amps; } return NULL; } static void amps_go_idle(amps_t *amps); /* Create transceiver instance and link to a list. */ int amps_create(int channel, enum amps_chan_type chan_type, const char *audiodev, int samplerate, double rx_gain, int pre_emphasis, int de_emphasis, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, amps_si *si, uint16_t sid, uint8_t sat, int polarity, int tolerant, int loopback) { sender_t *sender; amps_t *amps; int rc; enum amps_chan_type ct; char band; /* check for channel number */ if (channel < 1 || channel > 666) { PDEBUG(DAMPS, DEBUG_ERROR, "Channel number %d invalid.\n", channel); return -EINVAL; } /* no paging channel (without control channel) support */ if (chan_type == CHAN_TYPE_PC) { PDEBUG(DAMPS, DEBUG_ERROR, "Dedicated paging channel currently not supported. Please select CC/PC or CC/PC/VC instead.\n"); return -EINVAL; } /* check if there is only one paging channel */ if (chan_type == CHAN_TYPE_PC || chan_type == CHAN_TYPE_CC_PC || chan_type == CHAN_TYPE_CC_PC_VC) { for (sender = sender_head; sender; sender = sender->next) { amps = (amps_t *)sender; if (amps->chan_type == CHAN_TYPE_PC || amps->chan_type == CHAN_TYPE_CC_PC || amps->chan_type == CHAN_TYPE_CC_PC_VC) { PDEBUG(DAMPS, DEBUG_ERROR, "Only one paging channel is currently supported. Please check your channel types.\n"); return -EINVAL; } } } /* check if channel type matches channel number */ ct = amps_channel2type(channel); if (ct == CHAN_TYPE_CC && chan_type != CHAN_TYPE_PC && chan_type != CHAN_TYPE_CC_PC && chan_type != CHAN_TYPE_CC_PC_VC) { PDEBUG(DAMPS, DEBUG_NOTICE, "Channel number %d belongs to a control channel, but your channel type '%s' requires to be on a voice channel number. Some phone may reject this, but all my phones don't.\n", channel, chan_type_long_name(chan_type)); } if (ct == CHAN_TYPE_VC && chan_type != CHAN_TYPE_VC) { PDEBUG(DAMPS, DEBUG_ERROR, "Channel number %d belongs to a voice channel, but your channel type '%s' requires to be on a control channel number. Please use correct channel.\n", channel, chan_type_long_name(chan_type)); return -EINVAL; } /* check if sid machtes channel band */ band = amps_channel2band(channel); if (band == 'A' && (sid & 1) == 0 && chan_type != CHAN_TYPE_VC) { PDEBUG(DAMPS, DEBUG_ERROR, "Channel number %d belongs to system A, but your SID %d is even and belongs to system B. Please give odd SID.\n", channel, sid); return -EINVAL; } if (band == 'B' && (sid & 1) == 1 && chan_type != CHAN_TYPE_VC) { PDEBUG(DAMPS, DEBUG_ERROR, "Channel number %d belongs to system B, but your SID %d is odd and belongs to system A. Please give even SID.\n", channel, sid); return -EINVAL; } /* check if we use combined voice channel hack */ if (chan_type == CHAN_TYPE_CC_PC_VC) { PDEBUG(DAMPS, DEBUG_NOTICE, "You selected '%s'. This is a hack, but the only way to use control channel and voice channel on one transceiver. Some phones may reject this, but all my phones don't.\n", chan_type_long_name(chan_type)); } amps = calloc(1, sizeof(amps_t)); if (!amps) { PDEBUG(DAMPS, DEBUG_ERROR, "No memory!\n"); return -ENOMEM; } PDEBUG(DAMPS, DEBUG_DEBUG, "Creating 'AMPS' instance for channel = %d (sample rate %d).\n", channel, samplerate); /* init general part of transceiver */ rc = sender_create(&s->sender, channel, amps_channel2freq(channel, 0), amps_channel2freq(channel, 1), audiodev, samplerate, rx_gain, 0, 0, write_rx_wave, write_tx_wave, read_rx_wave, loopback, 0, PAGING_SIGNAL_NONE); if (rc < 0) { PDEBUG(DAMPS, DEBUG_ERROR, "Failed to init transceiver process!\n"); goto error; } /* init audio processing */ rc = dsp_init_sender(amps, (de_emphasis == 0), tolerant); if (rc < 0) { PDEBUG(DAMPS, DEBUG_ERROR, "Failed to init audio processing!\n"); goto error; } if (polarity < 0) amps->flip_polarity = 1; amps->chan_type = chan_type; memcpy(&s->si, si, sizeof(amps->si)); amps->sat = sat; amps->pre_emphasis = pre_emphasis; amps->de_emphasis = de_emphasis; rc = init_emphasis(&s->estate, samplerate, CUT_OFF_EMPHASIS_DEFAULT); if (rc < 0) goto error; /* go into idle state */ amps_go_idle(amps); #ifdef DEBUG_VC uint32_t min1; uint16_t min2; amps_number2min("1234567890", &min1, &min2); transaction_t __attribute__((__unused__)) *trans = create_transaction(amps, TRANS_CALL_ASSIGN, min1, min2, 0, 0, 0, amps->sender.kanal); amps_new_state(amps, STATE_BUSY); #endif return 0; error: amps_destroy(&s->sender); return rc; } /* Destroy transceiver instance and unlink from list. */ void amps_destroy(sender_t *sender) { amps_t *amps = (amps_t *) sender; transaction_t *trans; PDEBUG(DAMPS, DEBUG_DEBUG, "Destroying 'AMPS' instance for channel = %d.\n", sender->kanal); while ((trans = amps->trans_list)) { const char *number = amps_min2number(trans->min1, trans->min2); PDEBUG(DAMPS, DEBUG_NOTICE, "Removing pending transaction for subscriber '%s'\n", number); destroy_transaction(trans); } dsp_cleanup_sender(amps); sender_destroy(&s->sender); free(amps); } /* Abort connection towards mobile station by sending FOCC/FVC pattern. */ static void amps_go_idle(amps_t *amps) { int frame_length; if (amps->state == STATE_IDLE) return; if (amps->trans_list) { PDEBUG(DAMPS, DEBUG_ERROR, "Releasing but still having transaction, please fix!\n"); if (amps->trans_list->callref) call_in_release(amps->trans_list->callref, CAUSE_NORMAL); destroy_transaction(amps->trans_list); } amps_new_state(amps, STATE_IDLE); if (amps->chan_type != CHAN_TYPE_VC) { PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Entering IDLE state, sending Overhead/Filler frames on %s.\n", chan_type_long_name(amps->chan_type)); if (amps->sender.loopback) frame_length = 441; /* bits after sync (FOCC) */ else frame_length = 247; /* bits after sync (RECC) */ amps_set_dsp_mode(amps, DSP_MODE_FRAME_RX_FRAME_TX, frame_length); } else { PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Entering IDLE state, sending test tone on %s.\n", chan_type_long_name(amps->chan_type)); amps_set_dsp_mode(amps, DSP_MODE_OFF, 0); } } /* Abort connection towards mobile station by sending FOCC/FVC pattern. */ static void amps_release(transaction_t *trans, uint8_t cause) { amps_t *amps = trans->amps; timer_stop(&trans->timer); timer_start(&trans->timer, RELEASE_TIMER); trans_new_state(trans, TRANS_CALL_RELEASE); trans->chan = 0; trans->msg_type = 0; trans->ordq = 0; trans->order = 3; /* release towards call control */ if (trans->callref) { call_in_release(trans->callref, cause); trans->callref = 0; } /* change DSP mode to transmit release */ if (amps->dsp_mode == DSP_MODE_AUDIO_RX_AUDIO_TX) amps_set_dsp_mode(amps, DSP_MODE_AUDIO_RX_FRAME_TX, 0); } /* * receive signaling */ void amps_rx_signaling_tone(amps_t *amps, int tone, double quality) { transaction_t *trans = amps->trans_list; if (trans == NULL) { PDEBUG_CHAN(DAMPS, DEBUG_ERROR, "Signaling Tone without transaction, please fix!\n"); return; } if (tone) PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Detected Signaling Tone with quality=%.0f.\n", quality * 100.0); else PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Lost Signaling Tone signal\n"); switch (trans->state) { case TRANS_CALL: case TRANS_CALL_RELEASE: case TRANS_CALL_RELEASE_SEND: if (!tone) break; timer_stop(&trans->timer); destroy_transaction(trans); if (trans->callref) { call_in_release(trans->callref, CAUSE_NORMAL); trans->callref = 0; } amps_go_idle(amps); break; case TRANS_CALL_MT_ALERT: if (tone) { timer_stop(&trans->timer); call_in_alerting(trans->callref); amps_set_dsp_mode(amps, DSP_MODE_AUDIO_RX_AUDIO_TX, 0); trans_new_state(trans, TRANS_CALL_MT_ALERT_SEND); timer_start(&trans->timer, ALERT_TO); } break; case TRANS_CALL_MT_ALERT_SEND: if (!tone) { timer_stop(&trans->timer); if (!trans->sat_detected) timer_start(&trans->timer, SAT_TO1); call_in_answer(trans->callref, amps_min2number(trans->min1, trans->min2)); trans_new_state(trans, TRANS_CALL); } break; default: PDEBUG_CHAN(DAMPS, DEBUG_ERROR, "Signaling Tone without active call, please fix!\n"); } } void amps_rx_sat(amps_t *amps, int tone, double quality) { transaction_t *trans = amps->trans_list; if (trans == NULL) { PDEBUG_CHAN(DAMPS, DEBUG_ERROR, "SAT signal without transaction, please fix!\n"); return; } /* irgnoring SAT loss on release */ if (trans->state == TRANS_CALL_RELEASE || trans->state == TRANS_CALL_RELEASE_SEND) return; if (trans->state != TRANS_CALL && trans->state != TRANS_CALL_MT_ALERT && trans->state != TRANS_CALL_MT_ALERT_SEND) { PDEBUG_CHAN(DAMPS, DEBUG_ERROR, "SAT signal without active call, please fix!\n"); return; } if (tone) { PDEBUG(DAMPS, DEBUG_INFO, "Detected SAT signal with quality=%.0f.\n", quality * 100.0); trans->sat_detected = 1; } else { PDEBUG(DAMPS, DEBUG_INFO, "Lost SAT signal\n"); trans->sat_detected = 0; } if (amps->sender.loopback) return; /* no SAT during alerting */ if (trans->state == TRANS_CALL_MT_ALERT || trans->state == TRANS_CALL_MT_ALERT_SEND) return; if (tone) { timer_stop(&trans->timer); } else { timer_start(&trans->timer, SAT_TO2); } } static void timeout_sat(amps_t *amps, double duration) { if (!amps->trans_list) { PDEBUG_CHAN(DAMPS, DEBUG_ERROR, "SAT timeout, but no transaction, please fix!\n"); return; } if (duration == SAT_TO1) PDEBUG_CHAN(DAMPS, DEBUG_NOTICE, "Timeout after %.0f seconds not receiving SAT signal.\n", duration); else PDEBUG_CHAN(DAMPS, DEBUG_NOTICE, "Timeout after %.0f seconds loosing SAT signal.\n", duration); PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Release call towards network.\n"); amps_release(amps->trans_list, CAUSE_TEMPFAIL); } /* receive message from phone on RECC */ void amps_rx_recc(amps_t *amps, uint8_t scm, uint32_t esn, uint32_t min1, uint16_t min2, uint8_t msg_type, uint8_t ordq, uint8_t order, const char *dialing) { amps_t *vc; transaction_t *trans; const char *callerid = amps_min2number(min1, min2); /* check if we are busy, so we ignore all signaling */ if (amps->state == STATE_BUSY) { PDEBUG_CHAN(DAMPS, DEBUG_NOTICE, "Ignoring RECC messages from phone while using this channel for voice.\n"); return; } if (order == 13 && (ordq == 0 || ordq == 1 || ordq == 2 || ordq == 3) && msg_type == 0) { PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Registration %s (ESN = %08x, %s)\n", callerid, esn, amps_scm(scm)); trans = create_transaction(amps, TRANS_REGISTER_ACK, min1, min2, msg_type, ordq, order, 0); if (!trans) { PDEBUG(DAMPS, DEBUG_ERROR, "Failed to create transaction\n"); return; } } else if (order == 13 && ordq == 3 && msg_type == 1) { PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Registration - Power Down %s (ESN = %08x, %s)\n", callerid, esn, amps_scm(scm)); trans = create_transaction(amps, TRANS_REGISTER_ACK, min1, min2, msg_type, ordq, order, 0); if (!trans) { PDEBUG(DAMPS, DEBUG_ERROR, "Failed to create transaction\n"); return; } } else if (order == 0 && ordq == 0 && msg_type == 0) { if (!dialing) PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Paging reply %s (ESN = %08x, %s)\n", callerid, esn, amps_scm(scm)); else PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Call %s -> %s (ESN = %08x, %s)\n", callerid, dialing, esn, amps_scm(scm)); trans = search_transaction_number(amps, min1, min2); if (!trans && !dialing) { PDEBUG(DAMPS, DEBUG_NOTICE, "Paging reply, but call is already gone, rejecting call\n"); goto reject; } if (trans && dialing) PDEBUG(DAMPS, DEBUG_NOTICE, "There is already a transaction for this phone. Cloning?\n"); vc = search_free_vc(); if (!vc) { PDEBUG(DAMPS, DEBUG_NOTICE, "No free channel, rejecting call\n"); reject: if (!trans) { trans = create_transaction(amps, TRANS_CALL_REJECT, min1, min2, 0, 0, 3, 0); if (!trans) { PDEBUG(DAMPS, DEBUG_ERROR, "Failed to create transaction\n"); return; } } else { trans_new_state(trans, TRANS_CALL_REJECT); trans->chan = 0; trans->msg_type = 0; trans->ordq = 0; trans->order = 3; } return; } if (!trans) { trans = create_transaction(amps, TRANS_CALL_MO_ASSIGN, min1, min2, 0, 0, 0, vc->sender.kanal); strncpy(trans->dialing, dialing, sizeof(trans->dialing) - 1); if (!trans) { PDEBUG(DAMPS, DEBUG_ERROR, "Failed to create transaction\n"); return; } } else { trans_new_state(trans, TRANS_CALL_MT_ASSIGN); trans->chan = vc->sender.kanal; } } else PDEBUG_CHAN(DAMPS, DEBUG_NOTICE, "Unsupported RECC messages: ORDER: %d ORDQ: %d MSG TYPE: %d (See Table 4 of specs.)\n", order, ordq, msg_type); } /* * call states received from call control */ /* Call control starts call towards mobile station. */ int call_out_setup(int callref, const char __attribute__((unused)) *caller_id, enum number_type __attribute__((unused)) caller_type, const char *dialing) { sender_t *sender; amps_t *amps; transaction_t *trans; uint32_t min1; uint16_t min2; int i; /* 1. check if number is invalid, return INVALNUMBER */ if (strlen(dialing) == 11 && !strncmp(dialing, "+", 1)) dialing += 1; if (strlen(dialing) == 11 && !strncmp(dialing, "1", 1)) dialing += 1; if (strlen(dialing) != 10) { inval: PDEBUG(DAMPS, DEBUG_NOTICE, "Outgoing call to invalid number '%s', rejecting!\n", dialing); return -CAUSE_INVALNUMBER; } for (i = 0; i < 10; i++) { if (dialing[i] < '0' || dialing[i] > '9') goto inval; } amps_number2min(dialing, &min1, &min2); /* 2. check if the subscriber is attached */ // if (!find_db(min1, min2)) { // PDEBUG(DAMPS, DEBUG_NOTICE, "Outgoing call to not attached subscriber, rejecting!\n"); // return -CAUSE_OUTOFORDER; // } /* 3. check if given number is already in a call, return BUSY */ for (sender = sender_head; sender; sender = sender->next) { amps = (amps_t *) sender; /* search transaction for this number */ trans = search_transaction_number(amps, min1, min2); if (trans) break; } if (sender) { PDEBUG(DAMPS, DEBUG_NOTICE, "Outgoing call to busy number, rejecting!\n"); return -CAUSE_BUSY; } /* 4. check if all senders are busy, return NOCHANNEL */ if (!search_free_vc()) { PDEBUG(DAMPS, DEBUG_NOTICE, "Outgoing call, but no free channel, rejecting!\n"); return -CAUSE_NOCHANNEL; } /* 5. check if we have (currently) no paging channel, return NOCHANNEL */ amps = search_pc(); if (!amps) { PDEBUG(DAMPS, DEBUG_NOTICE, "Outgoing call, but paging channel (control channel) is currently busy, rejecting!\n"); return -CAUSE_NOCHANNEL; } PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Call to mobile station, paging station id '%s'\n", dialing); /* 6. trying to page mobile station */ trans = create_transaction(amps, TRANS_PAGE, min1, min2, 0, 0, 0, 0); if (!trans) { PDEBUG(DAMPS, DEBUG_ERROR, "Failed to create transaction\n"); return -CAUSE_TEMPFAIL; } trans->callref = callref; trans->page_retry = 1; return 0; } /* Call control sends disconnect (with tones). * An active call stays active, so tones and annoucements can be received * by mobile station. */ void call_out_disconnect(int callref, int cause) { sender_t *sender; amps_t *amps; transaction_t *trans; PDEBUG(DAMPS, DEBUG_INFO, "Call has been disconnected by network.\n"); for (sender = sender_head; sender; sender = sender->next) { amps = (amps_t *) sender; /* search transaction for this callref */ trans = search_transaction_callref(amps, callref); if (trans) break; } if (!sender) { PDEBUG(DAMPS, DEBUG_NOTICE, "Outgoing disconnect, but no callref!\n"); call_in_release(callref, CAUSE_INVALCALLREF); return; } /* Release when not active */ switch (amps->dsp_mode) { case DSP_MODE_AUDIO_RX_AUDIO_TX: case DSP_MODE_AUDIO_RX_FRAME_TX: if (trans->state == TRANS_CALL_MT_ALERT || trans->state == TRANS_CALL_MT_ALERT_SEND) { PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Call control disconnect on voice channel while alerting, releasing towards mobile station.\n"); amps_release(trans, cause); } return; default: PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Call control disconnects on control channel, removing transaction.\n"); call_in_release(callref, cause); trans->callref = 0; destroy_transaction(trans); amps_go_idle(amps); } } /* Call control releases call toward mobile station. */ void call_out_release(int callref, int cause) { sender_t *sender; amps_t *amps; transaction_t *trans; PDEBUG(DAMPS, DEBUG_INFO, "Call has been released by network, releasing call.\n"); for (sender = sender_head; sender; sender = sender->next) { amps = (amps_t *) sender; /* search transaction for this callref */ trans = search_transaction_callref(amps, callref); if (trans) break; } if (!sender) { PDEBUG(DAMPS, DEBUG_NOTICE, "Outgoing release, but no callref!\n"); /* don't send release, because caller already released */ return; } trans->callref = 0; switch (amps->dsp_mode) { case DSP_MODE_AUDIO_RX_AUDIO_TX: case DSP_MODE_AUDIO_RX_FRAME_TX: PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Call control releases on voice channel, releasing towards mobile station.\n"); amps_release(trans, cause); break; default: PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Call control releases on control channel, removing transaction.\n"); destroy_transaction(trans); amps_go_idle(amps); } } /* Receive audio from call instance. */ void call_rx_audio(int callref, int16_t *samples, int count) { sender_t *sender; amps_t *amps; for (sender = sender_head; sender; sender = sender->next) { amps = (amps_t *) sender; if (amps->trans_list && amps->trans_list->callref == callref) break; } if (!sender) return; if (amps->dsp_mode == DSP_MODE_AUDIO_RX_AUDIO_TX) { int16_t up[(int)((double)count * amps->sender.srstate.factor + 0.5) + 10]; compress_audio(&s->cstate, samples, count); count = samplerate_upsample(&s->sender.srstate, samples, count, up); jitter_save(&s->sender.dejitter, up, count); } } /* Timeout handling */ void transaction_timeout(struct timer *timer) { transaction_t *trans = (transaction_t *)timer->priv; amps_t *amps = trans->amps; switch (trans->state) { case TRANS_CALL: timeout_sat(amps, timer->duration); break; case TRANS_CALL_RELEASE: case TRANS_CALL_RELEASE_SEND: PDEBUG_CHAN(DAMPS, DEBUG_NOTICE, "Release timeout, destroying transaction\n"); destroy_transaction(trans); amps_go_idle(amps); break; case TRANS_CALL_MT_ALERT: amps_release(trans, CAUSE_TEMPFAIL); break; case TRANS_CALL_MT_ALERT_SEND: PDEBUG_CHAN(DAMPS, DEBUG_NOTICE, "Alerting timeout, destroying transaction\n"); amps_release(trans, CAUSE_NOANSWER); break; case TRANS_PAGE_REPLY: if (trans->page_retry++ == PAGE_TRIES) { PDEBUG_CHAN(DAMPS, DEBUG_NOTICE, "Paging timeout, destroying transaction\n"); amps_release(trans, CAUSE_OUTOFORDER); } else { PDEBUG_CHAN(DAMPS, DEBUG_NOTICE, "Paging timeout, retrying\n"); trans_new_state(trans, TRANS_PAGE); } break; default: PDEBUG_CHAN(DAMPS, DEBUG_ERROR, "Timeout unhandled in state %d\n", trans->state); } } /* assigning voice channel and moving transaction+callref to that channel */ static amps_t *assign_voice_channel(transaction_t *trans) { amps_t *amps = trans->amps, *vc; const char *callerid = amps_min2number(trans->min1, trans->min2); int callref = ++new_callref; int rc; vc = search_channel(trans->chan); if (!vc) { PDEBUG(DAMPS, DEBUG_NOTICE, "Channel %d is not free anymore, rejecting call\n", trans->chan); amps_release(trans, CAUSE_NOCHANNEL); return NULL; } if (vc == amps) PDEBUG(DAMPS, DEBUG_INFO, "Staying on combined control + voice channel %d\n", vc->sender.kanal); else PDEBUG(DAMPS, DEBUG_INFO, "Moving to traffic channel %d\n", vc->sender.kanal); if (!trans->callref) { /* setup call */ PDEBUG(DAMPS, DEBUG_INFO, "Setup call to network.\n"); rc = call_in_setup(callref, callerid, trans->dialing); if (rc < 0) { PDEBUG(DAMPS, DEBUG_NOTICE, "Call rejected (cause %d), releasing.\n", rc); amps_release(trans, 0); return NULL; } trans->callref = callref; } timer_start(&trans->timer, SAT_TO1); /* make channel busy */ amps_new_state(vc, STATE_BUSY); /* relink */ unlink_transaction(trans); link_transaction(trans, vc); /* flush all other transactions, if any (in case of combined VC + CC) */ amps_flush_other_transactions(vc, trans); return vc; } transaction_t *amps_tx_frame_focc(amps_t *amps) { transaction_t *trans; amps_t *vc; again: trans = amps->trans_list; if (!trans) return NULL; switch (trans->state) { case TRANS_REGISTER_ACK: PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Sending Register acknowledge\n"); trans_new_state(trans, TRANS_REGISTER_ACK_SEND); return trans; case TRANS_REGISTER_ACK_SEND: destroy_transaction(trans); goto again; case TRANS_CALL_REJECT: PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Rejecting call from mobile station\n"); trans_new_state(trans, TRANS_CALL_REJECT_SEND); return trans; case TRANS_CALL_REJECT_SEND: destroy_transaction(trans); goto again; case TRANS_CALL_MO_ASSIGN: PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Assigning channel to call from mobile station\n"); trans_new_state(trans, TRANS_CALL_MO_ASSIGN_SEND); return trans; case TRANS_CALL_MO_ASSIGN_SEND: vc = assign_voice_channel(trans); if (vc) { PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Assignment complete, voice connected\n"); trans_new_state(trans, TRANS_CALL); amps_set_dsp_mode(vc, DSP_MODE_AUDIO_RX_AUDIO_TX, 0); } return NULL; case TRANS_CALL_MT_ASSIGN: PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Assigning channel to call to mobile station\n"); trans_new_state(trans, TRANS_CALL_MT_ASSIGN_SEND); return trans; case TRANS_CALL_MT_ASSIGN_SEND: vc = assign_voice_channel(trans); if (vc) { PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Assignment complete, next: sending alerting on VC\n"); trans->chan = 0; trans->msg_type = 0; trans->ordq = 0; trans->order = 1; trans_new_state(trans, TRANS_CALL_MT_ALERT); amps_set_dsp_mode(vc, DSP_MODE_AUDIO_RX_FRAME_TX, 0); } return NULL; case TRANS_PAGE: PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Paging the phone\n"); trans_new_state(trans, TRANS_PAGE_SEND); return trans; case TRANS_PAGE_SEND: trans_new_state(trans, TRANS_PAGE_REPLY); timer_start(&trans->timer, (trans->page_retry == PAGE_TRIES) ? PAGE_TO2 : PAGE_TO1); return NULL; default: return NULL; } } transaction_t *amps_tx_frame_fvc(amps_t *amps) { transaction_t *trans = amps->trans_list; again: trans = amps->trans_list; if (!trans) return NULL; switch (trans->state) { case TRANS_CALL_RELEASE: PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Releasing call towards mobile station\n"); trans_new_state(trans, TRANS_CALL_RELEASE_SEND); return trans; case TRANS_CALL_RELEASE_SEND: PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Release call was sent, destroying call\n"); destroy_transaction(trans); amps_go_idle(amps); goto again; case TRANS_CALL_MT_ALERT: PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Sending alerting\n"); return trans; default: return NULL; } } void dump_info(void) {}