Drop count is 64 bits
[obnox/wireshark/wip.git] / wiretap / network_instruments.c
index c6c19bec36a6d27daed4c3cff580251b5268891f..a574934a223dcc90c5c698f0e2bea5636d1db751 100644 (file)
@@ -3,7 +3,7 @@
  */
 
 /***************************************************************************
-                          NetworkInstruments.c  -  description
+                          network_instruments.c  -  description
                              -------------------
     begin                : Wed Oct 29 2003
     copyright            : (C) 2003 by root
@@ -18,7 +18,7 @@
  *   (at your option) any later version.                                   *
  *                                                                         *
  ***************************************************************************/
+
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
 #include "buffer.h"
 #include "network_instruments.h"
 
-static const char network_instruments_magic[] = {"ObserverPktBufferVersion=09.00"};
+static const char network_instruments_magic[] = {"ObserverPktBufferVersion=15.00"};
 static const int true_magic_length = 17;
 
 static const guint32 observer_packet_magic = 0x88888888;
 
-static const int observer_encap[] = {
-       WTAP_ENCAP_ETHERNET,
-       WTAP_ENCAP_TOKEN_RING
-};
-#define NUM_OBSERVER_ENCAPS (sizeof observer_encap / sizeof observer_encap[0])
-
-static const int from_wtap_encap[] = {
-       OBSERVER_UNDEFINED,
-       OBSERVER_ETHERNET,
-       OBSERVER_TOKENRING,
-};
-#define NUM_FROM_WTAP_ENCAPS (sizeof from_wtap_encap / sizeof observer_encap[0])
-
-#define CAPTUREFILE_HEADER_SIZE sizeof(capture_file_header)
+/*
+ * This structure is used to keep state when writing files. An instance is
+ * allocated for each file, and its address is stored in the wtap_dumper.priv
+ * pointer field.
+ */
+typedef struct {
+    guint64 packet_count;
+    guint8  network_type;
+    guint32 time_format;
+} observer_dump_private_state;
 
 /*
- * The time in Observer files is in nanoseconds since midnight, January 1,
- * 2000, 00:00:00 local time.
+ * Some time offsets are calculated in advance here, when the first Observer
+ * file is opened for reading or writing, and are then used to adjust frame
+ * timestamps as they are read or written.
+ *
+ * The Wiretap API expects timestamps in nanoseconds relative to
+ * January 1, 1970, 00:00:00 GMT (the Wiretap epoch).
  *
- * We want the seconds portion to be seconds since midnight, January 1,
- * 1970, 00:00:00 GMT.
+ * Observer versions before 13.10 encode frame timestamps in nanoseconds
+ * relative to January 1, 2000, 00:00:00 local time (the Observer epoch).
+ * Versions 13.10 and later switch over to GMT encoding. Which encoding was used
+ * when saving the file is identified via the time format TLV following
+ * the file header.
  *
- * To do that, we add the number of seconds between midnight, January 1,
- * 2000, 00:00:00 local time and midnight, January 1, 1970, 00:00:00 GMT.
- * (That gets the wrong answer if the time zone is being read in a different
- * time zone, but there's not much we can do about that.)
+ * Unfortunately, even though Observer versions before 13.10 saved in local
+ * time, they didn't include the timezone from which the frames were captured,
+ * so converting to GMT correctly from all timezones is impossible. So an
+ * assumption is made that the file is being read from within the same timezone
+ * that it was written.
+ *
+ * All code herein is normalized to versions 13.10 and later, special casing for
+ * versions earlier. In other words, timestamps are worked with as if
+ * they are GMT-encoded, and adjustments from local time are made only if
+ * the source file warrants it.
+ *
+ * All destination files are saved in GMT format.
  */
-static gboolean have_time_offset;
-static time_t seconds1970to2000;
+static const time_t ansi_to_observer_epoch_offset = 946684800;
+static time_t gmt_to_localtime_offset = (time_t) -1;
 
-static void init_time_offset(void)
+static void init_gmt_to_localtime_offset(void)
 {
-       if (!have_time_offset) {
-               struct tm midnight_2000_01_01;
-
-               /*
-                * Get the number of seconds between midnight, January 1,
-                * 2000, 00:00:00 local time - that's just the UNIX
-                * time stamp for 2000-01-01 00:00:00 local time.
-                */
-               midnight_2000_01_01.tm_year = 2000 - 1900;
-               midnight_2000_01_01.tm_mon = 0;
-               midnight_2000_01_01.tm_mday = 1;
-               midnight_2000_01_01.tm_hour = 0;
-               midnight_2000_01_01.tm_min = 0;
-               midnight_2000_01_01.tm_sec = 0;
-               midnight_2000_01_01.tm_isdst = -1;
-               seconds1970to2000 = mktime(&midnight_2000_01_01);
-               have_time_offset = TRUE;
-       }
+    if (gmt_to_localtime_offset == (time_t) -1) {
+        time_t ansi_epoch_plus_one_day = 86400;
+        struct tm gmt_tm;
+        struct tm local_tm;
+
+        /*
+         * Compute the local time zone offset as the number of seconds west
+         * of GMT. There's no obvious cross-platform API for querying this
+         * directly. As a workaround, GMT and local tm structures are populated
+         * relative to the ANSI time_t epoch (plus one day to ensure that
+         * local time stays after 1970/1/1 00:00:00). They are then converted
+         * back to time_t as if they were both local times, resulting in the
+         * time zone offset being the difference between them.
+         */
+        gmt_tm = *gmtime(&ansi_epoch_plus_one_day);
+        local_tm = *localtime(&ansi_epoch_plus_one_day);
+        local_tm.tm_isdst = 0;
+        gmt_to_localtime_offset = mktime(&gmt_tm) - mktime(&local_tm);
+    }
 }
 
 static gboolean observer_read(wtap *wth, int *err, gchar **err_info,
-    long *data_offset);
-static gboolean observer_seek_read(wtap *wth, long seek_off,
-    union wtap_pseudo_header *pseudo_header, guchar *pd, int length,
-    int *err, gchar **err_info);
-static int read_packet_header(FILE_T fh, packet_entry_header *packet_header,
+    gint64 *data_offset);
+static gboolean observer_seek_read(wtap *wth, gint64 seek_off,
+    union wtap_pseudo_header *pseudo_header, guint8 *pd, int length,
     int *err, gchar **err_info);
