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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
//! The `Provider` trait definition and various implementations.
//!
//! A Bindle Provider can be anything that gives (e.g. "provides") access to Bindles. This could be
//! anything from a cache, to a proxy, or a storage backend (such as a database). Thus, Bindles can
//! be fetched from an arbitrarily complex chain of providers depending on the needs of your
//! team/code/organization.
//!
//! In general, we don't recommend too many nested chains in code
//! (`MyLocalCache<MyRemoteCache<SharedCache<CompanyCache<FileProvider>>>>` is not a happy thing in
//! code) and suggest using chains of proxies between various providers. In practice, it could look
//! like this (not real types): `LocalCache<Proxy> -> (Another server) Proxy -> (The server where
//! bindles are stored) DatabaseProvider` This pattern can be particularly useful in larger teams as
//! you could easily add another source of Bindles to your approved list of external sources at any
//! point along the chain
//!
//! ## Terminal Providers
//!
//! One concept that is not actually code, but is important to understand, is the idea of "Terminal
//! Providers." These are providers that mark the end of a chain and are generally the actual
//! storage/database backend where the Bindles exist. In general, Providers that are _not_ terminal
//! will generally contain another Provider implementation or an HTTP client to talk to another
//! server upstream

#[cfg(feature = "providers")]
pub mod embedded;
#[cfg(feature = "providers")]
pub mod file;

use std::convert::TryInto;

use thiserror::Error;
use tokio_stream::Stream;

use crate::verification::Verified;
use crate::SignatureError;
use crate::{Id, Signed};

/// A custom shorthand result type that always has an error type of [`ProviderError`](ProviderError)
pub type Result<T> = core::result::Result<T, ProviderError>;

/// The basic functionality required for a Bindle provider.
///
/// Please note that due to this being an `async_trait`, the types might look complicated. Look at
/// the code directly to see the simpler function signatures for implementation.
///
/// IMPORTANT: If you are implementing a terminal provider, you must internally handle atomic
/// operations/locking to avoid race conditions in the back end. If you are writing one for a
/// database, this is likely handled for you by the database. However, in cases such as the built in
/// [`FileProvider`](crate::provider::file::FileProvider), you must ensure that two create
/// operations do not conflict and that a read operation of something being created also does not
/// conflict.
#[async_trait::async_trait]
pub trait Provider {
    /// This takes an invoice and creates it in storage. Returns the newly created invoice and a
    /// list of missing parcels
    ///
    /// It must verify that each referenced parcel is present in storage. Any parcel that is not
    /// present must be returned in the list of labels.
    async fn create_invoice<I>(&self, inv: I) -> Result<(crate::Invoice, Vec<super::Label>)>
    where
        I: Signed + Verified + Send + Sync;

    /// Load an invoice and return it
    ///
    /// This will return an invoice if the bindle exists and is not yanked. The default
    /// implementation of this method is sufficient for most use cases, but can be overridden if
    /// needed
    async fn get_invoice<I>(&self, id: I) -> Result<super::Invoice>
    where
        I: TryInto<Id> + Send,
        I::Error: Into<ProviderError>,
    {
        match self.get_yanked_invoice(id).await {
            Ok(inv) if !inv.yanked.unwrap_or(false) => Ok(inv),
            Err(e) => Err(e),
            _ => Err(ProviderError::Yanked),
        }
    }

    /// Load an invoice, even if it is yanked. This is called by the default implementation of
    /// `get_invoice`
    async fn get_yanked_invoice<I>(&self, id: I) -> Result<super::Invoice>
    where
        I: TryInto<Id> + Send,
        I::Error: Into<ProviderError>;

    /// Remove an invoice by ID
    async fn yank_invoice<I>(&self, id: I) -> Result<()>
    where
        I: TryInto<Id> + Send,
        I::Error: Into<ProviderError>;

    /// Checks if the given parcel ID exists within an invoice. The default implementation will fetch
    /// the parcel and check if the given parcel ID exists. Returns the parcel label if valid. Most
    /// providers should implement some sort of caching for `get_yanked_invoice` to avoid fetching
    /// the invoice every single time a parcel is requested. Provider implementations may also
    /// implement this function to include other validation logic if desired.
    async fn validate_parcel<I>(&self, bindle_id: I, parcel_id: &str) -> Result<crate::Label>
    where
        I: TryInto<Id> + Send,
        I::Error: Into<ProviderError>,
    {
        let inv = self.get_yanked_invoice(bindle_id).await?;
        match inv
            .parcel
            .unwrap_or_default()
            .into_iter()
            .find(|p| p.label.sha256 == parcel_id)
        {
            Some(p) => Ok(p.label),
            None => Err(ProviderError::NotFound),
        }
    }

