fog_pack/
lib.rs

1//! A serialization library for content-addressed, decentralized storage.
2//!
3//! The fog-pack serialization format is designed from the ground-up to be effective and useful for
4//! content-addressed storage systems, and to work effectively in a decentralized network. With
5//! these being the highest priorities for the format, it has had to make some tough choices that
6//! other serialization formats do not. Here's the quick rundown:
7//!
8//! - It's a self-describing binary serialization format
9//! - It builds on [`serde`](https://serde.rs/) for serialization of Rust structs
10//! - It has a canonical form for all data. The same data will only ever have one valid serialized
11//!     version of itself.
12//! - It supports [Schema][schema::Schema] for verifying serialized data
13//! - Schema may be serialized
14//! - Data can be encapsulated into [Documents][document::Document], which can
15//!     be tagged with a schema the data conforms to. Documents always have a
16//!     cryptographic hash that uniquely identifies the data.
17//! - Data can also be encapsulated into [Entries][entry::Entry], which are
18//!     always associated with a parent document, and have a string for grouping
19//!     them with other similar Entries.
20//! - Documents and Entries may be **cryptographically signed**, which changes their identifying
21//!     hashes.
22//! - Documents and Entries may be **compressed with zstandard**, which does not change their
23//!     identifying hashes. Zstandard dictionaries are supported when a schema is used.
24//! - Documents and Entries are size-limited and have a limited nesting depth by design.
25//! - Encrypted objects are available, using the
26//!     [`fog-crypto`](https://crates.io/crates/fog-crypto) crate.
27//!
28//! # Key Concepts
29//!
30//! - [`Schemas`][schema::Schema]: A schema, which validates Documents and associated Entries, and can
31//!     compress both of them
32//! - [`Documents`][document::Document]: A hashed piece of serialized data, which may adhere to a schema and
33//!     be cryptographically signed.
34//! - [`Entries`][entry::Entry]: A hashed piece of serialized data, which has an associated parent
35//!     document and key string. It may also be cryptographically signed.
36//! - [`Queries`][query::Query]: A query, which may be used to find entries attached to a Document.
37//!
38//! These four types form the core of fog-pack's concepts, and are used to build up complex,
39//! inter-related data in content-addressed storage systems.
40//!
41//! So, what does it look like in use? Let's start with a simple idea: we want to make a streaming
42//! series of small text posts. It's some kind of blog, so let's have there be an author, blog
43//! title, and optional website link. Posts can be attached to the blog as entries, which will have
44//! a creation timestamp, an optional title, and the post content.
45//!
46//! We'll start by declaring the documents and the schema:
47//!
48//! ```
49//! # use fog_pack::{
50//! #     validator::*,
51//! #     schema::*,
52//! #     document::*,
53//! #     entry::*,
54//! #     query::*,
55//! #     types::*,
56//! # };
57//! # use serde::{Serialize, Deserialize};
58//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
59//! #
60//! // Our Blog's main document
61//! #[derive(Serialize, Deserialize)]
62//! struct Blog {
63//!     title: String,
64//!     author: String,
65//!     // We prefer to omit the field if it's set to None, which is not serde's default
66//!     #[serde(skip_serializing_if = "Option::is_none")]
67//!     link: Option<String>,
68//! }
69//!
70//! // Each post in our blog
71//! #[derive(Serialize, Deserialize)]
72//! struct Post {
73//!     created: Timestamp,
74//!     content: String,
75//!     #[serde(skip_serializing_if = "Option::is_none")]
76//!     title: Option<String>,
77//! }
78//!
79//! // Build our schema into a completed schema document.
80//! let schema_doc = SchemaBuilder::new(MapValidator::new()
81//!         .req_add("title", StrValidator::new().build())
82//!         .req_add("author", StrValidator::new().build())
83//!         .opt_add("link", StrValidator::new().build())
84//!         .build()
85//!     )
86//!     .entry_add("post", MapValidator::new()
87//!         .req_add("created", TimeValidator::new().query(true).ord(true).build())
88//!         .opt_add("title", StrValidator::new().query(true).regex(true).build())
89//!         .req_add("content", StrValidator::new().build())
90//!         .build(),
91//!         None
92//!     )
93//!     .build()
94//!     .unwrap();
95//! // For actual use, we'll turn the schema document into a Schema
96//! let schema = Schema::from_doc(&schema_doc)?;
97//! #
98//! # Ok(())
99//! # }
100//! ```
101//!
102//! Now that we have our schema and structs, we can make a new blog and make
103//! posts to it. We'll sign everything with a cryptographic key, so people can
104//! know we're the ones making these posts. This is also what will make the Blog
105//! document completely unique to us, as a signed document will have a hash that
106//! includes the signature.
107//!
108//! We can even make a query that can be used to search for specific posts!
109//!
110//! ```
111//! # use fog_pack::{
112//! #     validator::*,
113//! #     schema::*,
114//! #     document::*,
115//! #     entry::*,
116//! #     query::*,
117//! #     types::*,
118//! # };
119//! # use serde::{Serialize, Deserialize};
120//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
121//! # #[derive(Serialize, Deserialize)]
122//! # struct Blog {
123//! #     title: String,
124//! #     author: String,
125//! #     #[serde(skip_serializing_if = "Option::is_none")]
126//! #     link: Option<String>,
127//! # }
128//! # #[derive(Serialize, Deserialize)]
129//! # struct Post {
130//! #     created: Timestamp,
131//! #     content: String,
132//! #     #[serde(skip_serializing_if = "Option::is_none")]
133//! #     title: Option<String>,
134//! # }
135//! # let schema_doc = SchemaBuilder::new(MapValidator::new()
136//! #         .req_add("title", StrValidator::new().build())
137//! #         .req_add("author", StrValidator::new().build())
138//! #         .opt_add("link", StrValidator::new().build())
139//! #         .build()
140//! #     )
141//! #     .entry_add("post", MapValidator::new()
142//! #         .req_add("created", TimeValidator::new().query(true).ord(true).build())
143//! #         .opt_add("title", StrValidator::new().query(true).regex(true).build())
144//! #         .req_add("content", StrValidator::new().build())
145//! #         .map_ok(true)
146//! #         .build(),
147//! #         None
148//! #     )
149//! #     .build()
150//! #     .unwrap();
151//! # let schema = Schema::from_doc(&schema_doc)?;
152//!
153//! // Brand new blog time!
154//! let my_key = fog_crypto::identity::IdentityKey::new();
155//! let my_blog = Blog {
156//!     title: "Rusted Gears: A programming blog".into(),
157//!     author: "ElectricCogs".into(),
158//!     link: Some("https://cognoscan.github.io/".into()),
159//! };
160//! let my_blog = NewDocument::new(Some(schema.hash()), my_blog)?.sign(&my_key)?;
161//! let my_blog = schema.validate_new_doc(my_blog)?;
162//!
163//! // First post!
164//! let new_post = Post {
165//!     created: Timestamp::now(),
166//!     title: Some("My first post".into()),
167//!     content: "I'm making my first post using fog-pack!".into(),
168//! };
169//! let new_post = NewEntry::new("post", &my_blog, new_post)?.sign(&my_key)?;
170//! let new_post = schema.validate_new_entry(new_post)?.complete()?;
171//!
172//! // We can find entries using a Query:
173//! let query = NewQuery::new("post", MapValidator::new()
174//!     .req_add("title", StrValidator::new().in_add("My first post").build())
175//!     .build()
176//! );
177//!
178//! // To complete serialization of all these structs, we need to pass them through the schema one
179//! // more time:
180//! let (blog_hash, encoded_blog): (Hash, Vec<u8>) =
181//!     schema.encode_doc(my_blog)?;
182//! let (post_ref, encoded_post, _): (EntryRef, Vec<u8>, Vec<Hash>) =
183//!     schema.encode_entry(new_post)?;
184//! let encoded_query =
185//!     schema.encode_query(query)?;
186//!
187//! // Decoding is also done via the schema:
188//! let my_blog = schema.decode_doc(encoded_blog)?;
189//! let new_post = schema.decode_entry(encoded_post, &post_ref.key, &my_blog)?;
190//! let query = schema.decode_query(encoded_query)?;
191//!
192//! # Ok(())
193//! # }
194//! ```
195//!
196
197#![warn(missing_docs)]
198
199mod compress;
200mod de;
201mod depth_tracking;
202mod element;
203mod integer;
204mod marker;
205mod ser;
206mod timestamp;
207mod utils;
208mod value;
209mod value_ref;
210
211pub mod document;
212pub mod entry;
213pub mod error;
214pub mod query;
215pub mod schema;
216pub mod validator;
217
218use types::*;
219use utils::*;
220pub mod types {
221    //! Various fog-pack content types.
222    //!
223    //! The fog-pack serialization format has a number of special types, in addition to the usual
224    //! primitive types:
225    //!
226    //! - Null
227    //! - Bool
228    //! - [`Int`][crate::types::Integer] - any integer from -2^63 to 2^64-1
229    //! - F32 - 32-bit floating point
230    //! - F64 - 64-bit floating point
231    //! - Bin - Binary byte sequences
232    //! - Str - UTF-8 strings
233    //! - Array - heterogeneous sequence of values
234    //! - Map - Ordered key-value map, with strings for keys
235    //! - [`Time`][Timestamp] - a unix timestamp
236    //! - [`struct@Hash`] - a cryptographic hash
237    //! - [`Identity`][crate::types::Identity] - a public signing key
238    //! - [`IdentityKey`][crate::types::IdentityKey] - a private signing key
239    //! - [`BareIdKey`][crate::types::BareIdKey] - a private signing key that can be serialized
240    //! - [`StreamId`][crate::types::StreamId] - an identifier for a symmetric encryption key
241    //! - [`StreamKey`][crate::types::StreamKey] - a symmetric encryption key, for
242    //!     encryption & decryption of lockboxes.
243    //! - [`LockId`][crate::types::LockId] - a public key for encryption of lockboxes.
244    //! - [`LockKey`][crate::types::LockKey] - a private key for decryption of lockboxes.
245    //! - [`DataLockbox`][crate::types::DataLockbox] - Encrypted general data
246    //! - [`IdentityLockbox`][crate::types::IdentityLockbox] - An encrypted private signing key
247    //! - [`StreamLockbox`][crate::types::StreamLockbox] - An encrypted key for symmetric encryption
248    //! - [`LockLockbox`][crate::types::LockLockbox] - An encrypted private key
249    //!
250    //! A general structure for holding fog-pack data is [`Value`][crate::types::Value]. The non-owning
251    //! version of it is [`ValueRef`][crate::types::ValueRef].
252    //!
253    pub use crate::integer::*;
254    pub use crate::timestamp::*;
255    pub use crate::value::Value;
256    pub use crate::value_ref::ValueRef;
257    pub use fog_crypto::{
258        hash::Hash,
259        identity::{BareIdKey, Identity, IdentityKey},
260        lock::{LockId, LockKey},
261        lockbox::{
262            DataLockbox, DataLockboxRef, IdentityLockbox, IdentityLockboxRef, LockLockbox,
263            LockLockboxRef, StreamLockbox, StreamLockboxRef,
264        },
265        stream::{StreamId, StreamKey},
266    };
267}
268
269/// The maximum nesting depth allowed for any fog-pack value is 200. No encoded
270/// document will ever nest Map/Array markers deeper than this.
271pub const MAX_DEPTH: usize = 200;
272/// The maximum allowed size of a raw document, including signatures, is 1 MiB
273/// (2^20-1 = 1048575).  No encoded document will ever be equal to or larger
274/// than this size.
275pub const MAX_DOC_SIZE: usize = (1usize << 20) - 1; // 1 MiB
276/// The maximum allowed size of a raw entry, including signatures, is 64 kiB
277/// (65535 bytes). No encoded entry will ever be equal to or larger than this
278/// size.
279pub const MAX_ENTRY_SIZE: usize = (1usize << 16) - 1; // 64 kiB
280/// The maximum allowed size of a raw query, is 64 kiB (65535 bytes). No encoded
281/// query will ever be equal to or larger than this size.
282pub const MAX_QUERY_SIZE: usize = (1usize << 16) - 1; // 64 kiB