-static gboolean read_packet_data(FILE_T fh, int offset_to_frame, int offset,
+static int read_packet_header(FILE_T fh, union wtap_pseudo_header *pseudo_header, 
+    packet_entry_header *packet_header, int *err, gchar **err_info);
+static int read_packet_data(FILE_T fh, int offset_to_frame, int current_offset_from_packet_header,
     guint8 *pd, int length, int *err, char **err_info);
-static gboolean skip_to_next_packet(wtap *wth, int offset,
-    int offset_to_next_packet, int *err, char **err_info);
-static gboolean observer_dump_close(wtap_dumper *wdh, int *err);
+static gboolean skip_to_next_packet(wtap *wth, int offset_to_next_packet,
+    int current_offset_from_packet_header, int *err, char **err_info);
 static gboolean observer_dump(wtap_dumper *wdh, const struct wtap_pkthdr *phdr,
-    const union wtap_pseudo_header *pseudo_header, const guchar *pd, int *err);
+    const union wtap_pseudo_header *pseudo_header, const guint8 *pd, int *err);
+static gint observer_to_wtap_encap(int observer_encap);
+static gint wtap_to_observer_encap(int wtap_encap);
 
 int network_instruments_open(wtap *wth, int *err, gchar **err_info)
 {
-       int bytes_read;
-
-       capture_file_header file_header;
-       packet_entry_header packet_header;
-
-       errno = WTAP_ERR_CANT_READ;
-
-       /* Read in the buffer file header */
-       bytes_read = file_read(&file_header, sizeof file_header, 1, wth->fh);
-       if (bytes_read != sizeof file_header) {
-               *err = file_error(wth->fh);
-               if (*err != 0)
-                       return -1;
-               return 0;
-       }
-
-       /* check the magic number */
-       if (memcmp(file_header.observer_version, network_instruments_magic, true_magic_length)!=0) {
-               return 0;
-       }
-
-       /* check the version */
-       if (strncmp(network_instruments_magic, file_header.observer_version, 30)!=0) {
-               *err = WTAP_ERR_UNSUPPORTED_ENCAP;
-               *err_info = g_strdup_printf("Observer: unsupported file version %s", file_header.observer_version);
-               return -1;
-       }
-
-       /* get to the first packet */
-       file_header.offset_to_first_packet =
-           GUINT16_FROM_LE(file_header.offset_to_first_packet);
-       if (file_seek(wth->fh, file_header.offset_to_first_packet, SEEK_SET,
-           err) == -1) {
-               if (*err != 0)
-                       return -1;
-               return 0;
-       }
-
-       /* pull off the packet header */
-       bytes_read = file_read(&packet_header, sizeof packet_header, 1, wth->fh);
-       if (bytes_read != sizeof packet_header) {
-               *err = file_error(wth->fh);
-               if (*err != 0)
-                       return -1;
-               return 0;
-       }
-
-       /* check the packet's magic number; the magic number is all 8's,
-          so the byte order doesn't matter */
-       if (packet_header.packet_magic != observer_packet_magic) {
-               *err = WTAP_ERR_UNSUPPORTED_ENCAP;
-               *err_info = g_strdup_printf("Observer: unsupported packet version %ul", packet_header.packet_magic);
-               return -1;
-       }
-
-       /* Check the data link type. */
-       if (packet_header.network_type >= NUM_OBSERVER_ENCAPS) {
-               *err = WTAP_ERR_UNSUPPORTED_ENCAP;
-               *err_info = g_strdup_printf("Observer: network type %u unknown or unsupported", packet_header.network_type);
-               return -1;
-       }
-       wth->file_encap = observer_encap[packet_header.network_type];
-
-       wth->file_type = WTAP_FILE_NETWORK_INSTRUMENTS_V9;
-
-       /* set up the rest of the capture parameters */
-       wth->subtype_read = observer_read;
-       wth->subtype_seek_read = observer_seek_read;
-       wth->subtype_close = NULL;
-       wth->subtype_sequential_close = NULL;
-       wth->snapshot_length = 0;
-       wth->tsprecision = WTAP_FILE_TSPREC_USEC;
-
-       /* reset the pointer to the first packet */
-       if (file_seek(wth->fh, file_header.offset_to_first_packet, SEEK_SET,
-           err) == -1) {
-               if (*err != 0)
-                       return -1;
-               return 0;
-       }
-       wth->data_offset = file_header.offset_to_first_packet;
-
-       init_time_offset();
-
-       return 1;
+    int bytes_read;
+    int offset;
+    capture_file_header file_header;
+    guint i;
+    tlv_header tlvh;
+    int seek_increment;
+    int header_offset;
+    packet_entry_header packet_header;
+    observer_dump_private_state * private_state = NULL;
+
+    errno = WTAP_ERR_CANT_READ;
+    offset = 0;
+
+    /* read in the buffer file header */
+    bytes_read = file_read(&file_header, sizeof file_header, wth->fh);
+    if (bytes_read != sizeof file_header) {
+        *err = file_error(wth->fh, err_info);
+        if (*err != 0)
+            return -1;
+        return 0;
+    }
+    offset += bytes_read;
+    CAPTURE_FILE_HEADER_FROM_LE_IN_PLACE(file_header);
+
+    /* check if version info is present */
+    if (memcmp(file_header.observer_version, network_instruments_magic, true_magic_length)!=0) {
+        return 0;
+    }
+
+    /* initialize the private state */
+    private_state = (observer_dump_private_state *) g_malloc(sizeof(observer_dump_private_state));
+    private_state->time_format = TIME_INFO_LOCAL;
+    wth->priv = (void *) private_state;
+
+    /* get the location of the first packet */
+    /* v15 and newer uses high byte offset, in previous versions it will be 0 */
+    header_offset = file_header.offset_to_first_packet + ((int)(file_header.offset_to_first_packet_high_byte)<<16);
+
+    /* process extra information */
+    for (i = 0; i < file_header.number_of_information_elements; i++) {
+        /* for safety break if we've reached the first packet */
+        if (offset >= header_offset)
+            break;
+
+        /* read the TLV header */
+        bytes_read = file_read(&tlvh, sizeof tlvh, wth->fh);
+        if (bytes_read != sizeof tlvh) {
+            *err = file_error(wth->fh, err_info);
+            if (*err == 0)
+                *err = WTAP_ERR_SHORT_READ;
+            return -1;
+        }
+        offset += bytes_read;
+        TLV_HEADER_FROM_LE_IN_PLACE(tlvh);
+
+        if (tlvh.length < sizeof tlvh) {
+            *err = WTAP_ERR_BAD_FILE;
+            *err_info = g_strdup_printf("Observer: bad record (TLV length %u < %lu)",
+                tlvh.length, (unsigned long)sizeof tlvh);
+            return -1;
+        }
+
+        /* process (or skip over) the current TLV */
+        switch (tlvh.type) {
+        case INFORMATION_TYPE_TIME_INFO:
+            bytes_read = file_read(&private_state->time_format, sizeof private_state->time_format, wth->fh);
+            if (bytes_read != sizeof private_state->time_format) {
+                *err = file_error(wth->fh, err_info);
+                if(*err == 0)
+                    *err = WTAP_ERR_SHORT_READ;
+                return -1;
+            }
+            private_state->time_format = GUINT32_FROM_LE(private_state->time_format);
+            offset += bytes_read;
+            break;
+        default:
+            seek_increment = tlvh.length - (int)sizeof tlvh;
+            if (seek_increment > 0) {
+                if (file_seek(wth->fh, seek_increment, SEEK_CUR, err) == -1)
+                    return -1;
+            }
+            offset += seek_increment;
+        }
+    }
+
+    /* get to the first packet */
+    if (header_offset < offset) {
+        *err = WTAP_ERR_BAD_FILE;
+        *err_info = g_strdup_printf("Observer: bad record (offset to first packet %d < %d)",
+            header_offset, offset);
+        return FALSE;
+    }
+    seek_increment = header_offset - offset;
+    if (seek_increment > 0) {
+        if (file_seek(wth->fh, seek_increment, SEEK_CUR, err) == -1)
+            return -1;
+    }
+
+    /* pull off the packet header */
+    bytes_read = file_read(&packet_header, sizeof packet_header, wth->fh);
+    if (bytes_read != sizeof packet_header) {
+        *err = file_error(wth->fh, err_info);
+        if (*err != 0)
+            return -1;
+        return 0;
+    }
+    PACKET_ENTRY_HEADER_FROM_LE_IN_PLACE(packet_header);
+
+    /* check the packet's magic number */
+    if (packet_header.packet_magic != observer_packet_magic) {
+        *err = WTAP_ERR_UNSUPPORTED_ENCAP;
+        *err_info = g_strdup_printf("Observer: unsupported packet version %ul", packet_header.packet_magic);
+        return -1;
+    }
+
+    /* check the data link type */
+    if (observer_to_wtap_encap(packet_header.network_type) == WTAP_ENCAP_UNKNOWN) {
+        *err = WTAP_ERR_UNSUPPORTED_ENCAP;
+        *err_info = g_strdup_printf("Observer: network type %u unknown or unsupported", packet_header.network_type);
+        return -1;
+    }
+    wth->file_encap = observer_to_wtap_encap(packet_header.network_type);
+
+    /* set up the rest of the capture parameters */
+    private_state->packet_count = 0;
+    private_state->network_type = wtap_to_observer_encap(wth->file_encap);
+    wth->subtype_read = observer_read;
+    wth->subtype_seek_read = observer_seek_read;
+    wth->subtype_close = NULL;
+    wth->subtype_sequential_close = NULL;
+    wth->snapshot_length = 0;    /* not available in header */
+    wth->tsprecision = WTAP_FILE_TSPREC_NSEC;
+    wth->file_type = WTAP_FILE_NETWORK_INSTRUMENTS;
+
+    /* reset the pointer to the first packet */
+    if (file_seek(wth->fh, header_offset, SEEK_SET,
+        err) == -1)
+        return -1;
+    wth->data_offset = header_offset;
+
+    init_gmt_to_localtime_offset();
+
+    return 1;
 }
 
