lsp_max/runtime/typestate/
mod.rs1use crate::runtime::sha256::{sha256, validate_and_reconstruct_chain_checked};
4use std::fmt::{Debug, Formatter};
5use std::marker::PhantomData;
6
7pub struct DeterministicSnapshot {
8 pub id: lsp_max_protocol::SnapshotId,
9 pub timestamp: u64,
10}
11
12impl DeterministicSnapshot {
13 pub fn new() -> Self {
14 let timestamp = std::time::SystemTime::now()
15 .duration_since(std::time::UNIX_EPOCH)
16 .unwrap_or(std::time::Duration::ZERO)
17 .as_secs();
18 Self {
19 id: lsp_max_protocol::SnapshotId(format!("snap-{}", timestamp)),
20 timestamp,
21 }
22 }
23}
24
25impl Default for DeterministicSnapshot {
26 fn default() -> Self {
27 Self::new()
28 }
29}
30
31pub trait Law {
33 type Error;
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub struct AccessAdmissionLaw;
39
40impl Law for AccessAdmissionLaw {
41 type Error = &'static str;
42}
43
44pub trait Phase {}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
48pub struct Uninitialized;
49impl Phase for Uninitialized {}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
52pub struct Initializing;
53impl Phase for Initializing {}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
56pub struct Initialized;
57impl Phase for Initialized {}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
60pub struct ShutDown;
61impl Phase for ShutDown {}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
64pub struct Exited;
65impl Phase for Exited {}
66
67pub trait Data {}
69
70#[derive(Debug, Clone, PartialEq, Eq, Default)]
71pub struct EmptyData {
72 pub client_capabilities: Option<serde_json::Value>,
73 pub server_capabilities: Option<serde_json::Value>,
74}
75impl Data for EmptyData {}
76
77#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
78pub struct InitializingData {
79 pub client_capabilities: serde_json::Value,
80}
81impl Data for InitializingData {}
82
83#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
84pub struct InitializedData {
85 pub client_capabilities: serde_json::Value,
86 pub server_capabilities: serde_json::Value,
87}
88impl Data for InitializedData {}
89
90pub struct Machine<L: Law, P: Phase, D: Data> {
92 pub _law: PhantomData<L>,
93 pub phase: P,
94 pub data: D,
95}
96
97impl<L: Law, P: Phase + Debug, D: Data + Debug> Debug for Machine<L, P, D> {
98 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
99 f.debug_struct("Machine")
100 .field("phase", &self.phase)
101 .field("data", &self.data)
102 .finish()
103 }
104}
105
106impl<L: Law, P: Phase + Clone, D: Data + Clone> Clone for Machine<L, P, D> {
107 fn clone(&self) -> Self {
108 Self {
109 _law: PhantomData,
110 phase: self.phase.clone(),
111 data: self.data.clone(),
112 }
113 }
114}
115
116impl<L: Law, P: Phase, D: Data> Machine<L, P, D> {
117 pub const fn new(phase: P, data: D) -> Self {
119 Self {
120 _law: PhantomData,
121 phase,
122 data,
123 }
124 }
125}
126
127#[derive(Debug, Clone, PartialEq, Eq)]
129pub enum ChainError {
130 EmptyHistory,
132 HashMismatch {
134 index: usize,
135 expected: String,
136 got: String,
137 },
138 ReceiptIdMismatch { index: usize, detail: String },
140 InsufficientHistory { required: usize, got: usize },
142 ExcessHistory { extra: usize },
144 ParseError { index: usize, detail: String },
146}
147
148impl std::fmt::Display for ChainError {
149 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150 match self {
151 ChainError::EmptyHistory => write!(f, "Receipt chain is empty"),
152 ChainError::HashMismatch {
153 index,
154 expected,
155 got,
156 } => write!(
157 f,
158 "Hash mismatch at index {}: expected '{}', got '{}'",
159 index, expected, got
160 ),
161 ChainError::ReceiptIdMismatch { index, detail } => {
162 write!(f, "Receipt ID mismatch at index {}: {}", index, detail)
163 }
164 ChainError::InsufficientHistory { required, got } => write!(
165 f,
166 "Insufficient history: required {} receipts, got {}",
167 required, got
168 ),
169 ChainError::ExcessHistory { extra } => {
170 write!(f, "History contains {} unexpected extra receipts", extra)
171 }
172 ChainError::ParseError { index, detail } => {
173 write!(f, "Parse error at index {}: {}", index, detail)
174 }
175 }
176 }
177}
178
179fn chain_err_from_str(e: String) -> ChainError {
181 if e.contains("must not be empty") || e.contains("History must not be empty") {
182 ChainError::EmptyHistory
183 } else if e.contains("Hash mismatch") {
184 let index = e
185 .split_whitespace()
186 .find_map(|w| w.trim_end_matches(':').parse::<usize>().ok())
187 .unwrap_or(0);
188 ChainError::HashMismatch {
189 index,
190 expected: String::new(),
191 got: e,
192 }
193 } else if e.contains("Insufficient history") {
194 ChainError::InsufficientHistory {
195 required: 0,
196 got: 0,
197 }
198 } else if e.contains("unexpected") {
199 ChainError::ExcessHistory { extra: 0 }
200 } else {
201 ChainError::ReceiptIdMismatch {
202 index: 0,
203 detail: e,
204 }
205 }
206}
207
208pub trait TypestateKernel<L: Law, P: Phase, D: Data> {
210 type Input;
211 type OutputPhase: Phase;
212 type OutputData: Data;
213 type Receipt;
214
215 fn validate(&self, input: &Self::Input) -> Result<(), L::Error>;
217
218 fn select(&self, input: &Self::Input) -> Self::OutputPhase;
220
221 fn admit(
223 self,
224 input: Self::Input,
225 ) -> Result<Machine<L, Self::OutputPhase, Self::OutputData>, L::Error>;
226
227 fn receipt(&self) -> Self::Receipt;
229
230 fn exit(self) -> D;
232
233 fn replay(history: Vec<Self::Receipt>) -> Result<Self, ChainError>
236 where
237 Self: Sized;
238}
239
240mod machine;
245
246#[cfg(test)]
247mod tests;