use super::{Certificate, HashTree};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use std::io::{self, Write};
use thiserror::Error;
pub mod strict;
#[derive(Debug, Error)]
pub enum CborError {
#[error("CBOR input exceeds the byte limit")]
InputTooLarge,
#[error("invalid CBOR: {0}")]
Decode(#[source] serde_cbor::Error),
#[error("CBOR output exceeds the byte limit")]
OutputTooLarge,
#[error("CBOR encoding failed: {0}")]
Encode(#[source] serde_cbor::Error),
}
#[derive(Debug, Deserialize)]
pub struct CanisterSignature {
#[serde(with = "serde_bytes")]
pub certificate: Vec<u8>,
pub tree: HashTree,
}
pub fn decode_certificate(bytes: &[u8], max_bytes: usize) -> Result<Certificate, CborError> {
decode(bytes, max_bytes)
}
pub fn decode_canister_signature(
bytes: &[u8],
max_bytes: usize,
) -> Result<CanisterSignature, CborError> {
decode(bytes, max_bytes)
}
pub fn encode_certificate(
certificate: &Certificate,
max_bytes: usize,
) -> Result<Vec<u8>, CborError> {
encode(certificate, max_bytes)
}
pub fn encode_canister_signature(
certificate: &[u8],
tree: &HashTree,
max_bytes: usize,
) -> Result<Vec<u8>, CborError> {
#[derive(Serialize)]
struct Envelope<'a> {
#[serde(with = "serde_bytes")]
certificate: &'a [u8],
tree: &'a HashTree,
}
encode(&Envelope { certificate, tree }, max_bytes)
}
pub(crate) fn decode<T: DeserializeOwned>(bytes: &[u8], max_bytes: usize) -> Result<T, CborError> {
if bytes.len() > max_bytes {
return Err(CborError::InputTooLarge);
}
serde_cbor::from_slice(bytes).map_err(CborError::Decode)
}
fn encode(value: &impl Serialize, max_bytes: usize) -> Result<Vec<u8>, CborError> {
encode_framed(value, max_bytes, true)
}
pub(crate) fn encode_framed(
value: &impl Serialize,
max_bytes: usize,
tagged: bool,
) -> Result<Vec<u8>, CborError> {
let mut output = Output {
bytes: Vec::new(),
maximum: max_bytes,
};
let mut serializer = serde_cbor::Serializer::new(serde_cbor::ser::IoWrite::new(&mut output));
(if tagged {
serializer.self_describe()
} else {
Ok(())
})
.and_then(|()| value.serialize(&mut serializer))
.map_err(|error| {
if error.is_io() {
CborError::OutputTooLarge
} else {
CborError::Encode(error)
}
})?;
Ok(output.bytes)
}
pub fn encode_certificate_untagged(
certificate: &Certificate,
max_bytes: usize,
) -> Result<Vec<u8>, CborError> {
encode_framed(certificate, max_bytes, false)
}
pub fn encode_hash_tree(tree: &HashTree, max_bytes: usize) -> Result<Vec<u8>, CborError> {
encode_framed(tree, max_bytes, false)
}
struct Output {
bytes: Vec<u8>,
maximum: usize,
}
impl Write for Output {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
if bytes.len() > self.maximum - self.bytes.len() {
return Err(io::ErrorKind::WriteZero.into());
}
let required = self.bytes.len() + bytes.len();
if required > self.bytes.capacity() {
let capacity = required
.max(self.bytes.capacity().saturating_mul(2))
.min(self.maximum);
self.bytes.reserve_exact(capacity - self.bytes.len());
}
self.bytes.extend_from_slice(bytes);
Ok(bytes.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[cfg(test)]
mod tests;