    /// Creates a parcel with the associated sha. The parcel can be anything that implements
    /// `Stream`
    ///
    /// For some terminal providers, the bindle ID may not be necessary, but it is always required
    /// for an implementation. Implementors MUST validate that the length of the sent parcel is the
    /// same as specified in the invoice
    async fn create_parcel<I, R, B>(&self, bindle_id: I, parcel_id: &str, data: R) -> Result<()>
    where
        I: TryInto<Id> + Send,
        I::Error: Into<ProviderError>,
        R: Stream<Item = std::io::Result<B>> + Unpin + Send + Sync + 'static,
        B: bytes::Buf + Send;

    /// Get a specific parcel using its SHA.
    ///
    /// For some terminal providers, the bindle ID may not be necessary, but it is always required
    /// for an implementation
    async fn get_parcel<I>(
        &self,
        bindle_id: I,
        parcel_id: &str,
    ) -> Result<Box<dyn Stream<Item = Result<bytes::Bytes>> + Unpin + Send + Sync>>
    where
        I: TryInto<Id> + Send,
        I::Error: Into<ProviderError>;

    /// Checks if the given parcel exists in storage.
    ///
    /// This should not load the full parcel but only indicate if the parcel exists. For some
    /// terminal providers, the bindle ID may not be necessary, but it is always required
    async fn parcel_exists<I>(&self, bindle_id: I, parcel_id: &str) -> Result<bool>
    where
        I: TryInto<Id> + Send,
        I::Error: Into<ProviderError>;
}

/// ProviderError describes the possible error states when storing and retrieving bindles.
#[derive(Error, Debug)]
pub enum ProviderError {
    /// The invoice being accessed has been yanked
    #[error("bindle is yanked")]
    Yanked,
    /// The error returned when the invoice is valid, but is already set to yanked
    #[error("bindle cannot be created as yanked")]
    CreateYanked,
    /// When the resource is not found in the store
    #[error("resource not found: if an item does not appear in our records, it does not exist!")]
    NotFound,
    /// Any errors that occur due to IO issues. Contains the underlying IO `Error`
    #[error("resource could not be loaded")]
    Io(#[from] std::io::Error),
    /// The resource being created already exists in the system
    #[error("resource already exists")]
    Exists,
    /// The error returned when the given `Id` was invalid and unable to be parsed
    #[error("invalid ID given")]
    InvalidId(#[from] crate::id::ParseError),
    /// An uploaded parcel does not match the SHA-256 sum provided with its label
    #[error("digest does not match")]
    DigestMismatch,
    #[error("parcel size does not match invoice")]
    SizeMismatch,
    #[error(
        "a write operation is currently in progress for this resource and it cannot be accessed"
    )]
    WriteInProgress,
    /// An error that occurs when the provider implementation uses a proxy and that proxy request
    /// encounters an error. Only available with the `client` feature enabled
    #[cfg(feature = "client")]
    #[error("proxy error")]
    ProxyError(#[from] crate::client::ClientError),

    /// The data cannot be properly deserialized from TOML
    #[error("resource is malformed")]
    Malformed(#[from] toml::de::Error),
    /// The data cannot be properly serialized from TOML
    #[error("resource cannot be stored")]
    Unserializable(#[from] toml::ser::Error),

    #[error("failed signature check invoice")]
    FailedSigning(#[from] SignatureError),

    /// A catch-all for uncategorized errors. Contains an error message describing the underlying
    /// issue
    #[error("{0}")]
    Other(String),
}

impl From<std::convert::Infallible> for ProviderError {
    fn from(_: std::convert::Infallible) -> ProviderError {
        // This can never happen (by definition of infallible), so it doesn't matter what we return
        ProviderError::Other("Shouldn't happen".to_string())
    }
}

// TODO(thomastaylor312): We should probably have a more generic form of
// deserialization/serialization errors that aren't tied to TOML as backends can serialize how they
// want. For now there is this workaround
#[cfg(feature = "providers")]
impl From<serde_cbor::Error> for ProviderError {
    fn from(e: serde_cbor::Error) -> Self {
        if e.is_io() {
            ProviderError::Io(std::io::Error::new(std::io::ErrorKind::Other, e))
        } else {
            ProviderError::Other(format!("Unable to parse CBOR payload: {}", e))
        }
    }
}