diff spandsp-0.0.6pre17/src/v8.c @ 4:26cd8f1ef0b1

import spandsp-0.0.6pre17
author Peter Meerwald <pmeerw@cosy.sbg.ac.at>
date Fri, 25 Jun 2010 15:50:58 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spandsp-0.0.6pre17/src/v8.c	Fri Jun 25 15:50:58 2010 +0200
@@ -0,0 +1,1089 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * v8.c - V.8 modem negotiation processing.
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2004 Steve Underwood
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1,
+ * as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * $Id: v8.c,v 1.42.4.3 2009/12/28 12:20:47 steveu Exp $
+ */
+ 
+/*! \file */
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <memory.h>
+#if defined(HAVE_TGMATH_H)
+#include <tgmath.h>
+#endif
+#if defined(HAVE_MATH_H)
+#include <math.h>
+#endif
+#include "floating_fudge.h"
+
+#include "spandsp/telephony.h"
+#include "spandsp/logging.h"
+#include "spandsp/queue.h"
+#include "spandsp/async.h"
+#include "spandsp/vector_int.h"
+#include "spandsp/complex.h"
+#include "spandsp/dds.h"
+#include "spandsp/tone_detect.h"
+#include "spandsp/tone_generate.h"
+#include "spandsp/super_tone_rx.h"
+#include "spandsp/power_meter.h"
+#include "spandsp/fsk.h"
+#include "spandsp/modem_connect_tones.h"
+#include "spandsp/v8.h"
+
+#include "spandsp/private/logging.h"
+#include "spandsp/private/fsk.h"
+#include "spandsp/private/modem_connect_tones.h"
+#include "spandsp/private/v8.h"
+
+enum
+{
+    V8_WAIT_1S,
+    V8_CI_ON,
+    V8_CI_OFF,
+    V8_HEARD_ANSAM,
+    V8_CM_ON,
+    V8_CJ_ON,
+    V8_CM_WAIT,
+
+    V8_SIGC,
+    V8_JM_ON,
+    V8_SIGA,
+
+    V8_PARKED
+} v8_states_e;
+
+enum
+{
+    V8_SYNC_UNKNOWN = 0,
+    V8_SYNC_CI,
+    V8_SYNC_CM_JM,
+    V8_SYNC_V92
+} v8_sync_types_e;
+
+enum
+{
+    V8_CALL_FUNCTION_TAG = 0x01,
+    V8_MODULATION_TAG = 0x05,
+    V8_PROTOCOLS_TAG = 0x0A,
+    V8_PSTN_ACCESS_TAG = 0x0D,
+    V8_NSF_TAG = 0x0F,
+    V8_PCM_MODEM_AVAILABILITY_TAG = 0x07,
+    V8_T66_TAG = 0x0E
+};
+
+enum
+{
+    V8_CI_SYNC_OCTET = 0x00,
+    V8_CM_JM_SYNC_OCTET = 0xE0
+};
+
+SPAN_DECLARE(const char *) v8_call_function_to_str(int call_function)
+{
+    switch (call_function)
+    {
+    case V8_CALL_TBS:
+        return "TBS";
+    case V8_CALL_H324:
+        return "H.324 PSTN multimedia terminal";
+    case V8_CALL_V18:
+        return "V.18 textphone";
+    case V8_CALL_T101:
+        return "T.101 videotext";
+    case V8_CALL_T30_TX:
+        return "T.30 Tx FAX";
+    case V8_CALL_T30_RX:
+        return "T.30 Rx FAX";
+    case V8_CALL_V_SERIES:
+        return "V series modem data";
+    case V8_CALL_FUNCTION_EXTENSION:
+        return "Call function is in extention octet";
+    }
+    return "???";
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) v8_modulation_to_str(int modulation_scheme)
+{
+    switch (modulation_scheme)
+    {
+    case V8_MOD_V17:
+        return "V.17 half-duplex";
+    case V8_MOD_V21:
+        return "V.21 duplex";
+    case V8_MOD_V22:
+        return "V.22/V.22bis duplex";
+    case V8_MOD_V23HALF:
+        return "V.23 half-duplex";
+    case V8_MOD_V23:
+        return "V.23 duplex";
+    case V8_MOD_V26BIS:
+        return "V.26bis duplex";
+    case V8_MOD_V26TER:
+        return "V.26ter duplex";
+    case V8_MOD_V27TER:
+        return "V.27ter duplex";
+    case V8_MOD_V29:
+        return "V.29 half-duplex";
+    case V8_MOD_V32:
+        return "V.32/V.32bis duplex";
+    case V8_MOD_V34HALF:
+        return "V.34 half-duplex";
+    case V8_MOD_V34:
+        return "V.34 duplex";
+    case V8_MOD_V90:
+        return "V.90 duplex";
+    case V8_MOD_V92:
+        return "V.92 duplex";
+    case V8_MOD_FAILED:
+        return "negotiation failed";
+    }
+    return "???";
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) v8_protocol_to_str(int protocol)
+{
+    switch (protocol)
+    {
+    case V8_PROTOCOL_NONE:
+        return "None";
+    case V8_PROTOCOL_LAPM_V42:
+        return "LAPM";
+    case V8_PROTOCOL_EXTENSION:
+        return "Extension";
+    }
+    return "Undefined";
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) v8_pstn_access_to_str(int pstn_access)
+{
+    switch (pstn_access)
+    {
+    case V8_PSTN_ACCESS_CALL_DCE_CELLULAR:
+        return "Calling modem on cellular";
+    case V8_PSTN_ACCESS_ANSWER_DCE_CELLULAR:
+        return "Answering modem on cellular";
+    case V8_PSTN_ACCESS_ANSWER_DCE_CELLULAR | V8_PSTN_ACCESS_CALL_DCE_CELLULAR:
+        return "Answering and calling modems on cellular";
+    case V8_PSTN_ACCESS_DCE_ON_DIGITAL:
+        return "DCE on digital";
+    case V8_PSTN_ACCESS_DCE_ON_DIGITAL | V8_PSTN_ACCESS_CALL_DCE_CELLULAR:
+        return "DCE on digital, and calling modem on cellular";
+    case V8_PSTN_ACCESS_DCE_ON_DIGITAL | V8_PSTN_ACCESS_ANSWER_DCE_CELLULAR:
+        return "DCE on digital, answering modem on cellular";
+    case V8_PSTN_ACCESS_DCE_ON_DIGITAL | V8_PSTN_ACCESS_ANSWER_DCE_CELLULAR | V8_PSTN_ACCESS_CALL_DCE_CELLULAR:
+        return "DCE on digital, and answering and calling modems on cellular";
+    }
+    return "???";
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) v8_nsf_to_str(int nsf)
+{
+    switch (nsf)
+    {
+    case 0:
+        return "???";
+    }
+    return "???";
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) v8_pcm_modem_availability_to_str(int pcm_modem_availability)
+{
+    switch (pcm_modem_availability)
+    {
+    case 0:
+        return "PCM unavailable";
+    case V8_PSTN_PCM_MODEM_V90_V92_ANALOGUE:
+        return "V.90/V.92 analogue available";
+    case V8_PSTN_PCM_MODEM_V90_V92_DIGITAL:
+        return "V.90/V.92 digital available";
+    case V8_PSTN_PCM_MODEM_V90_V92_DIGITAL | V8_PSTN_PCM_MODEM_V90_V92_ANALOGUE:
+        return "V.90/V.92 digital/analogue available";
+    case V8_PSTN_PCM_MODEM_V91:
+        return "V.91 available";
+    case V8_PSTN_PCM_MODEM_V91 | V8_PSTN_PCM_MODEM_V90_V92_ANALOGUE:
+        return "V.91 and V.90/V.92 analogue available";
+    case V8_PSTN_PCM_MODEM_V91 | V8_PSTN_PCM_MODEM_V90_V92_DIGITAL:
+        return "V.91 and V.90/V.92 digital available";
+    case V8_PSTN_PCM_MODEM_V91 | V8_PSTN_PCM_MODEM_V90_V92_DIGITAL | V8_PSTN_PCM_MODEM_V90_V92_ANALOGUE:
+        return "V.91 and V.90/V.92 digital/analogue available";
+    }
+    return "???";
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) v8_t66_to_str(int t66)
+{
+    /* T.66 doesn't really define any V.8 values. The bits are all reserved. */
+    switch (t66)
+    {
+    case 0:
+        return "???";
+    case 1:
+        return "Reserved TIA";
+    case 2:
+        return "Reserved";
+    case 3:
+        return "Reserved TIA + others";
+    case 4:
+        return "Reserved";
+    case 5:
+        return "Reserved TIA + others";
+    case 6:
+        return "Reserved";
+    case 7:
+        return "Reserved TIA + others";
+    }
+    return "???";
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) v8_log_supported_modulations(v8_state_t *s, int modulation_schemes)
+{
+    const char *comma;
+    int i;
+    
+    comma = "";
+    span_log(&s->logging, SPAN_LOG_FLOW, "");
+    for (i = 0;  i < 32;  i++)
+    {
+        if ((modulation_schemes & (1 << i)))
+        {
+            span_log(&s->logging, SPAN_LOG_FLOW | SPAN_LOG_SUPPRESS_LABELLING, "%s%s", comma, v8_modulation_to_str(modulation_schemes & (1 << i)));
+            comma = ", ";
+        }
+    }
+    span_log(&s->logging, SPAN_LOG_FLOW | SPAN_LOG_SUPPRESS_LABELLING, " supported\n");
+}
+/*- End of function --------------------------------------------------------*/
+
+static const uint8_t *process_call_function(v8_state_t *s, const uint8_t *p)
+{
+    s->result.call_function = (*p >> 5) & 0x07;
+    span_log(&s->logging, SPAN_LOG_FLOW, "%s\n", v8_call_function_to_str(s->result.call_function));
+    return ++p;
+}
+/*- End of function --------------------------------------------------------*/
+
+static const uint8_t *process_modulation_mode(v8_state_t *s, const uint8_t *p)
+{
+    unsigned int far_end_modulations;
+
+    /* Modulation mode octet */
+    far_end_modulations = 0;
+    if (*p & 0x80)
+        far_end_modulations |= V8_MOD_V34HALF;
+    if (*p & 0x40)
+        far_end_modulations |= V8_MOD_V34;
+    if (*p & 0x20)
+        far_end_modulations |= V8_MOD_V90;
+
+    /* Check for an extension octet */
+    if ((*++p & 0x38) == 0x10)
+    {
+        if (*p & 0x80)
+            far_end_modulations |= V8_MOD_V27TER;
+        if (*p & 0x40)
+            far_end_modulations |= V8_MOD_V29;
+        if (*p & 0x04)
+            far_end_modulations |= V8_MOD_V17;
+        if (*p & 0x02)
+            far_end_modulations |= V8_MOD_V22;
+        if (*p & 0x01)
+            far_end_modulations |= V8_MOD_V32;
+
+        /* Check for an extension octet */
+        if ((*++p & 0x38) == 0x10)
+        {
+            if (*p & 0x80)
+                far_end_modulations |= V8_MOD_V21;
+            if (*p & 0x40)
+                far_end_modulations |= V8_MOD_V23HALF;
+            if (*p & 0x04)
+                far_end_modulations |= V8_MOD_V23;
+            if (*p & 0x02)
+                far_end_modulations |= V8_MOD_V26BIS;
+            if (*p & 0x01)
+                far_end_modulations |= V8_MOD_V26TER;
+        }
+    }
+    s->far_end_modulations =
+    s->result.modulations = far_end_modulations;
+    v8_log_supported_modulations(s, far_end_modulations);
+    return ++p;
+}
+/*- End of function --------------------------------------------------------*/
+
+static const uint8_t *process_protocols(v8_state_t *s, const uint8_t *p)
+{
+    s->result.protocol = (*p >> 5) & 0x07;
+    span_log(&s->logging, SPAN_LOG_FLOW | SPAN_LOG_SUPPRESS_LABELLING, "%s\n", v8_protocol_to_str(s->result.protocol));
+    return ++p;
+}
+/*- End of function --------------------------------------------------------*/
+
+static const uint8_t *process_pstn_access(v8_state_t *s, const uint8_t *p)
+{
+    s->result.pstn_access = (*p >> 5) & 0x07;
+    span_log(&s->logging, SPAN_LOG_FLOW | SPAN_LOG_SUPPRESS_LABELLING, "%s\n", v8_pstn_access_to_str(s->result.pstn_access));
+    return ++p;
+}
+/*- End of function --------------------------------------------------------*/
+
+static const uint8_t *process_non_standard_facilities(v8_state_t *s, const uint8_t *p)
+{
+    s->result.nsf = (*p >> 5) & 0x07;
+    span_log(&s->logging, SPAN_LOG_FLOW | SPAN_LOG_SUPPRESS_LABELLING, "%s\n", v8_nsf_to_str(s->result.nsf));
+    return p;
+}
+/*- End of function --------------------------------------------------------*/
+
+static const uint8_t *process_pcm_modem_availability(v8_state_t *s, const uint8_t *p)
+{
+    s->result.pcm_modem_availability = (*p >> 5) & 0x07;
+    span_log(&s->logging, SPAN_LOG_FLOW | SPAN_LOG_SUPPRESS_LABELLING, "%s\n", v8_pcm_modem_availability_to_str(s->result.pcm_modem_availability));
+    return ++p;
+}
+/*- End of function --------------------------------------------------------*/
+
+static const uint8_t *process_t66(v8_state_t *s, const uint8_t *p)
+{
+    s->result.t66 = (*p >> 5) & 0x07;
+    span_log(&s->logging, SPAN_LOG_FLOW | SPAN_LOG_SUPPRESS_LABELLING, "%s\n", v8_t66_to_str(s->result.t66));
+    return ++p;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void ci_decode(v8_state_t *s)
+{
+    if ((s->rx_data[0] & 0x1F) == V8_CALL_FUNCTION_TAG)
+        process_call_function(s, &s->rx_data[0]);
+}
+/*- End of function --------------------------------------------------------*/
+
+static void cm_jm_decode(v8_state_t *s)
+{
+    const uint8_t *p;
+
+    if (s->got_cm_jm)
+        return;
+
+    /* We must receive two consecutive identical CM or JM sequences to accept it. */
+    if (s->cm_jm_len <= 0
+        ||
+        s->cm_jm_len != s->rx_data_ptr
+        ||
+        memcmp(s->cm_jm_data, s->rx_data, s->rx_data_ptr))
+    {
+        /* Save the current CM or JM sequence */
+        s->cm_jm_len = s->rx_data_ptr;
+        memcpy(s->cm_jm_data, s->rx_data, s->rx_data_ptr);
+        return;
+    }
+    /* We have a matching pair of CMs or JMs, so we are happy this is correct. */
+    s->got_cm_jm = TRUE;
+
+    span_log(&s->logging, SPAN_LOG_FLOW, "Decoding\n");
+
+    /* Zero indicates the end */
+    s->cm_jm_data[s->cm_jm_len] = 0;
+
+    s->result.modulations = 0;
+    p = s->cm_jm_data;
+
+    while (*p)
+    {
+        switch (*p & 0x1F)
+        {
+        case V8_CALL_FUNCTION_TAG:
+            p = process_call_function(s, p);
+            break;
+        case V8_MODULATION_TAG:
+            p = process_modulation_mode(s, p);
+            break;
+        case V8_PROTOCOLS_TAG:
+            p = process_protocols(s, p);
+            break;
+        case V8_PSTN_ACCESS_TAG:
+            p = process_pstn_access(s, p);
+            break;
+        case V8_NSF_TAG:
+            p = process_non_standard_facilities(s, p);
+            break;
+        case V8_PCM_MODEM_AVAILABILITY_TAG:
+            p = process_pcm_modem_availability(s, p);
+            break;
+        case V8_T66_TAG:
+            p = process_t66(s, p);
+            break;
+        default:
+            p++;
+            break;
+        }
+        /* Skip any future extensions we do not understand */
+        while ((*p & 0x38) == 0x10)
+            p++;
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+static void put_bit(void *user_data, int bit)
+{
+    v8_state_t *s;
+    int new_preamble_type;
+    const char *tag;
+    uint8_t data;
+
+    s = user_data;
+    if (bit < 0)
+    {
+        /* Special conditions */
+        switch (bit)
+        {
+        case SIG_STATUS_CARRIER_UP:
+        case SIG_STATUS_CARRIER_DOWN:
+        case SIG_STATUS_TRAINING_SUCCEEDED:
+        case SIG_STATUS_TRAINING_FAILED:
+            break;
+        default:
+            break;
+        }
+        return;
+    }
+    //span_log(&s->logging, SPAN_LOG_FLOW, "Bit %d\n", bit);
+    /* Wait until we sync. */
+    s->bit_stream = (s->bit_stream >> 1) | (bit << 19);
+    /* CI preamble is 10 ones then a framed 0x00
+       CM/JM preamble is 10 ones then a framed 0x07
+       V.92 preamble is 10 ones then a framed 0x55
+       Should we look at all 10 ones? The first couple might be
+       settling down. */
+    /* The preamble + synchronisation bit sequence should be unique in
+       any bit stream, so we can rely on seeing this at any time as being
+       a real sync code. */
+    switch (s->bit_stream)
+    {
+    case 0x803FF:
+        new_preamble_type = V8_SYNC_CI;
+        break;
+    case 0xF03FF:
+        new_preamble_type = V8_SYNC_CM_JM;
+        break;
+    case 0xAABFF:
+        new_preamble_type = V8_SYNC_V92;
+        break;
+    default:
+        new_preamble_type = V8_SYNC_UNKNOWN;
+        break;
+    }
+    if (new_preamble_type != V8_SYNC_UNKNOWN)
+    {
+        /* We have seen a fresh sync code */
+        /* Debug */
+        if (span_log_test(&s->logging, SPAN_LOG_FLOW))
+        {
+            if (s->preamble_type != V8_SYNC_UNKNOWN)
+            {
+                switch (s->preamble_type)
+                {
+                case V8_SYNC_CI:
+                    tag = "CI: ";
+                    break;
+                case V8_SYNC_CM_JM:
+                    tag = (s->calling_party)  ?  "JM: "  :  "CM: ";
+                    break;
+                case V8_SYNC_V92:
+                    tag = "V92: ";
+                    break;
+                default:
+                    tag = "??: ";
+                    break;
+                }
+                span_log_buf(&s->logging, SPAN_LOG_FLOW, tag, s->rx_data, s->rx_data_ptr);
+            }
+        }
+        /* If we were handling a valid sync code then we should process what has been
+           received to date. */
+        switch (s->preamble_type)
+        {
+        case V8_SYNC_CI:
+            ci_decode(s);
+            break;
+        case V8_SYNC_CM_JM:
+            cm_jm_decode(s);
+            break;
+        }
+        s->preamble_type = new_preamble_type;
+        s->bit_cnt = 0;
+        s->rx_data_ptr = 0;
+    }
+    
+    if (s->preamble_type != V8_SYNC_UNKNOWN)
+    {
+        /* Parse octets with 1 bit start, 1 bit stop */
+        s->bit_cnt++;
+        /* Start, stop? */
+        if ((s->bit_stream & 0x80400) == 0x80000  &&  s->bit_cnt >= 10)
+        {
+            /* Store the available data */
+            data = (uint8_t) ((s->bit_stream >> 11) & 0xFF);
+            /* CJ (3 successive zero octets) detection */
+            if (data == 0)
+            {
+                if (++s->zero_byte_count == 3)
+                    s->got_cj = TRUE;
+            }
+            else
+            {
+                s->zero_byte_count = 0;
+            }
+
+            if (s->rx_data_ptr < (int) (sizeof(s->rx_data) - 1))
+                s->rx_data[s->rx_data_ptr++] = data;
+            s->bit_cnt = 0;
+        }
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+static void v8_decode_init(v8_state_t *s)
+{
+    fsk_rx_init(&s->v21rx,
+                &preset_fsk_specs[(s->calling_party)  ?  FSK_V21CH2  :  FSK_V21CH1],
+                FSK_FRAME_MODE_ASYNC,
+                put_bit,
+                s);
+    fsk_rx_signal_cutoff(&s->v21rx, -45.5f);
+    s->preamble_type = V8_SYNC_UNKNOWN;
+    s->bit_stream = 0;
+    s->cm_jm_len = 0;
+    s->got_cm_jm = FALSE;
+    s->got_cj = FALSE;
+    s->zero_byte_count = 0;
+    s->rx_data_ptr = 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int get_bit(void *user_data)
+{
+    v8_state_t *s;
+    uint8_t bit;
+
+    s = user_data;
+    if (queue_read(s->tx_queue, &bit, 1) <= 0)
+        return SIG_STATUS_END_OF_DATA;
+    return bit;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void v8_put_preamble(v8_state_t *s)
+{
+    static const uint8_t preamble[10] =
+    {
+        1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+    };
+
+    queue_write(s->tx_queue, preamble, 10);
+}
+/*- End of function --------------------------------------------------------*/
+
+static void v8_put_byte(v8_state_t *s, int data)
+{
+    int i;
+    uint8_t bits[10];
+
+    /* Insert start & stop bits */
+    bits[0] = 0;
+    for (i = 1;  i < 9;  i++)
+    {
+        bits[i] = (uint8_t) (data & 1);
+        data >>= 1;
+    }
+    bits[9] = 1;
+    queue_write(s->tx_queue, bits, 10);
+}
+/*- End of function --------------------------------------------------------*/
+
+static void send_cm_jm(v8_state_t *s)
+{
+    int val;
+    unsigned int offered_modulations;
+    
+    offered_modulations = s->parms.modulations & s->far_end_modulations;
+
+    /* Send a CM, or a JM as appropriate */
+    v8_put_preamble(s);
+    v8_put_byte(s, V8_CM_JM_SYNC_OCTET);
+    /* Data call */
+    v8_put_byte(s, (s->result.call_function << 5) | V8_CALL_FUNCTION_TAG);
+    
+    /* Supported modulations */
+    val = 0x05;
+    if (offered_modulations & V8_MOD_V90)
+        val |= 0x20;
+    if (offered_modulations & V8_MOD_V34)
+        val |= 0x40;
+    v8_put_byte(s, val);
+
+    val = 0x10;
+    if (offered_modulations & V8_MOD_V32)
+        val |= 0x01;
+    if (offered_modulations & V8_MOD_V22)
+        val |= 0x02;
+    if (offered_modulations & V8_MOD_V17)
+        val |= 0x04;
+    if (offered_modulations & V8_MOD_V29)
+        val |= 0x40;
+    if (offered_modulations & V8_MOD_V27TER)
+        val |= 0x80;
+    v8_put_byte(s, val);
+
+    val = 0x10;
+    if (offered_modulations & V8_MOD_V26TER)
+        val |= 0x01;
+    if (offered_modulations & V8_MOD_V26BIS)
+        val |= 0x02;
+    if (offered_modulations & V8_MOD_V23)
+        val |= 0x04;
+    if (offered_modulations & V8_MOD_V23HALF)
+        val |= 0x40;
+    if (offered_modulations & V8_MOD_V21)
+        val |= 0x80;
+    v8_put_byte(s, val);
+
+    if (s->parms.protocol)
+        v8_put_byte(s, (s->parms.protocol << 5) | V8_PROTOCOLS_TAG);
+    if (s->parms.pcm_modem_availability)
+        v8_put_byte(s, (s->parms.pcm_modem_availability << 5) | V8_PCM_MODEM_AVAILABILITY_TAG);
+    if (s->parms.pstn_access)
+        v8_put_byte(s, (s->parms.pstn_access << 5) | V8_PSTN_ACCESS_TAG);
+    if (s->parms.t66 >= 0)
+        v8_put_byte(s, (s->parms.t66 << 5) | V8_T66_TAG);
+
+    /* No NSF */
+    //v8_put_byte(s, (0 << 5) | V8_NSF_TAG);
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE_NONSTD(int) v8_tx(v8_state_t *s, int16_t *amp, int max_len)
+{
+    int len;
+
+    //span_log(&s->logging, SPAN_LOG_FLOW, "v8_tx state %d\n", s->state);
+    len = 0;
+    if (s->modem_connect_tone_tx_on)
+    {
+        if (s->modem_connect_tone_tx_on > ms_to_samples(75))
+        {
+            /* Send the ANSam tone */
+            len = modem_connect_tones_tx(&s->ansam_tx, amp, max_len);
+            if (len < max_len)
+            {
+                span_log(&s->logging, SPAN_LOG_FLOW, "ANSam or ANSam/ ended\n");
+                s->modem_connect_tone_tx_on = ms_to_samples(75);
+            }
+        }
+        else
+        {
+            /* Send the 75ms of silence after the ANSam tone */
+            if (max_len > s->modem_connect_tone_tx_on)
+                len = s->modem_connect_tone_tx_on;
+            else
+                len = max_len;
+            vec_zeroi16(amp, len);
+            s->modem_connect_tone_tx_on -= len;
+        }
+    }
+    if (s->fsk_tx_on  &&  len < max_len)
+    {
+        max_len -= len;
+        len = fsk_tx(&s->v21tx, amp + len, max_len);
+        if (len < max_len)
+        {
+            span_log(&s->logging, SPAN_LOG_FLOW, "FSK ends\n");
+            s->fsk_tx_on = FALSE;
+        }
+    }
+    return len;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void v8_send_ci(v8_state_t *s)
+{
+    int i;
+
+    /* Send 4 CI packets in a burst (the spec says at least 3) */
+    for (i = 0;  i < 4;  i++)
+    {
+        v8_put_preamble(s);
+        v8_put_byte(s, V8_CI_SYNC_OCTET);
+        v8_put_byte(s, (s->result.call_function << 5) | V8_CALL_FUNCTION_TAG);
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+static void handle_modem_connect_tone(v8_state_t *s, int tone)
+{
+    s->result.modem_connect_tone = tone;
+    span_log(&s->logging, SPAN_LOG_FLOW, "'%s' recognised\n", modem_connect_tone_to_str(tone));
+    if (tone == MODEM_CONNECT_TONES_ANSAM
+        ||
+        tone == MODEM_CONNECT_TONES_ANSAM_PR)
+    {
+        /* Set the Te interval. The spec. says 500ms is the minimum,
+           but gives reasons why 1 second is a better value. */
+        s->state = V8_HEARD_ANSAM;
+        s->ci_timer = ms_to_samples(1000);
+    }
+    else
+    {
+        /* If we found a connect tone, and it isn't one of the modulated answer tones,
+           indicating V.8 startup, we are not going to do V.8 processing. */
+        span_log(&s->logging, SPAN_LOG_FLOW, "Non-V.8 modem connect tone detected\n");
+        s->state = V8_PARKED;
+        if (s->result_handler)
+            s->result_handler(s->result_handler_user_data, &s->result);
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE_NONSTD(int) v8_rx(v8_state_t *s, const int16_t *amp, int len)
+{
+    int i;
+    int residual_samples;
+    int tone;
+
+    //span_log(&s->logging, SPAN_LOG_FLOW, "v8_rx state %d\n", s->state);
+    residual_samples = 0;
+    switch (s->state)
+    {
+    case V8_WAIT_1S:
+        residual_samples = modem_connect_tones_rx(&s->ansam_rx, amp, len);
+        /* Wait 1 second before sending the first CI packet */
+        if ((s->negotiation_timer -= len) > 0)
+            break;
+        fsk_tx_restart(&s->v21tx, &preset_fsk_specs[FSK_V21CH1]);
+        v8_send_ci(s);
+        s->state = V8_CI_ON;
+        s->fsk_tx_on = TRUE;
+        break;
+    case V8_CI_ON:
+        residual_samples = modem_connect_tones_rx(&s->ansam_rx, amp, len);
+        /* Check if an ANSam or ANSam/ tone has been detected */
+        if ((tone = modem_connect_tones_rx_get(&s->ansam_rx)) != MODEM_CONNECT_TONES_NONE)
+        {
+            handle_modem_connect_tone(s, tone);
+            break;
+        }
+        if (queue_empty(s->tx_queue))
+        {
+            s->state = V8_CI_OFF;
+            s->ci_timer = ms_to_samples(500);
+            break;
+        }
+        break;
+    case V8_CI_OFF:
+        residual_samples = modem_connect_tones_rx(&s->ansam_rx, amp, len);
+        /* Check if an ANSam or ANSam/ tone has been detected */
+        if ((tone = modem_connect_tones_rx_get(&s->ansam_rx)) != MODEM_CONNECT_TONES_NONE)
+        {
+            handle_modem_connect_tone(s, tone);
+            break;
+        }
+        if ((s->ci_timer -= len) <= 0)
+        {
+            if (++s->ci_count >= 10)
+            {
+                /* The spec says we should give up now. */
+                span_log(&s->logging, SPAN_LOG_FLOW, "Timeout waiting for modem connect tone\n");
+                s->state = V8_PARKED;
+                if (s->result_handler)
+                    s->result_handler(s->result_handler_user_data, NULL);
+            }
+            else
+            {
+                /* Try again */
+                fsk_tx_restart(&s->v21tx, &preset_fsk_specs[FSK_V21CH1]);
+                v8_send_ci(s);
+                s->state = V8_CI_ON;
+                s->fsk_tx_on = TRUE;
+            }
+        }
+        break;
+    case V8_HEARD_ANSAM:
+        /* We have heard the ANSam or ANSam/ signal, but we still need to wait for the
+           end of the Te timeout period to comply with the spec. */
+        if ((s->ci_timer -= len) <= 0)
+        {
+            v8_decode_init(s);
+            s->negotiation_timer = ms_to_samples(5000);
+            fsk_tx_restart(&s->v21tx, &preset_fsk_specs[FSK_V21CH1]);
+            send_cm_jm(s);
+            s->state = V8_CM_ON;
+            s->fsk_tx_on = TRUE;
+        }
+        break;
+    case V8_CM_ON:
+        residual_samples = fsk_rx(&s->v21rx, amp, len);
+        if (s->got_cm_jm)
+        {
+            span_log(&s->logging, SPAN_LOG_FLOW, "JM recognised\n");
+            /* Now JM has been detected, we send CJ and wait for 75 ms
+               before finishing the V.8 analysis. */
+            fsk_tx_restart(&s->v21tx, &preset_fsk_specs[FSK_V21CH1]);
+            for (i = 0;  i < 3;  i++)
+                v8_put_byte(s, 0);
+            s->state = V8_CJ_ON;
+            s->fsk_tx_on = TRUE;
+            break;
+        }
+        if ((s->negotiation_timer -= len) <= 0)
+        {
+            /* Timeout */
+            span_log(&s->logging, SPAN_LOG_FLOW, "Timeout waiting for JM\n");
+            s->state = V8_PARKED;
+            if (s->result_handler)
+                s->result_handler(s->result_handler_user_data, NULL);
+        }
+        if (queue_contents(s->tx_queue) < 10)
+        {
+            /* Send CM again */
+            send_cm_jm(s);
+        }
+        break;
+    case V8_CJ_ON:
+        residual_samples = fsk_rx(&s->v21rx, amp, len);
+        if (queue_empty(s->tx_queue))
+        {
+            s->negotiation_timer = ms_to_samples(75);
+            s->state = V8_SIGC;
+        }
+        break;
+    case V8_SIGC:
+        if ((s->negotiation_timer -= len) <= 0)
+        {
+            /* The V.8 negotiation has succeeded. */
+            span_log(&s->logging, SPAN_LOG_FLOW, "Negotiation succeeded\n");
+            s->state = V8_PARKED;
+            if (s->result_handler)
+                s->result_handler(s->result_handler_user_data, &s->result);
+        }
+        break;
+    case V8_CM_WAIT:
+        residual_samples = fsk_rx(&s->v21rx, amp, len);
+        if (s->got_cm_jm)
+        {
+            span_log(&s->logging, SPAN_LOG_FLOW, "CM recognised\n");
+            
+            /* TODO: negotiate if the call function is acceptable */
+            
+            /* Stop sending ANSam or ANSam/ and send JM instead */
+            fsk_tx_init(&s->v21tx, &preset_fsk_specs[FSK_V21CH2], get_bit, s);
+            /* Set the timeout for JM */
+            s->negotiation_timer = ms_to_samples(5000); 
+            s->state = V8_JM_ON;
+            send_cm_jm(s);
+            s->modem_connect_tone_tx_on = ms_to_samples(75);
+            s->fsk_tx_on = TRUE;
+            break;
+        }
+        if ((s->negotiation_timer -= len) <= 0)
+        {
+            /* Timeout */
+            span_log(&s->logging, SPAN_LOG_FLOW, "Timeout waiting for CM\n");
+            s->state = V8_PARKED;
+            if (s->result_handler)
+                s->result_handler(s->result_handler_user_data, NULL);
+        }
+        break;
+    case V8_JM_ON:
+        residual_samples = fsk_rx(&s->v21rx, amp, len);
+        if (s->got_cj)
+        {
+            span_log(&s->logging, SPAN_LOG_FLOW, "CJ recognised\n");
+            /* Stop sending JM, flushing anything left in the buffer, and wait 75 ms */
+            queue_flush(s->tx_queue);
+            s->negotiation_timer = ms_to_samples(75);
+            s->state = V8_SIGA;
+            break;
+        }
+        if ((s->negotiation_timer -= len) <= 0)
+        {
+            /* Timeout */
+            span_log(&s->logging, SPAN_LOG_FLOW, "Timeout waiting for CJ\n");
+            s->state = V8_PARKED;
+            if (s->result_handler)
+                s->result_handler(s->result_handler_user_data, NULL);
+            break;
+        }
+        if (queue_contents(s->tx_queue) < 10)
+        {
+            /* Send JM */
+            send_cm_jm(s);
+        }
+        break;
+    case V8_SIGA:
+        if ((s->negotiation_timer -= len) <= 0)
+        {
+            /* The V.8 negotiation has succeeded. */
+            span_log(&s->logging, SPAN_LOG_FLOW, "Negotiation succeeded\n");
+            s->state = V8_PARKED;
+            if (s->result_handler)
+                s->result_handler(s->result_handler_user_data, &s->result);
+        }
+        break;
+    case V8_PARKED:
+        residual_samples = len;
+        break;
+    }
+    return residual_samples;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(logging_state_t *) v8_get_logging_state(v8_state_t *s)
+{
+    return &s->logging;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) v8_restart(v8_state_t *s,
+                             int calling_party,
+                             v8_parms_t *parms)
+{
+    memcpy(&s->parms, parms, sizeof(s->parms));
+    memset(&s->result, 0, sizeof(s->result));
+
+    s->result.call_function = s->parms.call_function;
+    s->result.nsf = -1;
+    s->result.t66 = -1;
+
+    s->ci_timer = 0;
+    if (calling_party)
+    {
+        s->calling_party = TRUE;
+        s->state = V8_WAIT_1S;
+        s->negotiation_timer = ms_to_samples(1000);
+        s->ci_count = 0;
+        modem_connect_tones_rx_init(&s->ansam_rx, MODEM_CONNECT_TONES_ANS_PR, NULL, NULL);
+        fsk_tx_init(&s->v21tx, &preset_fsk_specs[FSK_V21CH1], get_bit, s);
+    }
+    else
+    {
+        /* Send the ANSam or ANSam/ tone */
+        s->calling_party = FALSE;
+        modem_connect_tones_tx_init(&s->ansam_tx, s->parms.modem_connect_tone);
+                
+        v8_decode_init(s);
+        s->state = V8_CM_WAIT;
+        s->negotiation_timer = ms_to_samples(200 + 5000);
+        s->modem_connect_tone_tx_on = ms_to_samples(75) + 1;
+    }
+    s->result.modem_connect_tone = MODEM_CONNECT_TONES_NONE;
+
+    if ((s->tx_queue = queue_init(NULL, 1024, 0)) == NULL)
+        return -1;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(v8_state_t *) v8_init(v8_state_t *s,
+                                   int calling_party,
+                                   v8_parms_t *parms,
+                                   v8_result_handler_t *result_handler,
+                                   void *user_data)
+{
+    if (s == NULL)
+    {
+        if ((s = (v8_state_t *) malloc(sizeof(*s))) == NULL)
+            return NULL;
+    }
+    memset(s, 0, sizeof(*s));
+    span_log_init(&s->logging, SPAN_LOG_NONE, NULL);
+    span_log_set_protocol(&s->logging, "V.8");
+    s->result_handler = result_handler;
+    s->result_handler_user_data = user_data;
+
+    v8_restart(s, calling_party, parms);
+
+    memcpy(&s->parms, parms, sizeof(s->parms));
+
+    s->result.call_function = s->parms.call_function;
+    s->result.nsf = -1;
+    s->result.t66 = -1;
+
+    s->ci_timer = 0;
+    if (calling_party)
+    {
+        s->calling_party = TRUE;
+        s->state = V8_WAIT_1S;
+        s->negotiation_timer = ms_to_samples(1000);
+        s->ci_count = 0;
+        modem_connect_tones_rx_init(&s->ansam_rx, MODEM_CONNECT_TONES_ANS_PR, NULL, NULL);
+        fsk_tx_init(&s->v21tx, &preset_fsk_specs[FSK_V21CH1], get_bit, s);
+    }
+    else
+    {
+        /* Send the ANSam or ANSam/ tone */
+        s->calling_party = FALSE;
+        modem_connect_tones_tx_init(&s->ansam_tx, s->parms.modem_connect_tone);
+                
+        v8_decode_init(s);
+        s->state = V8_CM_WAIT;
+        s->negotiation_timer = ms_to_samples(200 + 5000);
+        s->modem_connect_tone_tx_on = ms_to_samples(75) + 1;
+    }
+    s->result.modem_connect_tone = MODEM_CONNECT_TONES_NONE;
+
+    if ((s->tx_queue = queue_init(NULL, 1024, 0)) == NULL)
+        return NULL;
+    return s;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) v8_release(v8_state_t *s)
+{
+    return queue_free(s->tx_queue);
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) v8_free(v8_state_t *s)
+{
+    int ret;
+    
+    ret = queue_free(s->tx_queue);
+    free(s);
+    return ret;
+}
+/*- End of function --------------------------------------------------------*/
+/*- End of file ------------------------------------------------------------*/

Repositories maintained by Peter Meerwald, pmeerw@pmeerw.net.