diff spandsp-0.0.6pre17/tests/tsb85_tests.c @ 4:26cd8f1ef0b1

import spandsp-0.0.6pre17
author Peter Meerwald <pmeerw@cosy.sbg.ac.at>
date Fri, 25 Jun 2010 15:50:58 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spandsp-0.0.6pre17/tests/tsb85_tests.c	Fri Jun 25 15:50:58 2010 +0200
@@ -0,0 +1,1308 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * tsb85_tests.c
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 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: tsb85_tests.c,v 1.32 2009/05/30 15:23:14 steveu Exp $
+ */
+
+/*! \file */
+
+#if defined(HAVE_CONFIG_H)
+#include <config.h>
+#endif
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.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 <fcntl.h>
+#include <time.h>
+#include <unistd.h>
+#include <sndfile.h>
+
+#if defined(HAVE_LIBXML_XMLMEMORY_H)
+#include <libxml/xmlmemory.h>
+#endif
+#if defined(HAVE_LIBXML_PARSER_H)
+#include <libxml/parser.h>
+#endif
+#if defined(HAVE_LIBXML_XINCLUDE_H)
+#include <libxml/xinclude.h>
+#endif
+
+//#if defined(WITH_SPANDSP_INTERNALS)
+#define SPANDSP_EXPOSE_INTERNAL_STRUCTURES
+//#endif
+
+#include "spandsp.h"
+#include "spandsp-sim.h"
+
+#include "fax_tester.h"
+#include "fax_utils.h"
+
+#define OUTPUT_TIFF_FILE_NAME   "tsb85.tif"
+
+#define OUTPUT_FILE_NAME_WAVE   "tsb85.wav"
+
+#define SAMPLES_PER_CHUNK       160
+
+SNDFILE *out_handle;
+
+int use_receiver_not_ready = FALSE;
+int test_local_interrupt = FALSE;
+
+const char *output_tiff_file_name;
+
+fax_state_t *fax;
+faxtester_state_t state;
+
+uint8_t image[1000000];
+
+uint8_t awaited[1000];
+int awaited_len = 0;
+
+char image_path[1024];
+
+t30_exchanged_info_t expected_rx_info;
+
+char next_tx_file[1000];
+
+static int next_step(faxtester_state_t *s);
+
+static int phase_b_handler(t30_state_t *s, void *user_data, int result)
+{
+    int i;
+    int status;
+    const char *u;
+    
+    i = (intptr_t) user_data;
+    status = T30_ERR_OK;
+    if ((u = t30_get_rx_ident(s)))
+    {
+        printf("%c: Phase B: remote ident '%s'\n", i, u);
+        if (expected_rx_info.ident[0]  &&  strcmp(expected_rx_info.ident, u))
+        {
+            printf("%c: Phase B: remote ident incorrect! - expected '%s'\n", i, expected_rx_info.ident);
+            status = T30_ERR_IDENT_UNACCEPTABLE;
+        }
+    }
+    else
+    {
+        if (expected_rx_info.ident[0])
+        {
+            printf("%c: Phase B: remote ident missing!\n", i);
+            status = T30_ERR_IDENT_UNACCEPTABLE;
+        }
+    }
+    if ((u = t30_get_rx_sub_address(s)))
+    {
+        printf("%c: Phase B: remote sub-address '%s'\n", i, u);
+        if (expected_rx_info.sub_address[0]  &&  strcmp(expected_rx_info.sub_address, u))
+        {
+            printf("%c: Phase B: remote sub-address incorrect! - expected '%s'\n", i, expected_rx_info.sub_address);
+            status = T30_ERR_SUB_UNACCEPTABLE;
+        }
+    }
+    else
+    {
+        if (expected_rx_info.sub_address[0])
+        {
+            printf("%c: Phase B: remote sub-address missing!\n", i);
+            status = T30_ERR_SUB_UNACCEPTABLE;
+        }
+    }
+    if ((u = t30_get_rx_polled_sub_address(s)))
+    {
+        printf("%c: Phase B: remote polled sub-address '%s'\n", i, u);
+        if (expected_rx_info.polled_sub_address[0]  &&  strcmp(expected_rx_info.polled_sub_address, u))
+        {
+            printf("%c: Phase B: remote polled sub-address incorrect! - expected '%s'\n", i, expected_rx_info.polled_sub_address);
+            status = T30_ERR_PSA_UNACCEPTABLE;
+        }
+    }
+    else
+    {
+        if (expected_rx_info.polled_sub_address[0])
+        {
+            printf("%c: Phase B: remote polled sub-address missing!\n", i);
+            status = T30_ERR_PSA_UNACCEPTABLE;
+        }
+    }
+    if ((u = t30_get_rx_selective_polling_address(s)))
+    {
+        printf("%c: Phase B: remote selective polling address '%s'\n", i, u);
+        if (expected_rx_info.selective_polling_address[0]  &&  strcmp(expected_rx_info.selective_polling_address, u))
+        {
+            printf("%c: Phase B: remote selective polling address incorrect! - expected '%s'\n", i, expected_rx_info.selective_polling_address);
+            status = T30_ERR_SEP_UNACCEPTABLE;
+        }
+    }
+    else
+    {
+        if (expected_rx_info.selective_polling_address[0])
+        {
+            printf("%c: Phase B: remote selective polling address missing!\n", i);
+            status = T30_ERR_SEP_UNACCEPTABLE;
+        }
+    }
+    if ((u = t30_get_rx_sender_ident(s)))
+    {
+        printf("%c: Phase B: remote sender ident '%s'\n", i, u);
+        if (expected_rx_info.sender_ident[0]  &&  strcmp(expected_rx_info.sender_ident, u))
+        {
+            printf("%c: Phase B: remote sender ident incorrect! - expected '%s'\n", i, expected_rx_info.sender_ident);
+            status = T30_ERR_SID_UNACCEPTABLE;
+        }
+    }
+    else
+    {
+        if (expected_rx_info.sender_ident[0])
+        {
+            printf("%c: Phase B: remote sender ident missing!\n", i);
+            status = T30_ERR_SID_UNACCEPTABLE;
+        }
+    }
+    if ((u = t30_get_rx_password(s)))
+    {
+        printf("%c: Phase B: remote password '%s'\n", i, u);
+        if (expected_rx_info.password[0]  &&  strcmp(expected_rx_info.password, u))
+        {
+            printf("%c: Phase B: remote password incorrect! - expected '%s'\n", i, expected_rx_info.password);
+            status = T30_ERR_PWD_UNACCEPTABLE;
+        }
+    }
+    else
+    {
+        if (expected_rx_info.password[0])
+        {
+            printf("%c: Phase B: remote password missing!\n", i);
+            status = T30_ERR_PWD_UNACCEPTABLE;
+        }
+    }
+    printf("%c: Phase B handler on channel %d - (0x%X) %s\n", i, i, result, t30_frametype(result));
+    return status;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int phase_d_handler(t30_state_t *s, void *user_data, int result)
+{
+    int i;
+    char tag[20];
+
+    i = (intptr_t) user_data;
+    snprintf(tag, sizeof(tag), "%c: Phase D:", i);
+
+    printf("%c: Phase D handler on channel %c - (0x%X) %s\n", i, i, result, t30_frametype(result));
+    log_transfer_statistics(s, tag);
+    log_tx_parameters(s, tag);
+    log_rx_parameters(s, tag);
+
+    if (use_receiver_not_ready)
+        t30_set_receiver_not_ready(s, 3);
+
+    if (test_local_interrupt)
+    {
+        if (i == 'A')
+        {
+            printf("%c: Initiating interrupt request\n", i);
+            t30_local_interrupt_request(s, TRUE);
+        }
+        else
+        {
+            switch (result)
+            {
+            case T30_PIP:
+            case T30_PRI_MPS:
+            case T30_PRI_EOM:
+            case T30_PRI_EOP:
+                printf("%c: Accepting interrupt request\n", i);
+                t30_local_interrupt_request(s, TRUE);
+                break;
+            case T30_PIN:
+                break;
+            }
+        }
+    }
+    return T30_ERR_OK;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void phase_e_handler(t30_state_t *s, void *user_data, int result)
+{
+    int i;
+    char tag[20];
+    
+    i = (intptr_t) user_data;
+    snprintf(tag, sizeof(tag), "%c: Phase E:", i);
+    printf("%c: Phase E handler on channel %c - (%d) %s\n", i, i, result, t30_completion_code_to_str(result));    
+    log_transfer_statistics(s, tag);
+    log_tx_parameters(s, tag);
+    log_rx_parameters(s, tag);
+}
+/*- End of function --------------------------------------------------------*/
+
+static void t30_real_time_frame_handler(t30_state_t *s,
+                                        void *user_data,
+                                        int direction,
+                                        const uint8_t *msg,
+                                        int len)
+{
+    if (msg == NULL)
+    {
+    }
+    else
+    {
+        fprintf(stderr,
+                "T.30: Real time frame handler - %s, %s, length = %d\n",
+                (direction)  ?  "line->T.30"  : "T.30->line",
+                t30_frametype(msg[2]),
+                len);
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+static int document_handler(t30_state_t *s, void *user_data, int event)
+{
+    int i;
+    
+    i = (intptr_t) user_data;
+    fprintf(stderr, "%d: Document handler on channel %d - event %d\n", i, i, event);
+    if (next_tx_file[0])
+    {
+        t30_set_tx_file(s, next_tx_file, -1, -1);
+        next_tx_file[0] = '\0';
+        return TRUE;
+    }
+    return FALSE;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void faxtester_real_time_frame_handler(faxtester_state_t *s,
+                                              void *user_data,
+                                              int direction,
+                                              const uint8_t *msg,
+                                              int len)
+{
+    if (msg == NULL)
+    {
+        while (next_step(s) == 0)
+            ;
+        /*endwhile*/
+    }
+    else
+    {
+        fprintf(stderr,
+                "TST: Real time frame handler - %s, %s, length = %d\n",
+                (direction)  ?  "line->tester"  : "tester->line",
+                t30_frametype(msg[2]),
+                len);
+        if (direction  &&  msg[1] == awaited[1])
+        {
+            if ((awaited_len >= 0  &&  len != abs(awaited_len))
+                ||
+                (awaited_len < 0  &&  len < abs(awaited_len))
+                ||
+                memcmp(msg, awaited, abs(awaited_len)) != 0)
+            {
+                span_log_buf(&s->logging, SPAN_LOG_FLOW, "Expected", awaited, abs(awaited_len));
+                span_log_buf(&s->logging, SPAN_LOG_FLOW, "Received", msg, len);
+                printf("Test failed\n");
+                exit(2);
+            }
+        }
+        if (msg[1] == awaited[1])
+        {
+            while (next_step(s) == 0)
+                ;
+            /*endwhile*/
+        }
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+static void faxtester_front_end_step_complete_handler(faxtester_state_t *s, void *user_data)
+{
+    while (next_step(s) == 0)
+        ;
+    /*endwhile*/
+}
+/*- End of function --------------------------------------------------------*/
+
+static void faxtester_front_end_step_timeout_handler(faxtester_state_t *s, void *user_data)
+{
+    span_log(&s->logging, SPAN_LOG_FLOW, "FAX tester step timed out\n");
+    printf("Test failed\n");
+    exit(2);
+}
+/*- End of function --------------------------------------------------------*/
+
+static void fax_prepare(void)
+{
+    t30_state_t *t30;
+    logging_state_t *logging;
+
+    t30 = fax_get_t30_state(fax);
+    fax_set_transmit_on_idle(fax, TRUE);
+    fax_set_tep_mode(fax, TRUE);
+#if 0
+    t30_set_tx_ident(t30, "1234567890");
+    t30_set_tx_sub_address(t30, "Sub-address");
+    t30_set_tx_sender_ident(t30, "Sender ID");
+    t30_set_tx_password(t30, "Password");
+    t30_set_tx_polled_sub_address(t30, "Polled sub-address");
+    t30_set_tx_selective_polling_address(t30, "Sel polling address");
+#endif
+    t30_set_tx_nsf(t30, (const uint8_t *) "\x50\x00\x00\x00Spandsp NSF\x00", 16);
+    //t30_set_tx_nss(t30, (const uint8_t *) "\x50\x00\x00\x00Spandsp NSS\x00", 16);
+    t30_set_tx_nsc(t30, (const uint8_t *) "\x50\x00\x00\x00Spandsp NSC\x00", 16);
+    t30_set_ecm_capability(t30, TRUE);
+    t30_set_supported_t30_features(t30,
+                                   T30_SUPPORT_IDENTIFICATION
+                                 | T30_SUPPORT_SELECTIVE_POLLING
+                                 | T30_SUPPORT_SUB_ADDRESSING);
+    t30_set_supported_image_sizes(t30,
+                                  T30_SUPPORT_US_LETTER_LENGTH
+                                | T30_SUPPORT_US_LEGAL_LENGTH
+                                | T30_SUPPORT_UNLIMITED_LENGTH
+                                | T30_SUPPORT_215MM_WIDTH
+                                | T30_SUPPORT_255MM_WIDTH
+                                | T30_SUPPORT_303MM_WIDTH);
+    t30_set_supported_resolutions(t30,
+                                  T30_SUPPORT_STANDARD_RESOLUTION
+                                | T30_SUPPORT_FINE_RESOLUTION
+                                | T30_SUPPORT_SUPERFINE_RESOLUTION
+                                | T30_SUPPORT_R8_RESOLUTION
+                                | T30_SUPPORT_R16_RESOLUTION
+                                | T30_SUPPORT_300_300_RESOLUTION
+                                | T30_SUPPORT_400_400_RESOLUTION
+                                | T30_SUPPORT_600_600_RESOLUTION
+                                | T30_SUPPORT_1200_1200_RESOLUTION
+                                | T30_SUPPORT_300_600_RESOLUTION
+                                | T30_SUPPORT_400_800_RESOLUTION
+                                | T30_SUPPORT_600_1200_RESOLUTION);
+    t30_set_supported_modems(t30, T30_SUPPORT_V27TER | T30_SUPPORT_V29 | T30_SUPPORT_V17);
+    t30_set_supported_compressions(t30, T30_SUPPORT_T4_1D_COMPRESSION | T30_SUPPORT_T4_2D_COMPRESSION | T30_SUPPORT_T6_COMPRESSION);
+    t30_set_phase_b_handler(t30, phase_b_handler, (void *) (intptr_t) 'A');
+    t30_set_phase_d_handler(t30, phase_d_handler, (void *) (intptr_t) 'A');
+    t30_set_phase_e_handler(t30, phase_e_handler, (void *) (intptr_t) 'A');
+    t30_set_real_time_frame_handler(t30, t30_real_time_frame_handler, (void *) (intptr_t) 'A');
+    t30_set_document_handler(t30, document_handler, (void *) (intptr_t) 'A');
+
+    logging = fax_get_logging_state(fax);
+    span_log_set_level(logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_SHOW_TAG | SPAN_LOG_SHOW_SAMPLE_TIME | SPAN_LOG_FLOW);
+    span_log_set_tag(logging, "A");
+
+    logging = t30_get_logging_state(t30);
+    span_log_set_level(logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_SHOW_TAG | SPAN_LOG_SHOW_SAMPLE_TIME | SPAN_LOG_FLOW);
+    span_log_set_tag(logging, "A");
+
+#if 0
+    span_log_set_level(&fax.modems.v27ter_rx.logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_SHOW_TAG | SPAN_LOG_SHOW_SAMPLE_TIME | SPAN_LOG_FLOW);
+    span_log_set_tag(&fax.modems.v27ter_rx.logging, "A");
+    span_log_set_level(&fax.modems.v29_rx.logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_SHOW_TAG | SPAN_LOG_SHOW_SAMPLE_TIME | SPAN_LOG_FLOW);
+    span_log_set_tag(&fax.modems.v29_rx.logging, "A");
+    span_log_set_level(&fax.modems.v17_rx.logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_SHOW_TAG | SPAN_LOG_SHOW_SAMPLE_TIME | SPAN_LOG_FLOW);
+    span_log_set_tag(&fax.modems.v17_rx.logging, "A");
+#endif
+}
+/*- End of function --------------------------------------------------------*/
+
+static int string_to_msg(uint8_t msg[], uint8_t mask[], const char buf[])
+{
+    int i;
+    int x;
+    const char *t;
+
+    msg[0] = 0;
+    mask[0] = 0xFF;
+    i = 0;
+    t = (char *) buf;
+    while (*t)
+    {
+        /* Skip white space */
+        while (isspace(*t))
+            t++;
+        /* If we find ... we allow arbitrary addition info beyond this point in the message */
+        if (t[0] == '.'  &&  t[1] == '.'  &&  t[2] == '.')
+        {
+            return -i;
+        }
+        else if (isxdigit(*t))
+        {
+            for (  ;  isxdigit(*t);  t++)
+            {
+                x = *t;
+                if (x >= 'a')
+                    x -= 0x20;
+                if (x >= 'A')
+                    x -= ('A' - 10);
+                else
+                    x -= '0';
+                msg[i] = (msg[i] << 4) | x;
+            }
+            mask[i] = 0xFF;
+            if (*t == '/')
+            {
+                /* There is a mask following the byte */
+                mask[i] = 0;
+                for (t++;  isxdigit(*t);  t++)
+                {
+                    x = *t;
+                    if (x >= 'a')
+                        x -= 0x20;
+                    if (x >= 'A')
+                        x -= ('A' - 10);
+                    else
+                        x -= '0';
+                    mask[i] = (mask[i] << 4) | x;
+                }
+            }
+            if (*t  &&  !isspace(*t))
+            {
+                /* Bad string */
+                return 0;
+            }
+            i++;
+        }
+    }
+    return i;
+}
+/*- End of function --------------------------------------------------------*/
+
+#if 0
+static void string_test2(const uint8_t msg[], int len)
+{
+    int i;
+    
+    if (len > 0)
+    {
+        for (i = 0;  i < len - 1;  i++)
+            printf("%02X ", msg[i]);
+        printf("%02X", msg[i]);
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+static void string_test3(const char buf[])
+{
+    uint8_t msg[1000];
+    uint8_t mask[1000];
+    int len;
+    int i;
+    
+    len = string_to_msg(msg, mask, buf);
+    printf("Len = %d: ", len);
+    string_test2(msg, abs(len));
+    printf("/");
+    string_test2(mask, abs(len));
+    printf("\n");
+}
+/*- End of function --------------------------------------------------------*/
+
+static int string_test(void)
+{
+    string_test3("FF C8 12 34 56 78");
+    string_test3("FF C8 12/55 34 56/aA 78 ");
+    string_test3("FF C8 12/55 34 56/aA 78 ...");
+    string_test3("FF C8 12/55 34 56/aA 78...");
+    string_test3("FF C8 12/55 34 56/aA 78 ... 99 88 77");
+    exit(0);
+}
+/*- End of function --------------------------------------------------------*/
+#endif
+
+static void corrupt_image(faxtester_state_t *s, uint8_t image[], int len, const char *bad_rows)
+{
+    int i;
+    int j;
+    int k;
+    uint32_t bits;
+    uint32_t bitsx;
+    int list[1000];
+    int x;
+    int row;
+    const char *t;
+
+    /* Form the list of rows to be hit */
+    x = 0;
+    t = bad_rows;
+    while (*t)
+    {
+        while (isspace(*t))
+            t++;
+        if (sscanf(t, "%d", &list[x]) < 1)
+            break;
+        x++;
+        while (isdigit(*t))
+            t++;
+        if (*t == ',')
+            t++;
+    }
+
+    /* Go through the image, and corrupt the first bit of every listed row */
+    bits = 0x7FF;
+    bitsx = 0x7FF;
+    row = 0;
+    for (i = 0;  i < len;  i++)
+    {
+        bits ^= (image[i] << 11);
+        bitsx ^= (image[i] << 11);
+        for (j = 0;  j < 8;  j++)
+        {
+            if ((bits & 0xFFF) == 0x800)
+            {
+                /* We are at an EOL. Is this row in the list of rows to be corrupted? */
+                row++;
+                for (k = 0;  k < x;  k++)
+                {
+                    if (list[k] == row)
+                    {
+                        /* Corrupt this row. TSB85 says to hit the first bit after the EOL */
+                        bitsx ^= 0x1000;
+                    }
+                }
+            }
+            bits >>= 1;
+            bitsx >>= 1;
+        }
+        image[i] = (bitsx >> 3) & 0xFF;
+    }
+    span_log(&s->logging, SPAN_LOG_FLOW, "%d rows found. %d corrupted\n", row, x);
+}
+/*- End of function --------------------------------------------------------*/
+
+static int next_step(faxtester_state_t *s)
+{
+    int delay;
+    int flags;
+    xmlChar *dir;
+    xmlChar *type;
+    xmlChar *modem;
+    xmlChar *value;
+    xmlChar *tag;
+    xmlChar *bad_rows;
+    xmlChar *crc_error;
+    xmlChar *pattern;
+    xmlChar *timeout;
+    xmlChar *min_bits;
+    xmlChar *frame_size;
+    xmlChar *block;
+    xmlChar *compression;
+    uint8_t buf[1000];
+    uint8_t mask[1000];
+    char path[1024];
+    int i;
+    int j;
+    int hdlc;
+    int short_train;
+    int min_row_bits;
+    int ecm_frame_size;
+    int ecm_block;
+    int compression_type;
+    int timer;
+    int len;
+    t4_state_t t4_state;
+    t30_state_t *t30;
+
+    if (s->cur == NULL)
+    {
+        if (!s->final_delayed)
+        {
+            /* Add a bit of waiting at the end, to ensure everything gets flushed through,
+               any timers can expire, etc. */
+            faxtester_set_timeout(s, -1);
+            faxtester_set_rx_type(s, T30_MODEM_NONE, 0, FALSE, FALSE);
+            faxtester_set_tx_type(s, T30_MODEM_PAUSE, 0, 120000, FALSE);
+            s->final_delayed = TRUE;
+            return 1;
+        }
+        /* Finished */
+        printf("Test passed\n");
+        exit(0);
+    }
+    while (s->cur  &&  xmlStrcmp(s->cur->name, (const xmlChar *) "step") != 0)
+        s->cur = s->cur->next;
+    if (s->cur == NULL)
+    {
+        /* Finished */
+        printf("Test passed\n");
+        exit(0);
+    }
+
+    dir = xmlGetProp(s->cur, (const xmlChar *) "dir");
+    type = xmlGetProp(s->cur, (const xmlChar *) "type");
+    modem = xmlGetProp(s->cur, (const xmlChar *) "modem");
+    value = xmlGetProp(s->cur, (const xmlChar *) "value");
+    tag = xmlGetProp(s->cur, (const xmlChar *) "tag");
+    bad_rows = xmlGetProp(s->cur, (const xmlChar *) "bad_rows");
+    crc_error = xmlGetProp(s->cur, (const xmlChar *) "crc_error");
+    pattern = xmlGetProp(s->cur, (const xmlChar *) "pattern");
+    timeout = xmlGetProp(s->cur, (const xmlChar *) "timeout");
+    min_bits = xmlGetProp(s->cur, (const xmlChar *) "min_bits");
+    frame_size = xmlGetProp(s->cur, (const xmlChar *) "frame_size");
+    block = xmlGetProp(s->cur, (const xmlChar *) "block");
+    compression = xmlGetProp(s->cur, (const xmlChar *) "compression");
+
+    s->cur = s->cur->next;
+
+    span_log(&s->logging,
+             SPAN_LOG_FLOW, 
+             "Dir - %s, type - %s, modem - %s, value - %s, timeout - %s, tag - %s\n",
+             (dir)  ?  (const char *) dir  :  "",
+             (type)  ?  (const char *) type  :  "",
+             (modem)  ?  (const char *) modem  :  "",
+             (value)  ?  (const char *) value  :  "",
+             (timeout)  ?  (const char *) timeout  :  "",
+             (tag)  ?  (const char *) tag  :  "");
+    if (type == NULL)
+        return 1;
+    if (timeout)
+        timer = atoi((const char *) timeout);
+    else
+        timer = -1;
+
+    if (dir  &&  strcasecmp((const char *) dir, "R") == 0)
+    {
+        /* Receive always has a timeout applied. */
+        if (timer < 0)
+            timer = 7000;
+        faxtester_set_timeout(s, timer);
+        if (modem)
+        {
+            hdlc = (strcasecmp((const char *) type, "PREAMBLE") == 0);
+            short_train = (strcasecmp((const char *) type, "TCF") != 0);
+            faxtester_set_tx_type(s, T30_MODEM_NONE, 0, FALSE, FALSE);
+            if (strcasecmp((const char *) modem, "V.21") == 0)
+            {
+                faxtester_set_rx_type(s, T30_MODEM_V21, 300, FALSE, TRUE);
+            }
+            else if (strcasecmp((const char *) modem, "V.17/14400") == 0)
+            {
+                faxtester_set_rx_type(s, T30_MODEM_V17, 14400, short_train, hdlc);
+            }
+            else if (strcasecmp((const char *) modem, "V.17/12000") == 0)
+            {
+                faxtester_set_rx_type(s, T30_MODEM_V17, 12000, short_train, hdlc);
+            }
+            else if (strcasecmp((const char *) modem, "V.17/9600") == 0)
+            {
+                faxtester_set_rx_type(s, T30_MODEM_V17, 9600, short_train, hdlc);
+            }
+            else if (strcasecmp((const char *) modem, "V.17/7200") == 0)
+            {
+                faxtester_set_rx_type(s, T30_MODEM_V17, 7200, short_train, hdlc);
+            }
+            else if (strcasecmp((const char *) modem, "V.29/9600") == 0)
+            {
+                faxtester_set_rx_type(s, T30_MODEM_V29, 9600, FALSE, hdlc);
+            }
+            else if (strcasecmp((const char *) modem, "V.29/7200") == 0)
+            {
+                faxtester_set_rx_type(s, T30_MODEM_V29, 7200, FALSE, hdlc);
+            }
+            else if (strcasecmp((const char *) modem, "V.27ter/4800") == 0)
+            {
+                faxtester_set_rx_type(s, T30_MODEM_V27TER, 4800, FALSE, hdlc);
+            }
+            else if (strcasecmp((const char *) modem, "V.27ter/2400") == 0)
+            {
+                faxtester_set_rx_type(s, T30_MODEM_V27TER, 2400, FALSE, hdlc);
+            }
+            else
+            {
+                span_log(&s->logging, SPAN_LOG_FLOW, "Unrecognised modem\n");
+            }
+        }
+
+        if (strcasecmp((const char *) type, "SET") == 0)
+        {
+            if (strcasecmp((const char *) tag, "IDENT") == 0)
+                strcpy(expected_rx_info.ident, (const char *) value);
+            else if (strcasecmp((const char *) tag, "SUB") == 0)
+                strcpy(expected_rx_info.sub_address, (const char *) value);
+            else if (strcasecmp((const char *) tag, "SEP") == 0)
+                strcpy(expected_rx_info.selective_polling_address, (const char *) value);
+            else if (strcasecmp((const char *) tag, "PSA") == 0)
+                strcpy(expected_rx_info.polled_sub_address, (const char *) value);
+            else if (strcasecmp((const char *) tag, "SID") == 0)
+                strcpy(expected_rx_info.sender_ident, (const char *) value);
+            else if (strcasecmp((const char *) tag, "PWD") == 0)
+                strcpy(expected_rx_info.password, (const char *) value);
+            return 0;
+        }
+        else if (strcasecmp((const char *) type, "CNG") == 0)
+        {
+            /* Look for CNG */
+            faxtester_set_rx_type(s, T30_MODEM_CNG, 0, FALSE, FALSE);
+            faxtester_set_tx_type(s, T30_MODEM_NONE, 0, FALSE, FALSE);
+        }
+        else if (strcasecmp((const char *) type, "CED") == 0)
+        {
+            /* Look for CED */
+            faxtester_set_rx_type(s, T30_MODEM_CED, 0, FALSE, FALSE);
+            faxtester_set_tx_type(s, T30_MODEM_NONE, 0, FALSE, FALSE);
+        }
+        else if (strcasecmp((const char *) type, "HDLC") == 0)
+        {
+            i = string_to_msg(buf, mask, (const char *) value);
+            bit_reverse(awaited, buf, abs(i));
+            awaited_len = i;
+        }
+        else if (strcasecmp((const char *) type, "TCF") == 0)
+        {
+        }
+        else if (strcasecmp((const char *) type, "MSG") == 0)
+        {
+        }
+        else if (strcasecmp((const char *) type, "PP") == 0)
+        {
+        }
+        else if (strcasecmp((const char *) type, "SILENCE") == 0)
+        {
+            faxtest_set_rx_silence(s);
+        }
+        else
+        {
+            span_log(&s->logging, SPAN_LOG_FLOW, "Unrecognised type '%s'\n", (const char *) type);
+            return 0;
+        }
+    }
+    else
+    {
+        faxtester_set_timeout(s, timer);
+        if (modem)
+        {
+            hdlc = (strcasecmp((const char *) type, "PREAMBLE") == 0);
+            short_train = (strcasecmp((const char *) type, "TCF") != 0);
+            faxtester_set_rx_type(s, T30_MODEM_NONE, 0, FALSE, FALSE);
+            if (strcasecmp((const char *) modem, "V.21") == 0)
+            {
+                faxtester_set_tx_type(s, T30_MODEM_V21, 300, FALSE, TRUE);
+            }
+            else if (strcasecmp((const char *) modem, "V.17/14400") == 0)
+            {
+                faxtester_set_tx_type(s, T30_MODEM_V17, 14400, short_train, hdlc);
+            }
+            else if (strcasecmp((const char *) modem, "V.17/12000") == 0)
+            {
+                faxtester_set_tx_type(s, T30_MODEM_V17, 12000, short_train, hdlc);
+            }
+            else if (strcasecmp((const char *) modem, "V.17/9600") == 0)
+            {
+                faxtester_set_tx_type(s, T30_MODEM_V17, 9600, short_train, hdlc);
+            }
+            else if (strcasecmp((const char *) modem, "V.17/7200") == 0)
+            {
+                faxtester_set_tx_type(s, T30_MODEM_V17, 7200, short_train, hdlc);
+            }
+            else if (strcasecmp((const char *) modem, "V.29/9600") == 0)
+            {
+                faxtester_set_tx_type(s, T30_MODEM_V29, 9600, FALSE, hdlc);
+            }
+            else if (strcasecmp((const char *) modem, "V.29/7200") == 0)
+            {
+                faxtester_set_tx_type(s, T30_MODEM_V29, 7200, FALSE, hdlc);
+            }
+            else if (strcasecmp((const char *) modem, "V.27ter/4800") == 0)
+            {
+                faxtester_set_tx_type(s, T30_MODEM_V27TER, 4800, FALSE, hdlc);
+            }
+            else if (strcasecmp((const char *) modem, "V.27ter/2400") == 0)
+            {
+                faxtester_set_tx_type(s, T30_MODEM_V27TER, 2400, FALSE, hdlc);
+            }
+            else
+            {
+                span_log(&s->logging, SPAN_LOG_FLOW, "Unrecognised modem\n");
+            }
+        }
+
+        if (strcasecmp((const char *) type, "SET") == 0)
+        {
+            t30 = fax_get_t30_state(fax);
+            if (strcasecmp((const char *) tag, "IDENT") == 0)
+                t30_set_tx_ident(t30, (const char *) value);
+            else if (strcasecmp((const char *) tag, "SUB") == 0)
+                t30_set_tx_sub_address(t30, (const char *) value);
+            else if (strcasecmp((const char *) tag, "SEP") == 0)
+                t30_set_tx_selective_polling_address(t30, (const char *) value);
+            else if (strcasecmp((const char *) tag, "PSA") == 0)
+                t30_set_tx_polled_sub_address(t30, (const char *) value);
+            else if (strcasecmp((const char *) tag, "SID") == 0)
+                t30_set_tx_sender_ident(t30, (const char *) value);
+            else if (strcasecmp((const char *) tag, "PWD") == 0)
+                t30_set_tx_password(t30, (const char *) value);
+            else if (strcasecmp((const char *) tag, "RXFILE") == 0)
+            {
+                if (value)
+                    t30_set_rx_file(t30, (const char *) value, -1);
+                else
+                    t30_set_rx_file(t30, output_tiff_file_name, -1);
+            }
+            else if (strcasecmp((const char *) tag, "TXFILE") == 0)
+            {
+                sprintf(next_tx_file, "%s/%s", image_path, (const char *) value);
+printf("Push '%s'\n", next_tx_file);
+            }
+            return 0;
+        }
+        else if (strcasecmp((const char *) type, "CALL") == 0)
+        {
+            fax = fax_init(NULL, FALSE);
+            fax_prepare();
+            next_tx_file[0] = '\0';
+            t30 = fax_get_t30_state(fax);
+            t30_set_rx_file(t30, output_tiff_file_name, -1);
+            /* Avoid libtiff 3.8.2 and earlier bug on complex 2D lines. */
+            t30_set_rx_encoding(t30, T4_COMPRESSION_ITU_T4_1D);
+            if (value)
+            {
+                sprintf(path, "%s/%s", image_path, (const char *) value);
+                t30_set_tx_file(t30, path, -1, -1);
+            }
+            return 0;
+        }
+        else if (strcasecmp((const char *) type, "ANSWER") == 0)
+        {
+            fax = fax_init(NULL, TRUE);
+            fax_prepare();
+            next_tx_file[0] = '\0';
+            t30 = fax_get_t30_state(fax);
+            /* Avoid libtiff 3.8.2 and earlier bug on complex 2D lines. */
+            t30_set_rx_encoding(t30, T4_COMPRESSION_ITU_T4_1D);
+            if (value)
+            {
+                sprintf(path, "%s/%s", image_path, (const char *) value);
+                t30_set_tx_file(t30, path, -1, -1);
+            }
+            return 0;
+        }
+        else if (strcasecmp((const char *) type, "CNG") == 0)
+        {
+            faxtester_set_rx_type(s, T30_MODEM_NONE, 0, FALSE, FALSE);
+            faxtester_set_tx_type(s, T30_MODEM_CNG, 0, FALSE, FALSE);
+        }
+        else if (strcasecmp((const char *) type, "CED") == 0)
+        {
+            faxtester_set_rx_type(s, T30_MODEM_NONE, 0, FALSE, FALSE);
+            faxtester_set_tx_type(s, T30_MODEM_CED, 0, FALSE, FALSE);
+        }
+        else if (strcasecmp((const char *) type, "WAIT") == 0)
+        {
+            delay = (value)  ?  atoi((const char *) value)  :  1;
+            faxtester_set_rx_type(s, T30_MODEM_NONE, 0, FALSE, FALSE);
+            faxtester_set_tx_type(s, T30_MODEM_PAUSE, 0, delay, FALSE);
+        }
+        else if (strcasecmp((const char *) type, "PREAMBLE") == 0)
+        {
+            flags = (value)  ?  atoi((const char *) value)  :  37;
+            faxtester_send_hdlc_flags(s, flags);
+        }
+        else if (strcasecmp((const char *) type, "POSTAMBLE") == 0)
+        {
+            flags = (value)  ?  atoi((const char *) value)  :  5;
+            faxtester_send_hdlc_flags(s, flags);
+        }
+        else if (strcasecmp((const char *) type, "HDLC") == 0)
+        {
+            i = string_to_msg(buf, mask, (const char *) value);
+            bit_reverse(buf, buf, abs(i));
+            if (crc_error  &&  strcasecmp((const char *) crc_error, "0") == 0)
+                faxtester_send_hdlc_msg(s, buf, abs(i), FALSE);
+            else
+                faxtester_send_hdlc_msg(s, buf, abs(i), TRUE);
+        }
+        else if (strcasecmp((const char *) type, "TCF") == 0)
+        {
+            if (value)
+                i = atoi((const char *) value);
+            else
+                i = 450;
+            if (pattern)
+            {
+                /* TODO: implement proper patterns */
+                j = atoi((const char *) pattern);
+                memset(image, 0x55, j);
+                if (i > j)
+                    memset(image + j, 0, i - j);
+            }
+            else
+            {
+                memset(image, 0, i);
+            }
+            faxtester_set_non_ecm_image_buffer(s, image, i);
+        }
+        else if (strcasecmp((const char *) type, "MSG") == 0)
+        {
+            /* A non-ECM page */
+            min_row_bits = (min_bits)  ?  atoi((const char *) min_bits)  :  0;
+            sprintf(path, "%s/%s", image_path, (const char *) value);
+            if (t4_tx_init(&t4_state, path, -1, -1) == NULL)
+            {
+                span_log(&s->logging, SPAN_LOG_FLOW, "Failed to init T.4 send\n");
+                printf("Test failed\n");
+                exit(2);
+            }
+            t4_tx_set_min_row_bits(&t4_state, min_row_bits);
+            t4_tx_set_header_info(&t4_state, NULL);
+            compression_type = T4_COMPRESSION_ITU_T4_1D;
+            if (compression)
+            {
+                if (strcasecmp((const char *) compression, "T.4 2D") == 0)
+                    compression_type = T4_COMPRESSION_ITU_T4_2D;
+                else if (strcasecmp((const char *) compression, "T.6") == 0)
+                    compression_type = T4_COMPRESSION_ITU_T6;
+            }
+            t4_tx_set_tx_encoding(&t4_state, compression_type);
+            if (t4_tx_start_page(&t4_state))
+            {
+                span_log(&s->logging, SPAN_LOG_FLOW, "Failed to start T.4 send\n");
+                printf("Test failed\n");
+                exit(2);
+            }
+            len = t4_tx_get_chunk(&t4_state, image, sizeof(image));
+            if (bad_rows)
+            {
+                span_log(&s->logging, SPAN_LOG_FLOW, "We need to corrupt the image\n");
+                corrupt_image(s, image, len, (const char *) bad_rows);
+            }
+            t4_tx_release(&t4_state);
+            span_log(&s->logging, SPAN_LOG_FLOW, "Non-ECM image is %d bytes\n", len);
+            faxtester_set_non_ecm_image_buffer(s, image, len);
+        }
+        else if (strcasecmp((const char *) type, "PP") == 0)
+        {
+            min_row_bits = (min_bits)  ?  atoi((const char *) min_bits)  :  0;
+            ecm_block = (block)  ?  atoi((const char *) block)  :  0;
+            ecm_frame_size = (frame_size)  ?  atoi((const char *) frame_size)  :  64;
+            i = (crc_error)  ?  atoi((const char *) crc_error)  :  -1;
+            sprintf(path, "%s/%s", image_path, (const char *) value);
+            if (t4_tx_init(&t4_state, path, -1, -1) == NULL)
+            {
+                span_log(&s->logging, SPAN_LOG_FLOW, "Failed to init T.4 send\n");
+                printf("Test failed\n");
+                exit(2);
+            }
+            t4_tx_set_min_row_bits(&t4_state, min_row_bits);
+            t4_tx_set_header_info(&t4_state, NULL);
+            compression_type = T4_COMPRESSION_ITU_T4_1D;
+            if (compression)
+            {
+                if (strcasecmp((const char *) compression, "T.4 2D") == 0)
+                    compression_type = T4_COMPRESSION_ITU_T4_2D;
+                else if (strcasecmp((const char *) compression, "T.6") == 0)
+                    compression_type = T4_COMPRESSION_ITU_T6;
+            }
+            t4_tx_set_tx_encoding(&t4_state, compression_type);
+            if (t4_tx_start_page(&t4_state))
+            {
+                span_log(&s->logging, SPAN_LOG_FLOW, "Failed to start T.4 send\n");
+                printf("Test failed\n");
+                exit(2);
+            }
+            /*endif*/
+            len = t4_tx_get_chunk(&t4_state, image, sizeof(image));
+            if (bad_rows)
+            {
+                span_log(&s->logging, SPAN_LOG_FLOW, "We need to corrupt the image\n");
+                corrupt_image(s, image, len, (const char *) bad_rows);
+            }
+            /*endif*/
+            t4_tx_release(&t4_state);
+            span_log(&s->logging, SPAN_LOG_FLOW, "ECM image is %d bytes\n", len);
+            faxtester_set_ecm_image_buffer(s, image, len, ecm_block, ecm_frame_size, i);
+        }
+        else
+        {
+            span_log(&s->logging, SPAN_LOG_FLOW, "Unrecognised type '%s'\n", (const char *) type);
+            return 0;
+        }
+        /*endif*/
+    }
+    /*endif*/
+    return 1;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void exchange(faxtester_state_t *s)
+{
+    int16_t amp[SAMPLES_PER_CHUNK];
+    int16_t out_amp[2*SAMPLES_PER_CHUNK];
+    int len;
+    int i;
+    int total_audio_time;
+    int log_audio;
+    logging_state_t *logging;
+
+    log_audio = TRUE;
+    output_tiff_file_name = OUTPUT_TIFF_FILE_NAME;
+
+    if (log_audio)
+    {
+        if ((out_handle = sf_open_telephony_write(OUTPUT_FILE_NAME_WAVE, 2)) == NULL)
+        {
+            fprintf(stderr, "    Cannot create audio file '%s'\n", OUTPUT_FILE_NAME_WAVE);
+            printf("Test failed\n");
+            exit(2);
+        }
+        /*endif*/
+    }
+    /*endif*/
+
+    total_audio_time = 0;
+
+    faxtester_set_transmit_on_idle(&state, TRUE);
+    faxtester_set_real_time_frame_handler(&state, faxtester_real_time_frame_handler, NULL);
+    faxtester_set_front_end_step_complete_handler(&state, faxtester_front_end_step_complete_handler, NULL);
+    faxtester_set_front_end_step_timeout_handler(&state, faxtester_front_end_step_timeout_handler, NULL);
+
+    fax = fax_init(NULL, FALSE);
+    fax_prepare();
+    next_tx_file[0] = '\0';
+
+    while (next_step(s) == 0)
+        ;
+    /*endwhile*/
+    for (;;)
+    {
+        len = fax_tx(fax, amp, SAMPLES_PER_CHUNK);
+        faxtester_rx(s, amp, len);
+        if (log_audio)
+        {
+            for (i = 0;  i < len;  i++)
+                out_amp[2*i + 0] = amp[i];
+            /*endfor*/
+        }
+        /*endif*/
+
+        total_audio_time += SAMPLES_PER_CHUNK;
+
+        logging = t30_get_logging_state(fax_get_t30_state(fax));
+        span_log_bump_samples(logging, len);
+#if 0
+        span_log_bump_samples(&fax.modems.v27ter_rx.logging, len);
+        span_log_bump_samples(&fax.modems.v29_rx.logging, len);
+        span_log_bump_samples(&fax.modems.v17_rx.logging, len);
+#endif
+        logging = fax_get_logging_state(fax);
+        span_log_bump_samples(logging, len);
+
+        span_log_bump_samples(&s->logging, len);
+                
+        len = faxtester_tx(s, amp, 160);
+        if (fax_rx(fax, amp, len))
+            break;
+        /*endif*/
+        if (log_audio)
+        {
+            for (i = 0;  i < len;  i++)
+                out_amp[2*i + 1] = amp[i];
+            /*endfor*/
+            if (sf_writef_short(out_handle, out_amp, SAMPLES_PER_CHUNK) != SAMPLES_PER_CHUNK)
+                break;
+            /*endif*/
+        }
+        /*endif*/
+    }
+    /*endfor*/
+    if (log_audio)
+    {
+        if (sf_close(out_handle))
+        {
+            fprintf(stderr, "    Cannot close audio file '%s'\n", OUTPUT_FILE_NAME_WAVE);
+            printf("Test failed\n");
+            exit(2);
+        }
+        /*endif*/
+    }
+    /*endif*/
+}
+/*- End of function --------------------------------------------------------*/
+
+static int parse_config(faxtester_state_t *s, xmlDocPtr doc, xmlNsPtr ns, xmlNodePtr cur)
+{
+    xmlChar *x;
+    xmlChar *y;
+
+    while (cur)
+    {
+        if (xmlStrcmp(cur->name, (const xmlChar *) "path") == 0)
+        {
+            if ((x = xmlGetProp(cur, (const xmlChar *) "type"))
+                &&
+                (y = xmlGetProp(cur, (const xmlChar *) "value")))
+            {
+                if (strcasecmp((const char *) x, "IMAGE") == 0)
+                {
+                    span_log(&s->logging, SPAN_LOG_FLOW, "Found '%s' '%s'\n", (char *) x, (char *) y);
+                    strcpy(image_path, (const char *) y);
+                }
+                /*endif*/
+            }
+            /*endif*/
+        }
+        /*endif*/
+        cur = cur->next;
+    }
+    /*endwhile*/
+    return -1;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int parse_test_group(faxtester_state_t *s, xmlDocPtr doc, xmlNsPtr ns, xmlNodePtr cur, const char *test)
+{
+    xmlChar *x;
+
+    while (cur)
+    {
+        if (xmlStrcmp(cur->name, (const xmlChar *) "test") == 0)
+        {
+            if ((x = xmlGetProp(cur, (const xmlChar *) "name")))
+            {
+                if (xmlStrcmp(x, (const xmlChar *) test) == 0)
+                {
+                    span_log(&s->logging, SPAN_LOG_FLOW, "Found '%s'\n", (char *) x);
+                    s->cur = cur->xmlChildrenNode;
+                    return 0;
+                }
+                /*endif*/
+            }
+            /*endif*/
+        }
+        /*endif*/
+        cur = cur->next;
+    }
+    /*endwhile*/
+    return -1;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int get_test_set(faxtester_state_t *s, const char *test_file, const char *test)
+{
+    xmlDocPtr doc;
+    xmlNsPtr ns;
+    xmlNodePtr cur;
+    xmlValidCtxt valid;
+
+    ns = NULL;    
+    xmlKeepBlanksDefault(0);
+    xmlCleanupParser();
+    if ((doc = xmlParseFile(test_file)) == NULL)
+    {
+        fprintf(stderr, "No document\n");
+        printf("Test failed\n");
+        exit(2);
+    }
+    /*endif*/
+    xmlXIncludeProcess(doc);
+    if (!xmlValidateDocument(&valid, doc))
+    {
+        fprintf(stderr, "Invalid document\n");
+        printf("Test failed\n");
+        exit(2);
+    }
+    /*endif*/
+
+    /* Check the document is of the right kind */
+    if ((cur = xmlDocGetRootElement(doc)) == NULL)
+    {
+        xmlFreeDoc(doc);
+        fprintf(stderr, "Empty document\n");
+        printf("Test failed\n");
+        exit(2);
+    }
+    /*endif*/
+    if (xmlStrcmp(cur->name, (const xmlChar *) "fax-tests"))
+    {
+        xmlFreeDoc(doc);
+        fprintf(stderr, "Document of the wrong type, root node != fax-tests");
+        printf("Test failed\n");
+        exit(2);
+    }
+    /*endif*/
+    cur = cur->xmlChildrenNode;
+    while (cur  &&  xmlIsBlankNode(cur))
+        cur = cur->next;
+    /*endwhile*/
+    if (cur == NULL)
+    {
+        printf("Test failed\n");
+        exit(2);
+    }
+    /*endif*/
+    while (cur)
+    {
+        if (xmlStrcmp(cur->name, (const xmlChar *) "config") == 0)
+        {
+            parse_config(s, doc, ns, cur->xmlChildrenNode);
+        }
+        /*endif*/
+        if (xmlStrcmp(cur->name, (const xmlChar *) "test-group") == 0)
+        {
+            if (parse_test_group(s, doc, ns, cur->xmlChildrenNode, test) == 0)
+            {
+                /* We found the test we want, so run it. */
+                exchange(s);
+                break;
+            }
+            /*endif*/
+        }
+        /*endif*/
+        cur = cur->next;
+    }
+    /*endwhile*/
+    xmlFreeDoc(doc);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+int main(int argc, char *argv[])
+{
+    const char *test_name;
+
+    //string_test();
+
+    test_name = "MRGN01";
+    if (argc > 1)
+        test_name = argv[1];
+
+    strcpy(image_path, ".");
+    faxtester_init(&state, TRUE);
+    memset(&expected_rx_info, 0, sizeof(expected_rx_info));
+    span_log_set_level(&state.logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_SHOW_TAG | SPAN_LOG_SHOW_SAMPLE_TIME | SPAN_LOG_FLOW);
+    span_log_set_tag(&state.logging, "B");
+    get_test_set(&state, "../spandsp/tsb85.xml", test_name);
+    printf("Done\n");
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+/*- End of file ------------------------------------------------------------*/

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