1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
use crate::align::write_padding;
use crate::config::PageAlignment;
use crate::error::WriteError;
use crate::frame::{close_frame, register_into_frame, Frame};
use crate::raw_writer::RawWriter;
use crate::sink::{RewindableSink, WriteCtx};
use crate::types::MAGIC;
/// Closing bytes for a kahon document as it stands at a point in time.
///
/// A complete kahon document can be assembled by concatenating the
/// first [`prefix_len`](Self::prefix_len) bytes of the writer's sink
/// with [`bytes`](Self::bytes). Useful for publishing snapshots of an
/// in-progress document without disturbing the live writer.
#[derive(Debug, Clone)]
pub struct TrailerSnapshot {
/// Number of bytes from the start of the writer's sink that
/// participate in the snapshot.
pub prefix_len: u64,
/// Closing bytes to append after the prefix. Length is `>= 12`.
pub bytes: Vec<u8>,
}
impl TrailerSnapshot {
/// Total length of the assembled snapshot document.
pub fn total_len(&self) -> u64 {
self.prefix_len + self.bytes.len() as u64
}
/// Absolute offset of the root value in the assembled snapshot.
pub fn root_offset(&self) -> u64 {
let n = self.bytes.len();
let mut buf = [0u8; 8];
buf.copy_from_slice(&self.bytes[n - 12..n - 4]);
u64::from_le_bytes(buf)
}
/// Absolute offset where the 12-byte trailer begins in the
/// assembled snapshot. Equal to `total_len() - 12`.
pub fn trailer_offset(&self) -> u64 {
self.total_len() - 12
}
}
impl<S: RewindableSink> RawWriter<S> {
/// Synthesize the closing bytes for the document as it stands,
/// without disturbing the writer. See [`TrailerSnapshot`].
pub fn snapshot_trailer(&self) -> Result<TrailerSnapshot, WriteError> {
if self.poisoned {
return Err(WriteError::Poisoned);
}
if !self.frames.is_empty() && self.root_offset.is_some() {
return Err(WriteError::MultipleRootValues);
}
// Snapshot can't reconcile a half-written extension: closing
// the open frames would either drop the extension's
// payload-routing or produce a slot that points at an extension
// tag with no payload. Require the caller to land on a stable
// boundary first.
if !self.pending_exts.is_empty() {
return Err(WriteError::ExtensionWithoutPayload);
}
let prefix_len = self.pos;
let mut tail: Vec<u8> = Vec::new();
let mut tail_pos = self.pos;
let mut tail_padding = self.padding_written;
let mut scratch = Vec::with_capacity(64);
let mut frames = self.frames.clone();
let mut root: Option<u64> = self.root_offset;
// Drop pending object keys in the cloned frames; the key strings
// already live in the prefix as orphaned bytes (kahon allows
// unreferenced bytes in the body).
for frame in frames.iter_mut() {
if let Frame::Object(o) = frame {
o.pending_key = None;
}
}
// Close frames innermost-first into the side buffer. Each closed
// frame's root is registered into its parent, or becomes the
// document root if there is no parent.
while let Some(frame) = frames.pop() {
let mut ctx = WriteCtx {
sink: &mut tail,
pos: &mut tail_pos,
scratch: &mut scratch,
padding_written: &mut tail_padding,
};
let root_off = close_frame(frame, &self.opts.policy, &mut ctx)?;
if let Some(parent) = frames.last_mut() {
register_into_frame(parent, root_off, &self.opts, &mut ctx)?;
} else {
root = Some(root_off);
}
}
let root = root.ok_or(WriteError::EmptyDocument)?;
// Pad so the 12-byte trailer ends on a page boundary (mirrors finish).
if let PageAlignment::Aligned { page_size } = self.opts.policy.align {
let ps = page_size as u64;
let target_mod = ps - 12;
let cur_mod = tail_pos % ps;
let pad = if cur_mod <= target_mod {
target_mod - cur_mod
} else {
ps - cur_mod + target_mod
};
if pad > 0 {
let mut ctx = WriteCtx {
sink: &mut tail,
pos: &mut tail_pos,
scratch: &mut scratch,
padding_written: &mut tail_padding,
};
write_padding(&mut ctx, pad as usize)?;
}
}
let mut trailer = [0u8; 12];
trailer[..8].copy_from_slice(&root.to_le_bytes());
trailer[8..].copy_from_slice(&MAGIC);
tail.extend_from_slice(&trailer);
Ok(TrailerSnapshot {
prefix_len,
bytes: tail,
})
}
}