Revert "TODO epan/dissectors/asn1/kerberos/packet-kerberos-template.c new GSS flags"
[wireshark-sm.git] / epan / dissectors / packet-osmo_trx.c
blob2ab10c8860b5333b618fc298744be90843f33747
1 /* packet-osmo_trx.c
2 * Dissector for OsmoTRX Protocol (GSM Transceiver control and data).
4 * (C) 2018 by Harald Welte <laforge@gnumonks.org>
5 * (C) 2019 by Vadim Yanitskiy <axilirator@gmail.com>
6 * (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
8 * Wireshark - Network traffic analyzer
9 * By Gerald Combs <gerald@wireshark.org>
10 * Copyright 1998 Gerald Combs
12 * SPDX-License-Identifier: GPL-2.0-or-later
15 #include "config.h"
17 #include <epan/packet.h>
18 #include <epan/expert.h>
19 #include <epan/unit_strings.h>
20 #include <epan/tfs.h>
21 #include <wsutil/array.h>
23 /* This is a non-standard, ad-hoc protocol to pass baseband GSM bursts between
24 * the transceiver (such as osmo-trx, fake_trx.py or grgsm_trx) and the L1
25 * program (such as osmo-bts-trx or trxcon). Osmocom inherited this protocol
26 * when forking OsmoTRX off the OpenBTS "Transceiver" program. */
28 void proto_register_osmo_trx(void);
29 void proto_reg_handoff_osmo_trx(void);
31 static dissector_handle_t otrxd_handle;
32 static dissector_handle_t otrxc_handle;
34 /* Which kind of message it is */
35 static int proto_otrxd;
36 static int proto_otrxc;
38 /* Generated fields */
39 static int hf_otrxd_burst_dir;
40 static int hf_otrxc_msg_dir;
42 /* TRXD PDU version */
43 static int hf_otrxd_pdu_ver;
45 /* TRXD common fields */
46 static int hf_otrxd_chdr_reserved;
47 static int hf_otrxd_shadow_ind;
48 static int hf_otrxd_batch_ind;
49 static int hf_otrxd_trx_num;
50 static int hf_otrxd_tdma_tn;
51 static int hf_otrxd_tdma_fn;
53 /* MTS (Modulation and Training Sequence) fields */
54 static int hf_otrxd_nope_ind;
55 static int hf_otrxd_nope_ind_pad;
56 static int hf_otrxd_mod_2b; /* 2 bit field */
57 static int hf_otrxd_mod_3b; /* 3 bit field */
58 static int hf_otrxd_mod_4b; /* 4 bit field */
59 static int hf_otrxd_tsc_set_x4;
60 static int hf_otrxd_tsc_set_x2;
61 static int hf_otrxd_tsc;
63 /* TRXD Rx header fields */
64 static int hf_otrxd_rssi;
65 static int hf_otrxd_toa256;
66 static int hf_otrxd_ci;
68 /* TRXD Tx header fields */
69 static int hf_otrxd_tx_att;
70 static int hf_otrxd_tx_scpir;
71 static int hf_otrxd_tx_rfu;
73 /* Burst soft (255 .. 0) / hard (1 or 0) bits */
74 static int hf_otrxd_soft_symbols;
75 static int hf_otrxd_hard_symbols;
76 static int hf_otrxd_burst_pad;
78 /* TRXC - Control and Clock protocol */
79 static int hf_otrxc_type;
80 static int hf_otrxc_delimiter;
81 static int hf_otrxc_verb;
82 static int hf_otrxc_params;
83 static int hf_otrxc_status;
85 static int ett_otrxd;
86 static int ett_otrxc;
88 static int ett_otrxd_rx_pdu;
89 static int ett_otrxd_tx_pdu;
91 static expert_field ei_otrxd_unknown_pdu_ver;
92 static expert_field ei_otrxd_injected_msg;
93 static expert_field ei_otrxd_unknown_dir;
94 static expert_field ei_otrxd_tail_octets;
96 static expert_field ei_otrxc_unknown_msg_type;
97 static expert_field ei_otrxc_bad_delimiter;
98 static expert_field ei_otrxc_rsp_no_code;
99 static expert_field ei_otrxc_injected_msg;
100 static expert_field ei_otrxc_unknown_dir;
102 /* Custom units */
103 static const unit_name_string otrx_units_toa256 = { " (1/256 of a symbol)", NULL };
105 /* TRXD SHADOW.ind value description */
106 static const true_false_string otrxd_shadow_bool_val = {
107 "This is a shadow PDU",
108 "This is a primary PDU",
111 /* TRXD BATCH.ind value description */
112 static const true_false_string otrxd_batch_bool_val = {
113 "Another PDU follows",
114 "This is the last PDU",
117 /* TRXD NOPE.{ind,req} value description */
118 static const true_false_string otrxd_nope_bool_val = {
119 "Burst is not present",
120 "Burst is present",
123 /* TRXD modulation types (2 bit field) */
124 static const value_string otrxd_mod_2b_vals[] = {
125 /* .00xx... */ { 0x00, "GMSK" },
126 /* .11xx... */ { 0x03, "AQPSK" },
127 { 0, NULL },
130 /* TRXD modulation types (3 bit field) */
131 static const value_string otrxd_mod_3b_vals[] = {
132 /* .010x... */ { 0x02, "8-PSK" },
133 /* .100x... */ { 0x04, "16QAM" },
134 /* .101x... */ { 0x05, "32QAM" },
135 { 0, NULL },
138 /* TRXD modulation types (4 bit field) */
139 static const value_string otrxd_mod_4b_vals[] = {
140 /* .0110... */ { 0x06, "GMSK (Access Burst)" },
141 /* .0111... */ { 0x07, "RFU (Reserved for Future Use)" },
142 { 0, NULL },
145 /* TRXD modulation type */
146 enum otrxd_mod_type {
147 OTRXD_MOD_T_GMSK = 0x00,
148 OTRXD_MOD_T_8PSK = 0x02,
149 OTRXD_MOD_T_AQPSK = 0x03,
150 OTRXD_MOD_T_16QAM = 0x04,
151 OTRXD_MOD_T_32QAM = 0x05,
152 OTRXD_MOD_T_GMSK_AB = 0x06,
153 OTRXD_MOD_T_RFU = 0x07,
156 /* See 3GPP TS 45.002, section 5.2 "Bursts" */
157 #define GMSK_BURST_LEN 148
159 /* TRXD modulation / burst length mapping */
160 static const uint16_t otrxd_burst_len[] = {
161 [OTRXD_MOD_T_GMSK] = GMSK_BURST_LEN * 1,
162 [OTRXD_MOD_T_GMSK_AB] = GMSK_BURST_LEN * 1,
163 [OTRXD_MOD_T_AQPSK] = GMSK_BURST_LEN * 2,
164 [OTRXD_MOD_T_8PSK] = GMSK_BURST_LEN * 3,
165 [OTRXD_MOD_T_16QAM] = GMSK_BURST_LEN * 4,
166 [OTRXD_MOD_T_32QAM] = GMSK_BURST_LEN * 5,
167 [OTRXD_MOD_T_RFU] = 0, /* unknown */
170 /* RSSI is encoded without a negative sign, so we need to show it */
171 static void format_rssi(char *buf, const uint32_t rssi)
173 snprintf(buf, ITEM_LABEL_LENGTH, "-%u%s", rssi, unit_name_string_get_value(rssi, &units_dbm));
176 /* TSC (Training Sequence Code) set number in 3GPP TS 45.002 starts
177 * from 1, while 'on the wire' it's encoded as X - 1 (starts from 0). */
178 static void format_tsc_set(char *buf, uint32_t tsc_set)
180 snprintf(buf, ITEM_LABEL_LENGTH, "%u", tsc_set + 1);
183 /* Message direction */
184 enum otrxcd_dir_type {
185 OTRXCD_DIR_UNKNOWN = 0,
186 OTRXCD_DIR_L12TRX,
187 OTRXCD_DIR_TRX2L1,
190 static const value_string otrxcd_dir_vals[] = {
191 { OTRXCD_DIR_UNKNOWN, "Unknown" },
192 { OTRXCD_DIR_L12TRX, "L1 -> TRX" },
193 { OTRXCD_DIR_TRX2L1, "TRX -> L1" },
194 { 0, NULL },
197 /* Determine message direction (L1 to TRX, or TRX to L1?) */
198 static enum otrxcd_dir_type otrxcd_get_dir(const packet_info *pinfo)
200 if (pinfo->srcport - pinfo->destport == 100)
201 return OTRXCD_DIR_L12TRX;
202 else if (pinfo->destport - pinfo->srcport == 100)
203 return OTRXCD_DIR_TRX2L1;
204 else
205 return OTRXCD_DIR_UNKNOWN;
208 /* Guess message direction (L1 to TRX, or TRX to L1?) */
209 static enum otrxcd_dir_type otrxcd_guess_dir(const packet_info *pinfo)
211 /* TODO: srcport can be also used for guessing,
212 * TODO: use port numbers from protocol preferences. */
213 switch (pinfo->destport) {
214 /* OsmoTRXD: Tx burst (L1 -> TRX) */
215 case 5702: case 5704: case 6702:
216 return OTRXCD_DIR_L12TRX;
217 /* OsmoTRXD: Rx burst (TRX -> L1) */
218 case 5802: case 5804: case 6802:
219 return OTRXCD_DIR_TRX2L1;
220 /* OsmoTRXC: Command (L1 -> TRX) */
221 case 5701: case 5703: case 6701:
222 return OTRXCD_DIR_L12TRX;
223 /* OsmoTRXC: Response or Indication (TRX -> L1) */
224 case 5801: case 5803: case 6801:
225 case 5800: case 6800:
226 return OTRXCD_DIR_TRX2L1;
227 default:
228 return OTRXCD_DIR_UNKNOWN;
232 /* TRXC message types */
233 enum otrxc_msg_type {
234 OTRXC_MSG_TYPE_UNKNOWN = 0,
235 OTRXC_MSG_TYPE_COMMAND,
236 OTRXC_MSG_TYPE_RESPONSE,
237 OTRXC_MSG_TYPE_INDICATION,
240 static const value_string otrxc_msg_type_enc[] = {
241 { OTRXC_MSG_TYPE_COMMAND, "CMD" },
242 { OTRXC_MSG_TYPE_RESPONSE, "RSP" },
243 { OTRXC_MSG_TYPE_INDICATION, "IND" },
244 { 0, NULL },
247 static const value_string otrxc_msg_type_desc[] = {
248 { OTRXC_MSG_TYPE_COMMAND, "Command" },
249 { OTRXC_MSG_TYPE_RESPONSE, "Response" },
250 { OTRXC_MSG_TYPE_INDICATION, "Indication" },
251 { 0, NULL },
254 /* TRXD PDU information */
255 struct otrxd_pdu_info {
256 /* PDU version */
257 uint32_t ver;
258 /* BATCH.ind marker */
259 bool batch;
260 /* SHADOW.ind marker */
261 bool shadow;
262 /* Number of batched PDUs */
263 uint32_t num_pdus;
264 /* TRX (RF channel) number */
265 uint32_t trx_num;
266 /* TDMA frame number */
267 uint32_t fn;
268 /* TDMA timeslot number */
269 uint32_t tn;
270 /* NOPE.{ind,req} marker */
271 bool nope;
272 /* Modulation type and string */
273 enum otrxd_mod_type mod;
274 const char *mod_str;
275 /* Training Sequence Code */
276 uint32_t tsc;
279 /* Dissector for common Rx/Tx TRXDv0/v1 header part */
280 static void dissect_otrxd_chdr_v0(tvbuff_t *tvb, packet_info *pinfo _U_,
281 proto_item *ti, proto_tree *tree,
282 struct otrxd_pdu_info *pi,
283 int *offset)
285 proto_tree_add_item(tree, hf_otrxd_chdr_reserved, tvb,
286 *offset, 1, ENC_NA);
287 proto_tree_add_item_ret_uint(tree, hf_otrxd_tdma_tn, tvb,
288 *offset, 1, ENC_NA, &pi->tn);
289 *offset += 1;
291 /* TDMA frame number (4 octets, big endian) */
292 proto_tree_add_item_ret_uint(tree, hf_otrxd_tdma_fn, tvb,
293 *offset, 4, ENC_BIG_ENDIAN, &pi->fn);
294 *offset += 4;
296 proto_item_append_text(ti, "TDMA FN %07u TN %u", pi->fn, pi->tn);
299 /* Dissector for MTS (Modulation and Training Sequence) */
300 static void dissect_otrxd_mts(tvbuff_t *tvb, proto_tree *tree,
301 struct otrxd_pdu_info *pi,
302 int offset)
304 /* NOPE indication contains no MTS information.
306 * | 7 6 5 4 3 2 1 0 | Bit numbers (value range)
307 * | X . . . . . . . | NOPE / IDLE indication
308 * | . X X X X . . . | MTS (Modulation and Training Sequence)
309 * | . . . . . X X X | TSC (Training Sequence Code)
311 proto_tree_add_item_ret_boolean(tree, hf_otrxd_nope_ind, tvb,
312 offset, 1, ENC_NA, &pi->nope);
313 if (pi->nope) {
314 proto_tree_add_item(tree, hf_otrxd_nope_ind_pad, tvb, offset, 1, ENC_NA);
315 return;
318 /* MTS (Modulation and Training Sequence info).
320 * | 7 6 5 4 3 2 1 0 | Bit numbers (value range)
321 * | . 0 0 X X . . . | GMSK, 4 TSC sets (0..3)
322 * | . 0 1 0 X . . . | 8-PSK, 2 TSC sets (0..1)
323 * | . 0 1 1 0 . . . | GMSK, Packet Access Burst
324 * | . 0 1 1 1 . . . | RFU (Reserved for Future Use)
325 * | . 1 0 0 X . . . | 16QAM, 2 TSC sets (0..1)
326 * | . 1 0 1 X . . . | 32QAM, 2 TSC sets (0..1)
327 * | . 1 1 X X . . . | AQPSK, 4 TSC sets (0..3)
329 * NOTE: 3GPP defines 4 TSC sets for both GMSK and AQPSK.
331 uint8_t mts = tvb_get_uint8(tvb, offset);
332 if ((mts >> 5) == 0x00 || (mts >> 5) == 0x03) { /* 2 bit: GMSK (0) or AQPSK (3) */
333 pi->mod = (enum otrxd_mod_type) (mts >> 5);
334 pi->mod_str = val_to_str(mts >> 5, otrxd_mod_2b_vals, "Unknown 0x%02x");
335 proto_tree_add_item(tree, hf_otrxd_mod_2b, tvb, offset, 1, ENC_NA);
336 proto_tree_add_item(tree, hf_otrxd_tsc_set_x4, tvb, offset, 1, ENC_NA);
337 } else if ((mts >> 4) != 0x03) { /* 3 bit: 8-PSK, 16QAM, or 32QAM */
338 pi->mod = (enum otrxd_mod_type) (mts >> 4);
339 pi->mod_str = val_to_str(mts >> 4, otrxd_mod_3b_vals, "Unknown 0x%02x");
340 proto_tree_add_item(tree, hf_otrxd_mod_3b, tvb, offset, 1, ENC_NA);
341 proto_tree_add_item(tree, hf_otrxd_tsc_set_x2, tvb, offset, 1, ENC_NA);
342 } else { /* 4 bit (without TSC set): GMSK (Packet Access Burst) or RFU */
343 pi->mod = (enum otrxd_mod_type) (mts >> 3);
344 pi->mod_str = val_to_str(mts >> 3, otrxd_mod_4b_vals, "Unknown 0x%02x");
345 proto_tree_add_item(tree, hf_otrxd_mod_4b, tvb, offset, 1, ENC_NA);
348 proto_tree_add_item_ret_uint(tree, hf_otrxd_tsc, tvb, offset, 1, ENC_NA, &pi->tsc);
351 /* Dissector for Rx TRXD header version 0 */
352 static int dissect_otrxd_rx_hdr_v0(tvbuff_t *tvb, packet_info *pinfo,
353 proto_item *ti, proto_tree *tree,
354 struct otrxd_pdu_info *pi,
355 int offset)
357 dissect_otrxd_chdr_v0(tvb, pinfo, ti, tree, pi, &offset);
359 proto_tree_add_item(tree, hf_otrxd_rssi, tvb, offset++, 1, ENC_NA);
360 proto_tree_add_item(tree, hf_otrxd_toa256, tvb, offset, 2, ENC_NA);
361 offset += 2;
363 return offset;
366 /* Dissector for Rx TRXD header version 1 */
367 static int dissect_otrxd_rx_hdr_v1(tvbuff_t *tvb, packet_info *pinfo,
368 proto_item *ti, proto_tree *tree,
369 struct otrxd_pdu_info *pi,
370 int offset)
372 /* Dissect V0 specific part first */
373 offset = dissect_otrxd_rx_hdr_v0(tvb, pinfo, ti, tree, pi, offset);
375 /* MTS (Modulation and Training Sequence) */
376 dissect_otrxd_mts(tvb, tree, pi, offset++);
377 if (!pi->nope)
378 proto_item_append_text(ti, ", Modulation %s, TSC %u", pi->mod_str, pi->tsc);
379 else
380 proto_item_append_text(ti, ", NOPE.ind");
382 /* C/I (Carrier to Interference ratio) */
383 proto_tree_add_item(tree, hf_otrxd_ci, tvb, offset, 2, ENC_NA);
384 offset += 2;
386 return offset;
389 /* Dissector for TRXD Rx header version 2 */
390 static int dissect_otrxd_rx_hdr_v2(tvbuff_t *tvb, packet_info *pinfo _U_,
391 proto_item *ti, proto_tree *tree,
392 struct otrxd_pdu_info *pi,
393 int offset)
395 proto_tree_add_item(tree, hf_otrxd_chdr_reserved, tvb, offset, 1, ENC_NA);
396 proto_tree_add_item_ret_uint(tree, hf_otrxd_tdma_tn, tvb,
397 offset, 1, ENC_NA, &pi->tn);
398 offset += 1;
400 proto_tree_add_item_ret_boolean(tree, hf_otrxd_batch_ind, tvb,
401 offset, 1, ENC_NA, &pi->batch);
402 proto_tree_add_item_ret_boolean(tree, hf_otrxd_shadow_ind, tvb,
403 offset, 1, ENC_NA, &pi->shadow);
404 proto_tree_add_item_ret_uint(tree, hf_otrxd_trx_num, tvb,
405 offset, 1, ENC_NA, &pi->trx_num);
406 offset += 1;
408 /* MTS (Modulation and Training Sequence) */
409 dissect_otrxd_mts(tvb, tree, pi, offset++);
411 /* RSSI (Received Signal Strength Indication) */
412 proto_tree_add_item(tree, hf_otrxd_rssi, tvb, offset++, 1, ENC_NA);
414 /* ToA256 (Timing of Arrival) and C/I (Carrier to Interference ratio) */
415 proto_tree_add_item(tree, hf_otrxd_toa256, tvb, offset, 2, ENC_BIG_ENDIAN);
416 proto_tree_add_item(tree, hf_otrxd_ci, tvb, offset + 2, 2, ENC_BIG_ENDIAN);
417 offset += 4;
419 /* TDMA frame number (absent in additional PDUs) */
420 if (pi->num_pdus == 0) {
421 proto_tree_add_item_ret_uint(tree, hf_otrxd_tdma_fn, tvb,
422 offset, 4, ENC_BIG_ENDIAN, &pi->fn);
423 offset += 4;
426 proto_item_append_text(ti, "TRXN %02u, TDMA FN %07u TN %u", pi->trx_num, pi->fn, pi->tn);
427 if (!pi->nope)
428 proto_item_append_text(ti, ", Modulation %s, TSC %u", pi->mod_str, pi->tsc);
429 else
430 proto_item_append_text(ti, ", NOPE.ind");
432 return offset;
435 /* Burst data in Receive direction */
436 static int dissect_otrxd_rx(tvbuff_t *tvb, packet_info *pinfo,
437 proto_item *pti, proto_tree *ptree,
438 struct otrxd_pdu_info *pi,
439 int offset)
441 int start, burst_len, padding;
442 proto_tree *tree;
443 proto_item *ti;
445 loop:
446 /* Add a sub-tree for each PDU (length is set below) */
447 tree = proto_tree_add_subtree(ptree, tvb, offset, -1,
448 ett_otrxd_rx_pdu, &ti,
449 "TRXD Rx PDU: ");
450 start = offset;
452 /* Parse version specific TRXD header part */
453 switch (pi->ver) {
454 case 0:
455 offset = dissect_otrxd_rx_hdr_v0(tvb, pinfo, ti, tree, pi, offset);
456 /* The remaining octets is basically soft-bits of the burst */
457 burst_len = tvb_reported_length(tvb) - offset;
458 /* ... there must be at least 148 soft-bits */
459 if (burst_len < GMSK_BURST_LEN)
460 burst_len = GMSK_BURST_LEN; /* let it crash! */
461 /* ... there can be 2 optional padding octets in the end */
462 padding = burst_len % GMSK_BURST_LEN;
463 proto_tree_add_item(tree, hf_otrxd_soft_symbols, tvb,
464 offset, burst_len - padding, ENC_NA);
465 offset += burst_len - padding;
466 if (padding == 0)
467 break;
468 proto_tree_add_item(tree, hf_otrxd_burst_pad, tvb,
469 offset, padding, ENC_NA);
470 offset += padding;
471 break;
472 case 1:
473 offset = dissect_otrxd_rx_hdr_v1(tvb, pinfo, ti, tree, pi, offset);
474 if (pi->nope) /* NOPE.ind contains no burst */
475 break;
476 burst_len = otrxd_burst_len[pi->mod];
477 proto_tree_add_item(tree, hf_otrxd_soft_symbols, tvb,
478 offset, burst_len, ENC_NA);
479 offset += burst_len;
480 break;
481 case 2:
482 offset = dissect_otrxd_rx_hdr_v2(tvb, pinfo, ti, tree, pi, offset);
483 if (pi->nope) /* NOPE.ind contains no burst */
484 break;
485 burst_len = otrxd_burst_len[pi->mod];
486 proto_tree_add_item(tree, hf_otrxd_soft_symbols, tvb,
487 offset, burst_len, ENC_NA);
488 offset += burst_len;
489 break;
490 default:
491 expert_add_info_format(pinfo, pti, &ei_otrxd_unknown_pdu_ver,
492 "Unknown TRXD PDU version %u", pi->ver);
493 offset = 1; /* Only the PDU version was parsed */
494 return offset;
497 proto_item_set_len(ti, offset - start);
499 /* Number of processed PDUs */
500 pi->num_pdus += 1;
502 /* There can be additional 'batched' PDUs */
503 if (pi->batch)
504 goto loop;
506 return offset;
509 /* Dissector for TRXDv0/v1 Tx burst */
510 static void dissect_otrxd_tx_burst_v0(tvbuff_t *tvb, packet_info *pinfo _U_,
511 proto_item *ti, proto_tree *tree,
512 struct otrxd_pdu_info *pi,
513 int *offset)
515 /* Calculate the burst length */
516 const int burst_len = tvb_reported_length(tvb) - *offset;
518 /* Attempt to guess modulation by the length */
519 switch (burst_len) {
520 /* We may also have NOPE.req in the future (to drive fake_trx.py) */
521 case 0:
522 proto_item_append_text(ti, ", NOPE.req");
523 pi->nope = true;
524 return;
526 /* TODO: introduce an enumerated type, detect other modulation types,
527 * TODO: add a generated field for "osmo_trxd.mod" */
528 case GMSK_BURST_LEN:
529 proto_item_append_text(ti, ", Modulation GMSK");
530 pi->mod_str = "GMSK";
531 break;
532 case 3 * GMSK_BURST_LEN:
533 proto_item_append_text(ti, ", Modulation 8-PSK");
534 pi->mod_str = "8-PSK";
535 break;
538 /* Hard-bits (1 or 0) */
539 proto_tree_add_item(tree, hf_otrxd_hard_symbols, tvb,
540 *offset, burst_len, ENC_NA);
541 *offset += burst_len;
544 /* Dissector for TRXD Tx header version 2 */
545 static void dissect_otrxd_tx_hdr_v2(tvbuff_t *tvb, packet_info *pinfo _U_,
546 proto_item *ti, proto_tree *tree,
547 struct otrxd_pdu_info *pi,
548 int *offset)
550 proto_tree_add_item(tree, hf_otrxd_chdr_reserved, tvb, *offset, 1, ENC_NA);
551 proto_tree_add_item_ret_uint(tree, hf_otrxd_tdma_tn, tvb,
552 *offset, 1, ENC_NA, &pi->tn);
553 *offset += 1;
555 proto_tree_add_item_ret_boolean(tree, hf_otrxd_batch_ind, tvb,
556 *offset, 1, ENC_NA, &pi->batch);
557 proto_tree_add_item_ret_uint(tree, hf_otrxd_trx_num, tvb,
558 *offset, 1, ENC_NA, &pi->trx_num);
559 *offset += 1;
561 /* MTS (Modulation and Training Sequence) */
562 dissect_otrxd_mts(tvb, tree, pi, *offset);
563 *offset += 1;
565 /* Tx power attenuation */
566 proto_tree_add_item(tree, hf_otrxd_tx_att, tvb, *offset, 1, ENC_NA);
567 *offset += 1;
569 /* SCPIR (Subchannel Power Imbalance Ratio) */
570 proto_tree_add_item(tree, hf_otrxd_tx_scpir, tvb, *offset, 1, ENC_NA);
571 *offset += 1;
573 /* RFU (currently just to make the header dword-alignment) */
574 proto_tree_add_item(tree, hf_otrxd_tx_rfu, tvb, *offset, 3, ENC_NA);
575 *offset += 3;
577 /* TDMA frame number (absent in additional PDUs) */
578 if (pi->num_pdus == 0) {
579 proto_tree_add_item_ret_uint(tree, hf_otrxd_tdma_fn, tvb,
580 *offset, 4, ENC_BIG_ENDIAN, &pi->fn);
581 *offset += 4;
584 proto_item_append_text(ti, "TRXN %02u, TDMA FN %07u TN %u", pi->trx_num, pi->fn, pi->tn);
585 if (!pi->nope)
586 proto_item_append_text(ti, ", Modulation %s, TSC %u", pi->mod_str, pi->tsc);
587 else
588 proto_item_append_text(ti, ", NOPE.req");
591 /* Burst data in Transmit direction */
592 static int dissect_otrxd_tx(tvbuff_t *tvb, packet_info *pinfo,
593 proto_item *pti, proto_tree *ptree,
594 struct otrxd_pdu_info *pi,
595 int offset)
597 proto_tree *tree;
598 proto_item *ti;
599 int burst_len;
600 int start;
602 loop:
603 /* Add a sub-tree for each PDU (length is set below) */
604 tree = proto_tree_add_subtree(ptree, tvb, offset, -1,
605 ett_otrxd_tx_pdu, &ti,
606 "TRXD Tx PDU: ");
607 start = offset;
609 switch (pi->ver) {
610 /* Both versions feature the same PDU format */
611 case 0:
612 case 1:
613 dissect_otrxd_chdr_v0(tvb, pinfo, ti, tree, pi, &offset);
614 proto_tree_add_item(tree, hf_otrxd_tx_att, tvb, offset++, 1, ENC_NA);
615 dissect_otrxd_tx_burst_v0(tvb, pinfo, ti, tree, pi, &offset);
616 break;
617 case 2:
618 dissect_otrxd_tx_hdr_v2(tvb, pinfo, ti, tree, pi, &offset);
619 if (pi->nope) /* NOPE.ind contains no burst */
620 break;
621 burst_len = otrxd_burst_len[pi->mod];
622 proto_tree_add_item(tree, hf_otrxd_hard_symbols, tvb,
623 offset, burst_len, ENC_NA);
624 offset += burst_len;
625 break;
626 default:
627 expert_add_info_format(pinfo, pti, &ei_otrxd_unknown_pdu_ver,
628 "Unknown TRXD PDU version %u", pi->ver);
629 offset = 1; /* Only the PDU version was parsed */
630 return offset;
633 proto_item_set_len(ti, offset - start);
635 /* Number of processed PDUs */
636 pi->num_pdus += 1;
638 /* There can be additional 'batched' PDUs */
639 if (pi->batch)
640 goto loop;
642 return offset;
645 /* Common dissector for bursts in both directions */
646 static int dissect_otrxd(tvbuff_t *tvb, packet_info *pinfo,
647 proto_tree *tree, void* data _U_)
649 struct otrxd_pdu_info pi = { 0 };
650 proto_tree *otrxd_tree;
651 proto_item *ti, *gi;
652 int offset = 0;
654 col_set_str(pinfo->cinfo, COL_PROTOCOL, "OsmoTRXD");
655 col_clear(pinfo->cinfo, COL_INFO);
657 ti = proto_tree_add_item(tree, proto_otrxd, tvb, 0, -1, ENC_NA);
658 otrxd_tree = proto_item_add_subtree(ti, ett_otrxd);
660 /* Determine the burst direction */
661 int burst_dir = otrxcd_get_dir(pinfo);
663 /* A burst might be injected by some other program using
664 * a random source port, so let's try to guess by destport. */
665 if (burst_dir == OTRXCD_DIR_UNKNOWN) {
666 expert_add_info(pinfo, ti, &ei_otrxd_injected_msg);
667 burst_dir = otrxcd_guess_dir(pinfo);
670 if (burst_dir == OTRXCD_DIR_L12TRX)
671 col_append_str(pinfo->cinfo, COL_INFO, "Tx burst (L1 -> TRX): ");
672 else if (burst_dir == OTRXCD_DIR_TRX2L1)
673 col_append_str(pinfo->cinfo, COL_INFO, "Rx burst (TRX -> L1): ");
674 else
675 col_append_str(pinfo->cinfo, COL_INFO, "Tx/Rx burst (Unknown): ");
677 /* Add a generated field, so we can filter bursts by direction */
678 gi = proto_tree_add_uint(otrxd_tree, hf_otrxd_burst_dir,
679 tvb, 0, 0, burst_dir);
680 proto_item_set_generated(gi);
682 /* Parse common TRXD PDU version */
683 proto_tree_add_item_ret_uint(otrxd_tree, hf_otrxd_pdu_ver, tvb,
684 offset, 1, ENC_NA, &pi.ver);
685 proto_item_append_text(ti, " Version %u", pi.ver);
687 if (burst_dir == OTRXCD_DIR_L12TRX)
688 offset = dissect_otrxd_tx(tvb, pinfo, ti, otrxd_tree, &pi, offset);
689 else if (burst_dir == OTRXCD_DIR_TRX2L1)
690 offset = dissect_otrxd_rx(tvb, pinfo, ti, otrxd_tree, &pi, offset);
691 else {
692 expert_add_info(pinfo, ti, &ei_otrxd_unknown_dir);
693 offset = 1; /* Only the PDU version was parsed */
696 /* Summary for all parsed PDUs */
697 if (pi.num_pdus == 1) {
698 col_append_fstr(pinfo->cinfo, COL_INFO, "TDMA FN %07u TN %u", pi.fn, pi.tn);
699 if (pi.mod_str != NULL)
700 col_append_fstr(pinfo->cinfo, COL_INFO, ", Modulation %s", pi.mod_str);
701 else if (pi.nope && burst_dir == OTRXCD_DIR_TRX2L1)
702 col_append_str(pinfo->cinfo, COL_INFO, ", NOPE.ind");
703 else if (pi.nope && burst_dir == OTRXCD_DIR_L12TRX)
704 col_append_str(pinfo->cinfo, COL_INFO, ", NOPE.req");
705 } else if (pi.num_pdus > 1) {
706 col_append_fstr(pinfo->cinfo, COL_INFO, "TDMA FN %07u", pi.fn);
707 col_append_fstr(pinfo->cinfo, COL_INFO, ", %u batched PDUs ", pi.num_pdus);
710 proto_item_set_len(ti, offset);
712 /* Let it warn us if there are unhandled tail octets */
713 if ((unsigned) offset < tvb_reported_length(tvb))
714 expert_add_info(pinfo, ti, &ei_otrxd_tail_octets);
716 return offset;
719 /* Dissector for Control commands and responses, and Clock indications */
720 static int dissect_otrxc(tvbuff_t *tvb, packet_info *pinfo,
721 proto_tree *tree, void *data _U_)
723 int offset = 0, msg_len, end_verb, end_status;
724 const uint8_t *msg_str, *msg_type_str;
725 proto_item *ti, *gi, *delim_item;
726 proto_tree *otrxc_tree;
727 uint32_t delimiter;
729 col_set_str(pinfo->cinfo, COL_PROTOCOL, "OsmoTRXC");
730 col_clear(pinfo->cinfo, COL_INFO);
732 msg_len = tvb_reported_length(tvb);
733 msg_str = tvb_get_string_enc(pinfo->pool, tvb, 0, msg_len, ENC_ASCII);
734 col_add_str(pinfo->cinfo, COL_INFO, msg_str);
736 ti = proto_tree_add_item(tree, proto_otrxc, tvb, 0, msg_len, ENC_ASCII);
737 otrxc_tree = proto_item_add_subtree(ti, ett_otrxc);
739 /* Determine the message direction */
740 int msg_dir = otrxcd_get_dir(pinfo);
742 /* A message might be injected by some other program using
743 * a random source port, so let's try to guess by destport. */
744 if (msg_dir == OTRXCD_DIR_UNKNOWN) {
745 expert_add_info(pinfo, ti, &ei_otrxc_injected_msg);
746 if ((msg_dir = otrxcd_guess_dir(pinfo)) == OTRXCD_DIR_UNKNOWN)
747 expert_add_info(pinfo, ti, &ei_otrxc_unknown_dir);
750 /* Add a generated field, so we can filter bursts by direction */
751 gi = proto_tree_add_uint(otrxc_tree, hf_otrxc_msg_dir,
752 tvb, 0, 0, msg_dir);
753 proto_item_set_generated(gi);
755 /* First 3 bytes define a type of the message ("IND", "CMD", "RSP") */
756 proto_tree_add_item_ret_string(otrxc_tree, hf_otrxc_type, tvb, offset, 3,
757 ENC_NA | ENC_ASCII, pinfo->pool,
758 &msg_type_str);
759 offset += 3;
761 /* Determine the message type */
762 enum otrxc_msg_type msg_type = str_to_val((const char *) msg_type_str,
763 otrxc_msg_type_enc,
764 OTRXC_MSG_TYPE_UNKNOWN);
765 proto_item_append_text(ti, ", %s", val_to_str_const(msg_type, otrxc_msg_type_desc,
766 "Unknown message type"));
767 if (msg_type == OTRXC_MSG_TYPE_UNKNOWN) {
768 expert_add_info(pinfo, ti, &ei_otrxc_unknown_msg_type);
769 return offset;
772 /* The message type is separated by a delimiter */
773 delim_item = proto_tree_add_item_ret_uint(otrxc_tree, hf_otrxc_delimiter,
774 tvb, offset, 1, ENC_NA, &delimiter);
775 proto_item_set_hidden(delim_item);
776 offset += 1;
778 /* Delimiter should be a space symbol */
779 if (delimiter != 0x20)
780 expert_add_info(pinfo, delim_item, &ei_otrxc_bad_delimiter);
782 /* The message type is followed by a verb, e.g. "IND CLOCK", "CMD POWEROFF" */
783 end_verb = tvb_find_uint8(tvb, offset, -1, (char) delimiter);
784 if (end_verb < 0) {
785 /* Just a command without parameters, e.g. "CMD POWERON" */
786 proto_tree_add_item(otrxc_tree, hf_otrxc_verb, tvb,
787 offset, -1, ENC_ASCII | ENC_NA);
788 if (msg_type == OTRXC_MSG_TYPE_RESPONSE)
789 expert_add_info(pinfo, ti, &ei_otrxc_rsp_no_code);
790 return tvb_captured_length(tvb);
791 } else {
792 proto_tree_add_item(otrxc_tree, hf_otrxc_verb, tvb,
793 offset, end_verb - offset,
794 ENC_ASCII | ENC_NA);
795 offset = end_verb;
798 /* Another delimiter between the verb and status code / parameters */
799 delim_item = proto_tree_add_item_ret_uint(otrxc_tree, hf_otrxc_delimiter,
800 tvb, offset, 1, ENC_NA, &delimiter);
801 proto_item_set_hidden(delim_item);
802 offset += 1;
804 if (msg_type == OTRXC_MSG_TYPE_RESPONSE) {
805 end_status = tvb_find_uint8(tvb, offset, -1, (char) delimiter);
806 if (end_status > 0) {
807 proto_tree_add_item(otrxc_tree, hf_otrxc_status,
808 tvb, offset, end_status - offset, ENC_ASCII | ENC_NA);
809 offset = end_status;
811 /* Another delimiter between the status code and parameters */
812 delim_item = proto_tree_add_item_ret_uint(otrxc_tree, hf_otrxc_delimiter,
813 tvb, offset, 1, ENC_NA, &delimiter);
814 proto_item_set_hidden(delim_item);
815 offset += 1;
816 } else if (offset < msg_len) {
817 /* Response without parameters, e.g. "RSP POWEROFF 0" */
818 proto_tree_add_item(otrxc_tree, hf_otrxc_status,
819 tvb, offset, msg_len - offset, ENC_ASCII | ENC_NA);
820 return tvb_captured_length(tvb);
821 } else {
822 expert_add_info(pinfo, ti, &ei_otrxc_rsp_no_code);
823 return offset;
827 if (offset < msg_len) {
828 proto_tree_add_item(otrxc_tree, hf_otrxc_params,
829 tvb, offset, -1, ENC_ASCII | ENC_NA);
832 return tvb_captured_length(tvb);
835 void proto_register_osmo_trx(void)
837 static hf_register_info hf_otrxd[] = {
838 /* Common generated field: burst direction */
839 { &hf_otrxd_burst_dir, { "Burst Direction", "osmo_trx.direction",
840 FT_UINT8, BASE_DEC, VALS(otrxcd_dir_vals), 0, NULL, HFILL } },
842 /* Rx/Tx header fields */
843 { &hf_otrxd_pdu_ver, { "PDU Version", "osmo_trxd.pdu_ver",
844 FT_UINT8, BASE_DEC, NULL, 0xf0, NULL, HFILL } },
845 { &hf_otrxd_chdr_reserved, { "Reserved", "osmo_trxd.chdr_reserved",
846 FT_UINT8, BASE_DEC, NULL, 0x08, NULL, HFILL } },
847 { &hf_otrxd_tdma_tn, { "TDMA Timeslot Number", "osmo_trxd.tdma.tn",
848 FT_UINT8, BASE_DEC, NULL, 0x07, NULL, HFILL } },
849 { &hf_otrxd_tdma_fn, { "TDMA Frame Number", "osmo_trxd.tdma.fn",
850 FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL } },
851 { &hf_otrxd_batch_ind, { "BATCH Indication", "osmo_trxd.batch_ind",
852 FT_BOOLEAN, 8, TFS(&otrxd_batch_bool_val), 0x80, NULL, HFILL } },
853 { &hf_otrxd_shadow_ind, { "PDU class", "osmo_trxd.shadow_ind",
854 FT_BOOLEAN, 8, TFS(&otrxd_shadow_bool_val), 0x40, NULL, HFILL } },
855 { &hf_otrxd_trx_num, { "TRX (RF Channel) Number", "osmo_trxd.trx_num",
856 FT_UINT8, BASE_DEC, NULL, 0x3f, NULL, HFILL } },
858 /* Rx header fields */
859 { &hf_otrxd_rssi, { "RSSI", "osmo_trxd.meas.rssi",
860 FT_UINT8, BASE_CUSTOM, CF_FUNC(format_rssi), 0, NULL, HFILL } },
861 { &hf_otrxd_toa256, { "Timing of Arrival", "osmo_trxd.meas.toa256",
862 FT_INT16, BASE_DEC | BASE_UNIT_STRING, UNS(&otrx_units_toa256), 0, NULL, HFILL } },
864 /* MTS (Modulation and Training Sequence) fields */
865 { &hf_otrxd_nope_ind, { "NOPE Indication", "osmo_trxd.nope_ind",
866 FT_BOOLEAN, 8, TFS(&otrxd_nope_bool_val), 0x80, NULL, HFILL } },
867 { &hf_otrxd_nope_ind_pad, { "NOPE Padding", "osmo_trxd.nope_ind_pad",
868 FT_UINT8, BASE_DEC, NULL, 0x7f, NULL, HFILL } },
869 { &hf_otrxd_mod_2b, { "Modulation", "osmo_trxd.mod",
870 FT_UINT8, BASE_DEC, VALS(otrxd_mod_2b_vals), 0x60, NULL, HFILL } },
871 { &hf_otrxd_mod_3b, { "Modulation", "osmo_trxd.mod",
872 FT_UINT8, BASE_DEC, VALS(otrxd_mod_3b_vals), 0x70, NULL, HFILL } },
873 { &hf_otrxd_mod_4b, { "Modulation", "osmo_trxd.mod",
874 FT_UINT8, BASE_DEC, VALS(otrxd_mod_4b_vals), 0x78, NULL, HFILL } },
875 { &hf_otrxd_tsc_set_x2, { "TSC Set", "osmo_trxd.tsc_set",
876 FT_UINT8, BASE_CUSTOM, CF_FUNC(format_tsc_set), 0x08, NULL, HFILL } },
877 { &hf_otrxd_tsc_set_x4, { "TSC Set", "osmo_trxd.tsc_set",
878 FT_UINT8, BASE_CUSTOM, CF_FUNC(format_tsc_set), 0x18, NULL, HFILL } },
879 { &hf_otrxd_tsc, { "TSC (Training Sequence Code)", "osmo_trxd.tsc",
880 FT_UINT8, BASE_DEC, NULL, 0x07, NULL, HFILL } },
881 { &hf_otrxd_ci, { "C/I (Carrier-to-Interference ratio)", "osmo_trxd.meas.ci",
882 FT_INT16, BASE_DEC | BASE_UNIT_STRING, UNS(&units_centibels), 0, NULL, HFILL } },
884 /* Tx header fields */
885 { &hf_otrxd_tx_att, { "Tx Attenuation", "osmo_trxd.tx_att",
886 FT_UINT8, BASE_DEC | BASE_UNIT_STRING, UNS(&units_decibels), 0, NULL, HFILL } },
887 { &hf_otrxd_tx_scpir, { "SCPIR Value", "osmo_trxd.scpir_val",
888 FT_INT8, BASE_DEC | BASE_UNIT_STRING, UNS(&units_decibels), 0, NULL, HFILL } },
889 { &hf_otrxd_tx_rfu, { "Spare padding", "osmo_trxd.spare",
890 FT_BYTES, SEP_SPACE, NULL, 0, NULL, HFILL } },
892 /* Burst soft (255 .. 0) / hard (1 or 0) bits */
893 { &hf_otrxd_soft_symbols, { "Soft-bits", "osmo_trxd.burst.sbits",
894 FT_BYTES, SEP_SPACE, NULL, 0, NULL, HFILL } },
895 { &hf_otrxd_hard_symbols, { "Hard-bits", "osmo_trxd.burst.hbits",
896 FT_BYTES, SEP_SPACE, NULL, 0, NULL, HFILL } },
897 { &hf_otrxd_burst_pad, { "Legacy padding", "osmo_trxd.burst.pad",
898 FT_BYTES, SEP_SPACE, NULL, 0, NULL, HFILL } },
901 static hf_register_info hf_otrxc[] = {
902 /* Common generated field: message direction */
903 { &hf_otrxc_msg_dir, { "Message Direction", "osmo_trx.direction",
904 FT_UINT8, BASE_DEC, VALS(otrxcd_dir_vals), 0, NULL, HFILL } },
906 { &hf_otrxc_type, { "Type", "osmo_trxc.type",
907 FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
908 { &hf_otrxc_delimiter, { "Delimiter", "osmo_trxc.delim",
909 FT_CHAR, BASE_HEX, NULL, 0, NULL, HFILL } },
910 { &hf_otrxc_verb, { "Verb", "osmo_trxc.verb",
911 FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
912 { &hf_otrxc_status, { "Status", "osmo_trxc.status",
913 FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
914 { &hf_otrxc_params, { "Parameters", "osmo_trxc.params",
915 FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
918 static int *ett[] = {
919 &ett_otrxd,
920 &ett_otrxd_rx_pdu,
921 &ett_otrxd_tx_pdu,
922 &ett_otrxc,
925 proto_otrxd = proto_register_protocol("OsmoTRX Data Protocol",
926 "OsmoTRXD", "osmo_trxd");
927 proto_otrxc = proto_register_protocol("OsmoTRX Control / Clock Protocol",
928 "OsmoTRXC", "osmo_trxc");
930 proto_register_field_array(proto_otrxd, hf_otrxd, array_length(hf_otrxd));
931 proto_register_field_array(proto_otrxc, hf_otrxc, array_length(hf_otrxc));
932 proto_register_subtree_array(ett, array_length(ett));
934 static ei_register_info ei_otrxd[] = {
935 { &ei_otrxd_injected_msg, { "osmo_trx.ei.injected_msg",
936 PI_COMMENTS_GROUP, PI_COMMENT, "Injected message", EXPFILL } },
937 { &ei_otrxd_unknown_dir, { "osmo_trx.ei.unknown_dir",
938 PI_UNDECODED, PI_ERROR, "Unknown direction", EXPFILL } },
939 { &ei_otrxd_unknown_pdu_ver, { "osmo_trxd.ei.unknown_pdu_ver",
940 PI_PROTOCOL, PI_ERROR, "Unknown PDU version", EXPFILL } },
941 { &ei_otrxd_tail_octets, { "osmo_trxd.ei.tail_octets",
942 PI_UNDECODED, PI_WARN, "Unhandled tail octets", EXPFILL } },
945 static ei_register_info ei_otrxc[] = {
946 { &ei_otrxc_injected_msg, { "osmo_trx.ei.injected_msg",
947 PI_COMMENTS_GROUP, PI_COMMENT, "Injected message", EXPFILL } },
948 { &ei_otrxc_unknown_dir, { "osmo_trx.ei.unknown_dir",
949 PI_ASSUMPTION, PI_WARN, "Unknown direction", EXPFILL } },
950 { &ei_otrxc_bad_delimiter, { "osmo_trxc.ei.bad_delimiter",
951 PI_PROTOCOL, PI_WARN, "Invalid delimiter", EXPFILL } },
952 { &ei_otrxc_rsp_no_code, { "osmo_trxc.ei.rsp_no_code",
953 PI_PROTOCOL, PI_ERROR, "Response without status code", EXPFILL } },
954 { &ei_otrxc_unknown_msg_type, { "osmo_trxc.ei.unknown_msg_type",
955 PI_PROTOCOL, PI_ERROR, "Unknown message type", EXPFILL } },
958 /* Expert info for OsmoTRXD protocol */
959 expert_module_t *expert_otrxd = expert_register_protocol(proto_otrxd);
960 expert_register_field_array(expert_otrxd, ei_otrxd, array_length(ei_otrxd));
962 /* Expert info for OsmoTRXC protocol */
963 expert_module_t *expert_otrxc = expert_register_protocol(proto_otrxc);
964 expert_register_field_array(expert_otrxc, ei_otrxc, array_length(ei_otrxc));
966 /* Register the dissectors */
967 otrxd_handle = register_dissector("osmo_trxd", dissect_otrxd, proto_otrxd);
968 otrxc_handle = register_dissector("osmo_trxc", dissect_otrxc, proto_otrxc);
971 void proto_reg_handoff_osmo_trx(void)
973 #if 0
974 /* The TRX-side control interface for C(N) is on port P = B + 2N + 1;
975 * the corresponding core-side interface for every socket is at P + 100.
976 * Give a base port B (5700), the master clock interface is at port P = B. */
977 #define OTRXC_UDP_PORTS \
978 "5701,5703,5800,5801,5803," /* The BTS side (osmo-trx, osmo-bts-trx) */ \
979 "6701,6703,6800,6801,6803" /* The MS side (trxcon, fake_trx, grgsm_trx) */
981 /* The data interface is on an odd numbered port P = B + 2N + 2. */
982 #define OTRXD_UDP_PORTS \
983 "5702,5802," /* The BTS side, TRX0 (osmo-trx, osmo-bts-trx) */ \
984 "5704,5804," /* The BTS side, TRX1 (osmo-trx, osmo-bts-trx) */ \
985 "6702,6802" /* The MS side (trxcon, fake_trx, grgsm_trx) */
987 dissector_add_uint_range_with_preference("udp.port", OTRXD_UDP_PORTS, otrxd_handle);
988 dissector_add_uint_range_with_preference("udp.port", OTRXC_UDP_PORTS, otrxc_handle);
989 #endif
991 dissector_add_for_decode_as("udp.port", otrxd_handle);
992 dissector_add_for_decode_as("udp.port", otrxc_handle);
996 * Editor modelines - https://www.wireshark.org/tools/modelines.html
998 * Local variables:
999 * c-basic-offset: 8
1000 * tab-width: 8
1001 * indent-tabs-mode: t
1002 * End:
1004 * vi: set shiftwidth=8 tabstop=8 noexpandtab:
1005 * :indentSize=8:tabSize=8:noTabs=false: