454 lines
16 KiB
Rust
454 lines
16 KiB
Rust
// file: crates/ksp-store-api/src/model/raw_primitives.rs
|
|
// version: 2
|
|
|
|
/// Maximum complete RAW account-data length admitted by the Store API.
|
|
///
|
|
/// This is a Store admission guard, not a Solana protocol-size claim.
|
|
pub const MAX_RAW_ACCOUNT_DATA_BYTES: usize = 16 * 1024 * 1024;
|
|
/// Maximum UTF-8 byte length accepted for one safe logical RAW/provenance code.
|
|
pub const MAX_RAW_CODE_BYTES: usize = 128;
|
|
/// Maximum KSP-owned canonical RAW payload admitted by the Store API.
|
|
///
|
|
/// This is a Store admission guard, not a Solana protocol-size claim.
|
|
pub const MAX_RAW_PAYLOAD_BYTES: usize = 16 * 1024 * 1024;
|
|
/// Maximum source-wire payload size recorded as acquisition metadata.
|
|
///
|
|
/// The source payload itself is never retained by this metadata field.
|
|
pub const MAX_RAW_SOURCE_PAYLOAD_BYTES: u64 = 64 * 1024 * 1024;
|
|
/// Maximum supported Unix millisecond timestamp (`9999-12-31T23:59:59.999Z`).
|
|
pub const MAX_RAW_UNIX_MILLIS: u64 = 253_402_300_799_999;
|
|
|
|
/// Fixed-size digest identifying canonical or source bytes without retaining them.
|
|
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
|
|
pub struct RawContentHash([u8; 32]);
|
|
|
|
impl RawContentHash {
|
|
/// Creates one opaque 32-byte KSP content digest.
|
|
#[must_use]
|
|
pub const fn new(bytes: [u8; 32]) -> Self {
|
|
return Self(bytes);
|
|
}
|
|
|
|
/// Returns the exact digest bytes.
|
|
#[must_use]
|
|
pub const fn as_bytes(&self) -> &[u8; 32] {
|
|
return &self.0;
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Debug for RawContentHash {
|
|
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
return formatter.write_str("RawContentHash(..)");
|
|
}
|
|
}
|
|
|
|
/// Stable deterministic idempotence key for one persisted acquisition observation.
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
pub struct RawObservationKey([u8; 32]);
|
|
|
|
impl RawObservationKey {
|
|
/// Creates one producer-owned deterministic observation key.
|
|
#[must_use]
|
|
pub const fn new(bytes: [u8; 32]) -> Self {
|
|
return Self(bytes);
|
|
}
|
|
|
|
/// Returns the exact observation-key bytes.
|
|
#[must_use]
|
|
pub const fn as_bytes(&self) -> &[u8; 32] {
|
|
return &self.0;
|
|
}
|
|
}
|
|
|
|
/// Canonical 64-byte Solana transaction signature used by Store identities.
|
|
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
|
pub struct RawTransactionSignature([u8; 64]);
|
|
|
|
impl RawTransactionSignature {
|
|
/// Creates one canonical signature from already-decoded Solana signature bytes.
|
|
#[must_use]
|
|
pub const fn new(bytes: [u8; 64]) -> Self {
|
|
return Self(bytes);
|
|
}
|
|
|
|
/// Returns the exact signature bytes.
|
|
#[must_use]
|
|
pub const fn as_bytes(&self) -> &[u8; 64] {
|
|
return &self.0;
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Debug for RawTransactionSignature {
|
|
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
return formatter.write_str("RawTransactionSignature(..)");
|
|
}
|
|
}
|
|
|
|
/// Bounded logical network/cluster identifier used in backend-independent Store identities.
|
|
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
|
pub struct RawNetworkId(std::string::String);
|
|
|
|
impl RawNetworkId {
|
|
/// Creates one safe non-empty network/cluster identifier.
|
|
pub fn new(value: impl std::convert::Into<std::string::String>) -> ksp_core_lib::Result<Self> {
|
|
let value = value.into();
|
|
if !valid_raw_code(value.as_str()) {
|
|
return std::result::Result::Err(raw_model_error("network"));
|
|
}
|
|
return std::result::Result::Ok(Self(value));
|
|
}
|
|
|
|
/// Returns the network/cluster identifier.
|
|
#[must_use]
|
|
pub fn as_str(&self) -> &str {
|
|
return self.0.as_str();
|
|
}
|
|
}
|
|
|
|
/// Bounded logical code used by acquisition provenance fields.
|
|
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
|
pub struct RawProvenanceCode(std::string::String);
|
|
|
|
impl RawProvenanceCode {
|
|
/// Creates one safe non-empty provenance code.
|
|
pub fn new(value: impl std::convert::Into<std::string::String>) -> ksp_core_lib::Result<Self> {
|
|
let value = value.into();
|
|
if !valid_raw_code(value.as_str()) {
|
|
return std::result::Result::Err(raw_provenance_error("code"));
|
|
}
|
|
return std::result::Result::Ok(Self(value));
|
|
}
|
|
|
|
/// Returns the validated provenance code.
|
|
#[must_use]
|
|
pub fn as_str(&self) -> &str {
|
|
return self.0.as_str();
|
|
}
|
|
}
|
|
|
|
/// Bounded identifier of one KSP-owned source-independent RAW persistence format.
|
|
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
|
pub struct RawFormatId(std::string::String);
|
|
|
|
impl RawFormatId {
|
|
/// Creates one safe non-empty RAW format identifier.
|
|
pub fn new(value: impl std::convert::Into<std::string::String>) -> ksp_core_lib::Result<Self> {
|
|
let value = value.into();
|
|
if !valid_raw_code(value.as_str()) {
|
|
return std::result::Result::Err(raw_payload_error("format_id"));
|
|
}
|
|
return std::result::Result::Ok(Self(value));
|
|
}
|
|
|
|
/// Returns the KSP-owned RAW format identifier.
|
|
#[must_use]
|
|
pub fn as_str(&self) -> &str {
|
|
return self.0.as_str();
|
|
}
|
|
}
|
|
|
|
/// Bounded UTC timestamp represented as whole milliseconds since Unix epoch.
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
|
pub struct RawTimestamp {
|
|
unix_millis: u64,
|
|
}
|
|
|
|
impl RawTimestamp {
|
|
/// Creates a bounded UTC timestamp from Unix milliseconds.
|
|
pub fn from_unix_millis(unix_millis: u64) -> ksp_core_lib::Result<Self> {
|
|
if unix_millis > crate::MAX_RAW_UNIX_MILLIS {
|
|
return std::result::Result::Err(raw_model_error("timestamp"));
|
|
}
|
|
return std::result::Result::Ok(Self { unix_millis });
|
|
}
|
|
|
|
/// Returns whole milliseconds since Unix epoch.
|
|
#[must_use]
|
|
pub const fn unix_millis(&self) -> u64 {
|
|
return self.unix_millis;
|
|
}
|
|
}
|
|
|
|
/// Origin category describing why one acquisition was performed.
|
|
#[non_exhaustive]
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
pub enum RawAcquisitionOrigin {
|
|
/// Historical acquisition intended to fill missing durable data.
|
|
Backfill,
|
|
/// Explicit import from a non-live source controlled by the caller.
|
|
Import,
|
|
/// Live acquisition from a currently active transport/session.
|
|
Live,
|
|
/// Explicit repair or reconciliation of previously known data.
|
|
Repair,
|
|
/// Explicit replay of an already-known source or archived acquisition.
|
|
Replay,
|
|
}
|
|
|
|
/// Safe source-independent acquisition provenance attached to one persisted observation.
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
pub struct RawAcquisitionProvenance {
|
|
acquisition_method: crate::RawProvenanceCode,
|
|
capture_session_id: std::option::Option<crate::RawProvenanceCode>,
|
|
commitment: std::option::Option<crate::RawProvenanceCode>,
|
|
endpoint_id: std::option::Option<crate::RawProvenanceCode>,
|
|
filter_id: std::option::Option<crate::RawProvenanceCode>,
|
|
observed_at: std::option::Option<crate::RawTimestamp>,
|
|
origin: crate::RawAcquisitionOrigin,
|
|
protocol: crate::RawProvenanceCode,
|
|
provider: crate::RawProvenanceCode,
|
|
received_at: crate::RawTimestamp,
|
|
source_payload_hash: std::option::Option<crate::RawContentHash>,
|
|
source_payload_size_bytes: std::option::Option<u64>,
|
|
}
|
|
|
|
impl RawAcquisitionProvenance {
|
|
/// Creates one successful acquisition provenance record with only mandatory safe metadata.
|
|
#[must_use]
|
|
pub fn new(
|
|
provider: crate::RawProvenanceCode,
|
|
protocol: crate::RawProvenanceCode,
|
|
acquisition_method: crate::RawProvenanceCode,
|
|
origin: crate::RawAcquisitionOrigin,
|
|
received_at: crate::RawTimestamp,
|
|
) -> Self {
|
|
return Self {
|
|
acquisition_method,
|
|
capture_session_id: std::option::Option::None,
|
|
commitment: std::option::Option::None,
|
|
endpoint_id: std::option::Option::None,
|
|
filter_id: std::option::Option::None,
|
|
observed_at: std::option::Option::None,
|
|
origin,
|
|
protocol,
|
|
provider,
|
|
received_at,
|
|
source_payload_hash: std::option::Option::None,
|
|
source_payload_size_bytes: std::option::Option::None,
|
|
};
|
|
}
|
|
|
|
/// Attaches one safe logical capture/session identifier.
|
|
#[must_use]
|
|
pub fn with_capture_session_id(mut self, value: crate::RawProvenanceCode) -> Self {
|
|
self.capture_session_id = std::option::Option::Some(value);
|
|
return self;
|
|
}
|
|
|
|
/// Attaches one safe commitment code captured at acquisition.
|
|
#[must_use]
|
|
pub fn with_commitment(mut self, value: crate::RawProvenanceCode) -> Self {
|
|
self.commitment = std::option::Option::Some(value);
|
|
return self;
|
|
}
|
|
|
|
/// Attaches one Config-owned logical endpoint identifier.
|
|
#[must_use]
|
|
pub fn with_endpoint_id(mut self, value: crate::RawProvenanceCode) -> Self {
|
|
self.endpoint_id = std::option::Option::Some(value);
|
|
return self;
|
|
}
|
|
|
|
/// Attaches one safe logical filter identifier.
|
|
#[must_use]
|
|
pub fn with_filter_id(mut self, value: crate::RawProvenanceCode) -> Self {
|
|
self.filter_id = std::option::Option::Some(value);
|
|
return self;
|
|
}
|
|
|
|
/// Attaches the source observation timestamp when it does not follow local receipt.
|
|
pub fn try_with_observed_at(mut self, value: crate::RawTimestamp) -> ksp_core_lib::Result<Self> {
|
|
if value > self.received_at {
|
|
return std::result::Result::Err(raw_provenance_error("observed_at"));
|
|
}
|
|
self.observed_at = std::option::Option::Some(value);
|
|
return std::result::Result::Ok(self);
|
|
}
|
|
|
|
/// Attaches the digest of source-specific bytes without retaining those bytes.
|
|
#[must_use]
|
|
pub fn with_source_payload_hash(mut self, value: crate::RawContentHash) -> Self {
|
|
self.source_payload_hash = std::option::Option::Some(value);
|
|
return self;
|
|
}
|
|
|
|
/// Attaches the bounded source-wire payload size.
|
|
pub fn try_with_source_payload_size_bytes(mut self, value: u64) -> ksp_core_lib::Result<Self> {
|
|
if value > crate::MAX_RAW_SOURCE_PAYLOAD_BYTES {
|
|
return std::result::Result::Err(raw_provenance_error("source_payload_size_bytes"));
|
|
}
|
|
self.source_payload_size_bytes = std::option::Option::Some(value);
|
|
return std::result::Result::Ok(self);
|
|
}
|
|
|
|
/// Returns the logical acquisition method code.
|
|
#[must_use]
|
|
pub const fn acquisition_method(&self) -> &crate::RawProvenanceCode {
|
|
return &self.acquisition_method;
|
|
}
|
|
|
|
/// Returns the optional logical capture/session identifier.
|
|
#[must_use]
|
|
pub fn capture_session_id(&self) -> std::option::Option<&crate::RawProvenanceCode> {
|
|
return self.capture_session_id.as_ref();
|
|
}
|
|
|
|
/// Returns the optional commitment code captured at acquisition.
|
|
#[must_use]
|
|
pub fn commitment(&self) -> std::option::Option<&crate::RawProvenanceCode> {
|
|
return self.commitment.as_ref();
|
|
}
|
|
|
|
/// Returns the optional Config-owned logical endpoint identifier.
|
|
#[must_use]
|
|
pub fn endpoint_id(&self) -> std::option::Option<&crate::RawProvenanceCode> {
|
|
return self.endpoint_id.as_ref();
|
|
}
|
|
|
|
/// Returns the optional logical filter identifier.
|
|
#[must_use]
|
|
pub fn filter_id(&self) -> std::option::Option<&crate::RawProvenanceCode> {
|
|
return self.filter_id.as_ref();
|
|
}
|
|
|
|
/// Returns the optional source observation timestamp when the source supplies one.
|
|
#[must_use]
|
|
pub const fn observed_at(&self) -> std::option::Option<crate::RawTimestamp> {
|
|
return self.observed_at;
|
|
}
|
|
|
|
/// Returns the acquisition origin category.
|
|
#[must_use]
|
|
pub const fn origin(&self) -> crate::RawAcquisitionOrigin {
|
|
return self.origin;
|
|
}
|
|
|
|
/// Returns the logical transport/protocol code.
|
|
#[must_use]
|
|
pub fn protocol(&self) -> &crate::RawProvenanceCode {
|
|
return &self.protocol;
|
|
}
|
|
|
|
/// Returns the safe provider code.
|
|
#[must_use]
|
|
pub fn provider(&self) -> &crate::RawProvenanceCode {
|
|
return &self.provider;
|
|
}
|
|
|
|
/// Returns the local receipt timestamp.
|
|
#[must_use]
|
|
pub const fn received_at(&self) -> crate::RawTimestamp {
|
|
return self.received_at;
|
|
}
|
|
|
|
/// Returns the optional digest of source-specific bytes without retaining those bytes.
|
|
#[must_use]
|
|
pub const fn source_payload_hash(&self) -> std::option::Option<crate::RawContentHash> {
|
|
return self.source_payload_hash;
|
|
}
|
|
|
|
/// Returns the optional source-wire payload size.
|
|
#[must_use]
|
|
pub const fn source_payload_size_bytes(&self) -> std::option::Option<u64> {
|
|
return self.source_payload_size_bytes;
|
|
}
|
|
}
|
|
|
|
/// Bounded source-independent KSP RAW persistence payload.
|
|
pub struct RawPayload {
|
|
bytes: std::boxed::Box<[u8]>,
|
|
content_hash: crate::RawContentHash,
|
|
format_id: crate::RawFormatId,
|
|
format_version: u32,
|
|
}
|
|
|
|
impl RawPayload {
|
|
/// Creates one canonical RAW payload after enforcing Store-owned admission invariants.
|
|
///
|
|
/// The supplied bytes must already use the KSP-owned source-independent format identified
|
|
/// by `format_id` and `format_version`; this constructor performs no transport conversion.
|
|
pub fn try_new(
|
|
format_id: crate::RawFormatId,
|
|
format_version: u32,
|
|
bytes: std::boxed::Box<[u8]>,
|
|
content_hash: crate::RawContentHash,
|
|
) -> ksp_core_lib::Result<Self> {
|
|
if format_version == 0 {
|
|
return std::result::Result::Err(raw_payload_error("format_version"));
|
|
}
|
|
if bytes.is_empty() || bytes.len() > crate::MAX_RAW_PAYLOAD_BYTES {
|
|
return std::result::Result::Err(
|
|
raw_payload_error("bytes")
|
|
.with_context("actual_len", bytes.len().to_string())
|
|
.with_context("maximum_len", crate::MAX_RAW_PAYLOAD_BYTES.to_string()),
|
|
);
|
|
}
|
|
return std::result::Result::Ok(Self { bytes, content_hash, format_id, format_version });
|
|
}
|
|
|
|
/// Returns the canonical RAW bytes without transport/provider interpretation.
|
|
#[must_use]
|
|
pub fn bytes(&self) -> &[u8] {
|
|
return self.bytes.as_ref();
|
|
}
|
|
|
|
/// Returns the deterministic content digest supplied for these exact canonical bytes.
|
|
#[must_use]
|
|
pub const fn content_hash(&self) -> crate::RawContentHash {
|
|
return self.content_hash;
|
|
}
|
|
|
|
/// Returns the KSP-owned source-independent format identifier.
|
|
#[must_use]
|
|
pub fn format_id(&self) -> &crate::RawFormatId {
|
|
return &self.format_id;
|
|
}
|
|
|
|
/// Returns the KSP-owned format version.
|
|
#[must_use]
|
|
pub const fn format_version(&self) -> u32 {
|
|
return self.format_version;
|
|
}
|
|
|
|
/// Returns the canonical payload length in bytes.
|
|
#[must_use]
|
|
pub fn byte_len(&self) -> usize {
|
|
return self.bytes.len();
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Debug for RawPayload {
|
|
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
return formatter
|
|
.debug_struct("RawPayload")
|
|
.field("format_id", &self.format_id)
|
|
.field("format_version", &self.format_version)
|
|
.field("len", &self.bytes.len())
|
|
.field("content_hash", &self.content_hash)
|
|
.finish();
|
|
}
|
|
}
|
|
|
|
fn raw_model_error(field: &'static str) -> ksp_core_lib::Error {
|
|
return ksp_core_lib::Error::new(crate::ERROR_CODE_RAW_MODEL_INVALID, "invalid backend-agnostic RAW Store model").with_context("field", field);
|
|
}
|
|
|
|
fn raw_payload_error(field: &'static str) -> ksp_core_lib::Error {
|
|
return ksp_core_lib::Error::new(crate::ERROR_CODE_RAW_PAYLOAD_INVALID, "invalid KSP RAW persistence payload").with_context("field", field);
|
|
}
|
|
|
|
fn raw_provenance_error(field: &'static str) -> ksp_core_lib::Error {
|
|
return ksp_core_lib::Error::new(crate::ERROR_CODE_RAW_PROVENANCE_INVALID, "invalid RAW acquisition provenance").with_context("field", field);
|
|
}
|
|
|
|
fn valid_raw_code(value: &str) -> bool {
|
|
if value.is_empty() || value.len() > crate::MAX_RAW_CODE_BYTES {
|
|
return false;
|
|
}
|
|
return value.bytes().all(|byte| return byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-' | b'.' | b':'));
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "../../unit_tests/model/raw_primitives.rs"]
|
|
mod tests;
|