1 /* dct3trace.c
2  * Routines for reading signalling traces generated by Gammu (www.gammu.org)
3  * from Nokia DCT3 phones in Netmonitor mode.
4  *
5  * gammu --nokiadebug nhm5_587.txt v18-19
6  *
7  * Duncan Salerno <duncan.salerno@googlemail.com>
8  *
9  * Wiretap Library
10  * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu>
11  *
12  * SPDX-License-Identifier: GPL-2.0-or-later
13  */
14 
15 #include "config.h"
16 #include "wtap-int.h"
17 #include "dct3trace.h"
18 #include "file_wrappers.h"
19 
20 #include <stdlib.h>
21 #include <string.h>
22 #include <errno.h>
23 
24 #include <wsutil/strtoi.h>
25 
26 /*
27    Example downlink data:
28 
29 <?xml version="1.0"?>
30 <dump>
31 <l1 direction="down" logicalchannel="96" physicalchannel="19" sequence="268116" error="0" timeshift="2992" bsic="22" data="31063F100DD0297A53E1020103C802398E0B2B2B2B2B2B" >
32 <l2 data="063F100DD0297A53E1020103" rest="C802398E0B2B2B2B2B2B" >
33 </l2>
34 </l1>
35 </dump>
36 
37    Example uplink data (no raw L1):
38 
39 <?xml version="1.0"?>
40 <dump>
41 <l1 direction="up" logicalchannel="112" >
42 <l2 type="U" subtype="Unknown" p="0" data="061500400000000000000000000000000000" >
43 </l2>
44 </l1>
45 </dump>
46 
47  */
48 
49 
50 /* Magic text to check */
51 static const char dct3trace_magic_line1[] = "<?xml version=\"1.0\"?>";
52 static const char dct3trace_magic_line2[] = "<dump>";
53 static const char dct3trace_magic_record_start[]  = "<l1 ";
54 static const char dct3trace_magic_record_end[]  = "</l1>";
55 static const char dct3trace_magic_l2_start[]  = "<l2 ";
56 #if 0 /* Not used ?? */
57 static const char dct3trace_magic_l2_end[]  = "</l2>";
58 #endif
59 static const char dct3trace_magic_end[]  = "</dump>";
60 
61 #define MAX_PACKET_LEN 23
62 
63 static gboolean dct3trace_read(wtap *wth, wtap_rec *rec,
64 	Buffer *buf, int *err, gchar **err_info, gint64 *data_offset);
65 static gboolean dct3trace_seek_read(wtap *wth, gint64 seek_off,
66 	wtap_rec *rec, Buffer *buf, int *err, gchar **err_info);
67 
68 static int dct3trace_file_type_subtype = -1;
69 
70 void register_dct3trace(void);
71 
72 /*
73  * Following 3 functions taken from gsmdecode-0.7bis, with permission:
74  *
75  *   https://web.archive.org/web/20091218112927/http://wiki.thc.org/gsm
76  */
77 
78 static int
hc2b(unsigned char hex)79 hc2b(unsigned char hex)
80 {
81 	hex = g_ascii_tolower(hex);
82 	if ((hex >= '0') && (hex <= '9'))
83 		return hex - '0';
84 	if ((hex >= 'a') && (hex <= 'f'))
85 		return hex - 'a' + 10;
86 	return -1;
87 }
88 
89 static int
hex2bin(guint8 * out,guint8 * out_end,char * in)90 hex2bin(guint8 *out, guint8 *out_end, char *in)
91 {
92 	guint8 *out_start = out;
93 	int is_low = 0;
94 	int c;
95 
96 	while (*in != '\0')
97 	{
98 		c = hc2b(*(unsigned char *)in);
99 		if (c < 0)
100 		{
101 			in++;
102 			continue;
103 		}
104 		if (out == out_end)
105 		{
106 			/* Too much data */
107 			return -1;
108 		}
109 		if (is_low == 0)
110 		{
111 			*out = c << 4;
112 			is_low = 1;
113 		} else {
114 			*out |= (c & 0x0f);
115 			is_low = 0;
116 			out++;
117 		}
118 		in++;
119 	}
120 
121 	return (int)(out - out_start);
122 }
123 
124 static gboolean
xml_get_int(int * val,const char * str,const char * pattern,int * err,gchar ** err_info)125 xml_get_int(int *val, const char *str, const char *pattern, int *err, gchar **err_info)
126 {
127 	const char *ptr, *endptr;
128 	char *start, *end;
129 	char buf[32];
130 
131 	ptr = strstr(str, pattern);
132 	if (ptr == NULL) {
133 		*err = WTAP_ERR_BAD_FILE;
134 		*err_info = g_strdup_printf("dct3trace: %s not found", pattern);
135 		return FALSE;
136 	}
137 	/*
138 	 * XXX - should we just skip past the pattern and check for ="?
139 	 */
140 	start = strchr(ptr, '"');
141 	if (start == NULL) {
142 		*err = WTAP_ERR_BAD_FILE;
143 		*err_info = g_strdup_printf("dct3trace: opening quote for %s not found", pattern);
144 		return FALSE;
145 	}
146 	start++;
147 	/*
148 	 * XXX - should we just use ws_strtoi32() and check whether
149 	 * the character following the number is a "?
150 	 */
151 	end = strchr(start, '"');
152 	if (end == NULL) {
153 		*err = WTAP_ERR_BAD_FILE;
154 		*err_info = g_strdup_printf("dct3trace: closing quote for %s not found", pattern);
155 		return FALSE;
156 	}
157 	if (end - start > 31) {
158 		*err = WTAP_ERR_BAD_FILE;
159 		*err_info = g_strdup_printf("dct3trace: %s value is too long", pattern);
160 		return FALSE;
161 	}
162 
163 	memcpy(buf, start, end - start);
164 	buf[end - start] = '\0';
165 	/*
166 	 * XXX - should we allow negative numbers in all cases?  Or are
167 	 * there cases where the number is unsigned?
168 	 */
169 	if (!ws_strtoi32(buf, &endptr, val)) {
170 		*err = WTAP_ERR_BAD_FILE;
171 		if (errno == ERANGE) {
172 			if (*val < 0)
173 				*err_info = g_strdup_printf("dct3trace: %s value is too small, minimum is %d", pattern, *val);
174 			else
175 				*err_info = g_strdup_printf("dct3trace: %s value is too large, maximum is %d", pattern, *val);
176 		} else
177 			*err_info = g_strdup_printf("dct3trace: %s value \"%s\" not a number", pattern, buf);
178 		return FALSE;
179 	}
180 	if (*endptr != '\0') {
181 		*err = WTAP_ERR_BAD_FILE;
182 		*err_info = g_strdup_printf("dct3trace: %s value \"%s\" not a number", pattern, buf);
183 		return FALSE;
184 	}
185 	return TRUE;
186 }
187 
188 
dct3trace_open(wtap * wth,int * err,gchar ** err_info)189 wtap_open_return_val dct3trace_open(wtap *wth, int *err, gchar **err_info)
190 {
191 	char line1[64], line2[64];
192 
193 	/* Look for Gammu DCT3 trace header */
194 	if (file_gets(line1, sizeof(line1), wth->fh) == NULL ||
195 		file_gets(line2, sizeof(line2), wth->fh) == NULL)
196 	{
197 		*err = file_error(wth->fh, err_info);
198 		if (*err != 0 && *err != WTAP_ERR_SHORT_READ)
199 			return WTAP_OPEN_ERROR;
200 		return WTAP_OPEN_NOT_MINE;
201 	}
202 
203 	/* Don't compare line endings */
204 	if( strncmp(dct3trace_magic_line1, line1, strlen(dct3trace_magic_line1)) != 0 ||
205 		strncmp(dct3trace_magic_line2, line2, strlen(dct3trace_magic_line2)) != 0)
206 	{
207 		return WTAP_OPEN_NOT_MINE;
208 	}
209 
210 	wth->file_encap = WTAP_ENCAP_GSM_UM;
211 	wth->file_type_subtype = dct3trace_file_type_subtype;
212 	wth->snapshot_length = 0; /* not known */
213 	wth->subtype_read = dct3trace_read;
214 	wth->subtype_seek_read = dct3trace_seek_read;
215 	wth->file_tsprec = WTAP_TSPREC_SEC;
216 
217 	/*
218 	 * Add an IDB; we don't know how many interfaces were
219 	 * involved, so we just say one interface, about which
220 	 * we only know the link-layer type, snapshot length,
221 	 * and time stamp resolution.
222 	 */
223 	wtap_add_generated_idb(wth);
224 
225 	return WTAP_OPEN_MINE;
226 }
227 
228 
dct3trace_get_packet(FILE_T fh,wtap_rec * rec,Buffer * buf,int * err,gchar ** err_info)229 static gboolean dct3trace_get_packet(FILE_T fh, wtap_rec *rec,
230 	Buffer *buf, int *err, gchar **err_info)
231 {
232 	char line[1024];
233 	guint8 databuf[MAX_PACKET_LEN], *bufp;
234 	gboolean have_data = FALSE;
235 	int len = 0;
236 
237 	bufp = &databuf[0];
238 	while (file_gets(line, sizeof(line), fh) != NULL)
239 	{
240 		if( memcmp(dct3trace_magic_end, line, strlen(dct3trace_magic_end)) == 0 )
241 		{
242 			/* Return on end of file </dump> */
243 			*err = 0;
244 			return FALSE;
245 		}
246 		else if( memcmp(dct3trace_magic_record_end, line, strlen(dct3trace_magic_record_end)) == 0 )
247 		{
248 			/* Return on end of record </l1> */
249 			if( have_data )
250 			{
251 				/* We've got a full packet! */
252 				rec->rec_type = REC_TYPE_PACKET;
253 				rec->block = wtap_block_create(WTAP_BLOCK_PACKET);
254 				rec->presence_flags = 0; /* no time stamp, no separate "on the wire" length */
255 				rec->ts.secs = 0;
256 				rec->ts.nsecs = 0;
257 				rec->rec_header.packet_header.caplen = len;
258 				rec->rec_header.packet_header.len = len;
259 
260 				*err = 0;
261 
262 				/* Make sure we have enough room for the packet */
263 				ws_buffer_assure_space(buf, rec->rec_header.packet_header.caplen);
264 				memcpy( ws_buffer_start_ptr(buf), databuf, rec->rec_header.packet_header.caplen );
265 
266 				return TRUE;
267 			}
268 			else
269 			{
270 				/* If not got any data return error */
271 				*err = WTAP_ERR_BAD_FILE;
272 				*err_info = g_strdup("dct3trace: record without data");
273 				return FALSE;
274 			}
275 		}
276 		else if( memcmp(dct3trace_magic_record_start, line, strlen(dct3trace_magic_record_start)) == 0 )
277 		{
278 			/* Parse L1 header <l1 ...>*/
279 			int channel, tmp;
280 			char *ptr;
281 
282 			rec->rec_header.packet_header.pseudo_header.gsm_um.uplink = !strstr(line, "direction=\"down\"");
283 			if (!xml_get_int(&channel, line, "logicalchannel", err, err_info))
284 				return FALSE;
285 
286 			/* Parse downlink only fields */
287 			if( !rec->rec_header.packet_header.pseudo_header.gsm_um.uplink )
288 			{
289 				if (!xml_get_int(&tmp, line, "physicalchannel", err, err_info))
290 					return FALSE;
291 				rec->rec_header.packet_header.pseudo_header.gsm_um.arfcn = tmp;
292 				if (!xml_get_int(&tmp, line, "sequence", err, err_info))
293 					return FALSE;
294 				rec->rec_header.packet_header.pseudo_header.gsm_um.tdma_frame = tmp;
295 				if (!xml_get_int(&tmp, line, "bsic", err, err_info))
296 					return FALSE;
297 				rec->rec_header.packet_header.pseudo_header.gsm_um.bsic = tmp;
298 				if (!xml_get_int(&tmp, line, "error", err, err_info))
299 					return FALSE;
300 				rec->rec_header.packet_header.pseudo_header.gsm_um.error = tmp;
301 				if (!xml_get_int(&tmp, line, "timeshift", err, err_info))
302 					return FALSE;
303 				rec->rec_header.packet_header.pseudo_header.gsm_um.timeshift = tmp;
304 			}
305 
306 			switch( channel )
307 			{
308 				case 128: rec->rec_header.packet_header.pseudo_header.gsm_um.channel = GSM_UM_CHANNEL_SDCCH; break;
309 				case 112: rec->rec_header.packet_header.pseudo_header.gsm_um.channel = GSM_UM_CHANNEL_SACCH; break;
310 				case 176: rec->rec_header.packet_header.pseudo_header.gsm_um.channel = GSM_UM_CHANNEL_FACCH; break;
311 				case 96: rec->rec_header.packet_header.pseudo_header.gsm_um.channel = GSM_UM_CHANNEL_CCCH; break;
312 				case 80: rec->rec_header.packet_header.pseudo_header.gsm_um.channel = GSM_UM_CHANNEL_BCCH; break;
313 				default: rec->rec_header.packet_header.pseudo_header.gsm_um.channel = GSM_UM_CHANNEL_UNKNOWN; break;
314 			}
315 
316 			/* Read data (if have it) into databuf */
317 			ptr = strstr(line, "data=\"");
318 			if( ptr )
319 			{
320 				have_data = TRUE; /* If has data... */
321 				len = hex2bin(bufp, &databuf[MAX_PACKET_LEN], ptr+6);
322 				if (len == -1)
323 				{
324 					*err = WTAP_ERR_BAD_FILE;
325 					*err_info = g_strdup_printf("dct3trace: record length %d too long", rec->rec_header.packet_header.caplen);
326 					return FALSE;
327 				}
328 			}
329 		}
330 		else if( !have_data && memcmp(dct3trace_magic_l2_start, line, strlen(dct3trace_magic_l2_start)) == 0 )
331 		{
332 			/* For uplink packets we might not get the raw L1, so have to recreate it from the L2 */
333 			/* Parse L2 header if didn't get data from L1 <l2 ...> */
334 			int data_len;
335 			char *ptr = strstr(line, "data=\"");
336 
337 			if( !ptr )
338 			{
339 				continue;
340 			}
341 
342 			have_data = TRUE;
343 
344 			/*
345 			 * We know we have no data already, so we know
346 			 * we have enough room for the header.
347 			 */
348 			if( rec->rec_header.packet_header.pseudo_header.gsm_um.channel == GSM_UM_CHANNEL_SACCH || rec->rec_header.packet_header.pseudo_header.gsm_um.channel == GSM_UM_CHANNEL_FACCH || rec->rec_header.packet_header.pseudo_header.gsm_um.channel == GSM_UM_CHANNEL_SDCCH )
349 			{
350 				/* Add LAPDm B header */
351 				memset(bufp, 0x1, 2);
352 				len = 3;
353 			}
354 			else
355 			{
356 				/* Add LAPDm Bbis header */
357 				len = 1;
358 			}
359 			bufp += len;
360 
361 			data_len = hex2bin(bufp, &databuf[MAX_PACKET_LEN], ptr+6);
362 			if (data_len == -1)
363 			{
364 				*err = WTAP_ERR_BAD_FILE;
365 				*err_info = g_strdup_printf("dct3trace: record length %d too long", rec->rec_header.packet_header.caplen);
366 				return FALSE;
367 			}
368 			len += data_len;
369 
370 			/* Add LAPDm length byte */
371 			*(bufp - 1) = data_len << 2 | 0x1;
372 		}
373 	}
374 
375 	*err = file_error(fh, err_info);
376 	if (*err == 0)
377 	{
378 		*err = WTAP_ERR_SHORT_READ;
379 	}
380 	return FALSE;
381 }
382 
383 
384 /* Find the next packet and parse it; called from wtap_read(). */
dct3trace_read(wtap * wth,wtap_rec * rec,Buffer * buf,int * err,gchar ** err_info,gint64 * data_offset)385 static gboolean dct3trace_read(wtap *wth, wtap_rec *rec, Buffer *buf,
386     int *err, gchar **err_info, gint64 *data_offset)
387 {
388 	*data_offset = file_tell(wth->fh);
389 
390 	return dct3trace_get_packet(wth->fh, rec, buf, err, err_info);
391 }
392 
393 
394 /* Used to read packets in random-access fashion */
dct3trace_seek_read(wtap * wth,gint64 seek_off,wtap_rec * rec,Buffer * buf,int * err,gchar ** err_info)395 static gboolean dct3trace_seek_read(wtap *wth, gint64 seek_off,
396 	wtap_rec *rec, Buffer *buf, int *err, gchar **err_info)
397 {
398 	if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1)
399 	{
400 		return FALSE;
401 	}
402 
403 	return dct3trace_get_packet(wth->random_fh, rec, buf, err, err_info);
404 }
405 
406 static const struct supported_block_type dct3trace_blocks_supported[] = {
407 	/*
408 	 * We support packet blocks, with no comments or other options.
409 	 */
410 	{ WTAP_BLOCK_PACKET, MULTIPLE_BLOCKS_SUPPORTED, NO_OPTIONS_SUPPORTED }
411 };
412 
413 static const struct file_type_subtype_info dct3trace_info = {
414 	"Gammu DCT3 trace", "dct3trace", "xml", NULL,
415 	FALSE, BLOCKS_SUPPORTED(dct3trace_blocks_supported),
416 	NULL, NULL, NULL
417 };
418 
register_dct3trace(void)419 void register_dct3trace(void)
420 {
421 	dct3trace_file_type_subtype = wtap_register_file_type_subtype(&dct3trace_info);
422 
423 	/*
424 	 * Register name for backwards compatibility with the
425 	 * wtap_filetypes table in Lua.
426 	 */
427 	wtap_register_backwards_compatibility_lua_name("DCT3TRACE",
428 	    dct3trace_file_type_subtype);
429 }
430 
431 /*
432  * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
433  *
434  * Local variables:
435  * c-basic-offset: 8
436  * tab-width: 8
437  * indent-tabs-mode: t
438  * End:
439  *
440  * vi: set shiftwidth=8 tabstop=8 noexpandtab:
441  * :indentSize=8:tabSize=8:noTabs=false:
442  */
443