summaryrefslogtreecommitdiffstats
path: root/epan/dissectors/packet-osmo_trx.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--epan/dissectors/packet-osmo_trx.c1004
1 files changed, 1004 insertions, 0 deletions
diff --git a/epan/dissectors/packet-osmo_trx.c b/epan/dissectors/packet-osmo_trx.c
new file mode 100644
index 00000000..b6ac3b41
--- /dev/null
+++ b/epan/dissectors/packet-osmo_trx.c
@@ -0,0 +1,1004 @@
+/* packet-osmo_trx.c
+ * Dissector for OsmoTRX Protocol (GSM Transceiver control and data).
+ *
+ * (C) 2018 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2019 by Vadim Yanitskiy <axilirator@gmail.com>
+ * (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "config.h"
+
+#include <epan/packet.h>
+#include <epan/expert.h>
+#include <epan/unit_strings.h>
+
+/* This is a non-standard, ad-hoc protocol to pass baseband GSM bursts between
+ * the transceiver (such as osmo-trx, fake_trx.py or grgsm_trx) and the L1
+ * program (such as osmo-bts-trx or trxcon). Osmocom inherited this protocol
+ * when forking OsmoTRX off the OpenBTS "Transceiver" program. */
+
+void proto_register_osmo_trx(void);
+void proto_reg_handoff_osmo_trx(void);
+
+static dissector_handle_t otrxd_handle;
+static dissector_handle_t otrxc_handle;
+
+/* Which kind of message it is */
+static int proto_otrxd = -1;
+static int proto_otrxc = -1;
+
+/* Generated fields */
+static int hf_otrxd_burst_dir = -1;
+static int hf_otrxc_msg_dir = -1;
+
+/* TRXD PDU version */
+static int hf_otrxd_pdu_ver = -1;
+
+/* TRXD common fields */
+static int hf_otrxd_chdr_reserved = -1;
+static int hf_otrxd_shadow_ind = -1;
+static int hf_otrxd_batch_ind = -1;
+static int hf_otrxd_trx_num = -1;
+static int hf_otrxd_tdma_tn = -1;
+static int hf_otrxd_tdma_fn = -1;
+
+/* MTS (Modulation and Training Sequence) fields */
+static int hf_otrxd_nope_ind = -1;
+static int hf_otrxd_nope_ind_pad = -1;
+static int hf_otrxd_mod_2b = -1; /* 2 bit field */
+static int hf_otrxd_mod_3b = -1; /* 3 bit field */
+static int hf_otrxd_mod_4b = -1; /* 4 bit field */
+static int hf_otrxd_tsc_set_x4 = -1;
+static int hf_otrxd_tsc_set_x2 = -1;
+static int hf_otrxd_tsc = -1;
+
+/* TRXD Rx header fields */
+static int hf_otrxd_rssi = -1;
+static int hf_otrxd_toa256 = -1;
+static int hf_otrxd_ci = -1;
+
+/* TRXD Tx header fields */
+static int hf_otrxd_tx_att = -1;
+static int hf_otrxd_tx_scpir = -1;
+static int hf_otrxd_tx_rfu = -1;
+
+/* Burst soft (255 .. 0) / hard (1 or 0) bits */
+static int hf_otrxd_soft_symbols = -1;
+static int hf_otrxd_hard_symbols = -1;
+static int hf_otrxd_burst_pad = -1;
+
+/* TRXC - Control and Clock protocol */
+static int hf_otrxc_type = -1;
+static int hf_otrxc_delimiter = -1;
+static int hf_otrxc_verb = -1;
+static int hf_otrxc_params = -1;
+static int hf_otrxc_status = -1;
+
+static gint ett_otrxd = -1;
+static gint ett_otrxc = -1;
+
+static gint ett_otrxd_rx_pdu = -1;
+static gint ett_otrxd_tx_pdu = -1;
+
+static expert_field ei_otrxd_unknown_pdu_ver = EI_INIT;
+static expert_field ei_otrxd_injected_msg = EI_INIT;
+static expert_field ei_otrxd_unknown_dir = EI_INIT;
+static expert_field ei_otrxd_tail_octets = EI_INIT;
+
+static expert_field ei_otrxc_unknown_msg_type = EI_INIT;
+static expert_field ei_otrxc_bad_delimiter = EI_INIT;
+static expert_field ei_otrxc_rsp_no_code = EI_INIT;
+static expert_field ei_otrxc_injected_msg = EI_INIT;
+static expert_field ei_otrxc_unknown_dir = EI_INIT;
+
+/* Custom units */
+static const unit_name_string otrx_units_toa256 = { " (1/256 of a symbol)", NULL };
+
+/* TRXD SHADOW.ind value description */
+static const true_false_string otrxd_shadow_bool_val = {
+ "This is a shadow PDU",
+ "This is a primary PDU",
+};
+
+/* TRXD BATCH.ind value description */
+static const true_false_string otrxd_batch_bool_val = {
+ "Another PDU follows",
+ "This is the last PDU",
+};
+
+/* TRXD NOPE.{ind,req} value description */
+static const true_false_string otrxd_nope_bool_val = {
+ "Burst is not present",
+ "Burst is present",
+};
+
+/* TRXD modulation types (2 bit field) */
+static const value_string otrxd_mod_2b_vals[] = {
+ /* .00xx... */ { 0x00, "GMSK" },
+ /* .11xx... */ { 0x03, "AQPSK" },
+ { 0, NULL },
+};
+
+/* TRXD modulation types (3 bit field) */
+static const value_string otrxd_mod_3b_vals[] = {
+ /* .010x... */ { 0x02, "8-PSK" },
+ /* .100x... */ { 0x04, "16QAM" },
+ /* .101x... */ { 0x05, "32QAM" },
+ { 0, NULL },
+};
+
+/* TRXD modulation types (4 bit field) */
+static const value_string otrxd_mod_4b_vals[] = {
+ /* .0110... */ { 0x06, "GMSK (Access Burst)" },
+ /* .0111... */ { 0x07, "RFU (Reserved for Future Use)" },
+ { 0, NULL },
+};
+
+/* TRXD modulation type */
+enum otrxd_mod_type {
+ OTRXD_MOD_T_GMSK = 0x00,
+ OTRXD_MOD_T_8PSK = 0x02,
+ OTRXD_MOD_T_AQPSK = 0x03,
+ OTRXD_MOD_T_16QAM = 0x04,
+ OTRXD_MOD_T_32QAM = 0x05,
+ OTRXD_MOD_T_GMSK_AB = 0x06,
+ OTRXD_MOD_T_RFU = 0x07,
+};
+
+/* See 3GPP TS 45.002, section 5.2 "Bursts" */
+#define GMSK_BURST_LEN 148
+
+/* TRXD modulation / burst length mapping */
+static const guint16 otrxd_burst_len[] = {
+ [OTRXD_MOD_T_GMSK] = GMSK_BURST_LEN * 1,
+ [OTRXD_MOD_T_GMSK_AB] = GMSK_BURST_LEN * 1,
+ [OTRXD_MOD_T_AQPSK] = GMSK_BURST_LEN * 2,
+ [OTRXD_MOD_T_8PSK] = GMSK_BURST_LEN * 3,
+ [OTRXD_MOD_T_16QAM] = GMSK_BURST_LEN * 4,
+ [OTRXD_MOD_T_32QAM] = GMSK_BURST_LEN * 5,
+ [OTRXD_MOD_T_RFU] = 0, /* unknown */
+};
+
+/* RSSI is encoded without a negative sign, so we need to show it */
+static void format_rssi(gchar *buf, const guint32 rssi)
+{
+ snprintf(buf, ITEM_LABEL_LENGTH, "-%u%s", rssi, unit_name_string_get_value(rssi, &units_dbm));
+}
+
+/* TSC (Training Sequence Code) set number in 3GPP TS 45.002 starts
+ * from 1, while 'on the wire' it's encoded as X - 1 (starts from 0). */
+static void format_tsc_set(gchar *buf, guint32 tsc_set)
+{
+ snprintf(buf, ITEM_LABEL_LENGTH, "%u", tsc_set + 1);
+}
+
+/* Message direction */
+enum otrxcd_dir_type {
+ OTRXCD_DIR_UNKNOWN = 0,
+ OTRXCD_DIR_L12TRX,
+ OTRXCD_DIR_TRX2L1,
+};
+
+static const value_string otrxcd_dir_vals[] = {
+ { OTRXCD_DIR_UNKNOWN, "Unknown" },
+ { OTRXCD_DIR_L12TRX, "L1 -> TRX" },
+ { OTRXCD_DIR_TRX2L1, "TRX -> L1" },
+ { 0, NULL },
+};
+
+/* Determine message direction (L1 to TRX, or TRX to L1?) */
+static enum otrxcd_dir_type otrxcd_get_dir(const packet_info *pinfo)
+{
+ if (pinfo->srcport - pinfo->destport == 100)
+ return OTRXCD_DIR_L12TRX;
+ else if (pinfo->destport - pinfo->srcport == 100)
+ return OTRXCD_DIR_TRX2L1;
+ else
+ return OTRXCD_DIR_UNKNOWN;
+}
+
+/* Guess message direction (L1 to TRX, or TRX to L1?) */
+static enum otrxcd_dir_type otrxcd_guess_dir(const packet_info *pinfo)
+{
+ /* TODO: srcport can be also used for guessing,
+ * TODO: use port numbers from protocol preferences. */
+ switch (pinfo->destport) {
+ /* OsmoTRXD: Tx burst (L1 -> TRX) */
+ case 5702: case 5704: case 6702:
+ return OTRXCD_DIR_L12TRX;
+ /* OsmoTRXD: Rx burst (TRX -> L1) */
+ case 5802: case 5804: case 6802:
+ return OTRXCD_DIR_TRX2L1;
+ /* OsmoTRXC: Command (L1 -> TRX) */
+ case 5701: case 5703: case 6701:
+ return OTRXCD_DIR_L12TRX;
+ /* OsmoTRXC: Response or Indication (TRX -> L1) */
+ case 5801: case 5803: case 6801:
+ case 5800: case 6800:
+ return OTRXCD_DIR_TRX2L1;
+ default:
+ return OTRXCD_DIR_UNKNOWN;
+ }
+}
+
+/* TRXC message types */
+enum otrxc_msg_type {
+ OTRXC_MSG_TYPE_UNKNOWN = 0,
+ OTRXC_MSG_TYPE_COMMAND,
+ OTRXC_MSG_TYPE_RESPONSE,
+ OTRXC_MSG_TYPE_INDICATION,
+};
+
+static const value_string otrxc_msg_type_enc[] = {
+ { OTRXC_MSG_TYPE_COMMAND, "CMD" },
+ { OTRXC_MSG_TYPE_RESPONSE, "RSP" },
+ { OTRXC_MSG_TYPE_INDICATION, "IND" },
+ { 0, NULL },
+};
+
+static const value_string otrxc_msg_type_desc[] = {
+ { OTRXC_MSG_TYPE_COMMAND, "Command" },
+ { OTRXC_MSG_TYPE_RESPONSE, "Response" },
+ { OTRXC_MSG_TYPE_INDICATION, "Indication" },
+ { 0, NULL },
+};
+
+/* TRXD PDU information */
+struct otrxd_pdu_info {
+ /* PDU version */
+ guint32 ver;
+ /* BATCH.ind marker */
+ gboolean batch;
+ /* SHADOW.ind marker */
+ gboolean shadow;
+ /* Number of batched PDUs */
+ guint32 num_pdus;
+ /* TRX (RF channel) number */
+ guint32 trx_num;
+ /* TDMA frame number */
+ guint32 fn;
+ /* TDMA timeslot number */
+ guint32 tn;
+ /* NOPE.{ind,req} marker */
+ gboolean nope;
+ /* Modulation type and string */
+ enum otrxd_mod_type mod;
+ const gchar *mod_str;
+ /* Training Sequence Code */
+ guint32 tsc;
+};
+
+/* Dissector for common Rx/Tx TRXDv0/v1 header part */
+static void dissect_otrxd_chdr_v0(tvbuff_t *tvb, packet_info *pinfo _U_,
+ proto_item *ti, proto_tree *tree,
+ struct otrxd_pdu_info *pi,
+ int *offset)
+{
+ proto_tree_add_item(tree, hf_otrxd_chdr_reserved, tvb,
+ *offset, 1, ENC_NA);
+ proto_tree_add_item_ret_uint(tree, hf_otrxd_tdma_tn, tvb,
+ *offset, 1, ENC_NA, &pi->tn);
+ *offset += 1;
+
+ /* TDMA frame number (4 octets, big endian) */
+ proto_tree_add_item_ret_uint(tree, hf_otrxd_tdma_fn, tvb,
+ *offset, 4, ENC_BIG_ENDIAN, &pi->fn);
+ *offset += 4;
+
+ proto_item_append_text(ti, "TDMA FN %07u TN %u", pi->fn, pi->tn);
+}
+
+/* Dissector for MTS (Modulation and Training Sequence) */
+static void dissect_otrxd_mts(tvbuff_t *tvb, proto_tree *tree,
+ struct otrxd_pdu_info *pi,
+ int offset)
+{
+ /* NOPE indication contains no MTS information.
+ *
+ * | 7 6 5 4 3 2 1 0 | Bit numbers (value range)
+ * | X . . . . . . . | NOPE / IDLE indication
+ * | . X X X X . . . | MTS (Modulation and Training Sequence)
+ * | . . . . . X X X | TSC (Training Sequence Code)
+ */
+ proto_tree_add_item_ret_boolean(tree, hf_otrxd_nope_ind, tvb,
+ offset, 1, ENC_NA, &pi->nope);
+ if (pi->nope) {
+ proto_tree_add_item(tree, hf_otrxd_nope_ind_pad, tvb, offset, 1, ENC_NA);
+ return;
+ }
+
+ /* MTS (Modulation and Training Sequence info).
+ *
+ * | 7 6 5 4 3 2 1 0 | Bit numbers (value range)
+ * | . 0 0 X X . . . | GMSK, 4 TSC sets (0..3)
+ * | . 0 1 0 X . . . | 8-PSK, 2 TSC sets (0..1)
+ * | . 0 1 1 0 . . . | GMSK, Packet Access Burst
+ * | . 0 1 1 1 . . . | RFU (Reserved for Future Use)
+ * | . 1 0 0 X . . . | 16QAM, 2 TSC sets (0..1)
+ * | . 1 0 1 X . . . | 32QAM, 2 TSC sets (0..1)
+ * | . 1 1 X X . . . | AQPSK, 4 TSC sets (0..3)
+ *
+ * NOTE: 3GPP defines 4 TSC sets for both GMSK and AQPSK.
+ */
+ guint8 mts = tvb_get_guint8(tvb, offset);
+ if ((mts >> 5) == 0x00 || (mts >> 5) == 0x03) { /* 2 bit: GMSK (0) or AQPSK (3) */
+ pi->mod = (enum otrxd_mod_type) (mts >> 5);
+ pi->mod_str = val_to_str(mts >> 5, otrxd_mod_2b_vals, "Unknown 0x%02x");
+ proto_tree_add_item(tree, hf_otrxd_mod_2b, tvb, offset, 1, ENC_NA);
+ proto_tree_add_item(tree, hf_otrxd_tsc_set_x4, tvb, offset, 1, ENC_NA);
+ } else if ((mts >> 4) != 0x03) { /* 3 bit: 8-PSK, 16QAM, or 32QAM */
+ pi->mod = (enum otrxd_mod_type) (mts >> 4);
+ pi->mod_str = val_to_str(mts >> 4, otrxd_mod_3b_vals, "Unknown 0x%02x");
+ proto_tree_add_item(tree, hf_otrxd_mod_3b, tvb, offset, 1, ENC_NA);
+ proto_tree_add_item(tree, hf_otrxd_tsc_set_x2, tvb, offset, 1, ENC_NA);
+ } else { /* 4 bit (without TSC set): GMSK (Packet Access Burst) or RFU */
+ pi->mod = (enum otrxd_mod_type) (mts >> 3);
+ pi->mod_str = val_to_str(mts >> 3, otrxd_mod_4b_vals, "Unknown 0x%02x");
+ proto_tree_add_item(tree, hf_otrxd_mod_4b, tvb, offset, 1, ENC_NA);
+ }
+
+ proto_tree_add_item_ret_uint(tree, hf_otrxd_tsc, tvb, offset, 1, ENC_NA, &pi->tsc);
+}
+
+/* Dissector for Rx TRXD header version 0 */
+static int dissect_otrxd_rx_hdr_v0(tvbuff_t *tvb, packet_info *pinfo,
+ proto_item *ti, proto_tree *tree,
+ struct otrxd_pdu_info *pi,
+ int offset)
+{
+ dissect_otrxd_chdr_v0(tvb, pinfo, ti, tree, pi, &offset);
+
+ proto_tree_add_item(tree, hf_otrxd_rssi, tvb, offset++, 1, ENC_NA);
+ proto_tree_add_item(tree, hf_otrxd_toa256, tvb, offset, 2, ENC_NA);
+ offset += 2;
+
+ return offset;
+}
+
+/* Dissector for Rx TRXD header version 1 */
+static int dissect_otrxd_rx_hdr_v1(tvbuff_t *tvb, packet_info *pinfo,
+ proto_item *ti, proto_tree *tree,
+ struct otrxd_pdu_info *pi,
+ int offset)
+{
+ /* Dissect V0 specific part first */
+ offset = dissect_otrxd_rx_hdr_v0(tvb, pinfo, ti, tree, pi, offset);
+
+ /* MTS (Modulation and Training Sequence) */
+ dissect_otrxd_mts(tvb, tree, pi, offset++);
+ if (!pi->nope)
+ proto_item_append_text(ti, ", Modulation %s, TSC %u", pi->mod_str, pi->tsc);
+ else
+ proto_item_append_text(ti, ", NOPE.ind");
+
+ /* C/I (Carrier to Interference ratio) */
+ proto_tree_add_item(tree, hf_otrxd_ci, tvb, offset, 2, ENC_NA);
+ offset += 2;
+
+ return offset;
+}
+
+/* Dissector for TRXD Rx header version 2 */
+static int dissect_otrxd_rx_hdr_v2(tvbuff_t *tvb, packet_info *pinfo _U_,
+ proto_item *ti, proto_tree *tree,
+ struct otrxd_pdu_info *pi,
+ int offset)
+{
+ proto_tree_add_item(tree, hf_otrxd_chdr_reserved, tvb, offset, 1, ENC_NA);
+ proto_tree_add_item_ret_uint(tree, hf_otrxd_tdma_tn, tvb,
+ offset, 1, ENC_NA, &pi->tn);
+ offset += 1;
+
+ proto_tree_add_item_ret_boolean(tree, hf_otrxd_batch_ind, tvb,
+ offset, 1, ENC_NA, &pi->batch);
+ proto_tree_add_item_ret_boolean(tree, hf_otrxd_shadow_ind, tvb,
+ offset, 1, ENC_NA, &pi->shadow);
+ proto_tree_add_item_ret_uint(tree, hf_otrxd_trx_num, tvb,
+ offset, 1, ENC_NA, &pi->trx_num);
+ offset += 1;
+
+ /* MTS (Modulation and Training Sequence) */
+ dissect_otrxd_mts(tvb, tree, pi, offset++);
+
+ /* RSSI (Received Signal Strength Indication) */
+ proto_tree_add_item(tree, hf_otrxd_rssi, tvb, offset++, 1, ENC_NA);
+
+ /* ToA256 (Timing of Arrival) and C/I (Carrier to Interference ratio) */
+ proto_tree_add_item(tree, hf_otrxd_toa256, tvb, offset, 2, ENC_BIG_ENDIAN);
+ proto_tree_add_item(tree, hf_otrxd_ci, tvb, offset + 2, 2, ENC_BIG_ENDIAN);
+ offset += 4;
+
+ /* TDMA frame number (absent in additional PDUs) */
+ if (pi->num_pdus == 0) {
+ proto_tree_add_item_ret_uint(tree, hf_otrxd_tdma_fn, tvb,
+ offset, 4, ENC_BIG_ENDIAN, &pi->fn);
+ offset += 4;
+ }
+
+ proto_item_append_text(ti, "TRXN %02u, TDMA FN %07u TN %u", pi->trx_num, pi->fn, pi->tn);
+ if (!pi->nope)
+ proto_item_append_text(ti, ", Modulation %s, TSC %u", pi->mod_str, pi->tsc);
+ else
+ proto_item_append_text(ti, ", NOPE.ind");
+
+ return offset;
+}
+
+/* Burst data in Receive direction */
+static int dissect_otrxd_rx(tvbuff_t *tvb, packet_info *pinfo,
+ proto_item *pti, proto_tree *ptree,
+ struct otrxd_pdu_info *pi,
+ int offset)
+{
+ int start, burst_len, padding;
+ proto_tree *tree;
+ proto_item *ti;
+
+loop:
+ /* Add a sub-tree for each PDU (length is set below) */
+ tree = proto_tree_add_subtree(ptree, tvb, offset, -1,
+ ett_otrxd_rx_pdu, &ti,
+ "TRXD Rx PDU: ");
+ start = offset;
+
+ /* Parse version specific TRXD header part */
+ switch (pi->ver) {
+ case 0:
+ offset = dissect_otrxd_rx_hdr_v0(tvb, pinfo, ti, tree, pi, offset);
+ /* The remaining octets is basically soft-bits of the burst */
+ burst_len = tvb_reported_length(tvb) - offset;
+ /* ... there must be at least 148 soft-bits */
+ if (burst_len < GMSK_BURST_LEN)
+ burst_len = GMSK_BURST_LEN; /* let it crash! */
+ /* ... there can be 2 optional padding octets in the end */
+ padding = burst_len % GMSK_BURST_LEN;
+ proto_tree_add_item(tree, hf_otrxd_soft_symbols, tvb,
+ offset, burst_len - padding, ENC_NA);
+ offset += burst_len - padding;
+ if (padding == 0)
+ break;
+ proto_tree_add_item(tree, hf_otrxd_burst_pad, tvb,
+ offset, padding, ENC_NA);
+ offset += padding;
+ break;
+ case 1:
+ offset = dissect_otrxd_rx_hdr_v1(tvb, pinfo, ti, tree, pi, offset);
+ if (pi->nope) /* NOPE.ind contains no burst */
+ break;
+ burst_len = otrxd_burst_len[pi->mod];
+ proto_tree_add_item(tree, hf_otrxd_soft_symbols, tvb,
+ offset, burst_len, ENC_NA);
+ offset += burst_len;
+ break;
+ case 2:
+ offset = dissect_otrxd_rx_hdr_v2(tvb, pinfo, ti, tree, pi, offset);
+ if (pi->nope) /* NOPE.ind contains no burst */
+ break;
+ burst_len = otrxd_burst_len[pi->mod];
+ proto_tree_add_item(tree, hf_otrxd_soft_symbols, tvb,
+ offset, burst_len, ENC_NA);
+ offset += burst_len;
+ break;
+ default:
+ expert_add_info_format(pinfo, pti, &ei_otrxd_unknown_pdu_ver,
+ "Unknown TRXD PDU version %u", pi->ver);
+ offset = 1; /* Only the PDU version was parsed */
+ return offset;
+ }
+
+ proto_item_set_len(ti, offset - start);
+
+ /* Number of processed PDUs */
+ pi->num_pdus += 1;
+
+ /* There can be additional 'batched' PDUs */
+ if (pi->batch)
+ goto loop;
+
+ return offset;
+}
+
+/* Dissector for TRXDv0/v1 Tx burst */
+static void dissect_otrxd_tx_burst_v0(tvbuff_t *tvb, packet_info *pinfo _U_,
+ proto_item *ti, proto_tree *tree,
+ struct otrxd_pdu_info *pi,
+ int *offset)
+{
+ /* Calculate the burst length */
+ const int burst_len = tvb_reported_length(tvb) - *offset;
+
+ /* Attempt to guess modulation by the length */
+ switch (burst_len) {
+ /* We may also have NOPE.req in the future (to drive fake_trx.py) */
+ case 0:
+ proto_item_append_text(ti, ", NOPE.req");
+ pi->nope = TRUE;
+ return;
+
+ /* TODO: introduce an enumerated type, detect other modulation types,
+ * TODO: add a generated field for "osmo_trxd.mod" */
+ case GMSK_BURST_LEN:
+ proto_item_append_text(ti, ", Modulation GMSK");
+ pi->mod_str = "GMSK";
+ break;
+ case 3 * GMSK_BURST_LEN:
+ proto_item_append_text(ti, ", Modulation 8-PSK");
+ pi->mod_str = "8-PSK";
+ break;
+ }
+
+ /* Hard-bits (1 or 0) */
+ proto_tree_add_item(tree, hf_otrxd_hard_symbols, tvb,
+ *offset, burst_len, ENC_NA);
+ *offset += burst_len;
+}
+
+/* Dissector for TRXD Tx header version 2 */
+static void dissect_otrxd_tx_hdr_v2(tvbuff_t *tvb, packet_info *pinfo _U_,
+ proto_item *ti, proto_tree *tree,
+ struct otrxd_pdu_info *pi,
+ int *offset)
+{
+ proto_tree_add_item(tree, hf_otrxd_chdr_reserved, tvb, *offset, 1, ENC_NA);
+ proto_tree_add_item_ret_uint(tree, hf_otrxd_tdma_tn, tvb,
+ *offset, 1, ENC_NA, &pi->tn);
+ *offset += 1;
+
+ proto_tree_add_item_ret_boolean(tree, hf_otrxd_batch_ind, tvb,
+ *offset, 1, ENC_NA, &pi->batch);
+ proto_tree_add_item_ret_uint(tree, hf_otrxd_trx_num, tvb,
+ *offset, 1, ENC_NA, &pi->trx_num);
+ *offset += 1;
+
+ /* MTS (Modulation and Training Sequence) */
+ dissect_otrxd_mts(tvb, tree, pi, *offset);
+ *offset += 1;
+
+ /* Tx power attenuation */
+ proto_tree_add_item(tree, hf_otrxd_tx_att, tvb, *offset, 1, ENC_NA);
+ *offset += 1;
+
+ /* SCPIR (Subchannel Power Imbalance Ratio) */
+ proto_tree_add_item(tree, hf_otrxd_tx_scpir, tvb, *offset, 1, ENC_NA);
+ *offset += 1;
+
+ /* RFU (currently just to make the header dword-alignment) */
+ proto_tree_add_item(tree, hf_otrxd_tx_rfu, tvb, *offset, 3, ENC_NA);
+ *offset += 3;
+
+ /* TDMA frame number (absent in additional PDUs) */
+ if (pi->num_pdus == 0) {
+ proto_tree_add_item_ret_uint(tree, hf_otrxd_tdma_fn, tvb,
+ *offset, 4, ENC_BIG_ENDIAN, &pi->fn);
+ *offset += 4;
+ }
+
+ proto_item_append_text(ti, "TRXN %02u, TDMA FN %07u TN %u", pi->trx_num, pi->fn, pi->tn);
+ if (!pi->nope)
+ proto_item_append_text(ti, ", Modulation %s, TSC %u", pi->mod_str, pi->tsc);
+ else
+ proto_item_append_text(ti, ", NOPE.req");
+}
+
+/* Burst data in Transmit direction */
+static int dissect_otrxd_tx(tvbuff_t *tvb, packet_info *pinfo,
+ proto_item *pti, proto_tree *ptree,
+ struct otrxd_pdu_info *pi,
+ int offset)
+{
+ proto_tree *tree;
+ proto_item *ti;
+ int burst_len;
+ int start;
+
+loop:
+ /* Add a sub-tree for each PDU (length is set below) */
+ tree = proto_tree_add_subtree(ptree, tvb, offset, -1,
+ ett_otrxd_tx_pdu, &ti,
+ "TRXD Tx PDU: ");
+ start = offset;
+
+ switch (pi->ver) {
+ /* Both versions feature the same PDU format */
+ case 0:
+ case 1:
+ dissect_otrxd_chdr_v0(tvb, pinfo, ti, tree, pi, &offset);
+ proto_tree_add_item(tree, hf_otrxd_tx_att, tvb, offset++, 1, ENC_NA);
+ dissect_otrxd_tx_burst_v0(tvb, pinfo, ti, tree, pi, &offset);
+ break;
+ case 2:
+ dissect_otrxd_tx_hdr_v2(tvb, pinfo, ti, tree, pi, &offset);
+ if (pi->nope) /* NOPE.ind contains no burst */
+ break;
+ burst_len = otrxd_burst_len[pi->mod];
+ proto_tree_add_item(tree, hf_otrxd_hard_symbols, tvb,
+ offset, burst_len, ENC_NA);
+ offset += burst_len;
+ break;
+ default:
+ expert_add_info_format(pinfo, pti, &ei_otrxd_unknown_pdu_ver,
+ "Unknown TRXD PDU version %u", pi->ver);
+ offset = 1; /* Only the PDU version was parsed */
+ return offset;
+ }
+
+ proto_item_set_len(ti, offset - start);
+
+ /* Number of processed PDUs */
+ pi->num_pdus += 1;
+
+ /* There can be additional 'batched' PDUs */
+ if (pi->batch)
+ goto loop;
+
+ return offset;
+}
+
+/* Common dissector for bursts in both directions */
+static int dissect_otrxd(tvbuff_t *tvb, packet_info *pinfo,
+ proto_tree *tree, void* data _U_)
+{
+ struct otrxd_pdu_info pi = { 0 };
+ proto_tree *otrxd_tree;
+ proto_item *ti, *gi;
+ int offset = 0;
+
+ col_set_str(pinfo->cinfo, COL_PROTOCOL, "OsmoTRXD");
+ col_clear(pinfo->cinfo, COL_INFO);
+
+ ti = proto_tree_add_item(tree, proto_otrxd, tvb, 0, -1, ENC_NA);
+ otrxd_tree = proto_item_add_subtree(ti, ett_otrxd);
+
+ /* Determine the burst direction */
+ int burst_dir = otrxcd_get_dir(pinfo);
+
+ /* A burst might be injected by some other program using
+ * a random source port, so let's try to guess by destport. */
+ if (burst_dir == OTRXCD_DIR_UNKNOWN) {
+ expert_add_info(pinfo, ti, &ei_otrxd_injected_msg);
+ burst_dir = otrxcd_guess_dir(pinfo);
+ }
+
+ if (burst_dir == OTRXCD_DIR_L12TRX)
+ col_append_str(pinfo->cinfo, COL_INFO, "Tx burst (L1 -> TRX): ");
+ else if (burst_dir == OTRXCD_DIR_TRX2L1)
+ col_append_str(pinfo->cinfo, COL_INFO, "Rx burst (TRX -> L1): ");
+ else
+ col_append_str(pinfo->cinfo, COL_INFO, "Tx/Rx burst (Unknown): ");
+
+ /* Add a generated field, so we can filter bursts by direction */
+ gi = proto_tree_add_uint(otrxd_tree, hf_otrxd_burst_dir,
+ tvb, 0, 0, burst_dir);
+ proto_item_set_generated(gi);
+
+ /* Parse common TRXD PDU version */
+ proto_tree_add_item_ret_uint(otrxd_tree, hf_otrxd_pdu_ver, tvb,
+ offset, 1, ENC_NA, &pi.ver);
+ proto_item_append_text(ti, " Version %u", pi.ver);
+
+ if (burst_dir == OTRXCD_DIR_L12TRX)
+ offset = dissect_otrxd_tx(tvb, pinfo, ti, otrxd_tree, &pi, offset);
+ else if (burst_dir == OTRXCD_DIR_TRX2L1)
+ offset = dissect_otrxd_rx(tvb, pinfo, ti, otrxd_tree, &pi, offset);
+ else {
+ expert_add_info(pinfo, ti, &ei_otrxd_unknown_dir);
+ offset = 1; /* Only the PDU version was parsed */
+ }
+
+ /* Summary for all parsed PDUs */
+ if (pi.num_pdus == 1) {
+ col_append_fstr(pinfo->cinfo, COL_INFO, "TDMA FN %07u TN %u", pi.fn, pi.tn);
+ if (pi.mod_str != NULL)
+ col_append_fstr(pinfo->cinfo, COL_INFO, ", Modulation %s", pi.mod_str);
+ else if (pi.nope && burst_dir == OTRXCD_DIR_TRX2L1)
+ col_append_str(pinfo->cinfo, COL_INFO, ", NOPE.ind");
+ else if (pi.nope && burst_dir == OTRXCD_DIR_L12TRX)
+ col_append_str(pinfo->cinfo, COL_INFO, ", NOPE.req");
+ } else if (pi.num_pdus > 1) {
+ col_append_fstr(pinfo->cinfo, COL_INFO, "TDMA FN %07u", pi.fn);
+ col_append_fstr(pinfo->cinfo, COL_INFO, ", %u batched PDUs ", pi.num_pdus);
+ }
+
+ proto_item_set_len(ti, offset);
+
+ /* Let it warn us if there are unhandled tail octets */
+ if ((guint) offset < tvb_reported_length(tvb))
+ expert_add_info(pinfo, ti, &ei_otrxd_tail_octets);
+
+ return offset;
+}
+
+/* Dissector for Control commands and responses, and Clock indications */
+static int dissect_otrxc(tvbuff_t *tvb, packet_info *pinfo,
+ proto_tree *tree, void *data _U_)
+{
+ int offset = 0, msg_len, end_verb, end_status;
+ const guint8 *msg_str, *msg_type_str;
+ proto_item *ti, *gi, *delim_item;
+ proto_tree *otrxc_tree;
+ guint32 delimiter;
+
+ col_set_str(pinfo->cinfo, COL_PROTOCOL, "OsmoTRXC");
+ col_clear(pinfo->cinfo, COL_INFO);
+
+ msg_len = tvb_reported_length(tvb);
+ msg_str = tvb_get_string_enc(pinfo->pool, tvb, 0, msg_len, ENC_ASCII);
+ col_add_str(pinfo->cinfo, COL_INFO, msg_str);
+
+ ti = proto_tree_add_item(tree, proto_otrxc, tvb, 0, msg_len, ENC_ASCII);
+ otrxc_tree = proto_item_add_subtree(ti, ett_otrxc);
+
+ /* Determine the message direction */
+ int msg_dir = otrxcd_get_dir(pinfo);
+
+ /* A message might be injected by some other program using
+ * a random source port, so let's try to guess by destport. */
+ if (msg_dir == OTRXCD_DIR_UNKNOWN) {
+ expert_add_info(pinfo, ti, &ei_otrxc_injected_msg);
+ if ((msg_dir = otrxcd_guess_dir(pinfo)) == OTRXCD_DIR_UNKNOWN)
+ expert_add_info(pinfo, ti, &ei_otrxc_unknown_dir);
+ }
+
+ /* Add a generated field, so we can filter bursts by direction */
+ gi = proto_tree_add_uint(otrxc_tree, hf_otrxc_msg_dir,
+ tvb, 0, 0, msg_dir);
+ proto_item_set_generated(gi);
+
+ /* First 3 bytes define a type of the message ("IND", "CMD", "RSP") */
+ proto_tree_add_item_ret_string(otrxc_tree, hf_otrxc_type, tvb, offset, 3,
+ ENC_NA | ENC_ASCII, pinfo->pool,
+ &msg_type_str);
+ offset += 3;
+
+ /* Determine the message type */
+ enum otrxc_msg_type msg_type = str_to_val((const gchar *) msg_type_str,
+ otrxc_msg_type_enc,
+ OTRXC_MSG_TYPE_UNKNOWN);
+ proto_item_append_text(ti, ", %s", val_to_str_const(msg_type, otrxc_msg_type_desc,
+ "Unknown message type"));
+ if (msg_type == OTRXC_MSG_TYPE_UNKNOWN) {
+ expert_add_info(pinfo, ti, &ei_otrxc_unknown_msg_type);
+ return offset;
+ }
+
+ /* The message type is separated by a delimiter */
+ delim_item = proto_tree_add_item_ret_uint(otrxc_tree, hf_otrxc_delimiter,
+ tvb, offset, 1, ENC_NA, &delimiter);
+ proto_item_set_hidden(delim_item);
+ offset += 1;
+
+ /* Delimiter should be a space symbol */
+ if (delimiter != 0x20)
+ expert_add_info(pinfo, delim_item, &ei_otrxc_bad_delimiter);
+
+ /* The message type is followed by a verb, e.g. "IND CLOCK", "CMD POWEROFF" */
+ end_verb = tvb_find_guint8(tvb, offset, -1, (char) delimiter);
+ if (end_verb < 0) {
+ /* Just a command without parameters, e.g. "CMD POWERON" */
+ proto_tree_add_item(otrxc_tree, hf_otrxc_verb, tvb,
+ offset, -1, ENC_ASCII | ENC_NA);
+ if (msg_type == OTRXC_MSG_TYPE_RESPONSE)
+ expert_add_info(pinfo, ti, &ei_otrxc_rsp_no_code);
+ return tvb_captured_length(tvb);
+ } else {
+ proto_tree_add_item(otrxc_tree, hf_otrxc_verb, tvb,
+ offset, end_verb - offset,
+ ENC_ASCII | ENC_NA);
+ offset = end_verb;
+ }
+
+ /* Another delimiter between the verb and status code / parameters */
+ delim_item = proto_tree_add_item_ret_uint(otrxc_tree, hf_otrxc_delimiter,
+ tvb, offset, 1, ENC_NA, &delimiter);
+ proto_item_set_hidden(delim_item);
+ offset += 1;
+
+ if (msg_type == OTRXC_MSG_TYPE_RESPONSE) {
+ end_status = tvb_find_guint8(tvb, offset, -1, (char) delimiter);
+ if (end_status > 0) {
+ proto_tree_add_item(otrxc_tree, hf_otrxc_status,
+ tvb, offset, end_status - offset, ENC_ASCII | ENC_NA);
+ offset = end_status;
+
+ /* Another delimiter between the status code and parameters */
+ delim_item = proto_tree_add_item_ret_uint(otrxc_tree, hf_otrxc_delimiter,
+ tvb, offset, 1, ENC_NA, &delimiter);
+ proto_item_set_hidden(delim_item);
+ offset += 1;
+ } else if (offset < msg_len) {
+ /* Response without parameters, e.g. "RSP POWEROFF 0" */
+ proto_tree_add_item(otrxc_tree, hf_otrxc_status,
+ tvb, offset, msg_len - offset, ENC_ASCII | ENC_NA);
+ return tvb_captured_length(tvb);
+ } else {
+ expert_add_info(pinfo, ti, &ei_otrxc_rsp_no_code);
+ return offset;
+ }
+ }
+
+ if (offset < msg_len) {
+ proto_tree_add_item(otrxc_tree, hf_otrxc_params,
+ tvb, offset, -1, ENC_ASCII | ENC_NA);
+ }
+
+ return tvb_captured_length(tvb);
+}
+
+void proto_register_osmo_trx(void)
+{
+ static hf_register_info hf_otrxd[] = {
+ /* Common generated field: burst direction */
+ { &hf_otrxd_burst_dir, { "Burst Direction", "osmo_trx.direction",
+ FT_UINT8, BASE_DEC, VALS(otrxcd_dir_vals), 0, NULL, HFILL } },
+
+ /* Rx/Tx header fields */
+ { &hf_otrxd_pdu_ver, { "PDU Version", "osmo_trxd.pdu_ver",
+ FT_UINT8, BASE_DEC, NULL, 0xf0, NULL, HFILL } },
+ { &hf_otrxd_chdr_reserved, { "Reserved", "osmo_trxd.chdr_reserved",
+ FT_UINT8, BASE_DEC, NULL, 0x08, NULL, HFILL } },
+ { &hf_otrxd_tdma_tn, { "TDMA Timeslot Number", "osmo_trxd.tdma.tn",
+ FT_UINT8, BASE_DEC, NULL, 0x07, NULL, HFILL } },
+ { &hf_otrxd_tdma_fn, { "TDMA Frame Number", "osmo_trxd.tdma.fn",
+ FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL } },
+ { &hf_otrxd_batch_ind, { "BATCH Indication", "osmo_trxd.batch_ind",
+ FT_BOOLEAN, 8, TFS(&otrxd_batch_bool_val), 0x80, NULL, HFILL } },
+ { &hf_otrxd_shadow_ind, { "PDU class", "osmo_trxd.shadow_ind",
+ FT_BOOLEAN, 8, TFS(&otrxd_shadow_bool_val), 0x40, NULL, HFILL } },
+ { &hf_otrxd_trx_num, { "TRX (RF Channel) Number", "osmo_trxd.trx_num",
+ FT_UINT8, BASE_DEC, NULL, 0x3f, NULL, HFILL } },
+
+ /* Rx header fields */
+ { &hf_otrxd_rssi, { "RSSI", "osmo_trxd.meas.rssi",
+ FT_UINT8, BASE_CUSTOM, CF_FUNC(format_rssi), 0, NULL, HFILL } },
+ { &hf_otrxd_toa256, { "Timing of Arrival", "osmo_trxd.meas.toa256",
+ FT_INT16, BASE_DEC | BASE_UNIT_STRING, &otrx_units_toa256, 0, NULL, HFILL } },
+
+ /* MTS (Modulation and Training Sequence) fields */
+ { &hf_otrxd_nope_ind, { "NOPE Indication", "osmo_trxd.nope_ind",
+ FT_BOOLEAN, 8, TFS(&otrxd_nope_bool_val), 0x80, NULL, HFILL } },
+ { &hf_otrxd_nope_ind_pad, { "NOPE Padding", "osmo_trxd.nope_ind_pad",
+ FT_UINT8, BASE_DEC, NULL, 0x7f, NULL, HFILL } },
+ { &hf_otrxd_mod_2b, { "Modulation", "osmo_trxd.mod",
+ FT_UINT8, BASE_DEC, VALS(otrxd_mod_2b_vals), 0x60, NULL, HFILL } },
+ { &hf_otrxd_mod_3b, { "Modulation", "osmo_trxd.mod",
+ FT_UINT8, BASE_DEC, VALS(otrxd_mod_3b_vals), 0x70, NULL, HFILL } },
+ { &hf_otrxd_mod_4b, { "Modulation", "osmo_trxd.mod",
+ FT_UINT8, BASE_DEC, VALS(otrxd_mod_4b_vals), 0x78, NULL, HFILL } },
+ { &hf_otrxd_tsc_set_x2, { "TSC Set", "osmo_trxd.tsc_set",
+ FT_UINT8, BASE_CUSTOM, CF_FUNC(format_tsc_set), 0x08, NULL, HFILL } },
+ { &hf_otrxd_tsc_set_x4, { "TSC Set", "osmo_trxd.tsc_set",
+ FT_UINT8, BASE_CUSTOM, CF_FUNC(format_tsc_set), 0x18, NULL, HFILL } },
+ { &hf_otrxd_tsc, { "TSC (Training Sequence Code)", "osmo_trxd.tsc",
+ FT_UINT8, BASE_DEC, NULL, 0x07, NULL, HFILL } },
+ { &hf_otrxd_ci, { "C/I (Carrier-to-Interference ratio)", "osmo_trxd.meas.ci",
+ FT_INT16, BASE_DEC | BASE_UNIT_STRING, &units_centibels, 0, NULL, HFILL } },
+
+ /* Tx header fields */
+ { &hf_otrxd_tx_att, { "Tx Attenuation", "osmo_trxd.tx_att",
+ FT_UINT8, BASE_DEC | BASE_UNIT_STRING, &units_decibels, 0, NULL, HFILL } },
+ { &hf_otrxd_tx_scpir, { "SCPIR Value", "osmo_trxd.scpir_val",
+ FT_INT8, BASE_DEC | BASE_UNIT_STRING, &units_decibels, 0, NULL, HFILL } },
+ { &hf_otrxd_tx_rfu, { "Spare padding", "osmo_trxd.spare",
+ FT_BYTES, SEP_SPACE, NULL, 0, NULL, HFILL } },
+
+ /* Burst soft (255 .. 0) / hard (1 or 0) bits */
+ { &hf_otrxd_soft_symbols, { "Soft-bits", "osmo_trxd.burst.sbits",
+ FT_BYTES, SEP_SPACE, NULL, 0, NULL, HFILL } },
+ { &hf_otrxd_hard_symbols, { "Hard-bits", "osmo_trxd.burst.hbits",
+ FT_BYTES, SEP_SPACE, NULL, 0, NULL, HFILL } },
+ { &hf_otrxd_burst_pad, { "Legacy padding", "osmo_trxd.burst.pad",
+ FT_BYTES, SEP_SPACE, NULL, 0, NULL, HFILL } },
+ };
+
+ static hf_register_info hf_otrxc[] = {
+ /* Common generated field: message direction */
+ { &hf_otrxc_msg_dir, { "Message Direction", "osmo_trx.direction",
+ FT_UINT8, BASE_DEC, VALS(otrxcd_dir_vals), 0, NULL, HFILL } },
+
+ { &hf_otrxc_type, { "Type", "osmo_trxc.type",
+ FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
+ { &hf_otrxc_delimiter, { "Delimiter", "osmo_trxc.delim",
+ FT_CHAR, BASE_HEX, NULL, 0, NULL, HFILL } },
+ { &hf_otrxc_verb, { "Verb", "osmo_trxc.verb",
+ FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
+ { &hf_otrxc_status, { "Status", "osmo_trxc.status",
+ FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
+ { &hf_otrxc_params, { "Parameters", "osmo_trxc.params",
+ FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
+ };
+
+ static gint *ett[] = {
+ &ett_otrxd,
+ &ett_otrxd_rx_pdu,
+ &ett_otrxd_tx_pdu,
+ &ett_otrxc,
+ };
+
+ proto_otrxd = proto_register_protocol("OsmoTRX Data Protocol",
+ "OsmoTRXD", "osmo_trxd");
+ proto_otrxc = proto_register_protocol("OsmoTRX Control / Clock Protocol",
+ "OsmoTRXC", "osmo_trxc");
+
+ proto_register_field_array(proto_otrxd, hf_otrxd, array_length(hf_otrxd));
+ proto_register_field_array(proto_otrxc, hf_otrxc, array_length(hf_otrxc));
+ proto_register_subtree_array(ett, array_length(ett));
+
+ static ei_register_info ei_otrxd[] = {
+ { &ei_otrxd_injected_msg, { "osmo_trx.ei.injected_msg",
+ PI_COMMENTS_GROUP, PI_COMMENT, "Injected message", EXPFILL } },
+ { &ei_otrxd_unknown_dir, { "osmo_trx.ei.unknown_dir",
+ PI_UNDECODED, PI_ERROR, "Unknown direction", EXPFILL } },
+ { &ei_otrxd_unknown_pdu_ver, { "osmo_trxd.ei.unknown_pdu_ver",
+ PI_PROTOCOL, PI_ERROR, "Unknown PDU version", EXPFILL } },
+ { &ei_otrxd_tail_octets, { "osmo_trxd.ei.tail_octets",
+ PI_UNDECODED, PI_WARN, "Unhandled tail octets", EXPFILL } },
+ };
+
+ static ei_register_info ei_otrxc[] = {
+ { &ei_otrxc_injected_msg, { "osmo_trx.ei.injected_msg",
+ PI_COMMENTS_GROUP, PI_COMMENT, "Injected message", EXPFILL } },
+ { &ei_otrxc_unknown_dir, { "osmo_trx.ei.unknown_dir",
+ PI_ASSUMPTION, PI_WARN, "Unknown direction", EXPFILL } },
+ { &ei_otrxc_bad_delimiter, { "osmo_trxc.ei.bad_delimiter",
+ PI_PROTOCOL, PI_WARN, "Invalid delimiter", EXPFILL } },
+ { &ei_otrxc_rsp_no_code, { "osmo_trxc.ei.rsp_no_code",
+ PI_PROTOCOL, PI_ERROR, "Response without status code", EXPFILL } },
+ { &ei_otrxc_unknown_msg_type, { "osmo_trxc.ei.unknown_msg_type",
+ PI_PROTOCOL, PI_ERROR, "Unknown message type", EXPFILL } },
+ };
+
+ /* Expert info for OsmoTRXD protocol */
+ expert_module_t *expert_otrxd = expert_register_protocol(proto_otrxd);
+ expert_register_field_array(expert_otrxd, ei_otrxd, array_length(ei_otrxd));
+
+ /* Expert info for OsmoTRXC protocol */
+ expert_module_t *expert_otrxc = expert_register_protocol(proto_otrxc);
+ expert_register_field_array(expert_otrxc, ei_otrxc, array_length(ei_otrxc));
+
+ /* Register the dissectors */
+ otrxd_handle = register_dissector("osmo_trxd", dissect_otrxd, proto_otrxd);
+ otrxc_handle = register_dissector("osmo_trxc", dissect_otrxc, proto_otrxc);
+}
+
+void proto_reg_handoff_osmo_trx(void)
+{
+#if 0
+/* The TRX-side control interface for C(N) is on port P = B + 2N + 1;
+ * the corresponding core-side interface for every socket is at P + 100.
+ * Give a base port B (5700), the master clock interface is at port P = B. */
+#define OTRXC_UDP_PORTS \
+ "5701,5703,5800,5801,5803," /* The BTS side (osmo-trx, osmo-bts-trx) */ \
+ "6701,6703,6800,6801,6803" /* The MS side (trxcon, fake_trx, grgsm_trx) */
+
+/* The data interface is on an odd numbered port P = B + 2N + 2. */
+#define OTRXD_UDP_PORTS \
+ "5702,5802," /* The BTS side, TRX0 (osmo-trx, osmo-bts-trx) */ \
+ "5704,5804," /* The BTS side, TRX1 (osmo-trx, osmo-bts-trx) */ \
+ "6702,6802" /* The MS side (trxcon, fake_trx, grgsm_trx) */
+
+ dissector_add_uint_range_with_preference("udp.port", OTRXD_UDP_PORTS, otrxd_handle);
+ dissector_add_uint_range_with_preference("udp.port", OTRXC_UDP_PORTS, otrxc_handle);
+#endif
+
+ dissector_add_for_decode_as("udp.port", otrxd_handle);
+ dissector_add_for_decode_as("udp.port", otrxc_handle);
+}
+
+/*
+ * Editor modelines - https://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 8
+ * tab-width: 8
+ * indent-tabs-mode: t
+ * End:
+ *
+ * vi: set shiftwidth=8 tabstop=8 noexpandtab:
+ * :indentSize=8:tabSize=8:noTabs=false:
+ */