1use std::io;
13
14use crate::entry::Block;
15use crate::footer::Footer;
16
17fn invalid(msg: &'static str) -> io::Error {
18 io::Error::new(io::ErrorKind::InvalidData, msg)
19}
20
21fn in_bounds(off: u64, len: u64, file_size: u64) -> bool {
23 off.checked_add(len).is_some_and(|end| end <= file_size)
24}
25
26pub fn validate_footer(footer: &Footer, file_size: u64) -> io::Result<()> {
31 if !in_bounds(
32 footer.manifest_offset,
33 footer.manifest_compressed,
34 file_size,
35 ) {
36 return Err(invalid("manifest region out of bounds"));
37 }
38 if !in_bounds(footer.payload_offset, footer.payload_size, file_size) {
39 return Err(invalid("payload region out of bounds"));
40 }
41 if footer.manifest_original > MAX_MANIFEST_ORIGINAL
44 || footer.manifest_original > file_size.saturating_mul(MAX_MANIFEST_EXPANSION)
45 {
46 return Err(invalid("manifest expands implausibly"));
47 }
48 if footer.dict_size > 0 && !in_bounds(footer.dict_offset, footer.dict_size as u64, file_size) {
49 return Err(invalid("dictionary region out of bounds"));
50 }
51 Ok(())
52}
53
54const MAX_MANIFEST_EXPANSION: u64 = 1024;
58
59const MAX_MANIFEST_ORIGINAL: u64 = 256 << 20;
63
64pub fn block_extent(footer: &Footer, block: &Block) -> io::Result<(u64, usize)> {
70 let abs = footer
71 .payload_offset
72 .checked_add(block.payload_offset)
73 .ok_or_else(|| invalid("payload offset overflow"))?;
74 let payload_end = footer
75 .payload_offset
76 .checked_add(footer.payload_size)
77 .ok_or_else(|| invalid("payload region overflow"))?;
78 if !in_bounds(abs, block.compressed_size, payload_end) {
79 return Err(invalid("block extends past the payload region"));
80 }
81 let len = usize::try_from(block.compressed_size)
82 .map_err(|_| invalid("block larger than this address space"))?;
83 Ok((abs, len))
84}
85
86pub fn block_original_size(block: &Block) -> io::Result<usize> {
95 if block.original_size > MAX_BLOCK_ORIGINAL {
96 return Err(invalid("block decompresses to an implausible size"));
97 }
98 usize::try_from(block.original_size)
99 .map_err(|_| invalid("block larger than this address space"))
100}
101
102const MAX_BLOCK_ORIGINAL: u64 = 256 * 1024 * 1024;
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use crate::footer::Flags;
112
113 fn footer() -> Footer {
114 Footer {
115 format_version: 1,
116 flags: Flags::empty(),
117 manifest_offset: 100,
118 manifest_compressed: 50,
119 manifest_original: 200,
120 payload_offset: 200,
121 payload_size: 300,
122 dict_offset: 0,
123 dict_size: 0,
124 manifest_checksum: [0; 4],
125 }
126 }
127
128 fn block(offset: u64, compressed: u64, original: u64) -> Block {
129 Block {
130 payload_offset: offset,
131 compressed_size: compressed,
132 original_size: original,
133 content_hash: [0u8; 32],
134 }
135 }
136
137 #[test]
138 fn well_formed_footer_passes() {
139 assert!(validate_footer(&footer(), 1000).is_ok());
140 }
141
142 #[test]
143 fn regions_past_the_file_are_rejected() {
144 let mut f = footer();
145 f.manifest_compressed = u64::MAX;
146 assert!(validate_footer(&f, 1000).is_err());
147
148 let mut f = footer();
149 f.payload_size = 10_000;
150 assert!(validate_footer(&f, 1000).is_err());
151
152 let mut f = footer();
153 f.dict_size = 900;
154 f.dict_offset = 500;
155 assert!(validate_footer(&f, 1000).is_err());
156 }
157
158 #[test]
159 fn overflowing_offsets_are_rejected() {
160 let mut f = footer();
161 f.manifest_offset = u64::MAX;
162 f.manifest_compressed = 1;
163 assert!(validate_footer(&f, 1000).is_err());
164 }
165
166 #[test]
167 fn absurd_manifest_expansion_is_rejected() {
168 let mut f = footer();
169 f.manifest_original = u64::MAX;
170 assert!(validate_footer(&f, 1000).is_err());
171 }
172
173 #[test]
174 fn block_within_payload_resolves() {
175 let f = footer();
176 let (abs, len) = block_extent(&f, &block(10, 20, 40)).unwrap();
177 assert_eq!((abs, len), (210, 20));
178 }
179
180 #[test]
181 fn block_past_the_payload_is_rejected() {
182 let f = footer();
183 assert!(block_extent(&f, &block(290, 20, 40)).is_err());
184 assert!(block_extent(&f, &block(u64::MAX, 1, 1)).is_err());
185 assert!(block_extent(&f, &block(0, u64::MAX, 1)).is_err());
186 }
187
188 #[test]
189 fn absurd_block_size_is_rejected() {
190 assert!(block_original_size(&block(0, 10, 40)).is_ok());
191 assert!(block_original_size(&block(0, 1, u64::MAX)).is_err());
192 assert!(block_original_size(&block(0, 4096, 4096)).is_ok());
194 }
195
196 #[test]
200 fn extreme_but_real_compression_ratios_are_accepted() {
201 let compressed = 30u64;
202 let original = 256 * 1024u64;
203 assert!(original / compressed > 4096, "fixture must be extreme");
204 assert!(block_original_size(&block(0, compressed, original)).is_ok());
205 }
206}