1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
10#[serde(transparent)]
11pub struct RemoteOffset(u64);
12
13impl RemoteOffset {
14 pub const fn new(value: u64) -> Self {
15 Self(value)
16 }
17
18 pub const fn get(self) -> u64 {
19 self.0
20 }
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
26#[error("read lengths must be between 1 byte and {} bytes", ReadLimit::MAX)]
27pub struct ReadLimitError;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
32#[serde(try_from = "u64", into = "u64")]
33pub struct ReadLimit(u64);
34
35impl ReadLimit {
36 pub const MAX: u64 = 256 * 1024 * 1024;
38 pub const DEFAULT_TAIL: Self = Self(256 * 1024);
40
41 pub fn new(value: u64) -> Result<Self, ReadLimitError> {
44 if value == 0 || value > Self::MAX {
45 Err(ReadLimitError)
46 } else {
47 Ok(Self(value))
48 }
49 }
50
51 pub const fn get(self) -> u64 {
52 self.0
53 }
54}
55impl TryFrom<u64> for ReadLimit {
56 type Error = ReadLimitError;
57 fn try_from(value: u64) -> Result<Self, Self::Error> {
58 Self::new(value)
59 }
60}
61impl From<ReadLimit> for u64 {
62 fn from(value: ReadLimit) -> Self {
63 value.get()
64 }
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
69#[serde(transparent)]
70pub struct RemoteSize(u64);
71
72impl RemoteSize {
73 pub const fn new(value: u64) -> Self {
74 Self(value)
75 }
76
77 pub const fn get(self) -> u64 {
78 self.0
79 }
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
90pub enum ReadSelection {
91 Full,
92 Range {
93 start: RemoteOffset,
94 length: ReadLimit,
95 },
96 Tail(ReadLimit),
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
103pub struct RemoteWindow {
104 start: RemoteOffset,
105 length: RemoteSize,
106 file_size: RemoteSize,
107}
108
109impl RemoteWindow {
110 pub fn resolve(selection: &ReadSelection, file_size: RemoteSize) -> Self {
114 let size = file_size.get();
115 let (start, length) = match selection {
116 ReadSelection::Full => (0, size),
117 ReadSelection::Range { start, length } => {
118 let start = start.get().min(size);
119 (start, length.get().min(size - start))
120 }
121 ReadSelection::Tail(length) => {
122 let start = size.saturating_sub(length.get());
123 (start, size - start)
124 }
125 };
126 Self {
127 start: RemoteOffset::new(start),
128 length: RemoteSize::new(length),
129 file_size,
130 }
131 }
132
133 pub fn start(&self) -> RemoteOffset {
135 self.start
136 }
137
138 pub fn length(&self) -> RemoteSize {
140 self.length
141 }
142
143 pub fn file_size(&self) -> RemoteSize {
145 self.file_size
146 }
147
148 pub fn is_complete(&self) -> bool {
150 self.start.get() == 0 && self.length == self.file_size
151 }
152
153 pub(super) fn reaches_eof(&self) -> bool {
156 self.start.get() + self.length.get() == self.file_size.get()
157 }
158
159 pub(super) fn narrowed(self, front: u64, kept: u64) -> Self {
162 Self {
163 start: RemoteOffset::new(self.start.get() + front),
164 length: RemoteSize::new(kept),
165 file_size: self.file_size,
166 }
167 }
168}
169
170pub(super) fn utf8_boundary_range(bytes: &[u8]) -> (usize, usize) {
176 let mut start = 0;
177 while start < bytes.len() && start < 3 && is_continuation(bytes[start]) {
179 start += 1;
180 }
181 let mut end = bytes.len();
182 let last = bytes[start..]
184 .iter()
185 .rposition(|&byte| !is_continuation(byte))
186 .map(|position| start + position);
187 if let Some(lead) = last {
188 let expected = sequence_length(bytes[lead]);
189 if lead + expected > end {
190 end = lead;
191 }
192 }
193 (start, end)
194}
195
196fn is_continuation(byte: u8) -> bool {
197 byte & 0xC0 == 0x80
198}
199
200fn sequence_length(lead: u8) -> usize {
203 match lead {
204 0xC2..=0xDF => 2,
205 0xE0..=0xEF => 3,
206 0xF0..=0xF4 => 4,
207 _ => 1,
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 fn window(selection: &ReadSelection, size: u64) -> RemoteWindow {
216 RemoteWindow::resolve(selection, RemoteSize::new(size))
217 }
218
219 #[test]
220 fn limits_are_checked_at_construction() {
221 assert!(ReadLimit::new(1).is_ok());
222 assert_eq!(ReadLimit::new(512).unwrap().get(), 512);
223 assert_eq!(
224 ReadLimit::new(ReadLimit::MAX).unwrap().get(),
225 ReadLimit::MAX
226 );
227 assert!(ReadLimit::new(0).is_err());
228 assert!(ReadLimit::new(ReadLimit::MAX + 1).is_err());
229 }
230
231 #[test]
232 fn full_covers_everything() {
233 let whole = window(&ReadSelection::Full, 4096);
234 assert_eq!(whole.start().get(), 0);
235 assert_eq!(whole.length().get(), 4096);
236 assert_eq!(whole.file_size().get(), 4096);
237 assert!(whole.is_complete());
238 assert!(whole.reaches_eof());
239 let empty_file = window(&ReadSelection::Full, 0);
240 assert!(empty_file.is_complete());
241 assert_eq!(empty_file.length().get(), 0);
242 }
243
244 #[test]
245 fn ranges_clamp_to_the_inspected_end() {
246 let selection = ReadSelection::Range {
247 start: RemoteOffset::new(100),
248 length: ReadLimit::new(50).unwrap(),
249 };
250 let clamped = window(&selection, 120);
251 assert_eq!(clamped.start().get(), 100);
252 assert_eq!(clamped.length().get(), 20);
253 assert!(!clamped.is_complete());
254 assert!(clamped.reaches_eof());
255 let past = window(
257 &ReadSelection::Range {
258 start: RemoteOffset::new(500),
259 length: ReadLimit::new(10).unwrap(),
260 },
261 100,
262 );
263 assert_eq!(past.start().get(), 100);
264 assert_eq!(past.length().get(), 0);
265 let zero = window(
267 &ReadSelection::Range {
268 start: RemoteOffset::new(10),
269 length: ReadLimit::new(1).unwrap(),
270 },
271 10,
272 );
273 assert_eq!(zero.length().get(), 0);
274 assert_eq!(zero.start().get(), 10);
275 }
276
277 #[test]
278 fn tails_stop_at_the_start_of_file() {
279 let selection = ReadSelection::Tail(ReadLimit::new(100).unwrap());
280 let tail = window(&selection, 1000);
281 assert_eq!(tail.start().get(), 900);
282 assert_eq!(tail.length().get(), 100);
283 assert!(tail.reaches_eof());
284 assert!(!tail.is_complete());
285 let short = window(&selection, 40);
286 assert_eq!(short.start().get(), 0);
287 assert_eq!(short.length().get(), 40);
288 assert!(short.is_complete());
289 let empty = window(
290 &ReadSelection::Tail(ReadLimit::new(ReadLimit::MAX).unwrap()),
291 0,
292 );
293 assert_eq!(empty.length().get(), 0);
294 }
295
296 #[test]
297 fn deserialization_cannot_bypass_read_admission() {
298 assert!(serde_json::from_str::<ReadLimit>("0").is_err());
299 assert!(serde_json::from_str::<ReadLimit>(&((ReadLimit::MAX + 1).to_string())).is_err());
300 }
301}