1use std::collections::BTreeMap;
27use std::fmt::{Debug, Display};
28use std::ops::Deref;
29use std::str::FromStr;
30
31#[cfg(feature = "fs")]
32pub use _fs::*;
33use amplify::confinement;
34use amplify::confinement::{Confined, TinyVec, U24};
35use baid58::Baid58ParseError;
36use rgb::{BundleId, ContractId, Schema, SchemaId, SchemaRoot};
37use strict_encoding::{
38 StrictDecode, StrictDeserialize, StrictDumb, StrictEncode, StrictSerialize, StrictType,
39};
40
41use crate::containers::transfer::TransferId;
42use crate::containers::{Cert, Contract, Transfer};
43use crate::interface::{Iface, IfaceId, IfaceImpl, ImplId};
44use crate::LIB_NAME_RGB_STD;
45
46pub trait BindleContent: StrictSerialize + StrictDeserialize + StrictDumb {
48 const MAGIC: [u8; 4];
50 const PLATE_TITLE: &'static str;
52
53 type Id: Copy
54 + Eq
55 + Debug
56 + Display
57 + FromStr<Err = Baid58ParseError>
58 + StrictType
59 + StrictDumb
60 + StrictEncode
61 + StrictDecode;
62
63 fn bindle_id(&self) -> Self::Id;
64 fn bindle_headers(&self) -> BTreeMap<&'static str, String> { none!() }
65 fn bindle(self) -> Bindle<Self> { Bindle::new(self) }
66 fn bindle_mnemonic(&self) -> Option<String> { None }
67}
68
69impl<Root: SchemaRoot> BindleContent for Schema<Root> {
70 const MAGIC: [u8; 4] = *b"SCHM";
71 const PLATE_TITLE: &'static str = "RGB SCHEMA";
72 type Id = SchemaId;
73 fn bindle_id(&self) -> Self::Id { self.schema_id() }
74 fn bindle_mnemonic(&self) -> Option<String> { Some(self.schema_id().to_mnemonic()) }
75}
76
77impl BindleContent for Contract {
78 const MAGIC: [u8; 4] = *b"CNRC";
79 const PLATE_TITLE: &'static str = "RGB CONTRACT";
80 type Id = ContractId;
81 fn bindle_id(&self) -> Self::Id { self.contract_id() }
82 fn bindle_headers(&self) -> BTreeMap<&'static str, String> {
83 bmap! {
84 "Version" => self.version.to_string(),
85 "Terminals" => self.terminals
86 .keys()
87 .map(BundleId::to_string)
88 .collect::<Vec<_>>()
89 .join(",\n "),
90 }
91 }
92}
93
94impl BindleContent for Transfer {
95 const MAGIC: [u8; 4] = *b"TRNS";
96 const PLATE_TITLE: &'static str = "RGB STATE TRANSFER";
97 type Id = TransferId;
98 fn bindle_id(&self) -> Self::Id { self.transfer_id() }
99 fn bindle_mnemonic(&self) -> Option<String> { Some(self.transfer_id().to_mnemonic()) }
100 fn bindle_headers(&self) -> BTreeMap<&'static str, String> {
101 bmap! {
102 "Version" => self.version.to_string(),
103 "ContractId" => self.contract_id().to_string(),
104 "Terminals" => self.terminals
105 .keys()
106 .map(BundleId::to_string)
107 .collect::<Vec<_>>()
108 .join(",\n "),
109 }
110 }
111}
112
113impl BindleContent for Iface {
114 const MAGIC: [u8; 4] = *b"IFCE";
115 const PLATE_TITLE: &'static str = "RGB INTERFACE";
116 type Id = IfaceId;
117 fn bindle_id(&self) -> Self::Id { self.iface_id() }
118 fn bindle_mnemonic(&self) -> Option<String> { Some(self.iface_id().to_mnemonic()) }
119 fn bindle_headers(&self) -> BTreeMap<&'static str, String> {
120 bmap! {
121 "Name" => self.name.to_string()
122 }
123 }
124}
125
126impl BindleContent for IfaceImpl {
127 const MAGIC: [u8; 4] = *b"IMPL";
128 const PLATE_TITLE: &'static str = "RGB INTERFACE IMPLEMENTATION";
129 type Id = ImplId;
130 fn bindle_id(&self) -> Self::Id { self.impl_id() }
131 fn bindle_mnemonic(&self) -> Option<String> { Some(self.impl_id().to_mnemonic()) }
132 fn bindle_headers(&self) -> BTreeMap<&'static str, String> {
133 bmap! {
134 "IfaceId" => format!("{:-#}", self.iface_id),
135 "SchemaId" => format!("{:-#}", self.schema_id),
136 }
137 }
138}
139
140#[derive(Clone, PartialEq, Eq, Hash, Debug)]
141#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
142#[strict_type(lib = LIB_NAME_RGB_STD)]
143#[cfg_attr(
144 feature = "serde",
145 derive(Serialize, Deserialize),
146 serde(crate = "serde_crate", rename_all = "camelCase")
147)]
148pub struct Bindle<C: BindleContent> {
149 id: C::Id,
150 data: C,
151 sigs: TinyVec<Cert>,
152}
153
154impl<C: BindleContent> Deref for Bindle<C> {
155 type Target = C;
156 fn deref(&self) -> &Self::Target { &self.data }
157}
158
159impl<C: BindleContent> From<C> for Bindle<C> {
160 fn from(data: C) -> Self { Bindle::new(data) }
161}
162
163impl<C: BindleContent> Bindle<C> {
164 pub fn new(data: C) -> Self {
165 Bindle {
166 id: data.bindle_id(),
167 data,
168 sigs: empty!(),
169 }
170 }
171
172 pub fn id(&self) -> C::Id { self.id }
173
174 pub fn into_split(self) -> (C, TinyVec<Cert>) { (self.data, self.sigs) }
175 pub fn unbindle(self) -> C { self.data }
176}
177
178#[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)]
179#[display(doc_comments)]
180pub enum BindleParseError<Id: Copy + Eq + Debug + Display> {
181 WrongStructure,
184
185 InvalidId(Baid58ParseError),
187
188 MismatchedId { actual: Id, expected: Id },
194
195 #[from(base85::Error)]
197 Base85,
198
199 #[from]
201 Deserialize(strict_encoding::DeserializeError),
202
203 #[from(confinement::Error)]
205 TooLarge,
206}
207
208impl<C: BindleContent> FromStr for Bindle<C> {
209 type Err = BindleParseError<C::Id>;
210
211 fn from_str(s: &str) -> Result<Self, Self::Err> {
212 let mut lines = s.lines();
213 let first = format!("-----BEGIN {}-----", C::PLATE_TITLE);
214 let last = format!("-----END {}-----", C::PLATE_TITLE);
215 if (lines.next(), lines.next_back()) != (Some(&first), Some(&last)) {
216 return Err(BindleParseError::WrongStructure);
217 }
218 let mut header_id = None;
219 for line in lines.by_ref() {
220 if line.is_empty() {
221 break;
222 }
223 if let Some(id_str) = line.strip_prefix("Id: ") {
224 header_id = Some(C::Id::from_str(id_str).map_err(BindleParseError::InvalidId)?);
225 }
226 }
227 let armor = lines.filter(|l| !l.is_empty()).collect::<String>();
228 let data = base85::decode(&armor)?;
229 let data = C::from_strict_serialized::<U24>(Confined::try_from(data)?)?;
230 let id = data.bindle_id();
231 if let Some(header_id) = header_id {
232 if header_id != id {
233 return Err(BindleParseError::MismatchedId {
234 actual: id,
235 expected: header_id,
236 });
237 }
238 }
239 Ok(Self {
242 id,
243 data,
244 sigs: none!(),
245 })
246 }
247}
248
249impl<C: BindleContent> Display for Bindle<C> {
250 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
251 writeln!(f, "-----BEGIN {}-----", C::PLATE_TITLE)?;
252 writeln!(f, "Id: {:-#}", self.id)?;
253 if let Some(mnemonic) = self.bindle_mnemonic() {
254 writeln!(f, "Mnemonic: {}", mnemonic)?;
255 }
256 for (header, value) in self.bindle_headers() {
257 writeln!(f, "{header}: {value}")?;
258 }
259 for cert in &self.sigs {
260 writeln!(f, "Signed-By: {}", cert.signer)?;
261 }
262 writeln!(f)?;
263
264 let data = self.data.to_strict_serialized::<U24>().expect("in-memory");
266 let data = base85::encode(&data);
267 let mut data = data.as_str();
268 while data.len() >= 64 {
269 let (line, rest) = data.split_at(64);
270 writeln!(f, "{}", line)?;
271 data = rest;
272 }
273 writeln!(f, "{}", data)?;
274
275 writeln!(f, "\n-----END {}-----", C::PLATE_TITLE)?;
276 Ok(())
277 }
278}
279
280#[cfg(feature = "fs")]
281mod _fs {
282 use std::io::{Read, Write};
283 use std::path::Path;
284 use std::{fs, io};
285
286 use rgb::SubSchema;
287 use strict_encoding::{DecodeError, StrictEncode, StrictReader, StrictWriter};
288
289 use super::*;
290
291 #[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
292 #[display(doc_comments)]
293 pub enum LoadError {
294 InvalidMagic,
296
297 #[display(inner)]
298 #[from]
299 #[from(io::Error)]
300 Decode(DecodeError),
301 }
302
303 #[derive(Clone, Debug, From)]
304 #[cfg_attr(
305 feature = "serde",
306 derive(Serialize, Deserialize),
307 serde(crate = "serde_crate", rename_all = "camelCase", tag = "type")
308 )]
309 pub enum UniversalBindle {
310 #[from]
311 #[cfg_attr(feature = "serde", serde(rename = "interface"))]
312 Iface(Bindle<Iface>),
313
314 #[from]
315 Schema(Bindle<SubSchema>),
316
317 #[from]
318 #[cfg_attr(feature = "serde", serde(rename = "implementation"))]
319 Impl(Bindle<IfaceImpl>),
320
321 #[from]
322 Contract(Bindle<Contract>),
323
324 #[from]
325 Transfer(Bindle<Transfer>),
326 }
327
328 impl<C: BindleContent> Bindle<C> {
329 pub fn load(path: impl AsRef<Path>) -> Result<Self, LoadError> {
330 let mut rgb = [0u8; 3];
331 let mut magic = [0u8; 4];
332 let mut file = fs::File::open(path)?;
333 file.read_exact(&mut rgb)?;
334 file.read_exact(&mut magic)?;
335 if rgb != *b"RGB" || magic != C::MAGIC {
336 return Err(LoadError::InvalidMagic);
337 }
338 let mut reader = StrictReader::with(usize::MAX, file);
339 let me = Self::strict_decode(&mut reader)?;
340 Ok(me)
341 }
342
343 pub fn save(&self, path: impl AsRef<Path>) -> Result<(), io::Error> {
344 let mut file = fs::File::create(path)?;
345 file.write_all(b"RGB")?;
346 file.write_all(&C::MAGIC)?;
347 let writer = StrictWriter::with(usize::MAX, file);
348 self.strict_encode(writer)?;
349 Ok(())
350 }
351 }
352
353 impl UniversalBindle {
354 pub fn load(path: impl AsRef<Path>) -> Result<Self, LoadError> {
355 let mut rgb = [0u8; 3];
356 let mut magic = [0u8; 4];
357 let mut file = fs::File::open(path)?;
358 file.read_exact(&mut rgb)?;
359 file.read_exact(&mut magic)?;
360 if rgb != *b"RGB" {
361 return Err(LoadError::InvalidMagic);
362 }
363 let mut reader = StrictReader::with(usize::MAX, file);
364 Ok(match magic {
365 x if x == Iface::MAGIC => Bindle::<Iface>::strict_decode(&mut reader)?.into(),
366 x if x == SubSchema::MAGIC => {
367 Bindle::<SubSchema>::strict_decode(&mut reader)?.into()
368 }
369 x if x == IfaceImpl::MAGIC => {
370 Bindle::<IfaceImpl>::strict_decode(&mut reader)?.into()
371 }
372 x if x == Contract::MAGIC => Bindle::<Contract>::strict_decode(&mut reader)?.into(),
373 x if x == Transfer::MAGIC => Bindle::<Transfer>::strict_decode(&mut reader)?.into(),
374 _ => return Err(LoadError::InvalidMagic),
375 })
376 }
377 }
378}