opa: Add dissectors for Intel’s Omni-Path Architecture (OPA)
[metze/wireshark/wip.git] / wiretap / mp2t.c
index 7133d96d66e48fa3ca7d963fa8ad1c1cf93da657..44ee0b728c66deead33510f8b277639cfea1e699 100644 (file)
@@ -4,8 +4,6 @@
  * Written by Weston Schmidt <weston_schmidt@alumni.purdue.edu>
  * Copyright 2012 Weston Schmidt
  *
- * $Id$
- *
  * Wiretap Library
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
 
 #ifdef HAVE_SYS_TYPES_H
 #include <sys/types.h>
@@ -37,7 +33,7 @@
 #include "mp2t.h"
 
 #include "wtap-int.h"
-#include "buffer.h"
+#include <wsutil/buffer.h>
 #include "file_wrappers.h"
 #include <errno.h>
 #include <stdlib.h>
@@ -46,8 +42,8 @@
 
 #define MP2T_SYNC_BYTE      0x47
 #define MP2T_SIZE           188
-#define MP2T_QAM256_BITRATE 38810700    /* bits per second */
 #define MP2T_QAM64_BITRATE  26970350    /* bits per second */
+#define MP2T_PCR_CLOCK      27000000    /* cycles per second - 27MHz */
 
 /* we try to detect trailing data up to 40 bytes after each packet */
 #define TRAILER_LEN_MAX 40
 
 
 typedef struct {
-    guint32 offset;
-    struct wtap_nstime now;
+    guint32 start_offset;
+    guint64 bitrate;
     /* length of trailing data (e.g. FEC) that's appended after each packet */
     guint8  trailer_len;
 } mp2t_filetype_t;
 
 static gboolean
-mp2t_read_data(guint8 *dest, int length, int *err, gchar **err_info, FILE_T fh)
+mp2t_read_packet(mp2t_filetype_t *mp2t, FILE_T fh, gint64 offset,
+                 struct wtap_pkthdr *phdr, Buffer *buf, int *err,
+                 gchar **err_info)
 {
-    int bytes_read;
+    guint64 tmp;
 
-    bytes_read = file_read(dest, length, fh);
-    if (length != bytes_read) {
-        *err = file_error(fh, err_info);
-        /* bytes_read==0 is end of file, not a short read */
-        if (bytes_read>0 && *err == 0) {
-            *err = WTAP_ERR_SHORT_READ;
-        }
+    /*
+     * MP2T_SIZE will always be less than WTAP_MAX_PACKET_SIZE, so
+     * we don't have to worry about the packet being too big.
+     */
+    ws_buffer_assure_space(buf, MP2T_SIZE);
+    if (!wtap_read_bytes_or_eof(fh, ws_buffer_start_ptr(buf), MP2T_SIZE, err, err_info))
         return FALSE;
-    }
+
+    phdr->rec_type = REC_TYPE_PACKET;
+
+    /* XXX - relative, not absolute, time stamps */
+    phdr->presence_flags = WTAP_HAS_TS;
+
+    /*
+     * Every packet in an MPEG2-TS stream is has a fixed size of
+     * MP2T_SIZE plus the number of trailer bytes.
+     *
+     * We assume that the bits in the transport stream are supplied at
+     * a constant rate; is that guaranteed by all media that use
+     * MPEG2-TS?  If so, the time offset, from the beginning of the
+     * stream, of a given packet is the packet offset, in bits, divided
+     * by the bitrate.
+     *
+     * It would be really cool to be able to configure the bitrate, in
+     * case our attempt to guess it from the PCRs of one of the programs
+     * doesn't get the right answer.
+     */
+    tmp = ((guint64)(offset - mp2t->start_offset) * 8); /* offset, in bits */
+    phdr->ts.secs = (time_t)(tmp / mp2t->bitrate);
+    phdr->ts.nsecs = (int)((tmp % mp2t->bitrate) * 1000000000 / mp2t->bitrate);
+
+    phdr->caplen = MP2T_SIZE;
+    phdr->len = MP2T_SIZE;
 
     return TRUE;
 }
