use serde::Serialize;
pub mod scan;
pub mod upgrade;
pub mod verify;
pub use upgrade::{plan_upgrade, upgrade_store, UpgradePlan, UpgradeReport, UpgradeStep};
pub use verify::{verify_path, VerifyOptions};
pub const VECTOR_UPDATE_FORMAT_VERSION: u32 = 1;
pub const VECTOR_MANIFEST_MAGIC: &[u8; 8] = b"HMSVUP01";
pub const VECTOR_ROWS_MAGIC: &[u8; 8] = b"HMSVRW01";
pub const VECTOR_LOG_MAGIC: &[u8; 8] = b"HMSVLG01";
pub const STORAGE_FORMAT_CURRENT: u32 = super::storage::STORAGE_FORMAT_VERSION;
pub const STORAGE_FORMAT_OLDEST: u32 = 2;
pub const STORE_SCHEMA_VERSION: u32 = 4;
pub const CHECKPOINT_SCHEMA_VERSION: u32 = 4;
pub const STORE_SCHEMA_OLDEST: u32 = 2;
pub const TRANSACTION_MAGIC: &[u8] = b"HMS-TXN-2\0";
pub const BINARY_TRANSACTION_MAGIC: &[u8] = b"HMS-TXN-3\0";
pub const TRANSACTION_MAGIC_PREFIX: &[u8] = b"HMS-TXN-";
pub const TEXT_SEGMENT_VERSION: u8 = 2;
pub const TEXT_SEGMENT_MAGIC: &[u8; 4] = b"HMST";
pub const TEXT_SEGMENT_HEADER_LEN: usize = 8;
pub const FRAME_HEADER_LEN: usize = super::storage::HEADER_SIZE;
pub const END_MARK: u32 = super::storage::END_MARK;
pub const MAX_FRAME_PAYLOAD: usize = super::storage::MAX_RAW_FRAME_SIZE;
pub const MANIFEST_MAGIC: &str = super::storage::FORMAT_MAGIC;
pub const AUDIT_ENTRY_LEN: usize = 105;
pub const SALT_LEN: usize = 16;
pub const KDF_MEMORY_KIB: u32 = 19 * 1024;
pub const KDF_ITERATIONS: u32 = 2;
pub const KDF_LANES: u32 = 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FormatVersions {
pub storage_format_current: u32,
pub storage_format_oldest: u32,
pub store_schema: u32,
pub transaction_magic: &'static str,
pub text_segment: u8,
}
pub const CURRENT: FormatVersions = FormatVersions {
storage_format_current: STORAGE_FORMAT_CURRENT,
storage_format_oldest: STORAGE_FORMAT_OLDEST,
store_schema: STORE_SCHEMA_VERSION,
transaction_magic: "HMS-TXN-3",
text_segment: TEXT_SEGMENT_VERSION,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum Severity {
Info,
Warning,
Error,
Unsupported,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Finding {
pub severity: Severity,
pub code: &'static str,
pub file: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub offset: Option<u64>,
pub message: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum Verdict {
Ok,
Recoverable,
Corrupt,
Unsupported,
}
impl Verdict {
pub fn exit_code(self) -> i32 {
match self {
Self::Ok => 0,
Self::Recoverable => 10,
Self::Corrupt => 20,
Self::Unsupported => 30,
}
}
}
#[derive(Debug, Clone, Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct StoreVersions {
#[serde(skip_serializing_if = "Option::is_none")]
pub storage_format: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub store_schema: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text_encoder: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub vector_encoder: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub encrypted: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dimensions: Option<u64>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub transaction_formats: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text_segment: Option<u8>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Report {
pub path: String,
pub versions: StoreVersions,
pub segments: usize,
pub frames: usize,
pub mutations: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub live_documents: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub live_vectors: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub memory_stamped_documents: Option<usize>,
pub payloads_authenticated: bool,
pub checks: Vec<&'static str>,
pub findings: Vec<Finding>,
pub verdict: Verdict,
pub exit_code: i32,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub tenants: Vec<TenantReport>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TenantReport {
pub name: String,
pub report: Report,
}
impl Report {
pub(crate) fn new(path: &std::path::Path) -> Self {
Self {
path: path.display().to_string(),
versions: StoreVersions::default(),
segments: 0,
frames: 0,
mutations: 0,
live_documents: None,
live_vectors: None,
memory_stamped_documents: None,
payloads_authenticated: false,
checks: Vec::new(),
findings: Vec::new(),
verdict: Verdict::Ok,
exit_code: 0,
tenants: Vec::new(),
}
}
pub(crate) fn add(
&mut self,
severity: Severity,
code: &'static str,
file: impl Into<String>,
offset: Option<u64>,
message: impl Into<String>,
) {
self.findings.push(Finding {
severity,
code,
file: file.into(),
offset,
message: message.into(),
});
}
pub(crate) fn finish(&mut self) {
let mut worst = self.findings.iter().map(|f| f.severity).max();
for tenant in &self.tenants {
worst = worst.max(Some(match tenant.report.verdict {
Verdict::Ok => Severity::Info,
Verdict::Recoverable => Severity::Warning,
Verdict::Corrupt => Severity::Error,
Verdict::Unsupported => Severity::Unsupported,
}));
}
self.verdict = match worst {
None | Some(Severity::Info) => Verdict::Ok,
Some(Severity::Warning) => Verdict::Recoverable,
Some(Severity::Error) => Verdict::Corrupt,
Some(Severity::Unsupported) => Verdict::Unsupported,
};
self.exit_code = self.verdict.exit_code();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn constants_match_the_writers() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("documents.text");
crate::core::documents::TextSegment::create(
&path,
#[cfg(feature = "security")]
None,
)
.unwrap();
let bytes = std::fs::read(&path).unwrap();
assert_eq!(&bytes[..4], TEXT_SEGMENT_MAGIC);
assert_eq!(bytes[4], TEXT_SEGMENT_VERSION);
assert_eq!(bytes.len(), TEXT_SEGMENT_HEADER_LEN);
assert_eq!(TRANSACTION_MAGIC, b"HMS-TXN-2\0");
assert_eq!(BINARY_TRANSACTION_MAGIC, b"HMS-TXN-3\0");
assert!(TRANSACTION_MAGIC.starts_with(TRANSACTION_MAGIC_PREFIX));
assert!(BINARY_TRANSACTION_MAGIC.starts_with(TRANSACTION_MAGIC_PREFIX));
assert_eq!(
CURRENT.transaction_magic.as_bytes(),
&BINARY_TRANSACTION_MAGIC[..9]
);
assert_eq!(
crate::core::engine::journal::JournalFormat::for_schema(STORE_SCHEMA_VERSION),
crate::core::engine::journal::JournalFormat::Binary
);
}
}