use crate::runtime::sha256::{sha256, validate_and_reconstruct_chain_checked};
use std::fmt::{Debug, Formatter};
use std::marker::PhantomData;
pub struct DeterministicSnapshot {
pub id: lsp_max_protocol::SnapshotId,
pub timestamp: u64,
}
impl DeterministicSnapshot {
pub fn new() -> Self {
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or(std::time::Duration::ZERO)
.as_secs();
Self {
id: lsp_max_protocol::SnapshotId(format!("snap-{}", timestamp)),
timestamp,
}
}
}
impl Default for DeterministicSnapshot {
fn default() -> Self {
Self::new()
}
}
pub trait Law {
type Error;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AccessAdmissionLaw;
impl Law for AccessAdmissionLaw {
type Error = &'static str;
}
pub trait Phase {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Uninitialized;
impl Phase for Uninitialized {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Initializing;
impl Phase for Initializing {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Initialized;
impl Phase for Initialized {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct ShutDown;
impl Phase for ShutDown {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Exited;
impl Phase for Exited {}
pub trait Data {}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct EmptyData {
pub client_capabilities: Option<serde_json::Value>,
pub server_capabilities: Option<serde_json::Value>,
}
impl Data for EmptyData {}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct InitializingData {
pub client_capabilities: serde_json::Value,
}
impl Data for InitializingData {}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct InitializedData {
pub client_capabilities: serde_json::Value,
pub server_capabilities: serde_json::Value,
}
impl Data for InitializedData {}
pub struct Machine<L: Law, P: Phase, D: Data> {
pub _law: PhantomData<L>,
pub phase: P,
pub data: D,
}
impl<L: Law, P: Phase + Debug, D: Data + Debug> Debug for Machine<L, P, D> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Machine")
.field("phase", &self.phase)
.field("data", &self.data)
.finish()
}
}
impl<L: Law, P: Phase + Clone, D: Data + Clone> Clone for Machine<L, P, D> {
fn clone(&self) -> Self {
Self {
_law: PhantomData,
phase: self.phase.clone(),
data: self.data.clone(),
}
}
}
impl<L: Law, P: Phase, D: Data> Machine<L, P, D> {
pub const fn new(phase: P, data: D) -> Self {
Self {
_law: PhantomData,
phase,
data,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ChainError {
EmptyHistory,
HashMismatch {
index: usize,
expected: String,
got: String,
},
ReceiptIdMismatch { index: usize, detail: String },
InsufficientHistory { required: usize, got: usize },
ExcessHistory { extra: usize },
ParseError { index: usize, detail: String },
}
impl std::fmt::Display for ChainError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ChainError::EmptyHistory => write!(f, "Receipt chain is empty"),
ChainError::HashMismatch {
index,
expected,
got,
} => write!(
f,
"Hash mismatch at index {}: expected '{}', got '{}'",
index, expected, got
),
ChainError::ReceiptIdMismatch { index, detail } => {
write!(f, "Receipt ID mismatch at index {}: {}", index, detail)
}
ChainError::InsufficientHistory { required, got } => write!(
f,
"Insufficient history: required {} receipts, got {}",
required, got
),
ChainError::ExcessHistory { extra } => {
write!(f, "History contains {} unexpected extra receipts", extra)
}
ChainError::ParseError { index, detail } => {
write!(f, "Parse error at index {}: {}", index, detail)
}
}
}
}
fn chain_err_from_str(e: String) -> ChainError {
if e.contains("must not be empty") || e.contains("History must not be empty") {
ChainError::EmptyHistory
} else if e.contains("Hash mismatch") {
let index = e
.split_whitespace()
.find_map(|w| w.trim_end_matches(':').parse::<usize>().ok())
.unwrap_or(0);
ChainError::HashMismatch {
index,
expected: String::new(),
got: e,
}
} else if e.contains("Insufficient history") {
ChainError::InsufficientHistory {
required: 0,
got: 0,
}
} else if e.contains("unexpected") {
ChainError::ExcessHistory { extra: 0 }
} else {
ChainError::ReceiptIdMismatch {
index: 0,
detail: e,
}
}
}
pub trait TypestateKernel<L: Law, P: Phase, D: Data> {
type Input;
type OutputPhase: Phase;
type OutputData: Data;
type Receipt;
fn validate(&self, input: &Self::Input) -> Result<(), L::Error>;
fn select(&self, input: &Self::Input) -> Self::OutputPhase;
fn admit(
self,
input: Self::Input,
) -> Result<Machine<L, Self::OutputPhase, Self::OutputData>, L::Error>;
fn receipt(&self) -> Self::Receipt;
fn exit(self) -> D;
fn replay(history: Vec<Self::Receipt>) -> Result<Self, ChainError>
where
Self: Sized;
}
mod machine;
#[cfg(test)]
mod tests;