telltale_machine/
transfer_semantics.rs1use std::collections::BTreeSet;
4
5use crate::coroutine::{Coroutine, Fault, Value};
6use crate::faults::{
7 transfer_fault_endpoint_not_owned, transfer_fault_expect_endpoint_register,
8 transfer_fault_expect_nat_target, transfer_fault_target_id_out_of_range,
9};
10use crate::instr::Endpoint;
11use crate::session::{OwnershipScope, SessionId};
12
13#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct TransferRequest {
16 pub endpoint: Endpoint,
18 pub target_id: usize,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
24pub struct DelegationReceipt {
25 pub receipt_id: u64,
27 pub session: SessionId,
29 pub endpoint: Endpoint,
31 pub from_coro: usize,
33 pub to_coro: usize,
35 pub scope: OwnershipScope,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
41pub struct DelegationAuditRecord {
42 pub tick: u64,
44 pub receipt: DelegationReceipt,
46 pub status: DelegationStatus,
48 pub reason: Option<String>,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
54pub enum DelegationStatus {
55 Committed,
57 RolledBack,
59}
60
61pub fn decode_transfer_request(
68 coro: &Coroutine,
69 role: &str,
70 endpoint_reg: u16,
71 target_reg: u16,
72) -> Result<TransferRequest, Fault> {
73 let endpoint_val = coro
74 .regs
75 .get(usize::from(endpoint_reg))
76 .ok_or(Fault::OutOfRegisters)?
77 .clone();
78 let endpoint = match endpoint_val {
79 Value::Endpoint(endpoint) => endpoint,
80 _ => return Err(transfer_fault_expect_endpoint_register(role)),
81 };
82
83 let target_val = coro
84 .regs
85 .get(usize::from(target_reg))
86 .ok_or(Fault::OutOfRegisters)?
87 .clone();
88 let target_id = match target_val {
89 Value::Nat(v) => {
90 usize::try_from(v).map_err(|_| transfer_fault_target_id_out_of_range(role))?
91 }
92 _ => return Err(transfer_fault_expect_nat_target(role)),
93 };
94
95 if !coro.owned_endpoints.contains(&endpoint) {
96 return Err(transfer_fault_endpoint_not_owned());
97 }
98
99 Ok(TransferRequest {
100 endpoint,
101 target_id,
102 })
103}
104
105#[must_use]
107pub fn delegation_scope_for_endpoint(endpoint: &Endpoint) -> OwnershipScope {
108 OwnershipScope::Fragments(BTreeSet::from([endpoint.role.clone()]))
109}
110
111#[must_use]
113pub fn delegation_receipt(
114 receipt_id: u64,
115 endpoint: Endpoint,
116 from_coro: usize,
117 to_coro: usize,
118) -> DelegationReceipt {
119 DelegationReceipt {
120 receipt_id,
121 session: endpoint.sid,
122 scope: delegation_scope_for_endpoint(&endpoint),
123 endpoint,
124 from_coro,
125 to_coro,
126 }
127}
128
129pub fn validate_delegation_coherence(
139 source: &Coroutine,
140 target: &Coroutine,
141 endpoint: &Endpoint,
142 role: &str,
143) -> Result<(), Fault> {
144 if source.session_id != endpoint.sid {
145 return Err(Fault::Transfer {
146 message: format!(
147 "{role}: delegated endpoint {}:{} is not owned by source session {}",
148 endpoint.sid, endpoint.role, source.session_id
149 ),
150 });
151 }
152 if target.session_id != endpoint.sid {
153 return Err(Fault::Transfer {
154 message: format!(
155 "{role}: target coroutine session {} mismatches delegated endpoint session {}",
156 target.session_id, endpoint.sid
157 ),
158 });
159 }
160 Ok(())
161}
162
163pub fn move_endpoint_bundle(
171 endpoint: &Endpoint,
172 source: &mut Coroutine,
173 target: Option<&mut Coroutine>,
174) -> Result<(), Fault> {
175 if !source.owned_endpoints.contains(endpoint) {
176 return Err(transfer_fault_endpoint_not_owned());
177 }
178
179 let mut moved_tokens = Vec::new();
180 source.progress_tokens.retain(|token| {
181 if token.endpoint == *endpoint {
182 moved_tokens.push(token.clone());
183 false
184 } else {
185 true
186 }
187 });
188 let mut moved_knowledge = Vec::new();
189 source.knowledge_set.retain(|fact| {
190 if fact.endpoint == *endpoint {
191 moved_knowledge.push(fact.clone());
192 false
193 } else {
194 true
195 }
196 });
197 source.owned_endpoints.retain(|e| e != endpoint);
198
199 if let Some(target) = target {
200 target.owned_endpoints.push(endpoint.clone());
201 target.progress_tokens.extend(moved_tokens);
202 target.knowledge_set.extend(moved_knowledge);
203 } else {
204 source.owned_endpoints.push(endpoint.clone());
205 source.progress_tokens.extend(moved_tokens);
206 source.knowledge_set.extend(moved_knowledge);
207 }
208
209 Ok(())
210}
211
212#[must_use]
214pub fn endpoint_owner_ids(coroutines: &[Coroutine], endpoint: &Endpoint) -> Vec<usize> {
215 coroutines
216 .iter()
217 .filter_map(|coro| coro.owned_endpoints.contains(endpoint).then_some(coro.id))
218 .collect()
219}