use polyc_proto::proto::polychrome::state::v1 as pb;
use polyc_state::{
command::{CommandEnvelope, CommandMetadata, ResourceBounds},
digest::ContentDigest,
error::StateError,
id::{Audience, CommandId, NamespaceId, Purpose},
revision::Revision,
versioned::{EntryExpectation, MAX_MUTATIONS_PER_TRANSACTION, MAX_TRANSACTION_PAYLOAD_BYTES},
wallet::{
CredentialId, KeyRef, PersonaId, SpendPolicyRecord, WalletAddress, WalletAddressPointer,
WalletCommand, WalletLinkRecord, WalletOperation, wallet_scope,
},
};
use crate::wire::{fixed_bytes, malformed, required};
pub(crate) fn expected_to_wire(value: EntryExpectation) -> pb::StateWalletExpectedEntry {
use pb::__buffa::oneof::state_wallet_expected_entry::Expected;
let expected = match value {
EntryExpectation::Absent => Expected::from(pb::StateWalletExpectedAbsent {
__buffa_unknown_fields: buffa::UnknownFields::default(),
}),
EntryExpectation::Revision(revision) => Expected::from(pb::StateWalletExpectedRevision {
revision: revision.get(),
__buffa_unknown_fields: buffa::UnknownFields::default(),
}),
};
pb::StateWalletExpectedEntry {
expected: Some(expected),
__buffa_unknown_fields: buffa::UnknownFields::default(),
}
}
fn expected_from_wire(value: pb::StateWalletExpectedEntry) -> Result<EntryExpectation, StateError> {
use pb::__buffa::oneof::state_wallet_expected_entry::Expected;
match value.expected {
Some(Expected::Absent(_)) => Ok(EntryExpectation::Absent),
Some(Expected::Revision(value)) => {
Ok(EntryExpectation::Revision(Revision::new(value.revision)))
}
None => Err(malformed(
"expected",
"a wallet operation declares its exact row premise",
)),
}
}
pub(crate) fn link_to_wire(value: &WalletLinkRecord) -> pb::StateWalletLink {
pb::StateWalletLink {
wallet_address: value.address().as_str().to_owned(),
currency: value.currency().as_str().to_owned(),
delegated_key_address: value.delegated_key_address().as_str().to_owned(),
key_ref: value.key_ref().as_str().to_owned(),
webauthn_credential_id: value.credential_id().as_str().to_owned(),
expiry_unix: value.expiry_unix(),
created_at_ms: value.created_at_ms(),
revoked: value.is_revoked(),
revoked_at_ms: value.revoked_at_ms(),
__buffa_unknown_fields: buffa::UnknownFields::default(),
}
}
pub(crate) fn link_from_wire(value: pb::StateWalletLink) -> WalletLinkRecord {
WalletLinkRecord::from_parts(
WalletAddress::new(value.wallet_address),
WalletAddress::new(value.currency),
WalletAddress::new(value.delegated_key_address),
KeyRef::new(value.key_ref),
CredentialId::new(value.webauthn_credential_id),
value.expiry_unix,
value.created_at_ms,
value.revoked,
value.revoked_at_ms,
)
}
pub(crate) fn pointer_to_wire(value: &WalletAddressPointer) -> pb::StateWalletAddressPointer {
pb::StateWalletAddressPointer {
persona_id: value.persona().as_str().to_owned(),
claimed_at_ms: value.claimed_at_ms(),
__buffa_unknown_fields: buffa::UnknownFields::default(),
}
}
pub(crate) fn pointer_from_wire(value: pb::StateWalletAddressPointer) -> WalletAddressPointer {
WalletAddressPointer::new(PersonaId::new(value.persona_id), value.claimed_at_ms)
}
pub(crate) fn policy_to_wire(value: &SpendPolicyRecord) -> pb::StateWalletSpendPolicy {
pb::StateWalletSpendPolicy {
limit: value.limit().to_owned(),
period_secs: value.period_secs(),
max_lifetime_secs: value.max_lifetime_secs(),
allowed_hosts: value.allowed_hosts().to_vec(),
set_by: value.set_by().as_str().to_owned(),
updated_at_ms: value.updated_at_ms(),
__buffa_unknown_fields: buffa::UnknownFields::default(),
}
}
pub(crate) fn policy_from_wire(value: pb::StateWalletSpendPolicy) -> SpendPolicyRecord {
SpendPolicyRecord::from_parts(
value.limit,
value.period_secs,
value.max_lifetime_secs,
value.allowed_hosts,
PersonaId::new(value.set_by),
value.updated_at_ms,
)
}
pub(crate) fn operation_to_wire(value: &WalletOperation) -> pb::StateWalletOperation {
use pb::__buffa::oneof::state_wallet_operation::Operation;
let operation = match value {
WalletOperation::Link {
now_ms,
link,
pointer,
supersedes,
link_expected,
pointer_expected,
} => Operation::from(pb::StateWalletLinkOperation {
now_ms: *now_ms,
link: buffa::MessageField::some(link_to_wire(link)),
pointer: buffa::MessageField::some(pointer_to_wire(pointer)),
supersedes_key_ref: supersedes.as_ref().map(|handle| handle.as_str().to_owned()),
link_expected: buffa::MessageField::some(expected_to_wire(*link_expected)),
pointer_expected: buffa::MessageField::some(expected_to_wire(*pointer_expected)),
__buffa_unknown_fields: buffa::UnknownFields::default(),
}),
WalletOperation::Revoke {
now_ms,
expected_key_ref,
link,
link_expected,
} => Operation::from(pb::StateWalletRevokeOperation {
now_ms: *now_ms,
expected_key_ref: expected_key_ref
.as_ref()
.map(|handle| handle.as_str().to_owned()),
link: buffa::MessageField::some(link_to_wire(link)),
link_expected: buffa::MessageField::some(expected_to_wire(*link_expected)),
__buffa_unknown_fields: buffa::UnknownFields::default(),
}),
WalletOperation::PutPolicy {
now_ms,
policy,
expected,
} => Operation::from(pb::StateWalletPutPolicyOperation {
now_ms: *now_ms,
policy: buffa::MessageField::some(policy_to_wire(policy)),
expected: buffa::MessageField::some(expected_to_wire(*expected)),
__buffa_unknown_fields: buffa::UnknownFields::default(),
}),
};
pb::StateWalletOperation {
operation: Some(operation),
__buffa_unknown_fields: buffa::UnknownFields::default(),
}
}
fn operation_from_wire(value: pb::StateWalletOperation) -> Result<WalletOperation, StateError> {
use pb::__buffa::oneof::state_wallet_operation::Operation;
match value.operation {
Some(Operation::Link(value)) => Ok(WalletOperation::Link {
now_ms: value.now_ms,
link: link_from_wire(required(
"link",
"a wallet link operation carries its result",
value.link,
)?),
pointer: pointer_from_wire(required(
"pointer",
"a wallet link operation carries its address pointer",
value.pointer,
)?),
supersedes: value.supersedes_key_ref.map(KeyRef::new),
link_expected: expected_from_wire(required(
"link_expected",
"a wallet link operation carries its link premise",
value.link_expected,
)?)?,
pointer_expected: expected_from_wire(required(
"pointer_expected",
"a wallet link operation carries its pointer premise",
value.pointer_expected,
)?)?,
}),
Some(Operation::Revoke(value)) => Ok(WalletOperation::Revoke {
now_ms: value.now_ms,
expected_key_ref: value.expected_key_ref.map(KeyRef::new),
link: link_from_wire(required(
"link",
"a wallet revocation carries its result",
value.link,
)?),
link_expected: expected_from_wire(required(
"link_expected",
"a wallet revocation carries its premise",
value.link_expected,
)?)?,
}),
Some(Operation::PutPolicy(value)) => Ok(WalletOperation::PutPolicy {
now_ms: value.now_ms,
policy: policy_from_wire(required(
"policy",
"a spend-policy write carries its result",
value.policy,
)?),
expected: expected_from_wire(required(
"expected",
"a spend-policy write carries its premise",
value.expected,
)?)?,
}),
None => Err(malformed(
"operation",
"a wallet command names one operation",
)),
}
}
pub(crate) fn metadata_to_wire(command: &WalletCommand) -> pb::StateWalletCommandMetadata {
let value = command.metadata();
pb::StateWalletCommandMetadata {
command_id: value.command_id().as_str().to_owned(),
namespace: value.scope().namespace().as_str().to_owned(),
purpose: value.envelope().purpose().as_str().to_owned(),
command_audience: value.envelope().audience().as_str().to_owned(),
digest: value.digest().as_bytes().to_vec(),
__buffa_unknown_fields: buffa::UnknownFields::default(),
}
}
pub(crate) fn command_from_wire(
metadata: pb::StateWalletCommandMetadata,
persona_id: String,
operation: pb::StateWalletOperation,
) -> Result<WalletCommand, StateError> {
let namespace = NamespaceId::new(metadata.namespace);
let digest = ContentDigest::from_bytes(fixed_bytes::<{ ContentDigest::LEN }>(
"digest",
&metadata.digest,
)?);
Ok(WalletCommand::new(
CommandMetadata::new(
CommandId::new(metadata.command_id),
polyc_state::wallet::family(),
digest,
wallet_scope(&namespace),
CommandEnvelope::new(
Purpose::new(metadata.purpose),
Audience::new(metadata.command_audience),
ResourceBounds::new(MAX_TRANSACTION_PAYLOAD_BYTES, MAX_MUTATIONS_PER_TRANSACTION),
),
),
PersonaId::new(persona_id),
operation_from_wire(operation)?,
))
}
#[cfg(test)]
mod tests {
use super::*;
const NOW: u64 = 1_700_000_000_000;
fn link() -> WalletLinkRecord {
WalletLinkRecord::from_parts(
WalletAddress::new("0xAb"),
WalletAddress::new("0xCd"),
WalletAddress::new("0xEf"),
KeyRef::new("handle-1"),
CredentialId::new("cred-1"),
7,
NOW,
false,
0,
)
}
fn policy() -> SpendPolicyRecord {
SpendPolicyRecord::from_parts(
"5.25".to_owned(),
3_600,
86_400,
vec!["a.example".to_owned(), "b.example".to_owned()],
PersonaId::new("admin-1"),
NOW,
)
}
#[test]
fn every_operation_round_trips_through_the_wire() {
let operations = [
WalletOperation::Link {
now_ms: NOW,
link: link(),
pointer: WalletAddressPointer::new(PersonaId::new("p-1"), NOW),
supersedes: Some(KeyRef::new("handle-0")),
link_expected: EntryExpectation::Absent,
pointer_expected: EntryExpectation::Revision(Revision::new(9)),
},
WalletOperation::Link {
now_ms: NOW,
link: link(),
pointer: WalletAddressPointer::new(PersonaId::new("p-1"), NOW),
supersedes: None,
link_expected: EntryExpectation::Absent,
pointer_expected: EntryExpectation::Absent,
},
WalletOperation::Revoke {
now_ms: NOW + 1,
expected_key_ref: Some(KeyRef::new("handle-1")),
link: link().revoking(NOW + 1),
link_expected: EntryExpectation::Revision(Revision::new(11)),
},
WalletOperation::Revoke {
now_ms: NOW + 1,
expected_key_ref: None,
link: link().revoking(NOW + 1),
link_expected: EntryExpectation::Revision(Revision::new(11)),
},
WalletOperation::PutPolicy {
now_ms: NOW + 2,
policy: policy(),
expected: EntryExpectation::Revision(Revision::new(13)),
},
];
for operation in operations {
let restored = operation_from_wire(operation_to_wire(&operation))
.expect("an operation survives its own encoding");
assert_eq!(restored, operation);
}
}
#[test]
fn an_absent_supersession_stays_absent() {
let wire = operation_to_wire(&WalletOperation::Link {
now_ms: NOW,
link: link(),
pointer: WalletAddressPointer::new(PersonaId::new("p-1"), NOW),
supersedes: None,
link_expected: EntryExpectation::Absent,
pointer_expected: EntryExpectation::Absent,
});
let restored = operation_from_wire(wire).expect("round trip");
assert!(
matches!(
restored,
WalletOperation::Link {
supersedes: None,
..
}
),
"an absent supersession came back as a declared one"
);
}
#[test]
fn the_rows_round_trip_through_the_wire() {
assert_eq!(link_from_wire(link_to_wire(&link())), link());
assert_eq!(policy_from_wire(policy_to_wire(&policy())), policy());
let pointer = WalletAddressPointer::new(PersonaId::new("p-1"), NOW);
assert_eq!(pointer_from_wire(pointer_to_wire(&pointer)), pointer);
}
#[test]
fn a_missing_oneof_and_a_missing_premise_fail_closed() {
assert!(
operation_from_wire(pb::StateWalletOperation::default()).is_err(),
"an operation with no variant was accepted"
);
assert!(
expected_from_wire(pb::StateWalletExpectedEntry::default()).is_err(),
"a premise with no variant was accepted"
);
let put = pb::StateWalletPutPolicyOperation {
now_ms: NOW,
policy: buffa::MessageField::some(policy_to_wire(&policy())),
expected: buffa::MessageField::none(),
__buffa_unknown_fields: buffa::UnknownFields::default(),
};
assert!(
operation_from_wire(pb::StateWalletOperation {
operation: Some(pb::__buffa::oneof::state_wallet_operation::Operation::from(
put
)),
__buffa_unknown_fields: buffa::UnknownFields::default(),
})
.is_err(),
"a policy write with no premise was accepted"
);
}
#[test]
fn a_short_digest_is_refused() {
let metadata = pb::StateWalletCommandMetadata {
command_id: "cmd-1".to_owned(),
namespace: "polychrome".to_owned(),
purpose: "wallet-link".to_owned(),
command_audience: "state".to_owned(),
digest: vec![0; ContentDigest::LEN - 1],
__buffa_unknown_fields: buffa::UnknownFields::default(),
};
assert!(
command_from_wire(
metadata,
"p-1".to_owned(),
operation_to_wire(&WalletOperation::PutPolicy {
now_ms: NOW,
policy: policy(),
expected: EntryExpectation::Absent,
})
)
.is_err(),
"a truncated digest was accepted"
);
}
}