Data-Over-Voice
[lcr.git] / dov.cpp
diff --git a/dov.cpp b/dov.cpp
new file mode 100644 (file)
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();
+}
+