use super::{Event, ReadError, Vector};
use crate::mock::server::check_session;
use crate::mock::{TIMESTAMP, unframe};
use crate::{
CRYPTO_DOMAIN_WIRE, CRYPTO_DOMAIN_WIRE_HOST_TO_ARK, Client, Error, HostToArk, handshake,
};
use base64::prelude::*;
use darkbio_crypto::{cbor, cose, xdsa, xhpke};
use prost::Message;
use serde_json::Value;
use std::cell::RefCell;
use std::io::{self, ErrorKind, Read, Write};
use std::path::Path;
use std::rc::Rc;
pub fn parse(json: &str) -> Vector {
let root: Value = serde_json::from_str(json).expect("vector is not JSON");
let server = &root["server"];
Vector {
scenario: text(&root["scenario"]),
script: list(&root["script"]).iter().map(text).collect(),
write_failures: root["write_failures"].as_bool().expect("expected a flag"),
identity: bytes(&server["identity"]),
attestation: bytes(&server["attestation"]),
server_keys: list(&server["xhpke"]).iter().map(bytes).collect(),
trace: list(&root["trace"]).iter().map(event).collect(),
}
}
fn text(value: &Value) -> String {
value.as_str().expect("expected a string").to_string()
}
fn bytes(value: &Value) -> Vec<u8> {
BASE64_STANDARD
.decode(value.as_str().expect("expected base64"))
.expect("invalid base64")
}
fn list(value: &Value) -> &[Value] {
value.as_array().expect("expected a list")
}
fn count(value: &Value) -> usize {
value.as_u64().expect("expected a count") as usize
}
fn payload(event: &Value) -> Vec<u8> {
match (&event["bytes"], &event["runs"]) {
(Value::String(text), _) => BASE64_STANDARD.decode(text).expect("invalid base64"),
(_, Value::Array(runs)) => runs
.iter()
.flat_map(|run| std::iter::repeat_n(count(&run[0]) as u8, count(&run[1])))
.collect(),
_ => panic!("event without bytes or runs"),
}
}
fn event(value: &Value) -> Event {
match value["event"].as_str().expect("event without a kind") {
"handshake" => Event::Handshake {
xdsa: bytes(&value["xdsa"]),
xhpke: bytes(&value["xhpke"]),
},
"send" => Event::Send {
message: bytes(&value["message"]),
},
"recv" => Event::Recv,
"ok" => Event::Ok {
message: value.get("message").map(bytes),
},
"error" => Event::Err {
kind: text(&value["kind"]),
},
"session" => Event::Session {
established: value["established"].as_bool().expect("expected a flag"),
},
"read" => match value.get("error") {
Some(error) => Event::ReadFailed {
error: ReadError::parse(&text(error)),
},
None => Event::Read {
bytes: payload(value),
chunk: value.get("chunk").map_or(0, count),
},
},
"write" => Event::Write {
bytes: payload(value),
failed: value.get("failed") == Some(&Value::Bool(true)),
},
"flush_failed" => Event::FlushFailed,
other => panic!("unknown event {other}"),
}
}
pub fn run(vector: &Vector) {
let tape = Rc::new(RefCell::new(Tape::new(vector.trace.clone())));
let mut client = Client::new(Reader(tape.clone()), Writer(tape.clone()));
let mut peer = Peer::new(vector);
while !tape.borrow().done() {
let event = tape.borrow_mut().next();
match event {
Event::Handshake { xdsa, xhpke } => {
let signer = xdsa::SecretKey::from_bytes(xdsa[..].try_into().unwrap());
let crypto = xhpke::SecretKey::from_bytes(xhpke[..].try_into().unwrap());
peer.signer = Some(signer.public_key());
let result = client
.handshake_with_keys(&peer.identity, signer, crypto, TIMESTAMP)
.map(|attestation| {
assert_eq!(attestation.as_bytes(), &vector.attestation[..]);
None
});
settle(&tape, &mut peer, None, result);
}
Event::Send { message } => {
let request = HostToArk::decode(&message[..]).unwrap();
let result = client.send_message(request).map(|_| None);
settle(&tape, &mut peer, Some(&message), result);
}
Event::Recv => {
let result = client.next_message().map(|msg| Some(msg.encode_to_vec()));
settle(&tape, &mut peer, None, result);
}
Event::Session { established } => check_session(&mut client, established),
event => panic!("transcript has {event:?} outside a call"),
}
}
}
fn settle(
tape: &Rc<RefCell<Tape>>,
peer: &mut Peer,
request: Option<&[u8]>,
result: Result<Option<Vec<u8>>, Error>,
) {
let (expected, writes) = {
let mut tape = tape.borrow_mut();
let expected = tape.next();
(expected, std::mem::take(&mut tape.writes))
};
match (result, expected) {
(Ok(message), Event::Ok { message: recorded }) => assert_eq!(message, recorded),
(Err(err), Event::Err { kind }) => assert_eq!(<&str>::from(&err), kind),
(result, expected) => {
panic!("client returned {result:?} where the transcript has {expected:?}")
}
}
for (recorded, actual) in writes {
peer.check(&recorded, &actual, request);
}
}
struct Tape {
trace: Vec<Event>,
next: usize, pending: Vec<u8>, chunk: usize, writes: Vec<(Vec<u8>, Vec<u8>)>, }
impl Tape {
fn new(trace: Vec<Event>) -> Self {
Self {
trace,
next: 0,
pending: Vec::new(),
chunk: 0,
writes: Vec::new(),
}
}
fn done(&self) -> bool {
self.next == self.trace.len()
}
fn next(&mut self) -> Event {
let event = self.trace.get(self.next).cloned();
self.next += 1;
event.expect("transcript ended before the client did")
}
}
struct Reader(Rc<RefCell<Tape>>);
impl Read for Reader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let mut tape = self.0.borrow_mut();
if tape.pending.is_empty() {
match tape.next() {
Event::Read { bytes, chunk } => {
tape.pending = bytes;
tape.chunk = chunk;
}
Event::ReadFailed {
error: ReadError::Eof,
} => return Ok(0),
Event::ReadFailed {
error: ReadError::Failed,
} => return Err(ErrorKind::WouldBlock.into()),
Event::ReadFailed {
error: ReadError::Interrupted,
} => return Err(ErrorKind::Interrupted.into()),
event => panic!("client read where the transcript has {event:?}"),
}
}
let mut n = buf.len().min(tape.pending.len());
if tape.chunk > 0 {
n = n.min(tape.chunk);
}
buf[..n].copy_from_slice(&tape.pending[..n]);
tape.pending.drain(..n);
Ok(n)
}
}
struct Writer(Rc<RefCell<Tape>>);
impl Write for Writer {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let mut tape = self.0.borrow_mut();
let (recorded, failed) = match tape.next() {
Event::Write { bytes, failed } => (bytes, failed),
event => panic!("client wrote where the transcript has {event:?}"),
};
let n = match failed {
true => recorded.len().min(buf.len()),
false => buf.len(),
};
tape.writes.push((recorded, buf[..n].to_vec()));
match failed {
true => Err(ErrorKind::BrokenPipe.into()),
false => Ok(n),
}
}
fn flush(&mut self) -> io::Result<()> {
let mut tape = self.0.borrow_mut();
if tape.trace.get(tape.next) == Some(&Event::FlushFailed) {
tape.next += 1;
return Err(ErrorKind::BrokenPipe.into());
}
Ok(())
}
}
struct Peer {
identity: xdsa::PublicKey, xhpke: Vec<xhpke::SecretKey>, signer: Option<xdsa::PublicKey>, receiver: Option<xhpke::Receiver>, }
impl Peer {
fn new(vector: &Vector) -> Self {
Self {
identity: xdsa::PublicKey::from_bytes(vector.identity[..].try_into().unwrap()).unwrap(),
xhpke: vector
.server_keys
.iter()
.map(|seed| xhpke::SecretKey::from_bytes(seed[..].try_into().unwrap()))
.collect(),
signer: None,
receiver: None,
}
}
fn check(&mut self, recorded: &[u8], actual: &[u8], request: Option<&[u8]>) {
if recorded == actual || actual.last() != Some(&0) {
return;
}
let packet = unframe(&actual[..actual.len() - 1]);
assert!(
cbor::decode::<handshake::HostHello>(&packet).is_err(),
"client hello differs from the recorded one"
);
match cose::recipient(&packet) {
Ok(fingerprint) => {
let crypto = self
.xhpke
.iter()
.find(|key| key.public_key().fingerprint() == fingerprint)
.expect("ack sealed to a key the server never had");
let auth = handshake::HostAckAuth {
ark_signer: self.identity.clone(),
ark_crypto: crypto.public_key(),
};
let signer = self.signer.as_ref().expect("ack before any handshake");
let ack: handshake::HostAck =
cose::open(&packet, &auth, crypto, signer, CRYPTO_DOMAIN_WIRE, None)
.expect("client ack does not open");
let encap: [u8; xhpke::ENCAP_KEY_SIZE] = ack
.h2a_encap
.try_into()
.expect("client ack encap size invalid");
self.receiver = Some(
crypto
.new_receiver(&encap, CRYPTO_DOMAIN_WIRE_HOST_TO_ARK)
.unwrap(),
);
}
Err(_) => {
let receiver = self.receiver.as_mut().expect("request before any ack");
let plain = receiver
.open(&packet, &[])
.expect("client request does not open");
assert_eq!(
Some(&plain[..]),
request,
"client request differs from the one sent"
);
}
}
}
}
#[test]
fn test_vectors_replay() {
let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("vectors/client");
let mut paths: Vec<_> = std::fs::read_dir(&dir)
.expect("no vectors directory")
.map(|entry| entry.unwrap().path())
.collect();
paths.sort();
assert!(!paths.is_empty(), "no vectors to replay");
for path in paths {
let json = std::fs::read_to_string(&path)
.unwrap()
.replace("\r\n", "\n");
let vector = parse(&json);
assert!(
vector.json() == json,
"{} does not re-encode to itself",
path.display()
);
run(&vector);
}
}