-/* reads the next packet */
+/* Reads the next packet. */
 static gboolean observer_read(wtap *wth, int *err, gchar **err_info,
-    long *data_offset)
+    gint64 *data_offset)
 {
-       int offset;
-       packet_entry_header packet_header;
-
-       /*
-        * Skip records other than data records.
-        */
-       for (;;) {
-               *data_offset = wth->data_offset;
-
-               /* process the packet header, including TLVs */
-               offset = read_packet_header(wth->fh, &packet_header, err,
-                   err_info);
-               if (offset <= 0)
-                       return FALSE;   /* EOF or error */
-
-               wth->data_offset += offset;
-
-               if (packet_header.packet_type == PACKET_TYPE_DATA_PACKET)
-                       break;
-
-               /* skip to next packet */
-               packet_header.offset_to_next_packet =
-                   GUINT16_FROM_LE(packet_header.offset_to_next_packet);
-               if (!skip_to_next_packet(wth, offset,
-                   packet_header.offset_to_next_packet, err, err_info))
-                       return FALSE;   /* EOF or error */
-       }
-
-       /* set-up the packet header */
-       packet_header.network_size =
-           GUINT16_FROM_LE(packet_header.network_size);
-       /* neglect frame markers for wiretap */
-       if (packet_header.network_size < 4) {
-               *err = WTAP_ERR_BAD_RECORD;
-               *err_info = g_strdup_printf("Observer: bad record: Packet length %u < 4",
-                   packet_header.network_size);
-               return FALSE;
-       }
-       packet_header.network_size -= 4;
-       packet_header.captured_size =
-           GUINT16_FROM_LE(packet_header.captured_size);
-       wth->phdr.pkt_encap = observer_encap[packet_header.network_type];
-       wth->phdr.len    = packet_header.network_size;
-       wth->phdr.caplen = MIN(packet_header.captured_size, wth->phdr.len);
-       packet_header.nano_seconds_since_2000 =
-           GUINT64_FROM_LE(packet_header.nano_seconds_since_2000);
-       wth->phdr.ts.secs =
-           (time_t) (packet_header.nano_seconds_since_2000/1000000000 + seconds1970to2000);
-       wth->phdr.ts.nsecs = packet_header.nano_seconds_since_2000%1000000000;
-
-       /* set-up the packet buffer */
-       buffer_assure_space(wth->frame_buffer, packet_header.captured_size);
-
-       /* read data */
-       if (!read_packet_data(wth->fh, packet_header.offset_to_frame, offset,
-           buffer_start_ptr(wth->frame_buffer), packet_header.captured_size,
-           err, err_info))
-               return FALSE;
-       wth->data_offset += packet_header.captured_size;
-       offset += packet_header.captured_size;
-
-       /* update the pseudo header */
-       switch (wth->file_encap) {
-
-       case WTAP_ENCAP_ETHERNET:
-               /* There is no FCS in the frame */
-               wth->pseudo_header.eth.fcs_len = 0;
-               break;
-       }
-
-       return TRUE;
+    int bytes_consumed;
+    int offset_from_packet_header = 0;
+    packet_entry_header packet_header;
+
+    /* skip records other than data records */
+    for (;;) {
+        *data_offset = wth->data_offset;
+
+        /* process the packet header, including TLVs */
+        bytes_consumed = read_packet_header(wth->fh, &wth->pseudo_header, &packet_header, err,
+            err_info);
+        if (bytes_consumed <= 0)
+            return FALSE;    /* EOF or error */
+
+        wth->data_offset += bytes_consumed;
+
+        if (packet_header.packet_type == PACKET_TYPE_DATA_PACKET)
+            break;
+
+        /* skip to next packet */
+        offset_from_packet_header = (int) (wth->data_offset - *data_offset);
+        if (!skip_to_next_packet(wth, packet_header.offset_to_next_packet,
+                offset_from_packet_header, err, err_info)) {
+            return FALSE;    /* EOF or error */
+        }
+    }
+
+    /* neglect frame markers for wiretap */
+    if (packet_header.network_size < 4) {
+        *err = WTAP_ERR_BAD_FILE;
+        *err_info = g_strdup_printf("Observer: bad record: Packet length %u < 4",
+            packet_header.network_size);
+        return FALSE;
+    }
+
+    /* set the wiretap packet header fields */
+    wth->phdr.pkt_encap = observer_to_wtap_encap(packet_header.network_type);
+    if(wth->file_encap == WTAP_ENCAP_FIBRE_CHANNEL_FC2_WITH_FRAME_DELIMS) {
+        wth->phdr.len = packet_header.network_size;
+        wth->phdr.caplen = packet_header.captured_size;
+    } else {
+        wth->phdr.len = packet_header.network_size - 4;
+        wth->phdr.caplen = MIN(packet_header.captured_size, wth->phdr.len);
+    }
+
+    /* set the wiretap timestamp, assuming for the moment that Observer encoded it in GMT */
+    wth->phdr.ts.secs = (time_t) ((packet_header.nano_seconds_since_2000 / 1000000000) + ansi_to_observer_epoch_offset);
+    wth->phdr.ts.nsecs = (int) (packet_header.nano_seconds_since_2000 % 1000000000);
+
+    /* adjust to local time, if necessary, also accounting for DST if the frame
+       was captured while it was in effect */
+    if (((observer_dump_private_state*)wth->priv)->time_format == TIME_INFO_LOCAL)
+    {
+        struct tm daylight_tm;
+        struct tm standard_tm;
+        time_t    dst_offset;
+
+        /* the Observer timestamp was encoded as local time, so add a
+           correction from local time to GMT */
+        wth->phdr.ts.secs += gmt_to_localtime_offset;
+
+        /* perform a DST adjustment if necessary */
+        standard_tm = *localtime(&wth->phdr.ts.secs);
+        if (standard_tm.tm_isdst > 0) {
+            daylight_tm = standard_tm;
+            standard_tm.tm_isdst = 0;
+            dst_offset = mktime(&standard_tm) - mktime(&daylight_tm);
+            wth->phdr.ts.secs -= dst_offset;
+        }
+    }
+
+    /* update the pseudo header */
+    switch (wth->file_encap) {
+    case WTAP_ENCAP_ETHERNET:
+        /* There is no FCS in the frame */
+        wth->pseudo_header.eth.fcs_len = 0;
+        break;
+    case WTAP_ENCAP_IEEE_802_11_WITH_RADIO:
+        /* Updated in read_packet_header */
+        break;
+    }
+
+    /* set-up the packet buffer */
+    buffer_assure_space(wth->frame_buffer, packet_header.captured_size);
+
+    /* read the frame data */
+    offset_from_packet_header = (int) (wth->data_offset - *data_offset);
+    bytes_consumed = read_packet_data(wth->fh, packet_header.offset_to_frame,
+            offset_from_packet_header, buffer_start_ptr(wth->frame_buffer),
+            packet_header.captured_size, err, err_info);
+    if (bytes_consumed < 0) {
+        return FALSE;
+    }
+    wth->data_offset += bytes_consumed;
+
+    /* skip over any extra bytes following the frame data */
+    offset_from_packet_header = (int) (wth->data_offset - *data_offset);
+    if (!skip_to_next_packet(wth, packet_header.offset_to_next_packet,
+            offset_from_packet_header, err, err_info)) {
+        return FALSE;
+    }
+
+    return TRUE;
 }
 
