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