1644d9f42ad17a90ac1bd8816a3359194f1bb158
[obnox/wireshark/wip.git] / wiretap / airopeek9.c
1 /* airopeek9.c
2  * Routines for opening EtherPeek and AiroPeek V9 files
3  *
4  * $Id$
5  *
6  * Wiretap Library
7  * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu>
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License
11  * as published by the Free Software Foundation; either version 2
12  * of the License, or (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27 #include <errno.h>
28 #include <string.h>
29 #include <stdlib.h>
30 #include "wtap-int.h"
31 #include "file_wrappers.h"
32 #include "buffer.h"
33 #include "airopeek9.h"
34
35 /* CREDITS
36  *
37  * This file decoder could not have been writen without examining
38  * http://www.varsanofiev.com/inside/airopeekv9.htm, the help from
39  * Martin Regner and Guy Harris, and the etherpeek.c file.
40  */
41
42 /*
43  * NOTE: it says "airopeek" because the first files seen that use this
44  * format were AiroPeek files; however, EtherPeek files using it have
45  * also been seen.
46  */
47
48 /* section header */
49 typedef struct airopeek_section_header {
50         gint8   section_id[4];
51         guint32 section_len;
52         guint32 section_const;
53 } airopeek_section_header_t;
54
55 /*
56  * Network subtype values.
57  *
58  * XXX - do different network subtype values for 802.11 indicate different
59  * network adapter types, with some adapters supplying the FCS and others
60  * not supplying the FCS?
61  */
62 #define AIROPEEK_V9_NST_ETHERNET                0
63 #define AIROPEEK_V9_NST_802_11                  1       /* 802.11 with 0's at the end */
64 #define AIROPEEK_V9_NST_802_11_2                2       /* 802.11 with 0's at the end */
65 #define AIROPEEK_V9_NST_802_11_WITH_FCS         3       /* 802.11 with FCS at the end */
66
67 /* tags for fields in packet header */
68 #define TAG_AIROPEEK_V9_LENGTH                  0x0000
69 #define TAG_AIROPEEK_V9_TIMESTAMP_LOWER         0x0001
70 #define TAG_AIROPEEK_V9_TIMESTAMP_UPPER         0x0002
71 #define TAG_AIROPEEK_V9_FLAGS_AND_STATUS        0x0003
72 #define TAG_AIROPEEK_V9_CHANNEL                 0x0004
73 #define TAG_AIROPEEK_V9_RATE                    0x0005
74 #define TAG_AIROPEEK_V9_SIGNAL_PERC             0x0006
75 #define TAG_AIROPEEK_V9_SIGNAL_DBM              0x0007
76 #define TAG_AIROPEEK_V9_NOISE_PERC              0x0008
77 #define TAG_AIROPEEK_V9_NOISE_DBM               0x0009
78 #define TAG_AIROPEEK_V9_UNKNOWN_0x000D          0x000D
79 #define TAG_AIROPEEK_V9_SLICE_LENGTH            0xffff
80
81 /* 64-bit time in nanoseconds from the (Windows FILETIME) epoch */
82 typedef struct airopeek_utime {
83         guint32 upper;
84         guint32 lower;
85 } airopeek_utime;
86
87 static gboolean airopeekv9_read(wtap *wth, int *err, gchar **err_info,
88     gint64 *data_offset);
89 static gboolean airopeekv9_seek_read(wtap *wth, gint64 seek_off,
90     union wtap_pseudo_header *pseudo_header, guchar *pd, int length,
91     int *err, gchar **err_info);
92 static void airopeekv9_close(wtap *wth);
93
94 static int wtap_file_read_pattern (wtap *wth, const char *pattern, int *err)
95 {
96     int c;
97     const char *cp;
98
99     cp = pattern;
100     while (*cp)
101     {
102         c = file_getc(wth->fh);
103         if (c == EOF) {
104             if (file_eof(wth->fh))
105                 return 0;       /* EOF */
106             else {
107                 /* We (presumably) got an error (there's no equivalent to
108                    "ferror()" in zlib, alas, so we don't have a wrapper
109                    to check for an error). */
110                 *err = file_error(wth->fh);
111                 return -1;      /* error */
112             }
113         }
114         if (c == *cp)
115             cp++;
116         else
117         {
118             if (c == pattern[0])
119                 cp = &pattern[1];
120             else
121                 cp = pattern;
122         }
123     }
124     return (*cp == '\0' ? 1 : 0);
125 }
126
127
128 static int wtap_file_read_till_separator (wtap *wth, char *buffer, int buflen,
129                                         const char *separators, int *err)
130 {
131     int c;
132     char *cp;
133     int i;
134
135     for (cp = buffer, i = 0; i < buflen; i++, cp++)
136     {
137         c = file_getc(wth->fh);
138         if (c == EOF) {
139             if (file_eof(wth->fh))
140                 return 0;       /* EOF */
141             else {
142                 /* We (presumably) got an error (there's no equivalent to
143                    "ferror()" in zlib, alas, so we don't have a wrapper
144                    to check for an error). */
145                 *err = file_error(wth->fh);
146                 return -1;      /* error */
147             }
148         }
149         if (strchr (separators, c) != NULL)
150         {
151             *cp = '\0';
152             break;
153         }
154         else
155             *cp = c;
156     }
157     return i;
158 }
159
160
161 static int wtap_file_read_number (wtap *wth, guint32 *num, int *err)
162 {
163     int ret;
164     char str_num[12];
165     unsigned long value;
166     char *p;
167
168     ret = wtap_file_read_till_separator (wth, str_num, sizeof (str_num)-1, "<",
169                                          err);
170     if (ret != 1) {
171         /* 0 means EOF, which means "not a valid AiroPeek V9 file";
172            -1 means error, and "err" has been set. */
173         return ret;
174     }
175     value = strtoul (str_num, &p, 10);
176     if (p == str_num || value > G_MAXUINT32)
177         return 0;
178     *num = (guint32)value;
179     return 1;
180 }
181
182
183 int airopeek9_open(wtap *wth, int *err, gchar **err_info)
184 {
185     airopeek_section_header_t ap_hdr;
186     int ret;
187     guint32 fileVersion;
188     guint32 mediaType;
189     guint32 mediaSubType = 0;
190     int file_encap;
191     static const int airopeek9_encap[] = {
192         WTAP_ENCAP_ETHERNET,
193         WTAP_ENCAP_IEEE_802_11_WITH_RADIO,
194         WTAP_ENCAP_IEEE_802_11_WITH_RADIO,
195         WTAP_ENCAP_IEEE_802_11_WITH_RADIO
196     };
197     #define NUM_AIROPEEK9_ENCAPS (sizeof airopeek9_encap / sizeof airopeek9_encap[0])
198
199     wtap_file_read_unknown_bytes(&ap_hdr, sizeof(ap_hdr), wth->fh, err);
200
201     if (memcmp (ap_hdr.section_id, "\177ver", sizeof(ap_hdr.section_id)) != 0)
202         return 0;       /* doesn't begin with a "\177ver" section */
203
204     /*
205      * XXX - we should get the length of the "\177ver" section, check
206      * that it's followed by a little-endian 0x00000200, and then,
207      * when reading the XML, make sure we don't go past the end of
208      * that section, and skip to the end of that section when
209      * we have the file version (and possibly check to make sure all
210      * tags are properly opened and closed).
211      */
212     ret = wtap_file_read_pattern (wth, "<FileVersion>", err);
213     if (ret != 1) {
214         /* 0 means EOF, which means "not a valid AiroPeek V9 file";
215            -1 means error, and "err" has been set. */
216         return ret;
217     }
218     ret = wtap_file_read_number (wth, &fileVersion, err);
219     if (ret != 1) {
220         /* 0 means EOF, which means "not a valid AiroPeek V9 file";
221            -1 means error, and "err" has been set. */
222         return ret;
223     }
224
225     /* If we got this far, we assume it's an AiroPeek V9 file. */
226     if (fileVersion != 9) {
227         /* We only support version 9. */
228         *err = WTAP_ERR_UNSUPPORTED;
229         *err_info = g_strdup_printf("airopeekv9: version %u unsupported",
230             fileVersion);
231         return -1;
232     }
233
234     /*
235      * XXX - once we've skipped the "\177ver" section, we should
236      * check for a "sess" section and fail if we don't see it.
237      * Then we should get the length of the "sess" section, check
238      * that it's followed by a little-endian 0x00000200, and then,
239      * when reading the XML, make sure we don't go past the end of
240      * that section, and skip to the end of the section when
241      * we have the file version (and possibly check to make sure all
242      * tags are properly opened and closed).
243      */
244     ret = wtap_file_read_pattern (wth, "<MediaType>", err);
245     if (ret == -1)
246         return -1;
247     if (ret == 0) {
248         *err = WTAP_ERR_UNSUPPORTED;
249         *err_info = g_strdup("airopeekv9: <MediaType> tag not found");
250         return -1;
251     }
252     /* XXX - this appears to be 0 in both the EtherPeek and AiroPeek
253        files we've seen; should we require it to be 0? */
254     ret = wtap_file_read_number (wth, &mediaType, err);
255     if (ret == -1)
256         return -1;
257     if (ret == 0) {
258         *err = WTAP_ERR_UNSUPPORTED;
259         *err_info = g_strdup("airopeekv9: <MediaType> value not found");
260         return -1;
261     }
262
263     ret = wtap_file_read_pattern (wth, "<MediaSubType>", err);
264     if (ret == -1)
265         return -1;
266     if (ret == 0) {
267         *err = WTAP_ERR_UNSUPPORTED;
268         *err_info = g_strdup("airopeekv9: <MediaSubType> tag not found");
269         return -1;
270     }
271     ret = wtap_file_read_number (wth, &mediaSubType, err);
272     if (ret == -1)
273         return -1;
274     if (ret == 0) {
275         *err = WTAP_ERR_UNSUPPORTED;
276         *err_info = g_strdup("airopeekv9: <MediaSubType> value not found");
277         return -1;
278     }
279     if (mediaSubType >= NUM_AIROPEEK9_ENCAPS
280         || airopeek9_encap[mediaSubType] == WTAP_ENCAP_UNKNOWN) {
281         *err = WTAP_ERR_UNSUPPORTED_ENCAP;
282         *err_info = g_strdup_printf("airopeekv9: network type %u unknown or unsupported",
283             mediaSubType);
284         return -1;
285     }
286
287     ret = wtap_file_read_pattern (wth, "pkts", err);
288     if (ret == -1)
289         return -1;
290     if (ret == 0) {
291         *err = WTAP_ERR_SHORT_READ;
292         return -1;
293     }
294
295     /* skip 8 zero bytes */
296     if (file_seek (wth->fh, 8L, SEEK_CUR, err) == -1)
297         return 0;
298
299     /*
300      * This is an EtherPeek or AiroPeek V9 file.
301      */
302     wth->data_offset = file_tell (wth->fh);
303
304     file_encap = airopeek9_encap[mediaSubType];
305
306     wth->file_type = WTAP_FILE_AIROPEEK_V9;
307     wth->file_encap = file_encap;
308     wth->subtype_read = airopeekv9_read;
309     wth->subtype_seek_read = airopeekv9_seek_read;
310     wth->subtype_close = airopeekv9_close;
311     wth->tsprecision = WTAP_FILE_TSPREC_NSEC;
312
313     wth->capture.airopeek9 = (airopeek9_t *)g_malloc(sizeof(airopeek9_t));
314     switch (mediaSubType) {
315
316     case AIROPEEK_V9_NST_ETHERNET:
317     case AIROPEEK_V9_NST_802_11:
318     case AIROPEEK_V9_NST_802_11_2:
319         wth->capture.airopeek9->has_fcs = FALSE;
320         break;
321
322     case AIROPEEK_V9_NST_802_11_WITH_FCS:
323         wth->capture.airopeek9->has_fcs = TRUE;
324         break;
325     }
326
327     wth->snapshot_length   = 0; /* not available in header */
328
329     return 1;
330 }
331
332 typedef struct {
333     guint32 length;
334     guint32 sliceLength;
335     airopeek_utime timestamp;
336     struct ieee_802_11_phdr ieee_802_11;
337 } hdr_info_t;
338
339 /*
340  * Process the packet header.
341  *
342  * XXX - we should supply the additional radio information;
343  * the pseudo-header should probably be supplied in a fashion
344  * similar to the new BSD radio header, so that the 802.11
345  * dissector can determine which, if any, information items
346  * are present.
347  */
348 static int
349 airopeekv9_process_header(FILE_T fh, hdr_info_t *hdr_info, int *err,
350     gchar **err_info)
351 {
352     int header_len = 0;
353     int bytes_read;
354     guint8 tag_value[6];
355     guint16 tag;
356     gboolean saw_length = FALSE;
357     gboolean saw_timestamp_lower = FALSE;
358     gboolean saw_timestamp_upper = FALSE;
359
360     /* Extract the fields from the packet header */
361     do {
362         /* Get the tag and value.
363            XXX - this assumes all values are 4 bytes long. */
364         bytes_read = file_read(tag_value, 1, sizeof tag_value, fh);
365         if (bytes_read != (int) sizeof tag_value) {
366             *err = file_error(fh);
367             if (*err == 0) {
368                 if (bytes_read > 0)
369                     *err = WTAP_ERR_SHORT_READ;
370                 else if (bytes_read == 0) {
371                     /*
372                      * Short read if we've read something already;
373                      * just an EOF if we haven't.
374                      */
375                     if (header_len != 0)
376                         *err = WTAP_ERR_SHORT_READ;
377                 }
378             }
379             return 0;
380         }
381         header_len += (int) sizeof(tag_value);
382         tag = pletohs(&tag_value[0]);
383         switch (tag) {
384
385         case TAG_AIROPEEK_V9_LENGTH:
386             if (saw_length) {
387                 *err = WTAP_ERR_BAD_RECORD;
388                 *err_info = g_strdup("airopeekv9: record has two length fields");
389                 return 0;
390             }
391             hdr_info->length = pletohl(&tag_value[2]);
392             saw_length = TRUE;
393             break;
394     
395         case TAG_AIROPEEK_V9_TIMESTAMP_LOWER:
396             if (saw_timestamp_lower) {
397                 *err = WTAP_ERR_BAD_RECORD;
398                 *err_info = g_strdup("airopeekv9: record has two timestamp-lower fields");
399                 return 0;
400             }
401             hdr_info->timestamp.lower = pletohl(&tag_value[2]);
402             saw_timestamp_lower = TRUE;
403             break;
404
405         case TAG_AIROPEEK_V9_TIMESTAMP_UPPER:
406             if (saw_timestamp_upper) {
407                 *err = WTAP_ERR_BAD_RECORD;
408                 *err_info = g_strdup("airopeekv9: record has two timestamp-upper fields");
409                 return 0;
410             }
411             hdr_info->timestamp.upper = pletohl(&tag_value[2]);
412             saw_timestamp_upper = TRUE;
413             break;
414
415         case TAG_AIROPEEK_V9_FLAGS_AND_STATUS:
416             /* XXX - not used yet */
417             break;
418
419         case TAG_AIROPEEK_V9_CHANNEL:
420             hdr_info->ieee_802_11.channel = pletohl(&tag_value[2]);
421             break;
422
423         case TAG_AIROPEEK_V9_RATE:
424             hdr_info->ieee_802_11.data_rate = pletohl(&tag_value[2]);
425             break;
426
427         case TAG_AIROPEEK_V9_SIGNAL_PERC:
428             hdr_info->ieee_802_11.signal_level = pletohl(&tag_value[2]);
429             break;
430
431         case TAG_AIROPEEK_V9_SIGNAL_DBM:
432             /* XXX - not used yet */
433             break;
434
435         case TAG_AIROPEEK_V9_NOISE_PERC:
436             /* XXX - not used yet */
437             break;
438
439         case TAG_AIROPEEK_V9_NOISE_DBM:
440             /* XXX - not used yet */
441             break;
442
443         case TAG_AIROPEEK_V9_UNKNOWN_0x000D:
444             /* XXX - seen in an EtherPeek capture; value unknown */
445             break;
446
447         case TAG_AIROPEEK_V9_SLICE_LENGTH:
448             hdr_info->sliceLength = pletohl(&tag_value[2]);
449             break;
450
451         default:
452             break;
453         }
454     } while (tag != TAG_AIROPEEK_V9_SLICE_LENGTH);      /* last tag */
455
456     if (!saw_length) {
457         *err = WTAP_ERR_BAD_RECORD;
458         *err_info = g_strdup("airopeekv9: record has no length field");
459         return 0;
460     }
461     if (!saw_timestamp_lower) {
462         *err = WTAP_ERR_BAD_RECORD;
463         *err_info = g_strdup("airopeekv9: record has no timestamp-lower field");
464         return 0;
465     }
466     if (!saw_timestamp_upper) {
467         *err = WTAP_ERR_BAD_RECORD;
468         *err_info = g_strdup("airopeekv9: record has no timestamp-upper field");
469         return 0;
470     }
471
472     return header_len;
473 }
474
475 /*
476  * Time stamps appear to be in nanoseconds since the Windows epoch
477  * as used in FILETIMEs, i.e. midnight, January 1, 1601.
478  *
479  * This magic number came from "nt_time_to_nstime()" in "packet-smb.c".
480  * 1970-1601 is 369; I'm not sure what the extra 3 days and 6 hours are
481  * that are being subtracted.
482  */
483 #define TIME_FIXUP_CONSTANT (369.0*365.25*24*60*60-(3.0*24*60*60+6.0*60*60))
484
485 static gboolean airopeekv9_read(wtap *wth, int *err, gchar **err_info,
486     gint64 *data_offset)
487 {
488     hdr_info_t hdr_info;
489     int hdrlen;
490     double  t;
491
492     *data_offset = wth->data_offset;
493
494     /* Process the packet header. */
495     hdrlen = airopeekv9_process_header(wth->fh, &hdr_info, err, err_info);
496     if (hdrlen == 0)
497         return FALSE;
498     wth->data_offset += hdrlen;
499
500     /* force sliceLength to be the actual length of the packet */
501     if (hdr_info.sliceLength == 0)
502         hdr_info.sliceLength = hdr_info.length;
503
504     /* fill in packet header length values before slicelength may be
505        adjusted */
506     wth->phdr.len    = hdr_info.length;
507     wth->phdr.caplen = hdr_info.sliceLength;
508
509     /* read the frame data */
510     buffer_assure_space(wth->frame_buffer, hdr_info.sliceLength);
511     wtap_file_read_expected_bytes(buffer_start_ptr(wth->frame_buffer),
512                                   hdr_info.sliceLength, wth->fh, err);
513     wth->data_offset += hdr_info.sliceLength;
514
515     /* recalculate and fill in packet time stamp */
516     t =  (double) hdr_info.timestamp.lower +
517          (double) hdr_info.timestamp.upper * 4294967296.0;
518
519     t *= 1.0e-9;
520     t -= TIME_FIXUP_CONSTANT;
521     wth->phdr.ts.secs  = (time_t) t;
522     wth->phdr.ts.nsecs = (guint32) ((t - wth->phdr.ts.secs)*1000000000);
523
524     switch (wth->file_encap) {
525
526     case WTAP_ENCAP_IEEE_802_11_WITH_RADIO:
527         /*
528          * The last 4 bytes sometimes contains the FCS but on a lot of
529          * interfaces these are zero.  Is there some way to determine
530          * from the packet header whether it's an FCS or not?
531          *
532          * For now, we just discard those bytes; if we can determine
533          * whether it's an FCS or not, we should use that to determine
534          * whether to supply it as an FCS or discard it.
535          */
536         wth->pseudo_header.ieee_802_11 = hdr_info.ieee_802_11;
537         if (wth->capture.airopeek9->has_fcs)
538             wth->pseudo_header.ieee_802_11.fcs_len = 4;
539         else {
540             wth->pseudo_header.ieee_802_11.fcs_len = 0;
541             wth->phdr.len -= 4;
542             wth->phdr.caplen -= 4;
543         }
544         break;
545
546     case WTAP_ENCAP_ETHERNET:
547         /*
548          * The last 4 bytes appear to be 0 in the captures I've seen;
549          * are there any captures where it's an FCS?
550          */
551         wth->pseudo_header.eth.fcs_len = 0;
552         wth->phdr.len -= 4;
553         wth->phdr.caplen -= 4;
554         break;
555     }
556
557     return TRUE;
558 }
559
560
561 static gboolean
562 airopeekv9_seek_read(wtap *wth, gint64 seek_off,
563     union wtap_pseudo_header *pseudo_header, guchar *pd, int length,
564     int *err, gchar **err_info)
565 {
566     hdr_info_t hdr_info;
567
568     if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1)
569         return FALSE;
570
571     /* Process the packet header. */
572     if (airopeekv9_process_header(wth->random_fh, &hdr_info, err, err_info) == -1)
573         return FALSE;
574
575     switch (wth->file_encap) {
576
577     case WTAP_ENCAP_IEEE_802_11_WITH_RADIO:
578         pseudo_header->ieee_802_11 = hdr_info.ieee_802_11;
579         if (wth->capture.airopeek9->has_fcs)
580             pseudo_header->ieee_802_11.fcs_len = 4;
581         else
582             pseudo_header->ieee_802_11.fcs_len = 0;
583         break;
584
585     case WTAP_ENCAP_ETHERNET:
586         pseudo_header->eth.fcs_len = 0;
587         break;
588     }
589
590     /*
591      * XXX - should "errno" be set in "wtap_file_read_expected_bytes()"?
592      */
593     errno = WTAP_ERR_CANT_READ;
594     wtap_file_read_expected_bytes(pd, length, wth->random_fh, err);
595     return TRUE;
596 }
597
598 static void
599 airopeekv9_close(wtap *wth)
600 {
601     g_free(wth->capture.airopeek9);
602 }