mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-04-05 07:06:08 +00:00
This PR allows the TURN allocation binding to be optionally configured by `TURN_LOWEST_PORT` and `TURN_HIGHEST_PORT` environment variables. This will allow client app developers to test their apps against a fully-working local development cluster in Docker Desktop for Linux/macOS/Windows, allowing us to remove the PortalMock, Connlib Mock, and SwiftMock codepaths entirely. cc @roop @pratikvelani
683 lines
22 KiB
Rust
683 lines
22 KiB
Rust
use bytecodec::{DecodeExt, EncodeExt};
|
|
use prometheus_client::registry::Registry;
|
|
use rand::rngs::mock::StepRng;
|
|
use relay::{
|
|
AddressFamily, Allocate, AllocationId, Attribute, Binding, ChannelBind, ChannelData,
|
|
ClientMessage, Command, IpStack, Refresh, Server,
|
|
};
|
|
use std::collections::HashMap;
|
|
use std::iter;
|
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4};
|
|
use std::time::{Duration, SystemTime};
|
|
use stun_codec::rfc5389::attributes::{ErrorCode, Nonce, Realm, Username, XorMappedAddress};
|
|
use stun_codec::rfc5389::errors::Unauthorized;
|
|
use stun_codec::rfc5389::methods::BINDING;
|
|
use stun_codec::rfc5766::attributes::{ChannelNumber, Lifetime, XorPeerAddress, XorRelayAddress};
|
|
use stun_codec::rfc5766::methods::{ALLOCATE, CHANNEL_BIND, REFRESH};
|
|
use stun_codec::{Message, MessageClass, MessageDecoder, MessageEncoder, TransactionId};
|
|
use test_strategy::proptest;
|
|
use uuid::Uuid;
|
|
use Output::{CreateAllocation, FreeAllocation, Wake};
|
|
|
|
#[proptest]
|
|
fn can_answer_stun_request_from_ip4_address(
|
|
#[strategy(relay::proptest::binding())] request: Binding,
|
|
source: SocketAddrV4,
|
|
public_relay_addr: Ipv4Addr,
|
|
) {
|
|
let _ = env_logger::try_init();
|
|
let mut server = TestServer::new(public_relay_addr);
|
|
|
|
let transaction_id = request.transaction_id();
|
|
|
|
server.assert_commands(
|
|
from_client(source, request, SystemTime::now()),
|
|
[send_message(
|
|
source,
|
|
binding_response(transaction_id, source),
|
|
)],
|
|
);
|
|
}
|
|
|
|
#[proptest]
|
|
fn deallocate_once_time_expired(
|
|
#[strategy(relay::proptest::transaction_id())] transaction_id: TransactionId,
|
|
#[strategy(relay::proptest::allocation_lifetime())] lifetime: Lifetime,
|
|
#[strategy(relay::proptest::username_salt())] username_salt: String,
|
|
source: SocketAddrV4,
|
|
public_relay_addr: Ipv4Addr,
|
|
#[strategy(relay::proptest::now())] now: SystemTime,
|
|
#[strategy(relay::proptest::nonce())] nonce: Uuid,
|
|
) {
|
|
let mut server = TestServer::new(public_relay_addr).with_nonce(nonce);
|
|
let secret = server.auth_secret();
|
|
|
|
server.assert_commands(
|
|
from_client(
|
|
source,
|
|
Allocate::new_authenticated_udp_implicit_ip4(
|
|
transaction_id,
|
|
Some(lifetime.clone()),
|
|
valid_username(now, &username_salt),
|
|
secret,
|
|
nonce,
|
|
),
|
|
now,
|
|
),
|
|
[
|
|
Wake(now + lifetime.lifetime()),
|
|
CreateAllocation(49152, AddressFamily::V4),
|
|
send_message(
|
|
source,
|
|
allocate_response(transaction_id, public_relay_addr, 49152, source, &lifetime),
|
|
),
|
|
],
|
|
);
|
|
|
|
server.assert_commands(
|
|
forward_time_to(now + lifetime.lifetime() + Duration::from_secs(1)),
|
|
[FreeAllocation(49152, AddressFamily::V4)],
|
|
);
|
|
}
|
|
|
|
#[proptest]
|
|
fn unauthenticated_allocate_triggers_authentication(
|
|
#[strategy(relay::proptest::transaction_id())] transaction_id: TransactionId,
|
|
#[strategy(relay::proptest::allocation_lifetime())] lifetime: Lifetime,
|
|
#[strategy(relay::proptest::username_salt())] username_salt: String,
|
|
source: SocketAddrV4,
|
|
public_relay_addr: Ipv4Addr,
|
|
#[strategy(relay::proptest::now())] now: SystemTime,
|
|
) {
|
|
// Nonces are generated randomly and we control the randomness in the test, thus this is deterministic.
|
|
let first_nonce = Uuid::from_u128(0x0);
|
|
|
|
let mut server = TestServer::new(public_relay_addr);
|
|
let secret = server.auth_secret().to_owned();
|
|
|
|
server.assert_commands(
|
|
from_client(
|
|
source,
|
|
Allocate::new_unauthenticated_udp(transaction_id, Some(lifetime.clone())),
|
|
now,
|
|
),
|
|
[send_message(
|
|
source,
|
|
unauthorized_allocate_response(transaction_id, first_nonce),
|
|
)],
|
|
);
|
|
|
|
server.assert_commands(
|
|
from_client(
|
|
source,
|
|
Allocate::new_authenticated_udp_implicit_ip4(
|
|
transaction_id,
|
|
Some(lifetime.clone()),
|
|
valid_username(now, &username_salt),
|
|
&secret,
|
|
first_nonce,
|
|
),
|
|
now,
|
|
),
|
|
[
|
|
Wake(now + lifetime.lifetime()),
|
|
CreateAllocation(49152, AddressFamily::V4),
|
|
send_message(
|
|
source,
|
|
allocate_response(transaction_id, public_relay_addr, 49152, source, &lifetime),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
#[proptest]
|
|
fn when_refreshed_in_time_allocation_does_not_expire(
|
|
#[strategy(relay::proptest::transaction_id())] allocate_transaction_id: TransactionId,
|
|
#[strategy(relay::proptest::transaction_id())] refresh_transaction_id: TransactionId,
|
|
#[strategy(relay::proptest::allocation_lifetime())] allocate_lifetime: Lifetime,
|
|
#[strategy(relay::proptest::allocation_lifetime())] refresh_lifetime: Lifetime,
|
|
#[strategy(relay::proptest::username_salt())] username_salt: String,
|
|
source: SocketAddrV4,
|
|
public_relay_addr: Ipv4Addr,
|
|
#[strategy(relay::proptest::now())] now: SystemTime,
|
|
#[strategy(relay::proptest::nonce())] nonce: Uuid,
|
|
) {
|
|
let mut server = TestServer::new(public_relay_addr).with_nonce(nonce);
|
|
let secret = server.auth_secret().to_owned();
|
|
let first_wake = now + allocate_lifetime.lifetime();
|
|
|
|
server.assert_commands(
|
|
from_client(
|
|
source,
|
|
Allocate::new_authenticated_udp_implicit_ip4(
|
|
allocate_transaction_id,
|
|
Some(allocate_lifetime.clone()),
|
|
valid_username(now, &username_salt),
|
|
&secret,
|
|
nonce,
|
|
),
|
|
now,
|
|
),
|
|
[
|
|
Wake(first_wake),
|
|
CreateAllocation(49152, AddressFamily::V4),
|
|
send_message(
|
|
source,
|
|
allocate_response(
|
|
allocate_transaction_id,
|
|
public_relay_addr,
|
|
49152,
|
|
source,
|
|
&allocate_lifetime,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
|
|
// Forward time
|
|
let now = now + allocate_lifetime.lifetime() / 2;
|
|
let second_wake = now + refresh_lifetime.lifetime();
|
|
|
|
server.assert_commands(
|
|
from_client(
|
|
source,
|
|
Refresh::new(
|
|
refresh_transaction_id,
|
|
Some(refresh_lifetime.clone()),
|
|
valid_username(now, &username_salt),
|
|
&secret,
|
|
nonce,
|
|
),
|
|
now,
|
|
),
|
|
[
|
|
Wake(second_wake),
|
|
send_message(
|
|
source,
|
|
refresh_response(refresh_transaction_id, refresh_lifetime.clone()),
|
|
),
|
|
],
|
|
);
|
|
|
|
// The allocation MUST NOT be expired 1 sec before its refresh lifetime.
|
|
// Note that depending on how the lifetimes were generated, this may still be before the initial allocation lifetime.
|
|
// This is okay because lifetimes do not roll over, i.e. a refresh is not "added" to the initial lifetime but the allocation's lifetime is simply computed from now + requested lifetime of the refresh request.
|
|
server.assert_commands(
|
|
forward_time_to(now + refresh_lifetime.lifetime() - Duration::from_secs(1)),
|
|
[],
|
|
);
|
|
}
|
|
#[proptest]
|
|
fn when_receiving_lifetime_0_for_existing_allocation_then_delete(
|
|
#[strategy(relay::proptest::transaction_id())] allocate_transaction_id: TransactionId,
|
|
#[strategy(relay::proptest::transaction_id())] refresh_transaction_id: TransactionId,
|
|
#[strategy(relay::proptest::allocation_lifetime())] allocate_lifetime: Lifetime,
|
|
#[strategy(relay::proptest::username_salt())] username_salt: String,
|
|
source: SocketAddrV4,
|
|
public_relay_addr: Ipv4Addr,
|
|
#[strategy(relay::proptest::now())] now: SystemTime,
|
|
#[strategy(relay::proptest::nonce())] nonce: Uuid,
|
|
) {
|
|
let mut server = TestServer::new(public_relay_addr).with_nonce(nonce);
|
|
let secret = server.auth_secret().to_owned();
|
|
let first_wake = now + allocate_lifetime.lifetime();
|
|
|
|
server.assert_commands(
|
|
from_client(
|
|
source,
|
|
Allocate::new_authenticated_udp_implicit_ip4(
|
|
allocate_transaction_id,
|
|
Some(allocate_lifetime.clone()),
|
|
valid_username(now, &username_salt),
|
|
&secret,
|
|
nonce,
|
|
),
|
|
now,
|
|
),
|
|
[
|
|
Wake(first_wake),
|
|
CreateAllocation(49152, AddressFamily::V4),
|
|
send_message(
|
|
source,
|
|
allocate_response(
|
|
allocate_transaction_id,
|
|
public_relay_addr,
|
|
49152,
|
|
source,
|
|
&allocate_lifetime,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
|
|
// Forward time
|
|
let now = now + allocate_lifetime.lifetime() / 2;
|
|
|
|
server.assert_commands(
|
|
from_client(
|
|
source,
|
|
Refresh::new(
|
|
refresh_transaction_id,
|
|
Some(Lifetime::new(Duration::ZERO).unwrap()),
|
|
valid_username(now, &username_salt),
|
|
&secret,
|
|
nonce,
|
|
),
|
|
now,
|
|
),
|
|
[
|
|
FreeAllocation(49152, AddressFamily::V4),
|
|
send_message(
|
|
source,
|
|
refresh_response(
|
|
refresh_transaction_id,
|
|
Lifetime::new(Duration::ZERO).unwrap(),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
|
|
// Assert that forwarding time does not produce an obsolete event.
|
|
server.assert_commands(forward_time_to(first_wake + Duration::from_secs(1)), []);
|
|
}
|
|
|
|
// #[test]
|
|
// fn server_waits_for_5_minutes_before_allowing_reuse_of_channel_number_after_expiry() {
|
|
// // todo!()
|
|
// }
|
|
|
|
#[proptest]
|
|
fn ping_pong_relay(
|
|
#[strategy(relay::proptest::transaction_id())] allocate_transaction_id: TransactionId,
|
|
#[strategy(relay::proptest::transaction_id())] channel_bind_transaction_id: TransactionId,
|
|
#[strategy(relay::proptest::allocation_lifetime())] lifetime: Lifetime,
|
|
#[strategy(relay::proptest::username_salt())] username_salt: String,
|
|
#[strategy(relay::proptest::channel_number())] channel: ChannelNumber,
|
|
source: SocketAddrV4,
|
|
peer: SocketAddrV4,
|
|
public_relay_addr: Ipv4Addr,
|
|
#[strategy(relay::proptest::now())] now: SystemTime,
|
|
peer_to_client_ping: [u8; 32],
|
|
client_to_peer_ping: [u8; 32],
|
|
#[strategy(relay::proptest::nonce())] nonce: Uuid,
|
|
) {
|
|
let _ = env_logger::try_init();
|
|
|
|
let mut server = TestServer::new(public_relay_addr).with_nonce(nonce);
|
|
let secret = server.auth_secret().to_owned();
|
|
|
|
server.assert_commands(
|
|
from_client(
|
|
source,
|
|
Allocate::new_authenticated_udp_implicit_ip4(
|
|
allocate_transaction_id,
|
|
Some(lifetime.clone()),
|
|
valid_username(now, &username_salt),
|
|
&secret,
|
|
nonce,
|
|
),
|
|
now,
|
|
),
|
|
[
|
|
Wake(now + lifetime.lifetime()),
|
|
CreateAllocation(49152, AddressFamily::V4),
|
|
send_message(
|
|
source,
|
|
allocate_response(
|
|
allocate_transaction_id,
|
|
public_relay_addr,
|
|
49152,
|
|
source,
|
|
&lifetime,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
|
|
let now = now + Duration::from_secs(1);
|
|
|
|
server.assert_commands(
|
|
from_client(
|
|
source,
|
|
ChannelBind::new(
|
|
channel_bind_transaction_id,
|
|
channel,
|
|
XorPeerAddress::new(peer.into()),
|
|
valid_username(now, &username_salt),
|
|
&secret,
|
|
nonce,
|
|
),
|
|
now,
|
|
),
|
|
[send_message(
|
|
source,
|
|
channel_bind_response(channel_bind_transaction_id),
|
|
)],
|
|
);
|
|
|
|
let now = now + Duration::from_secs(1);
|
|
|
|
server.assert_commands(
|
|
from_client(
|
|
source,
|
|
ChannelData::new(channel.value(), client_to_peer_ping.as_ref()),
|
|
now,
|
|
),
|
|
[forward(peer, &client_to_peer_ping, 49152)],
|
|
);
|
|
|
|
server.assert_commands(
|
|
from_peer(peer, peer_to_client_ping.as_ref(), 49152),
|
|
[send_channel_data(
|
|
source,
|
|
ChannelData::new(channel.value(), peer_to_client_ping.as_ref()),
|
|
)],
|
|
);
|
|
}
|
|
|
|
#[proptest]
|
|
fn can_make_ipv6_allocation(
|
|
#[strategy(relay::proptest::transaction_id())] transaction_id: TransactionId,
|
|
#[strategy(relay::proptest::allocation_lifetime())] lifetime: Lifetime,
|
|
#[strategy(relay::proptest::username_salt())] username_salt: String,
|
|
source: SocketAddrV4,
|
|
public_relay_ip4_addr: Ipv4Addr,
|
|
public_relay_ip6_addr: Ipv6Addr,
|
|
#[strategy(relay::proptest::now())] now: SystemTime,
|
|
#[strategy(relay::proptest::nonce())] nonce: Uuid,
|
|
) {
|
|
let mut server =
|
|
TestServer::new((public_relay_ip4_addr, public_relay_ip6_addr)).with_nonce(nonce);
|
|
let secret = server.auth_secret();
|
|
|
|
server.assert_commands(
|
|
from_client(
|
|
source,
|
|
Allocate::new_authenticated_udp_ip6(
|
|
transaction_id,
|
|
Some(lifetime.clone()),
|
|
valid_username(now, &username_salt),
|
|
secret,
|
|
nonce,
|
|
),
|
|
now,
|
|
),
|
|
[
|
|
Wake(now + lifetime.lifetime()),
|
|
CreateAllocation(49152, AddressFamily::V6),
|
|
send_message(
|
|
source,
|
|
allocate_response(
|
|
transaction_id,
|
|
public_relay_ip6_addr,
|
|
49152,
|
|
source,
|
|
&lifetime,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
struct TestServer {
|
|
server: Server<StepRng>,
|
|
id_to_port: HashMap<u16, AllocationId>,
|
|
}
|
|
|
|
impl TestServer {
|
|
fn new(relay_public_addr: impl Into<IpStack>) -> Self {
|
|
Self {
|
|
server: Server::new(
|
|
relay_public_addr,
|
|
StepRng::new(0, 0),
|
|
&mut Registry::default(),
|
|
49152,
|
|
65535,
|
|
),
|
|
id_to_port: Default::default(),
|
|
}
|
|
}
|
|
|
|
fn with_nonce(mut self, nonce: Uuid) -> Self {
|
|
self.server.add_nonce(nonce);
|
|
|
|
self
|
|
}
|
|
|
|
fn auth_secret(&self) -> &str {
|
|
self.server.auth_secret()
|
|
}
|
|
|
|
fn assert_commands<const N: usize>(&mut self, input: Input, output: [Output; N]) {
|
|
match input {
|
|
Input::Client(sender, message, now) => {
|
|
self.server.handle_client_message(message, sender, now);
|
|
}
|
|
Input::Time(now) => {
|
|
self.server.handle_deadline_reached(now);
|
|
}
|
|
Input::Peer(peer, data, port) => {
|
|
self.server
|
|
.handle_relay_input(&data, peer, self.id_to_port[&port]);
|
|
}
|
|
}
|
|
|
|
for expected_output in output {
|
|
let Some(actual_output) = self.server.next_command() else {
|
|
let msg = match expected_output {
|
|
Output::SendMessage((recipient, msg)) => format!("to send message {:?} to {recipient}", msg),
|
|
Wake(time) => format!("to be woken at {time:?}"),
|
|
CreateAllocation(port, family) => format!("to create allocation on port {port} for address family {family}"),
|
|
FreeAllocation(port, family) => format!("to free allocation on port {port} for address family {family}"),
|
|
Output::SendChannelData((peer, _)) => format!("to send channel data from {peer} to client"),
|
|
Output::Forward((peer, _, _)) => format!("to forward data to peer {peer}")
|
|
};
|
|
|
|
panic!("No commands produced but expected {msg}");
|
|
};
|
|
|
|
match (expected_output, actual_output) {
|
|
(
|
|
Output::SendMessage((to, message)),
|
|
Command::SendMessage { payload, recipient },
|
|
) => {
|
|
let expected_bytes = MessageEncoder::new()
|
|
.encode_into_bytes(message.clone())
|
|
.unwrap();
|
|
|
|
if expected_bytes != payload {
|
|
let expected_message = format!("{:?}", message);
|
|
let actual_message = format!("{:?}", parse_message(&payload));
|
|
|
|
difference::assert_diff!(&expected_message, &actual_message, "\n", 0);
|
|
}
|
|
|
|
assert_eq!(recipient, to);
|
|
}
|
|
(Wake(when), Command::Wake { deadline }) => {
|
|
assert_eq!(when, deadline);
|
|
}
|
|
(
|
|
CreateAllocation(expected_port, expected_family),
|
|
Command::CreateAllocation {
|
|
id,
|
|
family: actual_family,
|
|
port: actual_port,
|
|
},
|
|
) => {
|
|
self.id_to_port.insert(actual_port, id);
|
|
assert_eq!(expected_port, actual_port);
|
|
assert_eq!(expected_family, actual_family);
|
|
}
|
|
(
|
|
FreeAllocation(port, family),
|
|
Command::FreeAllocation {
|
|
id,
|
|
family: actual_family,
|
|
},
|
|
) => {
|
|
let actual_id = self.id_to_port.remove(&port).expect("to have port in map");
|
|
assert_eq!(id, actual_id);
|
|
assert_eq!(family, actual_family);
|
|
}
|
|
(Wake(when), Command::SendMessage { payload, .. }) => {
|
|
panic!(
|
|
"Expected `Wake({})`, got `SendMessage({:?})`",
|
|
when.duration_since(SystemTime::UNIX_EPOCH)
|
|
.unwrap()
|
|
.as_secs(),
|
|
parse_message(&payload)
|
|
)
|
|
}
|
|
(
|
|
Output::SendChannelData((peer, channeldata)),
|
|
Command::SendMessage { recipient, payload },
|
|
) => {
|
|
let expected_channel_data = hex::encode(channeldata.to_bytes());
|
|
let actual_message = hex::encode(payload);
|
|
|
|
assert_eq!(expected_channel_data, actual_message);
|
|
assert_eq!(recipient, peer);
|
|
}
|
|
(
|
|
Output::Forward((peer, expected_data, port)),
|
|
Command::ForwardData {
|
|
id,
|
|
data: actual_data,
|
|
receiver,
|
|
},
|
|
) => {
|
|
assert_eq!(hex::encode(expected_data), hex::encode(actual_data));
|
|
assert_eq!(receiver, peer);
|
|
assert_eq!(self.id_to_port[&port], id);
|
|
}
|
|
(expected, actual) => panic!("Unhandled combination: {expected:?} {actual:?}"),
|
|
}
|
|
}
|
|
|
|
let remaining_commands = iter::from_fn(|| self.server.next_command()).collect::<Vec<_>>();
|
|
|
|
assert_eq!(remaining_commands, vec![])
|
|
}
|
|
}
|
|
|
|
fn valid_username(now: SystemTime, salt: &str) -> Username {
|
|
let now_unix = now
|
|
.duration_since(SystemTime::UNIX_EPOCH)
|
|
.unwrap()
|
|
.as_secs();
|
|
|
|
let expiry = now_unix + 1000;
|
|
|
|
Username::new(format!("{expiry}:{salt}")).unwrap()
|
|
}
|
|
|
|
fn binding_response(
|
|
transaction_id: TransactionId,
|
|
address: impl Into<SocketAddr>,
|
|
) -> Message<Attribute> {
|
|
let mut message =
|
|
Message::<Attribute>::new(MessageClass::SuccessResponse, BINDING, transaction_id);
|
|
message.add_attribute(XorMappedAddress::new(address.into()).into());
|
|
|
|
message
|
|
}
|
|
|
|
fn allocate_response(
|
|
transaction_id: TransactionId,
|
|
public_relay_addr: impl Into<IpAddr>,
|
|
port: u16,
|
|
source: SocketAddrV4,
|
|
lifetime: &Lifetime,
|
|
) -> Message<Attribute> {
|
|
let mut message =
|
|
Message::<Attribute>::new(MessageClass::SuccessResponse, ALLOCATE, transaction_id);
|
|
message.add_attribute(
|
|
XorRelayAddress::new(SocketAddr::new(public_relay_addr.into(), port)).into(),
|
|
);
|
|
message.add_attribute(XorMappedAddress::new(source.into()).into());
|
|
message.add_attribute(lifetime.clone().into());
|
|
|
|
message
|
|
}
|
|
|
|
fn unauthorized_allocate_response(
|
|
transaction_id: TransactionId,
|
|
nonce: Uuid,
|
|
) -> Message<Attribute> {
|
|
let mut message =
|
|
Message::<Attribute>::new(MessageClass::ErrorResponse, ALLOCATE, transaction_id);
|
|
message.add_attribute(ErrorCode::from(Unauthorized).into());
|
|
message.add_attribute(
|
|
Nonce::new(nonce.as_hyphenated().to_string())
|
|
.unwrap()
|
|
.into(),
|
|
);
|
|
message.add_attribute(Realm::new("firezone".to_owned()).unwrap().into());
|
|
|
|
message
|
|
}
|
|
|
|
fn refresh_response(transaction_id: TransactionId, lifetime: Lifetime) -> Message<Attribute> {
|
|
let mut message =
|
|
Message::<Attribute>::new(MessageClass::SuccessResponse, REFRESH, transaction_id);
|
|
message.add_attribute(lifetime.into());
|
|
|
|
message
|
|
}
|
|
|
|
fn channel_bind_response(transaction_id: TransactionId) -> Message<Attribute> {
|
|
Message::<Attribute>::new(MessageClass::SuccessResponse, CHANNEL_BIND, transaction_id)
|
|
}
|
|
|
|
fn parse_message(message: &[u8]) -> Message<Attribute> {
|
|
MessageDecoder::new()
|
|
.decode_from_bytes(message)
|
|
.unwrap()
|
|
.unwrap()
|
|
}
|
|
|
|
enum Input<'a> {
|
|
Client(SocketAddr, ClientMessage<'a>, SystemTime),
|
|
Peer(SocketAddr, Vec<u8>, u16),
|
|
Time(SystemTime),
|
|
}
|
|
|
|
fn from_client<'a>(
|
|
from: impl Into<SocketAddr>,
|
|
message: impl Into<ClientMessage<'a>>,
|
|
now: SystemTime,
|
|
) -> Input<'a> {
|
|
Input::Client(from.into(), message.into(), now)
|
|
}
|
|
|
|
fn from_peer<'a>(from: impl Into<SocketAddr>, data: &[u8], port: u16) -> Input<'a> {
|
|
Input::Peer(from.into(), data.to_vec(), port)
|
|
}
|
|
|
|
fn forward_time_to<'a>(when: SystemTime) -> Input<'a> {
|
|
Input::Time(when)
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
enum Output<'a> {
|
|
SendMessage((SocketAddr, Message<Attribute>)),
|
|
SendChannelData((SocketAddr, ChannelData<'a>)),
|
|
Forward((SocketAddr, Vec<u8>, u16)),
|
|
Wake(SystemTime),
|
|
CreateAllocation(u16, AddressFamily),
|
|
FreeAllocation(u16, AddressFamily),
|
|
}
|
|
|
|
fn send_message<'a>(source: impl Into<SocketAddr>, message: Message<Attribute>) -> Output<'a> {
|
|
Output::SendMessage((source.into(), message))
|
|
}
|
|
|
|
fn send_channel_data(source: impl Into<SocketAddr>, message: ChannelData) -> Output {
|
|
Output::SendChannelData((source.into(), message))
|
|
}
|
|
|
|
fn forward(source: impl Into<SocketAddr>, data: &[u8], port: u16) -> Output {
|
|
Output::Forward((source.into(), data.to_vec(), port))
|
|
}
|