use serde::{Deserialize, Serialize};
use super::{Caller, ContractKind, FINGERPRINT_FORMAT, ResolvedMutation, identifier};
use crate::serve::protocol::Version;
use crate::serve::storage::Failure;
const RECEIPT_FORMAT: u32 = 2;
#[derive(Debug, Serialize)]
pub struct Receipt {
format: u32,
request_id: String,
request_origin: Version,
caller: Caller,
contract_kind: ContractKind,
fingerprint_format: u32,
fingerprint: String,
branch: String,
version: Version,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct ReceiptWire {
format: u32,
request_id: String,
request_origin: Version,
caller: Caller,
contract_kind: ContractKind,
fingerprint_format: u32,
fingerprint: String,
branch: String,
version: Version,
}
#[derive(Deserialize)]
struct FormatHeader {
format: Option<u64>,
}
impl Receipt {
pub fn for_request(request: &ResolvedMutation, installed: Version) -> Result<Self, Failure> {
Ok(Self {
format: RECEIPT_FORMAT,
request_id: request.request_id().to_owned(),
request_origin: request.origin(),
caller: request.caller().clone(),
contract_kind: request.contract().kind(),
fingerprint_format: FINGERPRINT_FORMAT,
fingerprint: request.fingerprint()?,
branch: request.destination().to_owned(),
version: installed,
})
}
pub fn decode(bytes: &[u8]) -> Result<Self, Failure> {
let header: FormatHeader = serde_json::from_slice(bytes)
.map_err(|error| Failure::new("stored_receipt_corrupt", error.to_string()))?;
if header.format != Some(u64::from(RECEIPT_FORMAT)) {
return Err(Failure::new(
"stored_receipt_format_unsupported",
"stored receipt is not format 2",
));
}
let wire: ReceiptWire = serde_json::from_slice(bytes)
.map_err(|error| Failure::new("stored_receipt_corrupt", error.to_string()))?;
identifier(&wire.request_id)?;
identifier(&wire.branch)?;
if wire.fingerprint_format != FINGERPRINT_FORMAT {
return Err(Failure::new(
"stored_fingerprint_format_unsupported",
"stored receipt has an unsupported fingerprint format",
));
}
if wire.fingerprint.len() != 64
|| !wire
.fingerprint
.bytes()
.all(|byte| matches!(byte, b'0'..=b'9' | b'a'..=b'f'))
{
return Err(Failure::new(
"stored_receipt_corrupt",
"stored receipt fingerprint is not a canonical digest",
));
}
Ok(Self {
format: wire.format,
request_id: wire.request_id,
request_origin: wire.request_origin,
caller: wire.caller,
contract_kind: wire.contract_kind,
fingerprint_format: wire.fingerprint_format,
fingerprint: wire.fingerprint,
branch: wire.branch,
version: wire.version,
})
}
pub fn replay(&self, request: &ResolvedMutation) -> Result<&Self, Failure> {
if self.request_id != request.request_id() || self.branch != request.destination() {
return Err(Failure::new(
"receipt_corrupt",
"stored receipt identity differs from its requested key",
));
}
if self.caller != *request.caller()
|| self.request_origin != request.origin()
|| self.contract_kind != request.contract().kind()
|| self.fingerprint != request.fingerprint()?
{
return Err(Failure::new(
"request_id_reused",
"request identity was used with different content",
));
}
Ok(self)
}
#[must_use]
pub const fn caller(&self) -> &Caller {
&self.caller
}
#[must_use]
pub const fn request_origin(&self) -> Version {
self.request_origin
}
#[must_use]
pub fn branch(&self) -> &str {
&self.branch
}
#[must_use]
pub const fn version(&self) -> Version {
self.version
}
}