#![allow(clippy::absurd_extreme_comparisons)]
use super::*;
use crate::routing_table::*;
use core::convert::TryInto;
fourcc_type!(ReceiptVersion);
pub const RECEIPT_VERSION_RCP0: ReceiptVersion = ReceiptVersion::new(*b"RCP0");
pub const RCP0_NONCE_LENGTH: usize = 24;
pub const RCP0_SIGNATURE_LENGTH: usize = 64;
pub const RCP0_MAX_RECEIPT_SIZE: usize = 1380;
pub const RCP0_MAX_EXTRA_DATA_SIZE: usize = RCP0_MAX_RECEIPT_SIZE - RCP0_MIN_RECEIPT_SIZE; pub const RCP0_MIN_RECEIPT_SIZE: usize = 130;
pub const VALID_RECEIPT_VERSIONS: [ReceiptVersion; 1] = [RECEIPT_VERSION_RCP0];
pub fn best_receipt_version() -> ReceiptVersion {
VALID_RECEIPT_VERSIONS[0]
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Receipt {
RCP0 { rcp0: ReceiptRCP0 },
}
impl Receipt {
#[cfg_attr(
feature = "instrument",
instrument(level = "trace", target = "envelope", skip_all, fields(__VEILID_LOG_KEY = crypto.log_key()))
)]
pub fn try_new_rcp0(
crypto: &Crypto,
crypto_kind: CryptoKind,
nonce: Nonce,
sender_id: NodeId,
extra_data: Bytes,
) -> VeilidAPIResult<Self> {
Ok(Self::RCP0 {
rcp0: ReceiptRCP0::try_new(crypto, crypto_kind, nonce, sender_id, extra_data)?,
})
}
#[cfg_attr(
feature = "instrument",
instrument(level = "trace", target = "receipt", skip_all, err, fields(__VEILID_LOG_KEY = crypto.log_key()))
)]
pub async fn try_from_signed_data(crypto: &Crypto, data: Bytes) -> VeilidAPIResult<Receipt> {
if data.len() < 4 {
apibail_parse_error!("receipt header too small", data.len());
}
let version: ReceiptVersion = data[0x00..0x04]
.try_into()
.map_err(VeilidAPIError::internal)?;
match version {
RECEIPT_VERSION_RCP0 => Ok(Self::RCP0 {
rcp0: ReceiptRCP0::try_from_signed_data(crypto, data).await?,
}),
_ => {
apibail_parse_error!("unsupported receipt version", version);
}
}
}
#[cfg_attr(
feature = "instrument",
instrument(level = "trace", target = "envelope", skip_all, fields(__VEILID_LOG_KEY = crypto.log_key()))
)]
pub async fn to_signed_data(
&self,
crypto: &Crypto,
secret_key: &SecretKey,
) -> VeilidAPIResult<Bytes> {
match self {
Receipt::RCP0 { rcp0 } => rcp0.to_signed_data(crypto, secret_key).await,
}
}
#[expect(dead_code)]
pub fn get_version(&self) -> ReceiptVersion {
match self {
Receipt::RCP0 { rcp0: _ } => RECEIPT_VERSION_RCP0,
}
}
#[expect(dead_code)]
pub fn get_crypto_kind(&self) -> CryptoKind {
match self {
Receipt::RCP0 { rcp0 } => rcp0.get_crypto_kind(),
}
}
pub fn get_nonce(&self) -> Nonce {
match self {
Receipt::RCP0 { rcp0 } => rcp0.get_nonce(),
}
}
#[expect(dead_code)]
pub fn get_sender_id(&self) -> NodeId {
match self {
Receipt::RCP0 { rcp0 } => rcp0.get_sender_id(),
}
}
pub fn get_extra_data(&self) -> &[u8] {
match self {
Receipt::RCP0 { rcp0 } => rcp0.get_extra_data(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReceiptRCP0 {
crypto_kind: CryptoKind,
nonce: Nonce,
bare_sender_id: BareNodeId,
extra_data: Bytes,
}
impl ReceiptRCP0 {
pub fn try_new(
crypto: &Crypto,
crypto_kind: CryptoKind,
nonce: Nonce,
sender_id: NodeId,
extra_data: Bytes,
) -> VeilidAPIResult<Self> {
let vcrypto = Self::validate_crypto_kind(crypto, crypto_kind)?;
vcrypto.check_nonce(&nonce)?;
Self::check_node_id(crypto_kind, &sender_id)?;
if extra_data.as_ref().len() > RCP0_MAX_EXTRA_DATA_SIZE {
apibail_parse_error!(
"extra data too large for receipt",
extra_data.as_ref().len()
);
}
Ok(Self {
crypto_kind,
nonce,
bare_sender_id: sender_id.value(),
extra_data,
})
}
#[cfg_attr(
feature = "instrument",
instrument(level = "trace", target = "receipt", skip_all, err, fields(__VEILID_LOG_KEY = crypto.log_key()))
)]
pub async fn try_from_signed_data(crypto: &Crypto, data: Bytes) -> VeilidAPIResult<Self> {
if data.len() < RCP0_MIN_RECEIPT_SIZE {
apibail_parse_error!("receipt too small", data.len());
}
let crypto_kind = CryptoKind::try_from(&data[0x04..0x08])?;
let vcrypto = Self::validate_crypto_kind(crypto, crypto_kind)?.as_async();
let size: u16 = u16::from_le_bytes(
data[0x08..0x0A]
.try_into()
.map_err(VeilidAPIError::internal)?,
);
if (size as usize) > RCP0_MAX_RECEIPT_SIZE {
apibail_parse_error!("receipt size is too large", size);
}
if (size as usize) != data.len() {
apibail_parse_error!(
"size doesn't match receipt size",
format!("size={} data.len()={}", size, data.len())
);
}
let bare_sender_id = BareNodeId::new(
data[0x22..0x42]
.try_into()
.map_err(VeilidAPIError::internal)?,
);
let sender_public_key = PublicKey::new(crypto_kind, BarePublicKey::new(&bare_sender_id));
let bare_signature = BareSignature::new(
data[(data.len() - 64)..]
.try_into()
.map_err(VeilidAPIError::internal)?,
);
let signature = Signature::new(crypto_kind, bare_signature);
if !vcrypto
.verify(
&sender_public_key,
data.slice(0..(data.len() - 64)),
&signature,
)
.await
.map_err(VeilidAPIError::generic)?
{
apibail_parse_error!("signature failure in receipt", signature);
}
let nonce: Nonce = Nonce::new(
data[0x0A..0x22]
.try_into()
.map_err(VeilidAPIError::internal)?,
);
let extra_data = Bytes::copy_from_slice(&data[0x42..(data.len() - 64)]);
Ok(Self {
crypto_kind,
nonce,
bare_sender_id,
extra_data,
})
}
#[cfg_attr(
feature = "instrument",
instrument(level = "trace", target = "receipt", skip_all, err, fields(__VEILID_LOG_KEY = crypto.log_key()))
)]
pub async fn to_signed_data(
&self,
crypto: &Crypto,
secret_key: &SecretKey,
) -> VeilidAPIResult<Bytes> {
let vcrypto = crypto
.get_async(self.crypto_kind)
.expect_or_log("need to ensure only valid crypto kinds here");
vcrypto.check_secret_key(secret_key)?;
let receipt_size: usize = self.extra_data.len() + RCP0_MIN_RECEIPT_SIZE;
if receipt_size > RCP0_MAX_RECEIPT_SIZE {
apibail_parse_error!("receipt too large", receipt_size);
}
let mut data = BytesMut::zeroed(receipt_size);
data[0x00..0x04].copy_from_slice(&RECEIPT_VERSION_RCP0.0);
data[0x04..0x08].copy_from_slice(self.crypto_kind.bytes());
data[0x08..0x0A].copy_from_slice(&(receipt_size as u16).to_le_bytes());
data[0x0A..0x22].copy_from_slice(&self.nonce);
data[0x22..0x42].copy_from_slice(&self.bare_sender_id);
if !self.extra_data.is_empty() {
data[0x42..(receipt_size - RCP0_SIGNATURE_LENGTH)].copy_from_slice(&self.extra_data);
}
let sender_public_key =
PublicKey::new(self.crypto_kind, BarePublicKey::new(&self.bare_sender_id));
let data = vcrypto
.sign_in_place(
&sender_public_key,
secret_key,
data,
0..(receipt_size - RCP0_SIGNATURE_LENGTH),
receipt_size - RCP0_SIGNATURE_LENGTH,
)
.await
.map_err(VeilidAPIError::generic)?;
Ok(data.freeze())
}
pub fn get_crypto_kind(&self) -> CryptoKind {
self.crypto_kind
}
pub fn get_nonce(&self) -> Nonce {
self.nonce.clone()
}
pub fn get_sender_id(&self) -> NodeId {
NodeId::new(self.crypto_kind, self.bare_sender_id.clone())
}
pub fn get_extra_data(&self) -> &[u8] {
&self.extra_data
}
fn validate_crypto_kind(
crypto: &Crypto,
crypto_kind: CryptoKind,
) -> VeilidAPIResult<CryptoSystemGuard<'_>> {
let vcrypto = crypto
.get(crypto_kind)
.ok_or_else(|| VeilidAPIError::parse_error("unsupported crypto kind", crypto_kind))?;
if vcrypto.nonce_length() != RCP0_NONCE_LENGTH
|| vcrypto.hash_digest_length() != HASH_COORDINATE_LENGTH
|| vcrypto.public_key_length() != HASH_COORDINATE_LENGTH
{
apibail_generic!("unsupported crypto kind for this envelope type");
}
Ok(vcrypto)
}
fn check_node_id(crypto_kind: CryptoKind, node_id: &NodeId) -> VeilidAPIResult<()> {
if node_id.kind() != crypto_kind {
apibail_parse_error!("invalid crypto kind for RCP0", node_id.kind());
}
if node_id.ref_value().len() != HASH_COORDINATE_LENGTH {
apibail_parse_error!("invalid node_id length for RCP0", node_id.ref_value().len());
}
Ok(())
}
}