Import upstream v0.16.22, stripped

Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f
Enterprise-only files removed or emptied: 63
Enterprise-only snippets removed: 117 in 50 files
Dangling module declarations removed: 5
Cargo edits turning enterprise off: 14
Verification: clean
Enterprise feature gates left for rebuilt features: 19 in 18 files

Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
2026-09-18 10:21:56 -07:00
commit 7dae9b29fd
1650 changed files with 485521 additions and 0 deletions
+88
View File
@@ -0,0 +1,88 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::{Archive, ArchiveVersion};
use crate::{U32_LEN, U64_LEN};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AssertValue {
U32(u32),
U64(u64),
Hash(u64),
Archive(ArchiveVersion),
Some,
None,
}
pub trait ToAssertValue {
fn to_assert_value(&self) -> AssertValue;
}
impl ToAssertValue for AssertValue {
fn to_assert_value(&self) -> AssertValue {
*self
}
}
impl ToAssertValue for () {
fn to_assert_value(&self) -> AssertValue {
AssertValue::None
}
}
impl ToAssertValue for u64 {
fn to_assert_value(&self) -> AssertValue {
AssertValue::U64(*self)
}
}
impl ToAssertValue for u32 {
fn to_assert_value(&self) -> AssertValue {
AssertValue::U32(*self)
}
}
impl<T> ToAssertValue for Archive<T> {
fn to_assert_value(&self) -> AssertValue {
AssertValue::Archive(self.version)
}
}
impl<T> ToAssertValue for &Archive<T> {
fn to_assert_value(&self) -> AssertValue {
AssertValue::Archive(self.version)
}
}
impl AssertValue {
pub fn matches(&self, bytes: &[u8]) -> bool {
match self {
AssertValue::U32(v) => bytes
.get(bytes.len() - U32_LEN..)
.is_some_and(|b| b == v.to_be_bytes()),
AssertValue::U64(v) => bytes
.get(bytes.len() - U64_LEN..)
.is_some_and(|b| b == v.to_be_bytes()),
AssertValue::Hash(v) => xxhash_rust::xxh3::xxh3_64(bytes) == *v,
AssertValue::Archive(v) => match v {
ArchiveVersion::Versioned { hash, .. } => bytes
.get(bytes.len() - U32_LEN - U64_LEN - 1..bytes.len() - U64_LEN - 1)
.is_some_and(|b| b == hash.to_be_bytes()),
ArchiveVersion::Hashed { hash } => bytes
.get(bytes.len() - U32_LEN - 1..bytes.len() - 1)
.is_some_and(|b| b == hash.to_be_bytes()),
ArchiveVersion::Unversioned => false,
},
AssertValue::None => false,
AssertValue::Some => true,
}
}
pub fn is_none(&self) -> bool {
matches!(self, AssertValue::None)
}
}
+556
View File
@@ -0,0 +1,556 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::{
Batch, BatchBuilder, ChangedCollection, IntoOperations, Operation, ValueClass, ValueOp,
assert::ToAssertValue, log::VanishedItem,
};
use crate::{
SerializeInfallible, U32_LEN,
write::{
LogCollection, MergeFnc, MergeOperation, Params, SetFnc, SetOperation, TaskQueueClass,
},
};
use registry::{
schema::structs::Task,
types::{EnumImpl, ObjectImpl},
};
use types::{
collection::{Collection, SyncCollection, VanishedCollection},
field::FieldType,
};
use utils::{map::vec_map::VecMap, snowflake::SnowflakeIdGenerator};
impl BatchBuilder {
pub fn new() -> Self {
Self {
ops: Vec::with_capacity(32),
current_account_id: None,
current_collection: None,
current_document_id: None,
changes: Default::default(),
changed_collections: Default::default(),
batch_size: 0,
batch_ops: 0,
has_assertions: false,
commit_points: Vec::new(),
}
}
pub fn with_account_id(&mut self, account_id: u32) -> &mut Self {
if self
.current_account_id
.is_none_or(|current_account_id| current_account_id != account_id)
{
self.current_account_id = account_id.into();
self.ops.push(Operation::AccountId { account_id });
}
self
}
pub fn with_collection(&mut self, collection: Collection) -> &mut Self {
let collection_ = Some(collection);
if collection_ != self.current_collection {
self.current_collection = collection_;
self.ops.push(Operation::Collection { collection });
}
self
}
pub fn with_document(&mut self, document_id: u32) -> &mut Self {
self.ops.push(Operation::DocumentId { document_id });
self.current_document_id = Some(document_id);
self.has_assertions = false;
self
}
pub fn assert_value(
&mut self,
class: impl Into<ValueClass>,
value: impl ToAssertValue,
) -> &mut Self {
self.ops.push(Operation::AssertValue {
class: class.into(),
assert_value: value.to_assert_value(),
});
self.batch_ops += 1;
self.has_assertions = true;
self
}
pub fn index(&mut self, field: impl FieldType, value: impl Into<Vec<u8>>) -> &mut Self {
let field = field.into();
let value = value.into();
let value_len = value.len();
self.ops.push(Operation::Index {
field,
key: value,
set: true,
});
self.batch_size += (U32_LEN * 3) + value_len;
self.batch_ops += 1;
self
}
pub fn unindex(&mut self, field: impl FieldType, value: impl Into<Vec<u8>>) -> &mut Self {
let field = field.into();
let value = value.into();
let value_len = value.len();
self.ops.push(Operation::Index {
field,
key: value,
set: false,
});
self.batch_size += (U32_LEN * 3) + value_len;
self.batch_ops += 1;
self
}
#[inline(always)]
pub fn tag(&mut self, field: impl FieldType) -> &mut Self {
self.index(field, vec![])
}
#[inline(always)]
pub fn untag(&mut self, field: impl FieldType) -> &mut Self {
self.unindex(field, vec![])
}
pub fn add(&mut self, class: impl Into<ValueClass>, value: i64) -> &mut Self {
let class = class.into();
self.batch_size += class.serialized_size() + std::mem::size_of::<i64>();
self.ops.push(Operation::Value {
class,
op: ValueOp::AtomicAdd(value),
});
self.batch_ops += 1;
self
}
pub fn add_and_get(&mut self, class: impl Into<ValueClass>, value: i64) -> &mut Self {
let class = class.into();
self.batch_size += class.serialized_size() + (std::mem::size_of::<i64>() * 2);
self.ops.push(Operation::Value {
class,
op: ValueOp::AddAndGet(value),
});
self.batch_ops += 1;
self
}
pub fn set(&mut self, class: impl Into<ValueClass>, value: impl Into<Vec<u8>>) -> &mut Self {
let class = class.into();
let value = value.into();
self.batch_size += class.serialized_size() + value.len();
self.ops.push(Operation::Value {
class,
op: ValueOp::Set(value),
});
self.batch_ops += 1;
self
}
pub fn set_fnc(
&mut self,
class: impl Into<ValueClass>,
params: Params,
fnc: SetFnc,
) -> &mut Self {
self.ops.push(Operation::Value {
class: class.into(),
op: ValueOp::SetFnc(SetOperation { fnc, params }),
});
self
}
pub fn merge_fnc(
&mut self,
class: impl Into<ValueClass>,
params: Params,
fnc: MergeFnc,
) -> &mut Self {
self.ops.push(Operation::Value {
class: class.into(),
op: ValueOp::MergeFnc(MergeOperation { fnc, params }),
});
self
}
pub fn clear(&mut self, class: impl Into<ValueClass>) -> &mut Self {
let class = class.into();
self.batch_size += class.serialized_size();
self.ops.push(Operation::Value {
class,
op: ValueOp::Clear,
});
self.batch_ops += 1;
self
}
pub fn acl_grant(&mut self, grant_account_id: u32, op: Vec<u8>) -> &mut Self {
self.batch_size += (U32_LEN * 3) + op.len();
self.ops.push(Operation::Value {
class: ValueClass::Acl(grant_account_id),
op: ValueOp::Set(op),
});
self.batch_ops += 1;
self
}
pub fn acl_revoke(&mut self, grant_account_id: u32) -> &mut Self {
self.batch_size += U32_LEN * 3;
self.ops.push(Operation::Value {
class: ValueClass::Acl(grant_account_id),
op: ValueOp::Clear,
});
self.batch_ops += 1;
self
}
pub fn log_item_insert(
&mut self,
collection: SyncCollection,
prefix: Option<u32>,
) -> &mut Self {
if let (Some(account_id), Some(document_id)) =
(self.current_account_id, self.current_document_id)
{
self.changes.get_mut_or_insert(account_id).log_item_insert(
collection,
prefix,
document_id,
);
}
self
}
pub fn log_item_update(
&mut self,
collection: SyncCollection,
prefix: Option<u32>,
) -> &mut Self {
if let (Some(account_id), Some(document_id)) =
(self.current_account_id, self.current_document_id)
{
self.changes.get_mut_or_insert(account_id).log_item_update(
collection,
prefix,
document_id,
);
}
self
}
pub fn log_item_delete(
&mut self,
collection: SyncCollection,
prefix: Option<u32>,
) -> &mut Self {
if let (Some(account_id), Some(document_id)) =
(self.current_account_id, self.current_document_id)
{
self.changes.get_mut_or_insert(account_id).log_item_delete(
collection,
prefix,
document_id,
);
}
self
}
pub fn log_container_insert(&mut self, collection: SyncCollection) -> &mut Self {
if let (Some(account_id), Some(document_id)) =
(self.current_account_id, self.current_document_id)
{
self.changes
.get_mut_or_insert(account_id)
.log_container_insert(collection, document_id);
}
self
}
pub fn log_container_update(&mut self, collection: SyncCollection) -> &mut Self {
if let (Some(account_id), Some(document_id)) =
(self.current_account_id, self.current_document_id)
{
self.changes
.get_mut_or_insert(account_id)
.log_container_update(collection, document_id);
}
self
}
pub fn log_container_delete(&mut self, collection: SyncCollection) -> &mut Self {
if let (Some(account_id), Some(document_id)) =
(self.current_account_id, self.current_document_id)
{
self.changes
.get_mut_or_insert(account_id)
.log_container_delete(collection, document_id);
}
self
}
pub fn log_container_property_change(
&mut self,
collection: SyncCollection,
document_id: u32,
) -> &mut Self {
if let Some(account_id) = self.current_account_id {
self.changes
.get_mut_or_insert(account_id)
.log_container_property_update(collection, document_id);
}
self
}
pub fn log_vanished_item(
&mut self,
collection: VanishedCollection,
item: impl Into<VanishedItem>,
) -> &mut Self {
if let Some(account_id) = self.current_account_id {
let item = item.into();
self.batch_size += item.serialized_size();
self.changes
.get_mut_or_insert(account_id)
.log_vanished_item(collection, item);
}
self
}
pub fn log_share_notification(
&mut self,
notification_id: u64,
notify_account_id: u32,
value: impl SerializeInfallible,
) -> &mut Self {
self.changed_collections
.get_mut_or_insert(notify_account_id)
.share_notification_id = Some(notification_id);
self.set(
ValueClass::ShareNotification {
notification_id,
notify_account_id,
},
value.serialize(),
)
}
fn serialize_changes(&mut self) {
if !self.changes.is_empty() {
for (account_id, changelog) in std::mem::take(&mut self.changes) {
self.with_account_id(account_id);
// Serialize changes
for (collection, changes) in changelog.changes.into_iter() {
let cc = self.changed_collections.get_mut_or_insert(account_id);
if changes.has_container_changes() {
cc.changed_containers.insert(collection);
}
if changes.has_item_changes() {
cc.changed_items.insert(collection);
}
self.ops.push(Operation::Log {
collection: LogCollection::Sync(collection),
set: changes.serialize(),
});
}
// Serialize vanished items
for (collection, vanished) in changelog.vanished.into_iter() {
self.ops.push(Operation::Log {
collection: LogCollection::Vanished(collection),
set: vanished.serialize(),
});
}
}
}
}
pub fn commit_point(&mut self) -> &mut Self {
if self.is_large_batch() {
self.serialize_changes();
self.commit_points.push(self.ops.len());
self.batch_ops = 0;
self.batch_size = 0;
if let Some(account_id) = self.current_account_id {
self.ops.push(Operation::AccountId { account_id });
}
if let Some(collection) = self.current_collection {
self.ops.push(Operation::Collection { collection });
}
}
self
}
#[inline]
pub fn is_large_batch(&self) -> bool {
self.batch_size > 5_000_000 || self.batch_ops > 1000
}
pub fn any_op(&mut self, op: Operation) -> &mut Self {
if let Operation::Value { class, op } = &op {
self.batch_size += class.serialized_size();
if let ValueOp::Set(value) = op {
self.batch_size += value.len();
}
}
self.ops.push(op);
self.batch_ops += 1;
self
}
pub fn custom(&mut self, value: impl IntoOperations) -> trc::Result<&mut Self> {
value.build(self)?;
Ok(self)
}
pub fn last_account_id(&self) -> Option<u32> {
self.current_account_id
}
pub fn last_collection(&self) -> Option<Collection> {
self.current_collection
}
pub fn last_document_id(&self) -> Option<u32> {
self.current_document_id
}
pub fn commit_points(&mut self) -> CommitPointIterator {
self.serialize_changes();
CommitPointIterator {
commit_points: std::mem::take(&mut self.commit_points),
commit_point_last: self.ops.len(),
offset_start: 0,
}
}
pub fn build_one(&mut self, commit_point: CommitPoint) -> Batch<'_> {
Batch {
changes: &self.changed_collections,
ops: &mut self.ops[commit_point.offset_start..commit_point.offset_end],
}
}
pub fn build_all(&mut self) -> Batch<'_> {
self.serialize_changes();
Batch {
changes: &self.changed_collections,
ops: self.ops.as_mut_slice(),
}
}
pub fn changes(self) -> Option<VecMap<u32, ChangedCollection>> {
if self.has_changes() {
Some(self.changed_collections)
} else {
None
}
}
pub fn has_changes(&self) -> bool {
!self.changed_collections.is_empty()
}
pub fn ops(&self) -> &[Operation] {
self.ops.as_slice()
}
pub fn len(&self) -> usize {
self.batch_size
}
pub fn is_empty(&self) -> bool {
self.batch_ops == 0
}
pub fn schedule_task(&mut self, task: Task) -> &mut Self {
let due = task.due_timestamp();
let class = task.object_type().to_id();
let task = task.to_pickled_vec();
let id = SnowflakeIdGenerator::global_id().unwrap_or_default();
self.set(ValueClass::TaskQueue(TaskQueueClass::Task { id }), task)
.set(
ValueClass::TaskQueue(TaskQueueClass::Due { id, due }),
class.serialize(),
)
}
pub fn schedule_task_with_id(&mut self, id: u64, task: Task) -> &mut Self {
let due = task.due_timestamp();
let class = task.object_type().to_id();
let task = task.to_pickled_vec();
self.set(ValueClass::TaskQueue(TaskQueueClass::Task { id }), task)
.set(
ValueClass::TaskQueue(TaskQueueClass::Due { id, due }),
class.serialize(),
)
}
}
pub struct CommitPointIterator {
commit_points: Vec<usize>,
commit_point_last: usize,
offset_start: usize,
}
pub struct CommitPoint {
pub offset_start: usize,
pub offset_end: usize,
}
impl CommitPointIterator {
pub fn iter(&mut self) -> impl Iterator<Item = CommitPoint> {
self.commit_points
.iter()
.copied()
.chain([self.commit_point_last])
.map(|offset_end| {
let point = CommitPoint {
offset_start: self.offset_start,
offset_end,
};
self.offset_start = offset_end;
point
})
}
}
impl Batch<'_> {
pub fn is_atomic(&self) -> bool {
!self.ops.iter().any(|op| {
matches!(
op,
Operation::AssertValue { .. }
| Operation::Value {
op: ValueOp::AddAndGet(_),
..
}
)
})
}
pub fn first_account_id(&self) -> Option<u32> {
self.ops.iter().find_map(|op| match op {
Operation::AccountId { account_id } => Some(*account_id),
_ => None,
})
}
}
impl Default for BatchBuilder {
fn default() -> Self {
Self::new()
}
}
+382
View File
@@ -0,0 +1,382 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use bitpacking::{BitPacker, BitPacker1x, BitPacker4x, BitPacker8x};
use utils::codec::leb128::Leb128Reader;
use super::key::KeySerializer;
#[derive(Default)]
pub struct BitpackIterator<'x> {
pub(crate) bytes: &'x [u8],
pub(crate) bytes_offset: usize,
pub(crate) chunk: Vec<u32>,
pub(crate) chunk_offset: usize,
pub items_left: u32,
}
#[derive(Clone, Copy)]
pub(crate) struct BitBlockPacker {
bitpacker_1: BitPacker1x,
bitpacker_4: BitPacker4x,
bitpacker_8: BitPacker8x,
block_len: usize,
}
impl KeySerializer {
pub fn bitpack_sorted(self, items: &[u32]) -> Self {
let mut serializer = self;
let mut bitpacker = BitBlockPacker::new();
let mut compressed = vec![0u8; 4 * BitPacker8x::BLOCK_LEN];
let mut pos = 0;
let len = items.len();
let mut initial_value = None;
serializer = serializer.write_leb128(len as u32);
while pos < len {
let block_len = match len - pos {
0..=31 => {
for val in &items[pos..] {
serializer = serializer.write_leb128(*val);
}
break;
}
32..=127 => BitPacker1x::BLOCK_LEN,
128..=255 => BitPacker4x::BLOCK_LEN,
_ => BitPacker8x::BLOCK_LEN,
};
let chunk = &items[pos..pos + block_len];
bitpacker.block_len(block_len);
let num_bits: u8 = bitpacker.num_bits_strictly_sorted(initial_value, chunk);
let compressed_len = bitpacker.compress_strictly_sorted(
initial_value,
chunk,
&mut compressed[..],
num_bits,
);
serializer = serializer
.write(num_bits)
.write(&compressed[..compressed_len]);
initial_value = chunk[chunk.len() - 1].into();
pos += block_len;
}
serializer
}
}
impl<'x> BitpackIterator<'x> {
pub fn from_bytes_and_offset(bytes: &'x [u8], bytes_offset: usize, items_left: u32) -> Self {
BitpackIterator {
bytes,
bytes_offset,
items_left,
..Default::default()
}
}
pub fn new(bytes: &'x [u8]) -> Option<Self> {
bytes
.read_leb128::<u32>()
.map(|(items_left, bytes_offset)| BitpackIterator {
bytes,
bytes_offset,
items_left,
..Default::default()
})
}
}
impl Iterator for BitpackIterator<'_> {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if let Some(item) = self.chunk.get(self.chunk_offset) {
self.chunk_offset += 1;
return Some(*item);
}
let block_len = match self.items_left {
0 => return None,
1..=31 => {
self.items_left -= 1;
let (item, bytes_read) = self.bytes.get(self.bytes_offset..)?.read_leb128()?;
self.bytes_offset += bytes_read;
return Some(item);
}
32..=127 => BitPacker1x::BLOCK_LEN,
128..=255 => BitPacker4x::BLOCK_LEN,
_ => BitPacker8x::BLOCK_LEN,
};
let bitpacker = BitBlockPacker::with_block_len(block_len);
let num_bits = *self.bytes.get(self.bytes_offset)?;
let bytes_read = ((num_bits as usize) * block_len / 8) + 1;
let initial_value = self.chunk.last().copied();
self.chunk = vec![0u32; block_len];
self.chunk_offset = 1;
bitpacker.decompress_strictly_sorted(
initial_value,
self.bytes
.get(self.bytes_offset + 1..self.bytes_offset + bytes_read)?,
&mut self.chunk[..],
num_bits,
);
self.bytes_offset += bytes_read;
self.items_left -= block_len as u32;
self.chunk.first().copied()
}
}
impl BitBlockPacker {
pub fn with_block_len(block_len: usize) -> Self {
BitBlockPacker {
bitpacker_1: BitPacker1x::new(),
bitpacker_4: BitPacker4x::new(),
bitpacker_8: BitPacker8x::new(),
block_len,
}
}
pub fn block_len(&mut self, num: usize) {
self.block_len = num;
}
}
impl BitPacker for BitBlockPacker {
const BLOCK_LEN: usize = 0;
fn new() -> Self {
BitBlockPacker {
bitpacker_1: BitPacker1x::new(),
bitpacker_4: BitPacker4x::new(),
bitpacker_8: BitPacker8x::new(),
block_len: 1,
}
}
fn compress(&self, decompressed: &[u32], compressed: &mut [u8], num_bits: u8) -> usize {
match self.block_len {
BitPacker8x::BLOCK_LEN => self
.bitpacker_8
.compress(decompressed, compressed, num_bits),
BitPacker4x::BLOCK_LEN => self
.bitpacker_4
.compress(decompressed, compressed, num_bits),
_ => self
.bitpacker_1
.compress(decompressed, compressed, num_bits),
}
}
fn compress_sorted(
&self,
initial: u32,
decompressed: &[u32],
compressed: &mut [u8],
num_bits: u8,
) -> usize {
match self.block_len {
BitPacker8x::BLOCK_LEN => {
self.bitpacker_8
.compress_sorted(initial, decompressed, compressed, num_bits)
}
BitPacker4x::BLOCK_LEN => {
self.bitpacker_4
.compress_sorted(initial, decompressed, compressed, num_bits)
}
_ => self
.bitpacker_1
.compress_sorted(initial, decompressed, compressed, num_bits),
}
}
fn decompress(&self, compressed: &[u8], decompressed: &mut [u32], num_bits: u8) -> usize {
match self.block_len {
BitPacker8x::BLOCK_LEN => {
self.bitpacker_8
.decompress(compressed, decompressed, num_bits)
}
BitPacker4x::BLOCK_LEN => {
self.bitpacker_4
.decompress(compressed, decompressed, num_bits)
}
_ => self
.bitpacker_1
.decompress(compressed, decompressed, num_bits),
}
}
fn decompress_sorted(
&self,
initial: u32,
compressed: &[u8],
decompressed: &mut [u32],
num_bits: u8,
) -> usize {
match self.block_len {
BitPacker8x::BLOCK_LEN => {
self.bitpacker_8
.decompress_sorted(initial, compressed, decompressed, num_bits)
}
BitPacker4x::BLOCK_LEN => {
self.bitpacker_4
.decompress_sorted(initial, compressed, decompressed, num_bits)
}
_ => self
.bitpacker_1
.decompress_sorted(initial, compressed, decompressed, num_bits),
}
}
fn num_bits(&self, decompressed: &[u32]) -> u8 {
match self.block_len {
BitPacker8x::BLOCK_LEN => self.bitpacker_8.num_bits(decompressed),
BitPacker4x::BLOCK_LEN => self.bitpacker_4.num_bits(decompressed),
_ => self.bitpacker_1.num_bits(decompressed),
}
}
fn num_bits_sorted(&self, initial: u32, decompressed: &[u32]) -> u8 {
match self.block_len {
BitPacker8x::BLOCK_LEN => self.bitpacker_8.num_bits_sorted(initial, decompressed),
BitPacker4x::BLOCK_LEN => self.bitpacker_4.num_bits_sorted(initial, decompressed),
_ => self.bitpacker_1.num_bits_sorted(initial, decompressed),
}
}
fn compress_strictly_sorted(
&self,
initial: Option<u32>,
decompressed: &[u32],
compressed: &mut [u8],
num_bits: u8,
) -> usize {
match self.block_len {
BitPacker8x::BLOCK_LEN => self.bitpacker_8.compress_strictly_sorted(
initial,
decompressed,
compressed,
num_bits,
),
BitPacker4x::BLOCK_LEN => self.bitpacker_4.compress_strictly_sorted(
initial,
decompressed,
compressed,
num_bits,
),
_ => self.bitpacker_1.compress_strictly_sorted(
initial,
decompressed,
compressed,
num_bits,
),
}
}
fn decompress_strictly_sorted(
&self,
initial: Option<u32>,
compressed: &[u8],
decompressed: &mut [u32],
num_bits: u8,
) -> usize {
match self.block_len {
BitPacker8x::BLOCK_LEN => self.bitpacker_8.decompress_strictly_sorted(
initial,
compressed,
decompressed,
num_bits,
),
BitPacker4x::BLOCK_LEN => self.bitpacker_4.decompress_strictly_sorted(
initial,
compressed,
decompressed,
num_bits,
),
_ => self.bitpacker_1.decompress_strictly_sorted(
initial,
compressed,
decompressed,
num_bits,
),
}
}
fn num_bits_strictly_sorted(&self, initial: Option<u32>, decompressed: &[u32]) -> u8 {
match self.block_len {
BitPacker8x::BLOCK_LEN => self
.bitpacker_8
.num_bits_strictly_sorted(initial, decompressed),
BitPacker4x::BLOCK_LEN => self
.bitpacker_4
.num_bits_strictly_sorted(initial, decompressed),
_ => self
.bitpacker_1
.num_bits_strictly_sorted(initial, decompressed),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bitpack_roundtrip() {
for num_positions in [
1,
10,
BitPacker1x::BLOCK_LEN,
BitPacker4x::BLOCK_LEN,
BitPacker8x::BLOCK_LEN,
BitPacker8x::BLOCK_LEN + BitPacker4x::BLOCK_LEN + BitPacker1x::BLOCK_LEN,
BitPacker8x::BLOCK_LEN + BitPacker4x::BLOCK_LEN + BitPacker1x::BLOCK_LEN + 1,
(BitPacker8x::BLOCK_LEN * 3)
+ (BitPacker4x::BLOCK_LEN * 3)
+ (BitPacker1x::BLOCK_LEN * 3)
+ 1,
(BitPacker8x::BLOCK_LEN * 32) + 1,
] {
let serialized = KeySerializer::new(num_positions * std::mem::size_of::<u32>())
.bitpack_sorted(
&(0..num_positions)
.map(|i| (i * i) as u32)
.collect::<Vec<_>>(),
)
.finalize();
println!(
"Testing block {num_positions} with {} size...",
serialized.len()
);
let mut iter = BitpackIterator::new(&serialized).unwrap();
assert_eq!(
iter.items_left, num_positions as u32,
"failed for num_positions: {}",
num_positions
);
for i in 0..num_positions {
assert_eq!(
iter.next(),
Some((i * i) as u32),
"failed for position: {}",
i
);
}
assert_eq!(iter.next(), None, "expected end of iterator");
}
}
}
+296
View File
@@ -0,0 +1,296 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::{BlobOp, Operation, ValueClass, ValueOp, key::DeserializeBigEndian, now};
use crate::{
BlobStore, Deserialize, IterateParams, SerializeInfallible, Store, U16_LEN, U32_LEN, U64_LEN,
ValueKey,
write::{BatchBuilder, BlobLink, RegistryClass},
};
use registry::{
schema::prelude::Property,
types::{EnumImpl, id::ObjectId},
};
use std::time::Instant;
use trc::{AddContext, StoreEvent};
use types::{
blob::BlobClass,
blob_hash::{BLOB_HASH_LEN, BlobHash},
};
#[derive(Debug, PartialEq, Eq)]
pub struct BlobQuota {
pub bytes: usize,
pub count: usize,
}
impl Store {
pub async fn blob_exists(&self, hash: impl AsRef<BlobHash> + Sync + Send) -> trc::Result<bool> {
self.key_exists(ValueKey {
account_id: 0,
collection: 0,
document_id: 0,
class: ValueClass::Blob(BlobOp::Commit {
hash: hash.as_ref().clone(),
}),
})
.await
.caused_by(trc::location!())
}
pub async fn blob_has_access(
&self,
hash: impl AsRef<BlobHash> + Sync + Send,
class: impl AsRef<BlobClass> + Sync + Send,
) -> trc::Result<bool> {
let key = match class.as_ref() {
BlobClass::Reserved {
account_id,
expires,
} if *expires > now() => ValueKey {
account_id: *account_id,
collection: 0,
document_id: 0,
class: ValueClass::Blob(BlobOp::Link {
hash: hash.as_ref().clone(),
to: BlobLink::Temporary { until: *expires },
}),
},
BlobClass::Linked {
account_id,
collection,
document_id,
} => ValueKey {
account_id: *account_id,
collection: *collection,
document_id: *document_id,
class: ValueClass::Blob(BlobOp::Link {
hash: hash.as_ref().clone(),
to: BlobLink::Document,
}),
},
_ => return Ok(false),
};
self.key_exists(key).await
}
pub async fn purge_blobs_all_shards(&self, blob_store: BlobStore) -> trc::Result<()> {
for shard_index in 0u8..=255 {
self.purge_blobs(blob_store.clone(), shard_index).await?;
}
Ok(())
}
pub async fn purge_blobs(&self, blob_store: BlobStore, shard_index: u8) -> trc::Result<()> {
let mut total_active = 0;
let mut total_deleted = 0;
let started = Instant::now();
// Validate linked blobs
let mut from_hash = BlobHash::default();
let mut to_hash = BlobHash::new_max();
from_hash.0[0] = shard_index;
to_hash.0[0] = shard_index;
let from_key = ValueKey {
account_id: 0,
collection: 0,
document_id: 0,
class: ValueClass::Blob(BlobOp::Commit { hash: from_hash }),
};
let to_key = ValueKey {
account_id: u32::MAX,
collection: u8::MAX,
document_id: u32::MAX,
class: ValueClass::Blob(BlobOp::Link {
hash: to_hash,
to: BlobLink::Document,
}),
};
let mut state = BlobPurgeState::new();
self.iterate(
IterateParams::new(from_key, to_key).ascending(),
|key, value| {
let hash =
BlobHash::try_from_hash_slice(key.get(0..BLOB_HASH_LEN).ok_or_else(|| {
trc::Error::corrupted_key(key, value.into(), trc::location!())
})?)
.unwrap();
state.update_hash(hash);
state.process_key(key, value)?;
Ok(true)
},
)
.await
.caused_by(trc::location!())?;
state.finalize(BlobHash::default());
// Delete expired or unlinked blobs
for (_, op) in &state.delete_keys {
if let BlobOp::Commit { hash } = op {
blob_store
.delete_blob(hash.as_ref())
.await
.caused_by(trc::location!())?;
}
}
// Delete hashes
let mut batch = BatchBuilder::new();
for (account_id, op) in state.delete_keys {
if batch.is_large_batch() {
self.write(batch.build_all())
.await
.caused_by(trc::location!())?;
batch = BatchBuilder::new();
}
if let Some(account_id) = account_id {
batch.with_account_id(account_id);
}
batch.any_op(Operation::Value {
class: ValueClass::Blob(op),
op: ValueOp::Clear,
});
}
for (account_id, object_id) in state.delete_registry {
if batch.is_large_batch() {
self.write(batch.build_all())
.await
.caused_by(trc::location!())?;
batch = BatchBuilder::new();
}
let item_id = object_id.id().id();
let object_id = object_id.object().to_id();
batch
.clear(ValueClass::Registry(RegistryClass::Index {
index_id: Property::AccountId.to_id(),
object_id,
item_id,
key: (account_id as u64).serialize(),
}))
.clear(ValueClass::Registry(RegistryClass::Item {
object_id,
item_id,
}));
}
if !batch.is_empty() {
self.write(batch.build_all())
.await
.caused_by(trc::location!())?;
}
total_active += state.total_active - 1; // Exclude default hash
total_deleted += state.total_deleted;
trc::event!(
Store(StoreEvent::BlobStorePurged),
Id = shard_index as u16,
Expires = total_deleted,
Total = total_active,
Elapsed = started.elapsed()
);
Ok(())
}
}
struct BlobPurgeState {
last_hash: BlobHash,
last_hash_is_linked: bool,
delete_keys: Vec<(Option<u32>, BlobOp)>,
delete_registry: Vec<(u32, ObjectId)>,
now: u64,
total_deleted: u64,
total_active: u64,
}
impl BlobPurgeState {
fn new() -> Self {
Self {
last_hash: BlobHash::default(),
last_hash_is_linked: true, // Avoid deleting non-existing last_hash on first iteration
delete_keys: Vec::new(),
delete_registry: Vec::new(),
now: now(),
total_deleted: 0,
total_active: 0,
}
}
pub fn update_hash(&mut self, hash: BlobHash) {
if self.last_hash != hash {
self.finalize(hash);
self.last_hash_is_linked = false;
}
}
pub fn finalize(&mut self, new_hash: BlobHash) {
if !self.last_hash_is_linked {
self.total_deleted += 1;
self.delete_keys.push((
None,
BlobOp::Commit {
hash: std::mem::replace(&mut self.last_hash, new_hash),
},
));
} else {
self.total_active += 1;
self.last_hash = new_hash;
}
}
pub fn process_key(&mut self, key: &[u8], value: &[u8]) -> trc::Result<()> {
const TEMP_LINK: usize = BLOB_HASH_LEN + U32_LEN + U64_LEN;
const DOC_LINK: usize = BLOB_HASH_LEN + U64_LEN + 1;
const ID_LINK: usize = BLOB_HASH_LEN + U64_LEN;
match key.len() {
BLOB_HASH_LEN => {
// Main blob entry
Ok(())
}
TEMP_LINK => {
// Temporary link
let until = key.deserialize_be_u64(BLOB_HASH_LEN + U32_LEN)?;
if until <= self.now {
let account_id = key.deserialize_be_u32(BLOB_HASH_LEN)?;
self.delete_keys.push((
Some(account_id),
BlobOp::Link {
hash: self.last_hash.clone(),
to: BlobLink::Temporary { until },
},
));
if value.len() == U16_LEN + U64_LEN {
self.delete_registry
.push((account_id, ObjectId::deserialize(value)?));
}
} else {
self.last_hash_is_linked = true;
}
Ok(())
}
DOC_LINK | ID_LINK => {
// Document/Id link
self.last_hash_is_linked = true;
Ok(())
}
_ => Err(trc::Error::corrupted_key(
key,
value.into(),
trc::location!(),
)),
}
}
}
+685
View File
@@ -0,0 +1,685 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::{
AnyKey, BlobOp, InMemoryClass, QueueClass, TaskQueueClass, TelemetryClass, ValueClass,
};
use crate::{
IndexKey, IndexKeyPrefix, Key, LogKey, SUBSPACE_ACL, SUBSPACE_BLOB_LINK, SUBSPACE_COUNTER,
SUBSPACE_DELETED_ITEMS, SUBSPACE_DIRECTORY, SUBSPACE_IN_MEMORY_COUNTER,
SUBSPACE_IN_MEMORY_VALUE, SUBSPACE_INDEXES, SUBSPACE_LOGS, SUBSPACE_PROPERTY,
SUBSPACE_QUEUE_EVENT, SUBSPACE_QUEUE_MESSAGE, SUBSPACE_QUOTA, SUBSPACE_REGISTRY,
SUBSPACE_REGISTRY_IDX, SUBSPACE_REGISTRY_PK, SUBSPACE_REPORT_IN, SUBSPACE_REPORT_OUT,
SUBSPACE_SEARCH_INDEX, SUBSPACE_SPAM_SAMPLES, SUBSPACE_TASK_QUEUE, SUBSPACE_TELEMETRY_METRIC,
SUBSPACE_TELEMETRY_SPAN, U16_LEN, U32_LEN, U64_LEN, ValueKey, WITH_SUBSPACE,
write::{
BlobLink, IndexPropertyClass, RegistryClass, SearchIndex, SearchIndexId, SearchIndexType,
},
};
use registry::schema::prelude::ObjectType;
use std::convert::TryInto;
use types::{
blob_hash::BLOB_HASH_LEN,
collection::{Collection, SyncCollection},
field::{Field, MailboxField},
};
use utils::codec::leb128::Leb128_;
pub struct KeySerializer {
pub buf: Vec<u8>,
}
pub trait KeySerialize {
fn serialize(&self, buf: &mut Vec<u8>);
}
pub trait DeserializeBigEndian {
fn deserialize_be_u16(&self, index: usize) -> trc::Result<u16>;
fn deserialize_be_u32(&self, index: usize) -> trc::Result<u32>;
fn deserialize_be_u64(&self, index: usize) -> trc::Result<u64>;
}
impl KeySerializer {
pub fn new(capacity: usize) -> Self {
Self {
buf: Vec::with_capacity(capacity),
}
}
pub fn write<T: KeySerialize>(mut self, value: T) -> Self {
value.serialize(&mut self.buf);
self
}
pub fn write_leb128<T: Leb128_>(mut self, value: T) -> Self {
T::to_leb128_bytes(value, &mut self.buf);
self
}
pub fn finalize(self) -> Vec<u8> {
self.buf
}
}
impl KeySerialize for u8 {
fn serialize(&self, buf: &mut Vec<u8>) {
buf.push(*self);
}
}
impl KeySerialize for &str {
fn serialize(&self, buf: &mut Vec<u8>) {
buf.extend_from_slice(self.as_bytes());
}
}
impl KeySerialize for &String {
fn serialize(&self, buf: &mut Vec<u8>) {
buf.extend_from_slice(self.as_bytes());
}
}
impl KeySerialize for &[u8] {
fn serialize(&self, buf: &mut Vec<u8>) {
buf.extend_from_slice(self);
}
}
impl KeySerialize for u32 {
fn serialize(&self, buf: &mut Vec<u8>) {
buf.extend_from_slice(&self.to_be_bytes());
}
}
impl KeySerialize for u16 {
fn serialize(&self, buf: &mut Vec<u8>) {
buf.extend_from_slice(&self.to_be_bytes());
}
}
impl KeySerialize for u64 {
fn serialize(&self, buf: &mut Vec<u8>) {
buf.extend_from_slice(&self.to_be_bytes());
}
}
impl DeserializeBigEndian for &[u8] {
fn deserialize_be_u16(&self, index: usize) -> trc::Result<u16> {
self.get(index..index + U16_LEN)
.and_then(|bytes| bytes.try_into().ok())
.ok_or_else(|| {
trc::StoreEvent::DataCorruption
.caused_by(trc::location!())
.ctx(trc::Key::Value, *self)
})
.map(u16::from_be_bytes)
}
fn deserialize_be_u32(&self, index: usize) -> trc::Result<u32> {
self.get(index..index + U32_LEN)
.and_then(|bytes| bytes.try_into().ok())
.ok_or_else(|| {
trc::StoreEvent::DataCorruption
.caused_by(trc::location!())
.ctx(trc::Key::Value, *self)
})
.map(u32::from_be_bytes)
}
fn deserialize_be_u64(&self, index: usize) -> trc::Result<u64> {
self.get(index..index + U64_LEN)
.and_then(|bytes| bytes.try_into().ok())
.ok_or_else(|| {
trc::StoreEvent::DataCorruption
.caused_by(trc::location!())
.ctx(trc::Key::Value, *self)
})
.map(u64::from_be_bytes)
}
}
impl<T: AsRef<ValueClass>> ValueKey<T> {
pub fn with_document_id(self, document_id: u32) -> Self {
Self {
document_id,
..self
}
}
}
impl ValueKey<ValueClass> {
pub fn property(
account_id: u32,
collection: impl Into<u8>,
document_id: u32,
field: impl Into<u8>,
) -> ValueKey<ValueClass> {
ValueKey {
account_id,
collection: collection.into(),
document_id,
class: ValueClass::Property(field.into()),
}
}
pub fn archive(
account_id: u32,
collection: impl Into<u8>,
document_id: u32,
) -> ValueKey<ValueClass> {
ValueKey {
account_id,
collection: collection.into(),
document_id,
class: ValueClass::Property(Field::ARCHIVE.into()),
}
}
}
impl Key for IndexKeyPrefix {
fn serialize(&self, flags: u32) -> Vec<u8> {
{
if (flags & WITH_SUBSPACE) != 0 {
KeySerializer::new(std::mem::size_of::<IndexKeyPrefix>() + 1)
.write(crate::SUBSPACE_INDEXES)
} else {
KeySerializer::new(std::mem::size_of::<IndexKeyPrefix>())
}
}
.write(self.account_id)
.write(self.collection)
.write(self.field)
.finalize()
}
fn subspace(&self) -> u8 {
SUBSPACE_INDEXES
}
}
impl IndexKeyPrefix {
pub fn len() -> usize {
U32_LEN + 2
}
}
impl Key for LogKey {
fn subspace(&self) -> u8 {
SUBSPACE_LOGS
}
fn serialize(&self, flags: u32) -> Vec<u8> {
{
if (flags & WITH_SUBSPACE) != 0 {
KeySerializer::new(std::mem::size_of::<LogKey>() + 1).write(crate::SUBSPACE_LOGS)
} else {
KeySerializer::new(std::mem::size_of::<LogKey>())
}
}
.write(self.account_id)
.write(self.collection)
.write(self.change_id)
.finalize()
}
}
impl<T: AsRef<ValueClass> + Sync + Send + Clone> Key for ValueKey<T> {
fn subspace(&self) -> u8 {
self.class.as_ref().subspace(self.collection)
}
fn serialize(&self, flags: u32) -> Vec<u8> {
self.class
.as_ref()
.serialize(self.account_id, self.collection, self.document_id, flags)
}
}
impl ValueClass {
pub fn serialize(
&self,
account_id: u32,
collection: u8,
document_id: u32,
flags: u32,
) -> Vec<u8> {
let serializer = if (flags & WITH_SUBSPACE) != 0 {
KeySerializer::new(self.serialized_size() + 2).write(self.subspace(collection))
} else {
KeySerializer::new(self.serialized_size() + 1)
};
match self {
ValueClass::Property(property) => serializer
.write(account_id)
.write(collection)
.write(*property)
.write(document_id),
ValueClass::IndexProperty(property) => match property {
IndexPropertyClass::Hash { property, hash } => serializer
.write(account_id)
.write(collection)
.write(*property)
.write(hash.as_bytes())
.write(document_id),
IndexPropertyClass::Integer { property, value } => serializer
.write(account_id)
.write(collection)
.write(*property)
.write(*value)
.write(document_id),
},
ValueClass::Acl(grant_account_id) => serializer
.write(*grant_account_id)
.write(account_id)
.write(collection)
.write(document_id),
ValueClass::TaskQueue(task) => match task {
TaskQueueClass::Task { id } => serializer.write(0u64).write(*id),
TaskQueueClass::Due { id, due } => serializer.write(*due).write(*id),
},
ValueClass::Blob(op) => match op {
BlobOp::Commit { hash } => serializer.write::<&[u8]>(hash.as_ref()),
BlobOp::Link { hash, to } => match to {
BlobLink::Id { id } => serializer.write::<&[u8]>(hash.as_ref()).write(*id),
BlobLink::Document => serializer
.write::<&[u8]>(hash.as_ref())
.write(account_id)
.write(collection)
.write(document_id),
BlobLink::Temporary { until } => serializer
.write::<&[u8]>(hash.as_ref())
.write(account_id)
.write(*until),
},
},
ValueClass::InMemory(lookup) => match lookup {
InMemoryClass::Key(key) => serializer.write(key.as_slice()),
InMemoryClass::Counter(key) => serializer.write(key.as_slice()),
},
ValueClass::Registry(registry) => match registry {
RegistryClass::Item { object_id, item_id } => {
serializer.write(*object_id).write(*item_id)
}
RegistryClass::IndexId { object_id, item_id } => {
serializer.write(u16::MAX).write(*object_id).write(*item_id)
}
RegistryClass::Index {
index_id,
object_id,
item_id,
key,
} => serializer
.write(*object_id)
.write(*index_id)
.write(key.as_slice())
.write(*item_id),
RegistryClass::Reference {
to_object_id,
to_item_id,
from_object_id,
from_item_id,
} => serializer
.write(*to_object_id)
.write(*to_item_id)
.write(*from_object_id)
.write(*from_item_id),
RegistryClass::PrimaryKey {
object_id,
index_id,
key,
} => serializer
.write((*object_id).unwrap_or(u16::MAX))
.write(*index_id)
.write(key.as_slice()),
RegistryClass::IdCounter { object_id } => serializer.write(*object_id),
},
ValueClass::Queue(queue) => match queue {
QueueClass::Message(queue_id) => serializer.write(*queue_id),
QueueClass::MessageEvent(event) => serializer
.write(event.due)
.write(event.queue_id)
.write(event.queue_name.as_slice()),
QueueClass::QuotaCount(key) => serializer.write(0u8).write(key.as_slice()),
QueueClass::QuotaSize(key) => serializer.write(1u8).write(key.as_slice()),
},
ValueClass::Telemetry(telemetry) => match telemetry {
TelemetryClass::Span(span_id) => serializer.write(*span_id),
TelemetryClass::Metric(metric_id) => serializer.write(*metric_id),
},
ValueClass::DocumentId => serializer.write(account_id).write(collection),
ValueClass::ChangeId => serializer.write(account_id),
ValueClass::Quota => serializer.write(account_id).write(u8::MAX),
ValueClass::TenantQuota(tenant_id) => serializer.write(*tenant_id).write(u8::MAX - 1),
ValueClass::NodeId(node_id) => serializer.write(u32::MAX).write(*node_id),
ValueClass::ShareNotification {
notification_id,
notify_account_id,
} => serializer
.write(*notify_account_id)
.write(u8::from(SyncCollection::ShareNotification))
.write(*notification_id),
ValueClass::SearchIndex(index) => match &index.typ {
SearchIndexType::Term { field, hash } => {
let class = index.index.as_u8();
match &index.id {
SearchIndexId::Account {
account_id,
document_id,
} => serializer
.write(class)
.write(*account_id)
.write(hash.payload())
.write(hash.payload_len())
.write(*field)
.write(*document_id),
SearchIndexId::Global { id } => serializer
.write(class)
.write(hash.payload())
.write(hash.payload_len())
.write(*field)
.write(*id),
}
}
SearchIndexType::Index { field } => {
let class = index.index.as_u8() | 1 << 6;
match &index.id {
SearchIndexId::Account {
account_id,
document_id,
} => serializer
.write(class)
.write(*account_id)
.write(field.field_id)
.write(field.data.as_slice())
.write(*document_id),
SearchIndexId::Global { id } => serializer
.write(class)
.write(field.field_id)
.write(field.data.as_slice())
.write(*id),
}
}
SearchIndexType::Document => {
let class = index.index.as_u8() | 2 << 6;
match &index.id {
SearchIndexId::Account {
account_id,
document_id,
} => serializer
.write(class)
.write(*account_id)
.write(*document_id),
SearchIndexId::Global { id } => serializer.write(class).write(*id),
}
}
},
ValueClass::Any(any) => serializer.write(any.key.as_slice()),
}
.finalize()
}
}
impl<T: AsRef<[u8]> + Sync + Send + Clone> Key for IndexKey<T> {
fn subspace(&self) -> u8 {
SUBSPACE_INDEXES
}
fn serialize(&self, flags: u32) -> Vec<u8> {
let key = self.key.as_ref();
{
if (flags & WITH_SUBSPACE) != 0 {
KeySerializer::new(std::mem::size_of::<IndexKey<T>>() + key.len() + 1)
.write(crate::SUBSPACE_INDEXES)
} else {
KeySerializer::new(std::mem::size_of::<IndexKey<T>>() + key.len())
}
}
.write(self.account_id)
.write(self.collection)
.write(self.field)
.write(key)
.write(self.document_id)
.finalize()
}
}
impl<T: AsRef<[u8]> + Sync + Send + Clone> Key for AnyKey<T> {
fn serialize(&self, flags: u32) -> Vec<u8> {
let key = self.key.as_ref();
if (flags & WITH_SUBSPACE) != 0 {
KeySerializer::new(key.len() + 1).write(self.subspace)
} else {
KeySerializer::new(key.len())
}
.write(key)
.finalize()
}
fn subspace(&self) -> u8 {
self.subspace
}
}
const MAILBOX_COLLECTION: u8 = Collection::Mailbox as u8;
const MAILBOX_COUNTER_FIELD: u8 = MailboxField::UidCounter as u8;
const REG_ARCHIVED_ITEM: u16 = ObjectType::ArchivedItem as u16;
const REG_SPAM_SAMPLE: u16 = ObjectType::SpamTrainingSample as u16;
const REG_ACCOUNT: u16 = ObjectType::Account as u16;
const REG_DOMAIN: u16 = ObjectType::Domain as u16;
const REG_TENANT: u16 = ObjectType::Tenant as u16;
const REG_ROLE: u16 = ObjectType::Role as u16;
const REG_OAUTH_CLIENT: u16 = ObjectType::OAuthClient as u16;
const REG_MAILING_LIST: u16 = ObjectType::MailingList as u16;
const REG_MASKED_EMAIL: u16 = ObjectType::MaskedEmail as u16;
const REG_PUBLIC_KEY: u16 = ObjectType::PublicKey as u16;
const REG_TRACE: u16 = ObjectType::Trace as u16;
const REG_METRIC: u16 = ObjectType::Metric as u16;
const REPORT_EXTERNAL_ARF: u16 = ObjectType::ArfExternalReport as u16;
const REPORT_EXTERNAL_DMARC: u16 = ObjectType::DmarcExternalReport as u16;
const REPORT_EXTERNAL_TLS: u16 = ObjectType::TlsExternalReport as u16;
const REPORT_INTERNAL_DMARC: u16 = ObjectType::DmarcInternalReport as u16;
const REPORT_INTERNAL_TLS: u16 = ObjectType::TlsInternalReport as u16;
impl ValueClass {
pub fn serialized_size(&self) -> usize {
match self {
ValueClass::Property(_) => U32_LEN * 2 + 3,
ValueClass::IndexProperty(p) => match p {
IndexPropertyClass::Hash { hash, .. } => U32_LEN * 2 + 3 + hash.len(),
IndexPropertyClass::Integer { .. } => U32_LEN * 2 + 3 + U64_LEN,
},
ValueClass::Acl(_) => U32_LEN * 3 + 2,
ValueClass::InMemory(InMemoryClass::Counter(v) | InMemoryClass::Key(v)) => v.len(),
ValueClass::Registry(registry) => match registry {
RegistryClass::Item { .. } => U16_LEN + U64_LEN + 1,
RegistryClass::Reference { .. } => ((U16_LEN + U64_LEN) * 2) + 1,
RegistryClass::Index { key, .. } => (U16_LEN * 2) + U64_LEN + key.len() + 1,
RegistryClass::PrimaryKey { key, .. } => (U16_LEN * 2) + key.len() + 1,
RegistryClass::IndexId { .. } => U16_LEN + U64_LEN + 1,
RegistryClass::IdCounter { .. } => U16_LEN + 1,
},
ValueClass::Blob(op) => match op {
BlobOp::Commit { .. } => BLOB_HASH_LEN,
BlobOp::Link { to, .. } => {
BLOB_HASH_LEN
+ match to {
BlobLink::Id { .. } => U64_LEN,
BlobLink::Document => U32_LEN * 2 + 1,
BlobLink::Temporary { .. } => U32_LEN + U64_LEN,
}
}
},
ValueClass::TaskQueue(_) => (U64_LEN * 2) + 1,
ValueClass::Queue(q) => match q {
QueueClass::Message(_) => U64_LEN,
QueueClass::MessageEvent(_) => U64_LEN * 3,
QueueClass::QuotaCount(v) | QueueClass::QuotaSize(v) => v.len(),
},
ValueClass::Telemetry(telemetry) => match telemetry {
TelemetryClass::Span(_) | TelemetryClass::Metric(_) => U64_LEN + 1,
},
ValueClass::DocumentId | ValueClass::Quota | ValueClass::TenantQuota(_) => U32_LEN + 1,
ValueClass::ChangeId => U32_LEN,
ValueClass::ShareNotification { .. } => U32_LEN + U64_LEN + 1,
ValueClass::NodeId(_) => (U16_LEN * 3) + 1,
ValueClass::SearchIndex(v) => match &v.typ {
SearchIndexType::Term { hash, .. } => U64_LEN + hash.len() + 2,
SearchIndexType::Index { field, .. } => 1 + field.data.len() + U64_LEN,
SearchIndexType::Document => match &v.id {
SearchIndexId::Account { .. } => 1 + U32_LEN * 2,
SearchIndexId::Global { .. } => 1 + U64_LEN,
},
},
ValueClass::Any(v) => v.key.len(),
}
}
pub fn subspace(&self, collection: u8) -> u8 {
match self {
ValueClass::Property(field) => {
if collection == MAILBOX_COLLECTION && *field == MAILBOX_COUNTER_FIELD {
SUBSPACE_COUNTER
} else {
SUBSPACE_PROPERTY
}
}
ValueClass::IndexProperty { .. } => SUBSPACE_PROPERTY,
ValueClass::Acl(_) => SUBSPACE_ACL,
ValueClass::TaskQueue { .. } => SUBSPACE_TASK_QUEUE,
ValueClass::Blob(op) => match op {
BlobOp::Commit { .. } | BlobOp::Link { .. } => SUBSPACE_BLOB_LINK,
},
ValueClass::Registry(registry) => match registry {
RegistryClass::Item { object_id, .. } => match *object_id {
REG_ACCOUNT | REG_DOMAIN | REG_TENANT | REG_ROLE | REG_OAUTH_CLIENT
| REG_MAILING_LIST | REG_MASKED_EMAIL | REG_PUBLIC_KEY => SUBSPACE_DIRECTORY,
REG_ARCHIVED_ITEM => SUBSPACE_DELETED_ITEMS,
REG_SPAM_SAMPLE => SUBSPACE_SPAM_SAMPLES,
REG_TRACE => SUBSPACE_TELEMETRY_SPAN,
REG_METRIC => SUBSPACE_TELEMETRY_METRIC,
REPORT_EXTERNAL_ARF | REPORT_EXTERNAL_DMARC | REPORT_EXTERNAL_TLS => {
SUBSPACE_REPORT_IN
}
REPORT_INTERNAL_DMARC | REPORT_INTERNAL_TLS => SUBSPACE_REPORT_OUT,
_ => SUBSPACE_REGISTRY,
},
RegistryClass::IndexId { .. } | RegistryClass::Index { .. } => {
SUBSPACE_REGISTRY_IDX
}
RegistryClass::Reference { .. } | RegistryClass::PrimaryKey { .. } => {
SUBSPACE_REGISTRY_PK
}
RegistryClass::IdCounter { .. } => SUBSPACE_COUNTER,
},
ValueClass::NodeId(_) => SUBSPACE_REGISTRY_PK,
ValueClass::InMemory(lookup) => match lookup {
InMemoryClass::Key(_) => SUBSPACE_IN_MEMORY_VALUE,
InMemoryClass::Counter(_) => SUBSPACE_IN_MEMORY_COUNTER,
},
ValueClass::Queue(queue) => match queue {
QueueClass::Message(_) => SUBSPACE_QUEUE_MESSAGE,
QueueClass::MessageEvent(_) => SUBSPACE_QUEUE_EVENT,
QueueClass::QuotaCount(_) | QueueClass::QuotaSize(_) => SUBSPACE_QUOTA,
},
ValueClass::Telemetry(telemetry) => match telemetry {
TelemetryClass::Span { .. } => SUBSPACE_TELEMETRY_SPAN,
TelemetryClass::Metric { .. } => SUBSPACE_TELEMETRY_METRIC,
},
ValueClass::DocumentId
| ValueClass::ChangeId
| ValueClass::Quota
| ValueClass::TenantQuota(_) => SUBSPACE_COUNTER,
ValueClass::ShareNotification { .. } => SUBSPACE_LOGS,
ValueClass::SearchIndex(_) => SUBSPACE_SEARCH_INDEX,
ValueClass::Any(any) => any.subspace,
}
}
}
pub fn is_node_id_key(key: &[u8]) -> bool {
key.len() == U32_LEN + U16_LEN && key.starts_with(&u32::MAX.to_be_bytes())
}
impl From<ValueClass> for ValueKey<ValueClass> {
fn from(class: ValueClass) -> Self {
ValueKey {
account_id: 0,
collection: 0,
document_id: 0,
class,
}
}
}
impl From<RegistryClass> for ValueKey<ValueClass> {
fn from(value: RegistryClass) -> Self {
ValueKey {
account_id: 0,
collection: 0,
document_id: 0,
class: ValueClass::Registry(value),
}
}
}
impl From<RegistryClass> for ValueClass {
fn from(value: RegistryClass) -> Self {
ValueClass::Registry(value)
}
}
impl From<BlobOp> for ValueClass {
fn from(value: BlobOp) -> Self {
ValueClass::Blob(value)
}
}
impl SearchIndex {
pub fn to_u8(&self) -> u8 {
match self {
SearchIndex::Email => 0,
SearchIndex::Calendar => 1,
SearchIndex::Contacts => 2,
SearchIndex::File => 3,
SearchIndex::Tracing => 4,
SearchIndex::InMemory => unreachable!(),
}
}
pub fn try_from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(SearchIndex::Email),
1 => Some(SearchIndex::Calendar),
2 => Some(SearchIndex::Contacts),
3 => Some(SearchIndex::File),
4 => Some(SearchIndex::Tracing),
_ => None,
}
}
pub fn name(&self) -> &'static str {
match self {
SearchIndex::Email => "email",
SearchIndex::Calendar => "calendar",
SearchIndex::Contacts => "contacts",
SearchIndex::File => "file",
SearchIndex::Tracing => "tracing",
SearchIndex::InMemory => "in_memory",
}
}
pub fn try_from_str(value: &str) -> Option<Self> {
match value {
"email" => Some(SearchIndex::Email),
"calendar" => Some(SearchIndex::Calendar),
"contacts" => Some(SearchIndex::Contacts),
"file" => Some(SearchIndex::File),
"tracing" => Some(SearchIndex::Tracing),
_ => None,
}
}
}
+234
View File
@@ -0,0 +1,234 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{SerializeInfallible, U64_LEN};
use ahash::AHashSet;
use types::collection::{SyncCollection, VanishedCollection};
use utils::{codec::leb128::Leb128Vec, map::vec_map::VecMap};
use super::key::KeySerializer;
#[derive(Default, Debug)]
pub(crate) struct ChangeLogBuilder {
pub changes: VecMap<SyncCollection, Changes>,
pub vanished: VecMap<VanishedCollection, VanishedItems>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum VanishedItem {
Name(String),
Id(u64),
IdPair(u32, u32),
}
#[derive(Default, Debug)]
pub(crate) struct VanishedItems(Vec<VanishedItem>);
#[derive(Default, Debug)]
pub struct Changes {
pub item_inserts: AHashSet<u64>,
pub item_updates: AHashSet<u64>,
pub item_deletes: AHashSet<u64>,
pub container_inserts: AHashSet<u32>,
pub container_updates: AHashSet<u32>,
pub container_deletes: AHashSet<u32>,
pub container_property_changes: AHashSet<u32>,
}
impl ChangeLogBuilder {
pub fn log_container_insert(&mut self, collection: SyncCollection, document_id: u32) {
let changes = self.changes.get_mut_or_insert(collection);
if changes.container_deletes.remove(&document_id) {
changes.container_updates.insert(document_id);
} else {
changes.container_inserts.insert(document_id);
}
}
pub fn log_item_insert(
&mut self,
collection: SyncCollection,
prefix: Option<u32>,
document_id: u32,
) {
let id = build_id(prefix, document_id);
let changes = self.changes.get_mut_or_insert(collection);
if changes.item_deletes.remove(&id) {
changes.item_updates.insert(id);
} else {
changes.item_inserts.insert(id);
}
}
pub fn log_container_update(&mut self, collection: SyncCollection, document_id: u32) {
self.changes
.get_mut_or_insert(collection)
.container_updates
.insert(document_id);
}
pub fn log_container_property_update(&mut self, collection: SyncCollection, document_id: u32) {
self.changes
.get_mut_or_insert(collection)
.container_property_changes
.insert(document_id);
}
pub fn log_item_update(
&mut self,
collection: SyncCollection,
prefix: Option<u32>,
document_id: u32,
) {
self.changes
.get_mut_or_insert(collection)
.item_updates
.insert(build_id(prefix, document_id));
}
pub fn log_container_delete(&mut self, collection: SyncCollection, document_id: u32) {
let changes = self.changes.get_mut_or_insert(collection);
let id = document_id;
changes.container_updates.remove(&id);
changes.container_property_changes.remove(&id);
changes.container_deletes.insert(id);
}
pub fn log_item_delete(
&mut self,
collection: SyncCollection,
prefix: Option<u32>,
document_id: u32,
) {
let changes = self.changes.get_mut_or_insert(collection);
let id = build_id(prefix, document_id);
changes.item_updates.remove(&id);
changes.item_deletes.insert(id);
}
pub fn log_vanished_item(
&mut self,
collection: VanishedCollection,
item: impl Into<VanishedItem>,
) {
self.vanished
.get_mut_or_insert(collection)
.0
.push(item.into());
}
}
#[inline(always)]
fn build_id(prefix: Option<u32>, document_id: u32) -> u64 {
if let Some(prefix) = prefix {
((prefix as u64) << 32) | document_id as u64
} else {
document_id as u64
}
}
impl Changes {
pub fn has_container_changes(&self) -> bool {
!self.container_inserts.is_empty()
|| !self.container_updates.is_empty()
|| !self.container_property_changes.is_empty()
|| !self.container_deletes.is_empty()
}
pub fn has_item_changes(&self) -> bool {
!self.item_inserts.is_empty()
|| !self.item_updates.is_empty()
|| !self.item_deletes.is_empty()
}
}
impl SerializeInfallible for Changes {
fn serialize(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(
1 + (self.item_inserts.len()
+ self.item_updates.len()
+ self.item_deletes.len()
+ self.container_inserts.len()
+ self.container_updates.len()
+ self.container_property_changes.len()
+ self.container_deletes.len()
+ 4)
* std::mem::size_of::<usize>(),
);
buf.push_leb128(self.container_inserts.len());
buf.push_leb128(self.container_updates.len());
buf.push_leb128(self.container_property_changes.len());
buf.push_leb128(self.container_deletes.len());
buf.push_leb128(self.item_inserts.len());
buf.push_leb128(self.item_updates.len());
buf.push_leb128(self.item_deletes.len());
for list in [
&self.container_inserts,
&self.container_updates,
&self.container_property_changes,
&self.container_deletes,
] {
for id in list {
buf.push_leb128(*id);
}
}
for list in [&self.item_inserts, &self.item_updates, &self.item_deletes] {
for id in list {
buf.push_leb128(*id);
}
}
buf
}
}
impl From<String> for VanishedItem {
fn from(value: String) -> Self {
VanishedItem::Name(value)
}
}
impl From<u64> for VanishedItem {
fn from(value: u64) -> Self {
VanishedItem::Id(value)
}
}
impl From<(u32, u32)> for VanishedItem {
fn from(value: (u32, u32)) -> Self {
VanishedItem::Id((value.0 as u64) << 32 | value.1 as u64)
}
}
impl VanishedItem {
pub fn serialized_size(&self) -> usize {
match self {
VanishedItem::Name(name) => name.len() + 1,
VanishedItem::Id(_) | VanishedItem::IdPair(..) => U64_LEN,
}
}
}
impl SerializeInfallible for VanishedItems {
fn serialize(&self) -> Vec<u8> {
let mut buf = KeySerializer::new(64);
for item in &self.0 {
buf = match item {
VanishedItem::Name(name) => buf.write(name.as_bytes()).write(0u8),
VanishedItem::Id(id) => buf.write(id.to_be_bytes().as_slice()),
VanishedItem::IdPair(a, b) => buf
.write(a.to_be_bytes().as_slice())
.write(b.to_be_bytes().as_slice()),
};
}
buf.finalize()
}
}
+700
View File
@@ -0,0 +1,700 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use self::assert::AssertValue;
use crate::backend::MAX_TOKEN_LENGTH;
use log::ChangeLogBuilder;
use nlp::tokenizers::word::WordTokenizer;
use rkyv::util::AlignedVec;
use std::{collections::HashSet, hash::Hash, time::SystemTime};
use types::{
blob_hash::BlobHash,
collection::{Collection, SyncCollection, VanishedCollection},
field::{
CalendarEventField, CalendarNotificationField, ContactField, EmailField,
EmailSubmissionField, Field, MailboxField, PrincipalField, SieveField,
},
};
use utils::{
cheeky_hash::CheekyHash,
map::{bitmap::Bitmap, vec_map::VecMap},
};
pub mod assert;
pub mod batch;
pub mod bitpack;
pub mod blob;
pub mod key;
pub mod log;
pub mod serialize;
pub(crate) const ARCHIVE_ALIGNMENT: usize = 16;
#[derive(Debug, Clone)]
pub struct Archive<T> {
pub inner: T,
pub version: ArchiveVersion,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ArchiveVersion {
Versioned { change_id: u64, hash: u32 },
Hashed { hash: u32 },
Unversioned,
}
#[derive(Debug, Clone)]
pub enum AlignedBytes {
Aligned(AlignedVec<ARCHIVE_ALIGNMENT>),
Vec(Vec<u8>),
}
pub struct Archiver<T>
where
T: rkyv::Archive
+ for<'a> rkyv::Serialize<
rkyv::api::high::HighSerializer<
rkyv::util::AlignedVec,
rkyv::ser::allocator::ArenaHandle<'a>,
rkyv::rancor::Error,
>,
>,
{
pub inner: T,
pub flags: u8,
}
#[derive(Debug, Default)]
pub struct AssignedIds {
pub ids: Vec<AssignedId>,
current_change_id: Option<u64>,
}
#[derive(Debug)]
pub enum AssignedId {
Counter(i64),
ChangeId(ChangeId),
}
#[derive(Debug, Clone, Copy)]
pub struct ChangeId {
pub account_id: u32,
pub change_id: u64,
}
#[cfg(any(
feature = "rocks",
feature = "postgres",
feature = "mysql",
feature = "foundation"
))]
pub(crate) use commit_limits::{MAX_COMMIT_ATTEMPTS, MAX_COMMIT_TIME};
#[cfg(any(
feature = "rocks",
feature = "postgres",
feature = "mysql",
feature = "foundation"
))]
mod commit_limits {
use std::time::Duration;
#[cfg(not(feature = "test_mode"))]
pub(crate) const MAX_COMMIT_ATTEMPTS: u32 = 10;
#[cfg(not(feature = "test_mode"))]
pub(crate) const MAX_COMMIT_TIME: Duration = Duration::from_secs(10);
#[cfg(feature = "test_mode")]
pub(crate) const MAX_COMMIT_ATTEMPTS: u32 = 1000;
#[cfg(feature = "test_mode")]
pub(crate) const MAX_COMMIT_TIME: Duration = Duration::from_secs(3600);
}
#[derive(Debug)]
pub struct Batch<'x> {
pub(crate) changes: &'x VecMap<u32, ChangedCollection>,
pub(crate) ops: &'x mut [Operation],
}
#[derive(Debug)]
pub struct BatchBuilder {
current_account_id: Option<u32>,
current_collection: Option<Collection>,
current_document_id: Option<u32>,
changes: VecMap<u32, ChangeLogBuilder>,
changed_collections: VecMap<u32, ChangedCollection>,
has_assertions: bool,
batch_size: usize,
batch_ops: usize,
commit_points: Vec<usize>,
ops: Vec<Operation>,
}
#[derive(Debug, Default)]
pub struct ChangedCollection {
pub changed_containers: Bitmap<SyncCollection>,
pub changed_items: Bitmap<SyncCollection>,
pub share_notification_id: Option<u64>,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum Operation {
AccountId {
account_id: u32,
},
Collection {
collection: Collection,
},
DocumentId {
document_id: u32,
},
AssertValue {
class: ValueClass,
assert_value: AssertValue,
},
Value {
class: ValueClass,
op: ValueOp,
},
Index {
field: u8,
key: Vec<u8>,
set: bool,
},
Log {
collection: LogCollection,
set: Vec<u8>,
},
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum LogCollection {
Sync(SyncCollection),
Vanished(VanishedCollection),
}
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
pub enum ValueClass {
Property(u8),
IndexProperty(IndexPropertyClass),
Acl(u32),
InMemory(InMemoryClass),
TaskQueue(TaskQueueClass),
Blob(BlobOp),
Registry(RegistryClass),
Queue(QueueClass),
Telemetry(TelemetryClass),
SearchIndex(SearchIndexClass),
Any(AnyClass),
ShareNotification {
notification_id: u64,
notify_account_id: u32,
},
DocumentId,
ChangeId,
Quota,
TenantQuota(u32),
NodeId(u16),
}
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
pub enum IndexPropertyClass {
Hash { property: u8, hash: CheekyHash },
Integer { property: u8, value: u64 },
}
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
pub struct SearchIndexClass {
pub index: SearchIndex,
pub id: SearchIndexId,
pub typ: SearchIndexType,
}
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
pub enum SearchIndexType {
Term { field: u8, hash: CheekyHash },
Index { field: SearchIndexField },
Document,
}
pub(crate) const SEARCH_INDEX_MAX_FIELD_LEN: usize = 128;
#[derive(Debug, PartialEq, Eq, Clone, Hash, rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)]
pub struct SearchIndexField {
pub(crate) field_id: u8,
pub(crate) data: Vec<u8>,
}
#[derive(Debug, PartialEq, Clone, Copy, Eq, Hash)]
pub enum SearchIndexId {
Account { account_id: u32, document_id: u32 },
Global { id: u64 },
}
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
pub enum TaskQueueClass {
Task { id: u64 },
Due { id: u64, due: u64 },
}
#[derive(Debug, PartialEq, Clone, Copy, Eq, Hash)]
pub enum SearchIndex {
Email,
Calendar,
Contacts,
File,
Tracing,
InMemory,
}
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
pub struct AnyClass {
pub subspace: u8,
pub key: Vec<u8>,
}
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
pub enum InMemoryClass {
Key(Vec<u8>),
Counter(Vec<u8>),
}
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
pub enum RegistryClass {
Item {
object_id: u16,
item_id: u64,
},
Reference {
to_object_id: u16,
to_item_id: u64,
from_object_id: u16,
from_item_id: u64,
},
Index {
index_id: u16,
object_id: u16,
item_id: u64,
key: Vec<u8>,
},
IndexId {
object_id: u16,
item_id: u64,
},
PrimaryKey {
object_id: Option<u16>,
index_id: u16,
key: Vec<u8>,
},
IdCounter {
object_id: u16,
},
}
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
pub enum QueueClass {
Message(u64),
MessageEvent(QueueEvent),
QuotaCount(Vec<u8>),
QuotaSize(Vec<u8>),
}
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
pub enum TelemetryClass {
Span(u64),
Metric(u64),
}
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
pub struct QueueEvent {
pub due: u64,
pub queue_id: u64,
pub queue_name: [u8; 8],
}
#[derive(Debug, PartialEq, Eq, Hash, Default)]
pub enum ValueOp {
Set(Vec<u8>),
SetFnc(SetOperation),
MergeFnc(MergeOperation),
AtomicAdd(i64),
AddAndGet(i64),
#[default]
Clear,
}
pub enum MergeResult {
Update(Vec<u8>),
Skip,
Delete,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Param {
I64(i64),
U64(u64),
String(String),
Bytes(Vec<u8>),
Bool(bool),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct Params(Vec<Param>);
pub type SetFnc = fn(&Params, &AssignedIds) -> trc::Result<Vec<u8>>;
pub type MergeFnc = fn(&Params, &AssignedIds, Option<&[u8]>) -> trc::Result<MergeResult>;
#[derive(Debug, Clone)]
pub struct MergeOperation {
pub(crate) fnc: MergeFnc,
pub(crate) params: Params,
}
#[derive(Debug, Clone)]
pub struct SetOperation {
pub(crate) fnc: SetFnc,
pub(crate) params: Params,
}
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
pub enum BlobOp {
Commit { hash: BlobHash },
Link { hash: BlobHash, to: BlobLink },
}
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
pub enum BlobLink {
Id { id: u64 },
Document,
Temporary { until: u64 },
}
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
pub struct AnyKey<T: AsRef<[u8]>> {
pub subspace: u8,
pub key: T,
}
pub trait TokenizeText {
fn tokenize_into(&self, tokens: &mut HashSet<String>);
fn to_tokens(&self) -> HashSet<String>;
}
impl TokenizeText for &str {
fn tokenize_into(&self, tokens: &mut HashSet<String>) {
for token in WordTokenizer::new(self, MAX_TOKEN_LENGTH) {
tokens.insert(token.word.into_owned());
}
}
fn to_tokens(&self) -> HashSet<String> {
let mut tokens = HashSet::new();
self.tokenize_into(&mut tokens);
tokens
}
}
pub trait IntoOperations {
fn build(self, batch: &mut BatchBuilder) -> trc::Result<()>;
}
#[inline(always)]
pub fn now() -> u64 {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map_or(0, |d| d.as_secs())
}
impl AsRef<ValueClass> for ValueClass {
fn as_ref(&self) -> &ValueClass {
self
}
}
impl AssignedIds {
pub fn push_counter_id(&mut self, id: i64) {
self.ids.push(AssignedId::Counter(id));
}
pub fn push_change_id(&mut self, account_id: u32, change_id: u64) {
self.ids.push(AssignedId::ChangeId(ChangeId {
account_id,
change_id,
}));
}
pub fn last_change_id(&self, account_id: u32) -> trc::Result<u64> {
self.ids
.iter()
.filter_map(|id| match id {
AssignedId::ChangeId(change_id) if change_id.account_id == account_id => {
Some(change_id.change_id)
}
_ => None,
})
.next_back()
.ok_or_else(|| {
trc::StoreEvent::UnexpectedError
.caused_by(trc::location!())
.ctx(trc::Key::Reason, "No change ids were created")
})
}
pub fn current_change_id(&self) -> trc::Result<u64> {
self.current_change_id.ok_or_else(|| {
trc::StoreEvent::UnexpectedError
.caused_by(trc::location!())
.ctx(trc::Key::Reason, "No current change id is set")
})
}
pub(crate) fn set_current_change_id(&mut self, account_id: u32) -> trc::Result<u64> {
let change_id = self.last_change_id(account_id)?;
self.current_change_id = Some(change_id);
Ok(change_id)
}
pub fn last_counter_id(&self) -> trc::Result<i64> {
self.ids
.iter()
.filter_map(|id| match id {
AssignedId::Counter(counter_id) => Some(*counter_id),
_ => None,
})
.next_back()
.ok_or_else(|| {
trc::StoreEvent::UnexpectedError
.caused_by(trc::location!())
.ctx(trc::Key::Reason, "No counter ids were created")
})
}
}
impl<T: AsRef<[u8]>> AsRef<[u8]> for Archive<T> {
fn as_ref(&self) -> &[u8] {
self.inner.as_ref()
}
}
impl ArchiveVersion {
pub fn hash(&self) -> Option<u32> {
match self {
ArchiveVersion::Versioned { hash, .. } => Some(*hash),
ArchiveVersion::Hashed { hash } => Some(*hash),
ArchiveVersion::Unversioned => None,
}
}
pub fn change_id(&self) -> Option<u64> {
match self {
ArchiveVersion::Versioned { change_id, .. } => Some(*change_id),
_ => None,
}
}
}
impl From<LogCollection> for u8 {
fn from(value: LogCollection) -> Self {
match value {
LogCollection::Sync(col) => col as u8,
LogCollection::Vanished(col) => col as u8,
}
}
}
impl From<ContactField> for ValueClass {
fn from(value: ContactField) -> Self {
ValueClass::Property(value.into())
}
}
impl From<CalendarEventField> for ValueClass {
fn from(value: CalendarEventField) -> Self {
ValueClass::Property(value.into())
}
}
impl From<CalendarNotificationField> for ValueClass {
fn from(value: CalendarNotificationField) -> Self {
ValueClass::Property(value.into())
}
}
impl From<EmailField> for ValueClass {
fn from(value: EmailField) -> Self {
ValueClass::Property(value.into())
}
}
impl From<MailboxField> for ValueClass {
fn from(value: MailboxField) -> Self {
ValueClass::Property(value.into())
}
}
impl From<PrincipalField> for ValueClass {
fn from(value: PrincipalField) -> Self {
ValueClass::Property(value.into())
}
}
impl From<SieveField> for ValueClass {
fn from(value: SieveField) -> Self {
ValueClass::Property(value.into())
}
}
impl From<EmailSubmissionField> for ValueClass {
fn from(value: EmailSubmissionField) -> Self {
ValueClass::Property(value.into())
}
}
impl From<Field> for ValueClass {
fn from(value: Field) -> Self {
ValueClass::Property(value.into())
}
}
impl PartialEq for MergeOperation {
fn eq(&self, other: &Self) -> bool {
self.params == other.params
}
}
impl Eq for MergeOperation {}
impl PartialEq for SetOperation {
fn eq(&self, other: &Self) -> bool {
self.params == other.params
}
}
impl Eq for SetOperation {}
impl Hash for MergeOperation {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.params.hash(state);
}
}
impl Hash for SetOperation {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.params.hash(state);
}
}
impl SetOperation {
pub fn params(&self) -> &Params {
&self.params
}
}
impl MergeOperation {
pub fn params(&self) -> &Params {
&self.params
}
}
impl Params {
pub fn with_capacity(capacity: usize) -> Self {
Self(Vec::with_capacity(capacity))
}
pub fn new() -> Self {
Self(Vec::new())
}
pub fn with_i64(mut self, value: i64) -> Self {
self.0.push(Param::I64(value));
self
}
pub fn with_u64(mut self, value: u64) -> Self {
self.0.push(Param::U64(value));
self
}
pub fn with_string(mut self, value: String) -> Self {
self.0.push(Param::String(value));
self
}
pub fn with_str(mut self, value: &str) -> Self {
self.0.push(Param::String(value.to_string()));
self
}
pub fn with_bytes(mut self, value: Vec<u8>) -> Self {
self.0.push(Param::Bytes(value));
self
}
pub fn with_bool(mut self, value: bool) -> Self {
self.0.push(Param::Bool(value));
self
}
pub fn i64(&self, idx: usize) -> i64 {
match &self.0[idx] {
Param::I64(v) => *v,
_ => panic!("Param at index {} is not an i64", idx),
}
}
pub fn u64(&self, idx: usize) -> u64 {
match &self.0[idx] {
Param::U64(v) => *v,
_ => panic!("Param at index {} is not a u64", idx),
}
}
pub fn string(&self, idx: usize) -> &str {
match &self.0[idx] {
Param::String(v) => v.as_str(),
_ => panic!("Param at index {} is not a String", idx),
}
}
pub fn bytes(&self, idx: usize) -> &[u8] {
match &self.0[idx] {
Param::Bytes(v) => v.as_slice(),
_ => panic!("Param at index {} is not Bytes", idx),
}
}
pub fn bool(&self, idx: usize) -> bool {
match &self.0[idx] {
Param::Bool(v) => *v,
_ => panic!("Param at index {} is not a bool", idx),
}
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn as_slice(&self) -> &[Param] {
&self.0
}
}
impl Default for Params {
fn default() -> Self {
Self::new()
}
}
impl AsRef<[Param]> for Params {
fn as_ref(&self) -> &[Param] {
&self.0
}
}
+625
View File
@@ -0,0 +1,625 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::{ARCHIVE_ALIGNMENT, AlignedBytes, Archive, ArchiveVersion, Archiver};
use crate::{Deserialize, Serialize, SerializeInfallible, U32_LEN, U64_LEN, Value};
use compact_str::format_compact;
use rkyv::util::AlignedVec;
use roaring::{RoaringBitmap, RoaringTreemap};
const MAGIC_MARKER: u8 = 1 << 7;
const VERSIONED: u8 = 1 << 6;
const HASHED: u8 = 1 << 5;
const LZ4_COMPRESSED: u8 = 1 << 4;
const COMPRESS_WATERMARK: usize = 8192;
fn validate_marker_and_contents(bytes: &[u8]) -> Option<(bool, &[u8], ArchiveVersion)> {
let (marker, contents) = bytes
.split_last()
.filter(|(marker, _)| (**marker & MAGIC_MARKER) != 0)?;
let is_uncompressed = (marker & LZ4_COMPRESSED) == 0;
if marker & VERSIONED != 0 {
let (contents, change_id) = contents
.split_at_checked(contents.len() - U64_LEN)
.and_then(|(contents, change_id)| {
change_id
.try_into()
.ok()
.map(|change_id| (contents, u64::from_be_bytes(change_id)))
})?;
contents
.split_at_checked(contents.len() - U32_LEN)
.and_then(|(contents, archive_hash)| {
let hash = xxhash_rust::xxh3::xxh3_64(contents) as u32;
if hash.to_be_bytes().as_slice() == archive_hash {
Some((
is_uncompressed,
contents,
ArchiveVersion::Versioned { change_id, hash },
))
} else {
None
}
})
} else if marker & HASHED != 0 {
contents
.split_at_checked(contents.len() - U32_LEN)
.and_then(|(contents, archive_hash)| {
let hash = xxhash_rust::xxh3::xxh3_64(contents) as u32;
if hash.to_be_bytes().as_slice() == archive_hash {
Some((is_uncompressed, contents, ArchiveVersion::Hashed { hash }))
} else {
None
}
})
} else {
Some((is_uncompressed, contents, ArchiveVersion::Unversioned))
}
}
impl Deserialize for Archive<AlignedBytes> {
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
let (is_uncompressed, contents, version) =
validate_marker_and_contents(bytes).ok_or_else(|| {
trc::StoreEvent::DataCorruption
.into_err()
.details("Archive integrity compromised")
.ctx(trc::Key::Value, bytes)
.caused_by(trc::location!())
})?;
if is_uncompressed {
let mut bytes = AlignedVec::with_capacity(contents.len());
bytes.extend_from_slice(contents);
Ok(Archive {
version,
inner: AlignedBytes::Aligned(bytes),
})
} else {
aligned_lz4_deflate(contents).map(|inner| Archive { version, inner })
}
}
fn deserialize_owned(mut bytes: Vec<u8>) -> trc::Result<Self> {
let (is_uncompressed, contents, version) = validate_marker_and_contents(&bytes)
.ok_or_else(|| {
trc::StoreEvent::DataCorruption
.into_err()
.details("Archive integrity compromised")
.ctx(trc::Key::Value, bytes.as_slice())
.caused_by(trc::location!())
})?;
if is_uncompressed {
bytes.truncate(contents.len());
if bytes.as_ptr().addr() & (ARCHIVE_ALIGNMENT - 1) == 0 {
Ok(Archive {
version,
inner: AlignedBytes::Vec(bytes),
})
} else {
let mut aligned = AlignedVec::with_capacity(bytes.len());
aligned.extend_from_slice(&bytes);
Ok(Archive {
version,
inner: AlignedBytes::Aligned(aligned),
})
}
} else {
aligned_lz4_deflate(contents).map(|inner| Archive { version, inner })
}
}
}
#[inline]
fn aligned_lz4_deflate(archive: &[u8]) -> trc::Result<AlignedBytes> {
lz4_flex::block::uncompressed_size(archive)
.and_then(|(uncompressed_size, archive)| {
let mut bytes = AlignedVec::with_capacity(uncompressed_size);
unsafe {
// SAFETY: `new_len` is equal to `capacity` and vector is initialized by lz4_flex.
bytes.set_len(uncompressed_size);
}
lz4_flex::decompress_into(archive, &mut bytes)?;
Ok(AlignedBytes::Aligned(bytes))
})
.map_err(|err| {
trc::StoreEvent::DecompressError
.ctx(trc::Key::Value, archive)
.caused_by(trc::location!())
.reason(err)
})
}
impl<T> Serialize for Archiver<T>
where
T: rkyv::Archive
+ for<'a> rkyv::Serialize<
rkyv::api::high::HighSerializer<
rkyv::util::AlignedVec,
rkyv::ser::allocator::ArenaHandle<'a>,
rkyv::rancor::Error,
>,
>,
{
fn serialize(&self) -> trc::Result<Vec<u8>> {
rkyv::to_bytes::<rkyv::rancor::Error>(&self.inner)
.map_err(|err| {
trc::StoreEvent::DeserializeError
.caused_by(trc::location!())
.reason(err)
})
.map(|input| {
let input = input.as_ref();
let input_len = input.len();
let version_offset = ((self.flags & VERSIONED != 0) as usize) * U64_LEN;
let mut bytes = if input_len > COMPRESS_WATERMARK {
let mut bytes = vec![
self.flags | LZ4_COMPRESSED;
lz4_flex::block::get_maximum_output_size(input_len)
+ (U32_LEN * 2)
+ version_offset
+ 1
];
// Compress the data
let compressed_len =
lz4_flex::compress_into(input, &mut bytes[U32_LEN..]).unwrap();
if compressed_len < input_len {
// Prepend the length of the uncompressed data
bytes[..U32_LEN].copy_from_slice(&(input_len as u32).to_le_bytes());
if self.flags & HASHED != 0 {
// Hash the compressed data including the length
let hash =
xxhash_rust::xxh3::xxh3_64(&bytes[..compressed_len + U32_LEN])
as u32;
// Add the hash
bytes[compressed_len + U32_LEN..compressed_len + (U32_LEN * 2)]
.copy_from_slice(&hash.to_be_bytes());
// Truncate to the actual size
bytes.truncate(compressed_len + (U32_LEN * 2) + version_offset + 1);
} else {
// Truncate to the actual size
bytes.truncate(compressed_len + U32_LEN + 1);
}
return bytes;
}
bytes.clear();
bytes
} else {
Vec::with_capacity(input_len + U32_LEN + version_offset + 1)
};
bytes.extend_from_slice(input);
if self.flags & HASHED != 0 {
bytes.extend_from_slice(
&(xxhash_rust::xxh3::xxh3_64(input) as u32).to_be_bytes(),
);
}
if version_offset != 0 {
bytes.extend_from_slice(0u64.to_be_bytes().as_slice());
}
bytes.push(self.flags);
bytes
})
}
}
impl Archive<AlignedBytes> {
#[inline]
pub fn as_bytes(&self) -> &[u8] {
match &self.inner {
AlignedBytes::Vec(bytes) => bytes.as_slice(),
AlignedBytes::Aligned(bytes) => bytes.as_slice(),
}
}
pub fn unarchive<T>(&self) -> trc::Result<&<T as rkyv::Archive>::Archived>
where
T: rkyv::Archive,
T::Archived: for<'a> rkyv::bytecheck::CheckBytes<
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
> + rkyv::Deserialize<T, rkyv::api::high::HighDeserializer<rkyv::rancor::Error>>,
{
let bytes = self.as_bytes();
if self.version != ArchiveVersion::Unversioned {
if bytes.len() >= std::mem::size_of::<T::Archived>() {
// SAFETY: Trusted input with integrity hash
Ok(unsafe { rkyv::access_unchecked::<T::Archived>(bytes) })
} else {
Err(trc::StoreEvent::DataCorruption
.into_err()
.details(format_compact!(
"Archive size mismatch, expected {} bytes but got {} bytes.",
std::mem::size_of::<T::Archived>(),
bytes.len()
))
.ctx(trc::Key::Value, bytes)
.caused_by(trc::location!()))
}
} else {
rkyv::access::<T::Archived, rkyv::rancor::Error>(bytes).map_err(|err| {
trc::StoreEvent::DeserializeError
.ctx(trc::Key::Value, self.as_bytes())
.details("Archive access failed")
.caused_by(trc::location!())
.reason(err)
})
}
}
pub fn unarchive_untrusted<T>(&self) -> trc::Result<&<T as rkyv::Archive>::Archived>
where
T: rkyv::Archive,
T::Archived: for<'a> rkyv::bytecheck::CheckBytes<
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
> + rkyv::Deserialize<T, rkyv::api::high::HighDeserializer<rkyv::rancor::Error>>,
{
let bytes = self.as_bytes();
if bytes.len() >= std::mem::size_of::<T::Archived>() {
rkyv::access::<T::Archived, rkyv::rancor::Error>(bytes).map_err(|err| {
trc::StoreEvent::DeserializeError
.ctx(trc::Key::Value, self.as_bytes())
.details("Archive access failed")
.caused_by(trc::location!())
.reason(err)
})
} else {
Err(trc::StoreEvent::DataCorruption
.into_err()
.details(format_compact!(
"Archive size mismatch, expected {} bytes but got {} bytes.",
std::mem::size_of::<T::Archived>(),
bytes.len()
))
.ctx(trc::Key::Value, bytes)
.caused_by(trc::location!()))
}
}
pub fn deserialize<T>(&self) -> trc::Result<T>
where
T: rkyv::Archive,
T::Archived: for<'a> rkyv::bytecheck::CheckBytes<
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
> + rkyv::Deserialize<T, rkyv::api::high::HighDeserializer<rkyv::rancor::Error>>,
{
self.unarchive::<T>().and_then(|input| {
rkyv::deserialize(input).map_err(|err| {
trc::StoreEvent::DeserializeError
.ctx(trc::Key::Value, self.as_bytes())
.caused_by(trc::location!())
.reason(err)
})
})
}
pub fn deserialize_untrusted<T>(&self) -> trc::Result<T>
where
T: rkyv::Archive,
T::Archived: for<'a> rkyv::bytecheck::CheckBytes<
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
> + rkyv::Deserialize<T, rkyv::api::high::HighDeserializer<rkyv::rancor::Error>>,
{
self.unarchive_untrusted::<T>().and_then(|input| {
rkyv::deserialize(input).map_err(|err| {
trc::StoreEvent::DeserializeError
.ctx(trc::Key::Value, self.as_bytes())
.caused_by(trc::location!())
.reason(err)
})
})
}
pub fn to_unarchived<T>(&self) -> trc::Result<Archive<&<T as rkyv::Archive>::Archived>>
where
T: rkyv::Archive,
T::Archived: for<'a> rkyv::bytecheck::CheckBytes<
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
> + rkyv::Deserialize<T, rkyv::api::high::HighDeserializer<rkyv::rancor::Error>>,
{
self.unarchive::<T>().map(|inner| Archive {
version: self.version,
inner,
})
}
pub fn into_deserialized<T>(&self) -> trc::Result<Archive<T>>
where
T: rkyv::Archive,
T::Archived: for<'a> rkyv::bytecheck::CheckBytes<
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
> + rkyv::Deserialize<T, rkyv::api::high::HighDeserializer<rkyv::rancor::Error>>,
{
self.deserialize::<T>().map(|inner| Archive {
version: self.version,
inner,
})
}
pub fn into_inner(self) -> Vec<u8> {
let mut bytes = match self.inner {
AlignedBytes::Vec(bytes) => bytes,
AlignedBytes::Aligned(bytes) => bytes.to_vec(),
};
match self.version {
ArchiveVersion::Versioned { change_id, hash } => {
bytes.extend_from_slice(&hash.to_be_bytes());
bytes.extend_from_slice(&change_id.to_be_bytes());
bytes.push(MAGIC_MARKER | VERSIONED | HASHED);
}
ArchiveVersion::Hashed { hash } => {
bytes.extend_from_slice(&hash.to_be_bytes());
bytes.push(MAGIC_MARKER | HASHED);
}
ArchiveVersion::Unversioned => {
bytes.push(MAGIC_MARKER);
}
}
bytes
}
pub fn extract_hash(bytes: &[u8]) -> Option<u32> {
let marker = *bytes.last()?;
if marker & VERSIONED != 0 {
bytes
.get(bytes.len() - U32_LEN - U64_LEN - 1..bytes.len() - U64_LEN - 1)
.and_then(|slice| slice.try_into().ok().map(u32::from_be_bytes))
} else if marker & HASHED != 0 {
bytes
.get(bytes.len() - U32_LEN - 1..bytes.len() - 1)
.and_then(|slice| slice.try_into().ok().map(u32::from_be_bytes))
} else {
None
}
}
}
impl<T> Archiver<T>
where
T: rkyv::Archive
+ for<'a> rkyv::Serialize<
rkyv::api::high::HighSerializer<
rkyv::util::AlignedVec,
rkyv::ser::allocator::ArenaHandle<'a>,
rkyv::rancor::Error,
>,
>,
{
pub fn new(inner: T) -> Self {
Self {
inner,
flags: MAGIC_MARKER | HASHED,
}
}
pub fn into_inner(self) -> T {
self.inner
}
pub fn with_version(self) -> Self {
Self {
inner: self.inner,
flags: self.flags | VERSIONED,
}
}
pub fn untrusted(self) -> Self {
Self {
inner: self.inner,
flags: MAGIC_MARKER,
}
}
pub fn serialize_versioned(self) -> trc::Result<(u64, Vec<u8>)> {
self.with_version()
.serialize()
.map(|bytes| ((bytes.len() - U64_LEN - 1) as u64, bytes))
}
}
impl<T> Archive<&T>
where
T: rkyv::Portable
+ for<'a> rkyv::bytecheck::CheckBytes<rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>>
+ Sync
+ Send,
{
pub fn to_deserialized<V>(&self) -> trc::Result<Archive<V>>
where
T: rkyv::Deserialize<V, rkyv::api::high::HighDeserializer<rkyv::rancor::Error>>,
{
rkyv::deserialize::<V, rkyv::rancor::Error>(self.inner)
.map_err(|err| {
trc::StoreEvent::DeserializeError
.caused_by(trc::location!())
.reason(err)
})
.map(|inner| Archive {
version: self.version,
inner,
})
}
pub fn deserialize<V>(&self) -> trc::Result<V>
where
T: rkyv::Deserialize<V, rkyv::api::high::HighDeserializer<rkyv::rancor::Error>>,
{
rkyv::deserialize::<V, rkyv::rancor::Error>(self.inner).map_err(|err| {
trc::StoreEvent::DeserializeError
.caused_by(trc::location!())
.reason(err)
})
}
}
#[inline]
pub fn rkyv_deserialize<T, V>(input: &T) -> trc::Result<V>
where
T: rkyv::Portable
+ for<'a> rkyv::bytecheck::CheckBytes<rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>>
+ Sync
+ Send
+ rkyv::Deserialize<V, rkyv::api::high::HighDeserializer<rkyv::rancor::Error>>,
{
rkyv::deserialize::<V, rkyv::rancor::Error>(input).map_err(|err| {
trc::StoreEvent::DeserializeError
.caused_by(trc::location!())
.reason(err)
})
}
pub fn rkyv_unarchive<T>(input: &[u8]) -> trc::Result<&<T as rkyv::Archive>::Archived>
where
T: rkyv::Archive,
T::Archived: for<'a> rkyv::bytecheck::CheckBytes<rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>>
+ rkyv::Deserialize<T, rkyv::api::high::HighDeserializer<rkyv::rancor::Error>>,
{
rkyv::access::<T::Archived, rkyv::rancor::Error>(input).map_err(|err| {
trc::StoreEvent::DataCorruption
.caused_by(trc::location!())
.ctx(trc::Key::Value, input)
.reason(err)
})
}
impl SerializeInfallible for u32 {
fn serialize(&self) -> Vec<u8> {
self.to_be_bytes().to_vec()
}
}
impl SerializeInfallible for u64 {
fn serialize(&self) -> Vec<u8> {
self.to_be_bytes().to_vec()
}
}
impl SerializeInfallible for i64 {
fn serialize(&self) -> Vec<u8> {
self.to_be_bytes().to_vec()
}
}
impl SerializeInfallible for u16 {
fn serialize(&self) -> Vec<u8> {
self.to_be_bytes().to_vec()
}
}
impl SerializeInfallible for f64 {
fn serialize(&self) -> Vec<u8> {
self.to_be_bytes().to_vec()
}
}
impl SerializeInfallible for &str {
fn serialize(&self) -> Vec<u8> {
self.as_bytes().to_vec()
}
}
impl Deserialize for String {
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
Ok(String::from_utf8_lossy(bytes).into_owned())
}
fn deserialize_owned(bytes: Vec<u8>) -> trc::Result<Self> {
Ok(String::from_utf8(bytes)
.unwrap_or_else(|err| String::from_utf8_lossy(err.as_bytes()).into_owned()))
}
}
impl Deserialize for u64 {
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
Ok(u64::from_be_bytes(bytes.try_into().map_err(|_| {
trc::StoreEvent::DataCorruption.caused_by(trc::location!())
})?))
}
}
impl Deserialize for i64 {
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
Ok(i64::from_be_bytes(bytes.try_into().map_err(|_| {
trc::StoreEvent::DataCorruption.caused_by(trc::location!())
})?))
}
}
impl Deserialize for u32 {
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
Ok(u32::from_be_bytes(bytes.try_into().map_err(|_| {
trc::StoreEvent::DataCorruption.caused_by(trc::location!())
})?))
}
}
impl<T> From<Value<'static>> for Archive<T> {
fn from(_: Value<'static>) -> Self {
unimplemented!()
}
}
impl Default for Archive<AlignedBytes> {
fn default() -> Self {
Archive {
version: ArchiveVersion::Unversioned,
inner: AlignedBytes::Aligned(AlignedVec::new()),
}
}
}
impl Serialize for RoaringBitmap {
fn serialize(&self) -> trc::Result<Vec<u8>> {
let mut bytes = Vec::with_capacity(self.serialized_size());
self.serialize_into(&mut bytes)
.map_err(|err| {
trc::StoreEvent::UnexpectedError
.caused_by(trc::location!())
.reason(err)
})
.map(|_| bytes)
}
}
impl Deserialize for RoaringBitmap {
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
RoaringBitmap::deserialize_from(bytes).map_err(|err| {
trc::StoreEvent::DeserializeError
.caused_by(trc::location!())
.reason(err)
})
}
}
impl Serialize for RoaringTreemap {
fn serialize(&self) -> trc::Result<Vec<u8>> {
let mut bytes = Vec::with_capacity(self.serialized_size());
self.serialize_into(&mut bytes)
.map_err(|err| {
trc::StoreEvent::UnexpectedError
.caused_by(trc::location!())
.reason(err)
})
.map(|_| bytes)
}
}
impl Deserialize for RoaringTreemap {
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
RoaringTreemap::deserialize_from(bytes).map_err(|err| {
trc::StoreEvent::DeserializeError
.caused_by(trc::location!())
.reason(err)
})
}
}