/* C-Netz 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 channel state an callref: * * If channel state is busy, it is used as SpK, also it has callref. * * Each transaction has callref, but callref is only assigned to channel * in SpK mode, so voice frames are routed there. * */ /* Notes on transaction state: * * The state is used to define what is scheduled next, what message is awaited, * what is done when timeout. The event (scheduler, message, timeout) is * processed then and the state may change. */ /* Call control process: * * If an MT (mobile terminating) call is made, a transaction with callref is * created. The transaction is linked to OgK. When the scheduler schedules * VAK(R), the SpK is allocated and the transaction is linked to it. * * If no SpK is available, the call is rejected. If queue (Warteschlange) is * enabled, WSK(R) is scheduled. After transmission, the state changes to * TRANS_MT_QUEUE. Upon timeout (no channel becomes available), the call is * rejected by scheduling VA(R). Upon available channel the call proceeeds with * VAK(R) as described above. * * If an MO (mobile originating) call is made (received VWG(K)), a transaction * with callref is created. The transaction is linked to OgK. When the * scheduler schedules WAF(M), the process waits for WUE(M). If not received, * the process is repeated. After N times WBN(R) is scheduled and transaction * is destroyed. If WUE(M) is received, the scheduler schedules WBP(R) and * then schedules VAG(R), the SpK is allocated and the transaction is linked to * it. * * If no SpK is available, the call is rejected by scheduling WBN(R). If queue * (Warteschlange) is enabled, WWBP(R) is scheduled. After transmission, the * state is changed to TRANS_MO_QUEUE. Upon timeout (no channel becomes * available), the call is rejected by scheduling VA(R). Upon available channel * the call proceeeds with VAG(R) as described above. * * Switching to SpK is performed two time slots after transmitting VAK(R) or * VAG(R). The timer is started. The schedulers schedules 8 times BQ(K) and * awaits at least one BEL(K). If BEK(K) is received, the timer is stoped. If * BQ(K) was sent at least 8 times and if timer is stopped, the scheduler * schedules VHQ(K). If no BEL(K) was received, AFK(K) is scheduled N_AFKT * times, then the process on OgK (WBP+VAG or VAK) is repeated N times. * * Similar to BQ/BEL the DS/DSQ handing is performed. For MT calls, the BQ/BEL * is followed by RTA/RTAQ handling. If the phone answers, the AT(K) is * received and DS/DSQ handling is performed. * * After DS/DSQ handling, the SpK changes to distributed signalling mode. * VHQ1/VHQ2(V) is transmitted and VH(V) is received. If VH(V) is not received * F_VHQ times, the connection is terminated by sending AF(K) N_AFKT times. * Transaction is released. * * If AT(K) or AT(V) is received, AF(K) or AF(V) is sent once and transaction * is released. * * If call is released by upper layer, AF(K) is sent N_AFKT times or AF(V) is * sent N_AFV times. The transaction is released. * * More details about the process can be read from the source code. * * Special timings and correct scheduling is defined in source code and can be * read also in the C-Netz specs. */ /* * Notes on switching from OgK to SpK * * Upon transmission of TRANS_VAG and TRANS_VAK, the SpK channel is allocated, * set to busy, scheduled to switch to SpK mode after two frames. The trans- * action is relinked from OgK to SpK. * * In case of a combined OgK+SpK, the channel stays the same, but will change. * * See below for detailed processing. */ /* * Notes on database (subscriber) * * If a subscriber registers (transaction is created), an instance of the * subscriber database is created. A timer is running for each instance, so * the subscriber is paged to check availability of the phone. If the paging * fails, a retry counter is decreased until the subscriber is removed from * database. * * See database.c for more information. */ /* * Notes on the combined channel hack: * * For combined SpK+OgK hack, the channel is used as SpK as last choise. This * allows to use only one transceiver for making C-Netz to work. Also it allows * to use all transceivers for simultanious phone calls. Some phones may not * work with that. */ /* * Notes on sync: * * The encoder generates a precise clocked signal using correction value given * by command line. For multichannel, the second sound card's channel (slave) is * synced to the first one (master), if calculation of signal phase might drift * due to routing errors. * * The decoder is synced to the phone, whenever it receives a valid frame. * * See dsp.c and fsk_fm_demod.c for code about syncing. */ #define CHAN cnetz->sender.kanal #include #include #include #include #include #include #include #include "../libsample/sample.h" #include "../libdebug/debug.h" #include "../libmobile/call.h" #include "../libmncc/cause.h" #include "cnetz.h" #include "database.h" #include "sysinfo.h" #include "telegramm.h" #include "dsp.h" /* uncomment this to do echo debugging (-l) on Speech Channel */ //#define DEBUG_SPK #define CUT_OFF_EMPHASIS_CNETZ 796.0 /* 200 uS time constant */ /* 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 'unterband' to 1 to get frequency of mobile station. */ double cnetz_kanal2freq(int kanal, int unterband) { double freq = 465.750; if (unterband == 2) return -10.000 * 1e6; if ((kanal & 1)) freq -= (double)(kanal + 1) / 2.0 * 0.010; else freq -= (double)kanal / 2.0 * 0.0125; if (unterband) freq -= 10.000; return freq * 1e6; } const char *cnetz_state_name(enum cnetz_state state) { static char invalid[16]; switch (state) { case CNETZ_NULL: return "(NULL)"; case CNETZ_IDLE: return "IDLE"; case CNETZ_BUSY: return "BUSY"; } sprintf(invalid, "invalid(%d)", state); return invalid; } void cnetz_display_status(void) { sender_t *sender; cnetz_t *cnetz; transaction_t *trans; display_status_start(); for (sender = sender_head; sender; sender = sender->next) { cnetz = (cnetz_t *) sender; display_status_channel(cnetz->sender.kanal, chan_type_short_name(cnetz->chan_type), cnetz_state_name(cnetz->state)); for (trans = cnetz->trans_list; trans; trans = trans->next) display_status_subscriber(transaction2rufnummer(trans), trans_short_state_name(trans->state)); } display_status_end(); } static void cnetz_new_state(cnetz_t *cnetz, enum cnetz_state new_state) { if (cnetz->state == new_state) return; PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "State change: %s -> %s\n", cnetz_state_name(cnetz->state), cnetz_state_name(new_state)); cnetz->state = new_state; cnetz_display_status(); } /* Convert ISDN cause to 'Ausloesegrund' of C-Netz mobile station */ uint8_t cnetz_cause_isdn2cnetz(int cause) { switch (cause) { case CAUSE_NORMAL: case CAUSE_BUSY: case CAUSE_NOANSWER: return CNETZ_CAUSE_TEILNEHMERBESETZT; case CAUSE_OUTOFORDER: case CAUSE_INVALNUMBER: case CAUSE_NOCHANNEL: case CAUSE_TEMPFAIL: default: return CNETZ_CAUSE_GASSENBESETZT; } } /* global init */ int cnetz_init(void) { return 0; } /* Create transceiver instance and link to a list. */ int cnetz_create(int kanal, enum cnetz_chan_type chan_type, const char *audiodev, int use_sdr, enum demod_type demod, int samplerate, double rx_gain, int challenge_valid, uint64_t challenge, int response_valid, uint64_t response, int warteschlange, int metering, double dbm0_deviation, int ms_power, int measure_speed, double clock_speed[2], int polarity, int pre_emphasis, int de_emphasis, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, int loopback) { sender_t *sender; cnetz_t *cnetz; int rc; if ((kanal & 1) && kanal < 1 && kanal > 1147) { PDEBUG(DCNETZ, DEBUG_ERROR, "Channel ('Kanal') number %d invalid.\n", kanal); return -EINVAL; } if ((kanal & 1) && kanal > 947) { PDEBUG(DCNETZ, DEBUG_NOTICE, "You defined an extended frequency %d, only newer phones support this!\n", kanal); } if (!(kanal & 1) && kanal < 2 && kanal > 918) { PDEBUG(DCNETZ, DEBUG_ERROR, "Channel ('Kanal') number %d invalid.\n", kanal); return -EINVAL; } if (!(kanal & 1) && kanal > 758) { PDEBUG(DCNETZ, DEBUG_NOTICE, "You defined an extended frequency %d, only newer phones support this!\n", kanal); } if (kanal == 1 || kanal == 2) { PDEBUG(DCNETZ, DEBUG_NOTICE, "Channel ('Kanal') number %d is specified as 'unused', it might not work!\n", kanal); } /* OgK must be on channel 131 */ if ((chan_type == CHAN_TYPE_OGK || chan_type == CHAN_TYPE_OGK_SPK) && kanal != CNETZ_OGK_KANAL) { PDEBUG(DCNETZ, DEBUG_NOTICE, "You must use channel %d for calling channel ('Orga-Kanal') or for combined calling + traffic channel!\n", CNETZ_OGK_KANAL); return -EINVAL; } /* SpK must be on channel other than 131 */ if (chan_type == CHAN_TYPE_SPK && kanal == CNETZ_OGK_KANAL) { PDEBUG(DCNETZ, DEBUG_NOTICE, "You must not use channel %d for traffic channel!\n", CNETZ_OGK_KANAL); return -EINVAL; } /* warn if we combine SpK and OgK, this is not supported by standard */ if (chan_type == CHAN_TYPE_OGK_SPK) { PDEBUG(DCNETZ, DEBUG_NOTICE, "You selected channel %d ('Orga-Kanal') for combined calling + traffic channel. Some phones will reject this.\n", CNETZ_OGK_KANAL); } for (sender = sender_head; sender; sender = sender->next) { cnetz = (cnetz_t *)sender; if (!!strcmp(sender->audiodev, audiodev)) { PDEBUG(DCNETZ, DEBUG_NOTICE, "To be able to sync multiple channels, all channels must be on the same sound device!\n"); return -EINVAL; } } cnetz = calloc(1, sizeof(cnetz_t)); if (!cnetz) { PDEBUG(DCNETZ, DEBUG_ERROR, "No memory!\n"); return -ENOMEM; } PDEBUG(DCNETZ, DEBUG_DEBUG, "Creating 'C-Netz' instance for 'Kanal' = %d (sample rate %d).\n", kanal, samplerate); /* init general part of transceiver */ /* do not enable emphasis, since it is done by cnetz code, not by common sender code */ rc = sender_create(&cnetz->sender, kanal, cnetz_kanal2freq(kanal, 0), cnetz_kanal2freq(kanal, 1), audiodev, use_sdr, samplerate, rx_gain, 0, 0, write_rx_wave, write_tx_wave, read_rx_wave, read_tx_wave, loopback, PAGING_SIGNAL_NONE); if (rc < 0) { PDEBUG(DCNETZ, DEBUG_ERROR, "Failed to init transceiver process!\n"); goto error; } #if 0 #warning hacking: applying different clock to slave if (&cnetz->sender != sender_head) { clock_speed[0] = -3; clock_speed[1] = -3; } #endif /* init audio processing */ rc = dsp_init_sender(cnetz, measure_speed, clock_speed, demod, dbm0_deviation); if (rc < 0) { PDEBUG(DCNETZ, DEBUG_ERROR, "Failed to init signal processing!\n"); goto error; } cnetz->chan_type = chan_type; cnetz->challenge_valid = challenge_valid; cnetz->challenge = challenge; cnetz->response_valid = response_valid; cnetz->response = response; cnetz->warteschlange = warteschlange; cnetz->metering = metering; cnetz->ms_power = ms_power; switch (polarity) { case 1: /* select cell 0 for positive polarity */ cnetz->cell_nr = 0; cnetz->cell_auto = 0; if (si[cnetz->cell_nr].flip_polarity != 0) { fprintf(stderr, "cell %d must have positive polarity, please fix!\n", cnetz->cell_nr); abort(); } break; case -1: /* select cell 1 for negative polarity */ cnetz->cell_nr = 1; cnetz->cell_auto = 0; if (si[cnetz->cell_nr].flip_polarity == 0) { fprintf(stderr, "cell %d must have negative polarity, please fix!\n", cnetz->cell_nr); abort(); } break; default: /* send two cells and select by the first message from mobile */ cnetz->cell_nr = 0; /* use cell 0 until selected */ cnetz->cell_auto = 1; } cnetz->pre_emphasis = pre_emphasis; cnetz->de_emphasis = de_emphasis; rc = init_emphasis(&cnetz->estate, samplerate, CUT_OFF_EMPHASIS_CNETZ, CUT_OFF_HIGHPASS_DEFAULT, CUT_OFF_LOWPASS_DEFAULT); if (rc < 0) goto error; /* go into idle state */ cnetz_set_dsp_mode(cnetz, DSP_MODE_OGK); cnetz_set_sched_dsp_mode(cnetz, DSP_MODE_OGK, 0); cnetz_go_idle(cnetz); #ifdef DEBUG_SPK transaction_t *trans = create_transaction(cnetz, TRANS_DS, 2, 2, 22002, -1, -1); trans->mo_call = 1; cnetz_set_sched_dsp_mode(cnetz, DSP_MODE_SPK_K, 2); #else /* create transaction for speech channel loopback */ if (loopback && chan_type == CHAN_TYPE_SPK) { transaction_t *trans = create_transaction(cnetz, TRANS_VHQ_K, 2, 2, 22002, -1, -1); trans->mo_call = 1; cnetz_set_dsp_mode(cnetz, DSP_MODE_SPK_K); cnetz_set_sched_dsp_mode(cnetz, DSP_MODE_SPK_K, 0); } #endif #if 0 /* debug flushing transactions */ transaction_t *trans1, *trans2; trans1 = create_transaction(cnetz, 99, 6, 2, 15784, -1, -1); destroy_transaction(trans1); trans1 = create_transaction(cnetz, 99, 6, 2, 15784, -1, -1); destroy_transaction(trans1); trans1 = create_transaction(cnetz, 99, 6, 2, 15784, -1, -1); trans2 = create_transaction(cnetz, 99, 2, 2, 22002, -1, -1); unlink_transaction(trans1); link_transaction(trans1, cnetz); cnetz_flush_other_transactions(cnetz, trans1); trans2 = create_transaction(cnetz, 99, 2, 2, 22002, -1, -1); cnetz_flush_other_transactions(cnetz, trans2); #endif PDEBUG(DCNETZ, DEBUG_NOTICE, "Created 'Kanal' #%d of type '%s' = %s\n", kanal, chan_type_short_name(chan_type), chan_type_long_name(chan_type)); return 0; error: cnetz_destroy(&cnetz->sender); return rc; } /* Destroy transceiver instance and unlink from list. */ void cnetz_destroy(sender_t *sender) { cnetz_t *cnetz = (cnetz_t *) sender; transaction_t *trans; PDEBUG(DCNETZ, DEBUG_DEBUG, "Destroying 'C-Netz' instance for 'Kanal' = %d.\n", sender->kanal); while ((trans = search_transaction(cnetz, ~0))) { const char *rufnummer = transaction2rufnummer(trans); PDEBUG(DCNETZ, DEBUG_NOTICE, "Removing pending transaction for subscriber '%s'\n", rufnummer); destroy_transaction(trans); } dsp_cleanup_sender(cnetz); sender_destroy(&cnetz->sender); free(cnetz); } static cnetz_t *search_free_spk(int extended) { sender_t *sender; cnetz_t *cnetz, *ogk_spk = NULL; for (sender = sender_head; sender; sender = sender->next) { cnetz = (cnetz_t *) sender; /* ignore extended frequency, if not supported */ if (!extended) { if ((sender->kanal & 1) && sender->kanal > 947) continue; if (!(sender->kanal & 1) && sender->kanal > 758) continue; } /* ignore busy channel */ if (cnetz->state != CNETZ_IDLE) continue; /* return first free SpK */ if (cnetz->chan_type == CHAN_TYPE_SPK) return cnetz; /* remember OgK/SpK combined channel as second alternative */ if (cnetz->chan_type == CHAN_TYPE_OGK_SPK) ogk_spk = cnetz; } return ogk_spk; } static cnetz_t *search_ogk(void) { sender_t *sender; cnetz_t *cnetz; for (sender = sender_head; sender; sender = sender->next) { cnetz = (cnetz_t *) sender; /* ignore busy channel */ if (cnetz->state != CNETZ_IDLE) continue; if (cnetz->chan_type == CHAN_TYPE_OGK) return cnetz; if (cnetz->chan_type == CHAN_TYPE_OGK_SPK) return cnetz; } return NULL; } /* Abort connection, if any and send idle broadcast */ void cnetz_go_idle(cnetz_t *cnetz) { cnetz_t *ogk; transaction_t *trans; if (cnetz->state == CNETZ_IDLE) return; if (cnetz->trans_list) { PDEBUG(DCNETZ, DEBUG_ERROR, "Releasing but still having transaction, please fix!\n"); if (cnetz->trans_list->callref) call_up_release(cnetz->trans_list->callref, CAUSE_NORMAL); destroy_transaction(cnetz->trans_list); } PDEBUG(DCNETZ, DEBUG_INFO, "Entering IDLE state on channel %d.\n", cnetz->sender.kanal); cnetz_new_state(cnetz, CNETZ_IDLE); /* set scheduler to OgK or turn off SpK */ if (cnetz->dsp_mode == DSP_MODE_SPK_K || cnetz->dsp_mode == DSP_MODE_SPK_V) { /* go idle after next frame/slot */ cnetz_set_sched_dsp_mode(cnetz, (cnetz->sender.kanal == CNETZ_OGK_KANAL) ? DSP_MODE_OGK : DSP_MODE_OFF, 1); } else { cnetz_set_sched_dsp_mode(cnetz, (cnetz->sender.kanal == CNETZ_OGK_KANAL) ? DSP_MODE_OGK : DSP_MODE_OFF, 0); cnetz_set_dsp_mode(cnetz, (cnetz->sender.kanal == CNETZ_OGK_KANAL) ? DSP_MODE_OGK : DSP_MODE_OFF); } /* check for first phone in queue and trigger completion of call (becoming idle means that SpK is now available) */ ogk = search_ogk(); trans = search_transaction(ogk, TRANS_MT_QUEUE | TRANS_MO_QUEUE); if (trans) { PDEBUG(DCNETZ, DEBUG_NOTICE, "Now channel available for queued subscriber '%s'.\n", transaction2rufnummer(trans)); trans_new_state(trans, (trans->state == TRANS_MT_QUEUE) ? TRANS_MT_DELAY : TRANS_MO_DELAY); timer_stop(&trans->timer); timer_start(&trans->timer, 2.0); } } /* Initiate release connection on speech channel */ static void cnetz_release(transaction_t *trans, uint8_t cause) { trans_new_state(trans, (trans->cnetz->dsp_mode == DSP_MODE_OGK) ? TRANS_VA : TRANS_AF); trans->repeat = 0; trans->release_cause = cause; trans->cnetz->sched_switch_mode = 0; timer_stop(&trans->timer); } /* Receive audio from call instance. */ void call_down_audio(int callref, sample_t *samples, int count) { sender_t *sender; cnetz_t *cnetz; for (sender = sender_head; sender; sender = sender->next) { cnetz = (cnetz_t *) sender; if (cnetz->trans_list && cnetz->trans_list->callref == callref) break; } if (!sender) return; if (cnetz->dsp_mode == DSP_MODE_SPK_V) { /* store as is, since we convert rate when processing FSK frames */ jitter_save(&cnetz->sender.dejitter, samples, count); } } int call_down_setup(int callref, const char __attribute__((unused)) *caller_id, enum number_type __attribute__((unused)) caller_type, const char *dialing) { sender_t *sender; cnetz_t *cnetz, *spk; int rc; int extended; transaction_t *trans; uint8_t futln_nat; uint8_t futln_fuvst; uint16_t futln_rest; int i; /* 1. check if number is invalid, return INVALNUMBER */ if (strlen(dialing) == 11 && !strncmp(dialing, "0160", 4)) dialing += 4; if (strlen(dialing) != 7) { inval: PDEBUG(DCNETZ, DEBUG_NOTICE, "Outgoing call to invalid number '%s', rejecting!\n", dialing); return -CAUSE_INVALNUMBER; } for (i = 0; i < 7; i++) { if (dialing[i] < '0' || dialing[i] > '9') goto inval; } if (atoi(dialing + 2) > 65535) { PDEBUG(DCNETZ, DEBUG_NOTICE, "Last 5 digits '%s' must not exceed '65535', but they do!\n", dialing + 2); goto inval; } futln_nat = dialing[0] - '0'; futln_fuvst = dialing[1] - '0'; futln_rest = atoi(dialing + 2); /* 2. check if the subscriber is attached */ rc = find_db(futln_nat, futln_fuvst, futln_rest, NULL, &extended); if (rc < 0) { PDEBUG(DCNETZ, 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) { cnetz = (cnetz_t *) sender; /* search transaction for this number */ trans = search_transaction_number(cnetz, futln_nat, futln_fuvst, futln_rest); if (trans) break; } if (sender) { PDEBUG(DCNETZ, DEBUG_NOTICE, "Outgoing call to busy number, rejecting!\n"); return -CAUSE_BUSY; } /* 4. check if we have no OgK, return NOCHANNEL */ cnetz = search_ogk(); if (!cnetz) { PDEBUG(DCNETZ, DEBUG_NOTICE, "Outgoing call, but OgK is currently busy, rejecting!\n"); return -CAUSE_NOCHANNEL; } /* 5. check if all senders are busy, return NOCHANNEL */ spk = search_free_spk(extended); if (!spk) { if (!cnetz->warteschlange) { PDEBUG(DCNETZ, DEBUG_NOTICE, "Outgoing call, but no free channel, rejecting!\n"); return -CAUSE_NOCHANNEL; } else PDEBUG(DCNETZ, DEBUG_NOTICE, "Outgoing call, but no free channel, queuing call!\n"); } PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Call to mobile station, paging station id '%s'\n", dialing); /* 6. trying to page mobile station */ trans = create_transaction(cnetz, (spk) ? TRANS_VAK : TRANS_WSK, dialing[0] - '0', dialing[1] - '0', atoi(dialing + 2), -1, -1); if (!trans) { PDEBUG(DCNETZ, DEBUG_ERROR, "Failed to create transaction\n"); return -CAUSE_TEMPFAIL; } trans->callref = callref; trans->try = 1; return 0; } void call_down_answer(int __attribute__((unused)) callref) { } /* Call control sends disconnect (with tones). * An active call stays active, so tones and annoucements can be received * by mobile station. */ void call_down_disconnect(int callref, int cause) { sender_t *sender; cnetz_t *cnetz; transaction_t *trans; PDEBUG(DCNETZ, DEBUG_INFO, "Call has been disconnected by network.\n"); for (sender = sender_head; sender; sender = sender->next) { cnetz = (cnetz_t *) sender; /* search transaction for this callref */ trans = search_transaction_callref(cnetz, callref); if (trans) break; } if (!sender) { PDEBUG(DCNETZ, DEBUG_NOTICE, "Outgoing disconnect, but no callref!\n"); call_up_release(callref, CAUSE_INVALCALLREF); return; } /* Release when not active */ switch (cnetz->dsp_mode) { case DSP_MODE_SPK_V: return; case DSP_MODE_SPK_K: PDEBUG(DCNETZ, DEBUG_INFO, "Call control disconnects on speech channel, releasing towards mobile station.\n"); cnetz_release(trans, cnetz_cause_isdn2cnetz(cause)); call_up_release(callref, cause); trans->callref = 0; break; default: PDEBUG(DCNETZ, DEBUG_INFO, "Call control disconnects on organisation channel, removing transaction.\n"); call_up_release(callref, cause); trans->callref = 0; if (trans->state == TRANS_MT_QUEUE || trans->state == TRANS_MT_DELAY) { cnetz_release(trans, cnetz_cause_isdn2cnetz(cause)); } else { destroy_transaction(trans); cnetz_go_idle(cnetz); } } } /* Call control releases call toward mobile station. */ void call_down_release(int callref, int cause) { sender_t *sender; cnetz_t *cnetz; transaction_t *trans; PDEBUG(DCNETZ, DEBUG_INFO, "Call has been released by network, releasing call.\n"); for (sender = sender_head; sender; sender = sender->next) { cnetz = (cnetz_t *) sender; /* search transaction for this callref */ trans = search_transaction_callref(cnetz, callref); if (trans) break; } if (!sender) { PDEBUG(DCNETZ, DEBUG_NOTICE, "Outgoing release, but no callref!\n"); /* don't send release, because caller already released */ return; } trans->callref = 0; switch (cnetz->dsp_mode) { case DSP_MODE_SPK_K: case DSP_MODE_SPK_V: PDEBUG(DCNETZ, DEBUG_INFO, "Call control releases on speech channel, releasing towards mobile station.\n"); cnetz_release(trans, cnetz_cause_isdn2cnetz(cause)); break; default: PDEBUG(DCNETZ, DEBUG_INFO, "Call control releases on organisation channel, removing transaction.\n"); if (trans->state == TRANS_MT_QUEUE) { cnetz_release(trans, cnetz_cause_isdn2cnetz(cause)); } else { destroy_transaction(trans); cnetz_go_idle(cnetz); } } } int cnetz_meldeaufruf(uint8_t futln_nat, uint8_t futln_fuvst, uint16_t futln_rest) { cnetz_t *cnetz; transaction_t *trans; cnetz = search_ogk(); if (!cnetz) { PDEBUG(DCNETZ, DEBUG_NOTICE, "'Meldeaufruf', but OgK is currently busy!\n"); return -CAUSE_NOCHANNEL; } trans = create_transaction(cnetz, TRANS_MA, futln_nat, futln_fuvst, futln_rest, -1, -1); if (!trans) { PDEBUG(DCNETZ, DEBUG_ERROR, "Failed to create transaction\n"); return -CAUSE_TEMPFAIL; } return 0; } static struct cnetz_channels { enum cnetz_chan_type chan_type; const char *short_name; const char *long_name; } cnetz_channels[] = { { CHAN_TYPE_OGK_SPK, "OgK/SpK","combined control & voice channel" }, { CHAN_TYPE_OGK, "OgK", "control channel" }, { CHAN_TYPE_SPK, "SpK", "voice channel" }, { 0, NULL, NULL } }; void cnetz_channel_list(void) { int i; printf("Type\tDescription\n"); printf("------------------------------------------------------------------------\n"); for (i = 0; cnetz_channels[i].long_name; i++) printf("%s\t%s\n", cnetz_channels[i].short_name, cnetz_channels[i].long_name); } int cnetz_channel_by_short_name(const char *short_name) { int i; for (i = 0; cnetz_channels[i].short_name; i++) { if (!strcasecmp(cnetz_channels[i].short_name, short_name)) return cnetz_channels[i].chan_type; } return -1; } const char *chan_type_short_name(enum cnetz_chan_type chan_type) { int i; for (i = 0; cnetz_channels[i].short_name; i++) { if (cnetz_channels[i].chan_type == chan_type) return cnetz_channels[i].short_name; } return "invalid"; } const char *chan_type_long_name(enum cnetz_chan_type chan_type) { int i; for (i = 0; cnetz_channels[i].long_name; i++) { if (cnetz_channels[i].chan_type == chan_type) return cnetz_channels[i].long_name; } return "invalid"; } /* Timeout handling */ void transaction_timeout(struct timer *timer) { transaction_t *trans = (transaction_t *)timer->priv; cnetz_t *cnetz = trans->cnetz; switch (trans->state) { case TRANS_WAF: PDEBUG_CHAN(DCNETZ, DEBUG_NOTICE, "No response after dialing request 'Wahlaufforderung'\n"); if (trans->try == N) { trans_new_state(trans, TRANS_WBN); break; } trans->try++; trans_new_state(trans, TRANS_VWG); break; case TRANS_MT_QUEUE: PDEBUG_CHAN(DCNETZ, DEBUG_NOTICE, "Phone in queue, but still no channel available, releasing call!\n"); call_up_release(trans->callref, CAUSE_NOCHANNEL); trans->callref = 0; cnetz_release(trans, CNETZ_CAUSE_GASSENBESETZT); break; case TRANS_MO_QUEUE: PDEBUG_CHAN(DCNETZ, DEBUG_NOTICE, "Phone in queue, but still no channel available, releasing!\n"); cnetz_release(trans, CNETZ_CAUSE_GASSENBESETZT); break; case TRANS_MT_DELAY: trans_new_state(trans, TRANS_VAK); break; case TRANS_MO_DELAY: trans_new_state(trans, TRANS_VAG); break; case TRANS_BQ: PDEBUG_CHAN(DCNETZ, DEBUG_NOTICE, "No response after channel allocation 'Belegung Quittung'\n"); trans_new_state(trans, TRANS_AF); trans->repeat = 0; break; case TRANS_ZFZ: PDEBUG_CHAN(DCNETZ, DEBUG_NOTICE, "No response after sending random number 'Zufallszahl'\n"); if (trans->callref) { call_up_release(trans->callref, CAUSE_TEMPFAIL); trans->callref = 0; } cnetz_release(trans, CNETZ_CAUSE_FUNKTECHNISCH); break; case TRANS_AP: PDEBUG_CHAN(DCNETZ, DEBUG_NOTICE, "No response after waiting for challenge response 'Autorisierungsparameter'\n"); if (trans->callref) { call_up_release(trans->callref, CAUSE_TEMPFAIL); trans->callref = 0; } cnetz_release(trans, CNETZ_CAUSE_FUNKTECHNISCH); break; case TRANS_VHQ_K: case TRANS_VHQ_V: if (cnetz->dsp_mode != DSP_MODE_SPK_V) PDEBUG_CHAN(DCNETZ, DEBUG_NOTICE, "No response while holding call 'Quittung Verbindung halten'\n"); else PDEBUG_CHAN(DCNETZ, DEBUG_NOTICE, "Lost signal from 'FuTln' (mobile station)\n"); if (trans->callref) { call_up_release(trans->callref, CAUSE_TEMPFAIL); trans->callref = 0; } cnetz_release(trans, CNETZ_CAUSE_FUNKTECHNISCH); break; case TRANS_DS: PDEBUG_CHAN(DCNETZ, DEBUG_NOTICE, "No response after connect 'Durchschalten'\n"); call_up_release(trans->callref, CAUSE_TEMPFAIL); trans->callref = 0; cnetz_release(trans, CNETZ_CAUSE_FUNKTECHNISCH); break; case TRANS_RTA: PDEBUG_CHAN(DCNETZ, DEBUG_NOTICE, "No response after ringing order 'Rufton anschalten'\n"); call_up_release(trans->callref, CAUSE_TEMPFAIL); trans->callref = 0; cnetz_release(trans, CNETZ_CAUSE_FUNKTECHNISCH); break; case TRANS_AHQ: PDEBUG_CHAN(DCNETZ, DEBUG_NOTICE, "No response after answer 'Abhebequittung'\n"); call_up_release(trans->callref, CAUSE_TEMPFAIL); trans->callref = 0; cnetz_release(trans, CNETZ_CAUSE_FUNKTECHNISCH); break; case TRANS_MFT: PDEBUG_CHAN(DCNETZ, DEBUG_NOTICE, "No response after keepalive order 'Meldeaufruf'\n"); /* no response to availability check */ trans->page_failed = 1; destroy_transaction(trans); break; default: PDEBUG_CHAN(DCNETZ, DEBUG_ERROR, "Timeout unhandled in state %" PRIu64 "\n", trans->state); } } /* * sync to phone * * because we don't know the actual delay on sound card, we need to sync * to the phone, that is synced to us. * * if block is given, we can set sync to absolute position in super frame. * if not, we just sync to the nearest block. */ void cnetz_sync_frame(cnetz_t *cnetz, double sync, int block) { double offset; if (block >= 0) { /* offset is the actual sync relative to bit_time */ offset = fmod(sync - BITS_PER_BLOCK * (double)block + BITS_PER_SUPERFRAME, BITS_PER_SUPERFRAME); if (offset > BITS_PER_SUPERFRAME / 2) offset -= BITS_PER_SUPERFRAME; } else { /* sync to the nearest block */ /* offset is the actual sync relative to bit_time */ offset = fmod(sync, BITS_PER_BLOCK); if (offset > BITS_PER_BLOCK / 2) offset -= BITS_PER_BLOCK; } /* if more than +- one bit out of sync */ if (offset < -0.5 || offset > 0.5) { PDEBUG_CHAN(DCNETZ, DEBUG_NOTICE, "Frame sync offset = %.2f, correcting!\n", offset); fsk_correct_sync(&cnetz->fsk_demod, offset); return; } /* resync by some fraction of received sync error */ PDEBUG_CHAN(DCNETZ, DEBUG_DEBUG, "Frame sync offset = %.2f, correcting.\n", offset); fsk_correct_sync(&cnetz->fsk_demod, offset / 2.0); } /* * OgK handling */ /* transmit rufblock */ const telegramm_t *cnetz_transmit_telegramm_rufblock(cnetz_t *cnetz) { static telegramm_t telegramm; transaction_t *trans; cnetz_t *spk; memset(&telegramm, 0, sizeof(telegramm)); telegramm.opcode = OPCODE_LR_R; telegramm.max_sendeleistung = cnetz->ms_power; telegramm.bedingte_genauigkeit_der_fufst = si[cnetz->cell_nr].genauigkeit; telegramm.zeitschlitz_nr = cnetz->sched_ts; telegramm.grenzwert_fuer_einbuchen_und_umbuchen = si[cnetz->cell_nr].grenz_einbuchen; telegramm.authentifikationsbit = si[cnetz->cell_nr].authentifikationsbit; telegramm.vermittlungstechnische_sperren = si[cnetz->cell_nr].vermittlungstechnische_sperren; telegramm.ws_kennung = si[cnetz->cell_nr].ws_kennung; telegramm.reduzierungsfaktor = si[cnetz->cell_nr].reduzierung; telegramm.fuz_nationalitaet = si[cnetz->cell_nr].fuz_nat; telegramm.fuz_fuvst_nr = si[cnetz->cell_nr].fuz_fuvst; telegramm.fuz_rest_nr = si[cnetz->cell_nr].fuz_rest; telegramm.kennung_fufst = si[cnetz->cell_nr].kennung_fufst; telegramm.nachbarschafts_prioritaets_bit = si[cnetz->cell_nr].nachbar_prio; telegramm.bewertung_nach_pegel_und_entfernung = si[cnetz->cell_nr].bewertung; telegramm.entfernungsangabe_der_fufst = si[cnetz->cell_nr].entfernung; telegramm.mittelungsfaktor_fuer_ausloesen = si[cnetz->cell_nr].mittel_ausloesen; telegramm.mittelungsfaktor_fuer_umschalten = si[cnetz->cell_nr].mittel_umschalten; telegramm.grenzwert_fuer_umschalten = si[cnetz->cell_nr].grenz_umschalten; telegramm.grenze_fuer_ausloesen = si[cnetz->cell_nr].grenz_ausloesen; trans = search_transaction(cnetz, TRANS_EM | TRANS_UM | TRANS_WBN | TRANS_WBP | TRANS_VAG | TRANS_VAK | TRANS_ATQ | TRANS_VA | TRANS_WSK); if (trans) { telegramm.futln_nationalitaet = trans->futln_nat; telegramm.futln_heimat_fuvst_nr = trans->futln_fuvst; telegramm.futln_rest_nr = trans->futln_rest; telegramm.ausloesegrund = trans->release_cause; switch (trans->state) { case TRANS_EM: PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Sending acknowledgment 'Einbuchquittung' to Attachment request.\n"); telegramm.opcode = OPCODE_EBQ_R; destroy_transaction(trans); break; case TRANS_UM: PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Sending acknowledgment 'Umbuchquittung' to Roaming requuest.\n"); telegramm.opcode = OPCODE_UBQ_R; destroy_transaction(trans); break; case TRANS_WBN: wbn: PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Sending call reject 'Wahlbestaetigung negativ'.\n"); telegramm.opcode = OPCODE_WBN_R; destroy_transaction(trans); cnetz_go_idle(cnetz); break; case TRANS_WBP: spk = search_free_spk(trans->extended); /* Accept call if channel available, otherwise reject or queue call */ if (spk) { PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Sending call accept 'Wahlbestaetigung positiv'.\n"); telegramm.opcode = OPCODE_WBP_R; trans_new_state(trans, TRANS_VAG); } else if (cnetz->warteschlange) { /* queue call if no channel is available, but queue allowed */ PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "No free channel, sending call accept in queue 'Wahlbestaetigung positiv in Warteschlage'.\n"); telegramm.opcode = OPCODE_WWBP_R; trans_new_state(trans, TRANS_MO_QUEUE); timer_start(&trans->timer, T_VAG2); /* Maximum time to hold queue */ } else { PDEBUG(DCNETZ, DEBUG_NOTICE, "No free channel anymore, rejecting call!\n"); trans_new_state(trans, TRANS_WBN); goto wbn; } break; case TRANS_VAG: case TRANS_VAK: vak: if (trans->state == TRANS_VAG) { PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Sending channel assignment 'Verbindungsaufbau gehend'.\n"); telegramm.opcode = OPCODE_VAG_R; } else { PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Sending channel assignment 'Verbindungsaufbau kommend'.\n"); telegramm.opcode = OPCODE_VAK_R; } trans_new_state(trans, TRANS_BQ); trans->repeat = 0; timer_start(&trans->timer, 0.150 + 0.0375 * F_BQ); /* two slots + F_BQ frames */ /* select channel */ spk = search_free_spk(trans->extended); if (!spk) { PDEBUG(DCNETZ, DEBUG_NOTICE, "No free channel anymore, rejecting call!\n"); destroy_transaction(trans); cnetz_go_idle(cnetz); break; } if (spk == cnetz) { PDEBUG(DCNETZ, DEBUG_INFO, "Staying on combined calling + traffic channel %d\n", spk->sender.kanal); } else { PDEBUG(DCNETZ, DEBUG_INFO, "Assigning phone to traffic channel %d\n", spk->sender.kanal); /* sync RX time to current OgK time */ fsk_copy_sync(&spk->fsk_demod, &cnetz->fsk_demod); } /* set channel */ telegramm.frequenz_nr = spk->sender.kanal; /* change state to busy */ cnetz_new_state(spk, CNETZ_BUSY); /* schedule switching two slots ahead */ cnetz_set_sched_dsp_mode(spk, DSP_MODE_SPK_K, 2); /* relink */ unlink_transaction(trans); link_transaction(trans, spk); /* flush all other transactions, if any (in case of OgK/SpK) */ cnetz_flush_other_transactions(spk, trans); break; case TRANS_ATQ: PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Sending acknowledgment 'Quittung fuer Ausloesen des FuTelG im OgK-Betrieb' to release request.\n"); telegramm.opcode = OPCODE_ATQ_R; destroy_transaction(trans); break; case TRANS_VA: PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Sending 'Vorzeitiges Ausloesen' to queued mobile station\n"); telegramm.opcode = OPCODE_VA_R; destroy_transaction(trans); break; case TRANS_WSK: spk = search_free_spk(trans->extended); /* if channel becomes free before we send the queue information, we proceed with channel assignment */ if (spk) { trans_new_state(trans, TRANS_VAK); goto vak; } PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "No free channel, sending incoming call in queue 'Warteschglange kommend'.\n"); telegramm.opcode = OPCODE_WSK_R; trans_new_state(trans, TRANS_MT_QUEUE); timer_start(&trans->timer, T_VAK); /* Maximum time to hold queue */ call_up_alerting(trans->callref); default: ; /* LR */ } } return &telegramm; } /* transmit meldeblock */ const telegramm_t *cnetz_transmit_telegramm_meldeblock(cnetz_t *cnetz) { static telegramm_t telegramm; transaction_t *trans; memset(&telegramm, 0, sizeof(telegramm)); telegramm.opcode = OPCODE_MLR_M; telegramm.max_sendeleistung = cnetz->ms_power; telegramm.ogk_verkehrsanteil = 0; /* must be 0 or phone might not respond to messages in different slot */ telegramm.teilnehmergruppensperre = si[cnetz->cell_nr].teilnehmergruppensperre; telegramm.anzahl_gesperrter_teilnehmergruppen = si[cnetz->cell_nr].anzahl_gesperrter_teilnehmergruppen; telegramm.ogk_vorschlag = CNETZ_OGK_KANAL; telegramm.fuz_rest_nr = si[cnetz->cell_nr].fuz_rest; trans = search_transaction(cnetz, TRANS_VWG | TRANS_MA); if (trans) { switch (trans->state) { case TRANS_VWG: PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Sending acknowledgment 'Wahlaufforderung' to outging call\n"); telegramm.opcode = OPCODE_WAF_M; telegramm.futln_nationalitaet = trans->futln_nat; telegramm.futln_heimat_fuvst_nr = trans->futln_fuvst; telegramm.futln_rest_nr = trans->futln_rest; trans_new_state(trans, TRANS_WAF); timer_start(&trans->timer, 1.0); /* Wait two slot cycles until resending */ break; case TRANS_MA: PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Sending keepalive request 'Meldeaufruf'\n"); telegramm.opcode = OPCODE_MA_M; telegramm.futln_nationalitaet = trans->futln_nat; telegramm.futln_heimat_fuvst_nr = trans->futln_fuvst; telegramm.futln_rest_nr = trans->futln_rest; trans_new_state(trans, TRANS_MFT); timer_start(&trans->timer, 1.0); /* Wait two slot cycles until timeout */ break; default: ; /* MLR */ } } return &telegramm; } void cnetz_receive_telegramm_ogk(cnetz_t *cnetz, telegramm_t *telegramm, int block) { uint8_t opcode = telegramm->opcode; int valid_frame = 0; transaction_t *trans; const char *rufnummer; switch (opcode) { case OPCODE_EM_R: if (!match_fuz(cnetz, telegramm, cnetz->cell_nr)) break; rufnummer = telegramm2rufnummer(telegramm); if (si[cnetz->cell_nr].authentifikationsbit && telegramm->chipkarten_futelg_bit) PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Received Attachment 'Einbuchen' message from Subscriber '%s' with chip card's ID %d (vendor id %d, hardware version %d, software version %d)\n", rufnummer, telegramm->kartenkennung, telegramm->herstellerkennung, telegramm->hardware_des_futelg, telegramm->software_des_futelg); else PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Received Attachment 'Einbuchen' message from Subscriber '%s' with %s card's security code %d\n", rufnummer, (telegramm->chipkarten_futelg_bit) ? "chip":"magnet", telegramm->sicherungs_code); if (telegramm->erweitertes_frequenzbandbit) PDEBUG(DCNETZ, DEBUG_INFO, " -> Phone support extended frequency band\n"); if (cnetz->state != CNETZ_IDLE) { PDEBUG(DCNETZ, DEBUG_NOTICE, "Ignoring Attachment from subscriber '%s', because we are busy becoming SpK.\n", rufnummer); break; } trans = create_transaction(cnetz, TRANS_EM, telegramm->futln_nationalitaet, telegramm->futln_heimat_fuvst_nr, telegramm->futln_rest_nr, telegramm->chipkarten_futelg_bit, telegramm->erweitertes_frequenzbandbit); if (!trans) { PDEBUG(DCNETZ, DEBUG_ERROR, "Failed to create transaction\n"); break; } valid_frame = 1; break; case OPCODE_UM_R: if (!match_fuz(cnetz, telegramm, cnetz->cell_nr)) break; rufnummer = telegramm2rufnummer(telegramm); if (si[cnetz->cell_nr].authentifikationsbit && telegramm->chipkarten_futelg_bit) PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Received Roaming 'Umbuchen' message from Subscriber '%s' with chip card's ID %d (vendor id %d, hardware version %d, software version %d)\n", rufnummer, telegramm->kartenkennung, telegramm->herstellerkennung, telegramm->hardware_des_futelg, telegramm->software_des_futelg); else PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Received Roaming 'Umbuchen' message from Subscriber '%s' with %s card's security code %d\n", rufnummer, (telegramm->chipkarten_futelg_bit) ? "chip":"magnet", telegramm->sicherungs_code); if (telegramm->erweitertes_frequenzbandbit) PDEBUG(DCNETZ, DEBUG_INFO, " -> Phone support extended frequency band\n"); if (cnetz->state != CNETZ_IDLE) { PDEBUG(DCNETZ, DEBUG_NOTICE, "Ignoring Roaming from subscriber '%s', because we are busy becoming SpK.\n", rufnummer); break; } trans = create_transaction(cnetz, TRANS_UM, telegramm->futln_nationalitaet, telegramm->futln_heimat_fuvst_nr, telegramm->futln_rest_nr, telegramm->chipkarten_futelg_bit, telegramm->erweitertes_frequenzbandbit); if (!trans) { PDEBUG(DCNETZ, DEBUG_ERROR, "Failed to create transaction\n"); break; } valid_frame = 1; break; case OPCODE_UWG_R: case OPCODE_UWK_R: if (!match_fuz(cnetz, telegramm, cnetz->cell_nr)) break; rufnummer = telegramm2rufnummer(telegramm); PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Received Roaming request 'Umbuchantrag' message from Subscriber '%s' on queue\n", rufnummer); break; case OPCODE_VWG_R: case OPCODE_SRG_R: if (!match_fuz(cnetz, telegramm, cnetz->cell_nr)) break; rufnummer = telegramm2rufnummer(telegramm); PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Received outgoing Call 'Verbindungswunsch gehend' message from Subscriber '%s'\n", rufnummer); if (cnetz->state != CNETZ_IDLE) { PDEBUG(DCNETZ, DEBUG_NOTICE, "Ignoring Call from subscriber '%s', because we are busy becoming SpK.\n", rufnummer); break; } trans = create_transaction(cnetz, TRANS_VWG, telegramm->futln_nationalitaet, telegramm->futln_heimat_fuvst_nr, telegramm->futln_rest_nr, -1, telegramm->erweitertes_frequenzbandbit); if (!trans) { PDEBUG(DCNETZ, DEBUG_ERROR, "Failed to create transaction\n"); break; } trans->try = 1; valid_frame = 1; break; case OPCODE_WUE_M: trans = search_transaction(cnetz, TRANS_WAF | TRANS_WBP | TRANS_VAG | TRANS_MO_QUEUE); if (!trans) { PDEBUG_CHAN(DCNETZ, DEBUG_NOTICE, "Received dialing digits 'Wahluebertragung' message without transaction, ignoring!\n"); break; } rufnummer = transaction2rufnummer(trans); strncpy(trans->dialing, telegramm->wahlziffern, sizeof(trans->dialing) - 1); PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Received dialing digits 'Wahluebertragung' message from Subscriber '%s' to Number '%s'\n", rufnummer, trans->dialing); timer_stop(&trans->timer); trans_new_state(trans, TRANS_WBP); trans->try = 1; /* try */ valid_frame = 1; break; case OPCODE_ATO_R: if (!match_fuz(cnetz, telegramm, cnetz->cell_nr)) break; rufnummer = telegramm2rufnummer(telegramm); PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Received release 'Ausloesen des FuTelG im OgK-Betrieb bei WS' message from Subscriber '%s'\n", rufnummer); trans = search_transaction_number(cnetz, telegramm->futln_nationalitaet, telegramm->futln_heimat_fuvst_nr, telegramm->futln_rest_nr); if (!trans) { /* create transaction, in case the phone repeats the release after we have acked it */ trans = create_transaction(cnetz, TRANS_ATQ, telegramm->futln_nationalitaet, telegramm->futln_heimat_fuvst_nr, telegramm->futln_rest_nr, -1, -1); if (!trans) { PDEBUG(DCNETZ, DEBUG_ERROR, "Failed to create transaction\n"); break; } } else { timer_stop(&trans->timer); trans_new_state(trans, TRANS_ATQ); } valid_frame = 1; break; case OPCODE_MFT_M: if (!match_fuz(cnetz, telegramm, cnetz->cell_nr)) break; trans = search_transaction_number(cnetz, telegramm->futln_nationalitaet, telegramm->futln_heimat_fuvst_nr, telegramm->futln_rest_nr); if (!trans) { PDEBUG_CHAN(DCNETZ, DEBUG_NOTICE, "Received acknowledge 'Meldung Funktelefonteilnehmer' message without transaction, ignoring!\n"); break; } rufnummer = transaction2rufnummer(trans); PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Received acknowledge 'Meldung Funktelefonteilnehmer' message from Subscriber '%s'\n", rufnummer); destroy_transaction(trans); valid_frame = 1; break; default: PDEBUG_CHAN(DCNETZ, DEBUG_NOTICE, "Received unexpected Telegramm (opcode %d = %s)\n", opcode, telegramm_name(opcode)); } if (cnetz->sender.loopback) { fprintf(stderr, "we don't know TS here, but we are in loopback mode. in loopback mode call to this function shall never happen. please fix or find a way to know when the time slot was received!\n"); abort(); } if (valid_frame) cnetz_sync_frame(cnetz, telegramm->sync_time, block); } /* * SpK handling */ /* transmit concentrated messages */ const telegramm_t *cnetz_transmit_telegramm_spk_k(cnetz_t *cnetz) { static telegramm_t telegramm; transaction_t *trans = cnetz->trans_list; cnetz_t *ogk; memset(&telegramm, 0, sizeof(telegramm)); if (!trans) return &telegramm; telegramm.max_sendeleistung = cnetz->ms_power; telegramm.sendeleistungsanpassung = 1; telegramm.entfernung = si[cnetz->cell_nr].entfernung; telegramm.fuz_nationalitaet = si[cnetz->cell_nr].fuz_nat; telegramm.fuz_fuvst_nr = si[cnetz->cell_nr].fuz_fuvst; telegramm.fuz_rest_nr = si[cnetz->cell_nr].fuz_rest; telegramm.futln_nationalitaet = trans->futln_nat; telegramm.futln_heimat_fuvst_nr = trans->futln_fuvst; telegramm.futln_rest_nr = trans->futln_rest; telegramm.frequenz_nr = cnetz->sender.kanal; telegramm.bedingte_genauigkeit_der_fufst = si[cnetz->cell_nr].genauigkeit; telegramm.zufallszahl = cnetz->challenge; switch (trans->state) { case TRANS_BQ: PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Sending 'Belegungsquittung' on traffic channel\n"); telegramm.opcode = OPCODE_BQ_K; if (++trans->repeat >= 8 && !timer_running(&trans->timer)) { if (cnetz->challenge_valid) { if (si[cnetz->cell_nr].authentifikationsbit == 0) { PDEBUG_CHAN(DCNETZ, DEBUG_NOTICE, "Cannot authenticate, because base station does not support it. (Authentication disabled in sysinfo.)\n"); goto no_auth; } if (trans->futelg_bit == 0) { PDEBUG_CHAN(DCNETZ, DEBUG_NOTICE, "Cannot authenticate, because mobile station does not support it. (Mobile station has magnetic card.)\n"); goto no_auth; } PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Perform authentication with subscriber's card.\n"); trans_new_state(trans, TRANS_ZFZ); timer_start(&trans->timer, 0.0375 * F_ZFZ); /* F_ZFZ frames */ } else { no_auth: trans_new_state(trans, TRANS_VHQ_K); timer_start(&trans->timer, 0.0375 * F_VHQK); /* F_VHQK frames */ } trans->repeat = 0; } break; case TRANS_ZFZ: PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Sending 'Zufallszahl' on traffic channel (0x%016" PRIx64 ").\n", telegramm.zufallszahl); telegramm.opcode = OPCODE_ZFZ_K; break; case TRANS_AP: PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Sending 'Quittung Verbindung halten' on traffic channel\n"); telegramm.opcode = OPCODE_VHQ_K; break; case TRANS_VHQ_K: if (!cnetz->sender.loopback) PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Sending 'Quittung Verbindung halten' on traffic channel\n"); telegramm.opcode = OPCODE_VHQ_K; /* continue until next sub frame, so we send DS from first block of next sub frame. */ if (!cnetz->sender.loopback && (cnetz->sched_ts & 7) == 7 && cnetz->sched_r_m && !timer_running(&trans->timer)) { /* next sub frame */ if (trans->mo_call) { int rc; trans->callref = ++new_callref; rc = call_up_setup(trans->callref, transaction2rufnummer(trans), trans->dialing); if (rc < 0) { PDEBUG(DCNETZ, DEBUG_NOTICE, "Call rejected (cause %d), releasing.\n", -rc); trans->callref = 0; cnetz_release(trans, cnetz_cause_isdn2cnetz(-rc)); goto call_failed; } trans_new_state(trans, TRANS_DS); trans->repeat = 0; timer_start(&trans->timer, 0.0375 * F_DS); /* F_DS frames */ } if (trans->mt_call) { trans_new_state(trans, TRANS_RTA); timer_start(&trans->timer, 0.0375 * F_RTA); /* F_RTA frames */ trans->repeat = 0; call_up_alerting(trans->callref); } } break; case TRANS_DS: PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Sending 'Durchschalten' on traffic channel\n"); telegramm.opcode = OPCODE_DSB_K; /* send exactly a sub frame (8 time slots) */ if ((cnetz->sched_ts & 7) == 7 && cnetz->sched_r_m && !timer_running(&trans->timer)) { /* next sub frame */ trans_new_state(trans, TRANS_VHQ_V); trans->repeat = 0; cnetz_set_sched_dsp_mode(cnetz, DSP_MODE_SPK_V, 1); #ifndef DEBUG_SPK timer_start(&trans->timer, 0.075 + 0.6 * F_VHQ); /* one slot + F_VHQ frames */ #endif } break; case TRANS_RTA: PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Sending 'Rufton anschalten' on traffic channel\n"); telegramm.opcode = OPCODE_RTA_K; break; case TRANS_AHQ: PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Sending 'Abhebe Quittung' on traffic channel\n"); telegramm.opcode = OPCODE_AHQ_K; if ((cnetz->sched_ts & 7) == 7 && cnetz->sched_r_m) { /* next sub frame */ trans_new_state(trans, TRANS_VHQ_V); trans->repeat = 0; cnetz_set_sched_dsp_mode(cnetz, DSP_MODE_SPK_V, 1); timer_start(&trans->timer, 0.075 + 0.6 * F_VHQ); /* one slot + F_VHQ frames */ } break; case TRANS_AF: call_failed: PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Sending 'Ausloesen durch FuFSt' on traffic channel\n"); telegramm.opcode = OPCODE_AF_K; if (++trans->repeat < N_AFKT) break; if (!trans->try) { /* no retry */ destroy_transaction(trans); cnetz_go_idle(cnetz); break; } if (trans->try == N) { PDEBUG(DCNETZ, DEBUG_INFO, "Maximum retries, removing transaction\n"); /* no response to incomming call */ trans->page_failed = 1; cnetz_release(trans, CNETZ_CAUSE_FUNKTECHNISCH); if (trans->callref) call_up_release(trans->callref, CAUSE_TEMPFAIL); /* must destroy transaction after cnetz_release */ destroy_transaction(trans); cnetz_go_idle(cnetz); break; } /* remove call from SpK (or OgK+SpK) */ unlink_transaction(trans); /* idle channel */ cnetz_go_idle(cnetz); /* alloc ogk again */ ogk = search_ogk(); if (!ogk) { PDEBUG(DCNETZ, DEBUG_NOTICE, "Cannot retry, because currently no OgK available (busy)\n"); cnetz_release(trans, CNETZ_CAUSE_FUNKTECHNISCH); if (trans->callref) call_up_release(trans->callref, CAUSE_NOCHANNEL); /* must destroy transaction after cnetz_release */ destroy_transaction(trans); break; } PDEBUG(DCNETZ, DEBUG_INFO, "Retry to assign channel.\n"); /* attach call to OgK */ link_transaction(trans, ogk); /* change state */ if (trans->mo_call) trans_new_state(trans, TRANS_WBP); if (trans->mt_call) trans_new_state(trans, TRANS_VAK); trans->try++; break; case TRANS_AT: PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Sending 'Auslosen durch FuFst' on traffic channel\n"); telegramm.opcode = OPCODE_AF_K; if (++trans->repeat == 1) { destroy_transaction(trans); cnetz_go_idle(cnetz); } break; } return &telegramm; } /* receive concentrated messages */ void cnetz_receive_telegramm_spk_k(cnetz_t *cnetz, telegramm_t *telegramm) { uint8_t opcode = telegramm->opcode; int valid_frame = 0; transaction_t *trans = cnetz->trans_list; if (!trans) return; switch (opcode) { case OPCODE_BEL_K: if (!match_fuz(cnetz, telegramm, cnetz->cell_nr)) { break; } if (!match_futln(telegramm, trans->futln_nat, trans->futln_fuvst, trans->futln_rest)) { break; } PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Received allocation 'Belegung' message.\n"); valid_frame = 1; if (trans->state != TRANS_BQ) break; timer_stop(&trans->timer); trans->try = 0; break; case OPCODE_DSQ_K: if (!match_fuz(cnetz, telegramm, cnetz->cell_nr)) { break; } if (!match_futln(telegramm, trans->futln_nat, trans->futln_fuvst, trans->futln_rest)) { break; } PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Received assignment confirm 'Durchschaltung Quittung' message.\n"); valid_frame = 1; if (trans->state != TRANS_DS) break; cnetz->scrambler = telegramm->betriebs_art; cnetz->scrambler_switch = 0; timer_stop(&trans->timer); break; case OPCODE_ZFZQ_K: PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Received random number acknowledge 'Zufallszahlquittung' message.\n"); valid_frame = 1; if (trans->state != TRANS_ZFZ) break; if (cnetz->challenge != telegramm->zufallszahl) { PDEBUG_CHAN(DCNETZ, DEBUG_NOTICE, "Received random number acknowledge (0x%016" PRIx64 ") does not match the transmitted one (0x%016" PRIx64 "), ignoring!\n", telegramm->zufallszahl, cnetz->challenge); break; } timer_stop(&trans->timer); trans_new_state(trans, TRANS_AP); timer_start(&trans->timer, T_AP); /* 750 milliseconds */ break; case OPCODE_AP_K: PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Received challenge response 'Autorisierungsparameter' message (0x%016" PRIx64 ").\n", telegramm->authorisierungsparameter); valid_frame = 1; if (trans->state != TRANS_AP) break; /* if authentication response from card does not match */ if (cnetz->response_valid && telegramm->authorisierungsparameter != cnetz->response) { PDEBUG_CHAN(DCNETZ, DEBUG_NOTICE, "Received challenge response (0x%016" PRIx64 ") does not match the expected one (0x%016" PRIx64 "), releasing!\n", telegramm->authorisierungsparameter, cnetz->response); if (trans->callref) { call_up_release(trans->callref, CAUSE_TEMPFAIL); /* jolly guesses that */ trans->callref = 0; } cnetz_release(trans, CNETZ_CAUSE_GASSENBESETZT); /* when authentication is not valid */ break; } timer_stop(&trans->timer); trans_new_state(trans, TRANS_VHQ_K); timer_start(&trans->timer, 0.0375 * F_VHQK); /* F_VHQK frames */ break; case OPCODE_VH_K: if (!match_fuz(cnetz, telegramm, cnetz->cell_nr)) { break; } if (!match_futln(telegramm, trans->futln_nat, trans->futln_fuvst, trans->futln_rest)) { break; } PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Received connection hold 'Verbindung halten' message.\n"); valid_frame = 1; if (trans->state != TRANS_VHQ_K) break; timer_stop(&trans->timer); break; case OPCODE_RTAQ_K: if (!match_fuz(cnetz, telegramm, cnetz->cell_nr)) { break; } if (!match_futln(telegramm, trans->futln_nat, trans->futln_fuvst, trans->futln_rest)) { break; } valid_frame = 1; PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Received ringback 'Rufton anschalten Quittung' message.\n"); if (trans->state != TRANS_RTA) break; timer_start(&trans->timer, 0.0375 * F_RTA); /* F_RTA frames */ break; case OPCODE_AH_K: if (!match_fuz(cnetz, telegramm, cnetz->cell_nr)) { break; } if (!match_futln(telegramm, trans->futln_nat, trans->futln_fuvst, trans->futln_rest)) { break; } PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Received answer frame 'Abheben' message.\n"); valid_frame = 1; /* if already received this frame, or if we are already on VHQ or if we are releasing */ if (trans->state == TRANS_AHQ || trans->state == TRANS_VHQ_K || trans->state == TRANS_AF) break; cnetz->scrambler = telegramm->betriebs_art; cnetz->scrambler_switch = 0; trans_new_state(trans, TRANS_AHQ); trans->repeat = 0; timer_stop(&trans->timer); call_up_answer(trans->callref, transaction2rufnummer(trans)); break; case OPCODE_AT_K: if (!match_fuz(cnetz, telegramm, cnetz->cell_nr)) { break; } if (!match_futln(telegramm, trans->futln_nat, trans->futln_fuvst, trans->futln_rest)) { break; } PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Received release frame 'Ausloesen durch FuTln' message.\n"); valid_frame = 1; /* if already received this frame, if we are releasing */ if (trans->state == TRANS_AT || trans->state == TRANS_AF) break; trans_new_state(trans, TRANS_AT); trans->repeat = 0; timer_stop(&trans->timer); if (trans->callref) { call_up_release(trans->callref, CAUSE_NORMAL); trans->callref = 0; } break; default: PDEBUG_CHAN(DCNETZ, DEBUG_NOTICE, "Received unexpected Telegramm (opcode %d = %s)\n", opcode, telegramm_name(opcode)); } if (valid_frame) cnetz_sync_frame(cnetz, telegramm->sync_time, -1); } /* transmit distributed messages */ const telegramm_t *cnetz_transmit_telegramm_spk_v(cnetz_t *cnetz) { static telegramm_t telegramm; transaction_t *trans = cnetz->trans_list; int meter = 0; memset(&telegramm, 0, sizeof(telegramm)); if (!trans) return &telegramm; if (cnetz->metering) { double now = get_time(); if (!trans->call_start) trans->call_start = now; meter = (now - trans->call_start) / (double)cnetz->metering + 1; } telegramm.max_sendeleistung = cnetz->ms_power; telegramm.sendeleistungsanpassung = 1; telegramm.ankuendigung_gespraechsende = 0; telegramm.gebuehren_stand = meter; telegramm.fuz_nationalitaet = si[cnetz->cell_nr].fuz_nat; telegramm.fuz_fuvst_nr = si[cnetz->cell_nr].fuz_fuvst; telegramm.fuz_rest_nr = si[cnetz->cell_nr].fuz_rest; telegramm.futln_nationalitaet = trans->futln_nat; telegramm.futln_heimat_fuvst_nr = trans->futln_fuvst; telegramm.futln_rest_nr = trans->futln_rest; telegramm.frequenz_nr = cnetz->sender.kanal; telegramm.entfernung = si[cnetz->cell_nr].entfernung; telegramm.bedingte_genauigkeit_der_fufst = si[cnetz->cell_nr].genauigkeit; telegramm.gueltigkeit_des_gebuehrenstandes = 0; telegramm.ausloesegrund = trans->release_cause; switch (trans->state) { case TRANS_VHQ_V: if ((cnetz->sched_ts & 8) == 0) { /* sub frame 1 and 3 */ PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Sending 'Quittung Verbindung halten 1' on traffic channel\n"); telegramm.opcode = OPCODE_VHQ1_V; } else { /* sub frame 2 and 4 */ PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Sending 'Quittung Verbindung halten 2' on traffic channel\n"); telegramm.opcode = OPCODE_VHQ2_V; } break; case TRANS_AF: PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Sending 'Ausloesen durch FuFSt' on traffic channel\n"); telegramm.opcode = OPCODE_AF_V; if (++trans->repeat == N_AFV) { destroy_transaction(trans); cnetz_go_idle(cnetz); } break; case TRANS_AT: PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Sending acknowledge to 'Ausloesen durch FuTln' on traffic channel\n"); telegramm.opcode = OPCODE_AF_V; if (++trans->repeat == 1) { destroy_transaction(trans); cnetz_go_idle(cnetz); } break; } return &telegramm; } /* receive distributed messages */ void cnetz_receive_telegramm_spk_v(cnetz_t *cnetz, telegramm_t *telegramm) { uint8_t opcode = telegramm->opcode; int valid_frame = 0; transaction_t *trans = cnetz->trans_list; if (!trans) return; switch (opcode) { case OPCODE_VH_V: case OPCODE_USAI_V: case OPCODE_USAE_V: if (!match_fuz(cnetz, telegramm, cnetz->cell_nr)) { break; } if (!match_futln(telegramm, trans->futln_nat, trans->futln_fuvst, trans->futln_rest)) { break; } if (trans->state != TRANS_VHQ_V) break; timer_start(&trans->timer, 0.6 * F_VHQ); /* F_VHQ frames */ switch (opcode) { case OPCODE_VH_V: PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Received supervisory frame 'Verbindung halten' message%s.\n", (telegramm->test_telefonteilnehmer_geraet) ? ", phone is a test-phone" : ""); break; case OPCODE_USAI_V: PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Received internal handover request frame 'Umschaltantrag intern' message%s.\n", (telegramm->test_telefonteilnehmer_geraet) ? ", phone is a test-phone" : ""); break; case OPCODE_USAE_V: PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Received external handover request frame 'Umschaltantrag extern' message%s.\n", (telegramm->test_telefonteilnehmer_geraet) ? ", phone is a test-phone" : ""); break; } valid_frame = 1; if (cnetz->scrambler != telegramm->betriebs_art) { /* if the scrambler mode changes, we wait 3 frames */ /* i guess that this was implemented to prevent switching by one corrupt frame. */ if (++cnetz->scrambler_switch >= 3) { cnetz->scrambler = telegramm->betriebs_art; cnetz->scrambler_switch = 0; } } else cnetz->scrambler_switch = 0; break; case OPCODE_AT_V: if (!match_fuz(cnetz, telegramm, cnetz->cell_nr)) { break; } if (!match_futln(telegramm, trans->futln_nat, trans->futln_fuvst, trans->futln_rest)) { break; } PDEBUG_CHAN(DCNETZ, DEBUG_INFO, "Received release frame 'Ausloesen durch FuTln' message.\n"); valid_frame = 1; /* if already received this frame, if we are releasing */ if (trans->state == TRANS_AT || trans->state == TRANS_AF) break; trans_new_state(trans, TRANS_AT); trans->repeat = 0; timer_stop(&trans->timer); if (trans->callref) { call_up_release(trans->callref, CAUSE_NORMAL); trans->callref = 0; } break; default: PDEBUG_CHAN(DCNETZ, DEBUG_NOTICE, "Received unexpected Telegramm (opcode %d = %s)\n", opcode, telegramm_name(opcode)); } if (valid_frame) cnetz_sync_frame(cnetz, telegramm->sync_time, -1); } void dump_info(void) { dump_db(); }