-/* reads a packet at an offset */
-static gboolean observer_seek_read(wtap *wth, long seek_off,
-    union wtap_pseudo_header *pseudo_header, guchar *pd, int length,
+/* Reads a packet at an offset. */
+static gboolean observer_seek_read(wtap *wth, gint64 seek_off,
+    union wtap_pseudo_header *pseudo_header, guint8 *pd, int length,
     int *err, gchar **err_info)
 {
-       packet_entry_header packet_header;
-       int offset;
-
-       if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1)
-               return FALSE;
-
-       /* process the packet header, including TLVs */
-       offset = read_packet_header(wth->random_fh, &packet_header, err,
-           err_info);
-       if (offset <= 0)
-               return FALSE;   /* EOF or error */
-
-       /* read data */
-       if (!read_packet_data(wth->random_fh, packet_header.offset_to_frame,
-           offset, pd, length, err, err_info))
-               return FALSE;
-
-       /* update the pseudo header */
-       switch (wth->file_encap) {
-
-       case WTAP_ENCAP_ETHERNET:
-               /* There is no FCS in the frame */
-               pseudo_header->eth.fcs_len = 0;
-               break;
-       }
-
-       return TRUE;
+    packet_entry_header packet_header;
+    int offset;
+
+    if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1)
+        return FALSE;
+
+    /* process the packet header, including TLVs */
+    offset = read_packet_header(wth->random_fh, pseudo_header, &packet_header, err,
+        err_info);
+    if (offset <= 0)
+        return FALSE;    /* EOF or error */
+
+    /* update the pseudo header */
+    switch (wth->file_encap) {
+
+    case WTAP_ENCAP_ETHERNET:
+        /* There is no FCS in the frame */
+        pseudo_header->eth.fcs_len = 0;
+        break;
+    case WTAP_ENCAP_IEEE_802_11_WITH_RADIO:
+        /* Updated in read_packet_header */
+        break;
+    }
+
+    /* read the frame data */
+    if (!read_packet_data(wth->random_fh, packet_header.offset_to_frame,
+        offset, pd, length, err, err_info))
+        return FALSE;
+
+    return TRUE;
 }
 
 static int
