/*
 * 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.123 2009/02/10 13:06:46 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.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.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:
        /* Some T.38 implementations insert these preamble indicators between HDLC frames, so
           we need to be tolerant of that. */
        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_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_CNTL_CHANNEL_1200:
    case T38_IND_V34_PRI_CHANNEL:
    case T38_IND_V34_CC_RETRAIN:
        /* V.34 support is a work in progress. */
        front_end_status(s, T30_FRONT_END_SIGNAL_PRESENT);
        break;
    default:
        front_end_status(s, T30_FRONT_END_SIGNAL_ABSENT);
        break;
    }
    fe->hdlc_rx.len = 0;
    fe->rx_data_missing = FALSE;
    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)
{
    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;
#if 0
    /* In termination mode we don't care very much what the data type is. */
    switch (data_type)
    {
    case T38_DATA_V21:
    case T38_DATA_V27TER_2400:
    case T38_DATA_V27TER_4800:
    case T38_DATA_V29_7200:
    case T38_DATA_V29_9600:
    case T38_DATA_V17_7200:
    case T38_DATA_V17_9600:
    case T38_DATA_V17_12000:
    case T38_DATA_V17_14400:
    case T38_DATA_V8:
    case T38_DATA_V34_PRI_RATE:
    case T38_DATA_V34_CC_1200:
    case T38_DATA_V34_PRI_CH:
    case T38_DATA_V33_12000:
    case T38_DATA_V33_14400:
    default:
        break;
    }
#endif
    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. */
            fe->timeout_rx_samples = fe->samples + ms_to_samples(MID_RX_TIMEOUT);
            front_end_status(s, T30_FRONT_END_SIGNAL_PRESENT);
            /* 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;
        fe->timeout_rx_samples = 0;
        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;
        fe->timeout_rx_samples = 0;
        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;
            fe->timeout_rx_samples = 0;
            front_end_status(s, T30_FRONT_END_RECEIVE_COMPLETE);
        }
        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;
        fe->timeout_rx_samples = 0;
        break;
    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);
        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);
        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);
        break;
    case T38_FIELD_V34RATE:
        if (len >= 3)
        {
            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;
    }
    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)
        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, fe->t38.indicator_tx_count);
            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->t38.indicator_tx_count);
            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, fe->t38.data_end_tx_count);
                    fe->timed_step = T38_TIMED_STEP_NON_ECM_MODEM_5;
                    delay = 0;
                }
            }
            t38_core_send_data(&fe->t38, fe->current_tx_data_type, T38_FIELD_T4_NON_ECM_DATA, buf, len, fe->t38.data_tx_count);
            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, fe->t38.data_end_tx_count);
                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, fe->t38.data_tx_count);
            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->t38.indicator_tx_count);
            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 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, fe->t38.indicator_tx_count);
            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, fe->t38.indicator_tx_count);
            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;
                        t38_core_send_data_multi_field(&fe->t38, fe->current_tx_data_type, data_fields, 2, fe->t38.data_tx_count);
                        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;
                        t38_core_send_data_multi_field(&fe->t38, fe->current_tx_data_type, data_fields, 2, fe->t38.data_tx_count);
                        fe->timed_step = T38_TIMED_STEP_HDLC_MODEM_3;
                        delay = bits_to_us(s, i*8 + fe->hdlc_tx.extra_bits);
                    }
                }
                else
                {
                    t38_core_send_data(&fe->t38, fe->current_tx_data_type, T38_FIELD_HDLC_DATA, &fe->hdlc_tx.buf[fe->hdlc_tx.ptr], i, fe->t38.data_tx_count);
                    fe->timed_step = T38_TIMED_STEP_HDLC_MODEM_4;
                    delay = bits_to_us(s, i*8);
                }
                break;
            }
            t38_core_send_data(&fe->t38, fe->current_tx_data_type, T38_FIELD_HDLC_DATA, &fe->hdlc_tx.buf[fe->hdlc_tx.ptr], fe->octets_per_data_packet, fe->t38.data_tx_count);
            fe->hdlc_tx.ptr += fe->octets_per_data_packet;
            delay = bits_to_us(s, fe->octets_per_data_packet*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 */
                t38_core_send_data(&fe->t38, previous, T38_FIELD_HDLC_FCS_OK_SIG_END, NULL, 0, fe->t38.data_end_tx_count);
                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. */
            t38_core_send_data(&fe->t38, previous, T38_FIELD_HDLC_FCS_OK, NULL, 0, fe->t38.data_tx_count);
            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->t38.indicator_tx_count);
            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, fe->t38.indicator_tx_count);
            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->t38.indicator_tx_count);
            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, fe->t38.indicator_tx_count);
            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->t38.indicator_tx_count);
            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 */
        s->t38_fe.t38.indicator_tx_count = 0;
        s->t38_fe.t38.data_tx_count = 1;
        s->t38_fe.t38.data_end_tx_count = 1;
        s->t38_fe.ms_per_tx_chunk = 0;
    }
    else
    {
        /* Paced streaming mode, as used for UDP transports */
        s->t38_fe.t38.indicator_tx_count = INDICATOR_TX_COUNT;
        s->t38_fe.t38.data_tx_count = DATA_TX_COUNT;
        s->t38_fe.t38.data_end_tx_count = 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);
    s->t38.fastest_image_data_rate = 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 ------------------------------------------------------------*/
