diff spandsp-0.0.6pre17/src/t38_terminal.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/t38_terminal.c	Fri Jun 25 15:50:58 2010 +0200
@@ -0,0 +1,1204 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * t38_terminal.c - T.38 termination, less the packet exchange part
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2005, 2006, 2008 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: t38_terminal.c,v 1.129.4.2 2009/12/19 10:44:10 steveu Exp $
+ */
+
+/*! \file */
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <time.h>
+#include <string.h>
+#if defined(HAVE_TGMATH_H)
+#include <tgmath.h>
+#endif
+#if defined(HAVE_MATH_H)
+#include <math.h>
+#endif
+#include "floating_fudge.h"
+#include <assert.h>
+#include <tiffio.h>
+
+#include "spandsp/telephony.h"
+#include "spandsp/logging.h"
+#include "spandsp/bit_operations.h"
+#include "spandsp/queue.h"
+#include "spandsp/power_meter.h"
+#include "spandsp/complex.h"
+#include "spandsp/tone_generate.h"
+#include "spandsp/async.h"
+#include "spandsp/hdlc.h"
+#include "spandsp/fsk.h"
+#include "spandsp/v29tx.h"
+#include "spandsp/v29rx.h"
+#include "spandsp/v27ter_tx.h"
+#include "spandsp/v27ter_rx.h"
+#include "spandsp/v17tx.h"
+#include "spandsp/v17rx.h"
+#include "spandsp/t4_rx.h"
+#include "spandsp/t4_tx.h"
+#include "spandsp/t30_fcf.h"
+#include "spandsp/t35.h"
+#include "spandsp/t30.h"
+#include "spandsp/t30_api.h"
+#include "spandsp/t30_logging.h"
+#include "spandsp/t38_core.h"
+#include "spandsp/t38_terminal.h"
+
+#include "spandsp/private/logging.h"
+#include "spandsp/private/t4_rx.h"
+#include "spandsp/private/t4_tx.h"
+#include "spandsp/private/t30.h"
+#include "spandsp/private/t38_core.h"
+#include "spandsp/private/t38_terminal.h"
+
+/* Settings suitable for paced transmission over a UDP transport */
+#define MS_PER_TX_CHUNK                         30
+
+#define INDICATOR_TX_COUNT                      3
+#define DATA_TX_COUNT                           1
+#define DATA_END_TX_COUNT                       3
+
+/* Settings suitable for unpaced transmission over a TCP transport */
+#define MAX_OCTETS_PER_UNPACED_CHUNK            300
+
+/* Backstop timeout if reception of packets stops in the middle of a burst */
+#define MID_RX_TIMEOUT                          15000
+
+enum
+{
+    T38_CHUNKING_MERGE_FCS_WITH_DATA = 0x0001,
+    T38_CHUNKING_WHOLE_FRAMES = 0x0002,
+    T38_CHUNKING_ALLOW_TEP_TIME = 0x0004
+};
+
+enum
+{
+    T38_TIMED_STEP_NONE = 0,
+    T38_TIMED_STEP_NON_ECM_MODEM = 0x10,
+    T38_TIMED_STEP_NON_ECM_MODEM_2 = 0x11,
+    T38_TIMED_STEP_NON_ECM_MODEM_3 = 0x12,
+    T38_TIMED_STEP_NON_ECM_MODEM_4 = 0x13,
+    T38_TIMED_STEP_NON_ECM_MODEM_5 = 0x14,
+    T38_TIMED_STEP_HDLC_MODEM = 0x20,
+    T38_TIMED_STEP_HDLC_MODEM_2 = 0x21,
+    T38_TIMED_STEP_HDLC_MODEM_3 = 0x22,
+    T38_TIMED_STEP_HDLC_MODEM_4 = 0x23,
+    T38_TIMED_STEP_HDLC_MODEM_5 = 0x24,
+    T38_TIMED_STEP_CED = 0x30,
+    T38_TIMED_STEP_CED_2 = 0x31,
+    T38_TIMED_STEP_CED_3 = 0x32,
+    T38_TIMED_STEP_CNG = 0x40,
+    T38_TIMED_STEP_CNG_2 = 0x41,
+    T38_TIMED_STEP_PAUSE = 0x50
+};
+
+static __inline__ void front_end_status(t38_terminal_state_t *s, int status)
+{
+    t30_front_end_status(&s->t30, status);
+}
+/*- End of function --------------------------------------------------------*/
+
+static __inline__ void hdlc_accept_frame(t38_terminal_state_t *s, const uint8_t *msg, int len, int ok)
+{
+    t30_hdlc_accept(&s->t30, msg, len, ok);
+}
+/*- End of function --------------------------------------------------------*/
+
+static int extra_bits_in_stuffed_frame(const uint8_t buf[], int len)
+{
+    int bitstream;
+    int ones;
+    int stuffed;
+    int i;
+    int j;
+    
+    bitstream = 0;
+    ones = 0;
+    stuffed = 0;
+    /* We should really append the CRC, and include the stuffed bits for that, to get
+       the exact number of bits in the frame. */
+    //len = crc_itu16_append(buf, len);
+    for (i = 0;  i < len;  i++)
+    {
+        bitstream = buf[i];
+        for (j = 0;  j < 8;  j++)
+        {
+            if ((bitstream & 1))
+            {
+                if (++ones >= 5)
+                {
+                    ones = 0;
+                    stuffed++;
+                }
+            }
+            else
+            {
+                ones = 0;
+            }
+            bitstream >>= 1;
+        }
+    }
+    /* The total length of the frame is:
+          the number of bits in the body
+        + the number of additional bits in the body due to stuffing
+        + the number of bits in the CRC
+        + the number of additional bits in the CRC due to stuffing
+        + 16 bits for the two terminating flag octets.
+       Lets just allow 3 bits for the CRC, which is the worst case. It
+       avoids calculating the real CRC, and the worst it can do is cause
+       a flag octet's worth of additional output.
+    */
+    return stuffed + 16 + 3 + 16;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int process_rx_missing(t38_core_state_t *t, void *user_data, int rx_seq_no, int expected_seq_no)
+{
+    t38_terminal_state_t *s;
+    
+    s = (t38_terminal_state_t *) user_data;
+    s->t38_fe.rx_data_missing = TRUE;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int process_rx_indicator(t38_core_state_t *t, void *user_data, int indicator)
+{
+    t38_terminal_state_t *s;
+    t38_terminal_front_end_state_t *fe;
+    
+    s = (t38_terminal_state_t *) user_data;
+    fe = &s->t38_fe;
+
+    if (t->current_rx_indicator == indicator)
+    {
+        /* This is probably due to the far end repeating itself, or slipping
+           preamble messages in between HDLC frames. T.38/V.1.3 tells us to
+           ignore it. Its harmless. */
+        return 0;
+    }
+    /* In termination mode we don't care very much about indicators telling us training
+       is starting. We only care about V.21 preamble starting, for timeout control, and
+       the actual data. */
+    switch (indicator)
+    {
+    case T38_IND_NO_SIGNAL:
+        if (t->current_rx_indicator == T38_IND_V21_PREAMBLE
+            &&
+            (fe->current_rx_type == T30_MODEM_V21  ||  fe->current_rx_type == T30_MODEM_CNG))
+        {
+            hdlc_accept_frame(s, NULL, SIG_STATUS_CARRIER_DOWN, TRUE);
+        }
+        fe->timeout_rx_samples = 0;
+        front_end_status(s, T30_FRONT_END_SIGNAL_ABSENT);
+        break;
+    case T38_IND_CNG:
+        /* We are completely indifferent to the startup tones. They serve no purpose for us.
+           We can't even assume that the existance of a tone means the far end is achieving
+           proper communication. Some T.38 gateways will just send out a CED or CNG indicator
+           without having seen anything from the far end FAX terminal.
+           Just report them for completeness. */
+        front_end_status(s, T30_FRONT_END_CNG_PRESENT);
+        break;
+    case T38_IND_CED:
+        front_end_status(s, T30_FRONT_END_CED_PRESENT);
+        break;
+    case T38_IND_V21_PREAMBLE:
+        fe->timeout_rx_samples = fe->samples + ms_to_samples(MID_RX_TIMEOUT);
+        front_end_status(s, T30_FRONT_END_SIGNAL_PRESENT);
+        break;
+    case T38_IND_V27TER_2400_TRAINING:
+    case T38_IND_V27TER_4800_TRAINING:
+    case T38_IND_V29_7200_TRAINING:
+    case T38_IND_V29_9600_TRAINING:
+    case T38_IND_V17_7200_SHORT_TRAINING:
+    case T38_IND_V17_7200_LONG_TRAINING:
+    case T38_IND_V17_9600_SHORT_TRAINING:
+    case T38_IND_V17_9600_LONG_TRAINING:
+    case T38_IND_V17_12000_SHORT_TRAINING:
+    case T38_IND_V17_12000_LONG_TRAINING:
+    case T38_IND_V17_14400_SHORT_TRAINING:
+    case T38_IND_V17_14400_LONG_TRAINING:
+    case T38_IND_V34_CNTL_CHANNEL_1200:
+    case T38_IND_V34_PRI_CHANNEL:
+    case T38_IND_V33_12000_TRAINING:
+    case T38_IND_V33_14400_TRAINING:
+        /* We really don't care what kind of modem is delivering the following image data.
+           We only care that some kind of fast modem signal is coming next. */
+        fe->timeout_rx_samples = fe->samples + ms_to_samples(MID_RX_TIMEOUT);
+        front_end_status(s, T30_FRONT_END_SIGNAL_PRESENT);
+        break;
+    case T38_IND_V8_ANSAM:
+    case T38_IND_V8_SIGNAL:
+    case T38_IND_V34_CC_RETRAIN:
+        /* V.34 support is a work in progress. */
+        break;
+    default:
+        front_end_status(s, T30_FRONT_END_SIGNAL_ABSENT);
+        break;
+    }
+    /*endswitch*/
+    fe->hdlc_rx.len = 0;
+    fe->rx_data_missing = FALSE;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int fake_rx_indicator(t38_core_state_t *t, t38_terminal_state_t *s, int indicator)
+{
+    int ret;
+
+    ret = process_rx_indicator(t, s, indicator);
+    t->current_rx_indicator = indicator;
+    return ret;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int process_rx_data(t38_core_state_t *t, void *user_data, int data_type, int field_type, const uint8_t *buf, int len)
+{
+    t38_terminal_state_t *s;
+    t38_terminal_front_end_state_t *fe;
+#if defined(_MSC_VER)
+    uint8_t *buf2 = (uint8_t *) _alloca(len);
+#else
+    uint8_t buf2[len];
+#endif
+
+    s = (t38_terminal_state_t *) user_data;
+    fe = &s->t38_fe;
+    /* In termination mode we don't care very much what the data type is apart from a couple of
+       special cases. */
+    switch (data_type)
+    {
+    case T38_DATA_V8:
+        switch (field_type)
+        {
+        case T38_FIELD_CM_MESSAGE:
+            if (len >= 1)
+                span_log(&s->logging, SPAN_LOG_FLOW, "CM profile %d - %s\n", buf[0] - '0', t38_cm_profile_to_str(buf[0]));
+            else
+                span_log(&s->logging, SPAN_LOG_FLOW, "Bad length for CM message - %d\n", len);
+            //front_end_status(s, T30_FRONT_END_RECEIVE_COMPLETE);
+            break;
+        case T38_FIELD_JM_MESSAGE:
+            if (len >= 2)
+                span_log(&s->logging, SPAN_LOG_FLOW, "JM - %s\n", t38_jm_to_str(buf, len));
+            else
+                span_log(&s->logging, SPAN_LOG_FLOW, "Bad length for JM message - %d\n", len);
+            //front_end_status(s, T30_FRONT_END_RECEIVE_COMPLETE);
+            break;
+        case T38_FIELD_CI_MESSAGE:
+            if (len >= 1)
+                span_log(&s->logging, SPAN_LOG_FLOW, "CI 0x%X\n", buf[0]);
+            else
+                span_log(&s->logging, SPAN_LOG_FLOW, "Bad length for CI message - %d\n", len);
+            //front_end_status(s, T30_FRONT_END_RECEIVE_COMPLETE);
+            break;
+        default:
+            break;
+        }
+        /*endswitch*/
+        return 0;
+    case T38_DATA_V34_PRI_RATE:
+        switch (field_type)
+        {
+        case T38_FIELD_V34RATE:
+            if (len >= 3)
+            {
+                /* Just get and store the rate. The front end has no real interest in the
+                   actual bit rate. */
+                fe->t38.v34_rate = t38_v34rate_to_bps(buf, len);
+                span_log(&s->logging, SPAN_LOG_FLOW, "V.34 rate %d bps\n", fe->t38.v34_rate);
+            }
+            else
+            {
+                span_log(&s->logging, SPAN_LOG_FLOW, "Bad length for V34rate message - %d\n", len);
+            }
+            break;
+        default:
+            break;
+        }
+        /*endswitch*/
+        return 0;
+    default:
+        break;
+    }
+    /*endswitch*/
+    switch (field_type)
+    {
+    case T38_FIELD_HDLC_DATA:
+        if (fe->timeout_rx_samples == 0)
+        {
+            /* HDLC can just start without any signal indicator on some platforms, even when
+               there is zero packet lost. Nasty, but true. Its a good idea to be tolerant of
+               loss, though, so accepting a sudden start of HDLC data is the right thing to do. */
+            fake_rx_indicator(t, s, T38_IND_V21_PREAMBLE);
+            /* All real HDLC messages in the FAX world start with 0xFF. If this one is not starting
+               with 0xFF it would appear some octets must have been missed before this one. */
+            if (len <= 0  ||  buf[0] != 0xFF)
+                fe->rx_data_missing = TRUE;
+        }
+        if (len > 0  &&  fe->hdlc_rx.len + len <= T38_MAX_HDLC_LEN)
+        {
+            bit_reverse(fe->hdlc_rx.buf + fe->hdlc_rx.len, buf, len);
+            fe->hdlc_rx.len += len;
+        }
+        fe->timeout_rx_samples = fe->samples + ms_to_samples(MID_RX_TIMEOUT);
+        break;
+    case T38_FIELD_HDLC_FCS_OK:
+        if (len > 0)
+        {
+            span_log(&s->logging, SPAN_LOG_WARNING, "There is data in a T38_FIELD_HDLC_FCS_OK!\n");
+            /* The sender has incorrectly included data in this message. It is unclear what we should do
+               with it, to maximise tolerance of buggy implementations. */
+        }
+        /* Some T.38 implementations send multiple T38_FIELD_HDLC_FCS_OK messages, in IFP packets with
+           incrementing sequence numbers, which are actually repeats. They get through to this point because
+           of the incrementing sequence numbers. We need to filter them here in a context sensitive manner. */
+        if (t->current_rx_data_type != data_type  ||  t->current_rx_field_type != field_type)
+        {
+            span_log(&s->logging, SPAN_LOG_FLOW, "Type %s - CRC OK (%s)\n", (fe->hdlc_rx.len >= 3)  ?  t30_frametype(fe->hdlc_rx.buf[2])  :  "???", (fe->rx_data_missing)  ?  "missing octets"  :  "clean");
+            hdlc_accept_frame(s, fe->hdlc_rx.buf, fe->hdlc_rx.len, !fe->rx_data_missing);
+        }
+        fe->hdlc_rx.len = 0;
+        fe->rx_data_missing = FALSE;
+        fe->timeout_rx_samples = fe->samples + ms_to_samples(MID_RX_TIMEOUT);
+        break;
+    case T38_FIELD_HDLC_FCS_BAD:
+        if (len > 0)
+        {
+            span_log(&s->logging, SPAN_LOG_WARNING, "There is data in a T38_FIELD_HDLC_FCS_BAD!\n");
+            /* The sender has incorrectly included data in this message. We can safely ignore it, as the
+               bad FCS means we will throw away the whole message, anyway. */
+        }
+        /* Some T.38 implementations send multiple T38_FIELD_HDLC_FCS_BAD messages, in IFP packets with
+           incrementing sequence numbers, which are actually repeats. They get through to this point because
+           of the incrementing sequence numbers. We need to filter them here in a context sensitive manner. */
+        if (t->current_rx_data_type != data_type  ||  t->current_rx_field_type != field_type)
+        {
+            span_log(&s->logging, SPAN_LOG_FLOW, "Type %s - CRC bad (%s)\n", (fe->hdlc_rx.len >= 3)  ?  t30_frametype(fe->hdlc_rx.buf[2])  :  "???", (fe->rx_data_missing)  ?  "missing octets"  :  "clean");
+            hdlc_accept_frame(s, fe->hdlc_rx.buf, fe->hdlc_rx.len, FALSE);
+        }
+        fe->hdlc_rx.len = 0;
+        fe->rx_data_missing = FALSE;
+        fe->timeout_rx_samples = fe->samples + ms_to_samples(MID_RX_TIMEOUT);
+        break;
+    case T38_FIELD_HDLC_FCS_OK_SIG_END:
+        if (len > 0)
+        {
+            span_log(&s->logging, SPAN_LOG_WARNING, "There is data in a T38_FIELD_HDLC_FCS_OK_SIG_END!\n");
+            /* The sender has incorrectly included data in this message. It is unclear what we should do
+               with it, to maximise tolerance of buggy implementations. */
+        }
+        /* Some T.38 implementations send multiple T38_FIELD_HDLC_FCS_OK_SIG_END messages, in IFP packets with
+           incrementing sequence numbers, which are actually repeats. They get through to this point because
+           of the incrementing sequence numbers. We need to filter them here in a context sensitive manner. */
+        if (t->current_rx_data_type != data_type  ||  t->current_rx_field_type != field_type)
+        {
+            span_log(&s->logging, SPAN_LOG_FLOW, "Type %s - CRC OK, sig end (%s)\n", (fe->hdlc_rx.len >= 3)  ?  t30_frametype(fe->hdlc_rx.buf[2])  :  "???", (fe->rx_data_missing)  ?  "missing octets"  :  "clean");
+            hdlc_accept_frame(s, fe->hdlc_rx.buf, fe->hdlc_rx.len, !fe->rx_data_missing);
+            hdlc_accept_frame(s, NULL, SIG_STATUS_CARRIER_DOWN, TRUE);
+        }
+        fe->hdlc_rx.len = 0;
+        fe->rx_data_missing = FALSE;
+        /* Treat this like a no signal indicator has occurred, so if the no signal indicator is missing, we are still OK */
+        fake_rx_indicator(t, s, T38_IND_NO_SIGNAL);
+        break;
+    case T38_FIELD_HDLC_FCS_BAD_SIG_END:
+        if (len > 0)
+        {
+            span_log(&s->logging, SPAN_LOG_WARNING, "There is data in a T38_FIELD_HDLC_FCS_BAD_SIG_END!\n");
+            /* The sender has incorrectly included data in this message. We can safely ignore it, as the
+               bad FCS means we will throw away the whole message, anyway. */
+        }
+        /* Some T.38 implementations send multiple T38_FIELD_HDLC_FCS_BAD_SIG_END messages, in IFP packets with
+           incrementing sequence numbers, which are actually repeats. They get through to this point because
+           of the incrementing sequence numbers. We need to filter them here in a context sensitive manner. */
+        if (t->current_rx_data_type != data_type  ||  t->current_rx_field_type != field_type)
+        {
+            span_log(&s->logging, SPAN_LOG_FLOW, "Type %s - CRC bad, sig end (%s)\n", (fe->hdlc_rx.len >= 3)  ?  t30_frametype(fe->hdlc_rx.buf[2])  :  "???", (fe->rx_data_missing)  ?  "missing octets"  :  "clean");
+            hdlc_accept_frame(s, fe->hdlc_rx.buf, fe->hdlc_rx.len, FALSE);
+            hdlc_accept_frame(s, NULL, SIG_STATUS_CARRIER_DOWN, TRUE);
+        }
+        fe->hdlc_rx.len = 0;
+        fe->rx_data_missing = FALSE;
+        /* Treat this like a no signal indicator has occurred, so if the no signal indicator is missing, we are still OK */
+        fake_rx_indicator(t, s, T38_IND_NO_SIGNAL);
+        break;
+    case T38_FIELD_HDLC_SIG_END:
+        if (len > 0)
+        {
+            span_log(&s->logging, SPAN_LOG_WARNING, "There is data in a T38_FIELD_HDLC_SIG_END!\n");
+            /* The sender has incorrectly included data in this message, but there seems nothing meaningful
+               it could be. There could not be an FCS good/bad report beyond this. */
+        }
+        /* Some T.38 implementations send multiple T38_FIELD_HDLC_SIG_END messages, in IFP packets with
+           incrementing sequence numbers, which are actually repeats. They get through to this point because
+           of the incrementing sequence numbers. We need to filter them here in a context sensitive manner. */
+        if (t->current_rx_data_type != data_type  ||  t->current_rx_field_type != field_type)
+        {
+            /* WORKAROUND: At least some Mediatrix boxes have a bug, where they can send this message at the
+                           end of non-ECM data. We need to tolerate this. We use the generic receive complete
+                           indication, rather than the specific HDLC carrier down. */
+            /* This message is expected under 2 circumstances. One is as an alternative to T38_FIELD_HDLC_FCS_OK_SIG_END - 
+               i.e. they send T38_FIELD_HDLC_FCS_OK, and then T38_FIELD_HDLC_SIG_END when the carrier actually drops.
+               The other is because the HDLC signal drops unexpectedly - i.e. not just after a final frame. */
+            fe->hdlc_rx.len = 0;
+            fe->rx_data_missing = FALSE;
+            front_end_status(s, T30_FRONT_END_RECEIVE_COMPLETE);
+        }
+        /* Treat this like a no signal indicator has occurred, so if the no signal indicator is missing, we are still OK */
+        fake_rx_indicator(t, s, T38_IND_NO_SIGNAL);
+        break;
+    case T38_FIELD_T4_NON_ECM_DATA:
+        if (!fe->rx_signal_present)
+        {
+            t30_non_ecm_put_bit(&s->t30, SIG_STATUS_TRAINING_SUCCEEDED);
+            fe->rx_signal_present = TRUE;
+        }
+        if (len > 0)
+        {
+            bit_reverse(buf2, buf, len);
+            t30_non_ecm_put_chunk(&s->t30, buf2, len);
+        }
+        fe->timeout_rx_samples = fe->samples + ms_to_samples(MID_RX_TIMEOUT);
+        break;
+    case T38_FIELD_T4_NON_ECM_SIG_END:
+        /* Some T.38 implementations send multiple T38_FIELD_T4_NON_ECM_SIG_END messages, in IFP packets with
+           incrementing sequence numbers, which are actually repeats. They get through to this point because
+           of the incrementing sequence numbers. We need to filter them here in a context sensitive manner. */
+        if (t->current_rx_data_type != data_type  ||  t->current_rx_field_type != field_type)
+        {
+            if (len > 0)
+            {
+                if (!fe->rx_signal_present)
+                {
+                    t30_non_ecm_put_bit(&s->t30, SIG_STATUS_TRAINING_SUCCEEDED);
+                    fe->rx_signal_present = TRUE;
+                }
+                bit_reverse(buf2, buf, len);
+                t30_non_ecm_put_chunk(&s->t30, buf2, len);
+            }
+            /* WORKAROUND: At least some Mediatrix boxes have a bug, where they can send HDLC signal end where
+                           they should send non-ECM signal end. It is possible they also do the opposite.
+                           We need to tolerate this, so we use the generic receive complete
+                           indication, rather than the specific non-ECM carrier down. */
+            front_end_status(s, T30_FRONT_END_RECEIVE_COMPLETE);
+        }
+        fe->rx_signal_present = FALSE;
+        /* Treat this like a no signal indicator has occurred, so if the no signal indicator is missing, we are still OK */
+        fake_rx_indicator(t, s, T38_IND_NO_SIGNAL);
+        break;
+    default:
+        break;
+    }
+    /*endswitch*/
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void send_hdlc(void *user_data, const uint8_t *msg, int len)
+{
+    t38_terminal_state_t *s;
+
+    s = (t38_terminal_state_t *) user_data;
+    if (len <= 0)
+    {
+        s->t38_fe.hdlc_tx.len = -1;
+    }
+    else
+    {
+        s->t38_fe.hdlc_tx.extra_bits = extra_bits_in_stuffed_frame(msg, len);
+        bit_reverse(s->t38_fe.hdlc_tx.buf, msg, len);
+        s->t38_fe.hdlc_tx.len = len;
+        s->t38_fe.hdlc_tx.ptr = 0;
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+static __inline__ int bits_to_us(t38_terminal_state_t *s, int bits)
+{
+    if (s->t38_fe.ms_per_tx_chunk == 0  ||  s->t38_fe.tx_bit_rate == 0)
+        return 0;
+    return bits*1000000/s->t38_fe.tx_bit_rate;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void set_octets_per_data_packet(t38_terminal_state_t *s, int bit_rate)
+{
+    s->t38_fe.tx_bit_rate = bit_rate;
+    if (s->t38_fe.ms_per_tx_chunk)
+    {
+        s->t38_fe.octets_per_data_packet = s->t38_fe.ms_per_tx_chunk*bit_rate/(8*1000);
+        /* Make sure we have a positive number (i.e. we didn't truncate to zero). */
+        if (s->t38_fe.octets_per_data_packet < 1)
+            s->t38_fe.octets_per_data_packet = 1;
+    }
+    else
+    {
+        s->t38_fe.octets_per_data_packet = MAX_OCTETS_PER_UNPACED_CHUNK;
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+static int stream_non_ecm(t38_terminal_state_t *s)
+{
+    t38_terminal_front_end_state_t *fe;
+    uint8_t buf[MAX_OCTETS_PER_UNPACED_CHUNK + 50];
+    int delay;
+    int len;
+
+    fe = &s->t38_fe;
+    for (delay = 0;  delay == 0;  )
+    {
+        switch (fe->timed_step)
+        {
+        case T38_TIMED_STEP_NON_ECM_MODEM:
+            /* Create a 75ms silence */
+            if (fe->t38.current_tx_indicator != T38_IND_NO_SIGNAL)
+                delay = t38_core_send_indicator(&fe->t38, T38_IND_NO_SIGNAL);
+            else
+                delay = 75000;
+            fe->timed_step = T38_TIMED_STEP_NON_ECM_MODEM_2;
+            fe->next_tx_samples = fe->samples;
+            break;
+        case T38_TIMED_STEP_NON_ECM_MODEM_2:
+            /* Switch on a fast modem, and give the training time to complete */
+            delay = t38_core_send_indicator(&fe->t38, fe->next_tx_indicator);
+            fe->timed_step = T38_TIMED_STEP_NON_ECM_MODEM_3;
+            break;
+        case T38_TIMED_STEP_NON_ECM_MODEM_3:
+            /* Send a chunk of non-ECM image data */
+            /* T.38 says it is OK to send the last of the non-ECM data in the signal end message.
+               However, I think the early versions of T.38 said the signal end message should not
+               contain data. Hopefully, following the current spec will not cause compatibility
+               issues. */
+            len = t30_non_ecm_get_chunk(&s->t30, buf, fe->octets_per_data_packet);
+            if (len > 0)
+                bit_reverse(buf, buf, len);
+            if (len < fe->octets_per_data_packet)
+            {
+                /* That's the end of the image data. */
+                if (s->t38_fe.ms_per_tx_chunk)
+                {
+                    /* Pad the end of the data with some zeros. If we just stop abruptly
+                       at the end of the EOLs, some ATAs fail to clean up properly before
+                       shutting down their transmit modem, and the last few rows of the image
+                       are lost or corrupted. Simply delaying the no-signal message does not
+                       help for all implentations. It is usually ignored, which is probably
+                       the right thing to do after receiving a message saying the signal has
+                       ended. */
+                    memset(buf + len, 0, fe->octets_per_data_packet - len);
+                    fe->non_ecm_trailer_bytes = 3*fe->octets_per_data_packet + len;
+                    len = fe->octets_per_data_packet;
+                    fe->timed_step = T38_TIMED_STEP_NON_ECM_MODEM_4;
+                }
+                else
+                {
+                    /* If we are sending quickly there seems no point in doing any padding */
+                    t38_core_send_data(&fe->t38, fe->current_tx_data_type, T38_FIELD_T4_NON_ECM_SIG_END, buf, len, T38_PACKET_CATEGORY_IMAGE_DATA_END);
+                    fe->timed_step = T38_TIMED_STEP_NON_ECM_MODEM_5;
+                    delay = 0;
+                    break;
+                }
+            }
+            t38_core_send_data(&fe->t38, fe->current_tx_data_type, T38_FIELD_T4_NON_ECM_DATA, buf, len, T38_PACKET_CATEGORY_IMAGE_DATA);
+            delay = bits_to_us(s, 8*len);
+            break;
+        case T38_TIMED_STEP_NON_ECM_MODEM_4:
+            /* Send padding */
+            len = fe->octets_per_data_packet;
+            fe->non_ecm_trailer_bytes -= fe->octets_per_data_packet;
+            if (fe->non_ecm_trailer_bytes <= 0)
+            {
+                len += fe->non_ecm_trailer_bytes;
+                memset(buf, 0, len);
+                t38_core_send_data(&fe->t38, fe->current_tx_data_type, T38_FIELD_T4_NON_ECM_SIG_END, buf, len, T38_PACKET_CATEGORY_IMAGE_DATA_END);
+                fe->timed_step = T38_TIMED_STEP_NON_ECM_MODEM_5;
+                /* Allow a bit more time than the data will take to play out, to ensure the far ATA does not
+                   cut things short. */
+                delay = bits_to_us(s, 8*len);
+                if (s->t38_fe.ms_per_tx_chunk)
+                    delay += 60000;
+                front_end_status(s, T30_FRONT_END_SEND_STEP_COMPLETE);
+                break;
+            }
+            memset(buf, 0, len);
+            t38_core_send_data(&fe->t38, fe->current_tx_data_type, T38_FIELD_T4_NON_ECM_DATA, buf, len, T38_PACKET_CATEGORY_IMAGE_DATA);
+            delay = bits_to_us(s, 8*len);
+            break;
+        case T38_TIMED_STEP_NON_ECM_MODEM_5:
+            /* This should not be needed, since the message above indicates the end of the signal, but it
+               seems like it can improve compatibility with quirky implementations. */
+            delay = t38_core_send_indicator(&fe->t38, T38_IND_NO_SIGNAL);
+            fe->timed_step = T38_TIMED_STEP_NONE;
+            return delay;
+        }
+    }
+    return delay;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int stream_hdlc(t38_terminal_state_t *s)
+{
+    t38_terminal_front_end_state_t *fe;
+    uint8_t buf[MAX_OCTETS_PER_UNPACED_CHUNK + 50];
+    t38_data_field_t data_fields[2];
+    int category;
+    int previous;
+    int delay;
+    int i;
+
+    fe = &s->t38_fe;
+    for (delay = 0;  delay == 0;  )
+    {
+        switch (fe->timed_step)
+        {
+        case T38_TIMED_STEP_HDLC_MODEM:
+            /* Create a 75ms silence */
+            if (fe->t38.current_tx_indicator != T38_IND_NO_SIGNAL)
+                delay = t38_core_send_indicator(&fe->t38, T38_IND_NO_SIGNAL);
+            else
+                delay = 75000;
+            fe->timed_step = T38_TIMED_STEP_HDLC_MODEM_2;
+            fe->next_tx_samples = fe->samples;
+            break;
+        case T38_TIMED_STEP_HDLC_MODEM_2:
+            /* Send HDLC preambling */
+            delay = t38_core_send_indicator(&fe->t38, fe->next_tx_indicator);
+            delay += t38_core_send_flags_delay(&fe->t38, fe->next_tx_indicator);
+            fe->timed_step = T38_TIMED_STEP_HDLC_MODEM_3;
+            break;
+        case T38_TIMED_STEP_HDLC_MODEM_3:
+            /* Send a chunk of HDLC data */
+            i = fe->hdlc_tx.len - fe->hdlc_tx.ptr;
+            if (fe->octets_per_data_packet >= i)
+            {
+                /* The last part of an HDLC frame */
+                if (fe->chunking_modes & T38_CHUNKING_MERGE_FCS_WITH_DATA)
+                {
+                    /* Copy the data, as we might be about to refill the buffer it is in */
+                    memcpy(buf, &fe->hdlc_tx.buf[fe->hdlc_tx.ptr], i);
+                    data_fields[0].field_type = T38_FIELD_HDLC_DATA;
+                    data_fields[0].field = buf;
+                    data_fields[0].field_len = i;
+
+                    /* Now see about the next HDLC frame. This will tell us whether to send FCS_OK or FCS_OK_SIG_END */
+                    previous = fe->current_tx_data_type;
+                    fe->hdlc_tx.ptr = 0;
+                    fe->hdlc_tx.len = 0;
+                    front_end_status(s, T30_FRONT_END_SEND_STEP_COMPLETE);
+                    /* The above step should have got the next HDLC step ready - either another frame, or an instruction to stop transmission. */
+                    if (fe->hdlc_tx.len < 0)
+                    {
+                        data_fields[1].field_type = T38_FIELD_HDLC_FCS_OK_SIG_END;
+                        data_fields[1].field = NULL;
+                        data_fields[1].field_len = 0;
+                        category = (s->t38_fe.current_tx_data_type == T38_DATA_V21)  ?  T38_PACKET_CATEGORY_CONTROL_DATA_END  :  T38_PACKET_CATEGORY_IMAGE_DATA_END;
+                        t38_core_send_data_multi_field(&fe->t38, fe->current_tx_data_type, data_fields, 2, category);
+                        fe->timed_step = T38_TIMED_STEP_HDLC_MODEM_5;
+                        /* We add a bit of extra time here, as with some implementations
+                           the carrier falling too abruptly causes data loss. */
+                        delay = bits_to_us(s, i*8 + fe->hdlc_tx.extra_bits);
+                        if (s->t38_fe.ms_per_tx_chunk)
+                            delay += 100000;
+                        front_end_status(s, T30_FRONT_END_SEND_STEP_COMPLETE);
+                    }
+                    else
+                    {
+                        data_fields[1].field_type = T38_FIELD_HDLC_FCS_OK;
+                        data_fields[1].field = NULL;
+                        data_fields[1].field_len = 0;
+                        category = (s->t38_fe.current_tx_data_type == T38_DATA_V21)  ?  T38_PACKET_CATEGORY_CONTROL_DATA  :  T38_PACKET_CATEGORY_IMAGE_DATA;
+                        t38_core_send_data_multi_field(&fe->t38, fe->current_tx_data_type, data_fields, 2, category);
+                        fe->timed_step = T38_TIMED_STEP_HDLC_MODEM_3;
+                        delay = bits_to_us(s, i*8 + fe->hdlc_tx.extra_bits);
+                    }
+                    break;
+                }
+                category = (s->t38_fe.current_tx_data_type == T38_DATA_V21)  ?  T38_PACKET_CATEGORY_CONTROL_DATA  :  T38_PACKET_CATEGORY_IMAGE_DATA;
+                t38_core_send_data(&fe->t38, fe->current_tx_data_type, T38_FIELD_HDLC_DATA, &fe->hdlc_tx.buf[fe->hdlc_tx.ptr], i, category);
+                fe->timed_step = T38_TIMED_STEP_HDLC_MODEM_4;
+            }
+            else
+            {
+                i = fe->octets_per_data_packet;
+                category = (s->t38_fe.current_tx_data_type == T38_DATA_V21)  ?  T38_PACKET_CATEGORY_CONTROL_DATA  :  T38_PACKET_CATEGORY_IMAGE_DATA;
+                t38_core_send_data(&fe->t38, fe->current_tx_data_type, T38_FIELD_HDLC_DATA, &fe->hdlc_tx.buf[fe->hdlc_tx.ptr], i, category);
+                fe->hdlc_tx.ptr += i;
+            }
+            delay = bits_to_us(s, i*8);
+            break;
+        case T38_TIMED_STEP_HDLC_MODEM_4:
+            /* End of HDLC frame */
+            previous = fe->current_tx_data_type;
+            fe->hdlc_tx.ptr = 0;
+            fe->hdlc_tx.len = 0;
+            front_end_status(s, T30_FRONT_END_SEND_STEP_COMPLETE);
+            /* The above step should have got the next HDLC step ready - either another frame, or an instruction to stop transmission. */
+            if (fe->hdlc_tx.len < 0)
+            {
+                /* End of transmission */
+                category = (s->t38_fe.current_tx_data_type == T38_DATA_V21)  ?  T38_PACKET_CATEGORY_CONTROL_DATA_END  :  T38_PACKET_CATEGORY_IMAGE_DATA_END;
+                t38_core_send_data(&fe->t38, previous, T38_FIELD_HDLC_FCS_OK_SIG_END, NULL, 0, category);
+                fe->timed_step = T38_TIMED_STEP_HDLC_MODEM_5;
+                /* We add a bit of extra time here, as with some implementations
+                   the carrier falling too abruptly causes data loss. */
+                delay = bits_to_us(s, fe->hdlc_tx.extra_bits);
+                if (s->t38_fe.ms_per_tx_chunk)
+                    delay += 100000;
+                front_end_status(s, T30_FRONT_END_SEND_STEP_COMPLETE);
+                break;
+            }
+            if (fe->hdlc_tx.len == 0)
+            {
+                /* Now, how did we get here? We have finished a frame, but have no new frame to
+                   send, and no end of transmission condition. */
+                span_log(&s->logging, SPAN_LOG_FLOW, "No new frame or end transmission condition.\n");
+            }
+            /* Finish the current frame off, and prepare for the next one. */
+            category = (s->t38_fe.current_tx_data_type == T38_DATA_V21)  ?  T38_PACKET_CATEGORY_CONTROL_DATA  :  T38_PACKET_CATEGORY_IMAGE_DATA;
+            t38_core_send_data(&fe->t38, previous, T38_FIELD_HDLC_FCS_OK, NULL, 0, category);
+            fe->timed_step = T38_TIMED_STEP_HDLC_MODEM_3;
+            /* We should now wait enough time for everything to clear through an analogue modem at the far end. */
+            delay = bits_to_us(s, fe->hdlc_tx.extra_bits);
+            break;
+        case T38_TIMED_STEP_HDLC_MODEM_5:
+            /* Note that some boxes do not like us sending a T38_FIELD_HDLC_SIG_END at this point.
+               A T38_IND_NO_SIGNAL should always be OK. */
+            delay = t38_core_send_indicator(&fe->t38, T38_IND_NO_SIGNAL);
+            fe->timed_step = T38_TIMED_STEP_NONE;
+            return delay;
+        }
+    }
+    return delay;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int stream_ced(t38_terminal_state_t *s)
+{
+    t38_terminal_front_end_state_t *fe;
+    int delay;
+
+    fe = &s->t38_fe;
+    for (delay = 0;  delay == 0;  )
+    {
+        switch (fe->timed_step)
+        {
+        case T38_TIMED_STEP_CED:
+            /* It seems common practice to start with a no signal indicator, though
+               this is not a specified requirement. Since we should be sending 200ms
+               of silence, starting the delay with a no signal indication makes sense.
+               We do need a 200ms delay, as that is a specification requirement. */
+            fe->timed_step = T38_TIMED_STEP_CED_2;
+            delay = t38_core_send_indicator(&fe->t38, T38_IND_NO_SIGNAL);
+            delay = 200000;
+            fe->next_tx_samples = fe->samples;
+            break;
+        case T38_TIMED_STEP_CED_2:
+            /* Initial 200ms delay over. Send the CED indicator */
+            fe->timed_step = T38_TIMED_STEP_CED_3;
+            delay = t38_core_send_indicator(&fe->t38, T38_IND_CED);
+            fe->current_tx_data_type = T38_DATA_NONE;
+            break;
+        case T38_TIMED_STEP_CED_3:
+            /* End of CED */
+            fe->timed_step = T38_TIMED_STEP_NONE;
+            front_end_status(s, T30_FRONT_END_SEND_STEP_COMPLETE);
+            return 0;
+        }
+    }
+    return delay;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int stream_cng(t38_terminal_state_t *s)
+{
+    t38_terminal_front_end_state_t *fe;
+    int delay;
+
+    fe = &s->t38_fe;
+    for (delay = 0;  delay == 0;  )
+    {
+        switch (fe->timed_step)
+        {
+        case T38_TIMED_STEP_CNG:
+            /* It seems common practice to start with a no signal indicator, though
+               this is not a specified requirement of the T.38 spec. Since we should
+               be sending 200ms of silence, according to T.30, starting that delay with
+               a no signal indication makes sense. */
+            fe->timed_step = T38_TIMED_STEP_CNG_2;
+            delay = t38_core_send_indicator(&fe->t38, T38_IND_NO_SIGNAL);
+            delay = 200000;
+            fe->next_tx_samples = fe->samples;
+            break;
+        case T38_TIMED_STEP_CNG_2:
+            /* Initial short delay over. Send the CNG indicator. CNG persists until something
+               coming the other way interrupts it, or a long timeout controlled by the T.30 engine
+               expires. */
+            fe->timed_step = T38_TIMED_STEP_NONE;
+            delay = t38_core_send_indicator(&fe->t38, T38_IND_CNG);
+            fe->current_tx_data_type = T38_DATA_NONE;
+            return delay;
+        }
+    }
+    return delay;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t38_terminal_send_timeout(t38_terminal_state_t *s, int samples)
+{
+    t38_terminal_front_end_state_t *fe;
+    int delay;
+
+    fe = &s->t38_fe;
+    if (fe->current_rx_type == T30_MODEM_DONE  ||  fe->current_tx_type == T30_MODEM_DONE)
+        return TRUE;
+
+    fe->samples += samples;
+    t30_timer_update(&s->t30, samples);
+    if (fe->timeout_rx_samples  &&  fe->samples > fe->timeout_rx_samples)
+    {
+        span_log(&s->logging, SPAN_LOG_FLOW, "Timeout mid-receive\n");
+        fe->timeout_rx_samples = 0;
+        front_end_status(s, T30_FRONT_END_RECEIVE_COMPLETE);
+    }
+    if (fe->timed_step == T38_TIMED_STEP_NONE)
+        return FALSE;
+    /* Wait until the right time comes along, unless we are working in "no delays" mode, while talking to an
+       IAF terminal. */
+    if (fe->ms_per_tx_chunk  &&  fe->samples < fe->next_tx_samples)
+        return FALSE;
+    /* Its time to send something */
+    delay = 0;
+    switch (fe->timed_step & 0xFFF0)
+    {
+    case T38_TIMED_STEP_NON_ECM_MODEM:
+        delay = stream_non_ecm(s);
+        break;
+    case T38_TIMED_STEP_HDLC_MODEM:
+        delay = stream_hdlc(s);
+        break;
+    case T38_TIMED_STEP_CED:
+        delay = stream_ced(s);
+        break;
+    case T38_TIMED_STEP_CNG:
+        delay = stream_cng(s);
+        break;
+    case T38_TIMED_STEP_PAUSE:
+        /* End of timed pause */
+        fe->timed_step = T38_TIMED_STEP_NONE;
+        front_end_status(s, T30_FRONT_END_SEND_STEP_COMPLETE);
+        break;
+    }
+    fe->next_tx_samples += us_to_samples(delay);
+    return FALSE;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void set_rx_type(void *user_data, int type, int bit_rate, int short_train, int use_hdlc)
+{
+    t38_terminal_state_t *s;
+
+    s = (t38_terminal_state_t *) user_data;
+    span_log(&s->logging, SPAN_LOG_FLOW, "Set rx type %d\n", type);
+    s->t38_fe.current_rx_type = type;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void start_tx(t38_terminal_front_end_state_t *fe, int use_hdlc)
+{
+    /* The actual transmission process depends on whether we are sending at a paced manner,
+       for interaction with a traditional FAX machine, or streaming as fast as we can, normally
+       over a TCP connection to a machine directly connected to the internet. */
+    if (fe->ms_per_tx_chunk)
+    {
+        /* Start the paced packet transmission process. */
+        fe->timed_step = (use_hdlc)  ?  T38_TIMED_STEP_HDLC_MODEM  :  T38_TIMED_STEP_NON_ECM_MODEM;
+        if (fe->next_tx_samples < fe->samples)
+            fe->next_tx_samples = fe->samples;
+    }
+    else
+    {
+        /* Start the fast streaming transmission process. */
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+static void set_tx_type(void *user_data, int type, int bit_rate, int short_train, int use_hdlc)
+{
+    t38_terminal_state_t *s;
+    t38_terminal_front_end_state_t *fe;
+
+    s = (t38_terminal_state_t *) user_data;
+    fe = &s->t38_fe;
+    span_log(&s->logging, SPAN_LOG_FLOW, "Set tx type %d\n", type);
+    if (fe->current_tx_type == type)
+        return;
+
+    set_octets_per_data_packet(s, bit_rate);
+    switch (type)
+    {
+    case T30_MODEM_NONE:
+        /* If a "no signal" indicator is waiting to be played out, don't disturb it. */
+        if (fe->timed_step != T38_TIMED_STEP_NON_ECM_MODEM_5  &&  fe->timed_step != T38_TIMED_STEP_HDLC_MODEM_5)
+            fe->timed_step = T38_TIMED_STEP_NONE;
+        fe->current_tx_data_type = T38_DATA_NONE;
+        break;
+    case T30_MODEM_PAUSE:
+        fe->next_tx_samples = fe->samples + ms_to_samples(short_train);
+        fe->timed_step = T38_TIMED_STEP_PAUSE;
+        fe->current_tx_data_type = T38_DATA_NONE;
+        break;
+    case T30_MODEM_CED:
+        fe->next_tx_samples = fe->samples;
+        fe->timed_step = T38_TIMED_STEP_CED;
+        fe->current_tx_data_type = T38_DATA_NONE;
+        break;
+    case T30_MODEM_CNG:
+        fe->next_tx_samples = fe->samples;
+        fe->timed_step = T38_TIMED_STEP_CNG;
+        fe->current_tx_data_type = T38_DATA_NONE;
+        break;
+    case T30_MODEM_V21:
+        fe->next_tx_indicator = T38_IND_V21_PREAMBLE;
+        fe->current_tx_data_type = T38_DATA_V21;
+        start_tx(fe, use_hdlc);
+        break;
+    case T30_MODEM_V27TER:
+        switch (bit_rate)
+        {
+        case 2400:
+            fe->next_tx_indicator = T38_IND_V27TER_2400_TRAINING;
+            fe->current_tx_data_type = T38_DATA_V27TER_2400;
+            break;
+        case 4800:
+            fe->next_tx_indicator = T38_IND_V27TER_4800_TRAINING;
+            fe->current_tx_data_type = T38_DATA_V27TER_4800;
+            break;
+        }
+        start_tx(fe, use_hdlc);
+        break;
+    case T30_MODEM_V29:
+        switch (bit_rate)
+        {
+        case 7200:
+            fe->next_tx_indicator = T38_IND_V29_7200_TRAINING;
+            fe->current_tx_data_type = T38_DATA_V29_7200;
+            break;
+        case 9600:
+            fe->next_tx_indicator = T38_IND_V29_9600_TRAINING;
+            fe->current_tx_data_type = T38_DATA_V29_9600;
+            break;
+        }
+        start_tx(fe, use_hdlc);
+        break;
+    case T30_MODEM_V17:
+        switch (bit_rate)
+        {
+        case 7200:
+            fe->next_tx_indicator = (short_train)  ?  T38_IND_V17_7200_SHORT_TRAINING  :  T38_IND_V17_7200_LONG_TRAINING;
+            fe->current_tx_data_type = T38_DATA_V17_7200;
+            break;
+        case 9600:
+            fe->next_tx_indicator = (short_train)  ?  T38_IND_V17_9600_SHORT_TRAINING  :  T38_IND_V17_9600_LONG_TRAINING;
+            fe->current_tx_data_type = T38_DATA_V17_9600;
+            break;
+        case 12000:
+            fe->next_tx_indicator = (short_train)  ?  T38_IND_V17_12000_SHORT_TRAINING  :  T38_IND_V17_12000_LONG_TRAINING;
+            fe->current_tx_data_type = T38_DATA_V17_12000;
+            break;
+        case 14400:
+            fe->next_tx_indicator = (short_train)  ?  T38_IND_V17_14400_SHORT_TRAINING  :  T38_IND_V17_14400_LONG_TRAINING;
+            fe->current_tx_data_type = T38_DATA_V17_14400;
+            break;
+        }
+        start_tx(fe, use_hdlc);
+        break;
+    case T30_MODEM_DONE:
+        span_log(&s->logging, SPAN_LOG_FLOW, "FAX exchange complete\n");
+        fe->timed_step = T38_TIMED_STEP_NONE;
+        fe->current_tx_data_type = T38_DATA_NONE;
+        break;
+    }
+    fe->current_tx_type = type;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t38_terminal_set_config(t38_terminal_state_t *s, int without_pacing)
+{
+    if (without_pacing)
+    {
+        /* Continuous streaming mode, as used for TPKT over TCP transport */
+        /* Inhibit indicator packets */
+        t38_set_redundancy_control(&s->t38_fe.t38, T38_PACKET_CATEGORY_INDICATOR, 0);
+        t38_set_redundancy_control(&s->t38_fe.t38, T38_PACKET_CATEGORY_CONTROL_DATA, 1);
+        t38_set_redundancy_control(&s->t38_fe.t38, T38_PACKET_CATEGORY_CONTROL_DATA_END, 1);
+        t38_set_redundancy_control(&s->t38_fe.t38, T38_PACKET_CATEGORY_IMAGE_DATA, 1);
+        t38_set_redundancy_control(&s->t38_fe.t38, T38_PACKET_CATEGORY_IMAGE_DATA_END, 1);
+        s->t38_fe.ms_per_tx_chunk = 0;
+    }
+    else
+    {
+        /* Paced streaming mode, as used for UDP transports */
+        t38_set_redundancy_control(&s->t38_fe.t38, T38_PACKET_CATEGORY_INDICATOR, INDICATOR_TX_COUNT);
+        t38_set_redundancy_control(&s->t38_fe.t38, T38_PACKET_CATEGORY_CONTROL_DATA, DATA_TX_COUNT);
+        t38_set_redundancy_control(&s->t38_fe.t38, T38_PACKET_CATEGORY_CONTROL_DATA_END, DATA_END_TX_COUNT);
+        t38_set_redundancy_control(&s->t38_fe.t38, T38_PACKET_CATEGORY_IMAGE_DATA, DATA_TX_COUNT);
+        t38_set_redundancy_control(&s->t38_fe.t38, T38_PACKET_CATEGORY_IMAGE_DATA_END, DATA_END_TX_COUNT);
+        s->t38_fe.ms_per_tx_chunk = MS_PER_TX_CHUNK;
+    }
+    set_octets_per_data_packet(s, 300);
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t38_terminal_set_tep_mode(t38_terminal_state_t *s, int use_tep)
+{
+    if (use_tep)
+        s->t38_fe.chunking_modes |= T38_CHUNKING_ALLOW_TEP_TIME;
+    else
+        s->t38_fe.chunking_modes &= ~T38_CHUNKING_ALLOW_TEP_TIME;
+    t38_set_tep_handling(&s->t38_fe.t38, use_tep);
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t38_terminal_set_fill_bit_removal(t38_terminal_state_t *s, int remove)
+{
+    if (remove)
+        s->t38_fe.iaf |= T30_IAF_MODE_NO_FILL_BITS;
+    else
+        s->t38_fe.iaf &= ~T30_IAF_MODE_NO_FILL_BITS;
+    t30_set_iaf_mode(&s->t30, s->t38_fe.iaf);
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(t30_state_t *) t38_terminal_get_t30_state(t38_terminal_state_t *s)
+{
+    return &s->t30;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(t38_core_state_t *) t38_terminal_get_t38_core_state(t38_terminal_state_t *s)
+{
+    return &s->t38_fe.t38;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int t38_terminal_t38_fe_init(t38_terminal_state_t *t,
+                                    t38_tx_packet_handler_t *tx_packet_handler,
+                                    void *tx_packet_user_data)
+{
+    t38_terminal_front_end_state_t *s;
+    
+    s = &t->t38_fe;
+    t38_core_init(&s->t38,
+                  process_rx_indicator,
+                  process_rx_data,
+                  process_rx_missing,
+                  (void *) t,
+                  tx_packet_handler,
+                  tx_packet_user_data);
+    t38_set_fastest_image_data_rate(&s->t38, 14400);
+
+    s->rx_signal_present = FALSE;
+    s->timed_step = T38_TIMED_STEP_NONE;
+    //s->iaf = T30_IAF_MODE_T37 | T30_IAF_MODE_T38;
+    s->iaf = T30_IAF_MODE_T38;
+
+    s->current_tx_data_type = T38_DATA_NONE;
+    s->next_tx_samples = 0;
+    s->chunking_modes = T38_CHUNKING_ALLOW_TEP_TIME;
+
+    s->hdlc_tx.ptr = 0;
+
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(logging_state_t *) t38_terminal_get_logging_state(t38_terminal_state_t *s)
+{
+    return &s->logging;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(t38_terminal_state_t *) t38_terminal_init(t38_terminal_state_t *s,
+                                                       int calling_party,
+                                                       t38_tx_packet_handler_t *tx_packet_handler,
+                                                       void *tx_packet_user_data)
+{
+    if (tx_packet_handler == NULL)
+        return NULL;
+
+    if (s == NULL)
+    {
+        if ((s = (t38_terminal_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, "T.38T");
+
+    t38_terminal_t38_fe_init(s, tx_packet_handler, tx_packet_user_data);
+
+    t38_terminal_set_config(s, FALSE);
+
+    t30_init(&s->t30,
+             calling_party,
+             set_rx_type,
+             (void *) s,
+             set_tx_type,
+             (void *) s,
+             send_hdlc,
+             (void *) s);
+    t30_set_iaf_mode(&s->t30, s->t38_fe.iaf);
+    t30_set_supported_modems(&s->t30,
+                             T30_SUPPORT_V27TER | T30_SUPPORT_V29 | T30_SUPPORT_V17 | T30_SUPPORT_IAF);
+    t30_restart(&s->t30);
+    return s;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t38_terminal_release(t38_terminal_state_t *s)
+{
+    t30_release(&s->t30);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t38_terminal_free(t38_terminal_state_t *s)
+{
+    t38_terminal_release(s);
+    free(s);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+/*- End of file ------------------------------------------------------------*/

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