@@ -86,102 +108,241 @@ static gboolean
 mp2t_read(wtap *wth, int *err, gchar **err_info, gint64 *data_offset)
 {
     mp2t_filetype_t *mp2t;
-    guint64 tmp;
 
     mp2t = (mp2t_filetype_t*) wth->priv;
 
-    *data_offset = mp2t->offset;
-    /* read only the actual mpeg2 ts packet, not including a trailer */
-    buffer_assure_space(wth->frame_buffer, MP2T_SIZE);
-    if (FALSE == mp2t_read_data(buffer_start_ptr(wth->frame_buffer),
-                                MP2T_SIZE, err, err_info, wth->fh))
-    {
+    *data_offset = file_tell(wth->fh);
+
+    if (!mp2t_read_packet(mp2t, wth->fh, *data_offset, &wth->phdr,
+                          wth->frame_buffer, err, err_info)) {
         return FALSE;
     }
 
     /* if there's a trailer, skip it and go to the start of the next packet */
-    if (mp2t->trailer_len!=0 &&
-        (-1 == file_seek(wth->fh, mp2t->trailer_len, SEEK_CUR, err))) {
-        return FALSE;
+    if (mp2t->trailer_len!=0) {
+        if (-1 == file_seek(wth->fh, mp2t->trailer_len, SEEK_CUR, err)) {
+            return FALSE;
+        }
     }
 
-    mp2t->offset += MP2T_SIZE + mp2t->trailer_len;
+    return TRUE;
+}
 
-    /* XXX - relative, not absolute, time stamps */
-    wth->phdr.presence_flags = WTAP_HAS_TS;
-
-    /* It would be really cool to be able to configure the bitrate... */
-    tmp = (MP2T_SIZE+mp2t->trailer_len) * 8;
-    tmp *= 1000000000;
-    tmp /= MP2T_QAM256_BITRATE;
-
-    wth->phdr.ts.secs = mp2t->now.secs;
-    wth->phdr.ts.nsecs = mp2t->now.nsecs;
-    mp2t->now.nsecs += (guint32)tmp;
-    if (1000000000 <= mp2t->now.nsecs) {
-        mp2t->now.nsecs -= 1000000000;
-        mp2t->now.secs++;
+static gboolean
+mp2t_seek_read(wtap *wth, gint64 seek_off, struct wtap_pkthdr *phdr,
+        Buffer *buf, int *err, gchar **err_info)
+{
+    mp2t_filetype_t *mp2t;
+
+    if (-1 == file_seek(wth->random_fh, seek_off, SEEK_SET, err)) {
+        return FALSE;
     }
-    wth->phdr.caplen = MP2T_SIZE;
-    wth->phdr.len = MP2T_SIZE;
 
+    mp2t = (mp2t_filetype_t*) wth->priv;
+
+    if (!mp2t_read_packet(mp2t, wth->random_fh, seek_off, phdr, buf,
+                          err, err_info)) {
+        if (*err == 0)
+            *err = WTAP_ERR_SHORT_READ;
+        return FALSE;
+    }
     return TRUE;
 }
 
+static guint64
+mp2t_read_pcr(guint8 *buffer)
+{
+    guint64 base;
+    guint64 ext;
+
+    base = pntoh40(buffer);
+    base >>= 7;
+
+    ext = pntoh16(&buffer[4]);
+    ext &= 0x01ff;
+
+    return (base * 300 + ext);
+}
+
 static gboolean
-mp2t_seek_read(wtap *wth, gint64 seek_off,
-        union wtap_pseudo_header *pseudo_header _U_, guint8 *pd, int length,
-        int *err, gchar **err_info)
+mp2t_find_next_pcr(wtap *wth, guint8 trailer_len,
+        int *err, gchar **err_info, guint32 *idx, guint64 *pcr, guint16 *pid)
 {
-    if (-1 == file_seek(wth->random_fh, seek_off, SEEK_SET, err)) {
-        return FALSE;
+    guint8 buffer[MP2T_SIZE+TRAILER_LEN_MAX];
+    gboolean found;
+    guint8 afc;
+    guint timeout = 0;
+
+    found = FALSE;
+    while (FALSE == found && timeout++ < SYNC_STEPS * SYNC_STEPS) {
+        (*idx)++;
+        if (!wtap_read_bytes_or_eof(
+                    wth->fh, buffer, MP2T_SIZE+trailer_len, err, err_info)) {
+            /* Read error, short read, or EOF */
+            return FALSE;
+        }
+
+        if (MP2T_SYNC_BYTE != buffer[0]) {
+            continue;
+        }
+
+        /* Read out the AFC value. */
+        afc = 3 & (buffer[3] >> 4);
+        if (afc < 2) {
+            continue;
+        }
+
+        /* Check the length. */
+        if (buffer[4] < 7) {
+            continue;
+        }
+
+        /* Check that there is the PCR flag. */
+        if (0x10 != (0x10 & buffer[5])) {
+            continue;
+        }
+
+        /* We have a PCR value! */
+        *pcr = mp2t_read_pcr(&buffer[6]);
+        *pid = 0x01ff & pntoh16(&buffer[1]);
+        found = TRUE;
+    }
+
+    return found;
+}
+
+static wtap_open_return_val
+mp2t_bits_per_second(wtap *wth, guint32 first, guint8 trailer_len,
+        guint64 *bitrate, int *err, gchar **err_info)
+{
+    guint32 pn1, pn2;
+    guint64 pcr1, pcr2;
+    guint16 pid1, pid2;
+    guint32 idx;
+    guint64 pcr_delta, bits_passed;
+
+    /* Find the first PCR + PID.
+     * Then find another PCR in that PID.
+     * Take the difference and that's our bitrate.
+     * All the different PCRs in different PIDs 'should' be the same.
+     *
+     * XXX - is this assuming that the time stamps in the PCRs correspond
+     * to the time scale of the underlying transport stream?
+     */
+    idx = first;
+
+    if (!mp2t_find_next_pcr(wth, trailer_len, err, err_info, &idx, &pcr1, &pid1)) {
+        /* Read error, short read, or EOF */
+        if (*err == WTAP_ERR_SHORT_READ)
+            return WTAP_OPEN_NOT_MINE;    /* not a full frame */
+        if (*err != 0)
+            return WTAP_OPEN_ERROR;
+
+        /* We don't have any PCRs, so we can't guess the bit rate.
+         * Default to something reasonable.
+         */
+        *bitrate = MP2T_QAM64_BITRATE;
+        return WTAP_OPEN_MINE;
+    }
+
+    pn1 = idx;
+    pn2 = pn1;
+
+    while (pn1 == pn2) {
+        if (!mp2t_find_next_pcr(wth, trailer_len, err, err_info, &idx, &pcr2, &pid2)) {
+            /* Read error, short read, or EOF */
+            if (*err == WTAP_ERR_SHORT_READ)
+                return WTAP_OPEN_NOT_MINE;    /* not a full frame */
+            if (*err != 0)
+                return WTAP_OPEN_ERROR;
+
+            /* We don't have two PCRs for the same PID, so we can't guess
+             * the bit rate.
+             * Default to something reasonable.
+             */
+            *bitrate = MP2T_QAM64_BITRATE;
+            return WTAP_OPEN_MINE;
+        }
+
+        if (pid1 == pid2) {
+            pn2 = idx;
+        }
     }
 
-    return mp2t_read_data(pd, length, err, err_info, wth->random_fh);
+    if (pcr2 <= pcr1) {
+        /* The PCRs for that PID didn't go forward; treat that as an
+         * indication that this isn't an MPEG-2 TS.
+         */
+        return WTAP_OPEN_NOT_MINE;
+    }
+    pcr_delta = pcr2 - pcr1;
+    /* cast one of the factors to guint64
+       otherwise, the multiplication would use guint32 and could
+       overflow before the result is assigned to the guint64 bits_passed */
+    bits_passed = (guint64)MP2T_SIZE * (pn2 - pn1) * 8;
+
+    *bitrate = ((MP2T_PCR_CLOCK * bits_passed) / pcr_delta);
+    if (*bitrate == 0) {
+        /* pcr_delta < MP2T_PCR_CLOCK * bits_passed (pn2 != pn1,
+         * as that's the test for the loop above, so bits_passed
+         * is non-zero).
+         *
+         * That will produce a fractional bitrate, which turns
+         * into zero, causing a zero divide later.
+         *
+         * XXX - should we report this as "not ours"?  A bitrate
+         * of less than 1 bit per second is not very useful for any
+         * form of audio/video, so presumably that's unlikely to
+         * be an MP2T file.
+         */
+        return WTAP_OPEN_ERROR;
+    }
+    return WTAP_OPEN_MINE;
 }
 
-int
+wtap_open_return_val
 mp2t_open(wtap *wth, int *err, gchar **err_info)
 {
-    int bytes_read;
     guint8 buffer[MP2T_SIZE+TRAILER_LEN_MAX];
     guint8 trailer_len = 0;
     guint sync_steps = 0;
-    int i;
-    int first;
+    guint i;
+    guint32 first = 0;
     mp2t_filetype_t *mp2t;
+    wtap_open_return_val status;
+    guint64 bitrate;
 
 
-    errno = WTAP_ERR_CANT_READ;
-    bytes_read = file_read(buffer, MP2T_SIZE, wth->fh);
-
-    if (MP2T_SIZE != bytes_read) {
-        *err = file_error(wth->fh, err_info);
-        return (*err == 0) ? 0 : -1;
+    if (!wtap_read_bytes(wth->fh, buffer, MP2T_SIZE, err, err_info)) {
+        if (*err != WTAP_ERR_SHORT_READ)
+            return WTAP_OPEN_ERROR;
+        return WTAP_OPEN_NOT_MINE;
     }
 
-    first = -1;
     for (i = 0; i < MP2T_SIZE; i++) {
         if (MP2T_SYNC_BYTE == buffer[i]) {
             first = i;
-            break;
+            goto found;
         }
     }
-    if (-1 == first) {
-        return 0; /* wrong file type - not an mpeg2 ts file */
-    }
+    /*
+     * No sync bytes found, so not an MPEG-2 Transport Stream file.
+     */
+    return WTAP_OPEN_NOT_MINE; /* wrong file type - not an mpeg2 ts file */
 
+found:
     if (-1 == file_seek(wth->fh, first, SEEK_SET, err)) {
-        return -1;
+        return WTAP_OPEN_ERROR;
     }
+
     /* read some packets and make sure they all start with a sync byte */
     do {
-       bytes_read = file_read(buffer, MP2T_SIZE+trailer_len, wth->fh);
-       if (bytes_read < 0)
-          return -1;  /* read error */
-       if (bytes_read < MP2T_SIZE+trailer_len)
+       if (!wtap_read_bytes(wth->fh, buffer, MP2T_SIZE+trailer_len, err, err_info)) {
+          if (*err != WTAP_ERR_SHORT_READ)
+            return WTAP_OPEN_ERROR;  /* read error */
+          if(sync_steps<2) return WTAP_OPEN_NOT_MINE; /* wrong file type - not an mpeg2 ts file */
           break;  /* end of file, that's ok if we're still in sync */
-
+       }
        if (buffer[0] == MP2T_SYNC_BYTE) {
                sync_steps++;
        }
@@ -192,14 +353,14 @@ mp2t_open(wtap *wth, int *err, gchar **err_info)
            /* if we've already detected a trailer field, we must remain in sync
               another mismatch means we have no mpeg2 ts file */
            if (trailer_len>0)
-               return 0;
+               return WTAP_OPEN_NOT_MINE;
 
            /* check if a trailer is appended to the packet */
            for (i=0; i<TRAILER_LEN_MAX; i++) {
                if (buffer[i] == MP2T_SYNC_BYTE) {
                    trailer_len = i;
                    if (-1 == file_seek(wth->fh, first, SEEK_SET, err)) {
-                       return -1;
+                       return WTAP_OPEN_ERROR;
                    }
                    sync_steps = 0;
                    break;
@@ -207,31 +368,51 @@ mp2t_open(wtap *wth, int *err, gchar **err_info)
            }
            /* no sync byte found in the vicinity, this is no mpeg2 ts file */
            if (i==TRAILER_LEN_MAX)
-               return 0;
+               return WTAP_OPEN_NOT_MINE;
        }
     } while (sync_steps < SYNC_STEPS);
 
     if (-1 == file_seek(wth->fh, first, SEEK_SET, err)) {
-        return -1;
+        return WTAP_OPEN_ERROR;
+    }
+
+    /* Ensure there is a valid bitrate */
+    status = mp2t_bits_per_second(wth, first, trailer_len,
+            &bitrate, err, err_info);
+    if (status != WTAP_OPEN_MINE) {
+        return status;
+    }
+
+    if (-1 == file_seek(wth->fh, first, SEEK_SET, err)) {
+        return WTAP_OPEN_ERROR;
     }
 
-    wth->file_type = WTAP_FILE_MPEG_2_TS;
+    wth->file_type_subtype = WTAP_FILE_TYPE_SUBTYPE_MPEG_2_TS;
     wth->file_encap = WTAP_ENCAP_MPEG_2_TS;
-    wth->tsprecision = WTAP_FILE_TSPREC_NSEC;
+    wth->file_tsprec = WTAP_TSPREC_NSEC;
     wth->subtype_read = mp2t_read;
     wth->subtype_seek_read = mp2t_seek_read;
     wth->snapshot_length = 0;
 
     mp2t = (mp2t_filetype_t*) g_malloc(sizeof(mp2t_filetype_t));
-    if (NULL == mp2t) {
-        return -1;
-    }
 
     wth->priv = mp2t;
-    mp2t->offset = (guint32) first;
-    mp2t->now.secs = 0;
-    mp2t->now.nsecs = 0;
+    mp2t->start_offset = first;
     mp2t->trailer_len = trailer_len;
+    mp2t->bitrate = bitrate;
 
-    return 1;
+    return WTAP_OPEN_MINE;
 }
+
+/*
+ * Editor modelines  -  http://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ *
+ * vi: set shiftwidth=4 tabstop=8 expandtab:
+ * :indentSize=4:tabSize=8:noTabs=true:
+ */