use std::fmt;
use crate::Result;
use crate::message::Message;
use crate::session::SessionParams;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum TcSchemeKind {
Signature,
Kem,
Dkg,
}
pub trait TcScheme: Send + Sync {
fn name(&self) -> &'static str;
fn kind(&self) -> TcSchemeKind;
fn create_session(&self, params: &SessionParams) -> Result<Box<dyn SessionImpl>>;
}
pub trait SessionImpl: Send {
fn round(&mut self, incoming: &[Message]) -> Result<RoundResult>;
fn result(&self) -> Result<Vec<u8>>;
fn destroy(&mut self);
}
#[derive(Debug, Clone, Default)]
pub struct RoundResult {
pub outgoing: Vec<Message>,
pub complete: bool,
}
impl RoundResult {
pub fn new(outgoing: Vec<Message>, complete: bool) -> Self {
RoundResult { outgoing, complete }
}
pub fn done() -> Self {
RoundResult {
outgoing: Vec::new(),
complete: true,
}
}
}
pub struct RegisteredScheme {
pub scheme: &'static dyn TcScheme,
}
inventory::collect!(RegisteredScheme);
pub fn iter() -> impl Iterator<Item = &'static dyn TcScheme> {
inventory::iter::<RegisteredScheme>().map(|r| r.scheme)
}
pub fn find(name: &str) -> Option<&'static dyn TcScheme> {
iter().find(|s| s.name() == name)
}
#[macro_export]
macro_rules! register_tc_scheme {
($scheme:ident) => {
::inventory::submit! {
$crate::registry::RegisteredScheme { scheme: &$scheme }
}
};
}
impl fmt::Debug for dyn TcScheme {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TcScheme")
.field("name", &self.name())
.field("kind", &self.kind())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error;
use crate::session::SessionParams;
use crate::share::Share;
struct NoopScheme;
impl TcScheme for NoopScheme {
fn name(&self) -> &'static str {
"test-noop"
}
fn kind(&self) -> TcSchemeKind {
TcSchemeKind::Signature
}
fn create_session(&self, params: &SessionParams) -> Result<Box<dyn SessionImpl>> {
Ok(Box::new(NoopSession {
result: params
.message
.clone()
.unwrap_or_else(|| b"noop-result".to_vec()),
done: false,
}))
}
}
struct NoopSession {
result: Vec<u8>,
done: bool,
}
impl SessionImpl for NoopSession {
fn round(&mut self, _incoming: &[Message]) -> Result<RoundResult> {
self.done = true;
Ok(RoundResult::done())
}
fn result(&self) -> Result<Vec<u8>> {
if !self.done {
return Err(error::SessionNotCompleteSnafu {}.build());
}
Ok(self.result.clone())
}
fn destroy(&mut self) {
self.result.fill(0);
}
}
inventory::submit! {
RegisteredScheme { scheme: &NoopScheme as &dyn TcScheme }
}
fn make_params() -> SessionParams {
use crate::party::{Party, PartyList};
SessionParams {
scheme: "test-noop".to_string(),
parties: PartyList::from_parties(vec![
Party::inproc("a"),
Party::inproc("b"),
Party::inproc("c"),
]),
threshold: 2,
this_party_idx: 0,
local_share: None,
message: None,
}
}
#[test]
fn registry_finds_registered_scheme() {
let scheme = find("test-noop").expect("noop scheme must be registered");
assert_eq!(scheme.name(), "test-noop");
assert_eq!(scheme.kind(), TcSchemeKind::Signature);
}
#[test]
fn registry_find_missing_returns_none() {
assert!(find("does-not-exist").is_none());
}
#[test]
fn scheme_create_session_runs_full_lifecycle() {
let scheme = find("test-noop").expect("registered");
let params = make_params();
let mut session = scheme.create_session(¶ms).expect("session created");
let rr = session.round(&[]).expect("round ok");
assert!(rr.complete);
assert!(rr.outgoing.is_empty());
let result = session.result().expect("result ok");
assert_eq!(result, b"noop-result");
session.destroy();
}
#[test]
fn scheme_create_session_propagates_message_param() {
let scheme = find("test-noop").expect("registered");
let mut params = make_params();
params.message = Some(vec![0x11, 0x22]);
let mut session = scheme.create_session(¶ms).expect("session created");
session.round(&[]).expect("round ok");
let result = session.result().expect("result ok");
assert_eq!(result, vec![0x11, 0x22]);
}
#[test]
fn result_before_complete_errors() {
let scheme = find("test-noop").expect("registered");
let params = make_params();
let session = scheme.create_session(¶ms).expect("session created");
let err = session.result().unwrap_err();
assert!(matches!(err, error::Error::SessionNotComplete { .. }));
}
#[test]
fn share_param_does_not_panic_when_absent() {
let params = make_params();
assert!(params.local_share.is_none());
let _ = Share::new("test-noop", vec![]);
}
}