ogg/
writing.rs

1// Ogg decoder and encoder written in Rust
2//
3// Copyright (c) 2016-2017 est31 <MTest31@outlook.com>
4// and contributors. All rights reserved.
5// Redistribution or use only under the terms
6// specified in the LICENSE file attached to this
7// source distribution.
8
9/*!
10Writing logic
11*/
12
13use std::borrow::Cow;
14use std::result;
15use std::io::{self, Cursor, Write, Seek, SeekFrom};
16use byteorder::{WriteBytesExt, LittleEndian};
17use std::collections::HashMap;
18use crate::crc::vorbis_crc32_update;
19
20
21/// Ogg version of the `std::io::Result` type.
22///
23/// We need `std::result::Result` at other points
24/// too, so we can't use `Result` as the name.
25type IoResult<T> = result::Result<T, io::Error>;
26
27/**
28Writer for packets into an Ogg stream.
29
30Note that the functionality of this struct isn't as well tested as for
31the `PacketReader` struct.
32*/
33pub struct PacketWriter<'writer, T :io::Write> {
34	wtr :T,
35
36	page_vals :HashMap<u32, CurrentPageValues<'writer>>,
37}
38
39struct CurrentPageValues<'writer> {
40	/// `true` if this page is the first one in the logical bitstream
41	first_page :bool,
42	/// Page counter of the current page
43	/// Increased for every page
44	sequence_num :u32,
45
46	/// Points to the first unwritten position in cur_pg_lacing.
47	segment_cnt :u8,
48	cur_pg_lacing :[u8; 255],
49	/// The data and the absgp's of the packets
50	cur_pg_data :Vec<(Cow<'writer, [u8]>, u64)>,
51
52	/// Some(offs), if the last packet
53	/// couldn't make it fully into this page, and
54	/// has to be continued in the next page.
55	///
56	/// `offs` should point to the first idx in
57	/// cur_pg_data[last] that should NOT be written
58	/// in this page anymore.
59	///
60	/// None if all packets can be written nicely.
61	pck_this_overflow_idx :Option<usize>,
62
63	/// Some(offs), if the first packet
64	/// couldn't make it fully into the last page, and
65	/// has to be continued in this page.
66	///
67	/// `offs` should point to the first idx in cur_pg_data[0]
68	/// that hasn't been written.
69	///
70	/// None if all packets can be written nicely.
71	pck_last_overflow_idx :Option<usize>,
72}
73
74/// Specifies whether to end something with the write of the packet.
75///
76/// If you want to end a stream you need to inform the Ogg `PacketWriter`
77/// about this. This is the enum to do so.
78///
79/// Also, Codecs sometimes have special requirements to put
80/// the first packet of the whole stream into its own page.
81/// The `EndPage` variant can be used for this.
82#[derive(PartialEq)]
83#[derive(Clone, Copy)]
84pub enum PacketWriteEndInfo {
85	/// No ends here, just a normal packet
86	NormalPacket,
87	/// Force-end the current page
88	EndPage,
89	/// End the whole logical stream.
90	EndStream,
91}
92
93impl <'writer, T :io::Write> PacketWriter<'writer, T> {
94	pub fn new(wtr :T) -> Self {
95		return PacketWriter {
96			wtr,
97			page_vals : HashMap::new(),
98		};
99	}
100	pub fn into_inner(self) -> T {
101		self.wtr
102	}
103	/// Access the interior writer
104	///
105	/// This allows access of the writer contained inside.
106	/// No guarantees are given onto the pattern of the writes.
107	/// They may change in the future.
108	pub fn inner(&self) -> &T {
109		&self.wtr
110	}
111	/// Access the interior writer mutably
112	///
113	/// This allows access of the writer contained inside.
114	/// No guarantees are given onto the pattern of the writes.
115	/// They may change in the future.
116	pub fn inner_mut(&mut self) -> &mut T {
117		&mut self.wtr
118	}
119	/// Write a packet
120	///
121	///
122	pub fn write_packet<P: Into<Cow<'writer, [u8]>>>(&mut self, pck_cont :P,
123			serial :u32,
124			inf :PacketWriteEndInfo,
125			/* TODO find a better way to design the API around
126				passing the absgp to the underlying implementation.
127				e.g. the caller passes a closure on init which gets
128				called when we encounter a new page... with the param
129				the index inside the current page, or something.
130			*/
131			absgp :u64) -> IoResult<()> {
132		let is_end_stream :bool = inf == PacketWriteEndInfo::EndStream;
133		let pg = self.page_vals.entry(serial).or_insert(
134			CurrentPageValues {
135				first_page : true,
136				sequence_num : 0,
137				segment_cnt : 0,
138				cur_pg_lacing :[0; 255],
139				cur_pg_data :Vec::with_capacity(255),
140				pck_this_overflow_idx : None,
141				pck_last_overflow_idx : None,
142			}
143		);
144
145		let pck_cont = pck_cont.into();
146		let cont_len = pck_cont.len();
147		pg.cur_pg_data.push((pck_cont, absgp));
148
149		let last_data_segment_size = (cont_len % 255) as u8;
150		let needed_segments :usize = (cont_len / 255) + 1;
151		let mut segment_in_page_i :u8 = pg.segment_cnt;
152		let mut at_page_end :bool = false;
153		for segment_i in 0 .. needed_segments {
154			at_page_end = false;
155			if segment_i + 1 < needed_segments {
156				// For all segments containing 255 pieces of data
157				pg.cur_pg_lacing[segment_in_page_i as usize] = 255;
158			} else {
159				// For the last segment, must contain < 255 pieces of data
160				// (including 0)
161				pg.cur_pg_lacing[segment_in_page_i as usize] = last_data_segment_size;
162			}
163			pg.segment_cnt = segment_in_page_i + 1;
164			segment_in_page_i = (segment_in_page_i + 1) % 255;
165			if segment_in_page_i == 0 {
166				if segment_i + 1 < needed_segments {
167					// We have to flush a page, but we know there are more to come...
168					pg.pck_this_overflow_idx = Some((segment_i + 1) * 255);
169					tri!(PacketWriter::write_page(&mut self.wtr, serial, pg,
170						false));
171				} else {
172					// We have to write a page end, and it's the very last
173					// we need to write
174					tri!(PacketWriter::write_page(&mut self.wtr,
175						serial, pg, is_end_stream));
176					// Not actually required
177					// (it is always None except if we set it to Some directly
178					// before we call write_page)
179					pg.pck_this_overflow_idx = None;
180					// Required (it could have been Some(offs) before)
181					pg.pck_last_overflow_idx = None;
182				}
183				at_page_end = true;
184			}
185		}
186		if (inf != PacketWriteEndInfo::NormalPacket) && !at_page_end {
187			// Write a page end
188			tri!(PacketWriter::write_page(&mut self.wtr, serial, pg,
189				is_end_stream));
190
191			pg.pck_last_overflow_idx = None;
192
193			// TODO if inf was PacketWriteEndInfo::EndStream, we have to
194			// somehow erase pg from the hashmap...
195			// any ideas? perhaps needs external scope...
196		}
197		// All went fine.
198		Ok(())
199	}
200	fn write_page(wtr :&mut T, serial :u32, pg :&mut CurrentPageValues,
201			last_page :bool)  -> IoResult<()> {
202		{
203			// The page header with everything but the lacing values:
204			let mut hdr_cur = Cursor::new(Vec::with_capacity(27));
205			tri!(hdr_cur.write_all(&[0x4f, 0x67, 0x67, 0x53, 0x00]));
206			let mut flags :u8 = 0;
207			if pg.pck_last_overflow_idx.is_some() { flags |= 0x01; }
208			if pg.first_page { flags |= 0x02; }
209			if last_page { flags |= 0x04; }
210
211			tri!(hdr_cur.write_u8(flags));
212
213			let pck_data = &pg.cur_pg_data;
214
215			let mut last_finishing_pck_absgp = (-1i64) as u64;
216			for (idx, &(_, absgp)) in pck_data.iter().enumerate() {
217				if !(idx + 1 == pck_data.len() &&
218						pg.pck_this_overflow_idx.is_some()) {
219					last_finishing_pck_absgp = absgp;
220				}
221			}
222
223			tri!(hdr_cur.write_u64::<LittleEndian>(last_finishing_pck_absgp));
224			tri!(hdr_cur.write_u32::<LittleEndian>(serial));
225			tri!(hdr_cur.write_u32::<LittleEndian>(pg.sequence_num));
226
227			// checksum, calculated later on :)
228			tri!(hdr_cur.write_u32::<LittleEndian>(0));
229
230			tri!(hdr_cur.write_u8(pg.segment_cnt));
231
232			let mut hash_calculated :u32;
233
234			let pg_lacing = &pg.cur_pg_lacing[0 .. pg.segment_cnt as usize];
235
236
237			hash_calculated = vorbis_crc32_update(0, hdr_cur.get_ref());
238			hash_calculated = vorbis_crc32_update(hash_calculated, pg_lacing);
239
240			for (idx, (pck, _)) in pck_data.iter().enumerate() {
241				let mut start :usize = 0;
242				if idx == 0 { if let Some(idx) = pg.pck_last_overflow_idx {
243					start = idx;
244				}}
245				let mut end :usize = pck.len();
246				if idx + 1 == pck_data.len() {
247					if let Some(idx) = pg.pck_this_overflow_idx {
248						end = idx;
249					}
250				}
251				hash_calculated = vorbis_crc32_update(hash_calculated,
252					&pck[start .. end]);
253			}
254
255			// Go back to enter the checksum
256			// Don't do excessive checking here (that the seek
257			// succeeded & we are at the right pos now).
258			// It's hopefully not required.
259			tri!(hdr_cur.seek(SeekFrom::Start(22)));
260			tri!(hdr_cur.write_u32::<LittleEndian>(hash_calculated));
261
262			// Now all is done, write the stuff!
263			tri!(wtr.write_all(hdr_cur.get_ref()));
264			tri!(wtr.write_all(pg_lacing));
265			for (idx, (pck, _)) in pck_data.iter().enumerate() {
266				let mut start :usize = 0;
267				if idx == 0 { if let Some(idx) = pg.pck_last_overflow_idx {
268					start = idx;
269				}}
270				let mut end :usize = pck.len();
271				if idx + 1 == pck_data.len() {
272					if let Some(idx) = pg.pck_this_overflow_idx {
273						end = idx;
274					}
275				}
276				tri!(wtr.write_all(&pck[start .. end]));
277			}
278		}
279
280		// Reset the page.
281		pg.first_page = false;
282		pg.sequence_num += 1;
283
284		pg.segment_cnt = 0;
285		// If we couldn't fully write the last
286		// packet, we need to keep it for the next page,
287		// otherwise just clear everything.
288		if pg.pck_this_overflow_idx.is_some() {
289			let d = pg.cur_pg_data.pop().unwrap();
290			pg.cur_pg_data.clear();
291			pg.cur_pg_data.push(d);
292		} else {
293			pg.cur_pg_data.clear();
294		}
295
296		pg.pck_last_overflow_idx = pg.pck_this_overflow_idx;
297		pg.pck_this_overflow_idx = None;
298
299		return Ok(());
300	}
301}
302
303impl<T :io::Seek + io::Write> PacketWriter<'_, T> {
304	pub fn get_current_offs(&mut self) -> Result<u64, io::Error> {
305		self.wtr.stream_position()
306	}
307}
308
309// TODO once 1.18 gets released, move this
310// to the test module and make wtr pub(crate).
311#[test]
312fn test_recapture() {
313	// Test that we can deal with recapture
314	// at varying distances.
315	// This is a regression test
316	use std::io::Write;
317	use super::PacketReader;
318	let mut c = Cursor::new(Vec::new());
319	let test_arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
320	let test_arr_2 = [2, 4, 8, 16, 32, 64, 128, 127, 126, 125, 124];
321	let test_arr_3 = [3, 5, 9, 17, 33, 65, 129, 129, 127, 126, 125];
322	{
323		let np = PacketWriteEndInfo::NormalPacket;
324		let ep = PacketWriteEndInfo::EndPage;
325		{
326			let mut w = PacketWriter::new(&mut c);
327			w.write_packet(&test_arr[..], 0xdeadb33f, ep, 0).unwrap();
328
329			// Now, after the end of the page, put in some noise.
330			w.wtr.write_all(&[0; 38]).unwrap();
331
332			w.write_packet(&test_arr_2[..], 0xdeadb33f, np, 1).unwrap();
333			w.write_packet(&test_arr_3[..], 0xdeadb33f, ep, 2).unwrap();
334		}
335	}
336	//print_u8_slice(c.get_ref());
337	assert_eq!(c.seek(SeekFrom::Start(0)).unwrap(), 0);
338	{
339		let mut r = PacketReader::new(c);
340		let p1 = r.read_packet().unwrap().unwrap();
341		assert_eq!(test_arr, *p1.data);
342		let p2 = r.read_packet().unwrap().unwrap();
343		assert_eq!(test_arr_2, *p2.data);
344		let p3 = r.read_packet().unwrap().unwrap();
345		assert_eq!(test_arr_3, *p3.data);
346	}
347}