From 034d3a91404addedc1c7a3494862c79532b0b878 Mon Sep 17 00:00:00 2001 From: Andreas Eversberg Date: Sat, 28 Jun 2014 09:24:14 +0200 Subject: [PATCH 1/1] Data-Over-Voice An experimental feature to send and receive an identification over voice channel. If a party answers, the ID is transmitted some seconds afterwards. The calling party listens 30 seconds after receiving an answer message for the ID. Add to your extension's settings file: dov_ident dov_log /path/to/log/file dov_type pwm|pcm dov_level 0|level 'pwm' survives analog transcoding. 'pcm' is fast and will almost not be recognised. 'level' can be used to alter default signal amplitude (100..30000). --- Makefile.am | 2 +- apppbx.cpp | 141 ++++++++++++++ apppbx.h | 7 + dov.cpp | 612 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ extension.c | 37 ++++ extension.h | 4 + mISDN.cpp | 15 +- message.h | 14 ++ port.cpp | 14 ++ port.h | 35 ++++ remote.cpp | 6 + sip.cpp | 14 +- 12 files changed, 894 insertions(+), 7 deletions(-) create mode 100644 dov.cpp diff --git a/Makefile.am b/Makefile.am index fd0cbea..cc4da0d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -186,7 +186,7 @@ lcr_SOURCES = \ $(MISDN_SOURCE) $(GSM_SOURCE) $(SS5_SOURCE) $(SIP_SOURCE) $(VOOTP_SOURCE) \ endpoint.cpp endpointapp.cpp \ appbridge.cpp apppbx.cpp route.c action.cpp action_efi.cpp action_vbox.cpp extension.c mail.c \ - join.cpp joinpbx.cpp + join.cpp joinpbx.cpp dov.cpp lcr_LDADD = $(LIBCRYPTO) $(MISDN_LIB) -lpthread $(GSM_LIB) $(SIP_LIB) $(VOOTP_LIB) diff --git a/apppbx.cpp b/apppbx.cpp index 7dfdf45..9752b87 100644 --- a/apppbx.cpp +++ b/apppbx.cpp @@ -1970,6 +1970,16 @@ void EndpointAppPBX::port_connect(struct port_list *portlist, int message_type, message = message_create(ea_endpoint->ep_serial, ea_endpoint->ep_join_id, EPOINT_TO_JOIN, MESSAGE_AUDIOPATH); message->param.audiopath = 1; message_put(message); + if (e_ext.dov_ident[0]) { + message = message_create(ea_endpoint->ep_serial, ea_endpoint->ep_join_id, EPOINT_TO_JOIN, MESSAGE_DOV_REQUEST); + SPRINT(message->param.dov.data, "%08x ", lcr_random); + SCAT(message->param.dov.data, e_ext.dov_ident); + message->param.dov.length = strlen((char *)message->param.dov.data); + message->param.dov.type = e_ext.dov_type; + message->param.dov.level = e_ext.dov_level; + dov_msg_write(&message->param, 1); + message_put(message); + } } else if (!e_adminid) { /* callback */ PDEBUG(DEBUG_EPOINT, "EPOINT(%d) we have a callback, so we create a call with cbcaller: \"%s\".\n", ea_endpoint->ep_serial, e_cbcaller); @@ -2440,6 +2450,18 @@ void EndpointAppPBX::port_disable_dejitter(struct port_list *portlist, int messa message_put(message); } +/* port MESSAGE_DOV_INDICATION */ +void EndpointAppPBX::port_dov_indication(struct port_list *portlist, int message_type, union parameter *param) +{ + struct lcr_msg *message; + + logmessage(message_type, param, portlist->port_id, DIRECTION_IN); + + message = message_create(ea_endpoint->ep_serial, ea_endpoint->ep_join_id, EPOINT_TO_JOIN, MESSAGE_DOV_INDICATION); + memcpy(&message->param, param, sizeof(union parameter)); + message_put(message); +} + /* port MESSAGE_UPDATEBRIDGE */ void EndpointAppPBX::port_updatebridge(struct port_list *portlist, int message_type, union parameter *param) @@ -2665,6 +2687,11 @@ void EndpointAppPBX::ea_message_port(unsigned int port_id, int message_type, uni port_vootp(portlist, message_type, param); break; + /* PORT indivated Data-Over-Voice */ + case MESSAGE_DOV_INDICATION: + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) epoint with terminal '%s' (caller id '%s') indicates Data-Over-Voice.\n", ea_endpoint->ep_serial, e_ext.number, e_callerinfo.id); + port_dov_indication(portlist, message_type, param); + break; default: PDEBUG(DEBUG_EPOINT, "EPOINT(%d) epoint with terminal '%s' (caller id '%s') received a wrong message: %d\n", ea_endpoint->ep_serial, e_ext.number, e_callerinfo.id, message_type); @@ -2884,6 +2911,13 @@ void EndpointAppPBX::join_connect(struct port_list *portlist, int message_type, message_put(message); time(&now); e_start = now; + + /* if the remote answered, we listen to DOV message */ + if (e_ext.dov_log[0]) { + message = message_create(ea_endpoint->ep_serial, ea_endpoint->ep_join_id, EPOINT_TO_JOIN, MESSAGE_DOV_LISTEN); + message->param.dov.type = e_ext.dov_type; + message_put(message); + } } /* join MESSAGE_DISCONNECT MESSAGE_RELEASE */ @@ -3202,6 +3236,43 @@ void EndpointAppPBX::join_disable_dejitter(struct port_list *portlist, int messa } } +/* join MESSAGE_DOV_INDICATION */ +void EndpointAppPBX::join_dov_indication(struct port_list *portlist, int message_type, union parameter *param) +{ + dov_msg_write(param, 0); +} + +/* join MESSAGE_DOV_REQUEST */ +void EndpointAppPBX::join_dov_request(struct port_list *portlist, int message_type, union parameter *param) +{ + struct lcr_msg *message; + + /* don't send DOV from estension to extension */ + if (e_ext.number[0]) + return; + + while(portlist) { + message = message_create(ea_endpoint->ep_serial, portlist->port_id, EPOINT_TO_PORT, MESSAGE_DOV_REQUEST); + memcpy(&message->param, param, sizeof(union parameter)); + message_put(message); + logmessage(message_type, param, portlist->port_id, DIRECTION_OUT); + portlist = portlist->next; + } +} + +/* join MESSAGE_DOV_LISTEN */ +void EndpointAppPBX::join_dov_listen(struct port_list *portlist, int message_type, union parameter *param) +{ + struct lcr_msg *message; + + while(portlist) { + message = message_create(ea_endpoint->ep_serial, portlist->port_id, EPOINT_TO_PORT, MESSAGE_DOV_LISTEN); + memcpy(&message->param, param, sizeof(union parameter)); + message_put(message); + portlist = portlist->next; + } +} + /* JOIN sends messages to the endpoint */ void EndpointAppPBX::ea_message_join(unsigned int join_id, int message_type, union parameter *param) @@ -3379,6 +3450,24 @@ void EndpointAppPBX::ea_message_join(unsigned int join_id, int message_type, uni join_disable_dejitter(portlist, message_type, param); break; + /* JOIN sends a Data-Over-Voice message indication */ + case MESSAGE_DOV_INDICATION: + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) epoint with terminal '%s' (caller id '%s') received Data-Over-Voice indication.\n", ea_endpoint->ep_serial, e_ext.number, e_callerinfo.id); + join_dov_indication(portlist, message_type, param); + break; + + /* JOIN sends a Data-Over-Voice message request */ + case MESSAGE_DOV_REQUEST: + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) epoint with terminal '%s' (caller id '%s') received Data-Over-Voice request.\n", ea_endpoint->ep_serial, e_ext.number, e_callerinfo.id); + join_dov_request(portlist, message_type, param); + break; + + /* JOIN sends a Data-Over-Voice listen order */ + case MESSAGE_DOV_LISTEN: + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) epoint with terminal '%s' (caller id '%s') received Data-Over-Voice listen order.\n", ea_endpoint->ep_serial, e_ext.number, e_callerinfo.id); + join_dov_listen(portlist, message_type, param); + break; + default: PDEBUG(DEBUG_EPOINT, "EPOINT(%d) epoint with terminal '%s' (caller id '%s') received a wrong message: #%d\n", ea_endpoint->ep_serial, e_ext.number, e_callerinfo.id, message_type); } @@ -4795,6 +4884,23 @@ void EndpointAppPBX::logmessage(int message_type, union parameter *param, unsign end_trace(); break; + case MESSAGE_DOV_INDICATION: + case MESSAGE_DOV_REQUEST: + trace_header("Data-Over-Voice", dir); + if (dir == DIRECTION_OUT) + add_trace("to", NULL, "CH(%lu)", port_id); + if (dir == DIRECTION_IN) + add_trace("from", NULL, "CH(%lu)", port_id); + { + char dov_str[param->dov.length + 1]; + memcpy(dov_str, param->dov.data, param->dov.length); + dov_str[param->dov.length] = '\0'; + add_trace("string", NULL, "%s", dov_str); + } + add_trace("type", NULL, "%d", param->dov.type); + end_trace(); + break; + default: PERROR("EPOINT(%d) message not of correct type (%d)\n", ea_endpoint->ep_serial, message_type); } @@ -4828,3 +4934,38 @@ void EndpointAppPBX::message_disconnect_port(struct port_list *portlist, int cau logmessage(message->type, &message->param, portlist->port_id, DIRECTION_OUT); } +void EndpointAppPBX::dov_msg_write(union parameter *param, int sent) +{ + FILE *fp; + struct tm *tm; + time_t ti; + int __attribute__((__unused__)) rc; + + /* no write, if no log file given */ + if (!e_ext.dov_log[0]) + return; + + fp = fopen(e_ext.dov_log, "a"); + if (!fp) { + PERROR("EPOINT(%d) failed to open Data-Over-Voice log file '%s'\n", ea_endpoint->ep_serial, e_ext.dov_log); + return; + } + + ti = time(NULL); + tm = localtime(&ti); + fprintf(fp, "%02d.%02d.%02d %02d:%02d:%02d ", tm->tm_mday, tm->tm_mon+1, tm->tm_year%100, tm->tm_hour, tm->tm_min, tm->tm_sec); + + + if (sent) { + fprintf(fp, "sent [%s] ", numberrize_callerinfo(e_callerinfo.id, e_callerinfo.ntype, options.national, options.international)); + } else { + fprintf(fp, "received [%s] ", e_dialinginfo.id); + } + + rc = fwrite(param->dov.data, param->dov.length, 1, fp); + + fprintf(fp, "\n"); + + fclose(fp); +} + diff --git a/apppbx.h b/apppbx.h index aea434e..618d9ac 100644 --- a/apppbx.h +++ b/apppbx.h @@ -219,6 +219,7 @@ class EndpointAppPBX : public EndpointApp void port_disable_dejitter(struct port_list *portlist, int message_type, union parameter *param); void port_updatebridge(struct port_list *portlist, int message_type, union parameter *param); void port_vootp(struct port_list *portlist, int message_type, union parameter *param); + void port_dov_indication(struct port_list *portlist, int message_type, union parameter *param); void ea_message_join(unsigned int join_id, int message, union parameter *param); void join_crypt(struct port_list *portlist, int message_type, union parameter *param); void join_mISDNsignal(struct port_list *portlist, int message_type, union parameter *param); @@ -234,6 +235,9 @@ class EndpointAppPBX : public EndpointApp void join_facility(struct port_list *portlist, int message_type, union parameter *param); void join_dtmf(struct port_list *portlist, int message_type, union parameter *param); void join_disable_dejitter(struct port_list *portlist, int message_type, union parameter *param); + void join_dov_indication(struct port_list *portlist, int message_type, union parameter *param); + void join_dov_request(struct port_list *portlist, int message_type, union parameter *param); + void join_dov_listen(struct port_list *portlist, int message_type, union parameter *param); /* epoint */ void new_state(int state); @@ -372,6 +376,9 @@ class EndpointAppPBX : public EndpointApp void message_disconnect_port(struct port_list *portlist, int cause, int location, const char *display); void logmessage(int message_type, union parameter *param, unsigned int port_id, int dir); void trace_header(const char *name, int direction); + + /* DOV */ + void dov_msg_write(union parameter *param, int sent); }; diff --git a/dov.cpp b/dov.cpp new file mode 100644 index 0000000..9889357 --- /dev/null +++ b/dov.cpp @@ -0,0 +1,612 @@ +/*****************************************************************************\ +** ** +** Linux-Call-Router ** +** ** +**---------------------------------------------------------------------------** +** Copyright: Andreas Eversberg ** +** ** +** data-over-voice ** +** ** +\*****************************************************************************/ + +/* + +Protocol description: + +PCM: A bit is defined as sample value. A 1 is positive level, a 0 negative. +The bit rate is 8000 Hz. + +PWM: A bit is defined by a duration between polarity change of signal. 4 +samples duration is 0, 12 samples duration is 1. + +GGGGGGGGGGGGG.... +0LLLLLLLL +0DDDDDDDD +0DDDDDDDD +.... +0CCCCCCCC +0CCCCCCCC +0CCCCCCCC +0CCCCCCCC +GGGGGGGGGGGGG.... + +G=guard / sync sequnce (bit=1) +L=length information (lsb first) +D=data (lsb first) +C=CRC (lsb first, network byte order) + +*/ + +#include "main.h" + +//#define DEBUG_DOV + +#define DOV_PWM_LEVEL 819 +#define DOV_PCM_LEVEL 100 +#define DOV_PCM_GUARD 400 +#define DOV_PWM_GUARD 34 + +#define DOV_TX_SEND_DELAY 3, 0 +#define DOV_RX_LISTEN_TIMEOUT 30, 0 + +static unsigned int dov_crc32_table[256]; + +inline unsigned int dov_crc_reflect(unsigned int ref, unsigned char ch) +{ + unsigned int value = 0; + int i; + + for (i = 1; i < ch + 1; i++) { + if ((ref & 1)) + value |= 1 << (ch - i); + ref >>= 1; + } + return value; +} + + +/* + * initialize CRC table + */ +void dov_crc_init(void) +{ + unsigned int ulPolynomial = 0x04c11db7; + int i, j; + + for (i = 0; i < 256; i++) { + dov_crc32_table[i] = dov_crc_reflect(i, 8) << 24; + for (j = 0; j < 8; j++) + dov_crc32_table[i] = (dov_crc32_table[i] << 1) ^ + (dov_crc32_table[i] & (1 << 31) ? + ulPolynomial : 0); + dov_crc32_table[i] = + dov_crc_reflect(dov_crc32_table[i], 32); + } +} + + +/* + * calculate CRC 32 of given data + * + * data: pointer to data + * length: length of data + * return: CRC 32 + */ +unsigned int dov_crc32(unsigned char *data, int length) +{ + unsigned int crc = 0xffffffff; + + while (length--) + crc = (crc >> 8) ^ dov_crc32_table[(crc & 0xff) ^ *data++]; + + return crc ^ 0xffffffff; +} + +int dov_tx_timer(struct lcr_timer *timer, void *instance, int index); +int dov_rx_timer(struct lcr_timer *timer, void *instance, int index); + +void Port::dov_init(void) +{ +#ifdef DEBUG_DOV + printf("DOV: init\n"); +#endif + + dov_crc_init(); + p_dov_tx = 0; + p_dov_rx = 0; + p_dov_tx_data = NULL; + p_dov_rx_data = NULL; + memset(&p_dov_tx_timer, 0, sizeof(p_dov_tx_timer)); + add_timer(&p_dov_tx_timer, dov_tx_timer, this, 0); + memset(&p_dov_rx_timer, 0, sizeof(p_dov_rx_timer)); + add_timer(&p_dov_rx_timer, dov_rx_timer, this, 0); +} + + +void Port::dov_reset_tx(void) +{ +#ifdef DEBUG_DOV + printf("DOV: reset TX\n"); +#endif + + if (p_dov_tx_data) + FREE(p_dov_tx_data, p_dov_tx_data_length); + p_dov_tx_data = NULL; + p_dov_tx = 0; + unsched_timer(&p_dov_tx_timer); +} + +void Port::dov_reset_rx(void) +{ +#ifdef DEBUG_DOV + printf("DOV: reset RX\n"); +#endif + + if (p_dov_rx_data) + FREE(p_dov_rx_data, 255 + 5); + p_dov_rx_data = NULL; + p_dov_rx = 0; + update_rxoff(); + unsched_timer(&p_dov_rx_timer); +} + +void Port::dov_exit(void) +{ +#ifdef DEBUG_DOV + printf("DOV: exit\n"); +#endif + + dov_reset_tx(); + del_timer(&p_dov_tx_timer); + dov_reset_rx(); + del_timer(&p_dov_rx_timer); +} + +void Port::dov_sendmsg(unsigned char *data, int length, enum dov_type type, int level) +{ + unsigned int crc; + +#ifdef DEBUG_DOV + printf("DOV: send message, start timer\n"); +#endif + + dov_reset_tx(); + + if (!length) + return; + p_dov_tx_data = (unsigned char *)MALLOC(length + 5); + p_dov_tx_data[0] = length; + memcpy(p_dov_tx_data + 1, data, length); + crc = dov_crc32(data, length); + p_dov_tx_data[length+1] = crc >> 24; + p_dov_tx_data[length+2] = crc >> 16; + p_dov_tx_data[length+3] = crc >> 8; + p_dov_tx_data[length+4] = crc; + p_dov_tx_data_length = length + 5; + p_dov_tx_data_pos = 0; + p_dov_tx_sync = 1; + p_dov_tx_bit_pos = 0; + p_dov_tx_pwm_pos = 0; + + p_dov_tx_type = type; + if (level) { + p_dov_up = audio_s16_to_law[(level) & 0xffff]; + p_dov_down = audio_s16_to_law[(-level) & 0xffff]; + } else if (type == DOV_TYPE_PWM) { + p_dov_up = audio_s16_to_law[(DOV_PWM_LEVEL) & 0xffff]; + p_dov_down = audio_s16_to_law[(-DOV_PWM_LEVEL) & 0xffff]; + } else { + p_dov_up = audio_s16_to_law[(DOV_PCM_LEVEL) & 0xffff]; + p_dov_down = audio_s16_to_law[(-DOV_PCM_LEVEL) & 0xffff]; + } + + schedule_timer(&p_dov_tx_timer, DOV_TX_SEND_DELAY); +} + +int dov_tx_timer(struct lcr_timer *timer, void *instance, int index) +{ + class Port *port = (class Port *)instance; + +#ifdef DEBUG_DOV + printf("DOV: timer fires, now sending\n"); +#endif + + port->p_dov_tx = 1; + + return 0; +} + +int Port::dov_tx(unsigned char *data, int length) +{ + int left = 0; + + if (!p_dov_tx) + return 0; + + switch (p_dov_tx_type) { + case DOV_TYPE_PWM: +#ifdef DEBUG_DOV + printf("DOV: prepare %d bytes of PWM data\n", length); +#endif + left = dov_tx_pwm(data, length); + break; + case DOV_TYPE_PCM: +#ifdef DEBUG_DOV + printf("DOV: prepare %d bytes of PCM data\n", length); +#endif + left = dov_tx_pcm(data, length); + break; + } + + return length - left; +} + +int Port::dov_tx_pwm(unsigned char *data, int length) +{ + while (length) { + /* send sync / guard sequence */ + if (p_dov_tx_sync) { + if (p_dov_tx_up) { + while (p_dov_tx_pwm_pos < 12) { + *data++ = p_dov_up; + p_dov_tx_pwm_pos++; + if (--length == 0) + return 0; + } + p_dov_tx_up = 0; + } else { + while (p_dov_tx_pwm_pos < 12) { + *data++ = p_dov_down; + p_dov_tx_pwm_pos++; + if (--length == 0) + return 0; + } + p_dov_tx_up = 1; + } + p_dov_tx_pwm_pos = 0; + if (++p_dov_tx_bit_pos == DOV_PWM_GUARD) { +#ifdef DEBUG_DOV + printf("DOV: TX, done with guard\n"); +#endif + p_dov_tx_bit_pos = -1; + if (p_dov_tx_sync == 2) { + dov_reset_tx(); + return length; + } + p_dov_tx_sync = 0; + } + continue; + } + + /* send start of byte */ + if (p_dov_tx_data_length == -1) { + if (p_dov_tx_up) { + while (p_dov_tx_pwm_pos < 4) { + *data++ = p_dov_up; + p_dov_tx_pwm_pos++; + if (--length == 0) + return 0; + } + p_dov_tx_up = 0; + } else { + while (p_dov_tx_pwm_pos < 4) { + *data++ = p_dov_down; + p_dov_tx_pwm_pos++; + if (--length == 0) + return 0; + } + p_dov_tx_up = 1; + } + p_dov_tx_pwm_pos = 0; + p_dov_tx_bit_pos = 0; + continue; + } + + /* send data */ + if ((p_dov_tx_data[p_dov_tx_data_pos] >> p_dov_tx_bit_pos) & 1) { + if (p_dov_tx_up) { + while (p_dov_tx_pwm_pos < 12) { + *data++ = p_dov_up; + p_dov_tx_pwm_pos++; + if (--length == 0) + return 0; + } + p_dov_tx_up = 0; + } else { + while (p_dov_tx_pwm_pos < 12) { + *data++ = p_dov_down; + p_dov_tx_pwm_pos++; + if (--length == 0) + return 0; + } + p_dov_tx_up = 1; + } + } else { + if (p_dov_tx_up) { + while (p_dov_tx_pwm_pos < 4) { + *data++ = p_dov_up; + p_dov_tx_pwm_pos++; + if (--length == 0) + return 0; + } + p_dov_tx_up = 0; + } else { + while (p_dov_tx_pwm_pos < 4) { + *data++ = p_dov_down; + p_dov_tx_pwm_pos++; + if (--length == 0) + return 0; + } + p_dov_tx_up = 1; + } + } + p_dov_tx_pwm_pos = 0; + if (++p_dov_tx_bit_pos == 8) { + p_dov_tx_bit_pos = -1; +#ifdef DEBUG_DOV + printf("DOV: TX, done with byte %d\n", p_dov_tx_data[p_dov_tx_data_pos]); +#endif + if (p_dov_tx_data_pos++ == p_dov_tx_data_length) { + p_dov_tx_sync = 2; + } + } + } + + return 0; +} + +int Port::dov_tx_pcm(unsigned char *data, int length) +{ + while (length--) { + /* send sync / guard sequence */ + if (p_dov_tx_sync) { + *data++ = p_dov_up; + if (++p_dov_tx_bit_pos == DOV_PCM_GUARD) { +#ifdef DEBUG_DOV + printf("DOV: TX, done with guard\n"); +#endif + p_dov_tx_bit_pos = -1; + if (p_dov_tx_sync == 2) { + dov_reset_tx(); + return length; + } + p_dov_tx_sync = 0; + } + continue; + } + + /* send start of byte */ + if (p_dov_tx_data_length == -1) { + *data++ = p_dov_down; + p_dov_tx_bit_pos = 0; + continue; + } + + /* send data */ + *data++ = (((p_dov_tx_data[p_dov_tx_data_pos] >> p_dov_tx_bit_pos) & 1)) ? p_dov_up : p_dov_down; + if (++p_dov_tx_bit_pos == 8) { + p_dov_tx_bit_pos = -1; +#ifdef DEBUG_DOV + printf("DOV: TX, done with byte %d\n", p_dov_tx_data[p_dov_tx_data_pos]); +#endif + if (p_dov_tx_data_pos++ == p_dov_tx_data_length) { + p_dov_tx_sync = 2; + } + } + } + + return 0; +} + +void Port::dov_listen(enum dov_type type) +{ +#ifdef DEBUG_DOV + printf("DOV: start listening, start timer\n"); +#endif + + dov_reset_rx(); + + p_dov_rx_data = (unsigned char *)MALLOC(255 + 5); + p_dov_rx_data_pos = 0; + p_dov_rx_sync = 1; + p_dov_rx_bit_pos = 0; + p_dov_rx_pwm_pos = 0; + p_dov_rx_pwm_duration = 0; + p_dov_rx_pwm_polarity = 0; + p_dov_rx_sync_word = 0; + + p_dov_rx_type = type; + + p_dov_rx = 1; + update_rxoff(); + + schedule_timer(&p_dov_rx_timer, DOV_RX_LISTEN_TIMEOUT); +} + +int dov_rx_timer(struct lcr_timer *timer, void *instance, int index) +{ + class Port *port = (class Port *)instance; + +#ifdef DEBUG_DOV + printf("DOV: timer fires, now stop listening\n"); +#endif + + port->dov_reset_rx(); + + return 0; +} + +void Port::dov_rx(unsigned char *data, int length) +{ + if (!p_dov_rx) + return; + + switch (p_dov_rx_type) { + case DOV_TYPE_PWM: +#ifdef DEBUG_DOV + printf("DOV: received %d bytes of PWM data\n", length); +#endif + dov_rx_pwm(data, length); + break; + case DOV_TYPE_PCM: +#ifdef DEBUG_DOV + printf("DOV: received %d bytes of PCM data\n", length); +#endif + dov_rx_pcm(data, length); + break; + } +} + +void Port::dov_rx_pwm(unsigned char *data, int length) +{ + signed int sample; + signed int level; + + while (length--) { + sample = audio_law_to_s32[*data++]; + p_dov_rx_pwm_duration++; + if (p_dov_rx_pwm_polarity == 1) { + if (sample > 0) + continue; + p_dov_rx_pwm_polarity = 0; + if (p_dov_rx_pwm_duration < 8) + level = 0; + else + level = 1; + p_dov_rx_pwm_duration = 0; + } else { + if (sample <= 0) + continue; + p_dov_rx_pwm_polarity = 1; + if (p_dov_rx_pwm_duration < 8) + level = 0; + else + level = 1; + p_dov_rx_pwm_duration = 0; + } + + /* catch sync */ + p_dov_rx_sync_word <<= 1; + if (level > 0) + p_dov_rx_sync_word |= 1; + if ((p_dov_rx_sync_word & 0x1ff) == 0x1ff) { + p_dov_rx_bit_pos = -1; + p_dov_rx_sync = 1; + p_dov_rx_data_pos = 0; + continue; + } + /* wait for sync */ + if (!p_dov_rx_sync) { + continue; + } + /* read start bit */ + if (p_dov_rx_bit_pos == -1) { + /* check violation of start bit */ + if (level > 0) { + p_dov_rx_sync = 0; + continue; + } + p_dov_rx_bit_pos = 0; + continue; + } + /* read data */ + p_dov_rx_data[p_dov_rx_data_pos] >>= 1; + if (level > 0) + p_dov_rx_data[p_dov_rx_data_pos] |= 128; + if (++p_dov_rx_bit_pos == 8) { +#ifdef DEBUG_DOV + printf("DOV: RX byte %d\n", p_dov_rx_data[p_dov_rx_data_pos]); +#endif + p_dov_rx_bit_pos = -1; + /* check for length,data,crc32 */ + if (++p_dov_rx_data_pos == p_dov_rx_data[0] + 5) { + dov_message(p_dov_rx_data + 1, p_dov_rx_data[0]); + p_dov_rx_sync = 0; + } + } + } + +} + +void Port::dov_rx_pcm(unsigned char *data, int length) +{ + signed int level; + + while (length--) { + level = audio_law_to_s32[*data++]; + /* catch sync */ + p_dov_rx_sync_word <<= 1; + if (level > 0) + p_dov_rx_sync_word |= 1; + if ((p_dov_rx_sync_word & 0x1ff) == 0x1ff) { + p_dov_rx_bit_pos = -1; + p_dov_rx_sync = 1; + p_dov_rx_data_pos = 0; + continue; + } + /* wait for sync */ + if (!p_dov_rx_sync) { + continue; + } + /* read start bit */ + if (p_dov_rx_bit_pos == -1) { + /* check violation of start bit */ + if (level > 0) { + p_dov_rx_sync = 0; + continue; + } + p_dov_rx_bit_pos = 0; + continue; + } + /* read data */ + p_dov_rx_data[p_dov_rx_data_pos] >>= 1; + if (level > 0) + p_dov_rx_data[p_dov_rx_data_pos] |= 128; + if (++p_dov_rx_bit_pos == 8) { +#ifdef DEBUG_DOV + printf("DOV: RX byte %d\n", p_dov_rx_data[p_dov_rx_data_pos]); +#endif + p_dov_rx_bit_pos = -1; + /* check for length,data,crc32 */ + if (++p_dov_rx_data_pos == p_dov_rx_data[0] + 5) { + dov_message(p_dov_rx_data + 1, p_dov_rx_data[0]); + p_dov_rx_sync = 0; + } + } + } +} + +void Port::dov_message(unsigned char *data, int length) +{ + unsigned int crc; + struct lcr_msg *message; + + /* prevent receiving zeroes (due to line noise). this would cause 0 crc, which seems correct. */ + if (length == 0) + return; + +#ifdef DEBUG_DOV + printf("DOV: received message\n"); +#endif + + crc = dov_crc32(p_dov_rx_data + 1, p_dov_rx_data[0]); + if (crc != (unsigned int) ( ((p_dov_rx_data[length+1]) << 24) | + ((p_dov_rx_data[length+2]) << 16) | + ((p_dov_rx_data[length+3]) << 8) | + (p_dov_rx_data[length+4]) )) + return; + +#ifdef DEBUG_DOV + printf("DOV: crc OK\n"); +#endif + + message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_DOV_INDICATION); + message->param.dov.type = p_dov_rx_type; + message->param.dov.length = p_dov_rx_data[0]; + memcpy(message->param.dov.data, p_dov_rx_data + 1, p_dov_rx_data[0]); + PDEBUG(DEBUG_PORT, "PmISDN(%s) Data-Over-Voice message received (len=%d)\n", p_name, message->param.dov.length); + message_put(message); + + dov_reset_rx(); +} + diff --git a/extension.c b/extension.c index 7332551..72ca1aa 100644 --- a/extension.c +++ b/extension.c @@ -759,6 +759,31 @@ int read_extension(struct extension *ext, char *num) if (!strcmp(option,"otp-ident")) { SCPY(ext->otp_ident, param); PDEBUG(DEBUG_CONFIG, "otp-ident: %s\n",param); + } else + if (!strcmp(option,"dov_ident")) { + if (param[0]) { + SCPY(ext->dov_ident, param); + PDEBUG(DEBUG_CONFIG, "dov_ident string: %s\n",param); + } + } else + if (!strcmp(option,"dov_log")) { + if (param[0]) { + SCPY(ext->dov_log, param); + PDEBUG(DEBUG_CONFIG, "dov_log filename: %s\n",param); + } + } else + if (!strcmp(option,"dov_type")) { + if (!strcasecmp(param, "pcm")) + ext->dov_type = DOV_TYPE_PCM; + else + ext->dov_type = DOV_TYPE_PWM; + PDEBUG(DEBUG_CONFIG, "given dov type: %s\n", param); + } else + if (!strcmp(option,"dov_level")) { + if (atoi(param)) { + ext->dov_level = atoi(param); + PDEBUG(DEBUG_CONFIG, "dov_level: %s\n",param); + } } else { PERROR_RUNTIME("Error in %s (line %d): wrong option keyword %s.\n",filename,line,option); } @@ -1166,6 +1191,18 @@ int write_extension(struct extension *ext, char *number) } fprintf(fp,"\n"); + fprintf(fp,"# Identify to/from remove via Data-Over-Voice feature.\n"); + fprintf(fp,"dov_ident %s\n", ext->dov_ident); + fprintf(fp,"dov_log %s\n", ext->dov_log); + switch(ext->dov_type) { + case DOV_TYPE_PWM: + fprintf(fp,"dov_type pwm\n"); + break; + case DOV_TYPE_PCM: + fprintf(fp,"dov_type pcm\n"); + break; + } + fprintf(fp,"dov_level %d\n\n", ext->dov_level); if (fp) fclose(fp); return(1); diff --git a/extension.h b/extension.h index 29f81b1..4de6952 100644 --- a/extension.h +++ b/extension.h @@ -171,6 +171,10 @@ struct extension { int no_seconds; /* don't include seconds in the connect message */ char otp_ident[9]; /* up to 8 bytes of ident */ + char dov_ident[256]; /* ident string to be sent to remote via Data-Over-Voice */ + char dov_log[256]; /* log file to store received and sent Data-Over-Voice messages */ + int dov_type; /* type of modulation */ + int dov_level; /* amplitude of signal */ }; int read_extension(struct extension *ext, char *number); diff --git a/mISDN.cpp b/mISDN.cpp index bac0e84..a7facb6 100644 --- a/mISDN.cpp +++ b/mISDN.cpp @@ -965,7 +965,7 @@ void PmISDN::load_tx(void) p_m_load = 0; /* to send data, tone must be on */ - if ((p_tone_name[0] || p_m_crypt_msg_loops || p_m_inband_send_on) /* what tones? */ + if ((p_tone_name[0] || p_dov_tx || p_m_crypt_msg_loops || p_m_inband_send_on) /* what tones? */ && (p_m_load < p_m_preload) /* not too much load? */ && (p_state==PORT_STATE_CONNECT || p_m_mISDNport->tones || p_m_inband_send_on)) { /* connected or inband-tones? */ int tosend = p_m_preload - p_m_load, length; @@ -1005,6 +1005,11 @@ void PmISDN::load_tx(void) tosend -= length; } + /* copy dov */ + if (p_dov_tx) { + tosend -= dov_tx(p, tosend); + } + /* copy tones */ if (p_tone_name[0] && tosend) { tosend -= read_audio(p, tosend); @@ -1142,6 +1147,10 @@ void PmISDN::bchannel_receive(struct mISDNhead *hh, unsigned char *data, int len return; } + /* dov is processed */ + if (p_dov_rx) + dov_rx(data, len); + /* inband is processed */ if (p_m_inband_receive_on) inband_receive(data, len); @@ -1500,7 +1509,7 @@ void PmISDN::update_rxoff(void) int tx_dejitter = 0; /* call bridges in user space OR crypto OR recording */ - if (p_bridge || p_m_crypt_msg_loops || p_m_crypt_listen || p_record || p_tap || p_m_inband_receive_on) { + if (p_bridge || p_m_crypt_msg_loops || p_m_crypt_listen || p_record || p_tap || p_m_inband_receive_on || p_dov_rx) { /* rx IS required */ if (p_m_rxoff) { /* turn on RX */ @@ -2313,7 +2322,7 @@ int PmISDN::bridge_rx(unsigned char *data, int length) /* check if high priority tones exist * ignore data in this case */ - if (p_tone_name[0] || p_m_crypt_msg_loops || p_m_inband_send_on) + if (p_tone_name[0] || p_dov_tx || p_m_crypt_msg_loops || p_m_inband_send_on) return -EBUSY; /* preload procedure diff --git a/message.h b/message.h index 38ee5da..47952ac 100644 --- a/message.h +++ b/message.h @@ -357,6 +357,13 @@ struct param_vootp { char id[32]; }; +struct param_dov { + int type; /* dov_type coding */ + int level; /* volume of sending signals */ + int length; + unsigned char data[255]; +}; + /* structure of message parameter */ union parameter { struct param_tone tone; /* MESSAGE_TONE */ @@ -384,6 +391,7 @@ union parameter { unsigned int bridge_id; /* MESSAGE_BRIDGE */ struct param_traffic traffic; /* MESSAGE_TRAFFIC */ struct param_3pty threepty; /* MESSAGE_TRAFFIC */ + struct param_dov dov; /* MESSAGE_DOV */ unsigned int queue; /* MESSAGE_DISABLE_DEJITTER */ struct param_vootp vootp; /* MESSAGE_VOOTP */ }; @@ -445,6 +453,9 @@ enum { /* messages between entities */ MESSAGE_DISABLE_DEJITTER,/* tell (mISDN) port not to dejitter */ MESSAGE_UPDATEBRIDGE, /* tell join to update bridge. (sent by mISDN port) */ MESSAGE_VOOTP, /* enable/disable VoOTP */ + MESSAGE_DOV_INDICATION, /* data over voice message received */ + MESSAGE_DOV_REQUEST, /* sending data over voice message */ + MESSAGE_DOV_LISTEN, /* listen order to data over voice message */ }; #define MESSAGES static const char *messages_txt[] = { \ @@ -486,6 +497,9 @@ enum { /* messages between entities */ "MESSAGE_DISABLE_DEJITTER", \ "MESSAGE_UPDATEBRIDGE", \ "MESSAGE_VOOTP", \ + "MESSAGE_DOV_INDIVATION", \ + "MESSAGE_DOV_REQUEST", \ + "MESSAGE_DOV_LISTEN", \ }; diff --git a/port.cpp b/port.cpp index 16fcd72..32184a8 100644 --- a/port.cpp +++ b/port.cpp @@ -192,6 +192,8 @@ Port::Port(int type, const char *portname, struct port_settings *settings, struc #ifdef WITH_VOOTP p_vootp = NULL; #endif + /* D-O-V */ + dov_init(); /* append port to chain */ next = NULL; @@ -234,6 +236,8 @@ Port::~Port(void) if (p_record) close_record(0, 0); + dov_exit(); + classuse--; /* disconnect port from endpoint */ @@ -660,6 +664,16 @@ int Port::message_epoint(unsigned int epoint_id, int message_id, union parameter set_vootp(¶m->vootp); return 1; #endif + + case MESSAGE_DOV_REQUEST: /* Data-Over-Voice message */ + PDEBUG(DEBUG_PORT, "PORT(%s) sending data over voice message (len=%d)\n", p_name, param->dov.length); + dov_sendmsg(param->dov.data, param->dov.length, (enum dov_type)param->dov.type, param->dov.level); + return 1; + + case MESSAGE_DOV_LISTEN: /* Data-Over-Voice listen order */ + PDEBUG(DEBUG_PORT, "PORT(%s) sending data over voice listen order\n", p_name); + dov_listen((enum dov_type)param->dov.type); + return 1; } return 0; diff --git a/port.h b/port.h index 56bd47b..cc7b071 100644 --- a/port.h +++ b/port.h @@ -182,6 +182,11 @@ struct port_bridge { extern struct port_bridge *p_bridge_first; +enum dov_type { + DOV_TYPE_PWM, + DOV_TYPE_PCM, +}; + /* generic port class */ class Port { @@ -276,6 +281,36 @@ class Port void set_vootp(struct param_vootp *vootp); #endif + /* DOV */ + int p_dov_tx, p_dov_rx; + int p_dov_tx_sync, p_dov_rx_sync; + enum dov_type p_dov_tx_type, p_dov_rx_type; + unsigned char *p_dov_tx_data, *p_dov_rx_data; + int p_dov_tx_data_length; + int p_dov_tx_data_pos, p_dov_rx_data_pos; + int p_dov_tx_bit_pos, p_dov_rx_bit_pos; + int p_dov_tx_pwm_pos, p_dov_rx_pwm_pos; + int p_dov_rx_pwm_duration, p_dov_rx_pwm_polarity; + int p_dov_tx_up; + int p_dov_rx_sync_word; + unsigned char p_dov_up; + unsigned char p_dov_down; + void dov_init(void); + void dov_exit(void); + void dov_reset_tx(void); + void dov_reset_rx(void); + struct lcr_timer p_dov_tx_timer; + struct lcr_timer p_dov_rx_timer; + void dov_sendmsg(unsigned char *data, int length, enum dov_type type, int level); + int dov_tx(unsigned char *data, int length); + int dov_tx_pcm(unsigned char *data, int length); + int dov_tx_pwm(unsigned char *data, int length); + void dov_listen(enum dov_type type); + void dov_rx(unsigned char *data, int length); + void dov_rx_pcm(unsigned char *data, int length); + void dov_rx_pwm(unsigned char *data, int length); + void dov_message(unsigned char *data, int length); + void free_epointlist(struct epoint_list *epointlist); void free_epointid(unsigned int epoint_id); struct epoint_list *epointlist_new(unsigned int epoint_id); diff --git a/remote.cpp b/remote.cpp index 273779c..cb6e528 100644 --- a/remote.cpp +++ b/remote.cpp @@ -140,6 +140,8 @@ void Premote::message_remote(int message_type, union parameter *param) switch (message_type) { case MESSAGE_TRAFFIC: + if (p_dov_rx) + dov_rx(param->traffic.data, param->traffic.len); /* record audio */ if (p_record) record(param->traffic.data, param->traffic.len, 0); // from down @@ -154,6 +156,10 @@ void Premote::message_remote(int message_type, union parameter *param) if (p_tap) tap(param->traffic.data, param->traffic.len, 1); // from up admin_message_from_lcr(p_r_remote_id, p_r_ref, MESSAGE_TRAFFIC, param); + } else if (p_dov_tx) { + /* use receeived traffic to trigger sending DOV */ + dov_tx(param->traffic.data, param->traffic.len); + admin_message_from_lcr(p_r_remote_id, p_r_ref, MESSAGE_TRAFFIC, param); } return; diff --git a/sip.cpp b/sip.cpp index 640631b..6dbb4b3 100644 --- a/sip.cpp +++ b/sip.cpp @@ -280,6 +280,8 @@ we only support alaw and ulaw! } while(n--) *to++ = flip[*from++]; + if (psip->p_dov_rx) + psip->dov_rx(payload, payload_len); psip->bridge_tx(payload, payload_len); return 0; @@ -600,7 +602,10 @@ int Psip::bridge_rx(unsigned char *data, int len) int ret; /* don't bridge, if tones are provided */ - if (p_tone_name[0]) + if (p_tone_name[0] || p_dov_tx) + return -EBUSY; + + if (p_dov_tx) return -EBUSY; if ((ret = Port::bridge_rx(data, len))) @@ -2045,7 +2050,7 @@ void Psip::update_load(void) return; /* don't start timer if ... */ - if (!p_tone_name[0]) + if (!p_tone_name[0] && !p_dov_tx) return; p_s_next_tv_sec = 0; @@ -2057,7 +2062,7 @@ static int load_timer(struct lcr_timer *timer, void *instance, int index) class Psip *psip = (class Psip *)instance; /* stop timer if ... */ - if (!psip->p_tone_name[0]) + if (!psip->p_tone_name[0] && !psip->p_dov_tx) return 0; psip->load_tx(); @@ -2111,6 +2116,9 @@ void Psip::load_tx(void) /* copy tones */ if (p_tone_name[0]) { tosend -= read_audio(p, tosend); + } else + if (p_dov_tx) { + tosend -= dov_tx(p, tosend); } if (tosend) { PERROR("buffer is not completely filled\n"); -- 2.13.6