use super::*;
use crate::peer::identity::{MachineIdentity, PairingCode};
use std::net::{TcpListener, TcpStream};
fn machine(name: &str) -> (tempfile::TempDir, MachineIdentity) {
let dir = tempfile::tempdir().expect("tempdir");
let identity = MachineIdentity::load_or_create(dir.path(), name).expect("an identity");
(dir, identity)
}
fn connect(
initiator: &MachineIdentity,
responder: &MachineIdentity,
initiator_expects: Expect<'_>,
responder_expects: Expect<'_>,
) -> (Result<Session, String>, Result<Session, String>) {
let listener = TcpListener::bind("127.0.0.1:0").expect("bind");
let address = listener.local_addr().expect("address");
std::thread::scope(|scope| {
let server = scope.spawn(|| {
let (stream, _) = listener.accept().expect("accept");
handshake(stream, Role::Responder, responder, responder_expects)
});
let client = {
let stream = TcpStream::connect(address).expect("connect");
handshake(stream, Role::Initiator, initiator, initiator_expects)
};
let server = server.join().expect("the responder thread");
(client, server)
})
}
#[test]
fn test9939_pairing_admits_only_the_holder_of_the_code() {
let (_a_dir, desktop) = machine("desktop");
let (_b_dir, laptop) = machine("laptop");
let code = PairingCode::mint();
let (client, server) = connect(
&desktop,
&laptop,
Expect::Pairing(&code),
Expect::Pairing(&code),
);
let client = client.expect("the initiator paired");
let server = server.expect("the responder paired");
assert_eq!(client.peer(), laptop.id());
assert_eq!(server.peer(), desktop.id());
assert_eq!(client.peer_name(), "laptop");
let wrong = PairingCode::parse("AMBER-11-ZEPHYR").expect("a code");
let (client, server) = connect(
&desktop,
&laptop,
Expect::Pairing(&code),
Expect::Pairing(&wrong),
);
assert!(client.is_err(), "a wrong code must not pair");
let error = server.expect_err("a wrong code must not pair");
assert!(error.contains("pairing code"), "{error}");
}
#[test]
fn test9940_a_changed_identity_is_refused() {
let (_a_dir, desktop) = machine("desktop");
let (_b_dir, laptop) = machine("laptop");
let (_c_dir, impostor) = machine("laptop");
let laptop_key = laptop.public_key();
let (client, _server) = connect(
&desktop,
&laptop,
Expect::Pinned(&laptop_key),
Expect::Pinned(&desktop.public_key()),
);
assert!(client.is_ok(), "the paired peer must connect");
let (client, _server) = connect(
&desktop,
&impostor,
Expect::Pinned(&laptop_key),
Expect::Pinned(&desktop.public_key()),
);
let error = client.expect_err("an unpinned identity must be refused");
assert!(error.contains("identity changed"), "{error}");
assert!(
error.contains("pair again"),
"the refusal must say what a person would do about it: {error}"
);
}
#[test]
fn test9941_messages_round_trip_over_the_encrypted_channel() {
let (_a_dir, desktop) = machine("desktop");
let (_b_dir, laptop) = machine("laptop");
let (client, server) = connect(
&desktop,
&laptop,
Expect::Pinned(&laptop.public_key()),
Expect::Pinned(&desktop.public_key()),
);
let mut client = client.expect("paired");
let mut server = server.expect("paired");
std::thread::scope(|scope| {
scope.spawn(|| {
let request: super::super::protocol::Request =
server.receive().expect("the request arrives");
assert_eq!(request, super::super::protocol::Request::Inventory);
server
.send(&super::super::protocol::Response::Acknowledged)
.expect("the response is sent");
});
client
.send(&super::super::protocol::Request::Inventory)
.expect("the request is sent");
let response: super::super::protocol::Response =
client.receive().expect("the response arrives");
assert_eq!(response, super::super::protocol::Response::Acknowledged);
});
}
#[test]
fn test9942_directions_have_different_keys() {
let (_a_dir, desktop) = machine("desktop");
let (_b_dir, laptop) = machine("laptop");
let listener = TcpListener::bind("127.0.0.1:0").expect("bind");
let address = listener.local_addr().expect("address");
let laptop_key = laptop.public_key();
let desktop_key = desktop.public_key();
std::thread::scope(|scope| {
let server = scope.spawn(|| {
let (stream, _) = listener.accept().expect("accept");
handshake(stream, Role::Responder, &laptop, Expect::Pinned(&desktop_key))
.expect("paired")
});
let mut client = handshake(
TcpStream::connect(address).expect("connect"),
Role::Initiator,
&desktop,
Expect::Pinned(&laptop_key),
)
.expect("paired");
let mut server = server.join().expect("thread");
client
.send(&super::super::protocol::Request::Inventory)
.expect("sent");
let _: super::super::protocol::Request = server.receive().expect("received");
server
.send(&super::super::protocol::Response::Acknowledged)
.expect("sent");
let response: super::super::protocol::Response = client.receive().expect("received");
assert_eq!(response, super::super::protocol::Response::Acknowledged);
});
}
#[test]
fn test9943_an_oversized_frame_is_refused_before_allocation() {
let (_a_dir, desktop) = machine("desktop");
let (_b_dir, laptop) = machine("laptop");
let listener = TcpListener::bind("127.0.0.1:0").expect("bind");
let address = listener.local_addr().expect("address");
let desktop_key = desktop.public_key();
let laptop_key = laptop.public_key();
std::thread::scope(|scope| {
let server = scope.spawn(|| {
let (stream, _) = listener.accept().expect("accept");
let mut session =
handshake(stream, Role::Responder, &laptop, Expect::Pinned(&desktop_key))
.expect("paired");
session.receive::<super::super::protocol::Request>()
});
let mut stream = TcpStream::connect(address).expect("connect");
let session = handshake(
stream.try_clone().expect("clone"),
Role::Initiator,
&desktop,
Expect::Pinned(&laptop_key),
)
.expect("paired");
use std::io::Write;
stream
.write_all(&u32::MAX.to_be_bytes())
.expect("the length prefix is sent");
stream.flush().expect("flushed");
let outcome = server.join().expect("thread");
let error = outcome.expect_err("an oversized frame must be refused");
assert!(error.contains("limit"), "{error}");
drop(session);
});
}