use std::time::Duration;
use bc_components::{ARID, keypair, keypair_using};
use bc_envelope::prelude::*;
use bc_rand::make_fake_random_number_generator;
use bc_xid::{XIDDocument, XIDGenesisMarkOptions, XIDInceptionKeyOptions};
use gstp::prelude::*;
use hex_literal::hex;
use indoc::indoc;
fn request_id() -> ARID {
ARID::from_data(hex!(
"c66be27dbad7cd095ca77647406d07976dc0f35f0d4d654bb0e96dd227a1e9fc"
))
}
fn request_date() -> Date { Date::try_from("2024-07-04T11:11:11Z").unwrap() }
fn request_continuation() -> Continuation {
let valid_duration = Duration::from_secs(60);
let valid_until = request_date() + valid_duration;
Continuation::new("The state of things.")
.with_valid_id(request_id())
.with_valid_until(valid_until)
}
fn response_continuation() -> Continuation {
let valid_duration = Duration::from_secs(60 * 60);
let valid_until = request_date() + valid_duration;
Continuation::new("The state of things.").with_valid_until(valid_until)
}
#[test]
fn test_request_continuation() {
bc_envelope::register_tags();
let continuation = request_continuation();
let envelope = continuation.to_envelope(None);
#[rustfmt::skip]
assert_eq!(envelope.format(), indoc!{r#"
{
"The state of things."
} [
'id': ARID(c66be27d)
'validUntil': 2024-07-04T11:12:11Z
]
"#}.trim());
let parsed_continuation = Continuation::try_from_envelope(
&envelope,
Some(request_id()),
None,
None,
)
.unwrap();
assert_eq!(continuation.state(), parsed_continuation.state());
assert_eq!(continuation.id(), parsed_continuation.id());
assert_eq!(
continuation.valid_until(),
parsed_continuation.valid_until()
);
assert_eq!(continuation, parsed_continuation);
}
#[test]
fn test_response_continuation() {
bc_envelope::register_tags();
let continuation = response_continuation();
let envelope = continuation.to_envelope(None);
#[rustfmt::skip]
assert_eq!(envelope.format(), indoc!{r#"
{
"The state of things."
} [
'validUntil': 2024-07-04T12:11:11Z
]
"#}.trim());
let parsed_continuation =
Continuation::try_from_envelope(&envelope, None, None, None).unwrap();
assert_eq!(continuation.state(), parsed_continuation.state());
assert_eq!(continuation.id(), parsed_continuation.id());
assert_eq!(
continuation.valid_until(),
parsed_continuation.valid_until()
);
assert_eq!(continuation, parsed_continuation);
}
#[test]
fn test_encrypted_continuation() {
bc_envelope::register_tags();
let (sender_private_keys, sender_public_keys) = keypair();
let continuation = request_continuation();
let envelope = continuation.to_envelope(Some(&sender_public_keys));
#[rustfmt::skip]
assert_eq!(envelope.format(), indoc!{r#"
ENCRYPTED [
'hasRecipient': SealedMessage
]
"#}.trim());
let valid_now = Some(request_date() + Duration::from_secs(30));
let parsed_continuation = Continuation::try_from_envelope(
&envelope,
Some(request_id()),
valid_now,
Some(&sender_private_keys),
)
.unwrap();
assert_eq!(continuation.state(), parsed_continuation.state());
assert_eq!(continuation.id(), parsed_continuation.id());
assert_eq!(
continuation.valid_until(),
parsed_continuation.valid_until()
);
assert_eq!(continuation, parsed_continuation);
let invalid_now = Some(request_date() + Duration::from_secs(90));
let invalid_continuation_error = Continuation::try_from_envelope(
&envelope,
Some(request_id()),
invalid_now,
Some(&sender_private_keys),
);
assert!(invalid_continuation_error.is_err());
let invalid_id = ARID::new();
let invalid_continuation_error = Continuation::try_from_envelope(
&envelope,
Some(invalid_id),
valid_now,
Some(&sender_private_keys),
);
assert!(invalid_continuation_error.is_err());
}
#[test]
fn test_sealed_request() {
bc_envelope::register_tags();
let mut rng = make_fake_random_number_generator();
let (server_private_keys, server_public_keys) =
keypair_using(&mut rng).unwrap();
let server = XIDDocument::new(
XIDInceptionKeyOptions::PublicAndPrivateKeys(
server_public_keys.clone(),
server_private_keys.clone(),
),
XIDGenesisMarkOptions::None,
);
let (client_private_keys, client_public_keys) =
keypair_using(&mut rng).unwrap();
let client = XIDDocument::new(
XIDInceptionKeyOptions::PublicAndPrivateKeys(
client_public_keys.clone(),
client_private_keys.clone(),
),
XIDGenesisMarkOptions::None,
);
let now = Date::try_from("2024-07-04T11:11:11Z").unwrap();
let server_response_date = now - Duration::from_secs(30);
let server_continuation_valid_until =
server_response_date + Duration::from_secs(60);
let server_state = Expression::new("nextPage")
.with_parameter("fromRecord", 100)
.with_parameter("toRecord", 199);
let server_continuation = Continuation::new(server_state)
.with_valid_until(server_continuation_valid_until);
let server_continuation =
server_continuation.to_envelope(Some(&server_public_keys));
let client_continuation_valid_until = now + Duration::from_secs(60);
let client_request = SealedRequest::new("test", request_id(), &client)
.with_parameter("param1", 42)
.with_parameter("param2", "hello")
.with_note("This is a test")
.with_date(now)
.with_state("The state of things.")
.with_peer_continuation(server_continuation);
let signed_client_request_envelope = client_request
.to_envelope(
Some(client_continuation_valid_until),
Some(&client_private_keys),
None,
)
.unwrap();
#[rustfmt::skip]
assert_eq!(signed_client_request_envelope.format(), (indoc! {r#"
{
request(ARID(c66be27d)) [
'body': «"test"» [
❰"param1"❱: 42
❰"param2"❱: "hello"
]
'date': 2024-07-04T11:11:11Z
'note': "This is a test"
'recipientContinuation': ENCRYPTED [
'hasRecipient': SealedMessage
]
'sender': XID(c017c16f) [
'key': PublicKeys(f0d6b2fc, SigningPublicKey(c017c16f, SchnorrPublicKey(92f53715)), EncapsulationPublicKey(57b57f13, X25519PublicKey(57b57f13))) [
'allow': 'All'
]
]
'senderContinuation': ENCRYPTED [
'hasRecipient': SealedMessage
]
]
} [
'signed': Signature
]
"#}).trim()
);
let sealed_client_request_envelope = client_request
.to_envelope(
Some(client_continuation_valid_until),
Some(&client_private_keys),
Some(&server),
)
.unwrap();
let parsed_client_request = SealedRequest::try_from_envelope(
&sealed_client_request_envelope,
None,
Some(now),
&server_private_keys,
)
.unwrap();
assert_eq!(
*parsed_client_request.function(),
Into::<Function>::into("test")
);
assert_eq!(
parsed_client_request
.extract_object_for_parameter::<i32>("param1")
.unwrap(),
42
);
assert_eq!(
parsed_client_request
.extract_object_for_parameter::<String>("param2")
.unwrap(),
"hello"
);
assert_eq!(parsed_client_request.note(), "This is a test");
assert_eq!(parsed_client_request.date(), Some(now));
let state = parsed_client_request.state().unwrap();
#[rustfmt::skip]
assert_eq!(state.format(), (indoc! {r#"
«"nextPage"» [
❰"fromRecord"❱: 100
❰"toRecord"❱: 199
]
"#}).trim());
let state = Expression::new("nextPage")
.with_parameter("fromRecord", 200)
.with_parameter("toRecord", 299);
let peer_continuation = parsed_client_request.peer_continuation();
let server_response =
SealedResponse::new_success(parsed_client_request.id(), server)
.with_result("Records retrieved: 100-199")
.with_state(state)
.with_peer_continuation(peer_continuation);
let server_continuation_valid_until = now + Duration::from_secs(60);
let signed_server_response_envelope = server_response
.to_envelope(
Some(server_continuation_valid_until),
Some(&server_private_keys),
None,
)
.unwrap();
#[rustfmt::skip]
assert_eq!(signed_server_response_envelope.format(), (indoc! {r#"
{
response(ARID(c66be27d)) [
'recipientContinuation': ENCRYPTED [
'hasRecipient': SealedMessage
]
'result': "Records retrieved: 100-199"
'sender': XID(57a4c9d8) [
'key': PublicKeys(f53a5f32, SigningPublicKey(57a4c9d8, SchnorrPublicKey(d5edb8ba)), EncapsulationPublicKey(822c6133, X25519PublicKey(822c6133))) [
'allow': 'All'
]
]
'senderContinuation': ENCRYPTED [
'hasRecipient': SealedMessage
]
]
} [
'signed': Signature
]
"#}).trim());
let sealed_server_response_envelope = server_response
.to_envelope(
Some(server_continuation_valid_until),
Some(&server_private_keys),
Some(&client),
)
.unwrap();
let parsed_server_response = SealedResponse::try_from_encrypted_envelope(
&sealed_server_response_envelope,
Some(parsed_client_request.id()),
Some(now),
&client_private_keys,
)
.unwrap();
#[rustfmt::skip]
assert_eq!(parsed_server_response.result().unwrap().format(), (indoc! {r#"
"Records retrieved: 100-199"
"#}).trim());
#[rustfmt::skip]
assert_eq!(parsed_server_response.state().unwrap().format(), (indoc! {r#"
"The state of things."
"#}).trim());
}
#[test]
fn test_multi_recipient_request_and_response() {
bc_envelope::register_tags();
let mut rng = make_fake_random_number_generator();
let (server_private_keys, server_public_keys) =
keypair_using(&mut rng).unwrap();
let server = XIDDocument::new(
XIDInceptionKeyOptions::PublicAndPrivateKeys(
server_public_keys.clone(),
server_private_keys.clone(),
),
XIDGenesisMarkOptions::None,
);
let (auditor_private_keys, auditor_public_keys) =
keypair_using(&mut rng).unwrap();
let auditor = XIDDocument::new(
XIDInceptionKeyOptions::PublicAndPrivateKeys(
auditor_public_keys.clone(),
auditor_private_keys.clone(),
),
XIDGenesisMarkOptions::None,
);
let (client_private_keys, client_public_keys) =
keypair_using(&mut rng).unwrap();
let client = XIDDocument::new(
XIDInceptionKeyOptions::PublicAndPrivateKeys(
client_public_keys.clone(),
client_private_keys.clone(),
),
XIDGenesisMarkOptions::None,
);
let now = Date::try_from("2024-07-04T11:11:11Z").unwrap();
let server_state = Expression::new("nextPage")
.with_parameter("fromRecord", 100)
.with_parameter("toRecord", 199);
let server_continuation = Continuation::new(server_state)
.with_valid_until(now + Duration::from_secs(60));
let server_continuation =
server_continuation.to_envelope(Some(&server_public_keys));
let client_continuation_valid_until = now + Duration::from_secs(60);
let client_request = SealedRequest::new("test", request_id(), &client)
.with_parameter("param1", 42)
.with_parameter("param2", "hello")
.with_note("This is a test")
.with_date(now)
.with_state("The state of things.")
.with_peer_continuation(server_continuation);
let recipients = vec![&server, &auditor];
let sealed_client_request_envelope = client_request
.to_envelope_for_recipients(
Some(client_continuation_valid_until),
Some(&client_private_keys),
&recipients,
)
.unwrap();
#[rustfmt::skip]
assert_eq!(
sealed_client_request_envelope.format(),
(indoc! {r#"
ENCRYPTED [
'hasRecipient': SealedMessage
'hasRecipient': SealedMessage
]
"#})
.trim()
);
let sealed_messages = sealed_client_request_envelope.recipients().unwrap();
assert_eq!(sealed_messages.len(), 2);
assert!(sealed_messages.iter().any(|sealed_message| {
sealed_message.decrypt(&server_private_keys).is_ok()
}));
assert!(sealed_messages.iter().any(|sealed_message| {
sealed_message.decrypt(&auditor_private_keys).is_ok()
}));
sealed_client_request_envelope
.decrypt_to_recipient(&server_private_keys)
.expect("server can decrypt multi-recipient request");
let parsed_client_request_server = SealedRequest::try_from_envelope(
&sealed_client_request_envelope,
None,
Some(now),
&server_private_keys,
)
.unwrap();
sealed_client_request_envelope
.decrypt_to_recipient(&auditor_private_keys)
.expect("auditor can decrypt multi-recipient request");
assert_eq!(
parsed_client_request_server
.extract_object_for_parameter::<i32>("param1")
.unwrap(),
42
);
assert_eq!(
parsed_client_request_server
.extract_object_for_parameter::<String>("param2")
.unwrap(),
"hello"
);
let server_state = Expression::new("nextPage")
.with_parameter("fromRecord", 200)
.with_parameter("toRecord", 299);
let peer_continuation = parsed_client_request_server.peer_continuation();
let server_response =
SealedResponse::new_success(parsed_client_request_server.id(), &server)
.with_result("Records retrieved: 100-199")
.with_state(server_state)
.with_peer_continuation(peer_continuation);
let response_recipients = vec![&client, &auditor];
let sealed_server_response_envelope = server_response
.to_envelope_for_recipients(
Some(now + Duration::from_secs(60)),
Some(&server_private_keys),
&response_recipients,
)
.unwrap();
#[rustfmt::skip]
assert_eq!(
sealed_server_response_envelope.format(),
(indoc! {r#"
ENCRYPTED [
'hasRecipient': SealedMessage
'hasRecipient': SealedMessage
]
"#})
.trim()
);
let parsed_server_response_client =
SealedResponse::try_from_encrypted_envelope(
&sealed_server_response_envelope,
Some(parsed_client_request_server.id()),
Some(now),
&client_private_keys,
)
.unwrap();
assert_eq!(
parsed_server_response_client
.extract_result::<String>()
.unwrap(),
"Records retrieved: 100-199"
);
sealed_server_response_envelope
.decrypt_to_recipient(&auditor_private_keys)
.expect("auditor can decrypt multi-recipient response");
}
#[test]
fn test_sealed_event() {
bc_envelope::register_tags();
let mut rng = make_fake_random_number_generator();
let (sender_private_keys, sender_public_keys) =
keypair_using(&mut rng).unwrap();
let sender = XIDDocument::new(
XIDInceptionKeyOptions::PublicAndPrivateKeys(
sender_public_keys.clone(),
sender_private_keys.clone(),
),
XIDGenesisMarkOptions::None,
);
let (recipient_private_keys, recipient_public_keys) =
keypair_using(&mut rng).unwrap();
let recipient = XIDDocument::new(
XIDInceptionKeyOptions::PublicAndPrivateKeys(
recipient_public_keys.clone(),
recipient_private_keys.clone(),
),
XIDGenesisMarkOptions::None,
);
let now = Date::try_from("2024-07-04T11:11:11Z").unwrap();
let event = SealedEvent::<String>::new("test", request_id(), &sender)
.with_note("This is a test")
.with_date(now);
let signed_event_envelope = event
.to_envelope(None, Some(&sender_private_keys), None)
.unwrap();
println!("{}", signed_event_envelope.format());
#[rustfmt::skip]
assert_eq!(signed_event_envelope.format(), (indoc! {r#"
{
event(ARID(c66be27d)) [
'content': "test"
'date': 2024-07-04T11:11:11Z
'note': "This is a test"
'sender': XID(57a4c9d8) [
'key': PublicKeys(f53a5f32, SigningPublicKey(57a4c9d8, SchnorrPublicKey(d5edb8ba)), EncapsulationPublicKey(822c6133, X25519PublicKey(822c6133))) [
'allow': 'All'
]
]
]
} [
'signed': Signature
]
"#}).trim());
let sealed_event_envelope = event
.to_envelope(None, Some(&sender_private_keys), Some(&recipient))
.unwrap();
let parsed_event = SealedEvent::<String>::try_from_envelope(
&sealed_event_envelope,
None,
None,
&recipient_private_keys,
)
.unwrap();
assert_eq!(parsed_event.content(), "test");
assert_eq!(parsed_event.note(), "This is a test");
assert_eq!(parsed_event.date(), Some(now));
}
#[test]
fn test_sealed_event_multiple_recipients() {
bc_envelope::register_tags();
let mut rng = make_fake_random_number_generator();
let (sender_private_keys, sender_public_keys) =
keypair_using(&mut rng).unwrap();
let sender = XIDDocument::new(
XIDInceptionKeyOptions::PublicAndPrivateKeys(
sender_public_keys.clone(),
sender_private_keys.clone(),
),
XIDGenesisMarkOptions::None,
);
let (recipient_a_private_keys, recipient_a_public_keys) =
keypair_using(&mut rng).unwrap();
let recipient_a = XIDDocument::new(
XIDInceptionKeyOptions::PublicAndPrivateKeys(
recipient_a_public_keys.clone(),
recipient_a_private_keys.clone(),
),
XIDGenesisMarkOptions::None,
);
let (recipient_b_private_keys, recipient_b_public_keys) =
keypair_using(&mut rng).unwrap();
let recipient_b = XIDDocument::new(
XIDInceptionKeyOptions::PublicAndPrivateKeys(
recipient_b_public_keys.clone(),
recipient_b_private_keys.clone(),
),
XIDGenesisMarkOptions::None,
);
let recipients = vec![&recipient_a, &recipient_b];
let valid_until = Date::try_from("2024-07-04T11:12:11Z").unwrap();
let event = SealedEvent::<Expression>::new(
Expression::new("sync"),
request_id(),
&sender,
)
.with_note("Escrow update")
.with_state("state");
let sealed_event_envelope = event
.to_envelope_for_recipients(
Some(valid_until),
Some(&sender_private_keys),
&recipients,
)
.unwrap();
#[rustfmt::skip]
assert_eq!(
sealed_event_envelope.format(),
(indoc! {r#"
ENCRYPTED [
'hasRecipient': SealedMessage
'hasRecipient': SealedMessage
]
"#})
.trim()
);
let parsed_event_a = SealedEvent::<Expression>::try_from_envelope(
&sealed_event_envelope,
Some(request_id()),
None,
&recipient_a_private_keys,
)
.unwrap();
let parsed_event_b = SealedEvent::<Expression>::try_from_envelope(
&sealed_event_envelope,
Some(request_id()),
None,
&recipient_b_private_keys,
)
.unwrap();
assert_eq!(parsed_event_a.note(), parsed_event_b.note());
}