diff options
Diffstat (limited to 'wiretap/mp2t.c')
-rw-r--r-- | wiretap/mp2t.c | 434 |
1 files changed, 434 insertions, 0 deletions
diff --git a/wiretap/mp2t.c b/wiretap/mp2t.c new file mode 100644 index 00000000..a0f3f611 --- /dev/null +++ b/wiretap/mp2t.c @@ -0,0 +1,434 @@ +/* mp2t.c + * + * ISO/IEC 13818-1 MPEG2-TS file format decoder for the Wiretap library. + * Written by Weston Schmidt <weston_schmidt@alumni.purdue.edu> + * Copyright 2012 Weston Schmidt + * + * Wiretap Library + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include <sys/types.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "mp2t.h" + +#include "wtap-int.h" +#include <wsutil/buffer.h> +#include "file_wrappers.h" +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#define MP2T_SYNC_BYTE 0x47 +#define MP2T_SIZE 188 +#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 + +/* number of consecutive packets we must read to decide that a file + is actually an mpeg2 ts */ +#define SYNC_STEPS 10 + + +typedef struct { + 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 int mp2t_file_type_subtype = -1; + +void register_mp2t(void); + +static gboolean +mp2t_read_packet(mp2t_filetype_t *mp2t, FILE_T fh, gint64 offset, + wtap_rec *rec, Buffer *buf, int *err, + gchar **err_info) +{ + guint64 tmp; + + /* + * MP2T_SIZE will always be less than WTAP_MAX_PACKET_SIZE_STANDARD, 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; + + rec->rec_type = REC_TYPE_PACKET; + rec->block = wtap_block_create(WTAP_BLOCK_PACKET); + + /* XXX - relative, not absolute, time stamps */ + rec->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 */ + rec->ts.secs = (time_t)(tmp / mp2t->bitrate); + rec->ts.nsecs = (int)((tmp % mp2t->bitrate) * 1000000000 / mp2t->bitrate); + + rec->rec_header.packet_header.caplen = MP2T_SIZE; + rec->rec_header.packet_header.len = MP2T_SIZE; + + return TRUE; +} + +static gboolean +mp2t_read(wtap *wth, wtap_rec *rec, Buffer *buf, int *err, + gchar **err_info, gint64 *data_offset) +{ + mp2t_filetype_t *mp2t; + + mp2t = (mp2t_filetype_t*) wth->priv; + + *data_offset = file_tell(wth->fh); + + if (!mp2t_read_packet(mp2t, wth->fh, *data_offset, rec, buf, 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) { + if (!wtap_read_bytes(wth->fh, NULL, mp2t->trailer_len, err, err_info)) { + return FALSE; + } + } + + return TRUE; +} + +static gboolean +mp2t_seek_read(wtap *wth, gint64 seek_off, wtap_rec *rec, + 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; + } + + mp2t = (mp2t_filetype_t*) wth->priv; + + if (!mp2t_read_packet(mp2t, wth->random_fh, seek_off, rec, 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_find_next_pcr(wtap *wth, guint8 trailer_len, + int *err, gchar **err_info, guint32 *idx, guint64 *pcr, guint16 *pid) +{ + 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; + } + } + + 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; +} + +wtap_open_return_val +mp2t_open(wtap *wth, int *err, gchar **err_info) +{ + guint8 buffer[MP2T_SIZE+TRAILER_LEN_MAX]; + guint8 trailer_len = 0; + guint sync_steps = 0; + guint i; + guint32 first = 0; + mp2t_filetype_t *mp2t; + wtap_open_return_val status; + guint64 bitrate; + + + 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; + } + + for (i = 0; i < MP2T_SIZE; i++) { + if (MP2T_SYNC_BYTE == buffer[i]) { + first = i; + goto found; + } + } + /* + * 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 WTAP_OPEN_ERROR; + } + + /* read some packets and make sure they all start with a sync byte */ + do { + 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++; + } + else { + /* no sync byte found, check if trailing data is appended + and we have to increase the packet size */ + + /* 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 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 WTAP_OPEN_ERROR; + } + sync_steps = 0; + break; + } + } + /* no sync byte found in the vicinity, this is no mpeg2 ts file */ + if (i==TRAILER_LEN_MAX) + return WTAP_OPEN_NOT_MINE; + } + } while (sync_steps < SYNC_STEPS); + + if (-1 == file_seek(wth->fh, first, SEEK_SET, err)) { + 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_subtype = mp2t_file_type_subtype; + wth->file_encap = WTAP_ENCAP_MPEG_2_TS; + wth->file_tsprec = WTAP_TSPREC_NSEC; + wth->subtype_read = mp2t_read; + wth->subtype_seek_read = mp2t_seek_read; + wth->snapshot_length = 0; + + mp2t = g_new(mp2t_filetype_t, 1); + + wth->priv = mp2t; + mp2t->start_offset = first; + mp2t->trailer_len = trailer_len; + mp2t->bitrate = bitrate; + + return WTAP_OPEN_MINE; +} + +static const struct supported_block_type mp2t_blocks_supported[] = { + /* + * We support packet blocks, with no comments or other options. + */ + { WTAP_BLOCK_PACKET, MULTIPLE_BLOCKS_SUPPORTED, NO_OPTIONS_SUPPORTED } +}; + +static const struct file_type_subtype_info mp2t_info = { + "MPEG2 transport stream", "mp2t", "mp2t", "ts;mpg", + FALSE, BLOCKS_SUPPORTED(mp2t_blocks_supported), + NULL, NULL, NULL +}; + +void register_mp2t(void) +{ + mp2t_file_type_subtype = wtap_register_file_type_subtype(&mp2t_info); + + /* + * Register name for backwards compatibility with the + * wtap_filetypes table in Lua. + */ + wtap_register_backwards_compatibility_lua_name("MPEG_2_TS", + mp2t_file_type_subtype); +} + +/* + * Editor modelines - https://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: + */ |