/* GStreamer EBML I/O * (c) 2003 Ronald Bultje * * ebml-read.c: read EBML data from file/stream * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "ebml-read.h" #include "ebml-ids.h" #include /* NAN is supposed to be in math.h, Microsoft defines it in xmath.h */ #ifdef _MSC_VER #include #endif /* If everything goes wrong try 0.0/0.0 which should be NAN */ #ifndef NAN #define NAN (0.0 / 0.0) #endif GST_DEBUG_CATEGORY (ebmlread_debug); #define GST_CAT_DEFAULT ebmlread_debug /* Peeks following element id and element length in datastream provided * by @peek with @ctx as user data. * Returns GST_FLOW_EOS if not enough data to read id and length. * Otherwise, @needed provides the prefix length (id + length), and * @length provides element length. * * @object and @offset are provided for informative messaging/debug purposes. */ GstFlowReturn gst_ebml_peek_id_length (guint32 * _id, guint64 * _length, guint * _needed, GstPeekData peek, gpointer * ctx, GstElement * el, guint64 offset) { guint needed; const guint8 *buf; gint len_mask = 0x80, read = 1, n = 1, num_ffs = 0; guint64 total; guint8 b; GstFlowReturn ret; g_return_val_if_fail (_id != NULL, GST_FLOW_ERROR); g_return_val_if_fail (_length != NULL, GST_FLOW_ERROR); g_return_val_if_fail (_needed != NULL, GST_FLOW_ERROR); /* well ... */ *_id = (guint32) GST_EBML_SIZE_UNKNOWN; *_length = GST_EBML_SIZE_UNKNOWN; /* read element id */ needed = 2; ret = peek (ctx, needed, &buf); if (ret != GST_FLOW_OK) goto peek_error; b = GST_READ_UINT8 (buf); total = (guint64) b; while (read <= 4 && !(total & len_mask)) { read++; len_mask >>= 1; } if (G_UNLIKELY (read > 4)) goto invalid_id; /* need id and at least something for subsequent length */ needed = read + 1; ret = peek (ctx, needed, &buf); if (ret != GST_FLOW_OK) goto peek_error; while (n < read) { b = GST_READ_UINT8 (buf + n); total = (total << 8) | b; ++n; } *_id = (guint32) total; /* read element length */ b = GST_READ_UINT8 (buf + n); total = (guint64) b; len_mask = 0x80; read = 1; while (read <= 8 && !(total & len_mask)) { read++; len_mask >>= 1; } if (G_UNLIKELY (read > 8)) goto invalid_length; if ((total &= (len_mask - 1)) == len_mask - 1) num_ffs++; needed += read - 1; ret = peek (ctx, needed, &buf); if (ret != GST_FLOW_OK) goto peek_error; buf += (needed - read); n = 1; while (n < read) { guint8 b = GST_READ_UINT8 (buf + n); if (G_UNLIKELY (b == 0xff)) num_ffs++; total = (total << 8) | b; ++n; } if (G_UNLIKELY (read == num_ffs)) *_length = G_MAXUINT64; else *_length = total; *_needed = needed; return GST_FLOW_OK; /* ERRORS */ peek_error: { GST_WARNING_OBJECT (el, "peek failed, ret = %d", ret); *_needed = needed; return ret; } invalid_id: { GST_ERROR_OBJECT (el, "Invalid EBML ID size tag (0x%x) at position %" G_GUINT64_FORMAT " (0x%" G_GINT64_MODIFIER "x)", (guint) b, offset, offset); return GST_FLOW_ERROR; } invalid_length: { GST_ERROR_OBJECT (el, "Invalid EBML length size tag (0x%x) at position %" G_GUINT64_FORMAT " (0x%" G_GINT64_MODIFIER "x)", (guint) b, offset, offset); return GST_FLOW_ERROR; } } /* setup for parsing @buf at position @offset on behalf of @el. * Takes ownership of @buf. */ void gst_ebml_read_init (GstEbmlRead * ebml, GstElement * el, GstBuffer * buf, guint64 offset) { GstEbmlMaster m; gsize buf_size; g_return_if_fail (el); g_return_if_fail (buf); ebml->el = el; ebml->offset = offset; ebml->buf = buf; ebml->buf_data = gst_buffer_map (buf, &buf_size, NULL, GST_MAP_READ); ebml->readers = g_array_sized_new (FALSE, FALSE, sizeof (GstEbmlMaster), 10); m.offset = ebml->offset; gst_byte_reader_init (&m.br, ebml->buf_data, buf_size); g_array_append_val (ebml->readers, m); } void gst_ebml_read_clear (GstEbmlRead * ebml) { if (ebml->readers) g_array_free (ebml->readers, TRUE); ebml->readers = NULL; if (ebml->buf) { gst_buffer_unmap (ebml->buf, ebml->buf_data, -1); gst_buffer_unref (ebml->buf); } ebml->buf = NULL; ebml->el = NULL; } static GstFlowReturn gst_ebml_read_peek (GstByteReader * br, guint peek, const guint8 ** data) { if (G_LIKELY (gst_byte_reader_peek_data (br, peek, data))) return GST_FLOW_OK; else return GST_FLOW_EOS; } static GstFlowReturn gst_ebml_peek_id_full (GstEbmlRead * ebml, guint32 * id, guint64 * length, guint * prefix) { GstFlowReturn ret; ret = gst_ebml_peek_id_length (id, length, prefix, (GstPeekData) gst_ebml_read_peek, (gpointer) gst_ebml_read_br (ebml), ebml->el, gst_ebml_read_get_pos (ebml)); if (ret != GST_FLOW_OK) return ret; GST_LOG_OBJECT (ebml->el, "id 0x%x at offset 0x%" G_GINT64_MODIFIER "x" " of length %" G_GUINT64_FORMAT ", prefix %d", *id, gst_ebml_read_get_pos (ebml), *length, *prefix); #ifndef GST_DISABLE_GST_DEBUG { const guint8 *data = NULL; GstByteReader *br = gst_ebml_read_br (ebml); guint size = gst_byte_reader_get_remaining (br); gst_byte_reader_peek_data (br, size, &data); GST_LOG_OBJECT (ebml->el, "current br %p; remaining %d", br, size); if (data) GST_MEMDUMP_OBJECT (ebml->el, "element", data, MIN (size, *length)); } #endif return ret; } GstFlowReturn gst_ebml_peek_id (GstEbmlRead * ebml, guint32 * id) { guint64 length; guint needed; return gst_ebml_peek_id_full (ebml, id, &length, &needed); } /* * Read the next element, the contents are supposed to be sub-elements which * can be read separately. A new bytereader is setup for doing so. */ GstFlowReturn gst_ebml_read_master (GstEbmlRead * ebml, guint32 * id) { guint64 length; guint prefix; const guint8 *data = NULL; GstFlowReturn ret; GstEbmlMaster m; ret = gst_ebml_peek_id_full (ebml, id, &length, &prefix); if (ret != GST_FLOW_OK) return ret; /* we just at least peeked the id */ if (!gst_byte_reader_skip (gst_ebml_read_br (ebml), prefix)) return GST_FLOW_ERROR; /* FIXME: do proper error handling */ m.offset = gst_ebml_read_get_pos (ebml); if (!gst_byte_reader_get_data (gst_ebml_read_br (ebml), length, &data)) return GST_FLOW_PARSE; GST_LOG_OBJECT (ebml->el, "pushing level %d at offset %" G_GUINT64_FORMAT, ebml->readers->len, m.offset); gst_byte_reader_init (&m.br, data, length); g_array_append_val (ebml->readers, m); return GST_FLOW_OK; } /* explicitly pop a bytereader from stack. Usually invoked automagically. */ GstFlowReturn gst_ebml_read_pop_master (GstEbmlRead * ebml) { g_return_val_if_fail (ebml->readers, GST_FLOW_ERROR); /* never remove initial bytereader */ if (ebml->readers->len > 1) { GST_LOG_OBJECT (ebml->el, "popping level %d", ebml->readers->len - 1); g_array_remove_index (ebml->readers, ebml->readers->len - 1); } return GST_FLOW_OK; } /* * Skip the next element. */ GstFlowReturn gst_ebml_read_skip (GstEbmlRead * ebml) { guint64 length; guint32 id; guint prefix; GstFlowReturn ret; ret = gst_ebml_peek_id_full (ebml, &id, &length, &prefix); if (ret != GST_FLOW_OK) return ret; if (!gst_byte_reader_skip (gst_ebml_read_br (ebml), length + prefix)) return GST_FLOW_PARSE; return ret; } /* * Read the next element as a GstBuffer (binary). */ GstFlowReturn gst_ebml_read_buffer (GstEbmlRead * ebml, guint32 * id, GstBuffer ** buf) { guint64 length; guint prefix; GstFlowReturn ret; ret = gst_ebml_peek_id_full (ebml, id, &length, &prefix); if (ret != GST_FLOW_OK) return ret; /* we just at least peeked the id */ if (!gst_byte_reader_skip (gst_ebml_read_br (ebml), prefix)) return GST_FLOW_ERROR; /* FIXME: do proper error handling */ if (G_LIKELY (length > 0)) { guint offset; offset = gst_ebml_read_get_pos (ebml) - ebml->offset; if (G_LIKELY (gst_byte_reader_skip (gst_ebml_read_br (ebml), length))) { *buf = gst_buffer_copy_region (ebml->buf, GST_BUFFER_COPY_ALL, offset, length); } else { *buf = NULL; return GST_FLOW_PARSE; } } else { *buf = gst_buffer_new (); } return ret; } /* * Read the next element, return a pointer to it and its size. */ static GstFlowReturn gst_ebml_read_bytes (GstEbmlRead * ebml, guint32 * id, const guint8 ** data, guint * size) { guint64 length; guint prefix; GstFlowReturn ret; *size = 0; ret = gst_ebml_peek_id_full (ebml, id, &length, &prefix); if (ret != GST_FLOW_OK) return ret; /* we just at least peeked the id */ if (!gst_byte_reader_skip (gst_ebml_read_br (ebml), prefix)) return GST_FLOW_ERROR; /* FIXME: do proper error handling */ *data = NULL; if (G_LIKELY (length >= 0)) { if (!gst_byte_reader_get_data (gst_ebml_read_br (ebml), length, data)) return GST_FLOW_PARSE; } *size = length; return ret; } /* * Read the next element as an unsigned int. */ GstFlowReturn gst_ebml_read_uint (GstEbmlRead * ebml, guint32 * id, guint64 * num) { const guint8 *data; guint size; GstFlowReturn ret; ret = gst_ebml_read_bytes (ebml, id, &data, &size); if (ret != GST_FLOW_OK) return ret; if (size > 8) { GST_ERROR_OBJECT (ebml->el, "Invalid integer element size %d at position %" G_GUINT64_FORMAT " (0x%" G_GINT64_MODIFIER "x)", size, gst_ebml_read_get_pos (ebml) - size, gst_ebml_read_get_pos (ebml) - size); return GST_FLOW_ERROR; } if (size == 0) { *num = 0; return ret; } *num = 0; while (size > 0) { *num = (*num << 8) | *data; size--; data++; } return ret; } /* * Read the next element as a signed int. */ GstFlowReturn gst_ebml_read_sint (GstEbmlRead * ebml, guint32 * id, gint64 * num) { const guint8 *data; guint size; gboolean negative = 0; GstFlowReturn ret; ret = gst_ebml_read_bytes (ebml, id, &data, &size); if (ret != GST_FLOW_OK) return ret; if (size > 8) { GST_ERROR_OBJECT (ebml->el, "Invalid integer element size %d at position %" G_GUINT64_FORMAT " (0x%" G_GINT64_MODIFIER "x)", size, gst_ebml_read_get_pos (ebml) - size, gst_ebml_read_get_pos (ebml) - size); return GST_FLOW_ERROR; } if (size == 0) { *num = 0; return ret; } *num = 0; if (*data & 0x80) { negative = 1; *num = *data & ~0x80; size--; data++; } while (size > 0) { *num = (*num << 8) | *data; size--; data++; } /* make signed */ if (negative) { *num = 0 - *num; } return ret; } /* Convert 80 bit extended precision float in big endian format to double. * Code taken from libavutil/intfloat_readwrite.c from ffmpeg, * licensed under LGPL */ struct _ext_float { guint8 exponent[2]; guint8 mantissa[8]; }; static gdouble _ext2dbl (const guint8 * data) { struct _ext_float ext; guint64 m = 0; gint e, i; memcpy (&ext.exponent, data, 2); memcpy (&ext.mantissa, data + 2, 8); for (i = 0; i < 8; i++) m = (m << 8) + ext.mantissa[i]; e = (((gint) ext.exponent[0] & 0x7f) << 8) | ext.exponent[1]; if (e == 0x7fff && m) return NAN; e -= 16383 + 63; /* In IEEE 80 bits, the whole (i.e. 1.xxxx) * mantissa bit is written as opposed to the * single and double precision formats */ if (ext.exponent[0] & 0x80) m = -m; return ldexp (m, e); } /* * Read the next element as a float. */ GstFlowReturn gst_ebml_read_float (GstEbmlRead * ebml, guint32 * id, gdouble * num) { const guint8 *data; guint size; GstFlowReturn ret; ret = gst_ebml_read_bytes (ebml, id, &data, &size); if (ret != GST_FLOW_OK) return ret; if (size != 0 && size != 4 && size != 8 && size != 10) { GST_ERROR_OBJECT (ebml->el, "Invalid float element size %d at position %" G_GUINT64_FORMAT " (0x%" G_GINT64_MODIFIER "x)", size, gst_ebml_read_get_pos (ebml) - size, gst_ebml_read_get_pos (ebml) - size); return GST_FLOW_ERROR; } if (size == 4) { gfloat f; memcpy (&f, data, 4); f = GFLOAT_FROM_BE (f); *num = f; } else if (size == 8) { gdouble d; memcpy (&d, data, 8); d = GDOUBLE_FROM_BE (d); *num = d; } else if (size == 10) { *num = _ext2dbl (data); } else { /* size == 0 means a value of 0.0 */ *num = 0.0; } return ret; } /* * Read the next element as a C string. */ static GstFlowReturn gst_ebml_read_string (GstEbmlRead * ebml, guint32 * id, gchar ** str) { const guint8 *data; guint size; GstFlowReturn ret; ret = gst_ebml_read_bytes (ebml, id, &data, &size); if (ret != GST_FLOW_OK) return ret; *str = g_malloc (size + 1); memcpy (*str, data, size); (*str)[size] = '\0'; return ret; } /* * Read the next element as an ASCII string. */ GstFlowReturn gst_ebml_read_ascii (GstEbmlRead * ebml, guint32 * id, gchar ** str_out) { GstFlowReturn ret; gchar *str; gchar *iter; #ifndef GST_DISABLE_GST_DEBUG guint64 oldoff = ebml->offset; #endif ret = gst_ebml_read_string (ebml, id, &str); if (ret != GST_FLOW_OK) return ret; for (iter = str; *iter != '\0'; iter++) { if (G_UNLIKELY (*iter & 0x80)) { GST_ERROR_OBJECT (ebml, "Invalid ASCII string at offset %" G_GUINT64_FORMAT, oldoff); g_free (str); return GST_FLOW_ERROR; } } *str_out = str; return ret; } /* * Read the next element as a UTF-8 string. */ GstFlowReturn gst_ebml_read_utf8 (GstEbmlRead * ebml, guint32 * id, gchar ** str) { GstFlowReturn ret; #ifndef GST_DISABLE_GST_DEBUG guint64 oldoff = gst_ebml_read_get_pos (ebml); #endif ret = gst_ebml_read_string (ebml, id, str); if (ret != GST_FLOW_OK) return ret; if (str != NULL && *str != NULL && **str != '\0' && !g_utf8_validate (*str, -1, NULL)) { GST_WARNING_OBJECT (ebml->el, "Invalid UTF-8 string at offset %" G_GUINT64_FORMAT, oldoff); } return ret; } /* * Read the next element as a date. * Returns the seconds since the unix epoch. */ GstFlowReturn gst_ebml_read_date (GstEbmlRead * ebml, guint32 * id, gint64 * date) { gint64 ebml_date; GstFlowReturn ret; ret = gst_ebml_read_sint (ebml, id, &ebml_date); if (ret != GST_FLOW_OK) return ret; *date = (ebml_date / GST_SECOND) + GST_EBML_DATE_OFFSET; return ret; } /* * Read the next element as binary data. */ GstFlowReturn gst_ebml_read_binary (GstEbmlRead * ebml, guint32 * id, guint8 ** binary, guint64 * length) { const guint8 *data; guint size; GstFlowReturn ret; ret = gst_ebml_read_bytes (ebml, id, &data, &size); if (ret != GST_FLOW_OK) return ret; *length = size; *binary = g_memdup (data, size); return GST_FLOW_OK; }