1use yo_common::{Addr, Code, Error, Result, Space};
49
50pub const CHUNK: usize = 64 * 1024;
57
58pub const FANOUT: usize = CHUNK / 8;
61
62pub const MAX_LEN: u64 = (FANOUT * CHUNK) as u64;
67
68pub trait Blocks {
75 fn put(&mut self, bytes: &[u8]) -> Result<Addr>;
77
78 fn get(&self, at: Addr) -> Result<&[u8]>;
84
85 fn bytes(&self) -> u64;
92
93 fn release(&mut self) {}
107}
108
109impl Blocks for Box<dyn Blocks> {
118 fn put(&mut self, bytes: &[u8]) -> Result<Addr> {
119 (**self).put(bytes)
120 }
121
122 fn get(&self, at: Addr) -> Result<&[u8]> {
123 (**self).get(at)
124 }
125
126 fn bytes(&self) -> u64 {
127 (**self).bytes()
128 }
129
130 fn release(&mut self) {
131 (**self).release();
132 }
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141pub struct Chain {
142 pub at: Addr,
144 pub len: u64,
146}
147
148#[must_use]
154pub const fn chunks_for(len: u64) -> u64 {
155 if len == 0 {
156 1
157 } else {
158 len.div_ceil(CHUNK as u64)
159 }
160}
161
162pub struct Scratch {
168 dir: Vec<u8>,
169}
170
171impl Scratch {
172 #[must_use]
174 pub fn new() -> Scratch {
175 Scratch {
176 dir: Vec::with_capacity(CHUNK),
177 }
178 }
179
180 #[must_use]
182 pub fn memory_bytes(&self) -> usize {
183 self.dir.capacity()
184 }
185}
186
187impl Default for Scratch {
188 fn default() -> Scratch {
189 Scratch::new()
190 }
191}
192
193pub fn write<B: Blocks>(blocks: &mut B, value: &[u8], scratch: &mut Scratch) -> Result<Chain> {
201 let len = value.len() as u64;
202 if len > MAX_LEN {
203 return Err(Error::fmt(
204 Code::Full,
205 format_args!("a value of {len} bytes is longer than a chain holds"),
206 ));
207 }
208
209 if value.len() <= CHUNK {
210 return Ok(Chain {
211 at: blocks.put(value)?,
212 len,
213 });
214 }
215
216 scratch.dir.clear();
217 for piece in value.chunks(CHUNK) {
218 let at = blocks.put(piece)?;
219 scratch.dir.extend_from_slice(&at.to_bits().to_le_bytes());
220 }
221 let at = blocks.put(&scratch.dir)?;
222 Ok(Chain { at, len })
223}
224
225pub struct Reader<'a, B: Blocks> {
232 blocks: &'a B,
233 len: u64,
234 dir: Dir<'a>,
237}
238
239enum Dir<'a> {
240 One(Addr),
241 Many(&'a [u8]),
242}
243
244impl<'a, B: Blocks> Reader<'a, B> {
245 pub fn open(blocks: &'a B, chain: Chain) -> Result<Reader<'a, B>> {
247 let want = chunks_for(chain.len);
248 let dir = if want == 1 {
249 Dir::One(chain.at)
250 } else {
251 let bytes = blocks.get(chain.at)?;
252 if bytes.len() as u64 != want * 8 {
253 return Err(Error::fmt(
254 Code::Corrupt,
255 format_args!(
256 "a chain of {} bytes wants {want} addresses and its directory has {}",
257 chain.len,
258 bytes.len() / 8
259 ),
260 ));
261 }
262 Dir::Many(bytes)
263 };
264 Ok(Reader {
265 blocks,
266 len: chain.len,
267 dir,
268 })
269 }
270
271 #[must_use]
273 pub const fn len(&self) -> u64 {
274 self.len
275 }
276
277 #[must_use]
280 pub const fn is_empty(&self) -> bool {
281 self.len == 0
282 }
283
284 #[must_use]
286 pub const fn chunks(&self) -> u64 {
287 chunks_for(self.len)
288 }
289
290 pub fn chunk(&self, i: u64) -> Result<&'a [u8]> {
292 let at = match self.dir {
293 Dir::One(at) if i == 0 => at,
294 Dir::One(_) => {
295 return Err(Error::new(Code::Invalid, "there is only one chunk"));
296 }
297 Dir::Many(bytes) => {
298 let start = (i as usize)
299 .checked_mul(8)
300 .filter(|s| s + 8 <= bytes.len())
301 .ok_or_else(|| Error::new(Code::Invalid, "no such chunk"))?;
302 let mut bits = [0u8; 8];
303 bits.copy_from_slice(&bytes[start..start + 8]);
304 Addr::from_bits(u64::from_le_bytes(bits))
305 }
306 };
307 if at.space() != Some(Space::Log) {
308 return Err(Error::fmt(
309 Code::Corrupt,
310 format_args!("chunk {i} is not in the log"),
311 ));
312 }
313 self.blocks.get(at)
314 }
315
316 pub fn range(&self, from: u64, to: u64) -> Pieces<'a, '_, B> {
323 let to = to.min(self.len);
324 let from = from.min(to);
325 Pieces {
326 reader: self,
327 at: from,
328 end: to,
329 }
330 }
331}
332
333pub struct Pieces<'a, 'r, B: Blocks> {
335 reader: &'r Reader<'a, B>,
336 at: u64,
337 end: u64,
338}
339
340impl<'a, B: Blocks> Iterator for Pieces<'a, '_, B> {
341 type Item = Result<&'a [u8]>;
342
343 fn next(&mut self) -> Option<Result<&'a [u8]>> {
344 if self.at >= self.end {
345 return None;
346 }
347 let chunk = self.at / CHUNK as u64;
348 let start = (self.at % CHUNK as u64) as usize;
349 let take = (self.end - self.at).min(CHUNK as u64 - start as u64) as usize;
350 self.at += take as u64;
351 Some(match self.reader.chunk(chunk) {
352 Ok(bytes) if start + take <= bytes.len() => Ok(&bytes[start..start + take]),
353 Ok(bytes) => Err(Error::fmt(
354 Code::Corrupt,
355 format_args!(
356 "chunk {chunk} is {} bytes and the range wants {}",
357 bytes.len(),
358 start + take
359 ),
360 )),
361 Err(e) => Err(e),
362 })
363 }
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369
370 struct Mem {
374 blobs: Vec<Vec<u8>>,
375 reads: std::cell::Cell<usize>,
376 }
377
378 impl Mem {
379 fn new() -> Mem {
380 Mem {
381 blobs: Vec::new(),
382 reads: std::cell::Cell::new(0),
383 }
384 }
385
386 fn reads(&self) -> usize {
387 self.reads.get()
388 }
389 }
390
391 impl Blocks for Mem {
392 fn put(&mut self, bytes: &[u8]) -> Result<Addr> {
393 self.blobs.push(bytes.to_vec());
394 Ok(Addr::new(Space::Log, (self.blobs.len() - 1) as u64))
395 }
396
397 fn get(&self, at: Addr) -> Result<&[u8]> {
398 self.reads.set(self.reads.get() + 1);
399 self.blobs
400 .get(at.offset() as usize)
401 .map(Vec::as_slice)
402 .ok_or_else(|| Error::new(Code::NotFound, "no such block"))
403 }
404
405 fn bytes(&self) -> u64 {
406 self.blobs.iter().map(|b| b.len() as u64).sum()
407 }
408 }
409
410 fn pattern(len: usize) -> Vec<u8> {
411 (0..len).map(|i| (i % 251) as u8).collect()
412 }
413
414 fn whole<B: Blocks>(r: &Reader<'_, B>) -> Vec<u8> {
415 let mut out = Vec::new();
416 for piece in r.range(0, r.len()) {
417 out.extend_from_slice(piece.expect("a piece the value has"));
418 }
419 out
420 }
421
422 #[test]
423 fn a_value_that_fits_in_one_chunk_has_no_directory() {
424 let mut m = Mem::new();
425 let value = pattern(1000);
426 let chain = write(&mut m, &value, &mut Scratch::new()).expect("written");
427 assert_eq!(
428 m.blobs.len(),
429 1,
430 "a directory was written and should not be"
431 );
432 assert_eq!(chain.len, 1000);
433
434 let r = Reader::open(&m, chain).expect("opened");
435 assert_eq!(r.chunks(), 1);
436 assert_eq!(whole(&r), value);
437 assert_eq!(
440 m.reads(),
441 1,
442 "reading a short value took more than one read"
443 );
444 }
445
446 #[test]
447 fn exactly_one_chunk_still_has_no_directory() {
448 let mut m = Mem::new();
449 let value = pattern(CHUNK);
450 let chain = write(&mut m, &value, &mut Scratch::new()).expect("written");
451 assert_eq!(m.blobs.len(), 1);
452 let r = Reader::open(&m, chain).expect("opened");
453 assert_eq!(r.chunks(), 1);
454 assert_eq!(whole(&r), value);
455 }
456
457 #[test]
458 fn one_byte_more_than_a_chunk_is_two_chunks_and_a_directory() {
459 let mut m = Mem::new();
460 let value = pattern(CHUNK + 1);
461 let chain = write(&mut m, &value, &mut Scratch::new()).expect("written");
462 assert_eq!(m.blobs.len(), 3, "two chunks and a directory");
463 let r = Reader::open(&m, chain).expect("opened");
464 assert_eq!(r.chunks(), 2);
465 assert_eq!(r.chunk(1).expect("the second chunk").len(), 1);
466 assert_eq!(whole(&r), value);
467 }
468
469 #[test]
470 fn an_empty_value_is_one_empty_chunk() {
471 let mut m = Mem::new();
472 let chain = write(&mut m, b"", &mut Scratch::new()).expect("written");
473 let r = Reader::open(&m, chain).expect("opened");
474 assert!(r.is_empty());
475 assert_eq!(r.chunks(), 1, "a chain always points at something");
476 assert_eq!(whole(&r), b"");
477 }
478
479 #[test]
480 fn a_range_inside_one_chunk_only_fetches_that_chunk() {
481 let mut m = Mem::new();
482 let value = pattern(10 * CHUNK);
483 let chain = write(&mut m, &value, &mut Scratch::new()).expect("written");
484
485 let r = Reader::open(&m, chain).expect("opened");
486 let before = m.reads();
487 let mut got = Vec::new();
488 let (from, to) = (7 * CHUNK as u64 + 100, 7 * CHUNK as u64 + 300);
491 for piece in r.range(from, to) {
492 got.extend_from_slice(piece.expect("a piece"));
493 }
494 assert_eq!(got, value[from as usize..to as usize]);
495 assert_eq!(
496 m.reads() - before,
497 1,
498 "a range inside one chunk of a ten chunk value should be one fetch"
499 );
500 }
501
502 #[test]
503 fn a_range_across_a_boundary_comes_back_in_two_pieces() {
504 let mut m = Mem::new();
505 let value = pattern(3 * CHUNK);
506 let chain = write(&mut m, &value, &mut Scratch::new()).expect("written");
507 let r = Reader::open(&m, chain).expect("opened");
508
509 let (from, to) = (CHUNK as u64 - 5, CHUNK as u64 + 5);
510 let pieces: Vec<usize> = r
511 .range(from, to)
512 .map(|p| p.expect("a piece").len())
513 .collect();
514 assert_eq!(
515 pieces,
516 vec![5, 5],
517 "the boundary was not where it should be"
518 );
519 }
520
521 #[test]
522 fn a_range_past_the_end_stops_at_the_end() {
523 let mut m = Mem::new();
524 let value = pattern(100);
525 let chain = write(&mut m, &value, &mut Scratch::new()).expect("written");
526 let r = Reader::open(&m, chain).expect("opened");
527 let mut got = Vec::new();
528 for piece in r.range(50, 1_000_000) {
529 got.extend_from_slice(piece.expect("a piece"));
530 }
531 assert_eq!(got, value[50..]);
532 assert_eq!(r.range(200, 300).count(), 0, "there is nothing out there");
533 assert_eq!(r.range(80, 20).count(), 0, "a backwards range is empty");
534 }
535
536 #[test]
537 fn every_chunk_but_the_last_is_full() {
538 let mut m = Mem::new();
539 let value = pattern(2 * CHUNK + 7);
540 let chain = write(&mut m, &value, &mut Scratch::new()).expect("written");
541 let r = Reader::open(&m, chain).expect("opened");
542 assert_eq!(r.chunks(), 3);
543 assert_eq!(r.chunk(0).expect("chunk 0").len(), CHUNK);
544 assert_eq!(r.chunk(1).expect("chunk 1").len(), CHUNK);
545 assert_eq!(r.chunk(2).expect("chunk 2").len(), 7);
546 assert!(r.chunk(3).is_err(), "there is no fourth chunk");
547 }
548
549 #[test]
550 fn a_multi_chunk_value_is_two_reads_and_not_more() {
551 let mut m = Mem::new();
552 let value = pattern(5 * CHUNK);
553 let chain = write(&mut m, &value, &mut Scratch::new()).expect("written");
554 let before = m.reads();
555 let r = Reader::open(&m, chain).expect("opened");
556 assert_eq!(m.reads() - before, 1);
560 r.chunk(4).expect("the last chunk");
561 assert_eq!(m.reads() - before, 2);
562 }
563
564 #[test]
565 fn a_directory_that_does_not_match_the_length_is_refused() {
566 let mut m = Mem::new();
567 let value = pattern(2 * CHUNK);
568 let chain = write(&mut m, &value, &mut Scratch::new()).expect("written");
569 let lying = Chain {
572 at: chain.at,
573 len: 9 * CHUNK as u64,
574 };
575 assert!(
576 Reader::open(&m, lying).is_err(),
577 "a directory that is the wrong size was accepted"
578 );
579 }
580
581 #[test]
582 fn a_value_longer_than_a_chain_holds_is_refused_rather_than_truncated() {
583 assert_eq!(MAX_LEN, 512 * 1024 * 1024);
587 assert_eq!(chunks_for(MAX_LEN), FANOUT as u64);
588 assert_eq!(chunks_for(MAX_LEN + 1), FANOUT as u64 + 1);
589 }
590
591 #[test]
592 fn the_scratch_is_reused_and_does_not_grow_with_every_write() {
593 let mut m = Mem::new();
594 let mut scratch = Scratch::new();
595 let value = pattern(4 * CHUNK);
596 for _ in 0..8 {
597 write(&mut m, &value, &mut scratch).expect("written");
598 }
599 assert_eq!(
600 scratch.memory_bytes(),
601 CHUNK,
602 "the directory buffer grew, so a command path is allocating"
603 );
604 }
605
606 #[test]
607 fn what_went_in_comes_back_at_every_awkward_size() {
608 let mut m = Mem::new();
609 let mut scratch = Scratch::new();
610 for len in [
611 0,
612 1,
613 CHUNK - 1,
614 CHUNK,
615 CHUNK + 1,
616 2 * CHUNK - 1,
617 2 * CHUNK,
618 2 * CHUNK + 1,
619 3 * CHUNK + 123,
620 ] {
621 let value = pattern(len);
622 let chain = write(&mut m, &value, &mut scratch).expect("written");
623 let r = Reader::open(&m, chain).expect("opened");
624 assert_eq!(r.len(), len as u64);
625 assert_eq!(whole(&r), value, "a value of {len} bytes came back wrong");
626 }
627 }
628}