1use core::fmt;
25use core::fmt::{Display, Formatter};
26use core::str::FromStr;
27
28use aluvm::{Lib, LibId};
29use amplify::confinement::TinyString;
30use baid64::DisplayBaid64;
31use commit_verify::{CommitEncode, CommitId, StrictHash};
32use sonic_callreq::MethodName;
33use strict_encoding::{StrictDecode, StrictDumb, StrictEncode, TypeName};
34use strict_types::TypeSystem;
35use ultrasonic::{CallId, Codex, CodexId, Identity, LibRepo};
36
37use crate::{Api, ApisChecksum, ParseVersionedError, SemanticError, Semantics, SigBlob, LIB_NAME_SONIC};
38
39#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
53#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
54#[strict_type(lib = LIB_NAME_SONIC)]
55#[derive(CommitEncode)]
56#[commit_encode(strategy = strict, id = StrictHash)]
57#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
58pub struct IssuerId {
59 pub codex_id: CodexId,
61 pub version: u16,
63 pub checksum: ApisChecksum,
65}
66
67impl Display for IssuerId {
68 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
69 write!(f, "{:#}/{}#", self.codex_id, self.version)?;
70 self.checksum.fmt_baid64(f)
71 }
72}
73
74impl FromStr for IssuerId {
75 type Err = ParseVersionedError;
76 fn from_str(s: &str) -> Result<Self, Self::Err> {
77 let (id, remnant) = s
78 .split_once('/')
79 .ok_or_else(|| ParseVersionedError::NoVersion(s.to_string()))?;
80 let (version, api_id) = remnant
81 .split_once('#')
82 .ok_or_else(|| ParseVersionedError::NoChecksum(s.to_string()))?;
83 Ok(Self {
84 codex_id: id.parse().map_err(ParseVersionedError::Id)?,
85 version: version.parse().map_err(ParseVersionedError::Version)?,
86 checksum: api_id.parse().map_err(ParseVersionedError::Checksum)?,
87 })
88 }
89}
90
91#[derive(Clone, Eq, PartialEq, Debug)]
101#[derive(StrictType, StrictDumb, StrictEncode)]
102#[strict_type(lib = LIB_NAME_SONIC)]
105pub struct Issuer {
106 codex: Codex,
108 semantics: Semantics,
111 sig: Option<SigBlob>,
114}
115
116impl Issuer {
117 pub fn new(codex: Codex, semantics: Semantics) -> Result<Self, SemanticError> {
119 semantics.check(&codex)?;
120 Ok(Self { semantics, codex, sig: None })
121 }
122
123 pub fn with<E>(
125 codex: Codex,
126 semantics: Semantics,
127 sig: SigBlob,
128 sig_validator: impl FnOnce(StrictHash, &Identity, &SigBlob) -> Result<(), E>,
129 ) -> Result<Self, SemanticError> {
130 let mut me = Self::new(codex, semantics)?;
131 let id = me.issuer_id().commit_id();
132 sig_validator(id, &me.codex.developer, &sig).map_err(|_| SemanticError::InvalidSignature)?;
133 me.sig = Some(sig);
134 Ok(me)
135 }
136
137 pub fn dismember(self) -> (Codex, Semantics) { (self.codex, self.semantics) }
138
139 pub fn issuer_id(&self) -> IssuerId {
142 IssuerId {
143 codex_id: self.codex.codex_id(),
144 version: self.semantics.version,
145 checksum: self.semantics.apis_checksum(),
146 }
147 }
148 pub fn codex_id(&self) -> CodexId { self.codex.codex_id() }
150 pub fn codex(&self) -> &Codex { &self.codex }
152 pub fn codex_name(&self) -> &TinyString { &self.codex.name }
154
155 pub fn semantics(&self) -> &Semantics { &self.semantics }
157 pub fn default_api(&self) -> &Api { &self.semantics.default }
159 pub fn custom_apis(&self) -> impl Iterator<Item = (&TypeName, &Api)> { self.semantics.custom.iter() }
161 pub fn types(&self) -> &TypeSystem { &self.semantics.types }
163 pub fn apis(&self) -> impl Iterator<Item = &Api> { self.semantics.apis() }
165 pub fn codex_libs(&self) -> impl Iterator<Item = &Lib> { self.semantics.codex_libs.iter() }
167
168 pub fn is_signed(&self) -> bool { self.sig.is_some() }
170
171 pub fn call_id(&self, method: impl Into<MethodName>) -> CallId {
177 self.semantics
178 .default
179 .verifier(method)
180 .expect("calling to method absent in Codex API")
181 }
182}
183
184impl LibRepo for Issuer {
185 fn get_lib(&self, lib_id: LibId) -> Option<&Lib> {
186 self.semantics
187 .codex_libs
188 .iter()
189 .find(|lib| lib.lib_id() == lib_id)
190 }
191}
192
193#[cfg(feature = "binfile")]
194mod _fs {
195 use std::io::{self, Read};
196 use std::path::Path;
197
198 use amplify::confinement::U24 as U24MAX;
199 use binfile::BinFile;
200 use commit_verify::{CommitId, StrictHash};
201 use strict_encoding::{DecodeError, DeserializeError, StreamReader, StreamWriter, StrictDecode, StrictEncode};
202 use ultrasonic::{Codex, Identity};
203
204 use crate::{Issuer, Semantics, SigBlob};
205
206 pub const ISSUER_MAGIC_NUMBER: u64 = u64::from_be_bytes(*b"ISSUER ");
208 pub const ISSUER_VERSION: u16 = 0;
210
211 impl Issuer {
212 pub fn load<E>(
213 path: impl AsRef<Path>,
214 sig_validator: impl FnOnce(StrictHash, &Identity, &SigBlob) -> Result<(), E>,
215 ) -> Result<Self, DeserializeError> {
216 let file = BinFile::<ISSUER_MAGIC_NUMBER, ISSUER_VERSION>::open(path)?;
219 let mut reader = StreamReader::new::<U24MAX>(file);
220
221 let codex = Codex::strict_read(&mut reader)?;
222 let semantics = Semantics::strict_read(&mut reader)?;
223 semantics
224 .check(&codex)
225 .map_err(|e| DecodeError::DataIntegrityError(e.to_string()))?;
226
227 let sig = Option::<SigBlob>::strict_read(&mut reader)?;
228 let me = Self { codex, semantics, sig };
229
230 if let Some(sig) = &me.sig {
231 sig_validator(me.issuer_id().commit_id(), &me.codex.developer, sig)
232 .map_err(|_| DecodeError::DataIntegrityError(s!("invalid signature")))?;
233 }
234
235 match reader.unconfine().read_exact(&mut [0u8; 1]) {
236 Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => Ok(me),
237 Err(e) => Err(e.into()),
238 Ok(()) => Err(DeserializeError::DataNotEntirelyConsumed),
239 }
240 }
241
242 pub fn save(&self, path: impl AsRef<Path>) -> io::Result<()> {
243 let file = BinFile::<ISSUER_MAGIC_NUMBER, ISSUER_VERSION>::create_new(path)?;
244 let writer = StreamWriter::new::<U24MAX>(file);
245 self.strict_write(writer)
246 }
247 }
248}
249#[cfg(feature = "binfile")]
250pub use _fs::*;