-read_packet_header(FILE_T fh, packet_entry_header *packet_header, int *err,
-    gchar **err_info)
+read_packet_header(FILE_T fh, union wtap_pseudo_header *pseudo_header, 
+    packet_entry_header *packet_header, int *err, gchar **err_info)
 {
-       int offset;
-       int bytes_read;
-       guint i;
-       tlv_header tlvh;
-       int seek_increment;
-
-       offset = 0;
-
-       /* pull off the packet header */
-       bytes_read = file_read(packet_header, sizeof *packet_header, 1, fh);
-       if (bytes_read != sizeof *packet_header) {
-               *err = file_error(fh);
-               if (*err != 0)
-                       return -1;
-               return 0;       /* EOF */
-       }
-       offset += bytes_read;
-
-       /* check the packet's magic number; the magic number is all 8's,
-          so the byte order doesn't matter */
-       if (packet_header->packet_magic != observer_packet_magic) {
-               *err = WTAP_ERR_BAD_RECORD;
-               *err_info = g_strdup_printf("Observer: bad record: Invalid magic number 0x%08x",
-                   GUINT32_FROM_LE(packet_header->packet_magic));
-               return -1;
-       }
-
-       /* process extra information */
-       for (i = 0; i < packet_header->number_of_information_elements; i++) {
-               /* read the TLV header */
-               bytes_read = file_read(&tlvh, sizeof tlvh, 1, fh);
-               if (bytes_read != sizeof tlvh) {
-                       *err = file_error(fh);
-                       if (*err == 0)
-                               *err = WTAP_ERR_SHORT_READ;
-                       return -1;
-               }
-               offset += bytes_read;
-
-               tlvh.length = GUINT16_FROM_LE(tlvh.length);
-               if (tlvh.length < sizeof tlvh) {
-                       *err = WTAP_ERR_BAD_RECORD;
-                       *err_info = g_strdup_printf("Observer: bad record (TLV length %u < %lu)",
-                           tlvh.length, (unsigned long)sizeof tlvh);
-                       return -1;
-               }
-
-               /* skip the TLV data */
-               seek_increment = tlvh.length - sizeof tlvh;
-               if(seek_increment>0) {
-                       if (file_seek(fh, seek_increment, SEEK_CUR, err) == -1)
-                               return -1;
-               }
-               offset += seek_increment;
-       }
-
-       packet_header->offset_to_frame =
-           GUINT16_FROM_LE(packet_header->offset_to_frame);
-
-       return offset;
+    int offset;
+    int bytes_read;
+    guint i;
+    tlv_header tlvh;
+    int seek_increment;
+    tlv_wireless_info wireless_header;
+
+    offset = 0;
+
+    /* pull off the packet header */
+    bytes_read = file_read(packet_header, sizeof *packet_header, fh);
+    if (bytes_read != sizeof *packet_header) {
+        *err = file_error(fh, err_info);
+        if (*err != 0)
+            return -1;
+        return 0;    /* EOF */
+    }
+    offset += bytes_read;
+    PACKET_ENTRY_HEADER_FROM_LE_IN_PLACE(*packet_header);
+
+    /* check the packet's magic number */
+    if (packet_header->packet_magic != observer_packet_magic) {
+
+        /*
+         * Some files are zero-padded at the end. There is no warning of this
+         * in the previous packet header information, such as setting
+         * offset_to_next_packet to zero. So detect this situation by treating
+         * an all-zero header as a sentinel. Return EOF when it is encountered,
+         * rather than treat it as a bad record.
+         */
+        for (i = 0; i < sizeof *packet_header; i++) {
+            if (((guint8*) packet_header)[i] != 0)
+                break;
+        }
+        if (i == sizeof *packet_header) {
+            *err = 0;
+            return 0;    /* EOF */
+        }
+
+        *err = WTAP_ERR_BAD_FILE;
+        *err_info = g_strdup_printf("Observer: bad record: Invalid magic number 0x%08x",
+            packet_header->packet_magic);
+        return -1;
+    }
+
+    /* process extra information */
+    for (i = 0; i < packet_header->number_of_information_elements; i++) {
+        /* read the TLV header */
+        bytes_read = file_read(&tlvh, sizeof tlvh, fh);
+        if (bytes_read != sizeof tlvh) {
+            *err = file_error(fh, err_info);
+            if (*err == 0)
+                *err = WTAP_ERR_SHORT_READ;
+            return -1;
+        }
+        offset += bytes_read;
+        TLV_HEADER_FROM_LE_IN_PLACE(tlvh);
+
+        if (tlvh.length < sizeof tlvh) {
+            *err = WTAP_ERR_BAD_FILE;
+            *err_info = g_strdup_printf("Observer: bad record (TLV length %u < %lu)",
+                tlvh.length, (unsigned long)sizeof tlvh);
+            return -1;
+        }
+
+        /* process (or skip over) the current TLV */
+        switch (tlvh.type) {
+        case INFORMATION_TYPE_WIRELESS:
+            bytes_read = file_read(&wireless_header, sizeof wireless_header, fh);
+            if (bytes_read != sizeof wireless_header) {
+                *err = file_error(fh, err_info);
+                if(*err == 0)
+                    *err = WTAP_ERR_SHORT_READ;
+                return -1;
+            }
+            /* update the pseudo header */
+            pseudo_header->ieee_802_11.fcs_len = 0;
+            pseudo_header->ieee_802_11.channel = wireless_header.frequency;
+            pseudo_header->ieee_802_11.data_rate = wireless_header.rate;
+            pseudo_header->ieee_802_11.signal_level = wireless_header.strengthPercent;
+            offset += bytes_read;
+            break;
+        default:
+            /* skip the TLV data */
+            seek_increment = tlvh.length - (int)sizeof tlvh;
+            if (seek_increment > 0) {
+                if (file_seek(fh, seek_increment, SEEK_CUR, err) == -1)
+                    return -1;
+            }
+            offset += seek_increment;
+        }
+    }
+
+    return offset;
 }
 
