pub mod client;
pub mod duplex;
#[cfg(all(feature = "fuzz", getrandom_backend = "custom"))]
pub mod random;
#[cfg(feature = "fuzz")]
pub mod seed;
pub mod server;
pub mod vector;
use crate::transport::{Attestation, Error, MAX_MESSAGE_SIZE, Sender, Write};
use darkbio_cobs as cobs;
use darkbio_crypto::cwt::claims::{self, eat};
use darkbio_crypto::{cwt, xdsa};
use std::io;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use vector::{Event, Vector};
pub const MAX_STEPS: usize = 64;
pub const TIMESTAMP: i64 = 0;
pub const SCRIPT_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(3600);
pub(super) const OVERSIZED_MESSAGE: &[u8] = &[0x42; MAX_MESSAGE_SIZE + 1];
pub fn payload(tag: u64) -> Vec<u8> {
tag.to_be_bytes().to_vec()
}
pub(super) fn send<W: Write>(sender: Option<&Sender<W>>, message: &[u8]) -> Result<(), Error> {
match sender {
Some(sender) => sender.send(message),
None => Err(Error::EncryptionFailed("no active session".into())),
}
}
pub fn self_attestation(signer: &xdsa::SecretKey) -> Attestation {
let claims = darkbio_trust::device::HardwareClaims {
sub: claims::Subject { sub: "".into() },
cnf: claims::Confirm::new(signer.public_key()),
nbf: claims::NotBefore { nbf: 0 },
iat: claims::IssuedAt { iat: 0 },
oem: eat::Oemid::new_pen(0),
hwm: eat::HwModel { hw_model: vec![] },
hwv: eat::HwVersion::new("".into()),
};
let cwt = cwt::issue_at(
&claims,
signer,
darkbio_trust::CRYPTO_DOMAIN_DEVICE_ATTESTATION,
TIMESTAMP,
)
.unwrap();
Attestation::new(cwt).unwrap()
}
pub fn cloud_attestation(signer: &xdsa::SecretKey) -> Vec<u8> {
let claims = darkbio_trust::cloud::SignerClaims {
iss: claims::Issuer { iss: "".into() },
sub: claims::Subject { sub: "".into() },
nbf: claims::NotBefore { nbf: 0 },
exp: claims::Expiration { exp: 1 },
cnf: claims::Confirm::new(signer.public_key()),
};
cwt::issue_at(
&claims,
signer,
darkbio_trust::CRYPTO_DOMAIN_DEVICE_ATTESTATION,
TIMESTAMP,
)
.unwrap()
}
pub fn frame(packet: &[u8]) -> Vec<u8> {
let mut buf = vec![0u8; cobs::encode_buffer(packet.len())];
let n = cobs::encode(packet, &mut buf).unwrap();
buf.truncate(n);
buf.push(0x00);
buf
}
pub fn unframe(frame: &[u8]) -> Vec<u8> {
let mut buf = vec![0u8; cobs::decode_buffer(frame.len())];
let n = cobs::decode(frame, &mut buf).expect("side under test wrote an undecodable frame");
buf.truncate(n);
buf
}
pub fn would_block() -> io::Error {
io::ErrorKind::WouldBlock.into()
}
pub type Recorder = Arc<Mutex<Option<Vector>>>;
pub fn trace(recorder: &Recorder, event: impl FnOnce() -> Event) {
if let Some(vector) = recorder.lock().unwrap().as_mut() {
vector.log(event());
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
pub enum CutPoint {
Start,
Middle(u16),
Delimiter,
Flush,
}
#[derive(Clone, Default)]
pub struct Outbox {
shared: Arc<Mutex<OutputState>>,
recorder: Recorder, }
#[derive(Default)]
struct OutputState {
bytes: Vec<u8>,
broken: bool,
cut: Option<CutPoint>,
timeout: bool,
write_error: Option<io::ErrorKind>, flush_error: Option<io::ErrorKind>,
}
impl Outbox {
pub fn take_frames(&self) -> Vec<Vec<u8>> {
let mut state = self.shared.lock().unwrap();
let mut frames: Vec<Vec<u8>> = state.bytes.split(|&b| b == 0).map(<[u8]>::to_vec).collect();
let tail = frames.pop().expect("split yields at least one piece");
state.bytes = tail;
frames
}
pub fn has_tail(&self) -> bool {
!self.shared.lock().unwrap().bytes.is_empty()
}
pub fn set_broken(&self, broken: bool) {
self.shared.lock().unwrap().broken = broken;
}
pub fn set_cut(&self, point: CutPoint) {
let mut state = self.shared.lock().unwrap();
state.cut = Some(point);
state.timeout = false;
}
pub fn set_timeout(&self, point: CutPoint) {
let mut state = self.shared.lock().unwrap();
state.cut = Some(point);
state.timeout = true;
}
fn accept(state: &mut OutputState, buf: &[u8]) -> (usize, Option<io::ErrorKind>) {
if let Some(point) = state.cut {
let accepted = match point {
CutPoint::Start => Some(0),
CutPoint::Middle(n) => (buf.len() > 2).then(|| 1 + (n as usize).min(buf.len() - 3)),
CutPoint::Delimiter => (buf.len() > 1).then(|| buf.len() - 1),
CutPoint::Flush => (buf.len() > 1).then_some(buf.len()),
};
if let Some(accepted) = accepted {
state.cut = None;
let error = match std::mem::take(&mut state.timeout) {
true => io::ErrorKind::TimedOut,
false => io::ErrorKind::BrokenPipe,
};
if point == CutPoint::Flush {
state.flush_error = Some(error);
return (accepted, None);
}
return (accepted, Some(error));
}
}
if state.broken {
return (0, Some(io::ErrorKind::BrokenPipe));
}
(buf.len(), None)
}
}
impl Write for Outbox {
fn set_write_deadline(&mut self, _deadline: Instant) -> io::Result<()> {
let mut state = self.shared.lock().unwrap();
state.write_error = None;
state.flush_error = None;
Ok(())
}
}
impl io::Write for Outbox {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let mut state = self.shared.lock().unwrap();
if let Some(error) = state.write_error.take() {
return Err(error.into());
}
let (accepted, error) = Self::accept(&mut state, buf);
state.bytes.extend_from_slice(&buf[..accepted]);
trace(&self.recorder, || match error {
Some(io::ErrorKind::TimedOut) => Event::WriteTimedOut {
bytes: buf[..accepted].to_vec(),
},
_ => Event::Write {
bytes: buf[..accepted].to_vec(),
failed: error.is_some(),
},
});
match error {
Some(error) if accepted > 0 => {
state.write_error = Some(error);
Ok(accepted)
}
Some(error) => Err(error.into()),
None => Ok(accepted),
}
}
fn flush(&mut self) -> io::Result<()> {
let mut state = self.shared.lock().unwrap();
if let Some(error) = state.write_error.take() {
return Err(error.into());
}
match state.flush_error.take() {
Some(error) => {
trace(&self.recorder, || match error {
io::ErrorKind::TimedOut => Event::FlushTimedOut,
_ => Event::FlushFailed,
});
Err(error.into())
}
None => Ok(()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write as _;
use std::time::Duration;
#[test]
fn test_partial_write_reports_progress_before_failure() {
let mut outbox = Outbox::default();
outbox.set_cut(CutPoint::Delimiter);
outbox
.set_write_deadline(Instant::now() + Duration::from_secs(1))
.unwrap();
assert_eq!(outbox.write(&[1, 2, 0]).unwrap(), 2);
assert_eq!(
outbox.write(&[0]).unwrap_err().kind(),
io::ErrorKind::BrokenPipe
);
assert!(outbox.take_frames().is_empty());
assert!(outbox.has_tail());
outbox
.set_write_deadline(Instant::now() + Duration::from_secs(1))
.unwrap();
outbox.write_all(&[0]).unwrap();
outbox.flush().unwrap();
assert_eq!(outbox.take_frames(), vec![vec![1, 2]]);
}
#[test]
fn test_new_output_discards_abandoned_fault() {
for cut in [CutPoint::Delimiter, CutPoint::Flush] {
let mut outbox = Outbox::default();
let deadline = Instant::now() + Duration::from_secs(1);
outbox.set_cut(cut);
outbox.set_write_deadline(deadline).unwrap();
assert!(outbox.write(&[1, 2, 0]).unwrap() > 0);
outbox.set_write_deadline(deadline).unwrap();
outbox.write_all(&[0]).unwrap();
outbox.flush().unwrap();
assert!(!outbox.take_frames().is_empty());
assert!(!outbox.has_tail());
}
}
}