use cbor_core::Value;
use crate::hash::Hash;
#[cfg(any(test, feature = "test_utils"))]
use crate::identity::SigningKey;
use crate::identity::{Signature, VerifyingKey};
use crate::logs::SeqNum;
use crate::operation::{AnyHeader, Builder};
use crate::traits::{Chain, Digest, Extensions, Offchain, Provenance};
use crate::{Body, HeaderError};
pub type Version = u16;
pub type PayloadSize = u32;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Header<E = ()> {
pub version: Version,
pub verifying_key: VerifyingKey,
pub signature: Signature,
pub payload_size: PayloadSize,
pub payload_hash: Option<Hash>,
pub seq_num: SeqNum,
pub backlink: Option<Hash>,
pub extensions: E,
pub(crate) extensions_cbor: Option<cbor_core::Value<'static>>,
pub(crate) size: u32,
pub(crate) digest: Hash,
}
impl<E> Header<E>
where
E: Extensions,
{
pub fn builder() -> Builder<E> {
Builder::new()
}
pub fn encode(&self) -> Vec<u8> {
encode_header(
self.version,
self.verifying_key,
Some(&self.signature),
self.payload_size,
self.payload_hash,
self.seq_num,
self.backlink,
self.extensions_cbor.as_ref(),
)
}
pub fn decode(bytes: &[u8]) -> Result<Self, HeaderError> {
let any_header = AnyHeader::decode(bytes)?;
Self::try_from(any_header)
}
pub fn hash(&self) -> Hash {
if cfg!(any(test, feature = "test_utils")) {
return Hash::digest(self.encode());
}
self.digest
}
pub fn size(&self) -> u32 {
if cfg!(any(test, feature = "test_utils")) {
return self.encode().len() as u32;
}
self.size
}
}
impl<E> Header<E> {
pub(crate) const fn has_zero_sized_extensions() -> bool {
std::mem::size_of::<E>() == 0
}
pub(crate) fn zero_sized_extensions() -> E {
assert!(Self::has_zero_sized_extensions());
unsafe { std::mem::zeroed() }
}
}
impl<E> Digest<Hash> for Header<E>
where
E: Extensions,
{
fn hash(&self) -> Hash {
self.hash()
}
}
impl<E> Provenance<VerifyingKey> for Header<E>
where
E: Extensions,
{
fn author(&self) -> VerifyingKey {
self.verifying_key
}
fn verify(&self) -> bool {
if cfg!(any(test, feature = "test_utils")) {
return self.verify();
}
true
}
}
impl<E> Chain<Hash> for Header<E>
where
E: Extensions,
{
fn backlink(&self) -> Option<Hash> {
self.backlink
}
fn seq_num(&self) -> SeqNum {
self.seq_num
}
}
impl<E> Offchain<Hash> for Header<E>
where
E: Extensions,
{
fn payload(&self) -> Option<&Body> {
None }
fn payload_hash(&self) -> Option<Hash> {
self.payload_hash
}
fn payload_size(&self) -> PayloadSize {
self.payload_size
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn encode_header(
version: Version,
verifying_key: VerifyingKey,
signature: Option<&Signature>,
payload_size: PayloadSize,
payload_hash: Option<Hash>,
seq_num: SeqNum,
backlink: Option<Hash>,
extensions: Option<&Value<'static>>,
) -> Vec<u8> {
let mut cbor = Value::array([Value::from(version), Value::from(verifying_key.as_bytes())]);
if let Some(signature) = &signature {
cbor.append(signature.to_bytes());
}
cbor.append(payload_size);
if let Some(payload_hash) = &payload_hash {
cbor.append(payload_hash.as_bytes());
}
cbor.append(seq_num);
if let Some(backlink) = &backlink {
cbor.append(backlink.as_bytes());
}
if let Some(extensions) = extensions {
cbor.append(extensions.to_owned());
}
cbor.encode()
}
impl<E> TryFrom<AnyHeader> for Header<E>
where
E: Extensions,
{
type Error = HeaderError;
fn try_from(value: AnyHeader) -> Result<Self, Self::Error> {
let extensions = match value.extensions {
Some(ref cbor) => {
if Header::<E>::has_zero_sized_extensions() {
return Err(HeaderError::UnexpectedExtensions);
}
cbor.deserialized()
.map_err(HeaderError::DecodingExtensions)?
}
None => {
if !Header::<E>::has_zero_sized_extensions() {
return Err(HeaderError::MissingExtensions);
} else {
Header::<E>::zero_sized_extensions()
}
}
};
Ok(Header {
version: value.version,
verifying_key: value.verifying_key,
signature: value.signature,
payload_size: value.payload_size,
payload_hash: value.payload_hash,
seq_num: value.seq_num,
backlink: value.backlink,
extensions,
extensions_cbor: value.extensions,
size: value.size,
digest: value.digest,
})
}
}
#[cfg(any(test, feature = "test_utils"))]
impl<E> Default for Header<E>
where
E: Default,
{
fn default() -> Self {
use crate::hash::HASH_LEN;
use crate::identity::SIGNATURE_LEN;
Self {
version: 1,
verifying_key: VerifyingKey::default(),
signature: Signature::from([0; SIGNATURE_LEN]),
payload_size: 0,
payload_hash: None,
seq_num: 0,
backlink: None,
extensions: E::default(),
extensions_cbor: None,
size: 0,
digest: Hash::from([0; HASH_LEN]),
}
}
}
#[cfg(any(test, feature = "test_utils"))]
impl<E> Header<E>
where
E: Extensions,
{
pub fn to_hex(&self) -> String {
hex::encode(self.encode())
}
fn encode_signing_bytes(&self) -> Vec<u8> {
encode_header(
self.version,
self.verifying_key,
None,
self.payload_size,
self.payload_hash,
self.seq_num,
self.backlink,
self.extensions_cbor.as_ref(),
)
}
pub fn sign(&mut self, signer: &SigningKey) {
let signing_bytes = self.encode_signing_bytes();
self.signature = signer.sign(&signing_bytes);
self.update_size_and_digest();
}
pub fn verify(&self) -> bool {
let signing_bytes = self.encode_signing_bytes();
self.verifying_key.verify(&signing_bytes, &self.signature)
}
fn update_size_and_digest(&mut self) {
self.size = self.size();
self.digest = self.hash();
}
}
#[cfg(feature = "arbitrary")]
impl<'a, E> arbitrary::Arbitrary<'a> for Header<E>
where
E: Default + Extensions,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
use crate::hash::HASH_LEN;
use crate::identity::SIGNATURE_LEN;
let header = Header {
version: 1,
verifying_key: u.arbitrary()?,
signature: Signature::from_bytes(&[0; SIGNATURE_LEN]),
payload_size: u.arbitrary()?,
payload_hash: u.arbitrary()?,
seq_num: u.arbitrary()?,
backlink: u.arbitrary()?,
extensions: E::default(),
extensions_cbor: None,
size: 0,
digest: Hash::from_bytes([0; HASH_LEN]),
};
Ok(header)
}
}
#[cfg(test)]
mod tests {
use super::Header;
#[test]
fn zst_size_matches_mem_checks() {
struct ZstExtensions;
assert_eq!(std::mem::size_of::<ZstExtensions>(), 0);
assert!(Header::<ZstExtensions>::has_zero_sized_extensions());
#[allow(unused)]
struct NonZstExtensions(u32);
assert_ne!(std::mem::size_of::<NonZstExtensions>(), 0);
assert!(!Header::<NonZstExtensions>::has_zero_sized_extensions());
}
}