use core::fmt;
use core::fmt::{Display, Formatter};
use core::str::FromStr;
use aluvm::{Lib, LibId};
use amplify::confinement::TinyString;
use baid64::DisplayBaid64;
use commit_verify::{CommitEncode, CommitId, StrictHash};
use sonic_callreq::MethodName;
use strict_encoding::{StrictDecode, StrictDumb, StrictEncode, TypeName};
use strict_types::TypeSystem;
use ultrasonic::{CallId, Codex, CodexId, Identity, LibRepo};
use crate::{Api, ApisChecksum, ParseVersionedError, SemanticError, Semantics, SigBlob, LIB_NAME_SONIC};
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_SONIC)]
#[derive(CommitEncode)]
#[commit_encode(strategy = strict, id = StrictHash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
pub struct IssuerId {
pub codex_id: CodexId,
pub version: u16,
pub checksum: ApisChecksum,
}
impl Display for IssuerId {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{:#}/{}#", self.codex_id, self.version)?;
self.checksum.fmt_baid64(f)
}
}
impl FromStr for IssuerId {
type Err = ParseVersionedError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (id, remnant) = s
.split_once('/')
.ok_or_else(|| ParseVersionedError::NoVersion(s.to_string()))?;
let (version, api_id) = remnant
.split_once('#')
.ok_or_else(|| ParseVersionedError::NoChecksum(s.to_string()))?;
Ok(Self {
codex_id: id.parse().map_err(ParseVersionedError::Id)?,
version: version.parse().map_err(ParseVersionedError::Version)?,
checksum: api_id.parse().map_err(ParseVersionedError::Checksum)?,
})
}
}
#[derive(Clone, Eq, PartialEq, Debug)]
#[derive(StrictType, StrictDumb, StrictEncode)]
#[strict_type(lib = LIB_NAME_SONIC)]
pub struct Issuer {
codex: Codex,
semantics: Semantics,
sig: Option<SigBlob>,
}
impl Issuer {
pub fn new(codex: Codex, semantics: Semantics) -> Result<Self, SemanticError> {
semantics.check(&codex)?;
Ok(Self { semantics, codex, sig: None })
}
pub fn with<E>(
codex: Codex,
semantics: Semantics,
sig: SigBlob,
sig_validator: impl FnOnce(StrictHash, &Identity, &SigBlob) -> Result<(), E>,
) -> Result<Self, SemanticError> {
let mut me = Self::new(codex, semantics)?;
let id = me.issuer_id().commit_id();
sig_validator(id, &me.codex.developer, &sig).map_err(|_| SemanticError::InvalidSignature)?;
me.sig = Some(sig);
Ok(me)
}
pub fn dismember(self) -> (Codex, Semantics) { (self.codex, self.semantics) }
pub fn issuer_id(&self) -> IssuerId {
IssuerId {
codex_id: self.codex.codex_id(),
version: self.semantics.version,
checksum: self.semantics.apis_checksum(),
}
}
pub fn codex_id(&self) -> CodexId { self.codex.codex_id() }
pub fn codex(&self) -> &Codex { &self.codex }
pub fn codex_name(&self) -> &TinyString { &self.codex.name }
pub fn semantics(&self) -> &Semantics { &self.semantics }
pub fn default_api(&self) -> &Api { &self.semantics.default }
pub fn custom_apis(&self) -> impl Iterator<Item = (&TypeName, &Api)> { self.semantics.custom.iter() }
pub fn types(&self) -> &TypeSystem { &self.semantics.types }
pub fn apis(&self) -> impl Iterator<Item = &Api> { self.semantics.apis() }
pub fn codex_libs(&self) -> impl Iterator<Item = &Lib> { self.semantics.codex_libs.iter() }
pub fn is_signed(&self) -> bool { self.sig.is_some() }
pub fn call_id(&self, method: impl Into<MethodName>) -> CallId {
self.semantics
.default
.verifier(method)
.expect("calling to method absent in Codex API")
}
}
impl LibRepo for Issuer {
fn get_lib(&self, lib_id: LibId) -> Option<&Lib> {
self.semantics
.codex_libs
.iter()
.find(|lib| lib.lib_id() == lib_id)
}
}
#[cfg(feature = "binfile")]
mod _fs {
use std::io::{self, Read};
use std::path::Path;
use amplify::confinement::U24 as U24MAX;
use binfile::BinFile;
use commit_verify::{CommitId, StrictHash};
use strict_encoding::{DecodeError, DeserializeError, StreamReader, StreamWriter, StrictDecode, StrictEncode};
use ultrasonic::{Codex, Identity};
use crate::{Issuer, Semantics, SigBlob};
pub const ISSUER_MAGIC_NUMBER: u64 = u64::from_be_bytes(*b"ISSUER ");
pub const ISSUER_VERSION: u16 = 0;
impl Issuer {
pub fn load<E>(
path: impl AsRef<Path>,
sig_validator: impl FnOnce(StrictHash, &Identity, &SigBlob) -> Result<(), E>,
) -> Result<Self, DeserializeError> {
let file = BinFile::<ISSUER_MAGIC_NUMBER, ISSUER_VERSION>::open(path)?;
let mut reader = StreamReader::new::<U24MAX>(file);
let codex = Codex::strict_read(&mut reader)?;
let semantics = Semantics::strict_read(&mut reader)?;
semantics
.check(&codex)
.map_err(|e| DecodeError::DataIntegrityError(e.to_string()))?;
let sig = Option::<SigBlob>::strict_read(&mut reader)?;
let me = Self { codex, semantics, sig };
if let Some(sig) = &me.sig {
sig_validator(me.issuer_id().commit_id(), &me.codex.developer, sig)
.map_err(|_| DecodeError::DataIntegrityError(s!("invalid signature")))?;
}
match reader.unconfine().read_exact(&mut [0u8; 1]) {
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => Ok(me),
Err(e) => Err(e.into()),
Ok(()) => Err(DeserializeError::DataNotEntirelyConsumed),
}
}
pub fn save(&self, path: impl AsRef<Path>) -> io::Result<()> {
let file = BinFile::<ISSUER_MAGIC_NUMBER, ISSUER_VERSION>::create_new(path)?;
let writer = StreamWriter::new::<U24MAX>(file);
self.strict_write(writer)
}
}
}
#[cfg(feature = "binfile")]
pub use _fs::*;