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