use async_trait::async_trait;
use simple_doip::{
Error, LogicalAddress,
messages::{
Decode, DiagnosticMessage, Message, OwnedMessage, Payload, RoutingActivationRequest,
RoutingActivationResponseCode,
},
server::{ResponseWriter, Server, ServerConnectionHandler},
};
use std::time::Duration;
use tokio::net::UdpSocket;
const TEST_TIMEOUT: Duration = Duration::from_secs(5);
const SERVER_LOGICAL_ADDRESS: LogicalAddress = LogicalAddress(0x0001);
const TEST_VIN: [u8; 17] = *b"MVIS0000000000001";
const TEST_ENTITY_ID: [u8; 6] = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF];
struct IdentityHandler;
#[async_trait]
impl ServerConnectionHandler for IdentityHandler {
fn get_vin(&self) -> [u8; 17] {
TEST_VIN
}
fn get_logical_address(&self) -> LogicalAddress {
SERVER_LOGICAL_ADDRESS
}
fn get_entity_id(&self) -> [u8; 6] {
TEST_ENTITY_ID
}
fn get_group_id(&self) -> Option<[u8; 6]> {
None
}
async fn routing_activation(
&self,
request: &RoutingActivationRequest,
) -> Result<OwnedMessage, Error> {
Ok(OwnedMessage::routing_activation_response(
self.protocol_version(),
request.source_address,
self.get_logical_address(),
RoutingActivationResponseCode::RoutingSuccessfullyActivated,
[0; 4],
None,
))
}
async fn diagnostic_message(
&self,
_message: &DiagnosticMessage<'_>,
_responses: &mut dyn ResponseWriter,
) -> Result<(), Error> {
Ok(())
}
}
const VEHICLE_IDENTIFICATION_REQUEST: [u8; 8] = [0x02, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00];
const TRUNCATED_DATAGRAM: [u8; 3] = [0x02, 0xFD, 0x00];
const ALIVE_CHECK_REQUEST: [u8; 8] = [0x02, 0xFD, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00];
const IDENTIFICATION_REQUEST_WITH_EID: [u8; 14] = [
0x02, 0xFD, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
];
const IDENTIFICATION_REQUEST_WITH_VIN: [u8; 25] = [
0x02, 0xFD, 0x00, 0x03, 0x00, 0x00, 0x00, 0x11, b'O', b'T', b'H', b'E', b'R', b'0', b'0', b'0',
b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'1',
];
const SILENCE_WINDOW: Duration = Duration::from_millis(250);
async fn start_udp_responder() -> std::net::SocketAddr {
let server_socket = UdpSocket::bind("127.0.0.1:0").await.expect("bind server");
let server_addr = server_socket.local_addr().expect("server addr");
let server = Server::new(IdentityHandler).expect("construct server");
tokio::spawn(async move {
let _ = server.run_udp_responder(server_socket).await;
});
server_addr
}
async fn expect_identification_response(client: &UdpSocket) {
let mut buf = [0u8; 256];
let (len, _from) = tokio::time::timeout(TEST_TIMEOUT, client.recv_from(&mut buf))
.await
.expect("timed out waiting for identification response")
.expect("recv identification response");
let (message, _rest) = Message::decode(&buf[..len]).expect("decode identification response");
match message.payload {
Payload::VehicleAnnouncement(response) => {
assert_eq!(response.vin, TEST_VIN);
assert_eq!(response.entity_id, TEST_ENTITY_ID);
assert_eq!(response.logical_address, SERVER_LOGICAL_ADDRESS);
}
other => panic!("expected a vehicle identification response, got {other:?}"),
}
}
#[tokio::test]
async fn udp_responder_answers_a_vehicle_identification_request() {
let server_addr = start_udp_responder().await;
let client = UdpSocket::bind("127.0.0.1:0").await.expect("bind client");
client
.send_to(&VEHICLE_IDENTIFICATION_REQUEST, server_addr)
.await
.expect("send identification request");
expect_identification_response(&client).await;
}
#[tokio::test]
async fn udp_responder_keeps_serving_after_datagrams_it_cannot_answer() {
let server_addr = start_udp_responder().await;
let client = UdpSocket::bind("127.0.0.1:0").await.expect("bind client");
client
.send_to(&TRUNCATED_DATAGRAM, server_addr)
.await
.expect("send truncated datagram");
client
.send_to(&ALIVE_CHECK_REQUEST, server_addr)
.await
.expect("send alive check request");
client
.send_to(&VEHICLE_IDENTIFICATION_REQUEST, server_addr)
.await
.expect("send identification request");
expect_identification_response(&client).await;
}
#[tokio::test]
async fn udp_responder_stays_silent_for_directed_eid_and_vin_requests() {
let server_addr = start_udp_responder().await;
let client = UdpSocket::bind("127.0.0.1:0").await.expect("bind client");
client
.send_to(&IDENTIFICATION_REQUEST_WITH_EID, server_addr)
.await
.expect("send identification request with EID");
client
.send_to(&IDENTIFICATION_REQUEST_WITH_VIN, server_addr)
.await
.expect("send identification request with VIN");
let mut buf = [0u8; 256];
let unexpected = tokio::time::timeout(SILENCE_WINDOW, client.recv_from(&mut buf)).await;
assert!(
unexpected.is_err(),
"responder answered a directed request it cannot match: {unexpected:?}"
);
client
.send_to(&VEHICLE_IDENTIFICATION_REQUEST, server_addr)
.await
.expect("send identification request");
expect_identification_response(&client).await;
}