1 /* packetlogger.c
2  * Routines for opening Apple's (Bluetooth) PacketLogger file format captures
3  * Copyright 2008-2009, Stephen Fisher (see AUTHORS file)
4  *
5  * Wireshark - Network traffic analyzer
6  * By Gerald Combs <gerald@wireshark.org>
7  * Copyright 1998 Gerald Combs
8  *
9  * Based on commview.c, Linux's BlueZ-Gnome Analyzer program and hexdumps of
10  * the output files from Apple's PacketLogger tool.
11  *
12  * SPDX-License-Identifier: GPL-2.0-or-later
13  */
14 
15 #include "config.h"
16 
17 #include <stdlib.h>
18 #include <errno.h>
19 #include <string.h>
20 
21 #include "wtap-int.h"
22 #include "file_wrappers.h"
23 #include "packetlogger.h"
24 
25 typedef struct {
26 	gboolean byte_swapped;
27 } packetlogger_t;
28 
29 typedef struct packetlogger_header {
30 	guint32 len;
31 	guint32 ts_secs;
32 	guint32 ts_usecs;
33 } packetlogger_header_t;
34 
35 /* Packet types. */
36 #define PKT_HCI_COMMAND     0x00
37 #define PKT_HCI_EVENT       0x01
38 #define PKT_SENT_ACL_DATA   0x02
39 #define PKT_RECV_ACL_DATA   0x03
40 #define PKT_LMP_SEND        0x0A
41 #define PKT_LMP_RECV        0x0B
42 #define PKT_SYSLOG          0xF7
43 #define PKT_KERNEL          0xF8
44 #define PKT_KERNEL_DEBUG    0xF9
45 #define PKT_ERROR           0xFA
46 #define PKT_POWER           0xFB
47 #define PKT_NOTE            0xFC
48 #define PKT_CONFIG          0xFD
49 #define PKT_NEW_CONTROLLER  0xFE
50 
51 static gboolean packetlogger_read(wtap *wth, wtap_rec *rec, Buffer *buf,
52 				  int *err, gchar **err_info,
53 				  gint64 *data_offset);
54 static gboolean packetlogger_seek_read(wtap *wth, gint64 seek_off,
55 				       wtap_rec *rec,
56 				       Buffer *buf, int *err, gchar **err_info);
57 static gboolean packetlogger_read_header(packetlogger_header_t *pl_hdr,
58 					 FILE_T fh, gboolean byte_swapped,
59 					 int *err, gchar **err_info);
60 static void packetlogger_byte_swap_header(packetlogger_header_t *pl_hdr);
61 static wtap_open_return_val packetlogger_check_record(wtap *wth,
62 						      packetlogger_header_t *pl_hdr,
63 						      int *err,
64 						      gchar **err_info);
65 static gboolean packetlogger_read_packet(wtap *wth, FILE_T fh, wtap_rec *rec,
66 					 Buffer *buf, int *err,
67 					 gchar **err_info);
68 
69 static int packetlogger_file_type_subtype = -1;
70 
71 void register_packetlogger(void);
72 
73 /*
74  * Number of packets to try reading.
75  */
76 #define PACKETS_TO_CHECK	5
77 
78 wtap_open_return_val packetlogger_open(wtap *wth, int *err, gchar **err_info)
79 {
80 	gboolean byte_swapped = FALSE;
81 	packetlogger_header_t pl_hdr;
82 	wtap_open_return_val ret;
83 	packetlogger_t *packetlogger;
84 
85 	/*
86 	 * Try to read the first record.
87 	 */
88 	if(!packetlogger_read_header(&pl_hdr, wth->fh, byte_swapped,
89 	    err, err_info)) {
90 		/*
91 		 * Either an immediate EOF or a short read indicates
92 		 * that the file is probably not a PacketLogger file.
93 		 */
94 		if (*err != 0 && *err != WTAP_ERR_SHORT_READ)
95 			return WTAP_OPEN_ERROR;
96 		return WTAP_OPEN_NOT_MINE;
97 	}
98 
99 	/*
100 	 * If the upper 16 bits of the length are non-zero and the lower
101 	 * 16 bits are zero, assume the file is byte-swapped from our
102 	 * byte order.
103 	 */
104 	if ((pl_hdr.len & 0x0000FFFF) == 0 &&
105 	    (pl_hdr.len & 0xFFFF0000) != 0) {
106 		/*
107 		 * Byte-swap the header.
108 		 */
109 		packetlogger_byte_swap_header(&pl_hdr);
110 		byte_swapped = TRUE;
111 	}
112 
113 	/*
114 	 * Check whether the first record looks like a PacketLogger
115 	 * record.
116 	 */
117 	ret = packetlogger_check_record(wth, &pl_hdr, err, err_info);
118 	if (ret != WTAP_OPEN_MINE) {
119 		/*
120 		 * Either we got an error or it's not valid.
121 		 */
122 		if (ret == WTAP_OPEN_NOT_MINE) {
123 			/*
124 			 * Not valid, so not a PacketLogger file.
125 			 */
126 			return WTAP_OPEN_NOT_MINE;
127 		}
128 
129 		/*
130 		 * Error. If it failed with a short read, we don't fail,
131 		 * so we treat it as a valid file and can then report
132 		 * it as a truncated file.
133 		 */
134 		if (*err != WTAP_ERR_SHORT_READ)
135 			return WTAP_OPEN_ERROR;
136 	} else {
137 		/*
138 		 * Now try reading a few more packets.
139 		 */
140 		for (int i = 1; i < PACKETS_TO_CHECK; i++) {
141 			/*
142 			 * Read and check the file header; we've already
143 			 * decided whether this would be a byte-swapped file
144 			 * or not, so we swap iff we decided it was.
145 			 */
146 			if (!packetlogger_read_header(&pl_hdr, wth->fh,
147 			    byte_swapped, err, err_info)) {
148 				if (*err == 0) {
149 					/* EOF; no more packets to try. */
150 					break;
151 				}
152 
153 				/*
154 				 * A short read indicates that the file
155 				 * is probably not a PacketLogger file.
156 				 */
157 				if (*err != WTAP_ERR_SHORT_READ)
158 					return WTAP_OPEN_ERROR;
159 				return WTAP_OPEN_NOT_MINE;
160 			}
161 
162 			/*
163 			 * Check whether this record looks like a PacketLogger
164 			 * record.
165 			 */
166 			ret = packetlogger_check_record(wth, &pl_hdr, err,
167 			    err_info);
168 			if (ret != WTAP_OPEN_MINE) {
169 				/*
170 				 * Either we got an error or it's not valid.
171 				 */
172 				if (ret == WTAP_OPEN_NOT_MINE) {
173 					/*
174 					 * Not valid, so not a PacketLogger
175 					 * file.
176 					 */
177 					return WTAP_OPEN_NOT_MINE;
178 				}
179 
180 				/*
181 				 * Error. If it failed with a short read,
182 				 * we don't fail, we just stop checking
183 				 * records, so we treat it as a valid file
184 				 * and can then report it as a truncated file.
185 				 */
186 				if (*err != WTAP_ERR_SHORT_READ)
187 					return WTAP_OPEN_ERROR;
188 				break;
189 			}
190 		}
191 	}
192 
193 	/* No file header. Reset the fh to 0 so we can read the first packet */
194 	if (file_seek(wth->fh, 0, SEEK_SET, err) == -1)
195 		return WTAP_OPEN_ERROR;
196 
197 	/* This is a PacketLogger file */
198 	packetlogger = g_new(packetlogger_t, 1);
199 	packetlogger->byte_swapped = byte_swapped;
200 	wth->priv = (void *)packetlogger;
201 
202 	/* Set up the pointers to the handlers for this file type */
203 	wth->subtype_read = packetlogger_read;
204 	wth->subtype_seek_read = packetlogger_seek_read;
205 
206 	wth->file_type_subtype = packetlogger_file_type_subtype;
207 	wth->file_encap = WTAP_ENCAP_PACKETLOGGER;
208 	wth->file_tsprec = WTAP_TSPREC_USEC;
209 
210 	/*
211 	 * Add an IDB; we don't know how many interfaces were
212 	 * involved, so we just say one interface, about which
213 	 * we only know the link-layer type, snapshot length,
214 	 * and time stamp resolution.
215 	 */
216 	wtap_add_generated_idb(wth);
217 
218 	return WTAP_OPEN_MINE; /* Our kind of file */
219 }
220 
221 static gboolean
222 packetlogger_read(wtap *wth, wtap_rec *rec, Buffer *buf, int *err,
223 		  gchar **err_info, gint64 *data_offset)
224 {
225 	*data_offset = file_tell(wth->fh);
226 
227 	return packetlogger_read_packet(wth, wth->fh, rec, buf, err, err_info);
228 }
229 
230 static gboolean
231 packetlogger_seek_read(wtap *wth, gint64 seek_off, wtap_rec *rec,
232 		       Buffer *buf, int *err, gchar **err_info)
233 {
234 	if(file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1)
235 		return FALSE;
236 
237 	if(!packetlogger_read_packet(wth, wth->random_fh, rec, buf, err, err_info)) {
238 		if(*err == 0)
239 			*err = WTAP_ERR_SHORT_READ;
240 
241 		return FALSE;
242 	}
243 	return TRUE;
244 }
245 
246 static gboolean
247 packetlogger_read_header(packetlogger_header_t *pl_hdr, FILE_T fh,
248 			 gboolean byte_swapped, int *err, gchar **err_info)
249 {
250 	if (!wtap_read_bytes_or_eof(fh, &pl_hdr->len, 4, err, err_info))
251 		return FALSE;
252 	if (!wtap_read_bytes(fh, &pl_hdr->ts_secs, 4, err, err_info))
253 		return FALSE;
254 	if (!wtap_read_bytes(fh, &pl_hdr->ts_usecs, 4, err, err_info))
255 		return FALSE;
256 
257 	/* Convert multi-byte values to host endian */
258 	if (byte_swapped)
259 		packetlogger_byte_swap_header(pl_hdr);
260 
261 	return TRUE;
262 }
263 
264 static void
265 packetlogger_byte_swap_header(packetlogger_header_t *pl_hdr)
266 {
267 	pl_hdr->len = GUINT32_SWAP_LE_BE(pl_hdr->len);
268 	pl_hdr->ts_secs = GUINT32_SWAP_LE_BE(pl_hdr->ts_secs);
269 	pl_hdr->ts_usecs = GUINT32_SWAP_LE_BE(pl_hdr->ts_usecs);
270 }
271 
272 static wtap_open_return_val
273 packetlogger_check_record(wtap *wth, packetlogger_header_t *pl_hdr, int *err,
274     gchar **err_info)
275 {
276 	guint32 length;
277 	guint8 type;
278 
279 	/* Is the header length valid?  If not, assume it's not ours. */
280 	if (pl_hdr->len < 8 || pl_hdr->len >= 65536)
281 		return WTAP_OPEN_NOT_MINE;
282 
283 	/* Is the microseconds field of the time stap out of range? */
284 	if (pl_hdr->ts_usecs >= 1000000)
285 		return WTAP_OPEN_NOT_MINE;
286 
287 	/*
288 	 * If we have any payload, it's a type field; read and check it.
289 	 */
290 	length = pl_hdr->len - 8;
291 	if (length != 0) {
292 		/*
293 		 * Check the type field.
294 		 */
295 		if (!wtap_read_bytes(wth->fh, &type, 1, err, err_info)) {
296 			if (*err != WTAP_ERR_SHORT_READ)
297 				return WTAP_OPEN_ERROR;
298 			return WTAP_OPEN_NOT_MINE;
299 		}
300 
301 		/* Verify this file belongs to us */
302 		switch (type) {
303 
304 		case PKT_HCI_COMMAND:
305 		case PKT_HCI_EVENT:
306 		case PKT_SENT_ACL_DATA:
307 		case PKT_RECV_ACL_DATA:
308 		case PKT_LMP_SEND:
309 		case PKT_LMP_RECV:
310 		case PKT_SYSLOG:
311 		case PKT_KERNEL:
312 		case PKT_KERNEL_DEBUG:
313 		case PKT_ERROR:
314 		case PKT_POWER:
315 		case PKT_NOTE:
316 		case PKT_CONFIG:
317 		case PKT_NEW_CONTROLLER:
318 			break;
319 
320 		default:
321 			return WTAP_OPEN_NOT_MINE;
322 		}
323 
324 		length--;
325 
326 		if (length != 0) {
327 			/*
328 			 * Now try to read past the rest of the packet bytes;
329 			 * if that fails with a short read, we don't fail,
330 			 * so that we can report the file as a truncated
331 			 * PacketLogger file.
332 			 */
333 			if (!wtap_read_bytes(wth->fh, NULL, length,
334 			    err, err_info))
335 				return WTAP_OPEN_ERROR;
336 		}
337 	}
338 	return WTAP_OPEN_MINE;
339 }
340 
341 static gboolean
342 packetlogger_read_packet(wtap *wth, FILE_T fh, wtap_rec *rec, Buffer *buf,
343 			 int *err, gchar **err_info)
344 {
345 	packetlogger_t *packetlogger = (packetlogger_t *)wth->priv;
346 	packetlogger_header_t pl_hdr;
347 
348 	if(!packetlogger_read_header(&pl_hdr, fh, packetlogger->byte_swapped,
349 	    err, err_info))
350 		return FALSE;
351 
352 	if (pl_hdr.len < 8) {
353 		*err = WTAP_ERR_BAD_FILE;
354 		*err_info = g_strdup_printf("packetlogger: record length %u is too small", pl_hdr.len);
355 		return FALSE;
356 	}
357 	if (pl_hdr.len - 8 > WTAP_MAX_PACKET_SIZE_STANDARD) {
358 		/*
359 		 * Probably a corrupt capture file; don't blow up trying
360 		 * to allocate space for an immensely-large packet.
361 		 */
362 		*err = WTAP_ERR_BAD_FILE;
363 		*err_info = g_strdup_printf("packetlogger: File has %u-byte packet, bigger than maximum of %u",
364 		    pl_hdr.len - 8, WTAP_MAX_PACKET_SIZE_STANDARD);
365 		return FALSE;
366 	}
367 
368 	rec->rec_type = REC_TYPE_PACKET;
369 	rec->block = wtap_block_create(WTAP_BLOCK_PACKET);
370 	rec->presence_flags = WTAP_HAS_TS;
371 
372 	rec->rec_header.packet_header.len = pl_hdr.len - 8;
373 	rec->rec_header.packet_header.caplen = pl_hdr.len - 8;
374 
375 	rec->ts.secs = (time_t)pl_hdr.ts_secs;
376 	rec->ts.nsecs = (int)(pl_hdr.ts_usecs * 1000);
377 
378 	return wtap_read_packet_bytes(fh, buf, rec->rec_header.packet_header.caplen, err, err_info);
379 }
380 
381 static const struct supported_block_type packetlogger_blocks_supported[] = {
382 	/*
383 	 * We support packet blocks, with no comments or other options.
384 	 */
385 	{ WTAP_BLOCK_PACKET, MULTIPLE_BLOCKS_SUPPORTED, NO_OPTIONS_SUPPORTED }
386 };
387 
388 static const struct file_type_subtype_info packetlogger_info = {
389 	"macOS PacketLogger", "pklg", "pklg", NULL,
390 	FALSE, BLOCKS_SUPPORTED(packetlogger_blocks_supported),
391 	NULL, NULL, NULL
392 };
393 
394 void register_packetlogger(void)
395 {
396 	packetlogger_file_type_subtype = wtap_register_file_type_subtype(&packetlogger_info);
397 
398 	/*
399 	 * Register name for backwards compatibility with the
400 	 * wtap_filetypes table in Lua.
401 	 */
402 	wtap_register_backwards_compatibility_lua_name("PACKETLOGGER",
403 	    packetlogger_file_type_subtype);
404 }
405 
406 /*
407  * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
408  *
409  * Local variables:
410  * c-basic-offset: 8
411  * tab-width: 8
412  * indent-tabs-mode: t
413  * End:
414  *
415  * vi: set shiftwidth=8 tabstop=8 noexpandtab:
416  * :indentSize=8:tabSize=8:noTabs=false:
417  */
418