Oregami
Repositories/oxedyne/ore

oxedyne/ore/cli/src/repo.rs

49.7 KiB, 161 runs

created by r2848102244:15, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! The repository on disk, and everything that reads or writes it.
2//!
3//! The layout is deliberately boring, and it is the command line tool's choice
4//! rather than the engine's: `oxedyne_fe2o3_ore` does no I/O at all, so where
5//! the bytes live is decided here and nowhere else. The segments themselves, the
6//! lock over them and the key material are `ore_store`, shared with the relay,
7//! which holds those and nothing else; what is here is everything a working copy
8//! adds.
9//!
10//! ```text
11//! .ore/config JDAT text: replica identifier, format versions, the
12//! git author to replica mapping an import recorded, the
13//! public keys this repository knows and whether it
14//! requires operations to be signed.
15//! .ore/key JDAT text: this replica's signing key pair, secret key
16//! included and unencrypted. Mode 0600. See [`crate::keys`].
17//! .ore/log/000000.seg ORESEG segments, appended to until one passes the size
18//! .ore/log/000001.seg threshold, whereupon the next append starts the next
19//! file. Numbering is ascending and replay order is
20//! numeric order.
21//! .ore/batches JDAT text: one entry per command that appended
22//! operations, with the frontier it began at.
23//! .ore/arrived JDAT text: what the last sync delivered, as the two
24//! frontiers either side of it. See [`crate::arrived`].
25//! .ore/reviewed JDAT text: the flags somebody has said they have seen.
26//! See [`crate::reviewed`].
27//! .ore/collisions/ One file per operation an overlap arbitration buried,
28//! holding what it wrote. Derived; see
29//! [`crate::collisions`].
30//! .ore/lock Where the advisory lock any command that may append to
31//! the log holds is kept. Its presence is not the lock;
32//! its contents name the holder, or are empty.
33//! .ore/pending JDAT text, present only between a sync that brought
34//! operations in from elsewhere and the next verb run
35//! here: the frontier this working copy still stands at.
36//! ```
37//!
38//! # One writer at a time
39//!
40//! Appending is read-modify-write: [`Repo::write_records`] reads the segment on
41//! disk, resumes the writer over it, and appends what the resume produced. Two
42//! commands doing that at once would each chain their records onto a segment the
43//! other was also extending, and the loser's digests would not match the bytes
44//! that ended up in front of them. So every command that may append takes
45//! [`Lock`] on the repository first, and a command that finds the lock held says
46//! so and stops rather than waiting.
47//!
48//! A sync writes to two repositories and therefore holds two locks. Two syncs
49//! crossing in opposite directions -- each holding its own end and asking for the
50//! other's -- both fail, promptly and with the same message; neither waits, so
51//! there is nothing to deadlock. The lock itself is [`ore_store::store::Lock`].
52//!
53//! # Why the batches are the tool's business
54//!
55//! The operation graph says what was written and what each writer could see. It
56//! does not say which operations one person's one command appended, and it
57//! should not: causality is a property of the graph, and a command is a
58//! property of whatever ran it. So the tool keeps that record itself, beside
59//! the log rather than in it, in the manner of a reflog.
60//!
61//! It is this working copy's own memory. Nothing is exchanged with another
62//! replica, nothing in the history depends on it, and losing the file costs
63//! only the knowledge of where one command ended and the next began -- which is
64//! to say it costs [`crate::verbs::undo`] its no-argument form and nothing
65//! else.
66//!
67//! # Counters are a Lamport clock
68//!
69//! An operation identifier is a replica and a counter, and the sequence
70//! structure breaks ties by counter first. So that a later edit really is
71//! later, the next counter is one past the highest counter any replica has
72//! reached, rather than one past the authoring replica's own. The engine mints
73//! it, in `OpLog::next_id`, and [`Repo::next_id`] is that and nothing else; it
74//! is why a git import whose commits come from several authors still orders as
75//! the commits did.
76//!
77//! # Every operation this tool writes is sealed
78//!
79//! A repository with a key seals each record it authors into an
80//! [`oxedyne_fe2o3_ore::envelope::Envelope`] and writes it as a sealed segment
81//! entry; one without a key writes the record bare. A segment carries both forms
82//! tagged, so a repository that began unsigned and later ran `ore key` replays
83//! its mixed log without ceremony. Replaying checks every signature it meets and
84//! refuses the whole log if one does not hold -- see [`Repo::replay`].
85
86use ore_store::keys::{
87 self,
88 Binding,
89 Prov,
90 Signing,
91 Trust,
92};
93use ore_store::store::{
94 Consumed,
95 Keep,
96 Replayed,
97 Store,
98 Verify,
99};
100use ore_store::veil::Veil;
101use ore_store::veilkey::{
102 VeilBinding,
103 VeilKey,
104 Wrap,
105};
106
107use oxedyne_fe2o3_core::prelude::*;
108use oxedyne_fe2o3_hash::sha256::Sha256;
109use oxedyne_fe2o3_jdat::prelude::*;
110use oxedyne_fe2o3_ore::envelope::Envelope;
111use oxedyne_fe2o3_ore::id::{
112 OpId,
113 ReplicaId,
114};
115use oxedyne_fe2o3_ore::log::OpLog;
116use oxedyne_fe2o3_ore::op::{
117 Header,
118 Op,
119 Record,
120};
121use oxedyne_fe2o3_ore::segment::{
122 self,
123 Entry,
124};
125
126use std::collections::BTreeMap;
127use std::fs;
128use std::path::{
129 Path,
130 PathBuf,
131};
132use std::time::{
133 SystemTime,
134 UNIX_EPOCH,
135};
136
137
138pub use ore_store::store::Lock;
139
140
141/// Name of the directory holding everything the tool owns.
142pub const ORE_DIR: &str = ".ore";
143/// Name of the directory git holds everything it owns in.
144///
145/// Named here for the same reason [`ORE_DIR`] is: a working copy scan has to
146/// leave both alone, and a version control system's own store is not a file the
147/// repository is versioning. Everything else beginning with a full stop is.
148pub const GIT_DIR: &str = ".git";
149/// Name of the configuration file, within [`ORE_DIR`].
150pub const CONFIG_FILE: &str = "config";
151/// Name of the batch record, within [`ORE_DIR`].
152pub const BATCH_FILE: &str = "batches";
153/// Name of the marker a sync leaves behind, within [`ORE_DIR`].
154pub const PENDING_FILE: &str = "pending";
155/// Name of the optional ignore file, at the root of the working tree.
156pub const IGNORE_FILE: &str = ".oreignore";
157
158/// Version of the configuration file this tool writes.
159pub const CONFIG_VERSION: u64 = 1;
160
161/// How many commands the batch record remembers.
162///
163/// The record is a convenience and not history, so it is bounded: what falls off
164/// the front is the ability to undo a command several hundred commands ago
165/// without naming a mark, and a mark is what naming a point is for.
166pub const BATCH_LIMIT: usize = 512;
167
168
169
170/// Returns the first eight bytes of the SHA-256 of the parts, as a number.
171fn digest_u64(parts: &[&[u8]]) -> u64 {
172 let mut sha = Sha256::new();
173 for part in parts {
174 sha.update(part);
175 }
176 let out = sha.finish();
177 let mut n: u64 = 0;
178 for byte in &out[..8] {
179 n = (n << 8) | (*byte as u64);
180 }
181 n
182}
183
184/// Mints a replica identifier for a new repository.
185///
186/// The value is the low four bytes of a digest over the clock, the process and
187/// the path, which keeps identifiers short enough to read in a log and far
188/// enough apart that two working copies of one project will not collide.
189pub fn mint_replica(root: &Path)
190 -> Outcome<ReplicaId>
191{
192 let stamp = res!(SystemTime::now().duration_since(UNIX_EPOCH));
193 let nanos = fmt!("{}", stamp.as_nanos());
194 let pid = fmt!("{}", std::process::id());
195 let path = root.to_string_lossy().into_owned();
196 let n = digest_u64(&[
197 b"ore-replica",
198 nanos.as_bytes(),
199 pid.as_bytes(),
200 path.as_bytes(),
201 ]) & 0xffff_ffff;
202 // Counters start at one and so, by the same convention, does a replica.
203 Ok(ReplicaId::new(n.max(1)))
204}
205
206/// Derives the replica identifier of a git author from their identity line.
207///
208/// It is a digest rather than a counter so that importing one repository twice,
209/// or two repositories sharing a contributor, gives that contributor the same
210/// identifier both times.
211pub fn author_replica(identity: &str) -> ReplicaId {
212 let n = digest_u64(&[b"ore-author", identity.as_bytes()]) & 0xffff_ffff;
213 ReplicaId::new(n.max(1))
214}
215
216
217/// The rule telling a mark this tool wrote from a mark a person chose, which is
218/// the engine's and not this tool's.
219///
220/// It is one character at the front of the name, and it is in `fe2o3_ore` beside
221/// [`Op::Mark`] because every reader of a history has to apply it and they are
222/// not all this program: `ore back` offering somebody the points they named, the
223/// exporter deciding which mark deserves a tag, and a forge listing them in a
224/// view are three consumers in two crates. A second copy of the rule down here
225/// would be a copy that could disagree.
226///
227/// What is *not* upstream is [`auto_mark_name`], which spells the name this tool
228/// writes. Recognising a mark is something every reader does; producing one is
229/// something only whatever authors operations does, and it wants a calendar the
230/// engine deliberately has not got.
231pub use oxedyne_fe2o3_ore::op::{
232 is_auto_mark,
233 AUTO_MARK_PREFIX,
234};
235
236
237/// Returns the clock, in microseconds since the Unix epoch, UTC.
238///
239/// The engine reads no clock. It holds no time zone, no calendar and no notion of
240/// now, which is what lets it be the same code on a machine and in a browser, so
241/// every time that reaches an operation is read here and passed in -- once, by
242/// whoever authors the operation. Nothing recomputes one: the value is inside the
243/// signature, and a second reading would be a different operation.
244///
245/// This is the finer of the two readings and it is what an automatic mark's
246/// **name** is spelled from; what reaches the operation is [`now_secs`], the wire
247/// carrying seconds. The extra digits are there to keep two names apart and for
248/// nothing else -- see [`auto_mark_name`].
249pub fn now_micros()
250 -> Outcome<u64>
251{
252 let stamp = res!(SystemTime::now().duration_since(UNIX_EPOCH));
253 Ok(stamp.as_micros() as u64)
254}
255
256/// Returns the clock, in seconds since the Unix epoch, UTC, which is what an
257/// operation carries.
258///
259/// Seconds rather than nanoseconds, and a plain number rather than a calendar
260/// type, because what the history wants is a stamp to show a reader and not a
261/// quantity to compute with. It orders nothing: a clock that is wrong is still a
262/// clock, and everything that decides anything decides by
263/// [`oxedyne_fe2o3_ore::seq::OpOrder`].
264pub fn now_secs()
265 -> Outcome<u64>
266{
267 Ok(res!(now_micros()) / 1_000_000)
268}
269
270/// Returns the name the automatic mark at the given time takes: [`AUTO_MARK_PREFIX`]
271/// and then the time as RFC 3339 in UTC to the microsecond, as in
272/// `@2026-08-17T04:12:09.482913Z`.
273///
274/// A person reading a log wants to know when, and an operation identifier does
275/// not say. The name is therefore the time, spelled the one way that sorts
276/// lexically as it sorts chronologically -- which is a convenience for the eye
277/// and not an ordering: two replicas' clocks disagree, and what orders marks is
278/// the operation order.
279///
280/// # Why the fraction is there
281///
282/// Named to the second, two commands run within one second take the same name,
283/// which was seen on the first trial rather than under any stress. Two marks of
284/// one name is not a harmless collision: this tool already gives it a meaning --
285/// a person saying where they are now -- and [`crate::verbs`] arbitrates between
286/// them, so two automatic marks sharing a name would make one name stand for two
287/// different points. RFC 3339 permits the fraction, so the name is still a
288/// datetime and still sorts.
289///
290/// Microseconds are enough **here**, and the reason is local rather than
291/// general: [`ore_store::store::Lock`] serialises the writers of one replica, so
292/// two automatic marks cannot be authored in the same microsecond on one replica.
293/// That is not a claim that a microsecond stamp is unique in general, and nothing
294/// should be built on it as though it were; two replicas may well name the same
295/// microsecond, and the operation order is what tells those apart.
296pub fn auto_mark_name(micros: u64) -> String {
297 let secs = micros / 1_000_000;
298 let (y, m, d) = civil(secs / 86_400);
299 let rest = secs % 86_400;
300 fmt!("{}{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:06}Z",
301 AUTO_MARK_PREFIX, y, m, d, rest / 3600, (rest % 3600) / 60, rest % 60,
302 micros % 1_000_000)
303}
304
305/// Returns the year, month and day of a count of days since 1970-01-01.
306///
307/// Howard Hinnant's civil-from-days, which is the shift-the-era arithmetic that
308/// turns the calendar's leap rules into four divisions and no table. It is here
309/// rather than taken from `fe2o3_datime` because what is wanted is seven
310/// characters of a mark's name: the calendar crate carries a time zone database,
311/// a naming scheme and an arbitrary-precision numeric layer behind it, and none
312/// of that is being asked a question. Every date this is given is a UTC one after
313/// the epoch, so nothing here has to handle a negative day count.
314///
315/// It is also why [`auto_mark_name`] did not go upstream beside
316/// [`AUTO_MARK_PREFIX`]. Putting it there would put a calendar into the crate
317/// whose stated property is that it holds no clock and no notion of now, and
318/// would make this the third civil-from-days in the tree. Nothing but a tool that
319/// authors operations ever produces one of these names, so there is no second
320/// implementation for it to disagree with -- which is exactly what was true of
321/// the prefix, and is why the prefix went up.
322fn civil(days: u64) -> (u64, u64, u64) {
323 // The era is a 400 year cycle, counted from 0000-03-01 so that the leap day
324 // falls at the end of a year and the months run 3 to 14.
325 let z = days + 719_468;
326 let era = z / 146_097;
327 let doe = z - era * 146_097; // Day of era, 0 to 146096.
328 let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
329 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // Day of year, March first.
330 let mp = (5 * doy + 2) / 153; // Month, 0 being March.
331 let d = doy - (153 * mp + 2) / 5 + 1;
332 let m = if mp < 10 { mp + 3 } else { mp - 9 };
333 let y = yoe + era * 400 + u64::from(m <= 2);
334 (y, m, d)
335}
336
337
338/// What `.ore/config` says.
339#[derive(Clone, Debug)]
340pub struct Config {
341 /// Version of the configuration format.
342 pub format: u64,
343 /// This working copy's replica identifier.
344 pub replica: ReplicaId,
345 /// Version of the segment format the repository was made at.
346 ///
347 /// A note of provenance and not a decision: every segment declares its own
348 /// version in its first bytes, and a log may hold segments of more than one
349 /// where the engine's reader spans them. Nothing consults this field, and a
350 /// repository whose value has fallen behind the engine's is not thereby
351 /// wrong about anything.
352 pub segment: u8,
353 /// Git author identity to replica identifier, as an import recorded it.
354 pub authors: BTreeMap<String, u64>,
355 /// Every public key this repository will accept a signature from: its own,
356 /// every key it has minted before a rotation, and every key a sync taught
357 /// it.
358 pub keys: Vec<Binding>,
359 /// Every veil key binding this repository has learned, so that it knows
360 /// which key to wrap a content key to for each replica.
361 pub veils: Vec<VeilBinding>,
362 /// The wraps this repository has made of its own content key, held until a
363 /// sync deposits them.
364 ///
365 /// A wrap is public: it is the content key encrypted to somebody else's veil
366 /// key, and it is kept here rather than only on a relay so that making one is
367 /// an offline act and depositing it is not.
368 pub wraps: Vec<Wrap>,
369 /// Whether this repository refuses to write or accept an unsigned
370 /// operation.
371 pub require_signed: bool,
372 /// Where this repository keeps a git mirror of its history, if it keeps one.
373 ///
374 /// Naming one is the whole of the bridge: every verb that appends operations
375 /// brings the mirror current when it finishes, so there is no export to
376 /// remember and no moment at which the mirror is behind. A relative path is
377 /// taken from the repository root. Absent from the file unless set.
378 pub mirror: Option<String>,
379}
380
381impl Config {
382 /// Constructs the configuration of a fresh repository.
383 pub fn new(replica: ReplicaId) -> Self {
384 Self {
385 format: CONFIG_VERSION,
386 replica,
387 segment: segment::VERSION,
388 authors: BTreeMap::new(),
389 keys: Vec::new(),
390 veils: Vec::new(),
391 wraps: Vec::new(),
392 require_signed: false,
393 mirror: None,
394 }
395 }
396
397 /// Records a public key binding, and reports whether it was new.
398 ///
399 /// Keys accumulate and are never taken away. A rotation leaves the key it
400 /// replaced behind on purpose: the operations that key signed are in the
401 /// history for good, and forgetting the key would turn every one of them
402 /// from verified into signed by a stranger.
403 ///
404 /// A key already here whose binding carried no signature takes on one that
405 /// arrives certified. The key is the same key and the trust set does not
406 /// move; what changes is that the binding can now be passed to a stranger,
407 /// and a repository that predates self-certification should not have to be
408 /// rebuilt to gain that.
409 /// Reads the configuration of the `.ore` directory `dir`.
410 pub fn read(dir: &Path)
411 -> Outcome<Self>
412 {
413 let path = dir.join(CONFIG_FILE);
414 let text = match fs::read_to_string(&path) {
415 Ok(t) => t,
416 Err(e) => return Err(err!(e,
417 "The configuration {:?} could not be read.", path;
418 IO, File, Read)),
419 };
420 let dat = match Dat::decode_string(text) {
421 Ok(d) => d,
422 Err(e) => return Err(err!(e,
423 "The configuration {:?} is not readable JDAT.", path;
424 Decode, Input)),
425 };
426 Self::from_dat(&dat)
427 }
428
429 pub fn learn(&mut self, binding: Binding) -> bool {
430 if let Some(held) = self.keys.iter_mut().find(|b| b.public == binding.public) {
431 if held.sig.is_none() && binding.is_certified() {
432 held.sig = binding.sig;
433 }
434 return false;
435 }
436 self.keys.push(binding);
437 self.keys.sort();
438 true
439 }
440
441 /// Records a veil key binding, and reports whether it was new.
442 ///
443 /// Only one whose chain holds against the signing bindings already known: a
444 /// veil key vouched for by a key this repository cannot place is a reading
445 /// key from a stranger, and writing it down would mean wrapping a content key
446 /// to whoever offered it.
447 pub fn learn_veil(&mut self, binding: VeilBinding) -> bool {
448 if !binding.is_chained(&self.keys) {
449 return false;
450 }
451 if self.veils.iter().any(|b| b.public == binding.public) {
452 return false;
453 }
454 self.veils.push(binding);
455 self.veils.sort();
456 true
457 }
458
459 /// Holds a wrap to be deposited, replacing any this repository already made
460 /// for the same veil key.
461 pub fn hold_wrap(&mut self, wrap: Wrap) {
462 self.wraps.retain(|w| w.to != wrap.to);
463 self.wraps.push(wrap);
464 self.wraps.sort();
465 }
466
467 /// Returns the trust set the bindings amount to.
468 pub fn trust(&self) -> Trust {
469 Trust::of(&self.keys)
470 }
471
472 /// Serialises the configuration to a [`Dat`].
473 pub fn to_dat(&self) -> Dat {
474 let mut authors = DaticleMap::new();
475 for (identity, replica) in &self.authors {
476 authors.insert(Dat::Str(identity.clone()), Dat::U64(*replica));
477 }
478 let mut map = DaticleMap::new();
479 map.insert(Dat::Str(fmt!("format")), Dat::U64(self.format));
480 map.insert(Dat::Str(fmt!("replica")), Dat::U64(self.replica.inner()));
481 map.insert(Dat::Str(fmt!("segment")), Dat::U8(self.segment));
482 map.insert(Dat::Str(fmt!("authors")), Dat::Map(authors));
483 map.insert(Dat::Str(fmt!("keys")), Dat::List(
484 self.keys.iter().map(|b| b.to_dat()).collect(),
485 ));
486 map.insert(Dat::Str(fmt!("veils")), Dat::List(
487 self.veils.iter().map(|b| b.to_dat()).collect(),
488 ));
489 map.insert(Dat::Str(fmt!("wraps")), Dat::List(
490 self.wraps.iter().map(|w| w.to_dat()).collect(),
491 ));
492 map.insert(Dat::Str(fmt!("require_signed")), Dat::Bool(self.require_signed));
493 if let Some(path) = &self.mirror {
494 map.insert(Dat::Str(fmt!("mirror")), Dat::Str(path.clone()));
495 }
496 Dat::Map(map)
497 }
498
499 /// Reconstructs the configuration from a [`Dat`].
500 pub fn from_dat(dat: &Dat)
501 -> Outcome<Self>
502 {
503 let map = match dat {
504 Dat::Map(m) => m,
505 other => return Err(err!(
506 "The configuration expects a map, got {:?}.", other;
507 Decode, Input, Mismatch)),
508 };
509 let format = res!(field_u64(map, "format"));
510 if format != CONFIG_VERSION {
511 return Err(err!(
512 "The configuration declares format version {}, and this tool knows \
513 only version {}.", format, CONFIG_VERSION;
514 Decode, Input, Version, Mismatch));
515 }
516 let replica = ReplicaId::new(res!(field_u64(map, "replica")));
517 let segment = res!(field_u64(map, "segment")) as u8;
518 let mut authors = BTreeMap::new();
519 match map.get(&Dat::Str(fmt!("authors"))) {
520 Some(Dat::Map(m)) => {
521 for (k, v) in m {
522 let identity = match k {
523 Dat::Str(s) => s.clone(),
524 other => return Err(err!(
525 "An author identity expects a string, got {:?}.", other;
526 Decode, Input, Mismatch)),
527 };
528 let n = match v {
529 Dat::U64(n) => *n,
530 Dat::U32(n) => *n as u64,
531 other => return Err(err!(
532 "The replica of the author {:?} expects a number, got \
533 {:?}.", identity, other;
534 Decode, Input, Mismatch)),
535 };
536 authors.insert(identity, n);
537 }
538 },
539 Some(other) => return Err(err!(
540 "The author mapping expects a map, got {:?}.", other;
541 Decode, Input, Mismatch)),
542 None => (),
543 }
544 // Both of the provenance fields are optional on the way in. A repository
545 // made before this tool signed anything holds neither, and it keeps
546 // working: it knows no keys and requires nothing.
547 let mut keys = Vec::new();
548 match map.get(&Dat::Str(fmt!("keys"))) {
549 Some(Dat::List(l)) => {
550 for item in l {
551 keys.push(res!(Binding::from_dat(item)));
552 }
553 },
554 Some(other) => return Err(err!(
555 "The key list expects a list, got {:?}.", other;
556 Decode, Input, Mismatch)),
557 None => (),
558 }
559 keys.sort();
560 // Absent from a repository made before wraps existed, exactly as the key
561 // list is absent from one made before this tool signed anything.
562 let mut veils = Vec::new();
563 match map.get(&Dat::Str(fmt!("veils"))) {
564 Some(Dat::List(l)) => {
565 for item in l {
566 veils.push(res!(VeilBinding::from_dat(item)));
567 }
568 },
569 Some(other) => return Err(err!(
570 "The veil key list expects a list, got {:?}.", other;
571 Decode, Input, Mismatch)),
572 None => (),
573 }
574 veils.sort();
575 let mut wraps = Vec::new();
576 match map.get(&Dat::Str(fmt!("wraps"))) {
577 Some(Dat::List(l)) => {
578 for item in l {
579 wraps.push(res!(Wrap::from_dat(item)));
580 }
581 },
582 Some(other) => return Err(err!(
583 "The wrap list expects a list, got {:?}.", other;
584 Decode, Input, Mismatch)),
585 None => (),
586 }
587 wraps.sort();
588 let require_signed = match map.get(&Dat::Str(fmt!("require_signed"))) {
589 Some(Dat::Bool(b)) => *b,
590 Some(other) => return Err(err!(
591 "The configuration field \"require_signed\" expects true or false, got \
592 {:?}.", other;
593 Decode, Input, Mismatch)),
594 None => false,
595 };
596 // Optional on the way in: a repository that keeps no git mirror says
597 // nothing about one.
598 let mirror = match map.get(&Dat::Str(fmt!("mirror"))) {
599 Some(Dat::Str(s)) => Some(s.clone()),
600 Some(other) => return Err(err!(
601 "The configuration field \"mirror\" expects a path as a string, got \
602 {:?}.", other;
603 Decode, Input, Mismatch)),
604 None => None,
605 };
606 Ok(Self {
607 format, replica, segment, authors, keys, veils, wraps, require_signed, mirror,
608 })
609 }
610}
611
612/// Reads a numeric field of the configuration map.
613fn field_u64(map: &DaticleMap, key: &str)
614 -> Outcome<u64>
615{
616 match map.get(&Dat::Str(fmt!("{}", key))) {
617 Some(Dat::U64(n)) => Ok(*n),
618 Some(Dat::U32(n)) => Ok(*n as u64),
619 Some(Dat::U16(n)) => Ok(*n as u64),
620 Some(Dat::U8(n)) => Ok(*n as u64),
621 Some(other) => Err(err!(
622 "The configuration field {:?} expects a number, got {:?}.", key, other;
623 Decode, Input, Mismatch)),
624 None => Err(err!(
625 "The configuration has no field {:?}.", key;
626 Decode, Input, Missing)),
627 }
628}
629
630
631/// One command that appended operations, and the frontier it found when it
632/// began.
633///
634/// The frontier is kept rather than the operations themselves because it is the
635/// thing a reader wants: rendering the log at `before` is the state as it stood
636/// before the command ran, and that is what undoing the command means.
637#[derive(Clone, Debug)]
638pub struct Batch {
639 /// The command that ran, verb and arguments.
640 pub verb: String,
641 /// The frontier of the log before the verb appended anything.
642 pub before: Vec<OpId>,
643}
644
645impl Batch {
646 /// Serialises the batch to a [`Dat`].
647 pub fn to_dat(&self) -> Dat {
648 let mut map = DaticleMap::new();
649 map.insert(Dat::Str(fmt!("verb")), Dat::Str(self.verb.clone()));
650 map.insert(Dat::Str(fmt!("before")), ids_to_dat(&self.before));
651 Dat::Map(map)
652 }
653
654 /// Reconstructs a batch from a [`Dat`].
655 pub fn from_dat(dat: &Dat)
656 -> Outcome<Self>
657 {
658 let map = match dat {
659 Dat::Map(m) => m,
660 other => return Err(err!(
661 "A batch expects a map, got {:?}.", other;
662 Decode, Input, Mismatch)),
663 };
664 let verb = match map.get(&Dat::Str(fmt!("verb"))) {
665 Some(Dat::Str(s)) => s.clone(),
666 other => return Err(err!(
667 "A batch's verb expects a string, got {:?}.", other;
668 Decode, Input, Mismatch)),
669 };
670 let before = res!(ids_from_dat(map.get(&Dat::Str(fmt!("before"))), "a batch's frontier"));
671 Ok(Self { verb, before })
672 }
673}
674
675/// Reads a number however wide the writer made it.
676pub fn number(dat: &Dat)
677 -> Outcome<u64>
678{
679 match dat {
680 Dat::U64(n) => Ok(*n),
681 Dat::U32(n) => Ok(*n as u64),
682 Dat::U16(n) => Ok(*n as u64),
683 Dat::U8(n) => Ok(*n as u64),
684 other => Err(err!(
685 "A number was expected, got {:?}.", other;
686 Decode, Input, Mismatch)),
687 }
688}
689
690/// Writes a list of operation identifiers as a [`Dat`].
691///
692/// Every sidecar this tool keeps holds a frontier or something shaped like one,
693/// so the pair of replica and counter is written in one place rather than in
694/// each of them.
695pub fn ids_to_dat(ids: &[OpId]) -> Dat {
696 Dat::List(ids.iter().map(|id| Dat::List(vec![
697 Dat::U64(id.replica.inner()),
698 Dat::U64(id.counter),
699 ])).collect())
700}
701
702/// Reads a list of operation identifiers, saying what was being read where it
703/// will not decode.
704pub fn ids_from_dat(dat: Option<&Dat>, what: &str)
705 -> Outcome<Vec<OpId>>
706{
707 let listed = match dat {
708 Some(Dat::List(l)) => l,
709 other => return Err(err!(
710 "{} expects a list, got {:?}.", what, other;
711 Decode, Input, Mismatch)),
712 };
713 let mut out = Vec::with_capacity(listed.len());
714 for item in listed {
715 let pair = match item {
716 Dat::List(l) if l.len() == 2 => l,
717 other => return Err(err!(
718 "An operation identifier expects a replica and a counter, got {:?}.",
719 other;
720 Decode, Input, Mismatch)),
721 };
722 let replica = res!(number(&pair[0]));
723 let counter = res!(number(&pair[1]));
724 out.push(OpId::new(ReplicaId::new(replica), counter));
725 }
726 Ok(out)
727}
728
729
730/// What a sync left behind in the repository it was not run from.
731///
732/// A sync updates the log at both ends and writes only the working copy it was
733/// run from, because the other end may be a mirror, a mounted drive or somebody
734/// else's checkout, and writing into it uninvited is not this tool's business.
735/// That leaves the log ahead of the working copy, which is a state every other
736/// verb would misread: capture compares the two and would record the difference
737/// as an edit, so a plain `ore log` would quietly revert what the sync brought.
738///
739/// The marker is what stops that. It records the frontier the working copy still
740/// stands at, so the next verb run there can tell the two apart: a working copy
741/// that still renders that frontier exactly is one nobody has touched, and it is
742/// written forward to the merged state; one that does not is one somebody has
743/// edited since, and their bytes are captured as the edit they are.
744#[derive(Clone, Debug)]
745pub struct Pending {
746 /// The frontier the working copy renders, which is where the log stood
747 /// before the sync brought anything in.
748 pub frontier: Vec<OpId>,
749 /// The repository the operations came from, for the message.
750 pub from: String,
751}
752
753impl Pending {
754
755 /// Serialises the marker to a [`Dat`].
756 pub fn to_dat(&self) -> Dat {
757 let mut map = DaticleMap::new();
758 map.insert(Dat::Str(fmt!("frontier")), ids_to_dat(&self.frontier));
759 map.insert(Dat::Str(fmt!("from")), Dat::Str(self.from.clone()));
760 Dat::Map(map)
761 }
762
763 /// Reconstructs the marker from a [`Dat`].
764 pub fn from_dat(dat: &Dat)
765 -> Outcome<Self>
766 {
767 let map = match dat {
768 Dat::Map(m) => m,
769 other => return Err(err!(
770 "A pending marker expects a map, got {:?}.", other;
771 Decode, Input, Mismatch)),
772 };
773 let from = match map.get(&Dat::Str(fmt!("from"))) {
774 Some(Dat::Str(s)) => s.clone(),
775 other => return Err(err!(
776 "A pending marker's origin expects a string, got {:?}.", other;
777 Decode, Input, Mismatch)),
778 };
779 let frontier = res!(ids_from_dat(
780 map.get(&Dat::Str(fmt!("frontier"))), "a pending marker's frontier"));
781 Ok(Self { frontier, from })
782 }
783}
784
785
786/// An open repository: where it lives, what it is configured as, and every
787/// operation it holds.
788pub struct Repo {
789 /// Root of the working tree, the directory holding `.ore`.
790 pub root: PathBuf,
791 /// The `.ore` directory itself.
792 pub dir: PathBuf,
793 /// The segments within it, and everything that reads or appends to them.
794 pub store: Store,
795 /// What the configuration file says.
796 pub cfg: Config,
797 /// Every operation, replayed from the segments.
798 pub log: OpLog,
799 /// What each command that appended operations found when it began, oldest
800 /// first.
801 pub batches: Vec<Batch>,
802 /// This replica's signing key, where it has one. A repository without one
803 /// authors bare records.
804 pub signer: Option<Signing>,
805 /// The envelope every sealed operation arrived or was authored in, kept so
806 /// that provenance can be handed on to a peer rather than stopping here.
807 pub envelopes: BTreeMap<OpId, Envelope>,
808 /// What is known about who wrote each operation.
809 pub prov: BTreeMap<OpId, Prov>,
810 /// This repository's content key, where it has one. Without one, operations
811 /// go to a relay as they stand and the relay can read them.
812 pub veil: Option<Veil>,
813 /// This replica's veil key, where it has one. It receives the content key
814 /// wrapped, and is what lets somebody be let in without a key being read out
815 /// loud.
816 pub veilkey: Option<VeilKey>,
817 /// Where the read that filled this stopped, so that anything derived from
818 /// the log can say which bytes it was derived from. See [`crate::listing`].
819 pub cursor: Consumed,
820}
821
822impl Repo {
823
824 /// Creates a repository at `root`, minting a replica identifier for it.
825 ///
826 /// A key pair is minted with it unless `signed` is false, and the public key
827 /// is recorded in the configuration. An unsigned repository is the state
828 /// every repository made before signing existed is in, and it is offered
829 /// here so that state can be reached deliberately as well as inherited.
830 ///
831 /// Fails if `.ore` is already there, since a second initialisation would
832 /// mint a second identifier for a working copy that already has one.
833 pub fn init(root: &Path, signed: bool)
834 -> Outcome<Self>
835 {
836 let dir = root.join(ORE_DIR);
837 if dir.exists() {
838 return Err(err!(
839 "{:?} is already an Ore repository.", root;
840 Invalid, Input, Exists));
841 }
842 // The directory itself, before anything is written into it. It used to
843 // arrive as a side effect of making the snapshot directory inside it.
844 match fs::create_dir_all(&dir) {
845 Ok(()) => (),
846 Err(e) => return Err(err!(e,
847 "The repository directory {:?} could not be created.", dir;
848 IO, File, Create)),
849 }
850 let mut cfg = Config::new(res!(mint_replica(root)));
851 let signer = if signed {
852 let key = res!(Signing::mint(cfg.replica));
853 res!(key.write(&dir));
854 cfg.learn(key.binding());
855 Some(key)
856 } else {
857 None
858 };
859 let store = res!(Store::create(&dir, Some(cfg.replica)));
860 let repo = Self {
861 root: root.to_path_buf(),
862 dir,
863 store,
864 cfg,
865 log: OpLog::new(),
866 batches: Vec::new(),
867 signer,
868 envelopes: BTreeMap::new(),
869 prov: BTreeMap::new(),
870 veil: None,
871 veilkey: None,
872 cursor: Consumed::new(),
873 };
874 res!(repo.save_config());
875 Ok(repo)
876 }
877
878 /// Returns the root of the repository containing `start`, searching it and
879 /// its ancestors.
880 pub fn find_root(start: &Path)
881 -> Outcome<PathBuf>
882 {
883 let start = res!(fs::canonicalize(start));
884 let mut at: Option<&Path> = Some(start.as_path());
885 while let Some(dir) = at {
886 if dir.join(ORE_DIR).join(CONFIG_FILE).is_file() {
887 return Ok(dir.to_path_buf());
888 }
889 at = dir.parent();
890 }
891 Err(err!(
892 "Neither {:?} nor any directory above it holds an Ore repository; run \
893 `ore init` first.", start;
894 Invalid, Input, Missing))
895 }
896
897 /// Opens the repository rooted exactly at `root`.
898 pub fn open_at(root: &Path, keep: Keep)
899 -> Outcome<Self>
900 {
901 let dir = root.join(ORE_DIR);
902 let mut cfg = res!(Config::read(&dir));
903 let signer = res!(Signing::read(&dir));
904 // A repository trusts the key it holds the secret half of, whatever its
905 // configuration says. The two part company only where the configuration
906 // has been edited or lost, and in that case the key file is the better
907 // evidence: it is the one this replica actually signs with.
908 let mut relearned = false;
909 if let Some(key) = &signer {
910 relearned = cfg.learn(key.binding());
911 }
912 let veil = res!(Veil::read(&dir));
913 let veilkey = res!(VeilKey::read(&dir));
914 let store = Store::at(&dir);
915 let mut repo = Self {
916 root: root.to_path_buf(),
917 dir,
918 store,
919 cfg,
920 log: OpLog::new(),
921 batches: Vec::new(),
922 signer,
923 envelopes: BTreeMap::new(),
924 prov: BTreeMap::new(),
925 veil,
926 veilkey,
927 cursor: Consumed::new(),
928 };
929 if relearned {
930 res!(repo.save_config());
931 }
932 res!(repo.replay(keep));
933 repo.batches = res!(repo.read_batches());
934 Ok(repo)
935 }
936
937 /// Writes the configuration file.
938 pub fn save_config(&self)
939 -> Outcome<()>
940 {
941 let text = res!(self.cfg.to_dat().jdat_to_lines(" "));
942 let path = self.dir.join(CONFIG_FILE);
943 match fs::write(&path, fmt!("{}\n", text)) {
944 Ok(()) => Ok(()),
945 Err(e) => Err(err!(e,
946 "The configuration {:?} could not be written.", path;
947 IO, File, Write)),
948 }
949 }
950
951 /// Returns the path of the batch record.
952 pub fn batch_path(&self) -> PathBuf {
953 self.dir.join(BATCH_FILE)
954 }
955
956 /// Reads the batch record, which a repository is not obliged to have.
957 ///
958 /// A file that will not decode is an error like any other: it is small, this
959 /// tool wrote it, and quietly starting again with an empty one would throw
960 /// away the only account of what the commands before this one did.
961 pub fn read_batches(&self)
962 -> Outcome<Vec<Batch>>
963 {
964 let path = self.batch_path();
965 if !path.is_file() {
966 return Ok(Vec::new());
967 }
968 let text = match fs::read_to_string(&path) {
969 Ok(t) => t,
970 Err(e) => return Err(err!(e,
971 "The batch record {:?} could not be read.", path;
972 IO, File, Read)),
973 };
974 let dat = match Dat::decode_string(text) {
975 Ok(d) => d,
976 Err(e) => return Err(err!(e,
977 "The batch record {:?} is not readable JDAT.", path;
978 Decode, Input)),
979 };
980 let listed = match &dat {
981 Dat::List(l) => l,
982 other => return Err(err!(
983 "The batch record {:?} expects a list, got {:?}.", path, other;
984 Decode, Input, Mismatch)),
985 };
986 let mut out = Vec::new();
987 for item in listed {
988 out.push(res!(Batch::from_dat(item)));
989 }
990 Ok(out)
991 }
992
993 /// Writes the batch record.
994 pub fn save_batches(&self)
995 -> Outcome<()>
996 {
997 let listed: Vec<Dat> = self.batches.iter().map(|b| b.to_dat()).collect();
998 let text = res!(Dat::List(listed).jdat_to_lines(" "));
999 let path = self.batch_path();
1000 match fs::write(&path, fmt!("{}\n", text)) {
1001 Ok(()) => Ok(()),
1002 Err(e) => Err(err!(e,
1003 "The batch record {:?} could not be written.", path;
1004 IO, File, Write)),
1005 }
1006 }
1007
1008 /// Records that a command which began at `before` appended operations, if it
1009 /// did.
1010 ///
1011 /// A command that appended nothing is not a batch: it left the history where
1012 /// it found it, and there is nothing about it to undo.
1013 pub fn record(&mut self, said: &str, before: Vec<OpId>)
1014 -> Outcome<()>
1015 {
1016 if self.log.frontier() == before {
1017 return Ok(());
1018 }
1019 self.batches.push(Batch { verb: fmt!("{}", said), before });
1020 if self.batches.len() > BATCH_LIMIT {
1021 let over = self.batches.len() - BATCH_LIMIT;
1022 self.batches.drain(..over);
1023 }
1024 self.save_batches()
1025 }
1026
1027 /// Returns the path of the marker a sync leaves behind.
1028 pub fn pending_path(&self) -> PathBuf {
1029 self.dir.join(PENDING_FILE)
1030 }
1031
1032 /// Reads the marker a sync left behind, if there is one.
1033 pub fn read_pending(&self)
1034 -> Outcome<Option<Pending>>
1035 {
1036 let path = self.pending_path();
1037 if !path.is_file() {
1038 return Ok(None);
1039 }
1040 let text = match fs::read_to_string(&path) {
1041 Ok(t) => t,
1042 Err(e) => return Err(err!(e,
1043 "The sync marker {:?} could not be read.", path;
1044 IO, File, Read)),
1045 };
1046 let dat = match Dat::decode_string(text) {
1047 Ok(d) => d,
1048 Err(e) => return Err(err!(e,
1049 "The sync marker {:?} is not readable JDAT.", path;
1050 Decode, Input)),
1051 };
1052 Ok(Some(res!(Pending::from_dat(&dat))))
1053 }
1054
1055 /// Writes the marker a sync leaves behind.
1056 pub fn save_pending(&self, pending: &Pending)
1057 -> Outcome<()>
1058 {
1059 let text = res!(pending.to_dat().jdat_to_lines(" "));
1060 let path = self.pending_path();
1061 match fs::write(&path, fmt!("{}\n", text)) {
1062 Ok(()) => Ok(()),
1063 Err(e) => Err(err!(e,
1064 "The sync marker {:?} could not be written.", path;
1065 IO, File, Write)),
1066 }
1067 }
1068
1069 /// Removes the marker a sync left behind, if there is one.
1070 pub fn clear_pending(&self)
1071 -> Outcome<()>
1072 {
1073 let path = self.pending_path();
1074 if !path.is_file() {
1075 return Ok(());
1076 }
1077 match fs::remove_file(&path) {
1078 Ok(()) => Ok(()),
1079 Err(e) => Err(err!(e,
1080 "The sync marker {:?} could not be removed.", path;
1081 IO, File, Write)),
1082 }
1083 }
1084
1085 /// Replays every segment into the log, verifying what it meets on the way.
1086 ///
1087 /// Every sealed entry is put to `ore_store::keys::check`, and one whose
1088 /// signature does not verify refuses the whole log by name: the verb does not
1089 /// run, and the message says which operation and which file. See
1090 /// [`ore_store::store::Store::replay`], which is where that happens; a
1091 /// working copy always asks for the checking, a relay never does.
1092 pub fn replay(&mut self, keep: Keep)
1093 -> Outcome<()>
1094 {
1095 let trust = self.cfg.trust();
1096 // Entered through the resumed form from a cursor over nothing, which is
1097 // what [`ore_store::store::Store::replay`] is, so that the cursor the read
1098 // stopped at comes back with it. Anything derived from this log and kept
1099 // on disk is kept under that cursor. See [`crate::listing`].
1100 let mut done = Replayed::new();
1101 self.cursor = res!(self.store.replay_since(
1102 &Consumed::new(), &mut done, Verify::Signatures(&trust), keep));
1103 self.log = done.log;
1104 self.envelopes = done.envelopes;
1105 self.prov = done.prov;
1106 Ok(())
1107 }
1108
1109 /// Returns the identifier the next operation of `replica` should carry.
1110 pub fn next_id(&self, replica: ReplicaId) -> OpId {
1111 self.log.next_id(replica)
1112 }
1113
1114 /// Reports whether this repository is in a state to author anything at all.
1115 ///
1116 /// One that requires signed operations and holds no key is not, and
1117 /// [`Repo::author_with`] refuses it by name. The question is worth asking
1118 /// separately where the authoring is done on another repository's behalf: a
1119 /// sync names the point it left the other end standing at, and a refusal
1120 /// arriving after the exchange had already been written there would fail the
1121 /// command over the smaller half of what it did.
1122 pub fn can_author(&self) -> bool {
1123 self.signer.is_some() || !self.cfg.require_signed
1124 }
1125
1126 /// Appends an operation authored by `replica` against the given parents,
1127 /// and returns its identifier.
1128 ///
1129 /// The record goes into the log and into the current segment at once, so a
1130 /// command that fails part way leaves behind exactly the operations it had
1131 /// already announced.
1132 ///
1133 /// Where this repository holds a key the record is sealed first, and what
1134 /// reaches the segment is the envelope. The seal covers the operation, the
1135 /// identifier and the parents together, so what is being attested to is not
1136 /// merely the edit but where in the history it was made.
1137 pub fn author_with(&mut self, replica: ReplicaId, parents: Vec<OpId>, op: Op)
1138 -> Outcome<OpId>
1139 {
1140 let head = res!(Header::new(self.next_id(replica), parents));
1141 let id = head.id();
1142 let rec = Record::new(head, op);
1143 let entry = match &self.signer {
1144 Some(key) => Entry::Sealed(res!(key.seal(&rec))),
1145 None => {
1146 if !self.can_author() {
1147 return Err(err!(
1148 "This repository requires signed operations and holds no key, so \
1149 {} cannot be written. Run `ore key` to mint one, or set \
1150 require_signed to false in {:?}.",
1151 id, self.dir.join(CONFIG_FILE);
1152 Invalid, Configuration, Missing, Key));
1153 }
1154 Entry::Bare(rec.clone())
1155 },
1156 };
1157 res!(self.write_entries(&[entry.clone()]));
1158 match entry {
1159 Entry::Sealed(env) => {
1160 self.envelopes.insert(id, env);
1161 // Its own key is a key it knows, so its own work is verified.
1162 self.prov.insert(id, Prov::Verified);
1163 },
1164 Entry::Bare(_) => {
1165 self.prov.insert(id, Prov::Bare);
1166 },
1167 // A working copy authors what it can read. The veil goes on where an
1168 // operation is handed to a carrier and comes off where one arrives, so
1169 // nothing this repository writes is ever veiled on the way in.
1170 Entry::Veiled(v) => return Err(err!(
1171 "The operation {} was authored veiled, which nothing here does.", v.head.id();
1172 Bug, Invalid, Unreachable)),
1173 }
1174 res!(self.log.append(rec));
1175 Ok(id)
1176 }
1177
1178 /// Appends an operation authored by `replica` against the log's frontier.
1179 pub fn author(&mut self, replica: ReplicaId, op: Op)
1180 -> Outcome<OpId>
1181 {
1182 let parents = self.log.frontier();
1183 self.author_with(replica, parents, op)
1184 }
1185
1186 /// Takes one operation authored elsewhere into this repository's log and its
1187 /// segments, with whatever this repository can say about who wrote it.
1188 ///
1189 /// The one thing a sync puts into the other end outside its exchange. The mark
1190 /// naming where an exchange left the two of them is authored once and written
1191 /// into both logs, because two marks -- one per end, each unknown to the other
1192 /// -- leave the two holding different histories after every sync, for ever.
1193 /// See [`crate::sync`].
1194 ///
1195 /// The entry goes in the form it was written in, sealed or bare, and its
1196 /// provenance is decided by this repository's own trust set rather than
1197 /// carried over from the one it came from: an operation is verified by whoever
1198 /// is reading it or it is not verified at all.
1199 pub fn take(&mut self, entry: Entry)
1200 -> Outcome<()>
1201 {
1202 let trust = self.cfg.trust();
1203 let checked = res!(keys::check_all(
1204 std::slice::from_ref(&entry), &trust, "the mark a sync left behind",
1205 Keep::Envelopes,
1206 ));
1207 for got in checked {
1208 if self.cfg.require_signed && got.prov == Prov::Bare {
1209 return Err(err!(
1210 "{:?} requires signed operations and {} is unsigned, so the mark \
1211 naming where the sync left this repository was not written.",
1212 self.root, got.rec.id();
1213 Invalid, Input, Security, Missing));
1214 }
1215 let id = got.rec.id();
1216 if self.log.contains(&id) {
1217 continue;
1218 }
1219 if let Some(env) = got.env {
1220 self.envelopes.insert(id, env);
1221 }
1222 self.prov.insert(id, got.prov);
1223 res!(self.log.append(got.rec));
1224 }
1225 self.write_entries_from(&[entry], None)
1226 }
1227
1228 /// Returns the entry an operation this repository holds is written as, sealed
1229 /// where a seal was kept for it and bare otherwise.
1230 pub fn entry_of(&self, id: &OpId)
1231 -> Outcome<Entry>
1232 {
1233 match self.envelopes.get(id) {
1234 Some(env) => Ok(Entry::Sealed(env.clone())),
1235 None => {
1236 let rec = res!(self.log.get(id).ok_or_else(|| err!(
1237 "The log does not hold the operation {}.", id; Missing)));
1238 Ok(Entry::Bare(rec.clone()))
1239 },
1240 }
1241 }
1242
1243 /// Writes entries to the current segment under this repository's name.
1244 pub fn write_entries(&self, entries: &[Entry])
1245 -> Outcome<()>
1246 {
1247 self.write_entries_from(entries, Some(self.cfg.replica))
1248 }
1249
1250 /// Writes entries to the current segment under the given replica hint.
1251 ///
1252 /// Operations that arrived from elsewhere carry no one replica's name, so a
1253 /// sync passes `None` rather than putting this repository's name on somebody
1254 /// else's work. See [`ore_store::store::Store::append`].
1255 pub fn write_entries_from(&self, entries: &[Entry], hint: Option<ReplicaId>)
1256 -> Outcome<()>
1257 {
1258 self.store.append(entries, hint)
1259 }
1260
1261 /// Mints a key pair for this replica, replacing whatever key was there.
1262 ///
1263 /// The new public key is added to the configuration and the old one stays:
1264 /// a rotation is this replica saying what it will sign with from now on, and
1265 /// it says nothing about what it signed before. Both files are written
1266 /// before the call returns, the key first, so a failure part way leaves a
1267 /// repository whose configuration knows every key its key file might be.
1268 pub fn rotate_key(&mut self)
1269 -> Outcome<(Signing, Option<String>)>
1270 {
1271 let was = self.signer.as_ref().map(|k| k.public_text());
1272 let key = res!(Signing::mint(self.cfg.replica));
1273 res!(key.write(&self.dir));
1274 self.cfg.learn(key.binding());
1275 res!(self.save_config());
1276 self.signer = Some(key.clone());
1277 Ok((key, was))
1278 }
1279
1280 /// Puts a content key in place, minting one where none is given, and returns
1281 /// it with whatever was there before.
1282 ///
1283 /// Replacing one is not a rotation of the kind [`Repo::rotate_key`] performs:
1284 /// a signing key that changes leaves every signature it made still checking
1285 /// out, and a content key that changes leaves every operation already on a
1286 /// relay unreadable by the new one. So the caller is told what was there, and
1287 /// it is the caller's business to say so.
1288 pub fn set_veil(&mut self, key: Option<&[u8]>)
1289 -> Outcome<(Veil, Option<String>)>
1290 {
1291 let was = self.veil.as_ref().map(|v| v.text());
1292 let veil = match key {
1293 Some(bytes) => res!(Veil::of(bytes)),
1294 None => res!(Veil::mint()),
1295 };
1296 res!(veil.write(&self.dir));
1297 self.veil = Some(veil.clone());
1298 // The wraps held here were made of the key that has just been replaced,
1299 // so depositing them again would hand out a key nothing here uses any
1300 // more. They are dropped and each member is wrapped for afresh, which is
1301 // what revocation looks like from this end.
1302 self.cfg.wraps.clear();
1303 Ok((veil, was))
1304 }
1305
1306 /// Mints this replica's veil key, or hands back the one already here.
1307 ///
1308 /// Minting a second one would orphan every wrap already addressed to the
1309 /// first, which is a way of losing access to a repository rather than a way
1310 /// of rotating anything, so a key already here is returned untouched and the
1311 /// caller says so.
1312 pub fn set_veilkey(&mut self)
1313 -> Outcome<(VeilKey, bool)>
1314 {
1315 if let Some(key) = &self.veilkey {
1316 return Ok((key.clone(), false));
1317 }
1318 let key = res!(VeilKey::mint(self.cfg.replica));
1319 res!(key.write(&self.dir));
1320 self.veilkey = Some(key.clone());
1321 Ok((key, true))
1322 }
1323
1324 /// Adds every key binding of another configuration to this one, and returns
1325 /// how many were new.
1326 ///
1327 /// This is trust on first use: a repository learns a key because it met it,
1328 /// and nothing here asks whether the key deserves it. What it buys is that
1329 /// the second meeting can be checked against the first.
1330 pub fn learn_keys(&mut self, from: &Config) -> usize {
1331 let mut fresh = 0usize;
1332 for binding in &from.keys {
1333 if self.cfg.learn(binding.clone()) {
1334 fresh += 1;
1335 }
1336 }
1337 fresh
1338 }
1339
1340}
1341
1342
1343#[cfg(test)]
1344mod tests {
1345 use super::*;
1346
1347 /// The name an automatic mark takes is the time, and it is the same time
1348 /// something other than this code agrees it is.
1349 ///
1350 /// The expected values were produced by GNU `date -u -d @<seconds>` and not by
1351 /// this module: a calendar checked against itself is a calendar that agrees
1352 /// with its own mistake. The interesting seconds are the ones where the leap
1353 /// rules argue -- a leap day, the day after one, the century that is not a
1354 /// leap year, and the four hundredth year that is.
1355 #[test]
1356 fn an_automatic_mark_is_named_for_the_time()
1357 -> Outcome<()>
1358 {
1359 let known: [(u64, &str); 18] = [
1360 (0, "1970-01-01T00:00:00Z"),
1361 (1, "1970-01-01T00:00:01Z"),
1362 (86_399, "1970-01-01T23:59:59Z"),
1363 (86_400, "1970-01-02T00:00:00Z"),
1364 (68_169_600, "1972-02-29T00:00:00Z"), // The first leap day after the epoch.
1365 (951_782_400, "2000-02-29T00:00:00Z"), // A century that is a leap year.
1366 (951_868_800, "2000-03-01T00:00:00Z"),
1367 (1_078_012_800, "2004-02-29T00:00:00Z"),
1368 (1_709_164_800, "2024-02-29T00:00:00Z"),
1369 (1_709_251_200, "2024-03-01T00:00:00Z"),
1370 (1_755_000_000, "2025-08-12T12:00:00Z"),
1371 (1_767_225_599, "2025-12-31T23:59:59Z"),
1372 (2_147_483_647, "2038-01-19T03:14:07Z"), // Where a 32 bit clock stops.
1373 (4_102_444_800, "2100-01-01T00:00:00Z"),
1374 (4_107_456_000, "2100-02-28T00:00:00Z"), // A century that is not.
1375 (4_107_542_400, "2100-03-01T00:00:00Z"),
1376 (13_574_563_200,"2400-02-29T00:00:00Z"), // The next era boundary.
1377 (13_574_649_600,"2400-03-01T00:00:00Z"),
1378 ];
1379 // Every name this produces is one the engine's own rule recognises. That is
1380 // the assertion holding the two halves together: the rule is upstream in
1381 // `fe2o3_ore` because three consumers in two crates apply it, and the
1382 // spelling is down here because only whoever authors operations writes one.
1383 // A generator that drifted out of its own convention would otherwise be
1384 // caught by nothing, each half being honestly correct on its own.
1385 for (secs, want) in known {
1386 // The whole second, spelled with a zero fraction.
1387 let got = auto_mark_name(secs * 1_000_000);
1388 assert_eq!(got, fmt!("{}{}.000000Z", AUTO_MARK_PREFIX, want.trim_end_matches('Z')),
1389 "the mark for {} seconds since the epoch", secs);
1390 assert!(is_auto_mark(&got), "and it is known for one this tool wrote");
1391 // And the microsecond within it, which is the digit that keeps two
1392 // commands run in one second from taking the same name.
1393 let fine = auto_mark_name(secs * 1_000_000 + 482_913);
1394 assert_eq!(fine, fmt!("{}{}.482913Z", AUTO_MARK_PREFIX, want.trim_end_matches('Z')));
1395 assert_ne!(fine, got, "two names within one second are two names");
1396 }
1397 // A name a person chose is not one of these, whatever it holds.
1398 assert!(!is_auto_mark("release 1.0"));
1399 assert!(!is_auto_mark("2026-08-17T04:12:09.482913Z"));
1400 Ok(())
1401 }
1402}