g1t/services/packages/src/range.rs

109 lines4,338 bytesCodeBlame

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 member1//! 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)]
7pub 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 *>`.
14pub 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.
25pub 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'30pub use g1t_blobstore::Wanted;
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member31
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.
36pub 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)]
73mod 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.