OXYGENHUB
# 5b9c6ad45f6e oxygen / crates / oxy-core / src / protocol.rs
3.4 KiB · Rust · last changed Remotes: local transport, push with seals and reference attestations, fetch, pull as state union, clone 1 month ago source blame history raw
1
//! Transport-agnostic protocol (spec §16): what a client asks of a remote and
2
//! what a server does with a push. `LocalRemote` serves another repository
3
//! directory in-process; an HTTPS transport implements the same trait and the
4
//! server side reuses `Repo::receive_push` and friends unchanged.
5
6
use crate::attest::Attest;
7
use crate::hash::{ChangeHash, ObjectRef, ObjectType};
8
use crate::identity::Signature;
9
use crate::objects::Change;
10
use crate::repo::{Repo, RepoError, Result};
11
use std::path::Path;
12
13
/// An attestation together with the signatures over its ObjectRef.
14
#[derive(Clone, Debug, PartialEq, Eq)]
15
pub struct SignedAttest {
16
    pub attest: Attest,
17
    pub sigs: Vec<Signature>,
18
}
19
20
/// One reference as a remote reports it.
21
#[derive(Clone, Debug)]
22
pub struct RemoteRef {
23
    pub name: String,
24
    pub state: ObjectRef,
25
    pub heads: Vec<ChangeHash>,
26
    pub attestation: Option<SignedAttest>,
27
}
28
29
/// Everything needed to add a set of changes to a repository.
30
#[derive(Clone, Debug, Default)]
31
pub struct Bundle {
32
    /// In dependency order, each with its individual signatures.
33
    pub changes: Vec<(Change, Vec<Signature>)>,
34
    /// Seals covering the changes.
35
    pub attests: Vec<SignedAttest>,
36
    /// Chunks, blobs, identities, and state objects referenced by the changes.
37
    pub objects: Vec<(ObjectType, Vec<u8>)>,
38
}
39
40
impl Bundle {
41
    pub fn is_empty(&self) -> bool {
42
        self.changes.is_empty() && self.attests.is_empty() && self.objects.is_empty()
43
    }
44
}
45
46
/// A requested bookmark move (spec §10.2, §16.3).
47
#[derive(Clone, Debug)]
48
pub struct RefUpdate {
49
    pub name: String,
50
    pub old_state: ObjectRef,
51
    pub new_state: ObjectRef,
52
    pub force: bool,
53
    pub attestation: Option<SignedAttest>,
54
}
55
56
#[derive(Clone, Debug, PartialEq, Eq)]
57
pub struct RefResult {
58
    pub name: String,
59
    pub ok: bool,
60
    pub reason: String,
61
}
62
63
pub trait Remote {
64
    fn repo_id(&self) -> Result<[u8; 16]>;
65
    fn refs(&self) -> Result<Vec<RemoteRef>>;
66
    /// Changes in the closure of `want` but not of `have`, in dependency order.
67
    fn negotiate(&self, have: &[ChangeHash], want: &[ChangeHash]) -> Result<Vec<ChangeHash>>;
68
    fn fetch(&self, changes: &[ChangeHash]) -> Result<Bundle>;
69
    fn push(&self, bundle: Bundle, updates: Vec<RefUpdate>) -> Result<Vec<RefResult>>;
70
}
71
72
/// Another repository on this machine, served in-process.
73
pub struct LocalRemote {
74
    pub repo: Repo,
75
}
76
77
impl LocalRemote {
78
    pub fn open(path: &Path) -> Result<LocalRemote> {
79
        Ok(LocalRemote { repo: Repo::open(path)? })
80
    }
81
}
82
83
impl Remote for LocalRemote {
84
    fn repo_id(&self) -> Result<[u8; 16]> {
85
        self.repo.id()
86
    }
87
    fn refs(&self) -> Result<Vec<RemoteRef>> {
88
        self.repo.remote_refs()
89
    }
90
    fn negotiate(&self, have: &[ChangeHash], want: &[ChangeHash]) -> Result<Vec<ChangeHash>> {
91
        self.repo.negotiate(have, want)
92
    }
93
    fn fetch(&self, changes: &[ChangeHash]) -> Result<Bundle> {
94
        self.repo.bundle_for(changes)
95
    }
96
    fn push(&self, bundle: Bundle, updates: Vec<RefUpdate>) -> Result<Vec<RefResult>> {
97
        self.repo.receive_push(bundle, updates)
98
    }
99
}
100
101
/// Open a remote by URL. Only local paths (optionally `file://`) exist so far.
102
pub fn open_remote(url: &str) -> Result<Box<dyn Remote>> {
103
    let path = url.strip_prefix("file://").unwrap_or(url);
104
    if path.contains("://") {
105
        return Err(RepoError::Msg(format!("unsupported remote URL {url:?}: only local paths are implemented")));
106
    }
107
    Ok(Box::new(LocalRemote::open(Path::new(path))?))
108
}