1 /**
2  * \file src/opus.cc
3  * \ingroup opus
4  *
5  * The way Opus is encapsulated into an Ogg stream, and the content of the packets we're dealing
6  * with here is defined by [RFC 7584](https://tools.ietf.org/html/rfc7845.html).
7  *
8  * Section 3 "Packet Organization" is critical for us:
9  *
10  * - The first page contains exactly 1 packet, the OpusHead, and it contains it entirely.
11  * - The second page begins the OpusTags packet, which may span several pages.
12  * - The OpusTags packet must finish the page on which it completes.
13  *
14  * The structure of the OpusTags packet is defined in section 5.2 "Comment Header" of the RFC.
15  *
16  * OpusTags is similar to [Vorbis Comment](https://www.xiph.org/vorbis/doc/v-comment.html), which
17  * gives us some context, but let's stick to the RFC for the technical details.
18  *
19  * \todo Validate that the vendor string and comments are valid UTF-8.
20  * \todo Validate that field names are ASCII: 0x20 through 0x7D, 0x3D ('=') excluded.
21  *
22  */
23 
24 #include <opustags.h>
25 
26 #include <sys/endian.h>
27 #include <string.h>
28 
29 #ifdef HAVE_ENDIAN_H
30 #  include <endian.h>
31 #endif
32 
33 #ifdef HAVE_SYS_ENDIAN_H
34 #  include <sys/endian.h>
35 #endif
36 
37 #ifdef __APPLE__
38 #include <libkern/OSByteOrder.h>
39 #define htole32(x) OSSwapHostToLittleInt32(x)
40 #define le32toh(x) OSSwapLittleToHostInt32(x)
41 #endif
42 
parse_tags(const ogg_packet & packet,opus_tags & tags)43 ot::status ot::parse_tags(const ogg_packet& packet, opus_tags& tags)
44 {
45 	if (packet.bytes < 0)
46 		return {st::int_overflow, "Overflowing comment header length"};
47 	size_t size = static_cast<size_t>(packet.bytes);
48 	const char* data = reinterpret_cast<char*>(packet.packet);
49 	size_t pos = 0;
50 	opus_tags my_tags;
51 
52 	// Magic number
53 	if (8 > size)
54 		return {st::cut_magic_number, "Comment header too short for the magic number"};
55 	if (memcmp(data, "OpusTags", 8) != 0)
56 		return {st::bad_magic_number, "Comment header did not start with OpusTags"};
57 
58 	// Vendor
59 	pos = 8;
60 	if (pos + 4 > size)
61 		return {st::cut_vendor_length,
62 		        "Vendor string length did not fit the comment header"};
63 	size_t vendor_length = le32toh(*((uint32_t*) (data + pos)));
64 	if (pos + 4 + vendor_length > size)
65 		return {st::cut_vendor_data, "Vendor string did not fit the comment header"};
66 	my_tags.vendor = std::string(data + pos + 4, vendor_length);
67 	pos += 4 + my_tags.vendor.size();
68 
69 	// Comment count
70 	if (pos + 4 > size)
71 		return {st::cut_comment_count, "Comment count did not fit the comment header"};
72 	uint32_t count = le32toh(*((uint32_t*) (data + pos)));
73 	pos += 4;
74 
75 	// Comments' data
76 	for (uint32_t i = 0; i < count; ++i) {
77 		if (pos + 4 > size)
78 			return {st::cut_comment_length,
79 			        "Comment length did not fit the comment header"};
80 		uint32_t comment_length = le32toh(*((uint32_t*) (data + pos)));
81 		if (pos + 4 + comment_length > size)
82 			return {st::cut_comment_data,
83 			        "Comment string did not fit the comment header"};
84 		const char *comment_value = data + pos + 4;
85 		my_tags.comments.emplace_back(comment_value, comment_length);
86 		pos += 4 + comment_length;
87 	}
88 
89 	// Extra data
90 	my_tags.extra_data = std::string(data + pos, size - pos);
91 
92 	tags = std::move(my_tags);
93 	return st::ok;
94 }
95 
render_tags(const opus_tags & tags)96 ot::dynamic_ogg_packet ot::render_tags(const opus_tags& tags)
97 {
98 	size_t size = 8 + 4 + tags.vendor.size() + 4;
99 	for (const std::string& comment : tags.comments)
100 		size += 4 + comment.size();
101 	size += tags.extra_data.size();
102 
103 	dynamic_ogg_packet op(size);
104 	op.b_o_s = 0;
105 	op.e_o_s = 0;
106 	op.granulepos = 0;
107 	op.packetno = 1;
108 
109 	unsigned char* data = op.packet;
110 	uint32_t n;
111 	memcpy(data, "OpusTags", 8);
112 	n = htole32(tags.vendor.size());
113 	memcpy(data+8, &n, 4);
114 	memcpy(data+12, tags.vendor.data(), tags.vendor.size());
115 	data += 12 + tags.vendor.size();
116 	n = htole32(tags.comments.size());
117 	memcpy(data, &n, 4);
118 	data += 4;
119 	for (const std::string& comment : tags.comments) {
120 		n = htole32(comment.size());
121 		memcpy(data, &n, 4);
122 		memcpy(data+4, comment.data(), comment.size());
123 		data += 4 + comment.size();
124 	}
125 	memcpy(data, tags.extra_data.data(), tags.extra_data.size());
126 
127 	return op;
128 }
129