diff spandsp-0.0.3/spandsp-0.0.3/src/t38_gateway.c @ 5:f762bf195c4b

import spandsp-0.0.3
author Peter Meerwald <pmeerw@cosy.sbg.ac.at>
date Fri, 25 Jun 2010 16:00:21 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spandsp-0.0.3/spandsp-0.0.3/src/t38_gateway.c	Fri Jun 25 16:00:21 2010 +0200
@@ -0,0 +1,1544 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * t38_gateway.c - An implementation of a T.38 gateway, less the packet exchange part
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2005, 2006 Steve Underwood
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2, 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU 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_gateway.c,v 1.62 2006/12/08 12:47:29 steveu Exp $
+ */
+
+/*! \file */
+
+#ifdef 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 <assert.h>
+#include <tiffio.h>
+
+#include "spandsp/telephony.h"
+#include "spandsp/logging.h"
+#include "spandsp/queue.h"
+#include "spandsp/bit_operations.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/silence_gen.h"
+#include "spandsp/fsk.h"
+#include "spandsp/v29rx.h"
+#include "spandsp/v29tx.h"
+#include "spandsp/v27ter_rx.h"
+#include "spandsp/v27ter_tx.h"
+#if defined(ENABLE_V17)
+#include "spandsp/v17rx.h"
+#include "spandsp/v17tx.h"
+#endif
+#include "spandsp/t4.h"
+
+#include "spandsp/t30_fcf.h"
+#include "spandsp/t35.h"
+#include "spandsp/t30.h"
+
+#include "spandsp/t38_core.h"
+#include "spandsp/t38_gateway.h"
+
+#define MS_PER_TX_CHUNK             30
+
+#define INDICATOR_TX_COUNT          3
+
+#define DISBIT1     0x01
+#define DISBIT2     0x02
+#define DISBIT3     0x04
+#define DISBIT4     0x08
+#define DISBIT5     0x10
+#define DISBIT6     0x20
+#define DISBIT7     0x40
+#define DISBIT8     0x80
+
+enum
+{
+    T38_NONE,
+    T38_V27TER_RX,
+    T38_V29_RX,
+    T38_V17_RX
+};
+
+enum
+{
+    HDLC_FLAG_FINISHED = 0x01,
+    HDLC_FLAG_CORRUPT_CRC = 0x02,
+    HDLC_FLAG_PROCEED_WITH_OUTPUT = 0x04,
+    HDLC_FLAG_MISSING_DATA = 0x08
+};
+
+static int restart_rx_modem(t38_gateway_state_t *s);
+static void add_to_non_ecm_tx_buffer(t38_gateway_state_t *s, const uint8_t *buf, int len);
+static int non_ecm_get_bit(void *user_data);
+static int process_rx_indicator(t38_core_state_t *t, void *user_data, int indicator);
+
+static void hdlc_underflow_handler(void *user_data)
+{
+    t38_gateway_state_t *s;
+    int old_data_type;
+    
+    s = (t38_gateway_state_t *) user_data;
+    span_log(&s->logging, SPAN_LOG_FLOW, "HDLC underflow at %d\n", s->hdlc_out);
+    /* If the current HDLC buffer is not at the HDLC_FLAG_PROCEED_WITH_OUTPUT stage, this
+       underflow must be an end of preamble condition. */
+    if ((s->hdlc_flags[s->hdlc_out] & HDLC_FLAG_PROCEED_WITH_OUTPUT))
+    {
+        old_data_type = s->hdlc_contents[s->hdlc_out];
+        s->hdlc_len[s->hdlc_out] = 0;
+        s->hdlc_flags[s->hdlc_out] = 0;
+        s->hdlc_contents[s->hdlc_out] = 0;
+        if (++s->hdlc_out >= T38_TX_HDLC_BUFS)
+            s->hdlc_out = 0;
+        span_log(&s->logging, SPAN_LOG_FLOW, "HDLC next is 0x%X\n", s->hdlc_contents[s->hdlc_out]);
+        if ((s->hdlc_contents[s->hdlc_out] & 0x100))
+        {
+            /* The next thing in the queue is an indicator, so we need to stop this modem. */
+            span_log(&s->logging, SPAN_LOG_FLOW, "HDLC shutdown\n");
+            hdlc_tx_frame(&s->hdlctx, NULL, 0);
+        }
+        else if ((s->hdlc_contents[s->hdlc_out] & 0x200))
+        {
+            /* Check if we should start sending the next frame */
+            if ((s->hdlc_flags[s->hdlc_out] & HDLC_FLAG_PROCEED_WITH_OUTPUT))
+            {
+                /* This frame is ready to go, and uses the same modem we are running now. So, send
+                   whatever we have. This might or might not be an entire frame. */
+                span_log(&s->logging, SPAN_LOG_FLOW, "HDLC start next frame\n");
+                hdlc_tx_frame(&s->hdlctx, s->hdlc_buf[s->hdlc_out], s->hdlc_len[s->hdlc_out]);
+                if ((s->hdlc_flags[s->hdlc_out] & HDLC_FLAG_CORRUPT_CRC))
+                    hdlc_tx_corrupt_frame(&s->hdlctx);
+            }
+        }
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+static int set_next_tx_type(t38_gateway_state_t *s)
+{
+    tone_gen_descriptor_t tone_desc;
+    get_bit_func_t get_bit_func;
+    void *get_bit_user_data;
+    int indicator;
+
+    if (s->next_tx_handler)
+    {
+        /* There is a handler queued, so that is the next one */
+        s->tx_handler = s->next_tx_handler;
+        s->tx_user_data = s->next_tx_user_data;
+        s->next_tx_handler = NULL;
+        return TRUE;
+    }
+    if (s->hdlc_in == s->hdlc_out)
+        return FALSE;
+    if ((s->hdlc_contents[s->hdlc_out] & 0x100) == 0)
+        return FALSE;
+    indicator = (s->hdlc_contents[s->hdlc_out] & 0xFF);
+    s->hdlc_len[s->hdlc_out] = 0;
+    s->hdlc_flags[s->hdlc_out] = 0;
+    s->hdlc_contents[s->hdlc_out] = 0;
+    if (++s->hdlc_out >= T38_TX_HDLC_BUFS)
+        s->hdlc_out = 0;
+    span_log(&s->logging, SPAN_LOG_FLOW, "Changing to %s\n", t38_indicator(indicator));
+    if (s->short_train  &&  s->ecm_mode)
+    {
+        span_log(&s->logging, SPAN_LOG_FLOW, "HDLC mode\n");
+        hdlc_tx_init(&s->hdlctx, FALSE, 2, TRUE, hdlc_underflow_handler, s);
+        get_bit_func = (get_bit_func_t) hdlc_tx_get_bit;
+        get_bit_user_data = (void *) &s->hdlctx;
+    }
+    else
+    {
+        span_log(&s->logging, SPAN_LOG_FLOW, "non-ECM mode\n");
+        get_bit_func = non_ecm_get_bit;
+        get_bit_user_data = (void *) s;
+    }
+    switch (indicator)
+    {
+    case T38_IND_NO_SIGNAL:
+        /* Impose 75ms minimum on transmitted silence */
+        silence_gen_set(&s->silence_gen, ms_to_samples(75));
+        s->tx_handler = (span_tx_handler_t *) &(silence_gen);
+        s->tx_user_data = &(s->silence_gen);
+        s->next_tx_handler = NULL;
+        break;
+    case T38_IND_CNG:
+        /* 0.5s of 1100Hz + 3.0s of silence repeating */
+        make_tone_gen_descriptor(&tone_desc,
+                                 1100,
+                                 -11,
+                                 0,
+                                 0,
+                                 500,
+                                 3000,
+                                 0,
+                                 0,
+                                 TRUE);
+        tone_gen_init(&(s->tone_gen), &tone_desc);
+        s->tx_handler = (span_tx_handler_t *) &(tone_gen);
+        s->tx_user_data = &(s->tone_gen);
+        silence_gen_set(&s->silence_gen, 0);
+        s->next_tx_handler = (span_tx_handler_t *) &(silence_gen);
+        s->next_tx_user_data = &(s->silence_gen);
+        break;
+    case T38_IND_CED:
+        /* 0.2s of silence, then 2.6s to 4s of 2100Hz tone, then 75ms of silence. */
+        silence_gen_alter(&s->silence_gen, ms_to_samples(200));
+        make_tone_gen_descriptor(&tone_desc,
+                                 2100,
+                                 -11,
+                                 0,
+                                 0,
+                                 2600,
+                                 75,
+                                 0,
+                                 0,
+                                 FALSE);
+        tone_gen_init(&(s->tone_gen), &tone_desc);
+        s->tx_handler = (span_tx_handler_t *) &(silence_gen);
+        s->tx_user_data = &(s->silence_gen);
+        s->next_tx_handler = (span_tx_handler_t *) &(tone_gen);
+        s->next_tx_user_data = &(s->tone_gen);
+        break;
+    case T38_IND_V21_PREAMBLE:
+        hdlc_tx_init(&s->hdlctx, FALSE, 2, TRUE, hdlc_underflow_handler, s);
+        hdlc_tx_preamble(&s->hdlctx, 32);
+        s->hdlc_len[s->hdlc_in] = 0;
+        fsk_tx_init(&(s->v21tx), &preset_fsk_specs[FSK_V21CH2], (get_bit_func_t) hdlc_tx_get_bit, &s->hdlctx);
+        /* Impose a minimum silence */
+        silence_gen_alter(&s->silence_gen, ms_to_samples(75));
+        s->tx_handler = (span_tx_handler_t *) &(silence_gen);
+        s->tx_user_data = &(s->silence_gen);
+        s->next_tx_handler = (span_tx_handler_t *) &(fsk_tx);
+        s->next_tx_user_data = &(s->v21tx);
+        break;
+    case T38_IND_V27TER_2400_TRAINING:
+        silence_gen_alter(&s->silence_gen, ms_to_samples(75));
+        hdlc_tx_preamble(&s->hdlctx, 60);
+        v27ter_tx_restart(&(s->v27ter_tx), 2400, s->use_tep);
+        v27ter_tx_set_get_bit(&(s->v27ter_tx), get_bit_func, get_bit_user_data);
+        s->tx_handler = (span_tx_handler_t *) &(silence_gen);
+        s->tx_user_data = &(s->silence_gen);
+        s->next_tx_handler = (span_tx_handler_t *) &(v27ter_tx);
+        s->next_tx_user_data = &(s->v27ter_tx);
+        break;
+    case T38_IND_V27TER_4800_TRAINING:
+        hdlc_tx_preamble(&s->hdlctx, 120);
+        silence_gen_alter(&s->silence_gen, ms_to_samples(75));
+        v27ter_tx_restart(&(s->v27ter_tx), 4800, s->use_tep);
+        v27ter_tx_set_get_bit(&(s->v27ter_tx), get_bit_func, get_bit_user_data);
+        s->tx_handler = (span_tx_handler_t *) &(silence_gen);
+        s->tx_user_data = &(s->silence_gen);
+        s->next_tx_handler = (span_tx_handler_t *) &(v27ter_tx);
+        s->next_tx_user_data = &(s->v27ter_tx);
+        break;
+    case T38_IND_V29_7200_TRAINING:
+        hdlc_tx_preamble(&s->hdlctx, 180);
+        silence_gen_alter(&s->silence_gen, ms_to_samples(75));
+        v29_tx_restart(&(s->v29tx), 7200, s->use_tep);
+        v29_tx_set_get_bit(&(s->v29tx), get_bit_func, get_bit_user_data);
+        s->tx_handler = (span_tx_handler_t *) &(silence_gen);
+        s->tx_user_data = &(s->silence_gen);
+        s->next_tx_handler = (span_tx_handler_t *) &(v29_tx);
+        s->next_tx_user_data = &(s->v29tx);
+        break;
+    case T38_IND_V29_9600_TRAINING:
+        hdlc_tx_preamble(&s->hdlctx, 240);
+        silence_gen_alter(&s->silence_gen, ms_to_samples(75));
+        v29_tx_restart(&(s->v29tx), 9600, s->use_tep);
+        v29_tx_set_get_bit(&(s->v29tx), get_bit_func, get_bit_user_data);
+        s->tx_handler = (span_tx_handler_t *) &(silence_gen);
+        s->tx_user_data = &(s->silence_gen);
+        s->next_tx_handler = (span_tx_handler_t *) &(v29_tx);
+        s->next_tx_user_data = &(s->v29tx);
+        break;
+#if defined(ENABLE_V17)
+    case T38_IND_V17_7200_SHORT_TRAINING:
+        hdlc_tx_preamble(&s->hdlctx, 180);
+        silence_gen_alter(&s->silence_gen, ms_to_samples(75));
+        v17_tx_restart(&(s->v17tx), 7200, s->use_tep, s->short_train);
+        v17_tx_set_get_bit(&(s->v17tx), get_bit_func, get_bit_user_data);
+        s->tx_handler = (span_tx_handler_t *) &(silence_gen);
+        s->tx_user_data = &(s->silence_gen);
+        s->next_tx_handler = (span_tx_handler_t *) &(v17_tx);
+        s->next_tx_user_data = &(s->v17tx);
+        break;
+    case T38_IND_V17_7200_LONG_TRAINING:
+        hdlc_tx_preamble(&s->hdlctx, 180);
+        silence_gen_alter(&s->silence_gen, ms_to_samples(75));
+        v17_tx_restart(&(s->v17tx), 7200, s->use_tep, s->short_train);
+        v17_tx_set_get_bit(&(s->v17tx), get_bit_func, get_bit_user_data);
+        s->tx_handler = (span_tx_handler_t *) &(silence_gen);
+        s->tx_user_data = &(s->silence_gen);
+        s->next_tx_handler = (span_tx_handler_t *) &(v17_tx);
+        s->next_tx_user_data = &(s->v17tx);
+        break;
+    case T38_IND_V17_9600_SHORT_TRAINING:
+        hdlc_tx_preamble(&s->hdlctx, 240);
+        silence_gen_alter(&s->silence_gen, ms_to_samples(75));
+        v17_tx_restart(&(s->v17tx), 9600, s->use_tep, s->short_train);
+        v17_tx_set_get_bit(&(s->v17tx), get_bit_func, get_bit_user_data);
+        s->tx_handler = (span_tx_handler_t *) &(silence_gen);
+        s->tx_user_data = &(s->silence_gen);
+        s->next_tx_handler = (span_tx_handler_t *) &(v17_tx);
+        s->next_tx_user_data = &(s->v17tx);
+        break;
+    case T38_IND_V17_9600_LONG_TRAINING:
+        hdlc_tx_preamble(&s->hdlctx, 240);
+        silence_gen_alter(&s->silence_gen, ms_to_samples(75));
+        v17_tx_restart(&(s->v17tx), 9600, s->use_tep, s->short_train);
+        v17_tx_set_get_bit(&(s->v17tx), get_bit_func, get_bit_user_data);
+        s->tx_handler = (span_tx_handler_t *) &(silence_gen);
+        s->tx_user_data = &(s->silence_gen);
+        s->next_tx_handler = (span_tx_handler_t *) &(v17_tx);
+        s->next_tx_user_data = &(s->v17tx);
+        break;
+    case T38_IND_V17_12000_SHORT_TRAINING:
+        hdlc_tx_preamble(&s->hdlctx, 300);
+        silence_gen_alter(&s->silence_gen, ms_to_samples(75));
+        v17_tx_restart(&(s->v17tx), 12000, s->use_tep, s->short_train);
+        v17_tx_set_get_bit(&(s->v17tx), get_bit_func, get_bit_user_data);
+        s->tx_handler = (span_tx_handler_t *) &(silence_gen);
+        s->tx_user_data = &(s->silence_gen);
+        s->next_tx_handler = (span_tx_handler_t *) &(v17_tx);
+        s->next_tx_user_data = &(s->v17tx);
+        break;
+    case T38_IND_V17_12000_LONG_TRAINING:
+        hdlc_tx_preamble(&s->hdlctx, 300);
+        silence_gen_alter(&s->silence_gen, ms_to_samples(75));
+        v17_tx_restart(&(s->v17tx), 12000, s->use_tep, s->short_train);
+        v17_tx_set_get_bit(&(s->v17tx), get_bit_func, get_bit_user_data);
+        s->tx_handler = (span_tx_handler_t *) &(silence_gen);
+        s->tx_user_data = &(s->silence_gen);
+        s->next_tx_handler = (span_tx_handler_t *) &(v17_tx);
+        s->next_tx_user_data = &(s->v17tx);
+        break;
+    case T38_IND_V17_14400_SHORT_TRAINING:
+        hdlc_tx_preamble(&s->hdlctx, 360);
+        silence_gen_alter(&s->silence_gen, ms_to_samples(75));
+        v17_tx_restart(&(s->v17tx), 14400, s->use_tep, s->short_train);
+        v17_tx_set_get_bit(&(s->v17tx), get_bit_func, get_bit_user_data);
+        s->tx_handler = (span_tx_handler_t *) &(silence_gen);
+        s->tx_user_data = &(s->silence_gen);
+        s->next_tx_handler = (span_tx_handler_t *) &(v17_tx);
+        s->next_tx_user_data = &(s->v17tx);
+        break;
+    case T38_IND_V17_14400_LONG_TRAINING:
+        hdlc_tx_preamble(&s->hdlctx, 360);
+        silence_gen_alter(&s->silence_gen, ms_to_samples(75));
+        v17_tx_restart(&(s->v17tx), 14400, s->use_tep, s->short_train);
+        v17_tx_set_get_bit(&(s->v17tx), get_bit_func, get_bit_user_data);
+        s->tx_handler = (span_tx_handler_t *) &(silence_gen);
+        s->tx_user_data = &(s->silence_gen);
+        s->next_tx_handler = (span_tx_handler_t *) &(v17_tx);
+        s->next_tx_user_data = &(s->v17tx);
+        break;
+#endif
+    case T38_IND_V8_ANSAM:
+        break;
+    case T38_IND_V8_SIGNAL:
+        break;
+    case T38_IND_V34_CNTL_CHANNEL_1200:
+        break;
+    case T38_IND_V34_PRI_CHANNEL:
+        break;
+    case T38_IND_V34_CC_RETRAIN:
+        break;
+    case T38_IND_V33_12000_TRAINING:
+        break;
+    case T38_IND_V33_14400_TRAINING:
+        break;
+    default:
+        break;
+    }
+    s->non_ecm_bit_no = 0;
+    s->current_non_ecm_octet = 0xFF;
+    s->non_ecm_flow_control_fill_octet = 0xFF;
+    s->non_ecm_at_initial_all_ones = TRUE;
+    s->bit_stream = 0;
+    if (s->non_ecm_flow_control_fill_octets)
+    {
+        span_log(&s->logging, SPAN_LOG_WARNING, "Flow control generated %d octets\n", s->non_ecm_flow_control_fill_octets);
+        s->non_ecm_flow_control_fill_octets = 0;
+    }
+    s->in_progress_rx_indicator = indicator;
+    return TRUE;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void pump_out_final_hdlc(t38_gateway_state_t *s, int good_fcs)
+{
+    if (!good_fcs)
+        s->hdlc_flags[s->hdlc_in] |= HDLC_FLAG_CORRUPT_CRC;
+    if (s->hdlc_in == s->hdlc_out)
+    {
+        /* This is the frame in progress at the output. */
+        if ((s->hdlc_flags[s->hdlc_out] & HDLC_FLAG_PROCEED_WITH_OUTPUT) == 0)
+        {
+            /* Output of this frame has not yet begun. Throw it all out now. */
+            hdlc_tx_frame(&s->hdlctx, s->hdlc_buf[s->hdlc_out], s->hdlc_len[s->hdlc_out]);
+        }
+        if ((s->hdlc_flags[s->hdlc_out] & HDLC_FLAG_CORRUPT_CRC))
+            hdlc_tx_corrupt_frame(&s->hdlctx);
+    }
+    s->hdlc_flags[s->hdlc_in] |= (HDLC_FLAG_PROCEED_WITH_OUTPUT | HDLC_FLAG_FINISHED);
+    if (++s->hdlc_in >= T38_TX_HDLC_BUFS)
+        s->hdlc_in = 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void constrain_fast_modem(t38_gateway_state_t *s, uint8_t *buf, int len)
+{
+    /* We may need to adjust the capabilities, so they do not exceed our own */
+    if (len < 5)
+        return;
+    /* TODO: fiddle the contents */
+    switch (buf[4] & (DISBIT6 | DISBIT5 | DISBIT4 | DISBIT3))
+    {
+    case 0:
+    case DISBIT4:
+        /* V.27ter only */
+        break;
+    case DISBIT3:
+    case (DISBIT4 | DISBIT3):
+        /* V.27ter and V.29 */
+        break;
+    case (DISBIT6 | DISBIT4 | DISBIT3):
+        /* V.27ter, V.29 and V.17 */
+#if !defined(ENABLE_V17)
+        buf[4] &= ~DISBIT6;
+#endif
+        break;
+    case (DISBIT5 | DISBIT4):
+    case (DISBIT6 | DISBIT4):
+    case (DISBIT6 | DISBIT5 | DISBIT4):
+    case (DISBIT6 | DISBIT5 | DISBIT4 | DISBIT3):
+        /* Reserved */
+        buf[4] &= ~(DISBIT6 | DISBIT5);
+        buf[4] |= (DISBIT4 | DISBIT3);
+        break;
+    default:
+        /* Not used */
+        buf[4] &= ~(DISBIT6 | DISBIT5);
+        buf[4] |= (DISBIT4 | DISBIT3);
+        break;
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+static void monitor_control_messages(t38_gateway_state_t *s, uint8_t *buf, int len, int from_modem)
+{
+    span_log(&s->logging, SPAN_LOG_FLOW, "monitor 0x%x\n", buf[2]);
+    if (len < 3)
+        return;
+    /* Monitor the control messages, so we can see what is happening to things like
+       training success/failure. */
+    switch (buf[2])
+    {
+    case T30_CFR:
+        /* We are changing from TCF exchange to image exchange */
+        /* Successful training means we should change to short training */
+        s->short_train = TRUE;
+        span_log(&s->logging, SPAN_LOG_FLOW, "CFR - short = %d, ECM = %d\n", s->short_train, s->ecm_mode);
+        if (!from_modem)
+            restart_rx_modem(s);
+        break;
+    case T30_RTP:
+        /* We are going back to the exchange of fresh TCF */
+        s->short_train = FALSE;
+        break;
+    case T30_DTC:
+    case T30_DCS:
+    case T30_DCS + 1:
+        /* We need to check which modem type is about to be used. */
+        switch (buf[4] & (DISBIT6 | DISBIT5 | DISBIT4 | DISBIT3))
+        {
+        case 0:
+            s->fast_bit_rate = 2400;
+            s->fast_modem = T38_V27TER_RX;
+            s->short_train = FALSE;
+            break;
+        case DISBIT4:
+            s->fast_bit_rate = 4800;
+            s->fast_modem = T38_V27TER_RX;
+            s->short_train = FALSE;
+            break;
+        case DISBIT3:
+            s->fast_bit_rate = 9600;
+            s->fast_modem = T38_V29_RX;
+            s->short_train = FALSE;
+            break;
+        case (DISBIT4 | DISBIT3):
+            s->fast_bit_rate = 7200;
+            s->fast_modem = T38_V29_RX;
+            s->short_train = FALSE;
+            break;
+#if defined(ENABLE_V17)
+        case DISBIT6:
+            s->fast_bit_rate = 14400;
+            s->fast_modem = T38_V17_RX;
+            s->short_train = FALSE;
+            break;
+        case (DISBIT6 | DISBIT4):
+            s->fast_bit_rate = 12000;
+            s->fast_modem = T38_V17_RX;
+            s->short_train = FALSE;
+            break;
+        case (DISBIT6 | DISBIT3):
+            s->fast_bit_rate = 9600;
+            s->fast_modem = T38_V17_RX;
+            s->short_train = FALSE;
+            break;
+        case (DISBIT6 | DISBIT4 | DISBIT3):
+            s->fast_bit_rate = 7200;
+            s->fast_modem = T38_V17_RX;
+            s->short_train = FALSE;
+            break;
+#endif
+        case (DISBIT5 | DISBIT3):
+        case (DISBIT5 | DISBIT4 | DISBIT3):
+        case (DISBIT6 | DISBIT5):
+        case (DISBIT6 | DISBIT5 | DISBIT3):
+        case (DISBIT6 | DISBIT5 | DISBIT4):
+        case (DISBIT6 | DISBIT5 | DISBIT4 | DISBIT3):
+            /* Reserved */
+            s->fast_bit_rate = 0;
+            s->fast_modem = T38_NONE;
+            break;
+        default:
+            /* Not used */
+            s->fast_bit_rate = 0;
+            s->fast_modem = T38_NONE;
+            break;
+        }
+        s->ecm_mode = (buf[6] & DISBIT3);
+        break;
+    default:
+        break;
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+static void queue_missing_indicator(t38_gateway_state_t *s, int data_type)
+{
+    t38_core_state_t *t;
+    
+    t = &s->t38;
+    /* Missing packets might have lost us the indicator that should have put us in
+       the required mode of operation. It might be a bit late to fill in such a gap
+       now, but we should try. We may also want to force indicators into the queue,
+       such as when the data says 'end of signal'. */
+    switch (data_type)
+    {
+    case -1:
+        if (s->t38.current_rx_indicator != T38_IND_NO_SIGNAL)
+            process_rx_indicator(t, (void *) s, T38_IND_NO_SIGNAL);
+        break;
+    case T38_DATA_V21:
+        if (s->t38.current_rx_indicator != T38_IND_V21_PREAMBLE)
+            process_rx_indicator(t, (void *) s, T38_IND_V21_PREAMBLE);
+        break;
+    case T38_DATA_V27TER_2400:
+        if (s->t38.current_rx_indicator != T38_IND_V27TER_2400_TRAINING)
+            process_rx_indicator(t, (void *) s, T38_IND_V27TER_2400_TRAINING);
+        break;
+    case T38_DATA_V27TER_4800:
+        if (s->t38.current_rx_indicator != T38_IND_V27TER_4800_TRAINING)
+            process_rx_indicator(t, (void *) s, T38_IND_V27TER_4800_TRAINING);
+        break;
+    case T38_DATA_V29_7200:
+        if (s->t38.current_rx_indicator != T38_IND_V29_7200_TRAINING)
+            process_rx_indicator(t, (void *) s, T38_IND_V29_7200_TRAINING);
+        break;
+    case T38_DATA_V29_9600:
+        if (s->t38.current_rx_indicator != T38_IND_V29_9600_TRAINING)
+            process_rx_indicator(t, (void *) s, T38_IND_V29_9600_TRAINING);
+        break;
+    case T38_DATA_V17_7200:
+        if (s->t38.current_rx_indicator != T38_IND_V17_7200_SHORT_TRAINING  &&  s->t38.current_rx_indicator != T38_IND_V17_7200_LONG_TRAINING)
+            process_rx_indicator(t, (void *) s, T38_IND_V17_7200_LONG_TRAINING);
+        break;
+    case T38_DATA_V17_9600:
+        if (s->t38.current_rx_indicator != T38_IND_V17_9600_SHORT_TRAINING  &&  s->t38.current_rx_indicator != T38_IND_V17_9600_LONG_TRAINING)
+            process_rx_indicator(t, (void *) s, T38_IND_V17_9600_LONG_TRAINING);
+        break;
+    case T38_DATA_V17_12000:
+        if (s->t38.current_rx_indicator != T38_IND_V17_12000_SHORT_TRAINING  &&  s->t38.current_rx_indicator != T38_IND_V17_12000_LONG_TRAINING)
+            process_rx_indicator(t, (void *) s, T38_IND_V17_12000_LONG_TRAINING);
+        break;
+    case T38_DATA_V17_14400:
+        if (s->t38.current_rx_indicator != T38_IND_V17_14400_SHORT_TRAINING  &&  s->t38.current_rx_indicator != T38_IND_V17_14400_LONG_TRAINING)
+            process_rx_indicator(t, (void *) s, T38_IND_V17_14400_LONG_TRAINING);
+        break;
+    case T38_DATA_V8:
+        break;
+    case T38_DATA_V34_PRI_RATE:
+        break;
+    case T38_DATA_V34_CC_1200:
+        break;
+    case T38_DATA_V34_PRI_CH:
+        break;
+    case T38_DATA_V33_12000:
+        break;
+    case T38_DATA_V33_14400:
+        break;
+    }
+}
+/*- 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_gateway_state_t *s;
+    
+    s = (t38_gateway_state_t *) user_data;
+    s->hdlc_flags[s->hdlc_in] |= HDLC_FLAG_MISSING_DATA;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int process_rx_indicator(t38_core_state_t *t, void *user_data, int indicator)
+{
+    t38_gateway_state_t *s;
+    
+    s = (t38_gateway_state_t *) user_data;
+
+    if (s->t38.current_rx_indicator != indicator)
+    {
+        if (s->hdlc_contents[s->hdlc_in])
+        {
+            if (++s->hdlc_in >= T38_TX_HDLC_BUFS)
+                s->hdlc_in = 0;
+        }
+        s->hdlc_contents[s->hdlc_in] = (indicator | 0x100);
+        if (++s->hdlc_in >= T38_TX_HDLC_BUFS)
+            s->hdlc_in = 0;
+        span_log(&s->logging,
+                 SPAN_LOG_FLOW,
+                 "Queued change - (%d) %s -> %s\n",
+                 silence_gen_remainder(&(s->silence_gen)),
+                 t38_indicator(s->t38.current_rx_indicator),
+                 t38_indicator(indicator));
+        s->current_rx_field_class = T38_FIELD_CLASS_NONE;
+    }
+    return 0;
+}
+/*- 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)
+{
+    int i;
+    int previous;
+    t38_gateway_state_t *s;
+    
+    s = (t38_gateway_state_t *) user_data;
+    switch (field_type)
+    {
+    case T38_FIELD_HDLC_DATA:
+        s->current_rx_field_class = T38_FIELD_CLASS_HDLC;
+        if (s->hdlc_contents[s->hdlc_in] != (data_type | 0x200))
+            queue_missing_indicator(s, data_type);
+        previous = s->hdlc_len[s->hdlc_in];
+        /* Check if this data would overflow the buffer. */
+        if (s->hdlc_len[s->hdlc_in] + len > T38_MAX_HDLC_LEN)
+            break;
+        s->hdlc_contents[s->hdlc_in] = (data_type | 0x200);
+        if (data_type == T38_DATA_V21)
+        {
+            for (i = 0;  i < len;  i++)
+            {
+                s->hdlc_buf[s->hdlc_in][s->hdlc_len[s->hdlc_in]++] = bit_reverse8(buf[i]);
+                /* Edit the message, if we need to control the communication between the end points. */
+                switch (s->hdlc_len[s->hdlc_in])
+                {
+                case 4:
+                    /* Check if we need to corrupt this message */
+                    if (s->hdlc_buf[s->hdlc_in][2] == T30_NSF
+                        ||
+                        s->hdlc_buf[s->hdlc_in][2] == T30_NSC
+                        ||
+                        s->hdlc_buf[s->hdlc_in][2] == T30_NSS)
+                    {
+                        /* Corrupt the message, so it will be ignored */
+                        span_log(&s->logging, SPAN_LOG_FLOW, "Corrupting non-specific procedures message\n");
+                        s->hdlc_buf[s->hdlc_in][3] = 0;
+                    }
+                    break;
+                case 6:
+                    switch (s->hdlc_buf[s->hdlc_in][2])
+                    {
+                    case T30_DIS:
+                        /* We may need to adjust the capabilities, so they do not exceed our own */
+                        span_log(&s->logging, SPAN_LOG_FLOW, "Constraining the fast modem\n");
+                        constrain_fast_modem(s, s->hdlc_buf[s->hdlc_in], s->hdlc_len[s->hdlc_in]);
+                        break;
+                    default:
+                        break;
+                    }
+                    break;
+                case 7:
+                    if (!s->ecm_allowed)
+                    {
+                        switch (s->hdlc_buf[s->hdlc_in][2])
+                        {
+                        case T30_DIS:
+                            /* Do not allow ECM or T.6 coding */
+                            span_log(&s->logging, SPAN_LOG_FLOW, "Inhibiting ECM\n");
+                            s->hdlc_buf[s->hdlc_in][6] &= ~(DISBIT3 | DISBIT7);
+                            break;
+                        }
+                    }
+                    break;
+                }
+            }
+            /* We need to send out the control messages as they are arriving. They are
+               too slow to capture a whole frame, and then pass it on. */
+            /* Don't start pumping data into the actual output stream until there is
+               enough backlog to create some elasticity for jitter tolerance. */
+            if (s->hdlc_len[s->hdlc_in] >= 8)
+            {
+                if (s->hdlc_in == s->hdlc_out)
+                {
+                    if ((s->hdlc_flags[s->hdlc_in] & HDLC_FLAG_PROCEED_WITH_OUTPUT) == 0)
+                        previous = 0;
+                    hdlc_tx_frame(&s->hdlctx, s->hdlc_buf[s->hdlc_out] + previous, s->hdlc_len[s->hdlc_out] - previous);
+                }
+                s->hdlc_flags[s->hdlc_in] |= HDLC_FLAG_PROCEED_WITH_OUTPUT;
+            }
+        }
+        else
+        {
+            /* For the faster frames, take in the whole frame before sending it out. Also, there
+               is no need to monitor, or modify, the contents of the faster frames. */
+            for (i = 0;  i < len;  i++)
+                s->hdlc_buf[s->hdlc_in][s->hdlc_len[s->hdlc_in] + i] = bit_reverse8(buf[i]);
+            s->hdlc_len[s->hdlc_in] += len;
+        }
+        break;
+    case T38_FIELD_HDLC_FCS_OK:
+        s->current_rx_field_class = T38_FIELD_CLASS_HDLC;
+        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. */
+        }
+        span_log(&s->logging, SPAN_LOG_FLOW, "Type %s - CRC good\n", t30_frametype(s->hdlc_buf[s->hdlc_in][2]));
+        if (s->hdlc_contents[s->hdlc_in] != (data_type | 0x200))
+            queue_missing_indicator(s, data_type);
+        /* Don't deal with zero length frames. Some T.38 implementations send multiple T38_FIELD_HDLC_FCS_OK
+           packets, when they have sent no data for the body of the frame. */
+        if (s->hdlc_len[s->hdlc_in] > 0)
+        {
+            s->hdlc_contents[s->hdlc_in] = (data_type | 0x200);
+            if (data_type == T38_DATA_V21  &&  (s->hdlc_flags[s->hdlc_in] & HDLC_FLAG_MISSING_DATA) == 0)
+                monitor_control_messages(s, s->hdlc_buf[s->hdlc_in], s->hdlc_len[s->hdlc_in], FALSE);
+            pump_out_final_hdlc(s, (s->hdlc_flags[s->hdlc_in] & HDLC_FLAG_MISSING_DATA) == 0);
+        }
+        s->hdlc_len[s->hdlc_in] = 0;
+        s->hdlc_flags[s->hdlc_in] = 0;
+        break;
+    case T38_FIELD_HDLC_FCS_BAD:
+        s->current_rx_field_class = T38_FIELD_CLASS_HDLC;
+        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. */
+        }
+        span_log(&s->logging, SPAN_LOG_FLOW, "Type %s - CRC bad\n", t30_frametype(s->hdlc_buf[s->hdlc_in][2]));
+        if (s->hdlc_contents[s->hdlc_in] != (data_type | 0x200))
+            queue_missing_indicator(s, data_type);
+        if (s->hdlc_len[s->hdlc_in] > 0)
+        {
+            s->hdlc_contents[s->hdlc_in] = (data_type | 0x200);
+            pump_out_final_hdlc(s, FALSE);
+        }
+        s->hdlc_len[s->hdlc_in] = 0;
+        s->hdlc_flags[s->hdlc_in] = 0;
+        break;
+    case T38_FIELD_HDLC_FCS_OK_SIG_END:
+        s->current_rx_field_class = T38_FIELD_CLASS_HDLC;
+        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. */
+        }
+        span_log(&s->logging, SPAN_LOG_FLOW, "Type %s - CRC OK, sig end\n", t30_frametype(s->hdlc_buf[s->hdlc_in][2]));
+        if (s->hdlc_contents[s->hdlc_in] != (data_type | 0x200))
+            queue_missing_indicator(s, data_type);
+        /* Don't deal with zero length frames. Some T.38 implementations send multiple T38_FIELD_HDLC_FCS_OK
+           packets, when they have sent no data for the body of the frame. */
+        if (s->hdlc_len[s->hdlc_in] > 0)
+        {
+            s->hdlc_contents[s->hdlc_in] = (data_type | 0x200);
+            if (data_type == T38_DATA_V21  &&  (s->hdlc_flags[s->hdlc_in] & HDLC_FLAG_MISSING_DATA) == 0)
+                monitor_control_messages(s, s->hdlc_buf[s->hdlc_in], s->hdlc_len[s->hdlc_in], FALSE);
+            pump_out_final_hdlc(s, (s->hdlc_flags[s->hdlc_in] & HDLC_FLAG_MISSING_DATA) == 0);
+        }
+        s->hdlc_len[s->hdlc_in] = 0;
+        s->hdlc_flags[s->hdlc_in] = 0;
+        s->hdlc_contents[s->hdlc_in] = 0;
+        queue_missing_indicator(s, -1);
+        break;
+    case T38_FIELD_HDLC_FCS_BAD_SIG_END:
+        s->current_rx_field_class = T38_FIELD_CLASS_HDLC;
+        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. */
+        }
+        span_log(&s->logging, SPAN_LOG_FLOW, "Type %s - CRC bad, sig end\n", t30_frametype(s->hdlc_buf[s->hdlc_in][2]));
+        if (s->hdlc_contents[s->hdlc_in] != (data_type | 0x200))
+            queue_missing_indicator(s, data_type);
+        if (s->hdlc_len[s->hdlc_in] > 0)
+        {
+            s->hdlc_contents[s->hdlc_in] = (data_type | 0x200);
+            pump_out_final_hdlc(s, FALSE);
+        }
+        s->hdlc_len[s->hdlc_in] = 0;
+        s->hdlc_flags[s->hdlc_in] = 0;
+        s->hdlc_contents[s->hdlc_in] = 0;
+        queue_missing_indicator(s, -1);
+        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. */
+        }
+        if (s->hdlc_contents[s->hdlc_in] != (data_type | 0x200))
+            queue_missing_indicator(s, data_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. */
+        if (s->current_rx_field_class != T38_FIELD_CLASS_NON_ECM)
+        {
+            /* 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. */
+            s->hdlc_len[s->hdlc_in] = 0;
+            s->hdlc_flags[s->hdlc_in] = 0;
+            s->hdlc_contents[s->hdlc_in] = 0;
+        }
+        else
+        {
+            span_log(&s->logging, SPAN_LOG_WARNING, "T38_FIELD_HDLC_SIG_END received at the end of non-ECM data!\n");
+            /* Don't flow control the data any more. Just pump out the remainder as fast as we can. */
+            s->non_ecm_tx_latest_eol_ptr = s->non_ecm_tx_in_ptr;
+            s->non_ecm_data_finished = TRUE;
+        }
+        queue_missing_indicator(s, -1);
+        s->current_rx_field_class = T38_FIELD_CLASS_NONE;
+        break;
+    case T38_FIELD_T4_NON_ECM_DATA:
+        s->current_rx_field_class = T38_FIELD_CLASS_NON_ECM;
+        if (s->hdlc_contents[s->hdlc_in] != (data_type | 0x200))
+            queue_missing_indicator(s, data_type);
+        add_to_non_ecm_tx_buffer(s, buf, len);
+        break;
+    case T38_FIELD_T4_NON_ECM_SIG_END:
+        if (len > 0)
+        {
+            if (s->hdlc_contents[s->hdlc_in] != (data_type | 0x200))
+                queue_missing_indicator(s, data_type);
+            add_to_non_ecm_tx_buffer(s, buf, len);
+        }
+        if (s->hdlc_contents[s->hdlc_in] != (data_type | 0x200))
+            queue_missing_indicator(s, data_type);
+        /* 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. */
+        if (s->current_rx_field_class != T38_FIELD_CLASS_HDLC)
+        {
+            /* Don't flow control the data any more. Just pump out the remainder as fast as we can. */
+            s->non_ecm_tx_latest_eol_ptr = s->non_ecm_tx_in_ptr;
+            s->non_ecm_data_finished = TRUE;
+        }
+        else
+        {
+            span_log(&s->logging, SPAN_LOG_WARNING, "T38_FIELD_NON_ECM_SIG_END received at the end of HDLC data!\n");
+            s->hdlc_len[s->hdlc_in] = 0;
+            s->hdlc_flags[s->hdlc_in] = 0;
+            s->hdlc_contents[s->hdlc_in] = 0;
+        }
+        queue_missing_indicator(s, -1);
+        s->current_rx_field_class = T38_FIELD_CLASS_NONE;
+        break;
+    case T38_FIELD_CM_MESSAGE:
+    case T38_FIELD_JM_MESSAGE:
+    case T38_FIELD_CI_MESSAGE:
+    case T38_FIELD_V34RATE:
+    default:
+        break;
+    }
+    s->current_rx_field_type = field_type;
+    s->current_rx_data_type = data_type;
+
+#if 0
+    if (span_log_test(&s->logging, SPAN_LOG_FLOW))
+    {
+        int i;
+
+        if (len > 0)
+        {
+            span_log(&s->logging, SPAN_LOG_FLOW, "Data: ");
+            for (i = 0;  i < len;  i++)
+                span_log(&s->logging, SPAN_LOG_FLOW | SPAN_LOG_SUPPRESS_LABELLING, " %02X", buf[i]);
+        }
+    }
+    span_log(&s->logging, SPAN_LOG_FLOW | SPAN_LOG_SUPPRESS_LABELLING, "\n");
+#endif
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void announce_training(t38_gateway_state_t *s)
+{
+    int ind;
+
+    ind = T38_IND_NO_SIGNAL;
+    switch (s->fast_rx_active)
+    {
+#if defined(ENABLE_V17)
+    case T38_V17_RX:
+        switch (s->fast_bit_rate)
+        {
+        case 7200:
+            ind = (s->short_train)  ?  T38_IND_V17_7200_SHORT_TRAINING  :  T38_IND_V17_7200_LONG_TRAINING;
+            s->current_tx_data_type = T38_DATA_V17_7200;
+            s->octets_per_data_packet = MS_PER_TX_CHUNK*7200/(8*1000);
+            break;
+        case 9600:
+            ind = (s->short_train)  ?  T38_IND_V17_9600_SHORT_TRAINING  :  T38_IND_V17_9600_LONG_TRAINING;
+            s->current_tx_data_type = T38_DATA_V17_9600;
+            s->octets_per_data_packet = MS_PER_TX_CHUNK*9600/(8*1000);
+            break;
+        case 12000:
+            ind = (s->short_train)  ?  T38_IND_V17_12000_SHORT_TRAINING  :  T38_IND_V17_12000_LONG_TRAINING;
+            s->current_tx_data_type = T38_DATA_V17_12000;
+            s->octets_per_data_packet = MS_PER_TX_CHUNK*12000/(8*1000);
+            break;
+        default:
+        case 14400:
+            ind = (s->short_train)  ?  T38_IND_V17_14400_SHORT_TRAINING  :  T38_IND_V17_14400_LONG_TRAINING;
+            s->current_tx_data_type = T38_DATA_V17_14400;
+            s->octets_per_data_packet = MS_PER_TX_CHUNK*14400/(8*1000);
+            break;
+        }
+        break;
+#endif
+    case T38_V27TER_RX:
+        switch (s->fast_bit_rate)
+        {
+        case 2400:
+            ind = T38_IND_V27TER_2400_TRAINING;
+            s->current_tx_data_type = T38_DATA_V27TER_2400;
+            s->octets_per_data_packet = MS_PER_TX_CHUNK*2400/(8*1000);
+            break;
+        default:
+        case 4800:
+            ind = T38_IND_V27TER_4800_TRAINING;
+            s->current_tx_data_type = T38_DATA_V27TER_4800;
+            s->octets_per_data_packet = MS_PER_TX_CHUNK*4800/(8*1000);
+            break;
+        }
+        break;
+    case T38_V29_RX:
+        switch (s->fast_bit_rate)
+        {
+        case 7200:
+            ind = T38_IND_V29_7200_TRAINING;
+            s->current_tx_data_type = T38_DATA_V29_7200;
+            s->octets_per_data_packet = MS_PER_TX_CHUNK*7200/(8*1000);
+            break;
+        default:
+        case 9600:
+            ind = T38_IND_V29_9600_TRAINING;
+            s->current_tx_data_type = T38_DATA_V29_9600;
+            s->octets_per_data_packet = MS_PER_TX_CHUNK*9600/(8*1000);
+            break;
+        }
+        break;
+    }
+    t38_core_send_indicator(&s->t38, ind, INDICATOR_TX_COUNT);
+}
+/*- End of function --------------------------------------------------------*/
+
+static void non_ecm_put_bit(void *user_data, int bit)
+{
+    t38_gateway_state_t *s;
+
+    s = (t38_gateway_state_t *) user_data;
+    if (bit < 0)
+    {
+        /* Special conditions */
+        switch (bit)
+        {
+        case PUTBIT_TRAINING_FAILED:
+            span_log(&s->logging, SPAN_LOG_FLOW, "Non-ECM carrier training failed\n");
+            break;
+        case PUTBIT_TRAINING_SUCCEEDED:
+            /* The modem is now trained */
+            span_log(&s->logging, SPAN_LOG_FLOW, "Non-ECM carrier trained\n");
+            s->rx_signal_present = TRUE;
+            s->v21_rx_active = FALSE;
+            announce_training(s);
+            s->samples_since_last_tx_packet = 0;
+            s->rx_data_ptr = 0;
+            break;
+        case PUTBIT_CARRIER_UP:
+            span_log(&s->logging, SPAN_LOG_FLOW, "Non-ECM carrier up\n");
+            break;
+        case PUTBIT_CARRIER_DOWN:
+            span_log(&s->logging, SPAN_LOG_FLOW, "Non-ECM carrier down\n");
+            switch (s->current_tx_data_type)
+            {
+            case T38_DATA_V17_7200:
+            case T38_DATA_V17_9600:
+            case T38_DATA_V17_12000:
+            case T38_DATA_V17_14400:
+            case T38_DATA_V27TER_2400:
+            case T38_DATA_V27TER_4800:
+            case T38_DATA_V29_7200:
+            case T38_DATA_V29_9600:
+                t38_core_send_data(&s->t38, s->current_tx_data_type, T38_FIELD_T4_NON_ECM_SIG_END, NULL, 0);
+                t38_core_send_indicator(&s->t38, T38_IND_NO_SIGNAL, INDICATOR_TX_COUNT);
+                s->rx_signal_present = FALSE;
+                restart_rx_modem(s);
+                break;
+            }
+            break;
+        default:
+            span_log(&s->logging, SPAN_LOG_WARNING, "Unexpected non-ECM special bit - %d!\n", bit);
+            break;
+        }
+        return;
+    }
+    s->current_non_ecm_octet = (s->current_non_ecm_octet << 1) | (bit & 1);
+    if (++s->non_ecm_bit_no >= 8)
+    {
+        s->rx_data[s->rx_data_ptr++] = (uint8_t) s->current_non_ecm_octet;
+        if (++s->octets_since_last_tx_packet >= s->octets_per_data_packet)
+        {
+            t38_core_send_data(&s->t38, s->current_tx_data_type, T38_FIELD_T4_NON_ECM_DATA, s->rx_data, s->rx_data_ptr);
+            /* Since we delay transmission by 2 octets, we should now have sent the last of the data octets when
+                we have just received the last of the CRC octets. */
+            s->rx_data_ptr = 0;
+            s->samples_since_last_tx_packet = 0;
+            s->octets_since_last_tx_packet = 0;
+        }
+        s->non_ecm_bit_no = 0;
+        s->current_non_ecm_octet = 0;
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+static int non_ecm_get_bit(void *user_data)
+{
+    t38_gateway_state_t *s;
+    int bit;
+
+    /* A rate adapting data stuffer for non-ECM image data */
+    s = (t38_gateway_state_t *) user_data;
+    if (s->non_ecm_bit_no <= 0)
+    {
+        /* We need another byte */
+        if (s->non_ecm_tx_out_ptr != s->non_ecm_tx_latest_eol_ptr)
+        {
+            s->current_non_ecm_octet = s->non_ecm_tx_data[s->non_ecm_tx_out_ptr];
+            s->non_ecm_tx_out_ptr = (s->non_ecm_tx_out_ptr + 1) & (T38_TX_BUF_LEN - 1);
+        }
+        else
+        {
+            if (s->non_ecm_data_finished)
+            {
+                /* The queue is empty, and we have received the end of data signal. This must
+                   really be the end to transmission. */
+                s->non_ecm_data_finished = FALSE;
+                /* Reset the data pointers for next time. */
+                s->non_ecm_tx_out_ptr = 0;
+                s->non_ecm_tx_in_ptr = 0;
+                s->non_ecm_tx_latest_eol_ptr = 0;
+                return PUTBIT_END_OF_DATA;
+            }
+            /* The queue is empty, but this does not appear to be the end of the data. Idle with
+               fill octets, which should be safe at this point. */
+            s->current_non_ecm_octet = s->non_ecm_flow_control_fill_octet;
+            s->non_ecm_flow_control_fill_octets++;
+        }
+        s->non_ecm_bit_no = 8;
+    }
+    s->non_ecm_bit_no--;
+    bit = (s->current_non_ecm_octet >> 7) & 1;
+    s->current_non_ecm_octet <<= 1;
+    return bit;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void add_to_non_ecm_tx_buffer(t38_gateway_state_t *s, const uint8_t *buf, int len)
+{
+    int i;
+    int upper;
+    int lower;
+
+    /* A rate adapting data stuffer for non-ECM image data */
+    i = 0;
+    if (s->non_ecm_at_initial_all_ones)
+    {
+        /* Dump initial 0xFF bytes. We will add enough of our own to makes things flow
+           smoothly. If we don't strip these off we might end up delaying the start of
+           forwarding by a large amount, as we could end up with a large block of 0xFF
+           bytes before the real data begins. This is especially true with PC FAX
+           systems. */
+        for (  ;  i < len;  i++)
+        {
+            if (buf[i] != 0xFF)
+            {
+                s->non_ecm_at_initial_all_ones = FALSE;
+                break;
+            }
+        }
+    }
+    if (s->short_train)
+    {
+        for (  ;  i < len;  i++)
+        {
+            /* Check for EOLs, because at an EOL we can pause and pump out zeros while
+               waiting for more incoming data. */
+            if (buf[i])
+            {
+                /* There might be an EOL here. Look for at least 11 zeros, followed by a one. */
+                upper = bottom_bit((s->bit_stream | 0x800) & 0xFFF);
+                lower = top_bit(buf[i] & 0xFF);
+                if (lower > 0  &&  upper - lower >= 3)
+                {
+                    s->non_ecm_tx_latest_eol_ptr = s->non_ecm_tx_in_ptr;
+                    s->non_ecm_flow_control_fill_octet = 0x00;
+                }
+            }
+            s->bit_stream = (s->bit_stream << 8) | buf[i];
+            s->non_ecm_tx_data[s->non_ecm_tx_in_ptr] = buf[i];
+            /* TODO: We can't buffer overflow, since we wrap around. However, the tail could overwrite
+                     itself if things fall badly behind. */
+            s->non_ecm_tx_in_ptr = (s->non_ecm_tx_in_ptr + 1) & (T38_TX_BUF_LEN - 1);
+        }
+    }
+    else
+    {
+        for (  ;  i < len;  i++)
+        {
+            /* Check for zero bytes, as we can pause and pump out zeros while waiting
+               for more incoming data. */
+            if (buf[i] == 0)
+            {
+                s->non_ecm_tx_latest_eol_ptr = s->non_ecm_tx_in_ptr;
+                s->non_ecm_flow_control_fill_octet = 0x00;
+            }
+            s->non_ecm_tx_data[s->non_ecm_tx_in_ptr] = buf[i];
+            /* TODO: We can't buffer overflow, since we wrap around. However, the tail could overwrite
+                     itself if things fall badly behind. */
+            s->non_ecm_tx_in_ptr = (s->non_ecm_tx_in_ptr + 1) & (T38_TX_BUF_LEN - 1);
+        }
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+static void t38_hdlc_rx_put_bit(hdlc_rx_state_t *t, int new_bit)
+{
+    t38_gateway_state_t *s;
+    int final;
+
+    s = (t38_gateway_state_t *) t->user_data;
+    if (new_bit < 0)
+    {
+        /* Special conditions */
+        switch (new_bit)
+        {
+        case PUTBIT_TRAINING_FAILED:
+            span_log(&s->logging, SPAN_LOG_FLOW, "HDLC carrier training failed\n");
+            break;
+        case PUTBIT_TRAINING_SUCCEEDED:
+            /* The modem is now trained */
+            span_log(&s->logging, SPAN_LOG_FLOW, "HDLC carrier trained\n");
+            s->rx_signal_present = TRUE;
+            s->v21_rx_active = FALSE;
+            announce_training(s);
+            /* Behave like HDLC preamble has been announced */
+            t->framing_ok_announced = TRUE;
+            s->samples_since_last_tx_packet = 0;
+            s->rx_data_ptr = 0;
+            break;
+        case PUTBIT_CARRIER_UP:
+            span_log(&s->logging, SPAN_LOG_FLOW, "HDLC carrier up\n");
+            /* Reset the HDLC receiver. */
+            t->len = 0;
+            t->num_bits = 0;
+            t->flags_seen = 0;
+            t->framing_ok_announced = FALSE;
+            s->rx_signal_present = TRUE;
+            break;
+        case PUTBIT_CARRIER_DOWN:
+            span_log(&s->logging, SPAN_LOG_FLOW, "HDLC carrier down\n");
+            if (t->framing_ok_announced)
+            {
+                t38_core_send_data(&s->t38, s->current_tx_data_type, T38_FIELD_HDLC_SIG_END, NULL, 0);
+                t38_core_send_indicator(&s->t38, T38_IND_NO_SIGNAL, INDICATOR_TX_COUNT);
+                t->framing_ok_announced = FALSE;
+            }
+            s->rx_signal_present = FALSE;
+            restart_rx_modem(s);
+            break;
+        default:
+            span_log(&s->logging, SPAN_LOG_WARNING, "Unexpected HDLC special bit - %d!\n", new_bit);
+            break;
+        }
+        return;
+    }
+    t->raw_bit_stream |= (new_bit & 1);
+    if ((t->raw_bit_stream & 0x3F) == 0x3E)
+    {
+        if ((t->raw_bit_stream & 0x40) == 0)
+        {
+            /* Stuffing */
+        }
+        else if ((t->raw_bit_stream & 0x80))
+        {
+            /* Hit HDLC abort */
+            t->rx_aborts++;
+            t->len = 0;
+            t->num_bits = 0;
+            s->corrupt_the_frame = FALSE;
+            s->octets_since_last_tx_packet = 0;
+        }
+        else
+        {
+            /* Hit HDLC flag */
+            if (t->flags_seen >= t->framing_ok_threshold)
+            {
+                if (t->len)
+                {
+                    if (t->len >= 2)
+                    {
+                        if ((s->crc & 0xFFFF) == 0xF0B8)
+                        {
+                            t->rx_frames++;
+                            t->rx_bytes += t->len - 2;
+                            final = (t->len - 2 >= 2  &&  t->buffer[0] == 0xFF  &&  t->buffer[1] == 0x13);
+                            span_log(&s->logging, SPAN_LOG_FLOW, "E Type %s\n", t30_frametype(t->buffer[2]));
+                            if (s->current_tx_data_type == T38_DATA_V21)
+                                monitor_control_messages(s, t->buffer, t->len, TRUE);
+                            if (s->rx_data_ptr)
+                            {
+                                t38_core_send_data(&s->t38, s->current_tx_data_type, T38_FIELD_HDLC_DATA, s->rx_data, s->rx_data_ptr);
+                                s->rx_data_ptr = 0;
+                            }
+                            /* It seems some boxes may not like us sending a _SIG_END here, and then another
+                               when the carrier actually drops. Lets just send T38_FIELD_HDLC_FCS_OK here. */
+                            t38_core_send_data(&s->t38, s->current_tx_data_type, T38_FIELD_HDLC_FCS_OK, NULL, 0);
+                        }
+                        else
+                        {
+                            t->rx_crc_errors++;
+                            final = (t->len - 2 >= 2  &&  t->buffer[0] == 0xFF  &&  t->buffer[1] == 0x13);
+                            span_log(&s->logging, SPAN_LOG_FLOW, "F Type %s\n", t30_frametype(t->buffer[2]));
+                            if (s->rx_data_ptr)
+                            {
+                                t38_core_send_data(&s->t38, s->current_tx_data_type, T38_FIELD_HDLC_DATA, s->rx_data, s->rx_data_ptr);
+                                s->rx_data_ptr = 0;
+                            }
+                            /* It seems some boxes may not like us sending a _SIG_END here, and then another
+                               when the carrier actually drops. Lets just send T38_FIELD_HDLC_FCS_OK here. */
+                            t38_core_send_data(&s->t38, s->current_tx_data_type, T38_FIELD_HDLC_FCS_BAD, NULL, 0);
+                        }
+                    }
+                    else
+                    {
+                        /* Frame too short */
+                        t->rx_length_errors++;
+                    }
+                }
+            }
+            else
+            {
+                if (++t->flags_seen >= t->framing_ok_threshold  &&  !t->framing_ok_announced)
+                {
+                    if (s->current_tx_data_type == T38_DATA_V21)
+                        t38_core_send_indicator(&s->t38, T38_IND_V21_PREAMBLE, INDICATOR_TX_COUNT);
+                    if (s->in_progress_rx_indicator == T38_IND_CNG)
+                        set_next_tx_type(s);
+                    t->framing_ok_announced = TRUE;
+                }
+            }
+            s->crc = 0xFFFF;
+            t->len = 0;
+            t->num_bits = 0;
+            s->corrupt_the_frame = FALSE;
+            s->octets_since_last_tx_packet = 0;
+        }
+    }
+    else
+    {
+        if (t->framing_ok_announced)
+        {
+            t->byte_in_progress = (t->byte_in_progress >> 1) | ((t->raw_bit_stream & 0x01) << 7);
+            if (++t->num_bits == 8)
+            {
+                if (t->len >= (int) sizeof(t->buffer))
+                {
+                    /* Frame too long */
+                    t->rx_length_errors++;
+                    t->flags_seen = t->framing_ok_threshold - 1;
+                    t->len = 0;
+                }
+                else
+                {
+                    t->buffer[t->len] = (uint8_t) t->byte_in_progress;
+                    /* Calculate the CRC progressively, before we start altering the frame */
+                    s->crc = crc_itu16_calc(&t->buffer[t->len], 1, s->crc);
+                    if (t->len >= 2)
+                    {
+                        /* Make the transmission lag by two bytes, so we do not send the CRC, and
+                           do not report the CRC result too late. */
+                        if (s->current_tx_data_type == T38_DATA_V21)
+                        {
+                            /* Edit the message, if we need to control the communication between the
+                               end points. */
+                            switch (t->len)
+                            {
+                            case 4:
+                                switch (t->buffer[2])
+                                {
+                                case T30_NSF:
+                                case T30_NSC:
+                                case T30_NSS:
+                                    /* We need to corrupt the rest of this message */
+                                    span_log(&s->logging, SPAN_LOG_FLOW, "Corrupting non-specific procedures message\n");
+                                    s->corrupt_the_frame = TRUE;
+                                    break;
+                                default:
+                                    break;
+                                }
+                                break;
+                            case 6:
+                                switch (t->buffer[2])
+                                {
+                                case T30_DIS:
+                                    /* We may need to adjust the capabilities, so they do not exceed our own */
+                                    span_log(&s->logging, SPAN_LOG_FLOW, "Constraining the fast modem\n");
+                                    constrain_fast_modem(s, t->buffer, t->len);
+                                    break;
+                                default:
+                                    break;
+                                }
+                                break;
+                            case 7:
+                                if (!s->ecm_allowed)
+                                {
+                                    switch (t->buffer[2])
+                                    {
+                                    case T30_DIS:
+                                        /* Do not allow ECM or T.6 coding */
+                                        span_log(&s->logging, SPAN_LOG_FLOW, "Inhibiting ECM\n");
+                                        t->buffer[6] &= ~(DISBIT3 | DISBIT7);
+                                        break;
+                                }
+                                }
+                                break;
+                            }
+                        }
+                        s->rx_data[s->rx_data_ptr++] = (s->corrupt_the_frame)  ?  0  :  bit_reverse8(t->buffer[t->len - 2]);
+                        if (s->rx_data_ptr >= s->octets_per_data_packet)
+                        {
+                            t38_core_send_data(&s->t38, s->current_tx_data_type, T38_FIELD_HDLC_DATA, s->rx_data, s->rx_data_ptr);
+                            /* Since we delay transmission by 2 octets, we should now have sent the last of the data octets when
+                               we have just received the last of the CRC octets. */
+                            s->rx_data_ptr = 0;
+                            s->samples_since_last_tx_packet = 0;
+                        }
+                    }
+                    t->len++;
+                }
+                t->num_bits = 0;
+            }
+        }
+    }
+    t->raw_bit_stream <<= 1;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int restart_rx_modem(t38_gateway_state_t *s)
+{
+    put_bit_func_t put_bit_func;
+    void *put_bit_user_data;
+
+    span_log(&s->logging, SPAN_LOG_FLOW, "Restart modem - short = %d, ECM = %d\n", s->short_train,  s->ecm_mode);
+
+    hdlc_rx_init(&(s->hdlcrx), FALSE, TRUE, 5, NULL, s);
+    s->crc = 0xFFFF;
+    s->rx_signal_present = FALSE;
+    /* Default to the transmit data being V.21, unless a faster modem pops up trained. */
+    s->current_tx_data_type = T38_DATA_V21;
+    fsk_rx_init(&(s->v21rx), &preset_fsk_specs[FSK_V21CH2], TRUE, (put_bit_func_t) t38_hdlc_rx_put_bit, &(s->hdlcrx));
+    s->v21_rx_active = TRUE;
+    if (s->short_train  &&  s->ecm_mode)
+    {
+        put_bit_func = (put_bit_func_t) t38_hdlc_rx_put_bit;
+        put_bit_user_data = (void *) &(s->hdlcrx);
+    }
+    else
+    {
+        put_bit_func = non_ecm_put_bit;
+        put_bit_user_data = (void *) s;
+    }
+    s->rx_data_ptr = 0;
+    s->octets_per_data_packet = 1;
+    switch (s->fast_modem)
+    {
+#if defined(ENABLE_V17)
+    case T38_V17_RX:
+        v17_rx_restart(&(s->v17rx), s->fast_bit_rate, s->short_train);
+        v17_rx_set_put_bit(&(s->v17rx), put_bit_func, put_bit_user_data);
+        s->fast_rx_active = T38_V17_RX;
+        break;
+#endif
+    case T38_V27TER_RX:
+        v27ter_rx_restart(&(s->v27ter_rx), s->fast_bit_rate, FALSE);
+        v27ter_rx_set_put_bit(&(s->v27ter_rx), put_bit_func, put_bit_user_data);
+        s->fast_rx_active = T38_V27TER_RX;
+        break;
+    case T38_V29_RX:
+        v29_rx_restart(&(s->v29rx), s->fast_bit_rate, FALSE);
+        v29_rx_set_put_bit(&(s->v29rx), put_bit_func, put_bit_user_data);
+        s->fast_rx_active = T38_V29_RX;
+        break;
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+int t38_gateway_rx(t38_gateway_state_t *s, const int16_t *amp, int len)
+{
+    s->samples_since_last_tx_packet += len;
+    if (s->v21_rx_active)
+        fsk_rx(&(s->v21rx), amp, len);
+    switch (s->fast_rx_active)
+    {
+#if defined(ENABLE_V17)
+    case T38_V17_RX:
+        v17_rx(&(s->v17rx), amp, len);
+        break;
+#endif
+    case T38_V27TER_RX:
+        v27ter_rx(&(s->v27ter_rx), amp, len);
+        break;
+    case T38_V29_RX:
+        v29_rx(&(s->v29rx), amp, len);
+        break;
+    }
+    return  0;
+}
+/*- End of function --------------------------------------------------------*/
+
+int t38_gateway_tx(t38_gateway_state_t *s, int16_t *amp, int max_len)
+{
+    int len;
+
+    if ((len = s->tx_handler(s->tx_user_data, amp, max_len)) < max_len)
+    {
+        if (set_next_tx_type(s))
+        {
+            /* Give the new handler a chance to file the remaining buffer space */
+            len += s->tx_handler(s->tx_user_data, amp + len, max_len - len);
+            if (len < max_len)
+            {
+                silence_gen_set(&(s->silence_gen), 0);
+                set_next_tx_type(s);
+            }
+        }
+    }
+    return len;
+}
+/*- End of function --------------------------------------------------------*/
+
+void t38_gateway_ecm_control(t38_gateway_state_t *s, int ecm_allowed)
+{
+    s->ecm_allowed = ecm_allowed;
+}
+/*- End of function --------------------------------------------------------*/
+
+t38_gateway_state_t *t38_gateway_init(t38_gateway_state_t *s,
+                                      t38_tx_packet_handler_t *tx_packet_handler,
+                                      void *tx_packet_user_data)
+{
+    if (tx_packet_handler == NULL)
+        return NULL;
+
+    memset(s, 0, sizeof(*s));
+    span_log_init(&s->logging, SPAN_LOG_NONE, NULL);
+    span_log_set_protocol(&s->logging, "T.38G");
+#if defined(ENABLE_V17)
+    v17_rx_init(&(s->v17rx), 14400, non_ecm_put_bit, s);
+    v17_tx_init(&(s->v17tx), 14400, FALSE, non_ecm_get_bit, s);
+#endif
+    v29_rx_init(&(s->v29rx), 9600, non_ecm_put_bit, s);
+    v29_tx_init(&(s->v29tx), 9600, FALSE, non_ecm_get_bit, s);
+    v27ter_rx_init(&(s->v27ter_rx), 4800, non_ecm_put_bit, s);
+    v27ter_tx_init(&(s->v27ter_tx), 4800, FALSE, non_ecm_get_bit, s);
+    s->octets_per_data_packet = 1;
+    silence_gen_init(&(s->silence_gen), 0);
+    hdlc_tx_init(&s->hdlctx, FALSE, 2, TRUE, hdlc_underflow_handler, s);
+    s->rx_signal_present = FALSE;
+    s->tx_handler = (span_tx_handler_t *) &(silence_gen);
+    s->tx_user_data = &(s->silence_gen);
+    t38_core_init(&s->t38, process_rx_indicator, process_rx_data, process_rx_missing, (void *) s);
+    s->t38.iaf = FALSE;
+    s->t38.tx_packet_handler = tx_packet_handler;
+    s->t38.tx_packet_user_data = tx_packet_user_data;
+#if defined(ENABLE_V17)
+    s->t38.fastest_image_data_rate = 14400;
+#else
+    s->t38.fastest_image_data_rate = 9600;
+#endif
+    s->ecm_allowed = FALSE;
+    restart_rx_modem(s);
+    return s;
+}
+/*- End of function --------------------------------------------------------*/
+/*- End of file ------------------------------------------------------------*/

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