libosmocore/tests/v110/ta_test.c

460 lines
15 KiB
C
Raw Permalink Normal View History

isdn: initial implementation of the V.110 TA ITU-T recommendation V.110 defines Terminal Adaptor (TA) functions for the connection of Terminal Equipment (TE) having standard V-series interfaces to the ISDN. This patch brings "software" implementation of the TA to libosmoisdn. The primary user for this soft-TA is the mobile-side implementation of CSD (Circuit Switched Data) in osmocom-bb. CSD is heavily based on V.110, which is not surprising given that GSM is a "wireless ISDN". Nevertheless, this code will likely also be useful in the context of retro-networking. Similarly to the existing V.110 code in libosmoisdn, the present implementation aims to be functional and correct, rather than efficient in any way. It also has several limitations, which are not critical for the CSD use case, but eventually may be a problem for other use cases in the context of retro-networking. Therefore, the V.110 TA API should be considered _unstable_, and may be subject to change in the future. +-------+ +------+ B-channel +------+ +-------+ | TE1 |------| TA |~~~~~~~~~~~~~~~| TA |------| TE2 | +-------+ +------+ +------+ +-------+ TE (also known as DTE) is basically a computer, having a V-series (usually RS-232) connection to TA (also known as DCE). The TA acts like a regular analog modem, except that it is not performing any kind of modulation or demodulation itself. The TE-TA interface is implemented by the user supplied callback functions, configured during the allocation of a TA instance: * .rx_cb() - receive call-back of the application, * .tx_cb() - transmit call-back of the application, * .status_update_cb() - status line update call-back. In addition to that, the application (TE) can interact with the V.24 status lines (circuits) using the following API: * osmo_v110_ta_{get,set}_status(), * osmo_v110_ta_{get,set}_circuit(). The Rx and Tx between TE and TA is always driven by the TA itself, as a result of an interaction with the lower layer implementing the B-channel interface. There is currently no buffering and thus no way for TE to initiate transmission or pull data on its own. The TA-TA (B-channel) interface is implemented by the following functions, which are meant to be called by the lower layer transmitting and receiving V.110 frames over certain medium: * osmo_v110_ta_frame_in() - indicate a received V.110 frame, * osmo_v110_ta_frame_out() - pull a V.110 frame for transmission, * osmo_v110_ta_[de]sync_ind() - indicate a synchronization event. The lower layer is responsible for finding the synchronization pattern (if needed), aligning to the frame boundaries, and doing the V.110 frame coding. The D-channel signalling is behind the scope of this module. Initial (Work-in-Progress) implementation by Harald Welte, completed and co-authored by Vadim Yanitskiy. Change-Id: I5716bd6fd0201ee7a7a29e72f775972cd374082f Related: OS#4396
2023-03-14 19:33:51 +00:00
/*
* (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Vadim Yanitskiy <vyanitskiy@sysmocom.de>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <osmocom/core/fsm.h>
#include <osmocom/core/bits.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/application.h>
#include <osmocom/isdn/v110.h>
#include <osmocom/isdn/v110_ta.h>
static void *test_ctx = NULL;
/* inverse logic: ON = binary 0; OFF = binary 1 */
#define V110_SX_BIT_ON 0
#define V110_SX_BIT_OFF 1
/*********************************************************************************
* V.110 TA configuration and callbacks
*********************************************************************************/
static void v110_ta_test_rx_cb(void *priv, const ubit_t *buf, size_t buf_size)
{
fprintf(stderr, "%s(buf_size=%zu): %s\n",
__func__, buf_size, osmo_ubit_dump(buf, buf_size));
}
static void v110_ta_test_tx_cb(void *priv, ubit_t *buf, size_t buf_size)
{
for (size_t i = 0; i < buf_size; i++)
buf[i] = (i & 1);
fprintf(stderr, "%s(buf_size=%zu): %s\n",
__func__, buf_size, osmo_ubit_dump(buf, buf_size));
}
static void v110_ta_test_status_update_cb(void *priv, unsigned int status)
{
fprintf(stderr, "%s(status=0x%08x)\n", __func__, status);
}
static const struct osmo_v110_ta_cfg v110_ta_test_cfg = {
.rate = OSMO_V110_SYNC_RA1_9600,
.rx_cb = &v110_ta_test_rx_cb,
.tx_cb = &v110_ta_test_tx_cb,
.status_update_cb = &v110_ta_test_status_update_cb,
};
/*********************************************************************************
* various helper functions
*********************************************************************************/
static void v110_ta_test_init_df(struct osmo_v110_decoded_frame *df)
{
/* quickly set all the bits to binary '1' */
memset(df, 1, sizeof(*df));
/* D-bits: 0101... pattern */
for (unsigned int i = 0; i < MAX_D_BITS; i += 2)
df->d_bits[i] = 0;
/* E-bits: E1/E2/E3 indicate 9600 bps */
df->e_bits[0] = 0;
}
static void v110_ta_test_dump_df(const struct osmo_v110_decoded_frame *df)
{
fprintf(stderr, " D-bits: %s\n", osmo_ubit_dump(&df->d_bits[0], MAX_D_BITS));
fprintf(stderr, " E-bits: %s\n", osmo_ubit_dump(&df->e_bits[0], MAX_E_BITS));
fprintf(stderr, " S-bits: %s\n", osmo_ubit_dump(&df->s_bits[0], MAX_S_BITS));
fprintf(stderr, " X-bits: %s\n", osmo_ubit_dump(&df->x_bits[0], MAX_X_BITS));
}
static void v110_ta_test_dump_circuit(const struct osmo_v110_ta *ta,
enum osmo_v110_ta_circuit circuit,
bool exp_state)
{
bool state = osmo_v110_ta_get_circuit(ta, circuit);
fprintf(stderr, "circuit %s (%s) is %s (expected to be %s)\n",
osmo_v110_ta_circuit_name(circuit),
osmo_v110_ta_circuit_desc(circuit),
state ? "ON" : "OFF",
exp_state ? "ON" : "OFF");
}
static void v110_ta_test_set_circuit(struct osmo_v110_ta *ta,
enum osmo_v110_ta_circuit circuit,
bool active)
{
int rc;
fprintf(stderr, "setting circuit %s (%s) %s\n",
osmo_v110_ta_circuit_name(circuit),
osmo_v110_ta_circuit_desc(circuit),
active ? "ON" : "OFF");
rc = osmo_v110_ta_set_circuit(ta, circuit, active);
fprintf(stderr, "osmo_v110_ta_set_circuit() returns %d\n", rc);
}
/*********************************************************************************
* the actual tests
*********************************************************************************/
static void test_idle_ready(void)
{
struct osmo_v110_decoded_frame df = { 0 };
struct osmo_v110_ta *ta;
int rc;
fprintf(stderr, "\n==== Running %s()\n", __func__);
ta = osmo_v110_ta_alloc(test_ctx, __func__, &v110_ta_test_cfg);
OSMO_ASSERT(ta != NULL);
/* we expect the TA FSM to be in V110_TA_ST_IDLE_READY */
fprintf(stderr, "Initial status: 0x%08x\n", osmo_v110_ta_get_status(ta));
v110_ta_test_dump_circuit(ta, OSMO_V110_TA_C_106, false);
v110_ta_test_dump_circuit(ta, OSMO_V110_TA_C_107, false);
v110_ta_test_dump_circuit(ta, OSMO_V110_TA_C_109, false);
fprintf(stderr, "osmo_v110_ta_frame_in(): all bits set to binary '1'\n");
memset(&df, 1, sizeof(df));
v110_ta_test_dump_df(&df);
rc = osmo_v110_ta_frame_in(ta, &df);
fprintf(stderr, "osmo_v110_ta_frame_in() returns %d\n", rc);
fprintf(stderr, "osmo_v110_ta_frame_out(): expecting all bits set to binary '1'\n");
rc = osmo_v110_ta_frame_out(ta, &df);
fprintf(stderr, "osmo_v110_ta_frame_out() returns %d\n", rc);
if (rc == 0)
v110_ta_test_dump_df(&df);
v110_ta_test_set_circuit(ta, OSMO_V110_TA_C_108, true);
v110_ta_test_set_circuit(ta, OSMO_V110_TA_C_108, false);
v110_ta_test_set_circuit(ta, OSMO_V110_TA_C_108, true);
osmo_v110_ta_free(ta);
}
static void test_conn_ta_line(void)
{
struct osmo_v110_decoded_frame df = { 0 };
struct osmo_v110_ta *ta;
int rc;
fprintf(stderr, "\n==== Running %s()\n", __func__);
ta = osmo_v110_ta_alloc(test_ctx, __func__, &v110_ta_test_cfg);
OSMO_ASSERT(ta != NULL);
/* we expect the TA FSM to be in V110_TA_ST_IDLE_READY */
v110_ta_test_set_circuit(ta, OSMO_V110_TA_C_108, true);
/* we expect the TA FSM to be in V110_TA_ST_CON_TA_TO_LINE */
fprintf(stderr, "osmo_v110_ta_frame_out(): S-/X-bits are expected to be 1 (OFF)\n");
fprintf(stderr, "osmo_v110_ta_frame_out(): D-/E-bits are all expected to be 1\n");
rc = osmo_v110_ta_frame_out(ta, &df);
fprintf(stderr, "osmo_v110_ta_frame_out() returns %d\n", rc);
if (rc == 0)
v110_ta_test_dump_df(&df);
/* TODO: test implicit sync by sending V110_TA_EV_RX_FRAME_IND */
fprintf(stderr, "osmo_v110_ta_sync_ind(): the lower layer indicates sync event\n");
osmo_v110_ta_sync_ind(ta);
fprintf(stderr, "osmo_v110_ta_frame_out(): S-/X-bits are expected to be 0 (ON)\n");
fprintf(stderr, "osmo_v110_ta_frame_out(): D-/E-bits are all expected to be 1\n");
rc = osmo_v110_ta_frame_out(ta, &df);
fprintf(stderr, "osmo_v110_ta_frame_out() returns %d\n", rc);
if (rc == 0)
v110_ta_test_dump_df(&df);
fprintf(stderr, "osmo_v110_ta_frame_in(): S-/X-bits are OFF, expect no state change\n");
v110_ta_test_init_df(&df);
v110_ta_test_dump_df(&df);
rc = osmo_v110_ta_frame_in(ta, &df);
fprintf(stderr, "osmo_v110_ta_frame_in() returns %d\n", rc);
fprintf(stderr, "osmo_v110_ta_frame_in(): S-/X-bits are ON, expect state change\n");
memset(&df.s_bits[0], V110_SX_BIT_ON, sizeof(df.s_bits));
memset(&df.x_bits[0], V110_SX_BIT_ON, sizeof(df.x_bits));
v110_ta_test_dump_df(&df);
rc = osmo_v110_ta_frame_in(ta, &df);
fprintf(stderr, "osmo_v110_ta_frame_in() returns %d\n", rc);
/* we expect the TA FSM to be in V110_TA_ST_DATA_TRANSFER */
osmo_v110_ta_free(ta);
}
static void _test_data_transfer_enter(struct osmo_v110_ta *ta)
{
struct osmo_v110_decoded_frame df;
int rc;
OSMO_ASSERT(osmo_v110_ta_get_circuit(ta, OSMO_V110_TA_C_108) == false);
/* we expect the TA FSM to be in V110_TA_ST_IDLE_READY */
v110_ta_test_set_circuit(ta, OSMO_V110_TA_C_108, true);
/* we expect the TA FSM to be in V110_TA_ST_CON_TA_TO_LINE */
fprintf(stderr, "osmo_v110_ta_sync_ind(): the lower layer indicates sync event\n");
osmo_v110_ta_sync_ind(ta);
fprintf(stderr, "osmo_v110_ta_frame_in(): S-/X-bits are ON, expect state change\n");
v110_ta_test_init_df(&df);
memset(&df.s_bits[0], V110_SX_BIT_ON, sizeof(df.s_bits));
memset(&df.x_bits[0], V110_SX_BIT_ON, sizeof(df.x_bits));
v110_ta_test_dump_df(&df);
rc = osmo_v110_ta_frame_in(ta, &df);
fprintf(stderr, "osmo_v110_ta_frame_in() returns %d\n", rc);
/* we expect the TA FSM to be in V110_TA_ST_DATA_TRANSFER */
}
static void test_data_transfer(void)
{
struct osmo_v110_decoded_frame df = { 0 };
struct osmo_v110_ta *ta;
int rc;
fprintf(stderr, "\n==== Running %s()\n", __func__);
ta = osmo_v110_ta_alloc(test_ctx, __func__, &v110_ta_test_cfg);
OSMO_ASSERT(ta != NULL);
/* we expect the TA FSM to be in V110_TA_ST_IDLE_READY */
_test_data_transfer_enter(ta);
/* we expect the TA FSM to be in V110_TA_ST_DATA_TRANSFER */
v110_ta_test_dump_circuit(ta, OSMO_V110_TA_C_106, true);
v110_ta_test_dump_circuit(ta, OSMO_V110_TA_C_107, true);
v110_ta_test_dump_circuit(ta, OSMO_V110_TA_C_109, true);
fprintf(stderr, "osmo_v110_ta_frame_out(): S-/X-bits are expected to be 0 (ON)\n");
fprintf(stderr, "osmo_v110_ta_frame_out(): E1..E3-bits are expected to be 011 (9600)\n");
fprintf(stderr, "osmo_v110_ta_frame_out(): we also expect the .tx_cb() to be called\n");
rc = osmo_v110_ta_frame_out(ta, &df);
fprintf(stderr, "osmo_v110_ta_frame_out() returns %d\n", rc);
if (rc == 0)
v110_ta_test_dump_df(&df);
fprintf(stderr, "osmo_v110_ta_frame_in(): feed that frame that we pulled out back into the TA\n");
rc = osmo_v110_ta_frame_in(ta, &df);
fprintf(stderr, "osmo_v110_ta_frame_in() returns %d\n", rc);
osmo_v110_ta_free(ta);
}
static void test_data_transfer_disc_local(void)
{
struct osmo_v110_decoded_frame df = { 0 };
struct osmo_v110_ta *ta;
int rc;
fprintf(stderr, "\n==== Running %s()\n", __func__);
ta = osmo_v110_ta_alloc(test_ctx, __func__, &v110_ta_test_cfg);
OSMO_ASSERT(ta != NULL);
/* we expect the TA FSM to be in V110_TA_ST_IDLE_READY */
_test_data_transfer_enter(ta);
/* we expect the TA FSM to be in V110_TA_ST_DATA_TRANSFER */
fprintf(stderr, "local TE initiates disconnection\n");
v110_ta_test_set_circuit(ta, OSMO_V110_TA_C_108, false);
/* we expect the TA FSM to be in V110_TA_ST_DISCONNECTING */
fprintf(stderr, "osmo_v110_ta_frame_out(): S-bits are expected to be 1 (OFF)\n");
fprintf(stderr, "osmo_v110_ta_frame_out(): X-bits are expected to be 0 (ON)\n");
fprintf(stderr, "osmo_v110_ta_frame_out(): D-bits are all expected to be 0\n");
rc = osmo_v110_ta_frame_out(ta, &df); /* TODO: what E-bits do we expect? */
fprintf(stderr, "osmo_v110_ta_frame_out() returns %d\n", rc);
if (rc == 0)
v110_ta_test_dump_df(&df);
v110_ta_test_dump_circuit(ta, OSMO_V110_TA_C_106, false);
v110_ta_test_dump_circuit(ta, OSMO_V110_TA_C_107, true);
v110_ta_test_dump_circuit(ta, OSMO_V110_TA_C_109, true);
fprintf(stderr, "osmo_v110_ta_frame_in(): S-/X-bits are ON, expect no state change\n");
v110_ta_test_init_df(&df);
memset(&df.s_bits[0], V110_SX_BIT_ON, sizeof(df.s_bits));
memset(&df.x_bits[0], V110_SX_BIT_ON, sizeof(df.x_bits));
v110_ta_test_dump_df(&df);
rc = osmo_v110_ta_frame_in(ta, &df);
fprintf(stderr, "osmo_v110_ta_frame_in() returns %d\n", rc);
fprintf(stderr, "osmo_v110_ta_frame_in(): S-bits are OFF, expect state change\n");
v110_ta_test_init_df(&df);
memset(&df.s_bits[0], V110_SX_BIT_OFF, sizeof(df.s_bits));
memset(&df.x_bits[0], V110_SX_BIT_ON, sizeof(df.x_bits));
v110_ta_test_dump_df(&df);
rc = osmo_v110_ta_frame_in(ta, &df);
fprintf(stderr, "osmo_v110_ta_frame_in() returns %d\n", rc);
/* we expect the TA FSM to be in V110_TA_ST_IDLE_READY */
v110_ta_test_dump_circuit(ta, OSMO_V110_TA_C_106, false);
v110_ta_test_dump_circuit(ta, OSMO_V110_TA_C_107, false);
v110_ta_test_dump_circuit(ta, OSMO_V110_TA_C_109, false);
osmo_v110_ta_free(ta);
}
static void test_data_transfer_disc_remote(void)
{
struct osmo_v110_decoded_frame df = { 0 };
struct osmo_v110_ta *ta;
int rc;
fprintf(stderr, "\n==== Running %s()\n", __func__);
ta = osmo_v110_ta_alloc(test_ctx, __func__, &v110_ta_test_cfg);
OSMO_ASSERT(ta != NULL);
/* we expect the TA FSM to be in V110_TA_ST_IDLE_READY */
_test_data_transfer_enter(ta);
/* we expect the TA FSM to be in V110_TA_ST_DATA_TRANSFER */
fprintf(stderr, "remote TE initiates disconnection\n");
fprintf(stderr, "osmo_v110_ta_frame_in(): S-bits are OFF, X-bits are ON\n");
fprintf(stderr, "osmo_v110_ta_frame_in(): D-bits are all set to 0\n");
v110_ta_test_init_df(&df);
memset(&df.s_bits[0], V110_SX_BIT_OFF, sizeof(df.s_bits));
memset(&df.x_bits[0], V110_SX_BIT_ON, sizeof(df.x_bits));
memset(&df.d_bits[0], 0, sizeof(df.d_bits));
v110_ta_test_dump_df(&df);
rc = osmo_v110_ta_frame_in(ta, &df);
fprintf(stderr, "osmo_v110_ta_frame_in() returns %d\n", rc);
v110_ta_test_dump_circuit(ta, OSMO_V110_TA_C_107, false);
v110_ta_test_dump_circuit(ta, OSMO_V110_TA_C_109, false);
fprintf(stderr, "local TE confirms disconnection\n");
v110_ta_test_set_circuit(ta, OSMO_V110_TA_C_108, false);
/* we expect the TA FSM to be in V110_TA_ST_DISCONNECTING */
osmo_v110_ta_desync_ind(ta);
/* we expect the TA FSM to be in V110_TA_ST_IDLE_READY */
v110_ta_test_dump_circuit(ta, OSMO_V110_TA_C_106, false);
v110_ta_test_dump_circuit(ta, OSMO_V110_TA_C_107, false);
v110_ta_test_dump_circuit(ta, OSMO_V110_TA_C_109, false);
osmo_v110_ta_free(ta);
}
static void test_syncing(void)
{
struct osmo_v110_decoded_frame df = { 0 };
struct osmo_v110_ta *ta;
int rc;
fprintf(stderr, "\n==== Running %s()\n", __func__);
ta = osmo_v110_ta_alloc(test_ctx, __func__, &v110_ta_test_cfg);
OSMO_ASSERT(ta != NULL);
/* we expect the TA FSM to be in V110_TA_ST_IDLE_READY */
_test_data_transfer_enter(ta);
/* we expect the TA FSM to be in V110_TA_ST_DATA_TRANSFER */
fprintf(stderr, "osmo_v110_ta_sync_ind(): the lower layer indicates out-of-sync event\n");
osmo_v110_ta_desync_ind(ta);
/* we expect the TA FSM to be in V110_TA_ST_RESYNCING */
fprintf(stderr, "osmo_v110_ta_frame_out(): S-bits are expected to be 0 (ON)\n");
fprintf(stderr, "osmo_v110_ta_frame_out(): X-bits are expected to be 1 (OFF)\n");
fprintf(stderr, "osmo_v110_ta_frame_out(): D-bits are to be set by .tx_cb()\n");
rc = osmo_v110_ta_frame_out(ta, &df);
fprintf(stderr, "osmo_v110_ta_frame_out() returns %d\n", rc);
if (rc == 0)
v110_ta_test_dump_df(&df);
fprintf(stderr, "osmo_v110_ta_sync_ind(): the lower layer indicates sync event\n");
osmo_v110_ta_sync_ind(ta);
/* we expect the TA FSM to be in V110_TA_ST_DATA_TRANSFER */
fprintf(stderr, "osmo_v110_ta_frame_out(): S-bits are expected to be 0 (ON)\n");
fprintf(stderr, "osmo_v110_ta_frame_out(): X-bits are expected to be 0 (ON)\n");
fprintf(stderr, "osmo_v110_ta_frame_out(): D-bits are to be set by .tx_cb()\n");
rc = osmo_v110_ta_frame_out(ta, &df);
fprintf(stderr, "osmo_v110_ta_frame_out() returns %d\n", rc);
if (rc == 0)
v110_ta_test_dump_df(&df);
osmo_v110_ta_free(ta);
}
int main(int argc, char **argv)
{
test_ctx = talloc_named_const(NULL, 0, __FILE__);
osmo_init_logging2(test_ctx, NULL);
log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_NONE);
log_set_print_level(osmo_stderr_target, 1);
log_set_print_category(osmo_stderr_target, 1);
log_set_print_category_hex(osmo_stderr_target, 0);
log_set_use_color(osmo_stderr_target, 0);
osmo_fsm_log_addr(false);
osmo_fsm_log_timeouts(true);
log_set_category_filter(osmo_stderr_target, DLGLOBAL, 1, LOGL_DEBUG);
test_idle_ready();
test_conn_ta_line();
/* TODO: test_conn_ta_line_timeout() */
test_data_transfer();
test_data_transfer_disc_local();
test_data_transfer_disc_remote();
/* TODO: test_disc_timeout() */
test_syncing();
/* TODO: test_syncing_timeout() */
log_fini();
OSMO_ASSERT(talloc_total_blocks(test_ctx) == 1);
talloc_free(test_ctx);
return 0;
}