Skip to content

Commit aaa89db

Browse files
committed
ogg_pager: Fix writing of perfectly divisible packets
1 parent 09dbfb5 commit aaa89db

File tree

2 files changed

+54
-29
lines changed

2 files changed

+54
-29
lines changed

ogg_pager/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
## [0.7.0] - 2024-10-26
10+
11+
### Fixed
12+
- Writing a packet whose size is perfectly divisible by 255 would make the second to last segment have a size of 0, rather than 255 ([issue](https://github.com/Serial-ATA/lofty-rs/issues/469)) ([PR](https://github.com/Serial-ATA/lofty-rs/pull/475))
13+
14+
### Removed
15+
- `Page::extend()` ([PR](https://github.com/Serial-ATA/lofty-rs/pull/475))
16+
- `segment_table()` ([PR](https://github.com/Serial-ATA/lofty-rs/pull/475))
17+
918
## [0.6.1] - 2024-04-21
1019

1120
### Fixed

ogg_pager/src/paginate.rs

Lines changed: 45 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ struct PaginateContext {
1616
idx: usize,
1717
remaining_page_size: usize,
1818
current_packet_len: usize,
19-
last_segment_size: u8,
19+
last_segment_size: Option<u8>,
2020
}
2121

2222
impl PaginateContext {
@@ -29,14 +29,14 @@ impl PaginateContext {
2929
flags: PaginateContextFlags {
3030
first_page: true,
3131
fresh_packet: true,
32-
packet_spans_multiple_pages: false,
3332
packet_finished_on_page: false,
33+
need_nil_page: false,
3434
},
3535
pos: 0,
3636
idx: 0,
3737
remaining_page_size: MAX_WRITTEN_CONTENT_SIZE,
3838
current_packet_len: 0,
39-
last_segment_size: 0,
39+
last_segment_size: None,
4040
}
4141
}
4242

@@ -45,7 +45,7 @@ impl PaginateContext {
4545
self.pos = 0;
4646

4747
self.current_packet_len = packet.len();
48-
self.last_segment_size = (packet.len() % 255) as u8;
48+
self.last_segment_size = Some((packet.len() % 255) as u8);
4949
}
5050

5151
fn flush_page(&mut self, content: &mut Vec<u8>) {
@@ -61,13 +61,7 @@ impl PaginateContext {
6161
_ => 0,
6262
}
6363
},
64-
abgp: if self.flags.packet_finished_on_page {
65-
self.abgp
66-
} else {
67-
// A special value of '-1' (in two's complement) indicates that no packets
68-
// finish on this page.
69-
1_u64.wrapping_neg()
70-
},
64+
abgp: 0,
7165
stream_serial: self.stream_serial,
7266
sequence_number: self.idx as u32,
7367
segments: Vec::new(),
@@ -79,27 +73,36 @@ impl PaginateContext {
7973
let content_len = content.len();
8074
self.pos += content_len as u64;
8175

76+
// Determine how many *full* segments our page content takes up.
77+
// Anything < 255 will be covered by `last_segment_size`
78+
let full_segments_occupied = content_len / 255;
79+
8280
// Moving on to a new packet
8381
debug_assert!(self.pos <= self.current_packet_len as u64);
84-
if self.pos == self.current_packet_len as u64 {
85-
self.flags.packet_spans_multiple_pages = false;
82+
let at_packet_end = self.pos == self.current_packet_len as u64;
83+
if at_packet_end && full_segments_occupied == MAX_WRITTEN_SEGMENT_COUNT {
84+
// See comment on `PaginateContextFlags.need_nil_page`
85+
self.flags.need_nil_page = true;
86+
self.flags.packet_finished_on_page = false;
8687
}
8788

88-
// We need to determine how many segments our page content takes up.
89-
// If it takes up the remainder of the segment table for the entire packet,
90-
// we'll just consume it as is.
91-
let segments_occupied = if content_len >= 255 {
92-
content_len.div_ceil(255)
89+
if self.flags.packet_finished_on_page {
90+
header.abgp = self.abgp;
9391
} else {
94-
1
95-
};
92+
// A special value of '-1' (in two's complement) indicates that no packets
93+
// finish on this page.
94+
header.abgp = 1_u64.wrapping_neg()
95+
}
9696

97-
debug_assert!(segments_occupied <= MAX_WRITTEN_SEGMENT_COUNT);
98-
if self.flags.packet_spans_multiple_pages {
99-
header.segments = vec![255; segments_occupied];
100-
} else {
101-
header.segments = vec![255; segments_occupied - 1];
102-
header.segments.push(self.last_segment_size);
97+
debug_assert!(full_segments_occupied <= MAX_WRITTEN_SEGMENT_COUNT);
98+
header.segments = vec![255; full_segments_occupied];
99+
100+
if full_segments_occupied != MAX_WRITTEN_SEGMENT_COUNT {
101+
// End of the packet
102+
let last_segment_size = self
103+
.last_segment_size
104+
.expect("fresh packet should be indicated at this point");
105+
header.segments.push(last_segment_size);
103106
}
104107

105108
self.pages.push(Page {
@@ -117,8 +120,16 @@ impl PaginateContext {
117120
struct PaginateContextFlags {
118121
first_page: bool,
119122
fresh_packet: bool,
120-
packet_spans_multiple_pages: bool,
121123
packet_finished_on_page: bool,
124+
// A 'nil' page just means it is zero-length. This is used when our packet is perfectly
125+
// divisible by `255 * MAX_SEGMENT_COUNT`. We need a zero-sized segment to mark the end of our
126+
// packet across page boundaries.
127+
//
128+
// Very rare circumstance, but still possible.
129+
//
130+
// From <https://xiph.org/ogg/doc/framing.html>:
131+
// "Note also that a 'nil' (zero length) packet is not an error; it consists of nothing more than a lacing value of zero in the header."
132+
need_nil_page: bool,
122133
}
123134

124135
/// Create pages from a list of packets
@@ -162,6 +173,13 @@ fn paginate_packet(ctx: &mut PaginateContext, packet: &[u8]) -> Result<()> {
162173
let mut page_content = Vec::with_capacity(MAX_WRITTEN_CONTENT_SIZE);
163174
let mut packet = packet;
164175
loop {
176+
// See comment on `PaginateContextFlags.need_nil_page`
177+
if ctx.flags.need_nil_page {
178+
assert!(packet.is_empty());
179+
ctx.flags.packet_finished_on_page = true;
180+
ctx.flush_page(&mut page_content);
181+
}
182+
165183
if packet.is_empty() {
166184
break;
167185
}
@@ -175,8 +193,6 @@ fn paginate_packet(ctx: &mut PaginateContext, packet: &[u8]) -> Result<()> {
175193

176194
if bytes_read <= MAX_WRITTEN_CONTENT_SIZE && packet.is_empty() {
177195
ctx.flags.packet_finished_on_page = true;
178-
} else {
179-
ctx.flags.packet_spans_multiple_pages = true;
180196
}
181197

182198
if ctx.remaining_page_size == 0 || packet.is_empty() {

0 commit comments

Comments
 (0)