Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 1 | //! Byte ranges: the `Content-Range` a chunk of an upload names, the |
| 2 | //! `Range` an upload's replies say it holds, and the `Range` a download | |
| 3 | //! asks for. | |
| 4 | ||
| 5 | /// The bytes a chunk says it holds, `start` to `end` inclusive. | |
| 6 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] | |
| 7 | pub struct ChunkRange { | |
| 8 | pub start: u64, | |
| 9 | pub end: u64, | |
| 10 | } | |
| 11 | ||
| 12 | /// A chunk's `Content-Range`: `<start>-<end>`, as the Distribution spec | |
| 13 | /// writes it, or the HTTP form `bytes <start>-<end>/<total or *>`. | |
| 14 | pub fn parse_content_range(header: &str) -> Option<ChunkRange> { | |
| 15 | let text = header.trim(); | |
| 16 | let text = text.strip_prefix("bytes ").unwrap_or(text).trim(); | |
| 17 | let text = text.split_once('/').map_or(text, |(range, _)| range); | |
| 18 | let (start, end) = text.split_once('-')?; | |
| 19 | let (start, end) = (start.trim().parse().ok()?, end.trim().parse().ok()?); | |
| 20 | (start <= end).then_some(ChunkRange { start, end }) | |
| 21 | } | |
| 22 | ||
| 23 | /// What an upload's replies say it holds so far: `0-<last byte>`, and | |
| 24 | /// `0-0` before the first byte, as clients expect. | |
| 25 | pub fn upload_range(offset: u64) -> String { | |
| 26 | format!("0-{}", offset.saturating_sub(1)) | |
| 27 | } | |
| 28 | ||
| 29 | /// A part of a blob a download asks for, resolved against its size. | |
| Merge branch 'worktree-agent-ac5b181a013e54348' | 30 | pub use g1t_blobstore::Wanted; |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 31 | |
| 32 | /// What a download's `Range` header asks for, against a blob of `size` | |
| 33 | /// bytes. `Ok(None)`: the whole blob (no header, or one this does not | |
| 34 | /// read, such as several ranges). `Err(())`: nothing of the blob is in it, | |
| 35 | /// which is answered 416. | |
| 36 | pub fn parse_range(header: Option<&str>, size: u64) -> Result<Option<Wanted>, ()> { | |
| 37 | let Some(spec) = header.and_then(|h| h.trim().strip_prefix("bytes=")) else { | |
| 38 | return Ok(None); | |
| 39 | }; | |
| 40 | if spec.contains(',') { | |
| 41 | return Ok(None); | |
| 42 | } | |
| 43 | let Some((first, last)) = spec.trim().split_once('-') else { | |
| 44 | return Ok(None); | |
| 45 | }; | |
| 46 | let (first, last) = (first.trim(), last.trim()); | |
| 47 | let wanted = if first.is_empty() { | |
| 48 | let Ok(suffix) = last.parse::<u64>() else { return Ok(None) }; | |
| 49 | if suffix == 0 || size == 0 { | |
| 50 | return Err(()); | |
| 51 | } | |
| 52 | let length = suffix.min(size); | |
| 53 | Wanted { offset: size - length, length } | |
| 54 | } else { | |
| 55 | let Ok(offset) = first.parse::<u64>() else { return Ok(None) }; | |
| 56 | if offset >= size { | |
| 57 | return Err(()); | |
| 58 | } | |
| 59 | let end = if last.is_empty() { | |
| 60 | size - 1 | |
| 61 | } else { | |
| 62 | match last.parse::<u64>() { | |
| 63 | Ok(end) if end >= offset => end.min(size - 1), | |
| 64 | _ => return Ok(None), | |
| 65 | } | |
| 66 | }; | |
| 67 | Wanted { offset, length: end - offset + 1 } | |
| 68 | }; | |
| 69 | Ok(Some(wanted)) | |
| 70 | } | |
| 71 | ||
| 72 | #[cfg(test)] | |
| 73 | mod tests { | |
| 74 | use super::*; | |
| 75 | ||
| 76 | #[test] | |
| 77 | fn a_chunk_names_its_range_either_way() { | |
| 78 | assert_eq!(parse_content_range("0-1023"), Some(ChunkRange { start: 0, end: 1023 })); | |
| 79 | assert_eq!(parse_content_range("bytes 1024-2047/*"), Some(ChunkRange { start: 1024, end: 2047 })); | |
| 80 | assert_eq!(parse_content_range(" 5-5 "), Some(ChunkRange { start: 5, end: 5 })); | |
| 81 | assert_eq!(parse_content_range("9-3"), None); | |
| 82 | assert_eq!(parse_content_range("x-3"), None); | |
| 83 | assert_eq!(parse_content_range(""), None); | |
| 84 | } | |
| 85 | ||
| 86 | #[test] | |
| 87 | fn an_upload_says_what_it_holds() { | |
| 88 | assert_eq!(upload_range(0), "0-0"); | |
| 89 | assert_eq!(upload_range(1), "0-0"); | |
| 90 | assert_eq!(upload_range(1024), "0-1023"); | |
| 91 | } | |
| 92 | ||
| 93 | #[test] | |
| 94 | fn a_download_range_is_resolved_against_the_size() { | |
| 95 | let w = |offset, length| Ok(Some(Wanted { offset, length })); | |
| 96 | assert_eq!(parse_range(None, 100), Ok(None)); | |
| 97 | assert_eq!(parse_range(Some("bytes=0-9"), 100), w(0, 10)); | |
| 98 | assert_eq!(parse_range(Some("bytes=90-"), 100), w(90, 10)); | |
| 99 | assert_eq!(parse_range(Some("bytes=90-500"), 100), w(90, 10)); | |
| 100 | assert_eq!(parse_range(Some("bytes=-10"), 100), w(90, 10)); | |
| 101 | assert_eq!(parse_range(Some("bytes=-500"), 100), w(0, 100)); | |
| 102 | assert_eq!(parse_range(Some("bytes=100-"), 100), Err(())); | |
| 103 | assert_eq!(parse_range(Some("bytes=-0"), 100), Err(())); | |
| 104 | assert_eq!(parse_range(Some("bytes=0-1,5-9"), 100), Ok(None)); | |
| 105 | assert_eq!(parse_range(Some("items=0-1"), 100), Ok(None)); | |
| 106 | assert_eq!(parse_range(Some("bytes=9-3"), 100), Ok(None)); | |
| 107 | assert_eq!(Wanted { offset: 90, length: 10 }.content_range(100), "bytes 90-99/100"); | |
| 108 | } | |
| 109 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.