summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--include/osmocom/gsm/protocol/gsm_12_21.h16
-rw-r--r--src/gsm/abis_nm.c155
-rw-r--r--src/gsm/libosmogsm.map5
-rw-r--r--tests/Makefile.am7
-rw-r--r--tests/abis/abis_test.c208
-rw-r--r--tests/abis/abis_test.ok41
-rw-r--r--tests/testsuite.at6
8 files changed, 437 insertions, 2 deletions
diff --git a/.gitignore b/.gitignore
index ecbceddd..4c6a78f2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -60,6 +60,7 @@ tests/package.m4
tests/testsuite
tests/testsuite.dir/
tests/testsuite.log
+tests/abis/abis_test
tests/ctrl/ctrl_test
tests/utils/utils_test
tests/stats/stats_test
diff --git a/include/osmocom/gsm/protocol/gsm_12_21.h b/include/osmocom/gsm/protocol/gsm_12_21.h
index c88f0b13..b35da447 100644
--- a/include/osmocom/gsm/protocol/gsm_12_21.h
+++ b/include/osmocom/gsm/protocol/gsm_12_21.h
@@ -29,6 +29,7 @@
/*! \file gsm_12_21.h */
#include <stdint.h>
+#include <stdbool.h>
#include <osmocom/gsm/tlv.h>
/*! \brief generic header in front of every OML message according to TS 08.59 */
@@ -791,6 +792,21 @@ enum ipac_bcch_info_type {
IPAC_BINF_CELL_ALLOC = (1 << 2),
};
+/*! \brief 3GPP TS 52.021 §9.4.62 SW Description */
+struct abis_nm_sw_desc {
+ uint8_t file_id[UINT8_MAX];
+ uint8_t file_id_len;
+
+ uint8_t file_version[UINT8_MAX];
+ uint8_t file_version_len;
+};
+
+uint16_t abis_nm_sw_desc_len(const struct abis_nm_sw_desc *sw, bool put_sw_descr);
+uint16_t abis_nm_put_sw_desc(struct msgb *msg, const struct abis_nm_sw_desc *sw, bool put_sw_descr);
+uint16_t abis_nm_put_sw_file(struct msgb *msg, const char *id, const char *ver, bool put_sw_desc);
+uint32_t abis_nm_get_sw_desc_len(const uint8_t * buf, size_t len);
+int abis_nm_get_sw_conf(const uint8_t * buf, size_t buf_len, struct abis_nm_sw_desc *sw, uint16_t sw_len);
+
struct msgb *abis_nm_fail_evt_rep(enum abis_nm_event_type t,
enum abis_nm_severity s,
enum abis_nm_pcause_type ct,
diff --git a/src/gsm/abis_nm.c b/src/gsm/abis_nm.c
index 934d7ceb..7553f84d 100644
--- a/src/gsm/abis_nm.c
+++ b/src/gsm/abis_nm.c
@@ -751,6 +751,161 @@ struct msgb *abis_nm_fail_evt_vrep(enum abis_nm_event_type t,
return nmsg;
}
+/*! \brief Compute length of given 3GPP TS 52.021 §9.4.62 SW Description.
+ * \param[in] sw SW Description struct
+ * \param[in] put_sw_descr boolean, whether to put NM_ATT_SW_DESCR IE or not
+ * \returns length of buffer space necessary to store sw
+ */
+uint16_t abis_nm_sw_desc_len(const struct abis_nm_sw_desc *sw, bool put_sw_desc)
+{
+ /* +3: type is 1-byte, length is 2-byte */
+ return (put_sw_desc ? 1 : 0) + (sw->file_id_len + 3) + (sw->file_version_len + 3);
+}
+
+/*! \brief Put given 3GPP TS 52.021 §9.4.62 SW Description into msgb.
+ * \param[out] msg message buffer
+ * \param[in] sw SW Description struct
+ * \param[in] put_sw_descr boolean, whether to put NM_ATT_SW_DESCR IE or not
+ * \returns length of buffer space necessary to store sw
+ */
+uint16_t abis_nm_put_sw_desc(struct msgb *msg, const struct abis_nm_sw_desc *sw, bool put_sw_desc)
+{
+ if (put_sw_desc)
+ msgb_v_put(msg, NM_ATT_SW_DESCR);
+
+ msgb_tl16v_put(msg, NM_ATT_FILE_ID, sw->file_id_len, sw->file_id);
+ msgb_tl16v_put(msg, NM_ATT_FILE_VERSION, sw->file_version_len, sw->file_version);
+
+ return abis_nm_sw_desc_len(sw, put_sw_desc);
+}
+
+/*! \brief Put given file ID/Version pair as 3GPP TS 52.021 §9.4.62 SW Description into msgb.
+ * \param[out] msg message buffer
+ * \param[in] id File ID part of SW Description
+ * \param[in] id File Version part of SW Description
+ * \param[in] put_sw_descr boolean, whether to put NM_ATT_SW_DESCR IE or not
+ * \returns length of buffer space necessary to store sw
+ */
+uint16_t abis_nm_put_sw_file(struct msgb *msg, const char *id, const char *ver, bool put_sw_desc)
+{
+ struct abis_nm_sw_desc sw = {
+ .file_id_len = strlen(id),
+ .file_version_len = strlen(ver),
+ };
+
+ memcpy(sw.file_id, id, sw.file_id_len);
+ memcpy(sw.file_version, ver, sw.file_version_len);
+
+ return abis_nm_put_sw_desc(msg, &sw, put_sw_desc);
+}
+
+/*! \brief Get length of first 3GPP TS 52.021 §9.4.62 SW Description from buffer.
+ * \param[in] buf buffer, may contain several SW Descriptions
+ * \param[in] len buffer length
+ * \returns length if parsing succeeded, 0 otherwise
+ */
+uint32_t abis_nm_get_sw_desc_len(const uint8_t *buf, size_t len)
+{
+ uint16_t sw = 2; /* 1-byte SW tag + 1-byte FILE_* tag */
+
+ if (buf[0] != NM_ATT_SW_DESCR)
+ sw = 1; /* 1-byte FILE_* tag */
+
+ if (buf[sw - 1] != NM_ATT_FILE_ID && buf[sw - 1] != NM_ATT_FILE_VERSION)
+ return 0;
+
+ /* + length of 1st FILE_* element + 1-byte tag + 2-byte length field of
+ 1st FILE_* element */
+ sw += (osmo_load16be(buf + sw) + 3);
+
+ /* + length of 2nd FILE_* element */
+ sw += osmo_load16be(buf + sw);
+
+ return sw + 2; /* + 2-byte length field of 2nd FILE_* element */
+}
+
+/*! \brief Parse single 3GPP TS 52.021 §9.4.62 SW Description from buffer.
+ * \param[out] sw SW Description struct
+ * \param[in] buf buffer
+ * \param[in] len buffer length
+ * \returns 0 if parsing succeeded, negative error code otherwise
+ */
+static inline int abis_nm_get_sw_desc(struct abis_nm_sw_desc *sw, const uint8_t *buf, size_t length)
+{
+ int rc;
+ uint32_t len = abis_nm_get_sw_desc_len(buf, length);
+ static struct tlv_parsed tp;
+ const struct tlv_definition sw_tlvdef = {
+ .def = {
+ [NM_ATT_SW_DESCR] = { TLV_TYPE_TV },
+ [NM_ATT_FILE_ID] = { TLV_TYPE_TL16V },
+ [NM_ATT_FILE_VERSION] = { TLV_TYPE_TL16V },
+ },
+ };
+
+ /* Basic sanity check */
+ if (len > length)
+ return -EFBIG;
+
+ /* Note: current implementation of TLV parser fails on multilpe SW Descr:
+ we will only parse the first one */
+ if (!len)
+ return -EINVAL;
+
+ /* Note: the return value is ignored here because SW Description tag
+ itself is considered optional. */
+ tlv_parse(&tp, &sw_tlvdef, buf, len, 0, 0);
+
+ /* Parsing SW Description is tricky for current implementation of TLV
+ parser which fails to properly handle TV when V has following
+ structure: | TL16V | TL16V |. Hence, the need for 2nd call: */
+ rc = tlv_parse(&tp, &sw_tlvdef, buf + TLVP_LEN(&tp, NM_ATT_SW_DESCR), len - TLVP_LEN(&tp, NM_ATT_SW_DESCR),
+ 0, 0);
+
+ if (rc < 0)
+ return rc;
+
+ if (!TLVP_PRESENT(&tp, NM_ATT_FILE_ID))
+ return -EBADF;
+
+ if (!TLVP_PRESENT(&tp, NM_ATT_FILE_VERSION))
+ return -EBADMSG;
+
+ sw->file_id_len = TLVP_LEN(&tp, NM_ATT_FILE_ID);
+ sw->file_version_len = TLVP_LEN(&tp, NM_ATT_FILE_VERSION);
+
+ memcpy(sw->file_id, TLVP_VAL(&tp, NM_ATT_FILE_ID), sw->file_id_len);
+ memcpy(sw->file_version, TLVP_VAL(&tp, NM_ATT_FILE_VERSION), sw->file_version_len);
+
+ return 0;
+}
+
+/*! \brief Parse 3GPP TS 52.021 §9.4.61 SW Configuration from buffer.
+ * \param[in] buf buffer
+ * \param[in] buf_len buffer length
+ * \param[out] sw SW Description struct array
+ * \param[in] sw_len Expected number of SW Description entries
+ * \returns 0 if parsing succeeded, negative error code otherwise
+ */
+int abis_nm_get_sw_conf(const uint8_t * buf, size_t buf_len, struct abis_nm_sw_desc *sw, uint16_t sw_len)
+{
+ int rc;
+ uint16_t len = 0, i;
+ for (i = 0; i < sw_len; i++) {
+ memset(&sw[i], 0, sizeof(sw[i]));
+ rc = abis_nm_get_sw_desc(&sw[i], buf + len, buf_len - len);
+ if (rc < 0)
+ return rc;
+
+ len += abis_nm_get_sw_desc_len(buf + len, buf_len - len);
+
+ if (len >= buf_len)
+ return i + 1;
+ }
+
+ return i;
+}
+
/*! \brief Obtain OML Channel Combination for phnsical channel config */
int abis_nm_chcomb4pchan(enum gsm_phys_chan_config pchan)
{
diff --git a/src/gsm/libosmogsm.map b/src/gsm/libosmogsm.map
index 3ac37e1a..97fd2b45 100644
--- a/src/gsm/libosmogsm.map
+++ b/src/gsm/libosmogsm.map
@@ -31,6 +31,11 @@ abis_mm_event_cause_names;
abis_nm_pcause_type_names;
abis_nm_msgtype_names;
abis_nm_att_names;
+abis_nm_sw_desc_len;
+abis_nm_put_sw_desc;
+abis_nm_put_sw_file;
+abis_nm_get_sw_conf;
+abis_nm_get_sw_desc_len;
osmo_sitype_strs;
osmo_c4;
diff --git a/tests/Makefile.am b/tests/Makefile.am
index ab80c1af..3ce76208 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -15,7 +15,7 @@ check_PROGRAMS = timer/timer_test sms/sms_test ussd/ussd_test \
bitvec/bitvec_test msgb/msgb_test bits/bitcomp_test \
tlv/tlv_test gsup/gsup_test oap/oap_test fsm/fsm_test \
write_queue/wqueue_test socket/socket_test \
- coding/coding_test
+ coding/coding_test abis/abis_test
if ENABLE_MSGFILE
check_PROGRAMS += msgfile/msgfile_test
@@ -43,6 +43,9 @@ comp128_comp128_test_LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/
auth_milenage_test_SOURCES = auth/milenage_test.c
auth_milenage_test_LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/gsm/libosmogsm.la
+abis_abis_test_SOURCES = abis/abis_test.c
+abis_abis_test_LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/gsm/libosmogsm.la
+
ctrl_ctrl_test_SOURCES = ctrl/ctrl_test.c
ctrl_ctrl_test_LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/ctrl/libosmoctrl.la
@@ -188,7 +191,7 @@ EXTRA_DIST = testsuite.at $(srcdir)/package.m4 $(TESTSUITE) \
vty/vty_test.ok comp128/comp128_test.ok \
utils/utils_test.ok stats/stats_test.ok \
bitvec/bitvec_test.ok msgb/msgb_test.ok bits/bitcomp_test.ok \
- sim/sim_test.ok tlv/tlv_test.ok \
+ sim/sim_test.ok tlv/tlv_test.ok abis/abis_test.ok \
gsup/gsup_test.ok gsup/gsup_test.err \
oap/oap_test.ok fsm/fsm_test.ok fsm/fsm_test.err \
write_queue/wqueue_test.ok socket/socket_test.ok \
diff --git a/tests/abis/abis_test.c b/tests/abis/abis_test.c
new file mode 100644
index 00000000..a303c919
--- /dev/null
+++ b/tests/abis/abis_test.c
@@ -0,0 +1,208 @@
+/*
+ * (C) 2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2017 by sysmocom s.m.f.c. GmbH <info@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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/lapdm.h>
+#include <osmocom/gsm/rsl.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <errno.h>
+#include <stdbool.h>
+#include <string.h>
+
+static struct log_info info = {};
+
+static const uint8_t simple_config[] = { 66, 18, 0, 3, 1, 2, 3, 19, 0, 3, 3, 4, 5 };
+
+static const uint8_t dual_config[] = {
+ 66, 18, 0, 3, 1, 2, 3, 19, 0, 3, 3, 4, 5,
+ 66, 18, 0, 3, 9, 7, 5, 19, 0, 3, 6, 7, 8,
+};
+
+static void test_simple_sw_config(void)
+{
+ struct abis_nm_sw_desc desc[1];
+ uint16_t len;
+ int rc;
+
+ rc = abis_nm_get_sw_conf(simple_config, ARRAY_SIZE(simple_config), &desc[0], ARRAY_SIZE(desc));
+ if (rc != 1) {
+ printf("%s(): FAILED to parse the File Id/File version: %d\n", __func__, rc);
+ abort();
+ }
+
+ len = abis_nm_sw_desc_len(&desc[0], true);
+ if (len != 13) {
+ printf("WRONG SIZE: %u\n", len);
+ abort();
+ }
+
+ printf("len: %u\n", len);
+ printf("file_id: %s\n", osmo_hexdump(desc[0].file_id, desc[0].file_id_len));
+ printf("file_ver: %s\n", osmo_hexdump(desc[0].file_version, desc[0].file_version_len));
+ printf("%s(): OK\n", __func__);
+}
+
+static void test_simple_sw_short(void)
+{
+ struct abis_nm_sw_desc desc[1];
+ int i;
+
+ for (i = 1; i < ARRAY_SIZE(simple_config); ++i) {
+ int rc = abis_nm_get_sw_conf(simple_config, ARRAY_SIZE(simple_config) - i, &desc[0], ARRAY_SIZE(desc));
+ if (rc >= 1) {
+ printf("SHOULD not have parsed: %d\n", rc);
+ abort();
+ }
+ }
+ printf("%s(): OK\n", __func__);
+}
+
+static void test_dual_sw_config(void)
+{
+ struct abis_nm_sw_desc desc[2];
+ uint16_t len0, len1;
+ int rc;
+
+ rc = abis_nm_get_sw_conf(dual_config, ARRAY_SIZE(dual_config), &desc[0], ARRAY_SIZE(desc));
+ if (rc != 2) {
+ printf("%s(): FAILED to parse the File Id/File version: %d (%d,%d)\n",
+ __func__, -rc, EBADF, EBADMSG);
+ abort();
+ }
+
+ len0 = abis_nm_sw_desc_len(&desc[0], true);
+ if (len0 != 13) {
+ printf("WRONG SIZE0: %u\n", len0);
+ abort();
+ }
+
+ len1 = abis_nm_sw_desc_len(&desc[1], true);
+ if (len1 != 13) {
+ printf("WRONG SIZE1: %u\n", len1);
+ abort();
+ }
+
+ printf("len: %u\n", len0);
+ printf("file_id: %s\n", osmo_hexdump(desc[0].file_id, desc[0].file_id_len));
+ printf("file_ver: %s\n", osmo_hexdump(desc[0].file_version, desc[0].file_version_len));
+
+ printf("len: %u\n", len1);
+ printf("file_id: %s\n", osmo_hexdump(desc[1].file_id, desc[1].file_id_len));
+ printf("file_ver: %s\n", osmo_hexdump(desc[1].file_version, desc[1].file_version_len));
+ printf("%s(): OK\n", __func__);
+}
+
+static inline void print_chk(const char *what, uint8_t len1, uint8_t len2, const uint8_t *x1, const uint8_t *x2)
+{
+ int cmp = memcmp(x1, x2, len2);
+ printf("\tFILE %s [%u == %u -> %d, %s] %d => %s\n", what, len1, len2, len1 == len2, len1 != len2 ? "fail" : "ok",
+ cmp, cmp != 0 ? "FAIL" : "OK");
+}
+
+static inline void chk_raw(const char *what, const uint8_t *data, uint16_t len)
+{
+ struct abis_nm_sw_desc sw = { 0 };
+ int res = abis_nm_get_sw_conf(data, len, &sw, 1);
+ uint16_t xlen = abis_nm_get_sw_desc_len(data, len);
+
+ printf("parsing chained %s <1st: %d, total: %d>\n\tSW Descr (%s)\n", osmo_hexdump(data, len), xlen, len, what);
+
+ if (res < 0)
+ printf("\tFAIL: %d\n", -res);
+ else {
+ printf("\tFILE ID: [%d] %s => OK\n", sw.file_id_len, osmo_hexdump(sw.file_id, sw.file_id_len));
+ printf("\tFILE VERSION: [%d] %s => OK\n", sw.file_version_len,
+ osmo_hexdump(sw.file_version, sw.file_version_len));
+ }
+
+ if (len != xlen)
+ chk_raw(" 2nd", data + xlen, len - xlen);
+}
+
+static inline void chk_descr(struct msgb *msg, const char *f_id, const char *f_ver, const char *desc, bool header)
+{
+ int res;
+ uint16_t len;
+ struct abis_nm_sw_desc sw = { 0 }, put = {
+ .file_id_len = strlen(f_id),
+ .file_version_len = strlen(f_ver),
+ };
+
+ memcpy(put.file_id, f_id, put.file_id_len);
+ memcpy(put.file_version, f_ver, put.file_version_len);
+ len = abis_nm_put_sw_file(msg, f_id, f_ver, header);
+
+ printf("msgb[%u] :: {msgb->len} %u == %u {len} - %s]:\n\tSW DESCR (%s)\n"
+ "\tlength: {extracted} %u = %u {expected} - %s, failsafe - %s\n",
+ msg->data_len, msg->len, len, len != msg->len ? "fail" : "ok", desc,
+ abis_nm_get_sw_desc_len(msgb_data(msg), msg->len), msg->len,
+ abis_nm_get_sw_desc_len(msgb_data(msg), msg->len) != msg->len ? "FAIL" : "OK",
+ len > put.file_version_len + put.file_id_len ? "OK" : "FAIL");
+
+ res = abis_nm_get_sw_conf(msgb_data(msg), msg->len, &sw, 1);
+ if (res < 0)
+ printf("\tSW DESCR (%s) parsing error code %d!\n", desc, -res);
+ else {
+ print_chk("ID", sw.file_id_len, put.file_id_len, sw.file_id, put.file_id);
+ print_chk("VERSION", sw.file_version_len, put.file_version_len, sw.file_version, put.file_version);
+ }
+}
+
+static void test_sw_descr()
+{
+ const char *f_id = "TEST.L0L", *f_ver = "0.1.666~deadbeeffacefeed-dirty";
+ uint8_t chain[] = { 0x42, 0x12, 0x00, 0x03, 0x01, 0x02, 0x03, 0x13, 0x00, 0x03, 0x03, 0x04, 0x05, 0x42, 0x12,
+ 0x00, 0x03, 0x09, 0x07, 0x05, 0x13, 0x00, 0x03, 0x06, 0x07, 0x08 };
+ struct msgb *msg = msgb_alloc_headroom(4096, 128, "sw");
+
+ printf("Testing SW Description (de)serialization...\n");
+
+ /* check that parsing |SW|ID|VER| works: */
+ chk_descr(msg, f_id, f_ver, "with header", true);
+ msgb_reset(msg);
+
+ /* check that parsing |ID|VER| works: */
+ chk_descr(msg, f_id, f_ver, "without header", false);
+
+ /* check that parsing |ID|VER|SW|ID|VER| fails - notice the lack of msgb_reset() to create bogus msgb data: */
+ chk_descr(msg, f_id, f_ver, "expected failure", true);
+
+ /* check multiple, chained SW-descr: */
+ chk_raw("half", chain, sizeof(chain) / 2);
+ chk_raw("full", chain, sizeof(chain));
+}
+
+int main(int argc, char **argv)
+{
+ osmo_init_logging(&info);
+
+ test_sw_descr();
+ test_simple_sw_config();
+ test_simple_sw_short();
+ test_dual_sw_config();
+
+ printf("OK.\n");
+
+ return 0;
+}
diff --git a/tests/abis/abis_test.ok b/tests/abis/abis_test.ok
new file mode 100644
index 00000000..e6b626be
--- /dev/null
+++ b/tests/abis/abis_test.ok
@@ -0,0 +1,41 @@
+Testing SW Description (de)serialization...
+msgb[4096] :: {msgb->len} 45 == 45 {len} - ok]:
+ SW DESCR (with header)
+ length: {extracted} 45 = 45 {expected} - OK, failsafe - OK
+ FILE ID [8 == 8 -> 1, ok] 0 => OK
+ FILE VERSION [30 == 30 -> 1, ok] 0 => OK
+msgb[4096] :: {msgb->len} 44 == 44 {len} - ok]:
+ SW DESCR (without header)
+ length: {extracted} 44 = 44 {expected} - OK, failsafe - OK
+ FILE ID [8 == 8 -> 1, ok] 0 => OK
+ FILE VERSION [30 == 30 -> 1, ok] 0 => OK
+msgb[4096] :: {msgb->len} 89 == 45 {len} - fail]:
+ SW DESCR (expected failure)
+ length: {extracted} 44 = 89 {expected} - FAIL, failsafe - OK
+ FILE ID [8 == 8 -> 1, ok] 0 => OK
+ FILE VERSION [30 == 30 -> 1, ok] 0 => OK
+parsing chained 42 12 00 03 01 02 03 13 00 03 03 04 05 <1st: 13, total: 13>
+ SW Descr (half)
+ FILE ID: [3] 01 02 03 => OK
+ FILE VERSION: [3] 03 04 05 => OK
+parsing chained 42 12 00 03 01 02 03 13 00 03 03 04 05 42 12 00 03 09 07 05 13 00 03 06 07 08 <1st: 13, total: 26>
+ SW Descr (full)
+ FILE ID: [3] 01 02 03 => OK
+ FILE VERSION: [3] 03 04 05 => OK
+parsing chained 42 12 00 03 09 07 05 13 00 03 06 07 08 <1st: 13, total: 13>
+ SW Descr ( 2nd)
+ FILE ID: [3] 09 07 05 => OK
+ FILE VERSION: [3] 06 07 08 => OK
+len: 13
+file_id: 01 02 03
+file_ver: 03 04 05
+test_simple_sw_config(): OK
+test_simple_sw_short(): OK
+len: 13
+file_id: 01 02 03
+file_ver: 03 04 05
+len: 13
+file_id: 09 07 05
+file_ver: 06 07 08
+test_dual_sw_config(): OK
+OK.
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 64df7249..7ee01645 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -9,6 +9,12 @@ cat $abs_srcdir/a5/a5_test.ok > expout
AT_CHECK([$abs_top_builddir/tests/a5/a5_test], [0], [expout])
AT_CLEANUP
+AT_SETUP([abis])
+AT_KEYWORDS([abis])
+cat $abs_srcdir/abis/abis_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/abis/abis_test], [0], [expout], [ignore])
+AT_CLEANUP
+
AT_SETUP([ctrl])
AT_KEYWORDS([ctrl])
cat $abs_srcdir/ctrl/ctrl_test.ok > expout