This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / knot2 / crates / knot-pack / src / frame.rs
16 kB 455 lines
1use std::collections::HashMap; 2 3use flate2::{Decompress, FlushDecompress, Status}; 4use gix_pack::data::{Entry, entry::Header}; 5 6use knot_types::ObjectCount; 7 8use crate::error::{PackError, PackLimit}; 9use crate::ids::PackOffset; 10use crate::meter::{PackLimits, check_depth, malformed, pack_object_count}; 11use crate::pkt::{self, Frame}; 12 13const HEADER_SLACK: usize = 64; 14 15fn pack_start(buf: &[u8]) -> Option<usize> { 16 let caps = pkt::first_command(buf) 17 .map(pkt::parse_caps) 18 .unwrap_or_default(); 19 let boundary = if caps.push_options { 2 } else { 1 }; 20 pkt::frames(buf, Some(boundary)) 21 .filter_map(|item| match item { 22 Ok((Frame::Flush, at)) => Some(at), 23 _ => None, 24 }) 25 .nth(boundary - 1) 26} 27 28fn new_oid_field(line: &[u8]) -> Option<&[u8]> { 29 let line = line.split(|byte| *byte == 0).next().unwrap_or(line); 30 line.split(|byte| *byte == b' ').nth(1) 31} 32 33fn no_pack_needed(buf: &[u8]) -> bool { 34 pkt::frames(buf, Some(1)) 35 .filter_map(|item| match item { 36 Ok((Frame::Data(payload), _)) => Some(payload), 37 _ => None, 38 }) 39 .all(|line| { 40 new_oid_field(line) 41 .map(|oid| oid.iter().all(|byte| *byte == b'0')) 42 .unwrap_or(false) 43 }) 44} 45 46const PREAMBLE_SCAN_LIMIT: usize = 16 * 1024 * 1024; 47 48trait PackSource { 49 fn len(&self) -> u64; 50 fn read_at(&self, offset: u64, buf: &mut [u8]) -> std::io::Result<usize>; 51} 52 53impl PackSource for [u8] { 54 fn len(&self) -> u64 { 55 <[u8]>::len(self) as u64 56 } 57 fn read_at(&self, offset: u64, buf: &mut [u8]) -> std::io::Result<usize> { 58 let start = usize::try_from(offset) 59 .unwrap_or(usize::MAX) 60 .min(<[u8]>::len(self)); 61 let read = (<[u8]>::len(self) - start).min(buf.len()); 62 buf[..read].copy_from_slice(&self[start..start + read]); 63 Ok(read) 64 } 65} 66 67struct FileSource<'a> { 68 file: &'a std::fs::File, 69 len: u64, 70} 71 72impl PackSource for FileSource<'_> { 73 fn len(&self) -> u64 { 74 self.len 75 } 76 fn read_at(&self, offset: u64, buf: &mut [u8]) -> std::io::Result<usize> { 77 use std::os::unix::fs::FileExt; 78 let read = self.len.saturating_sub(offset).min(buf.len() as u64) as usize; 79 self.file.read_exact_at(&mut buf[..read], offset)?; 80 Ok(read) 81 } 82} 83 84fn read_head<S: PackSource + ?Sized>(source: &S, upto: u64) -> Result<Vec<u8>, PackError> { 85 let limit = upto.min(PREAMBLE_SCAN_LIMIT as u64) as usize; 86 let mut head = vec![0u8; limit]; 87 let read = source 88 .read_at(0, &mut head) 89 .map_err(|error| PackError::Pack(format!("pack read: {error}")))?; 90 head.truncate(read); 91 Ok(head) 92} 93 94struct EntryInflate { 95 data_offset: PackOffset, 96 decompressed_size: u64, 97 decompress: Decompress, 98 produced: u64, 99} 100 101impl EntryInflate { 102 fn feed<S: PackSource + ?Sized>( 103 &mut self, 104 pack: &S, 105 scratch: &mut [u8], 106 chunk: &mut [u8], 107 ) -> Result<Option<PackOffset>, PackError> { 108 loop { 109 let consumed = self.decompress.total_in(); 110 let out_before = self.decompress.total_out(); 111 let read = pack 112 .read_at(self.data_offset.get() + consumed, chunk) 113 .map_err(|error| PackError::Pack(format!("pack read: {error}")))?; 114 let status = self 115 .decompress 116 .decompress(&chunk[..read], scratch, FlushDecompress::None) 117 .map_err(|error| PackError::Pack(format!("inflate: {error}")))?; 118 self.produced += self.decompress.total_out() - out_before; 119 if self.produced > self.decompressed_size { 120 return Err(malformed("object inflates beyond its declared size")); 121 } 122 match status { 123 Status::StreamEnd => { 124 return if self.produced == self.decompressed_size { 125 self.data_offset 126 .get() 127 .checked_add(self.decompress.total_in()) 128 .map(|offset| Some(PackOffset::new(offset))) 129 .ok_or_else(|| malformed("pack offset overflow")) 130 } else { 131 Err(malformed("object decompressed size mismatch")) 132 }; 133 } 134 Status::Ok | Status::BufError => { 135 if self.decompress.total_in() == consumed 136 && self.decompress.total_out() == out_before 137 { 138 return Ok(None); 139 } 140 } 141 } 142 } 143 } 144} 145 146#[derive(Default)] 147struct PackProgress { 148 num_objects: ObjectCount, 149 objects_done: ObjectCount, 150 next_offset: PackOffset, 151 total_decompressed: u64, 152 base_of: HashMap<PackOffset, PackOffset>, 153 current: Option<EntryInflate>, 154 depth_checked: bool, 155} 156 157impl PackProgress { 158 fn scan<S: PackSource + ?Sized>( 159 &mut self, 160 pack: &S, 161 limits: &PackLimits, 162 kind: gix::hash::Kind, 163 ) -> Result<Option<usize>, PackError> { 164 let hash_len = kind.len_in_bytes(); 165 let len = pack.len(); 166 let mut scratch = [0u8; 8192]; 167 let mut chunk = [0u8; 8192]; 168 let mut header = [0u8; HEADER_SLACK]; 169 loop { 170 if self.objects_done == self.num_objects { 171 if !self.depth_checked { 172 check_depth(&self.base_of, limits.max_delta_depth)?; 173 self.depth_checked = true; 174 } 175 let total_len = (self.next_offset.get() as usize) 176 .checked_add(hash_len) 177 .ok_or_else(|| malformed("pack length overflow"))?; 178 return Ok((len as usize >= total_len).then_some(total_len)); 179 } 180 match self.current.as_mut() { 181 Some(entry) => match entry.feed(pack, &mut scratch, &mut chunk)? { 182 Some(next_offset) => { 183 self.next_offset = next_offset; 184 self.objects_done = self.objects_done.succ(); 185 self.current = None; 186 } 187 None => return Ok(None), 188 }, 189 None => { 190 let start = self.next_offset; 191 if start.get() >= len { 192 return Ok(None); 193 } 194 let read = pack 195 .read_at(start.get(), &mut header) 196 .map_err(|error| PackError::Pack(format!("pack read: {error}")))?; 197 let mut reader: &[u8] = &header[..read]; 198 let entry = match Entry::from_read(&mut reader, start.get(), hash_len) { 199 Ok(entry) => entry, 200 Err(error) => { 201 return if len.saturating_sub(start.get()) < HEADER_SLACK as u64 { 202 Ok(None) 203 } else { 204 Err(PackError::Pack(error.to_string())) 205 }; 206 } 207 }; 208 if limits.max_object_bytes.exceeded_by(entry.decompressed_size) { 209 return Err(PackError::LimitExceeded(PackLimit::ObjectBytes)); 210 } 211 self.total_decompressed = self 212 .total_decompressed 213 .checked_add(entry.decompressed_size) 214 .ok_or_else(|| malformed("decompressed size overflow"))?; 215 if limits.max_total_bytes.exceeded_by(self.total_decompressed) { 216 return Err(PackError::LimitExceeded(PackLimit::TotalBytes)); 217 } 218 if let Header::OfsDelta { base_distance } = entry.header { 219 let base = entry 220 .checked_base_pack_offset(base_distance) 221 .ok_or_else(|| malformed("ofs-delta base out of range"))?; 222 self.base_of.insert(self.next_offset, PackOffset::new(base)); 223 } 224 self.current = Some(EntryInflate { 225 data_offset: PackOffset::new(entry.data_offset), 226 decompressed_size: entry.decompressed_size, 227 decompress: Decompress::new(true), 228 produced: 0, 229 }); 230 } 231 } 232 } 233 } 234} 235 236pub struct ReceiveFramer { 237 limits: PackLimits, 238 kind: gix::hash::Kind, 239 pack_start: Option<usize>, 240 pack: Option<PackProgress>, 241} 242 243impl ReceiveFramer { 244 pub fn new(limits: PackLimits, kind: gix::hash::Kind) -> Self { 245 Self { 246 limits, 247 kind, 248 pack_start: None, 249 pack: None, 250 } 251 } 252 253 pub fn pack_start(&self) -> Option<usize> { 254 self.pack_start 255 } 256 257 pub fn advance_bytes(&mut self, buf: &[u8]) -> Result<Option<usize>, PackError> { 258 self.advance(buf) 259 } 260 261 pub fn advance_file( 262 &mut self, 263 file: &std::fs::File, 264 len: u64, 265 ) -> Result<Option<usize>, PackError> { 266 self.advance(&FileSource { file, len }) 267 } 268 269 fn advance<S: PackSource + ?Sized>(&mut self, source: &S) -> Result<Option<usize>, PackError> { 270 let len = source.len(); 271 let pack_start = match self.pack_start { 272 Some(start) => start, 273 None => { 274 let head = read_head(source, len)?; 275 match pack_start(&head) { 276 Some(start) => { 277 self.pack_start = Some(start); 278 start 279 } 280 None => return Ok(None), 281 } 282 } 283 }; 284 if self.pack.is_none() { 285 let pack_len = len.saturating_sub(pack_start as u64); 286 if pack_len == 0 { 287 let head = read_head(source, pack_start as u64)?; 288 return Ok(no_pack_needed(&head).then_some(pack_start)); 289 } 290 if pack_len < 12 { 291 return Ok(None); 292 } 293 let mut header = [0u8; 12]; 294 source 295 .read_at(pack_start as u64, &mut header) 296 .map_err(|error| PackError::Pack(format!("pack read: {error}")))?; 297 if &header[..4] != b"PACK" { 298 return Err(malformed("packfile is missing its PACK signature")); 299 } 300 let num_objects = pack_object_count(&header)?; 301 if num_objects > self.limits.max_objects { 302 return Err(PackError::LimitExceeded(PackLimit::Objects)); 303 } 304 self.pack = Some(PackProgress { 305 num_objects, 306 next_offset: PackOffset::new(pack_start as u64 + 12), 307 ..PackProgress::default() 308 }); 309 } 310 let kind = self.kind; 311 self.pack 312 .as_mut() 313 .expect("pack progress initialized") 314 .scan(source, &self.limits, kind) 315 } 316} 317 318pub fn receive_request_complete( 319 buf: &[u8], 320 limits: &PackLimits, 321 kind: gix::hash::Kind, 322) -> Result<Option<usize>, PackError> { 323 ReceiveFramer::new(*limits, kind).advance_bytes(buf) 324} 325 326pub fn archive_request_complete(buf: &[u8]) -> Option<usize> { 327 pkt::frames(buf, Some(1)).find_map(|item| match item { 328 Ok((Frame::Flush, end)) => Some(end), 329 _ => None, 330 }) 331} 332 333#[derive(Default)] 334pub struct UploadFramer { 335 scanned: usize, 336 v2: bool, 337 flushes: usize, 338 complete: Option<usize>, 339} 340 341impl UploadFramer { 342 pub fn new() -> Self { 343 Self::default() 344 } 345 346 pub fn advance(&mut self, buf: &[u8]) -> Option<usize> { 347 if self.complete.is_some() { 348 return self.complete; 349 } 350 let base = self.scanned; 351 for item in pkt::frames(&buf[base..], None) { 352 let Ok((frame, at)) = item else { break }; 353 let boundary = base + at; 354 match frame { 355 Frame::Data(payload) => { 356 if payload.starts_with(b"command=") { 357 self.v2 = true; 358 } 359 let trimmed = payload 360 .iter() 361 .rposition(|byte| !byte.is_ascii_whitespace()) 362 .map(|end| &payload[..=end]) 363 .unwrap_or(payload); 364 if !self.v2 && trimmed == b"done" { 365 self.complete = Some(boundary); 366 return self.complete; 367 } 368 } 369 Frame::Flush => { 370 self.flushes += 1; 371 if self.v2 { 372 self.complete = Some(boundary); 373 return self.complete; 374 } 375 } 376 _ => {} 377 } 378 self.scanned = boundary; 379 } 380 None 381 } 382 383 pub fn unanswered_flushes(&self) -> usize { 384 self.flushes.saturating_sub(1) 385 } 386} 387 388pub fn upload_v0_nak() -> Vec<u8> { 389 let mut buf = Vec::new(); 390 pkt::write_data(&mut buf, b"NAK\n").expect("write to in-memory buffer never fails"); 391 buf 392} 393 394#[cfg(test)] 395mod tests { 396 use super::*; 397 398 fn v2_fetch_request() -> Vec<u8> { 399 let mut buf = Vec::new(); 400 pkt::write_data(&mut buf, b"command=fetch\n").unwrap(); 401 pkt::write_delim(&mut buf).unwrap(); 402 pkt::write_data(&mut buf, b"want 1111111111111111111111111111111111111111\n").unwrap(); 403 pkt::write_data(&mut buf, b"want 2222222222222222222222222222222222222222\n").unwrap(); 404 pkt::write_data(&mut buf, b"done\n").unwrap(); 405 pkt::write_flush(&mut buf).unwrap(); 406 buf 407 } 408 409 #[test] 410 fn upload_framer_completes_a_v2_request_at_the_terminating_flush() { 411 let request = v2_fetch_request(); 412 assert_eq!(UploadFramer::new().advance(&request), Some(request.len())); 413 } 414 415 #[test] 416 fn upload_framer_fed_one_byte_at_a_time_never_overruns_the_buffer() { 417 let request = v2_fetch_request(); 418 let mut framer = UploadFramer::new(); 419 let mut buf = Vec::new(); 420 let mut completed = None; 421 for byte in &request { 422 buf.push(*byte); 423 if let Some(len) = framer.advance(&buf) { 424 assert!( 425 len <= buf.len(), 426 "advance returned {len} past buffer of {}", 427 buf.len() 428 ); 429 completed = Some(len); 430 break; 431 } 432 } 433 assert_eq!( 434 completed, 435 Some(request.len()), 436 "incrementally fed request completes exactly once the whole buffer has arrived" 437 ); 438 } 439 440 #[test] 441 fn upload_framer_counts_v0_have_batch_flushes_without_completing() { 442 let mut buf = Vec::new(); 443 pkt::write_data(&mut buf, b"want 1111111111111111111111111111111111111111\n").unwrap(); 444 pkt::write_flush(&mut buf).unwrap(); 445 pkt::write_data(&mut buf, b"have 2222222222222222222222222222222222222222\n").unwrap(); 446 pkt::write_flush(&mut buf).unwrap(); 447 let mut framer = UploadFramer::new(); 448 assert_eq!(framer.advance(&buf), None, "v0 request is open until done"); 449 assert_eq!( 450 framer.unanswered_flushes(), 451 1, 452 "two flushes seen, one have-batch awaits NAK" 453 ); 454 } 455}