summaryrefslogtreecommitdiffstats
path: root/epan/dissectors/packet-id3v2.c
diff options
context:
space:
mode:
Diffstat (limited to 'epan/dissectors/packet-id3v2.c')
-rw-r--r--epan/dissectors/packet-id3v2.c576
1 files changed, 576 insertions, 0 deletions
diff --git a/epan/dissectors/packet-id3v2.c b/epan/dissectors/packet-id3v2.c
new file mode 100644
index 00000000..a0ec6638
--- /dev/null
+++ b/epan/dissectors/packet-id3v2.c
@@ -0,0 +1,576 @@
+/* packet-id3v2.c
+ * Routines for ID3v2 dissection
+ * Copyright 2022, Jeff Morriss <jeff.morriss.ws [AT] gmai.com>
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/*
+ * ID3v2 offers a flexible way of storing audio metadata within the audio
+ * file itself. It's the de facto standard for storing metadata in MP3s
+ * but is also supported in other file types including AIFF, WAV, and MP4.
+ *
+ * This dissector was written against ID3 v2.4 and v2.3:
+ * https://id3.org/id3v2.4.0-structure
+ * https://id3.org/id3v2.4.0-frames
+ * https://id3.org/id3v2.3.0
+ */
+
+#include <config.h>
+#include <epan/packet.h>
+#include <epan/expert.h>
+#include <wsutil/mpeg-audio.h>
+
+void proto_reg_handoff_id3v2(void);
+void proto_register_id3v2(void);
+
+static dissector_table_t media_type_dissector_table;
+
+static int proto_id3v2 = -1;
+static int hf_id3v2 = -1;
+static int hf_id3v2_file_id = -1;
+static int hf_id3v2_version = -1;
+static int hf_id3v2_flags = -1;
+static int hf_id3v2_size = -1;
+static int hf_id3v2_frame = -1;
+static int hf_id3v2_frame_id = -1;
+static int hf_id3v2_frame_size = -1;
+static int hf_id3v2_frame_flags = -1;
+static int hf_id3v2_frame_text_encoding = -1;
+static int hf_id3v2_frame_text_description = -1;
+static int hf_id3v2_frame_text_value = -1;
+static int hf_id3v2_frame_ufi_owner = -1;
+static int hf_id3v2_frame_ufi_id = -1;
+static int hf_id3v2_frame_apic_mime_type = -1;
+static int hf_id3v2_frame_apic_picture_type = -1;
+static int hf_id3v2_frame_apic_description = -1;
+static int hf_id3v2_frame_private = -1;
+static int hf_id3v2_frame_comment_language = -1;
+static int hf_id3v2_frame_comment_description = -1;
+static int hf_id3v2_frame_comment_text = -1;
+static int hf_id3v2_undecoded = -1;
+static int hf_id3v2_padding = -1;
+
+static gint ett_id3v2 = -1;
+static gint ett_id3v2_frame = -1;
+
+static expert_field ei_id3v2_undecoded = EI_INIT;
+
+#define ID3V2_MIN_LENGTH 10
+
+/* Technically this is version + revision; break it up into multiple fields? */
+static const value_string id3v2_version_values[] = {
+ { 0x0200, "2.2" },
+ { 0x0300, "2.3" },
+ { 0x0400, "2.4" },
+ { 0, NULL } };
+
+/* The below are v2.3 unless otherwise noted */
+static const string_string id3v2_tag_names[] = {
+ { "AENC", "Audio encryption" },
+ { "APIC", "Attached picture" },
+ { "ASPI", "Audio seek point index" },
+ { "COMM", "Comments" },
+ { "COMR", "Commercial frame" },
+ { "ENCR", "Encryption method registration" },
+ { "EQUA", "Equalization" }, /* v2.3 */
+ { "EQU2", "Equalization (2)" }, /* v2.4 */
+ { "ETCO", "Event timing codes" },
+ { "GEOB", "General encapsulated object" },
+ { "GRID", "Group identification registration" },
+ { "IPLS", "Involved people list" }, /* v2.3 */
+ { "TIPL", "Involved people list" }, /* v2.4 */
+ { "LINK", "Linked information" },
+ { "MCDI", "Music CD identifier" },
+ { "MLLT", "MPEG location lookup table" },
+ { "OWNE", "Ownership frame" },
+ { "PRIV", "Private frame" },
+ { "PCNT", "Play counter" },
+ { "POPM", "Popularimeter" },
+ { "POSS", "Position synchronisation frame" },
+ { "RBUF", "Recommended buffer size" },
+ { "RVAD", "Relative volume adjustment" }, /* v2.3 */
+ { "RVA2", "Relative volume adjustment (2)" }, /* v2.4 */
+ { "RVRB", "Reverb" },
+ { "SEEK", "Seek frame" }, /* v2.4 */
+ { "SIGN", "Signature frame" }, /* v2.4 */
+ { "SYLT", "Synchronized lyric/text" },
+ { "SYTC", "Synchronized tempo codes" },
+ { "TALB", "Album/Movie/Show title" },
+ { "TBPM", "BPM (beats per minute)" },
+ { "TCOM", "Composer" },
+ { "TCON", "Content type" },
+ { "TCOP", "Copyright message" },
+ { "TDAT", "Date" },
+ { "TDEN", "Encoding time" },
+ { "TDLY", "Playlist delay" },
+ { "TDRC", "Recording time" }, /* v2.4 */
+ { "TDRL", "Release time" }, /* v2.4 */
+ { "TDTG", "Tagging time" }, /* v2.4 */
+ { "TENC", "Encoded by" },
+ { "TEXT", "Lyricist/Text writer" },
+ { "TFLT", "File type" },
+ { "TIME", "Time" },
+ { "TIT1", "Content group description" },
+ { "TIT2", "Title/songname/content description" },
+ { "TIT3", "Subtitle/Description refinement" },
+ { "TKEY", "Initial key" },
+ { "TLAN", "Language(s)" },
+ { "TLEN", "Length" },
+ { "TMED", "Media type" },
+ { "TMOO", "Mood" }, /* v2.4 */
+ { "TMCL", "Musicians credits list" }, /* v2.4 */
+ { "TOAL", "Original album/movie/show title" },
+ { "TOFN", "Original filename" },
+ { "TOLY", "Original lyricist(s)/text writer(s)" },
+ { "TOPE", "Original artist(s)/performer(s)" },
+ { "TORY", "Original release year" }, /* v2.3 */
+ { "TDOR", "Original release time" }, /* v2.4 */
+ { "TOWN", "File owner/licensee" },
+ { "TPE1", "Lead performer(s)/Soloist(s)" },
+ { "TPE2", "Band/orchestra/accompaniment" },
+ { "TPE3", "Conductor/performer refinement" },
+ { "TPE4", "Interpreted, remixed, or otherwise modified by" },
+ { "TPOS", "Part of a set" },
+ { "TPUB", "Publisher" },
+ { "TPRO", "Produced notice" },
+ { "TRCK", "Track number/Position in set" },
+ { "TRDA", "Recording dates" },
+ { "TRSN", "Internet radio station name" },
+ { "TRSO", "Internet radio station owner" },
+ { "TSOA", "Album sort order" }, /* v2.4 */
+ { "TSO2", "Album artist sort order" }, /* iTunes */
+ { "TSOP", "Performer sort order" }, /* v2.4 */
+ { "TSOT", "Title sort order" }, /* v2.4 */
+ { "TSIZ", "Size" },
+ { "TSRC", "ISRC (international standard recording code)" },
+ { "TSSE", "Software/Hardware and settings used for encoding" },
+ { "TSST", "Set subtitle" }, /* v2.4 */
+ { "TYER", "Year" },
+ { "TXXX", "User defined" },
+ { "UFID", "Unique file identifier" },
+ { "USER", "Terms of use" },
+ { "USLT", "Unsynchronized lyric/text transcription" },
+ { "WCOM", "Commercial information" },
+ { "WCOP", "Copyright/Legal information" },
+ { "WOAF", "Official audio file webpage" },
+ { "WOAR", "Official artist/performer webpage" },
+ { "WOAS", "Official audio source webpage" },
+ { "WORS", "Official internet radio station homepage" },
+ { "WPAY", "Payment" },
+ { "WPUB", "Publishers official webpage" },
+ { "WXXX", "User defined URL link frame" },
+ { 0, NULL } };
+
+static const value_string id3v2_apic_types[] = {
+ { 0x00, "Other" },
+ { 0x01, "32x32 pixels 'file icon' (PNG only)" },
+ { 0x02, "Other file icon" },
+ { 0x03, "Cover (front)" },
+ { 0x04, "Cover (back)" },
+ { 0x05, "Leaflet page" },
+ { 0x06, "Media (e.g. label side of CD)" },
+ { 0x07, "Lead artist/lead performer/soloist" },
+ { 0x08, "Artist/performer" },
+ { 0x09, "Conductor" },
+ { 0x0A, "Band/Orchestra" },
+ { 0x0B, "Composer" },
+ { 0x0C, "Lyricist/text writer" },
+ { 0x0D, "Recording Location" },
+ { 0x0E, "During recording" },
+ { 0x0F, "During performance" },
+ { 0x10, "Movie/video screen capture" },
+ { 0x11, "A bright coloured fish" },
+ { 0x12, "Illustration" },
+ { 0x13, "Band/artist logotype" },
+ { 0x14, "Publisher/Studio logotype" },
+ { 0, NULL } };
+
+static const value_string id3v2_text_encoding_values[] = {
+ { 0x00, "ISO-8859-1" },
+ { 0x01, "UTF-16 with BOM" },
+ { 0x02, "UTF-16BE" },
+ { 0x03, "UTF-8" },
+ { 0, NULL } };
+
+static guint
+id3v2_decode_encoding(guint8 id3_encoding)
+{
+ guint encoding;
+
+ switch (id3_encoding) {
+ case 0x00:
+ encoding = ENC_ISO_8859_1;
+ break;
+ case 0x01:
+ encoding = ENC_UTF_16|ENC_BOM|ENC_LITTLE_ENDIAN;
+ break;
+ case 0x02:
+ encoding = ENC_UTF_16|ENC_BIG_ENDIAN;
+ break;
+ case 0x03:
+ default:
+ encoding = ENC_UTF_8;
+ break;
+ }
+
+ return encoding;
+}
+
+static char *
+id3v2_dissect_textz_item(tvbuff_t *tvb, proto_tree *tree, guint *offset, guint8 id3_encoding, int hf)
+{
+ guint encoding;
+ char *text_value;
+ guint text_length;
+
+ encoding = id3v2_decode_encoding(id3_encoding);
+
+ text_value = tvb_get_stringz_enc(wmem_packet_scope(), tvb, *offset, &text_length, encoding);
+ proto_tree_add_item(tree, hf, tvb, *offset, text_length, encoding);
+ *offset += text_length;
+
+ return text_value;
+}
+
+static char *
+id3v2_dissect_text_item(tvbuff_t *tvb, proto_tree *tree, guint *offset, guint end, guint8 id3_encoding, int hf)
+{
+ guint encoding;
+ char *text_value;
+
+ encoding = id3v2_decode_encoding(id3_encoding);
+
+ text_value = tvb_get_string_enc(wmem_packet_scope(), tvb, *offset, (end - *offset), encoding);
+ proto_tree_add_item(tree, hf, tvb, *offset, (end - *offset), encoding);
+
+ return text_value;
+}
+
+static void
+dissect_id3v2_comment_frame(tvbuff_t *tvb, proto_tree *tree, guint offset, guint length, proto_item *pi)
+{
+ guint8 id3_encoding;
+ guint end = offset + length;
+ char *comment_value;
+
+ id3_encoding = tvb_get_guint8(tvb, offset);
+ proto_tree_add_item(tree, hf_id3v2_frame_text_encoding, tvb, offset, 1, ENC_NA);
+ offset += 1;
+
+ proto_tree_add_item(tree, hf_id3v2_frame_comment_language, tvb, offset, 3, ENC_ISO_8859_1);
+ offset += 3;
+
+ id3v2_dissect_textz_item(tvb, tree, &offset, id3_encoding, hf_id3v2_frame_comment_description);
+
+ comment_value = id3v2_dissect_text_item(tvb, tree, &offset, end, id3_encoding, hf_id3v2_frame_comment_text);
+ proto_item_append_text(pi, ": %s", comment_value);
+}
+
+static void
+dissect_id3v2_apic_frame(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint offset, guint length)
+{
+ guint8 id3_encoding;
+ guint end = offset + length;
+ char *mime_type;
+ tvbuff_t *image_tvb;
+
+ id3_encoding = tvb_get_guint8(tvb, offset);
+ proto_tree_add_item(tree, hf_id3v2_frame_text_encoding, tvb, offset, 1, ENC_NA);
+ offset += 1;
+
+ mime_type = id3v2_dissect_textz_item(tvb, tree, &offset, id3_encoding, hf_id3v2_frame_apic_mime_type);
+
+ proto_tree_add_item(tree, hf_id3v2_frame_apic_picture_type, tvb, offset, 1, ENC_NA);
+ offset += 1;
+
+ id3v2_dissect_textz_item(tvb, tree, &offset, id3_encoding, hf_id3v2_frame_apic_description);
+
+ image_tvb = tvb_new_subset_length(tvb, offset, (end - offset));
+ dissector_try_string(media_type_dissector_table, mime_type, image_tvb, pinfo, tree, NULL);
+}
+
+static char *
+dissect_id3v2_text_frame(tvbuff_t *tvb, proto_item *item, proto_tree *tree, guint offset, guint length, gboolean is_txxx)
+{
+ guint8 id3_encoding;
+ char *text_value;
+ guint end = offset + length;
+
+ id3_encoding = tvb_get_guint8(tvb, offset);
+ proto_tree_add_item(tree, hf_id3v2_frame_text_encoding, tvb, offset, 1, ENC_NA);
+ offset += 1;
+
+ if (is_txxx) {
+ /* This is a user-defined text frame (contains a description and a value) */
+
+ text_value = id3v2_dissect_textz_item(tvb, tree, &offset, id3_encoding, hf_id3v2_frame_text_description);
+ proto_item_append_text(item, ": %s", text_value);
+ }
+
+ text_value = id3v2_dissect_text_item(tvb, tree, &offset, end, id3_encoding, hf_id3v2_frame_text_value);
+ proto_item_append_text(item, ": %s", text_value);
+
+ return text_value;
+}
+
+static int
+dissect_id3v2_frame(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint offset, guint8 id3_version)
+{
+ proto_item *frame_item;
+ proto_tree *frame_tree;
+ guint32 size;
+ char *frame_id;
+
+ frame_id = tvb_get_string_enc(wmem_packet_scope(), tvb, offset, 4, ENC_ISO_8859_1);
+
+ if (strlen(frame_id) == 0) {
+ proto_tree_add_item(tree, hf_id3v2_padding, tvb, offset, -1, ENC_NA);
+ return offset + tvb_reported_length_remaining(tvb, offset);
+ }
+
+ frame_item = proto_tree_add_item(tree, hf_id3v2_frame, tvb, offset, -1, ENC_NA);
+ frame_tree = proto_item_add_subtree(frame_item, ett_id3v2_frame);
+
+ proto_tree_add_item(frame_tree, hf_id3v2_frame_id, tvb, offset, 4, ENC_ISO_8859_1);
+ offset += 4;
+ proto_item_set_text(frame_item, "%s", str_to_str(frame_id, id3v2_tag_names, "Unknown: %s"));
+
+ if (id3_version == 0x04)
+ size = decode_synchsafe_int(tvb_get_guint32(tvb, offset, ENC_BIG_ENDIAN));
+ else
+ size = tvb_get_guint32(tvb, offset, ENC_BIG_ENDIAN);
+ proto_tree_add_uint(frame_tree, hf_id3v2_frame_size, tvb, offset, 4, size);
+ /* `size` does not include the 10-byte header */
+ proto_item_set_len(frame_item, size+10);
+ offset += 4;
+
+ /* TODO: decode each flag */
+ proto_tree_add_item(frame_tree, hf_id3v2_frame_flags, tvb, offset, 2, ENC_NA);
+ offset += 2;
+
+ if (frame_id[0] == 'T') {
+ char *tv;
+
+ tv = dissect_id3v2_text_frame(tvb, frame_item, frame_tree, offset, size, !strcmp(frame_id, "TXXX"));
+ offset += size;
+
+ if (!strcmp(frame_id, "TIT2"))
+ col_append_fstr(pinfo->cinfo, COL_INFO, "Title: %s, ", tv);
+ if (!strcmp(frame_id, "TPE1"))
+ col_append_fstr(pinfo->cinfo, COL_INFO, "Artist: %s, ", tv);
+ } else if (!strcmp(frame_id, "UFID")) {
+ guint text_length;
+ char *text_value;
+
+ text_value = tvb_get_stringz_enc(wmem_packet_scope(), tvb, offset, &text_length, ENC_UTF_8);
+ proto_tree_add_item(frame_tree, hf_id3v2_frame_ufi_owner, tvb, offset, text_length, ENC_ISO_8859_1);
+ offset += text_length;
+ proto_item_append_text(frame_item, " (Owner: %s)", text_value);
+
+ DISSECTOR_ASSERT(size >= text_length);
+ proto_tree_add_item(frame_tree, hf_id3v2_frame_ufi_id, tvb, offset, size-text_length, ENC_NA);
+ offset += (size-text_length);
+ } else if (!strcmp(frame_id, "APIC")) {
+ dissect_id3v2_apic_frame(tvb, pinfo, frame_tree, offset, size);
+ offset += size;
+ } else if (!strcmp(frame_id, "COMM")) {
+ dissect_id3v2_comment_frame(tvb, frame_tree, offset, size, frame_item);
+ offset += size;
+ } else if (!strcmp(frame_id, "PRIV")) {
+ proto_tree_add_item(frame_tree, hf_id3v2_frame_private, tvb, offset, size, ENC_NA);
+ offset += size;
+ } else {
+ proto_item *pi;
+
+ /* TODO: decode the rest */
+ pi = proto_tree_add_item(frame_tree, hf_id3v2_undecoded, tvb, offset, size, ENC_NA);
+ expert_add_info(pinfo, pi, &ei_id3v2_undecoded);
+ offset += size;
+ }
+
+ return offset;
+}
+
+static int
+dissect_id3v2(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
+{
+ proto_item *id3v2_item;
+ proto_tree *id3v2_tree;
+ tvbuff_t *id3v2_tvb;
+ guint offset = 0;
+ guint32 size;
+ guint8 id3_version;
+
+ /* Check that the packet is long enough for it to belong to us. */
+ if (tvb_reported_length(tvb) < ID3V2_MIN_LENGTH)
+ return 0;
+
+ /* Check if the first 3 bytes are "ID3" */
+ if (tvb_get_guint24(tvb, 0, ENC_BIG_ENDIAN) != 0x494433)
+ return 0;
+
+ /* Check if any of the high bits of the (synchsafe int) size are set */
+ if (tvb_get_guint8(tvb, 7) & 0x80 ||
+ tvb_get_guint8(tvb, 8) & 0x80 ||
+ tvb_get_guint8(tvb, 9) & 0x80 ||
+ tvb_get_guint8(tvb, 10) & 0x80)
+ return 0;
+
+ /* Looks like this is ID3v2... */
+ col_set_str(pinfo->cinfo, COL_PROTOCOL, "ID3v2");
+ col_clear(pinfo->cinfo, COL_INFO);
+
+ size = decode_synchsafe_int(tvb_get_guint32(tvb, 6, ENC_BIG_ENDIAN));
+ /* `size` does not include the 10-byte header */
+ id3v2_tvb = tvb_new_subset_length(tvb, offset, size+10);
+ id3v2_item = proto_tree_add_item(tree, hf_id3v2, id3v2_tvb, offset, tvb_captured_length(id3v2_tvb), ENC_NA);
+ id3v2_tree = proto_item_add_subtree(id3v2_item, ett_id3v2);
+
+ proto_tree_add_item(id3v2_tree, hf_id3v2_file_id, id3v2_tvb, offset, 3, ENC_ISO_8859_1);
+ offset += 3;
+
+ id3_version = tvb_get_guint8(tvb, offset); /* Fetch just the major version info */
+ proto_tree_add_item(id3v2_tree, hf_id3v2_version, id3v2_tvb, offset, 2, ENC_BIG_ENDIAN);
+ offset += 2;
+
+ /* TODO: decode each flag */
+ proto_tree_add_item(id3v2_tree, hf_id3v2_flags, id3v2_tvb, offset, 1, ENC_NA);
+ offset += 1;
+
+ proto_tree_add_uint(id3v2_tree, hf_id3v2_size, id3v2_tvb, offset, 4, size);
+ offset += 4;
+
+ /* TODO: detect and dissect extended header */
+
+ while(tvb_reported_length_remaining(id3v2_tvb, offset)) {
+ offset = dissect_id3v2_frame(id3v2_tvb, pinfo, id3v2_tree, offset, id3_version);
+ }
+
+ /* TODO: detect and dissect footer */
+
+ return tvb_reported_length(id3v2_tvb);
+}
+
+void
+proto_register_id3v2(void)
+{
+ expert_module_t *expert_id3v2;
+
+ static hf_register_info hf[] = {
+ { &hf_id3v2,
+ { "ID3v2", "id3v2", FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL }},
+ { &hf_id3v2_file_id,
+ { "File Identifier", "id3v2.file_id",
+ FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL }},
+ { &hf_id3v2_version,
+ { "Version", "id3v2.version",
+ FT_UINT16, BASE_HEX, VALS(id3v2_version_values), 0, NULL, HFILL }},
+ { &hf_id3v2_flags,
+ { "Flags", "id3v2.flags",
+ FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }},
+ { &hf_id3v2_size,
+ { "Size", "id3v2.size",
+ FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL }},
+ { &hf_id3v2_frame,
+ { "Frame", "id3v2.frame",
+ FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL }},
+ { &hf_id3v2_frame_id,
+ { "Frame Identifier", "id3v2.frame.id",
+ FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL }},
+ { &hf_id3v2_frame_size,
+ { "Frame Size", "id3v2.frame.size",
+ FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL }},
+ { &hf_id3v2_frame_flags,
+ { "Frame Flags", "id3v2.frame.flags",
+ FT_UINT16, BASE_HEX, NULL, 0, NULL, HFILL }},
+ { &hf_id3v2_frame_text_encoding,
+ { "Text Encoding", "id3v2.frame.text_encoding",
+ FT_UINT8, BASE_HEX, VALS(id3v2_text_encoding_values), 0, NULL, HFILL }},
+ { &hf_id3v2_frame_text_description,
+ { "Text description", "id3v2.frame.text_description",
+ FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL }},
+ { &hf_id3v2_frame_text_value,
+ { "Text value", "id3v2.frame.text_value",
+ FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL }},
+ { &hf_id3v2_frame_ufi_owner,
+ { "Unique file identifier owner", "id3v2.unique_file_identifier_owner",
+ FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL }},
+ { &hf_id3v2_frame_ufi_id,
+ { "Unique file identifier", "id3v2.unique_file_identifier",
+ FT_BYTES, BASE_SHOW_UTF_8_PRINTABLE, NULL, 0, NULL, HFILL }},
+ { &hf_id3v2_frame_apic_mime_type,
+ { "Attached picture MIME type", "id3v2.apic.mime_type",
+ FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL }},
+ { &hf_id3v2_frame_apic_picture_type,
+ { "Attached picture type", "id3v2.apic.type",
+ FT_UINT8, BASE_NONE, VALS(id3v2_apic_types), 0, NULL, HFILL }},
+ { &hf_id3v2_frame_apic_description,
+ { "Attached picture description", "id3v2.apic.description",
+ FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL }},
+ { &hf_id3v2_frame_private,
+ { "Private frame", "id3v2.private",
+ FT_BYTES, BASE_SHOW_UTF_8_PRINTABLE, NULL, 0, NULL, HFILL }},
+ { &hf_id3v2_frame_comment_language,
+ { "Comment language", "id3v2.comment.language",
+ FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL }},
+ { &hf_id3v2_frame_comment_description,
+ { "Comment description", "id3v2.comment.description",
+ FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL }},
+ { &hf_id3v2_frame_comment_text,
+ { "Comment text", "id3v2.comment.text",
+ FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL }},
+ { &hf_id3v2_undecoded,
+ { "Undecoded frame", "id3v2.undecoded",
+ FT_BYTES, BASE_SHOW_UTF_8_PRINTABLE, NULL, 0, NULL, HFILL }},
+ { &hf_id3v2_padding,
+ { "Padding", "id3v2.padding",
+ FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }},
+ };
+
+ static gint *ett[] = {
+ &ett_id3v2,
+ &ett_id3v2_frame,
+ };
+
+ static ei_register_info ei[] = {
+ { &ei_id3v2_undecoded,
+ { "id3v2.undecoded", PI_UNDECODED, PI_NOTE,
+ "Undecoded frame", EXPFILL }
+ }
+ };
+
+ proto_id3v2 = proto_register_protocol("ID3v2", "ID3v2", "id3v2");
+
+ proto_register_field_array(proto_id3v2, hf, array_length(hf));
+ proto_register_subtree_array(ett, array_length(ett));
+
+ expert_id3v2 = expert_register_protocol(proto_id3v2);
+ expert_register_field_array(expert_id3v2, ei, array_length(ei));
+
+ /* Allow other dissectors to find this one by name. */
+ register_dissector("id3v2", dissect_id3v2, proto_id3v2);
+}
+
+void
+proto_reg_handoff_id3v2(void)
+{
+ media_type_dissector_table = find_dissector_table("media_type");
+}
+
+/*
+ * Editor modelines - https://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 8
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ *
+ * vi: set shiftwidth=8 tabstop=8 expandtab:
+ * :indentSize=4:tabSize=8:noTabs=true:
+ */