use std::collections::BTreeSet;
use crate::coroutine::{Coroutine, Fault, Value};
use crate::faults::{
transfer_fault_endpoint_not_owned, transfer_fault_expect_endpoint_register,
transfer_fault_expect_nat_target, transfer_fault_target_id_out_of_range,
};
use crate::instr::Endpoint;
use crate::session::{OwnershipScope, SessionId};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TransferRequest {
pub endpoint: Endpoint,
pub target_id: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct DelegationReceipt {
pub receipt_id: u64,
pub session: SessionId,
pub endpoint: Endpoint,
pub from_coro: usize,
pub to_coro: usize,
pub scope: OwnershipScope,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct DelegationAuditRecord {
pub tick: u64,
pub receipt: DelegationReceipt,
pub status: DelegationStatus,
pub reason: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum DelegationStatus {
Committed,
RolledBack,
}
pub fn decode_transfer_request(
coro: &Coroutine,
role: &str,
endpoint_reg: u16,
target_reg: u16,
) -> Result<TransferRequest, Fault> {
let endpoint_val = coro
.regs
.get(usize::from(endpoint_reg))
.ok_or(Fault::OutOfRegisters)?
.clone();
let endpoint = match endpoint_val {
Value::Endpoint(endpoint) => endpoint,
_ => return Err(transfer_fault_expect_endpoint_register(role)),
};
let target_val = coro
.regs
.get(usize::from(target_reg))
.ok_or(Fault::OutOfRegisters)?
.clone();
let target_id = match target_val {
Value::Nat(v) => {
usize::try_from(v).map_err(|_| transfer_fault_target_id_out_of_range(role))?
}
_ => return Err(transfer_fault_expect_nat_target(role)),
};
if !coro.owned_endpoints.contains(&endpoint) {
return Err(transfer_fault_endpoint_not_owned());
}
Ok(TransferRequest {
endpoint,
target_id,
})
}
#[must_use]
pub fn delegation_scope_for_endpoint(endpoint: &Endpoint) -> OwnershipScope {
OwnershipScope::Fragments(BTreeSet::from([endpoint.role.clone()]))
}
#[must_use]
pub fn delegation_receipt(
receipt_id: u64,
endpoint: Endpoint,
from_coro: usize,
to_coro: usize,
) -> DelegationReceipt {
DelegationReceipt {
receipt_id,
session: endpoint.sid,
scope: delegation_scope_for_endpoint(&endpoint),
endpoint,
from_coro,
to_coro,
}
}
pub fn validate_delegation_coherence(
source: &Coroutine,
target: &Coroutine,
endpoint: &Endpoint,
role: &str,
) -> Result<(), Fault> {
if source.session_id != endpoint.sid {
return Err(Fault::Transfer {
message: format!(
"{role}: delegated endpoint {}:{} is not owned by source session {}",
endpoint.sid, endpoint.role, source.session_id
),
});
}
if target.session_id != endpoint.sid {
return Err(Fault::Transfer {
message: format!(
"{role}: target coroutine session {} mismatches delegated endpoint session {}",
target.session_id, endpoint.sid
),
});
}
Ok(())
}
pub fn move_endpoint_bundle(
endpoint: &Endpoint,
source: &mut Coroutine,
target: Option<&mut Coroutine>,
) -> Result<(), Fault> {
if !source.owned_endpoints.contains(endpoint) {
return Err(transfer_fault_endpoint_not_owned());
}
let mut moved_tokens = Vec::new();
source.progress_tokens.retain(|token| {
if token.endpoint == *endpoint {
moved_tokens.push(token.clone());
false
} else {
true
}
});
let mut moved_knowledge = Vec::new();
source.knowledge_set.retain(|fact| {
if fact.endpoint == *endpoint {
moved_knowledge.push(fact.clone());
false
} else {
true
}
});
source.owned_endpoints.retain(|e| e != endpoint);
if let Some(target) = target {
target.owned_endpoints.push(endpoint.clone());
target.progress_tokens.extend(moved_tokens);
target.knowledge_set.extend(moved_knowledge);
} else {
source.owned_endpoints.push(endpoint.clone());
source.progress_tokens.extend(moved_tokens);
source.knowledge_set.extend(moved_knowledge);
}
Ok(())
}
#[must_use]
pub fn endpoint_owner_ids(coroutines: &[Coroutine], endpoint: &Endpoint) -> Vec<usize> {
coroutines
.iter()
.filter_map(|coro| coro.owned_endpoints.contains(endpoint).then_some(coro.id))
.collect()
}