-static gboolean
-read_packet_data(FILE_T fh, int offset_to_frame, int offset, guint8 *pd,
+static int
+read_packet_data(FILE_T fh, int offset_to_frame, int current_offset_from_packet_header, guint8 *pd,
     int length, int *err, char **err_info)
 {
-       int seek_increment;
-
-       /* get to the packet data */
-       if (offset_to_frame < offset) {
-               *err = WTAP_ERR_BAD_RECORD;
-               *err_info = g_strdup_printf("Observer: bad record (offset to frame %d < %d)",
-                   offset_to_frame, offset);
-               return FALSE;
-       }
-       seek_increment = offset_to_frame - offset;
-       if(seek_increment>0) {
-               if (file_seek(fh, seek_increment, SEEK_CUR, err) == -1)
-                       return FALSE;
-       }
-
-       /* read in the packet */
-       wtap_file_read_expected_bytes(pd, length, fh, err);
-       return TRUE;
+    int seek_increment;
+    int bytes_consumed = 0;
+
+    /* validate offsets */
+    if (offset_to_frame < current_offset_from_packet_header) {
+        *err = WTAP_ERR_BAD_FILE;
+        *err_info = g_strdup_printf("Observer: bad record (offset to packet data %d < %d)",
+            offset_to_frame, current_offset_from_packet_header);
+        return -1;
+    }
+
+    /* skip to the packet data */
+    seek_increment = offset_to_frame - current_offset_from_packet_header;
+    if (seek_increment > 0) {
+        if (file_seek(fh, seek_increment, SEEK_CUR, err) == -1) {
+            return -1;
+        }
+        bytes_consumed += seek_increment;
+    }
+
+    /* read in the packet data */
+    wtap_file_read_expected_bytes(pd, length, fh, err, err_info);
+    bytes_consumed += length;
+
+    return bytes_consumed;
 }
 
 static gboolean
-skip_to_next_packet(wtap *wth, int offset, int offset_to_next_packet, int *err,
+skip_to_next_packet(wtap *wth, int offset_to_next_packet, int current_offset_from_packet_header, int *err,
     char **err_info)
 {
-       int seek_increment;
-
-       if (offset_to_next_packet < offset) {
-               *err = WTAP_ERR_BAD_RECORD;
-               *err_info = g_strdup_printf("Observer: bad record (offset to next packet %d < %d)",
-                   offset_to_next_packet, offset);
-               return FALSE;
-       }
-       seek_increment = offset_to_next_packet - offset;
-       if(seek_increment>0) {
-               if (file_seek(wth->fh, seek_increment, SEEK_CUR, err) == -1)
-                       return FALSE;
-       }
-       wth->data_offset += seek_increment;
-       return TRUE;
+    int seek_increment;
+
+    /* validate offsets */
+    if (offset_to_next_packet < current_offset_from_packet_header) {
+        *err = WTAP_ERR_BAD_FILE;
+        *err_info = g_strdup_printf("Observer: bad record (offset to next packet %d < %d)",
+            offset_to_next_packet, current_offset_from_packet_header);
+        return FALSE;
+    }
+
+    /* skip to the next packet header */
+    seek_increment = offset_to_next_packet - current_offset_from_packet_header;
+    if (seek_increment > 0) {
+        if (file_seek(wth->fh, seek_increment, SEEK_CUR, err) == -1)
+            return FALSE;
+        wth->data_offset += seek_increment;
+    }
+
+    return TRUE;
 }
 
 /* Returns 0 if we could write the specified encapsulation type,
    an error indication otherwise. */
 int network_instruments_dump_can_write_encap(int encap)
 {
-       /* Per-packet encapsulations aren't supported. */
-       if (encap == WTAP_ENCAP_PER_PACKET)
-               return WTAP_ERR_ENCAP_PER_PACKET_UNSUPPORTED;
-       
-       if (encap < 0 || (unsigned) encap >= NUM_FROM_WTAP_ENCAPS || from_wtap_encap[encap] == OBSERVER_UNDEFINED)
-               return WTAP_ERR_UNSUPPORTED_ENCAP;
-
-       return 0;
+    /* per-packet encapsulations aren't supported */
+    if (encap == WTAP_ENCAP_PER_PACKET)
+        return WTAP_ERR_ENCAP_PER_PACKET_UNSUPPORTED;
+
+    if (encap < 0 || (wtap_to_observer_encap(encap) == OBSERVER_UNDEFINED))
+        return WTAP_ERR_UNSUPPORTED_ENCAP;
+
+    return 0;
 }
 
 /* Returns TRUE on success, FALSE on failure; sets "*err" to an error code on
-   failure */
-gboolean network_instruments_dump_open(wtap_dumper *wdh, gboolean cant_seek, int *err)
+   failure. */
+gboolean network_instruments_dump_open(wtap_dumper *wdh, int *err)
 {
-       capture_file_header file_header;
-       tlv_header comment_header;
-       char comment[64];
-       struct tm *current_time;
-       time_t system_time;
-
-       if (cant_seek) {
-               *err = WTAP_ERR_CANT_WRITE_TO_PIPE;
-               return FALSE;
-       }
-
-       wdh->subtype_write = observer_dump;
-       wdh->subtype_close = observer_dump_close;
-
-       wdh->dump.niobserver = g_malloc(sizeof(niobserver_dump_t));
-       wdh->dump.niobserver->packet_count = 0;
-       wdh->dump.niobserver->network_type = from_wtap_encap[wdh->encap];
-
-       /* create the file comment */
-       time(&system_time);
-       current_time = localtime(&system_time);
-       memset(&comment, 0x00, sizeof(comment));
-       sprintf(comment, "This capture was saved from Ethereal on %s", asctime(current_time));
-
-       /* create the file header */
-       if (fseek(wdh->fh, 0, SEEK_SET) == -1) {
-               *err = errno;
-               return FALSE;
-       }
-       memset(&file_header, 0x00, sizeof(capture_file_header));
-       strcpy(file_header.observer_version, network_instruments_magic);
-       file_header.offset_to_first_packet = sizeof(capture_file_header) + sizeof(tlv_header) + strlen(comment);
-       file_header.number_of_information_elements = 1;
-       if(!fwrite(&file_header, sizeof(capture_file_header), 1, wdh->fh)) {
-               *err = errno;
-               return FALSE;
-       }
-
-       /* create the comment entry */
-       comment_header.type = INFORMATION_TYPE_COMMENT;
-       comment_header.length = sizeof(tlv_header) + strlen(comment);
-       if(!fwrite(&comment_header, sizeof(tlv_header), 1, wdh->fh)) {
-               *err = errno;
-               return FALSE;
-       }
-       if(!fwrite(&comment, sizeof(char), strlen(comment), wdh->fh)) {
-               *err = errno;
-               return FALSE;
-       }
-
-       init_time_offset();
-
-       return TRUE;
+    observer_dump_private_state * private_state = NULL;
+    capture_file_header file_header;
+
+    tlv_header comment_header;
+    tlv_time_info time_header;
+    char comment[64];
+    size_t comment_length;
+    struct tm * current_time;
+    time_t system_time;
+
+    /* initialize the private state */
+    private_state = (observer_dump_private_state *) g_malloc(sizeof(observer_dump_private_state));
+    private_state->packet_count = 0;
+    private_state->network_type = wtap_to_observer_encap(wdh->encap);
+    private_state->time_format = TIME_INFO_GMT;
+
+    /* populate the fields of wdh */
+    wdh->priv = (void *) private_state;
+    wdh->subtype_write = observer_dump;
+
+    /* initialize the file header */
+    memset(&file_header, 0x00, sizeof(file_header));
+    g_strlcpy(file_header.observer_version, network_instruments_magic, 31);
+    file_header.offset_to_first_packet = (guint16)sizeof(file_header);
+    file_header.offset_to_first_packet_high_byte = 0;
+
+    /* create the file comment TLV */
+    {
+        time(&system_time);
+        current_time = localtime(&system_time);
+        memset(&comment, 0x00, sizeof(comment));
+        g_snprintf(comment, 64, "This capture was saved from Wireshark on %s", asctime(current_time));
+        comment_length = strlen(comment);
+
+        comment_header.type = INFORMATION_TYPE_COMMENT;
+        comment_header.length = (guint16) (sizeof(comment_header) + comment_length);
+
+        /* update the file header to account for the comment TLV */
+        file_header.number_of_information_elements++;
+        file_header.offset_to_first_packet += comment_header.length;
+    }
+
+    /* create the timestamp encoding TLV */
+    {
+        time_header.type = INFORMATION_TYPE_TIME_INFO;
+        time_header.length = (guint16) (sizeof(time_header));
+        time_header.time_format = TIME_INFO_GMT;
+
+        /* update the file header to account for the timestamp encoding TLV */
+        file_header.number_of_information_elements++;
+        file_header.offset_to_first_packet += time_header.length;
+    }
+
+    /* write the file header, swapping any multibyte fields first */
+    CAPTURE_FILE_HEADER_TO_LE_IN_PLACE(file_header);
+    if (!wtap_dump_file_write(wdh, &file_header, sizeof(file_header), err)) {
+        return FALSE;
+    }
+    wdh->bytes_dumped += sizeof(file_header);
+
+    /* write the comment TLV */
+    {
+        TLV_HEADER_TO_LE_IN_PLACE(comment_header);
+        if (!wtap_dump_file_write(wdh, &comment_header, sizeof(comment_header), err)) {
+            return FALSE;
+        }
+        wdh->bytes_dumped += sizeof(comment_header);
+
+        if (!wtap_dump_file_write(wdh, &comment, comment_length, err)) {
+            return FALSE;
+        }
+        wdh->bytes_dumped += comment_length;
+    }
+
+    /* write the time info TLV */
+    {
+        TLV_TIME_INFO_TO_LE_IN_PLACE(time_header);
+        if (!wtap_dump_file_write(wdh, &time_header, sizeof(time_header), err)) {
+            return FALSE;
+        }
+        wdh->bytes_dumped += sizeof(time_header);
+    }
+
+    init_gmt_to_localtime_offset();
+
+    return TRUE;
 }
 
 /* Write a record for a packet to a dump file.
    Returns TRUE on success, FALSE on failure. */
 static gboolean observer_dump(wtap_dumper *wdh, const struct wtap_pkthdr *phdr,
-    const union wtap_pseudo_header *pseudo_header _U_, const guchar *pd,
+    const union wtap_pseudo_header *pseudo_header _U_, const guint8 *pd,
     int *err)
 {
-       niobserver_dump_t *niobserver = wdh->dump.niobserver;
-       packet_entry_header packet_header;
-       size_t nwritten;
-       guint64 capture_nanoseconds = 0;
-
-       if(phdr->ts.secs<(long)seconds1970to2000) {
-               if(phdr->ts.secs<0)
-                       capture_nanoseconds = 0;
-               else
-                       capture_nanoseconds = phdr->ts.secs;
-       } else
-               capture_nanoseconds = phdr->ts.secs - seconds1970to2000;
-       capture_nanoseconds = ((capture_nanoseconds*1000000) + (guint64)phdr->ts.nsecs);
-
-       memset(&packet_header, 0x00, sizeof(packet_entry_header));
-       packet_header.packet_magic = GUINT32_TO_LE(observer_packet_magic);
-       packet_header.network_speed = GUINT32_TO_LE(1000000);
-       packet_header.captured_size = GUINT16_TO_LE((guint16)phdr->caplen);
-       packet_header.network_size = GUINT16_TO_LE((guint16)(phdr->len+4));
-       packet_header.offset_to_frame = GUINT16_TO_LE(sizeof(packet_entry_header));
-       packet_header.offset_to_next_packet = GUINT16_TO_LE(sizeof(packet_entry_header) + phdr->caplen);
-       packet_header.network_type = niobserver->network_type;
-       packet_header.flags = 0x00;
-       packet_header.number_of_information_elements = 0;
-       packet_header.packet_type = PACKET_TYPE_DATA_PACKET;
-       packet_header.packet_number = GUINT64_TO_LE(niobserver->packet_count);
-       packet_header.original_packet_number = GUINT64_TO_LE(niobserver->packet_count);
-       niobserver->packet_count++;
-       packet_header.nano_seconds_since_2000 = GUINT64_TO_LE(capture_nanoseconds);
-
-       nwritten = fwrite(&packet_header, sizeof(packet_header), 1, wdh->fh);
-       if (nwritten != 1) {
-               if (nwritten == 0 && ferror(wdh->fh))
-                       *err = errno;
-               else
-                       *err = WTAP_ERR_SHORT_WRITE;
-               return FALSE;
-       }
-
-       nwritten = fwrite(pd, 1, phdr->caplen, wdh->fh);
-       if (nwritten != phdr->caplen) {
-               if (nwritten == 0 && ferror(wdh->fh))
-                       *err = errno;
-               else
-                       *err = WTAP_ERR_SHORT_WRITE;
-               return FALSE;
-       }
-
-       return TRUE;
+    observer_dump_private_state * private_state = NULL;
+    packet_entry_header           packet_header;
+    guint64                       seconds_since_2000;
+
+    /* convert the number of seconds since epoch from ANSI-relative to
+       Observer-relative */
+    if (phdr->ts.secs < ansi_to_observer_epoch_offset) {
+        if(phdr->ts.secs > (time_t) 0) {
+            seconds_since_2000 = phdr->ts.secs;
+        } else {
+            seconds_since_2000 = (time_t) 0;
+        }
+    } else {
+        seconds_since_2000 = phdr->ts.secs - ansi_to_observer_epoch_offset;
+    }
+
+    /* populate the fields of the packet header */
+    private_state = (observer_dump_private_state *) wdh->priv;
+
+    memset(&packet_header, 0x00, sizeof(packet_header));
+    packet_header.packet_magic = observer_packet_magic;
+    packet_header.network_speed = 1000000;
+    packet_header.captured_size = (guint16) phdr->caplen;
+    packet_header.network_size = (guint16) (phdr->len + 4);
+    packet_header.offset_to_frame = sizeof(packet_header);
+    /* XXX - what if this doesn't fit in 16 bits?  It's not guaranteed to... */
+    packet_header.offset_to_next_packet = (guint16)sizeof(packet_header) + phdr->caplen;
+    packet_header.network_type = private_state->network_type;
+    packet_header.flags = 0x00;
+    packet_header.number_of_information_elements = 0;
+    packet_header.packet_type = PACKET_TYPE_DATA_PACKET;
+    packet_header.packet_number = private_state->packet_count;
+    packet_header.original_packet_number = packet_header.packet_number;
+    packet_header.nano_seconds_since_2000 = seconds_since_2000 * 1000000000 + phdr->ts.nsecs;
+
+    private_state->packet_count++;
+
+    /* write the packet header */
+    PACKET_ENTRY_HEADER_TO_LE_IN_PLACE(packet_header);
+    if (!wtap_dump_file_write(wdh, &packet_header, sizeof(packet_header), err)) {
+        return FALSE;
+    }
+    wdh->bytes_dumped += sizeof(packet_header);
+
+    /* write the packet data */
+    if (!wtap_dump_file_write(wdh, pd, phdr->caplen, err)) {
+        return FALSE;
+    }
+    wdh->bytes_dumped += phdr->caplen;
+
+    return TRUE;
+}
+
+static gint observer_to_wtap_encap(int observer_encap)
+{
+    switch(observer_encap) {
+    case OBSERVER_ETHERNET:
+        return WTAP_ENCAP_ETHERNET;
+    case OBSERVER_TOKENRING:
+        return WTAP_ENCAP_TOKEN_RING;
+    case OBSERVER_FIBRE_CHANNEL:
+        return WTAP_ENCAP_FIBRE_CHANNEL_FC2_WITH_FRAME_DELIMS;
+    case OBSERVER_WIRELESS_802_11:
+        return WTAP_ENCAP_IEEE_802_11_WITH_RADIO;
+    case OBSERVER_UNDEFINED:
+        return WTAP_ENCAP_UNKNOWN;
+    }
+    return WTAP_ENCAP_UNKNOWN;
 }
 
-/* just returns TRUE, there is no clean up needed */
-static gboolean observer_dump_close(wtap_dumper *wdh _U_, int *err _U_)
+static gint wtap_to_observer_encap(int wtap_encap)
 {
-       return TRUE;
+    switch(wtap_encap) {
+    case WTAP_ENCAP_ETHERNET:
+        return OBSERVER_ETHERNET;
+    case WTAP_ENCAP_TOKEN_RING:
+        return OBSERVER_TOKENRING;
+    case WTAP_ENCAP_FIBRE_CHANNEL_FC2_WITH_FRAME_DELIMS:
+        return OBSERVER_FIBRE_CHANNEL;
+    case WTAP_ENCAP_UNKNOWN:
+        return OBSERVER_UNDEFINED;
+    }
+    return OBSERVER_UNDEFINED;
 }