sync15/bso/mod.rs
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
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/// This module defines our core "bso" abstractions.
/// In the terminology of this crate:
/// * "bso" is an acronym for "basic storage object" and used extensively in the sync server docs.
/// the record always has a well-defined "envelope" with metadata (eg, the ID of the record,
/// the server timestamp of the resource, etc) and a field called `payload`.
/// A bso is serialized to and from JSON.
/// * There's a "cleartext" bso:
/// * The payload is a String, which itself is JSON encoded (ie, this string `payload` is
/// always double JSON encoded in a server record)
/// * This supplies helper methods for working with the "content" (some arbitrary <T>) in the
/// payload.
/// * There's an "encrypted" bso
/// * The payload is an [crate::enc_payload::EncryptedPayload]
/// * Only clients use this; as soon as practical we decrypt and as late as practical we encrypt
/// to and from encrypted bsos.
/// * The encrypted bsos etc are all in the [crypto] module and require the `crypto` feature.
///
/// Let's look at some real-world examples:
/// # meta/global
/// A "bso" (ie, record with an "envelope" and a "payload" with a JSON string) - but the payload
/// is cleartext.
/// ```json
/// {
/// "id":"global",
/// "modified":1661564513.50,
/// "payload": "{\"syncID\":\"p1z5_oDdOfLF\",\"storageVersion\":5,\"engines\":{\"passwords\":{\"version\":1,\"syncID\":\"6Y6JJkB074cF\"} /* snip */},\"declined\":[]}"
/// }```
///
/// # encrypted bsos:
/// Encrypted BSOs are still a "bso" (ie, a record with a field names `payload` which is a string)
/// but the payload is in the form of an EncryptedPayload.
/// For example, crypto/keys:
/// ```json
/// {
/// "id":"keys",
/// "modified":1661564513.74,
/// "payload":"{\"IV\":\"snip-base-64==\",\"hmac\":\"snip-hex\",\"ciphertext\":\"snip-base64==\"}"
/// }```
/// (Note that as described above, most code working with bsos *do not* use that `payload`
/// directly, but instead a decrypted cleartext bso.
///
/// Note all collection responses are the same shape as `crypto/keys` - a `payload` field with a
/// JSON serialized EncryptedPayload, it's just that the final <T> content differs for each
/// collection (eg, tabs and bookmarks have quite different <T>s JSON-encoded in the
/// String payload.)
///
/// For completeness, some other "non-BSO" records - no "id", "modified" or "payload" fields in
/// the response, just plain-old clear-text JSON.
/// # Example
/// ## `info/collections`
/// ```json
/// {
/// "bookmarks":1661564648.65,
/// "meta":1661564513.50,
/// "addons":1661564649.09,
/// "clients":1661564643.57,
/// ...
/// }```
/// ## `info/configuration`
/// ```json
/// {
/// "max_post_bytes":2097152,
/// "max_post_records":100,
/// "max_record_payload_bytes":2097152,
/// ...
/// }```
///
/// Given our definitions above, these are not any kind of "bso", so are
/// not relevant to this module
use crate::{Guid, ServerTimestamp};
use serde::{Deserialize, Serialize};
#[cfg(feature = "crypto")]
mod crypto;
#[cfg(feature = "crypto")]
pub use crypto::{IncomingEncryptedBso, OutgoingEncryptedBso};
mod content;
// A feature for this would be ideal, but (a) the module is small and (b) it
// doesn't really fit the "features" model for sync15 to have a dev-dependency
// against itself but with a different feature set.
pub mod test_utils;
/// An envelope for an incoming item. Envelopes carry all the metadata for
/// a Sync BSO record (`id`, `modified`, `sortindex`), *but not* the payload
/// itself.
#[derive(Debug, Clone, Deserialize)]
pub struct IncomingEnvelope {
/// The ID of the record.
pub id: Guid,
// If we don't give it a default, a small handful of tests fail.
// XXX - we should probably fix the tests and kill this?
#[serde(default = "ServerTimestamp::default")]
pub modified: ServerTimestamp,
pub sortindex: Option<i32>,
pub ttl: Option<u32>,
}
/// An envelope for an outgoing item. This is conceptually identical to
/// [IncomingEnvelope], but omits fields that are only set by the server,
/// like `modified`.
#[derive(Debug, Default, Clone, Serialize)]
pub struct OutgoingEnvelope {
/// The ID of the record.
pub id: Guid,
#[serde(skip_serializing_if = "Option::is_none")]
pub sortindex: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ttl: Option<u32>,
}
/// Allow an outgoing envelope to be constructed with just a guid when default
/// values for the other fields are OK.
impl From<Guid> for OutgoingEnvelope {
fn from(id: Guid) -> Self {
OutgoingEnvelope {
id,
..Default::default()
}
}
}
/// IncomingBso's can come from:
/// * Directly from the server (ie, some records aren't encrypted, such as meta/global)
/// * From environments where the encryption is done externally (eg, Rust syncing in Desktop
/// Firefox has the encryption/decryption done by Firefox and the cleartext BSOs are passed in.
/// * Read from the server as an EncryptedBso; see EncryptedBso description above.
#[derive(Deserialize, Debug)]
pub struct IncomingBso {
#[serde(flatten)]
pub envelope: IncomingEnvelope,
// payload is public for some edge-cases in some components, but in general,
// you should use into_content<> to get a record out of it.
pub payload: String,
}
impl IncomingBso {
pub fn new(envelope: IncomingEnvelope, payload: String) -> Self {
Self { envelope, payload }
}
}
#[derive(Serialize, Debug)]
pub struct OutgoingBso {
#[serde(flatten)]
pub envelope: OutgoingEnvelope,
// payload is public for some edge-cases in some components, but in general,
// you should use into_content<> to get a record out of it.
pub payload: String,
}
impl OutgoingBso {
/// Most consumers will use `self.from_content` and `self.from_content_with_id`
/// but this exists for the few consumers for whom that doesn't make sense.
pub fn new<T: Serialize>(
envelope: OutgoingEnvelope,
val: &T,
) -> Result<Self, serde_json::Error> {
Ok(Self {
envelope,
payload: serde_json::to_string(&val)?,
})
}
}
/// We also have the concept of "content", which helps work with a `T` which
/// is represented inside the payload. Real-world examples of a `T` include
/// Bookmarks or Tabs.
/// See the content module for the implementations.
///
/// So this all flows together in the following way:
/// * Incoming encrypted data:
/// EncryptedIncomingBso -> IncomingBso -> [specific engine] -> IncomingContent<T>
/// * Incoming cleartext data:
/// IncomingBso -> IncomingContent<T>
/// (Note that incoming cleartext only happens for a few collections managed by
/// the sync client and never by specific engines - engine BSOs are always encryted)
/// * Outgoing encrypted data:
/// OutgoingBso (created in the engine) -> [this crate] -> EncryptedOutgoingBso
/// * Outgoing cleartext data: just an OutgoingBso with no conversions needed.
/// [IncomingContent] is the result of converting an [IncomingBso] into
/// some <T> - it consumes the Bso, so you get the envelope, and the [IncomingKind]
/// which reflects the state of parsing the json.
#[derive(Debug)]
pub struct IncomingContent<T> {
pub envelope: IncomingEnvelope,
pub kind: IncomingKind<T>,
}
/// The "kind" of incoming content after deserializing it.
pub enum IncomingKind<T> {
/// A good, live T.
Content(T),
/// A record that used to be a T but has been replaced with a tombstone.
Tombstone,
/// Either not JSON, or can't be made into a T.
Malformed,
}