use proptest::prelude::*;
use simple_doip::{
LogicalAddress,
messages::{
ActivationTypeCode, AliveCheckResponse, Decode, DiagnosticAckCode, DiagnosticMessage,
DiagnosticMessageAck, DiagnosticPowerModeCode, Encode, EntityStatusNodeType,
EntityStatusResponse, FurtherActionRequired, Header, Message, MessageError, NackCode,
Payload, PayloadType, ProtocolVersion, RoutingActivationRequest, RoutingActivationResponse,
RoutingActivationResponseCode, VehicleIdentificationResponse, VinGidSyncStatus,
},
};
fn encode_to<'buf>(value: &impl Encode<Error = MessageError>, buf: &'buf mut [u8]) -> &'buf [u8] {
let written = {
let mut writer: &mut [u8] = buf;
value.encode(&mut writer).expect("encode failed")
};
assert_eq!(
value.encoded_size().expect("encoded_size failed"),
written,
"encoded_size() disagrees with encode()"
);
&buf[..written]
}
macro_rules! byte_code_roundtrip {
($name:ident, $ty:ty) => {
proptest! {
#[test]
fn $name(byte in any::<u8>()) {
let decoded = <$ty>::from(byte);
let back: u8 = decoded.into();
prop_assert_eq!(byte, back);
}
}
};
}
byte_code_roundtrip!(prop_protocol_version_roundtrip, ProtocolVersion);
byte_code_roundtrip!(prop_activation_type_roundtrip, ActivationTypeCode);
byte_code_roundtrip!(prop_diagnostic_ack_code_roundtrip, DiagnosticAckCode);
byte_code_roundtrip!(prop_entity_node_type_roundtrip, EntityStatusNodeType);
byte_code_roundtrip!(prop_further_action_roundtrip, FurtherActionRequired);
byte_code_roundtrip!(prop_nack_code_roundtrip, NackCode);
byte_code_roundtrip!(prop_power_mode_code_roundtrip, DiagnosticPowerModeCode);
byte_code_roundtrip!(
prop_routing_response_code_roundtrip,
RoutingActivationResponseCode
);
byte_code_roundtrip!(prop_vin_gid_sync_roundtrip, VinGidSyncStatus);
proptest! {
#[test]
fn prop_payload_type_roundtrip(value in any::<u16>()) {
let decoded = PayloadType::from(value);
let back: u16 = decoded.into();
prop_assert_eq!(value, back);
}
#[test]
fn prop_logical_address_client_range(addr in 0x0E00u16..=0x0FFF) {
prop_assert!(LogicalAddress(addr).is_valid_client_address());
}
#[test]
fn prop_logical_address_outside_client_range(
addr in prop_oneof![0x0000u16..0x0E00, 0x1000u16..=0xFFFF],
) {
prop_assert!(!LogicalAddress(addr).is_valid_client_address());
}
#[test]
fn prop_header_roundtrip(
version in any::<u8>().prop_map(ProtocolVersion::from),
payload_type in any::<u16>().prop_map(PayloadType::from),
payload_length in any::<u32>(),
) {
let header = Header::new(version, payload_type, payload_length);
let mut buf = [0u8; Header::SIZE];
let bytes = encode_to(&header, &mut buf);
prop_assert_eq!(bytes.len(), Header::SIZE);
let parsed = Header::decode_exact(bytes).expect("header decode failed");
prop_assert_eq!(header, parsed);
}
#[test]
fn prop_alive_check_response_roundtrip(source in any::<u16>()) {
let value = AliveCheckResponse { source_address: LogicalAddress(source) };
let mut buf = [0u8; 16];
let bytes = encode_to(&value, &mut buf);
let parsed = AliveCheckResponse::decode_exact(bytes).expect("decode failed");
prop_assert_eq!(value, parsed);
}
#[test]
fn prop_diagnostic_message_roundtrip(
source in any::<u16>(),
target in any::<u16>(),
user_data in prop::collection::vec(any::<u8>(), 0..64),
) {
let value = DiagnosticMessage {
source_address: LogicalAddress(source),
target_address: LogicalAddress(target),
user_data: &user_data,
};
let mut buf = [0u8; 128];
let bytes = encode_to(&value, &mut buf);
let parsed = DiagnosticMessage::decode_exact(bytes).expect("decode failed");
prop_assert_eq!(value, parsed);
}
#[test]
fn prop_diagnostic_message_ack_roundtrip(
source in any::<u16>(),
target in any::<u16>(),
ack_code in any::<u8>().prop_map(DiagnosticAckCode::from),
previous in prop::collection::vec(any::<u8>(), 0..64),
) {
let value = DiagnosticMessageAck {
source_address: LogicalAddress(source),
target_address: LogicalAddress(target),
ack_code,
previous_message_data: &previous,
};
let mut buf = [0u8; 128];
let bytes = encode_to(&value, &mut buf);
let parsed = DiagnosticMessageAck::decode_exact(bytes).expect("decode failed");
prop_assert_eq!(value, parsed);
}
#[test]
fn prop_entity_status_response_roundtrip(
node_type in any::<u8>().prop_map(EntityStatusNodeType::from),
max_concurrent_tcp_sockets in any::<u8>(),
open_tcp_sockets in any::<u8>(),
max_data_size in any::<u32>(),
) {
let value = EntityStatusResponse {
node_type,
max_concurrent_tcp_sockets,
open_tcp_sockets,
max_data_size,
};
let mut buf = [0u8; 32];
let bytes = encode_to(&value, &mut buf);
let parsed = EntityStatusResponse::decode_exact(bytes).expect("decode failed");
prop_assert_eq!(value, parsed);
}
#[test]
fn prop_routing_activation_request_roundtrip(
source in any::<u16>(),
activation_type in any::<u8>().prop_map(ActivationTypeCode::from),
reserved in any::<[u8; 4]>(),
vehicle_manufacturer in proptest::option::of(any::<[u8; 4]>()),
) {
let value = RoutingActivationRequest {
source_address: LogicalAddress(source),
activation_type,
reserved,
reserved_vehicle_manufacturer: vehicle_manufacturer,
};
let mut buf = [0u8; 32];
let bytes = encode_to(&value, &mut buf);
let parsed = RoutingActivationRequest::decode_exact(bytes).expect("decode failed");
prop_assert_eq!(value, parsed);
}
#[test]
fn prop_routing_activation_response_roundtrip(
tester in any::<u16>(),
entity in any::<u16>(),
code in any::<u8>().prop_map(RoutingActivationResponseCode::from),
reserved_oem in any::<[u8; 4]>(),
oem_specific in proptest::option::of(any::<[u8; 4]>()),
) {
let value = RoutingActivationResponse {
logical_address_tester: LogicalAddress(tester),
logical_address_of_doip_entity: LogicalAddress(entity),
routing_activation_response_code: code,
reserved_oem,
oem_specific,
};
let mut buf = [0u8; 32];
let bytes = encode_to(&value, &mut buf);
let parsed = RoutingActivationResponse::decode_exact(bytes).expect("decode failed");
prop_assert_eq!(value, parsed);
}
#[test]
fn prop_vehicle_identification_response_roundtrip(
vin in any::<[u8; 17]>(),
logical_address in any::<u16>(),
entity_id in any::<[u8; 6]>(),
group_id in proptest::option::of(any::<[u8; 6]>()),
further_action in any::<u8>().prop_map(FurtherActionRequired::from),
vin_gid_sync_status in any::<u8>().prop_map(VinGidSyncStatus::from),
) {
let value = VehicleIdentificationResponse {
vin,
logical_address: LogicalAddress(logical_address),
entity_id,
group_id,
further_action,
vin_gid_sync_status,
};
let mut buf = [0u8; 64];
let bytes = encode_to(&value, &mut buf);
let parsed = VehicleIdentificationResponse::decode_exact(bytes).expect("decode failed");
prop_assert_eq!(value, parsed);
}
#[test]
fn prop_full_nack_frame_roundtrip(code in any::<u8>().prop_map(NackCode::from)) {
let payload = Payload::DoIPNack(code);
let mut buf = [0u8; 32];
let bytes = frame(ProtocolVersion::V2012, PayloadType::NegativeAcknowledge, &payload, &mut buf);
let (parsed, rest) = Message::decode(bytes).expect("message decode failed");
prop_assert!(rest.is_empty());
prop_assert_eq!(parsed.header.payload_type, PayloadType::NegativeAcknowledge);
prop_assert_eq!(parsed.payload, payload);
}
#[test]
fn prop_full_diagnostic_frame_roundtrip(
source in any::<u16>(),
target in any::<u16>(),
user_data in prop::collection::vec(any::<u8>(), 0..64),
) {
let payload = Payload::DiagnosticMessage(DiagnosticMessage {
source_address: LogicalAddress(source),
target_address: LogicalAddress(target),
user_data: &user_data,
});
let mut buf = [0u8; 128];
let bytes = frame(ProtocolVersion::V2012, PayloadType::DiagnosticMessage, &payload, &mut buf);
let (parsed, rest) = Message::decode(bytes).expect("message decode failed");
prop_assert!(rest.is_empty());
prop_assert_eq!(parsed.payload, payload);
}
#[test]
fn prop_full_alive_check_frame_roundtrip(source in any::<u16>()) {
let payload = Payload::AliveCheckResponse(AliveCheckResponse {
source_address: LogicalAddress(source),
});
let mut buf = [0u8; 32];
let bytes = frame(ProtocolVersion::V2012, PayloadType::AliveCheckResponse, &payload, &mut buf);
let (parsed, rest) = Message::decode(bytes).expect("message decode failed");
prop_assert!(rest.is_empty());
prop_assert_eq!(parsed.payload, payload);
}
#[test]
fn prop_full_routing_activation_request_frame_roundtrip(
source in any::<u16>(),
activation_type in any::<u8>().prop_map(ActivationTypeCode::from),
reserved in any::<[u8; 4]>(),
vehicle_manufacturer in proptest::option::of(any::<[u8; 4]>()),
) {
let payload = Payload::RoutingActivationRequest(RoutingActivationRequest {
source_address: LogicalAddress(source),
activation_type,
reserved,
reserved_vehicle_manufacturer: vehicle_manufacturer,
});
let mut buf = [0u8; 64];
let bytes = frame(
ProtocolVersion::V2012,
PayloadType::RoutingActivationRequest,
&payload,
&mut buf,
);
let (parsed, rest) = Message::decode(bytes).expect("message decode failed");
prop_assert!(rest.is_empty());
prop_assert_eq!(parsed.payload, payload);
}
}
fn frame<'buf>(
protocol_version: ProtocolVersion,
payload_type: PayloadType,
payload: &Payload<'_>,
buf: &'buf mut [u8],
) -> &'buf [u8] {
let payload_len = payload.encoded_size().expect("payload encoded_size failed");
let header = Header::new(
protocol_version,
payload_type,
u32::try_from(payload_len).expect("payload fits in u32"),
);
let written = {
let mut writer: &mut [u8] = buf;
header.encode(&mut writer).expect("header encode failed")
+ payload.encode(&mut writer).expect("payload encode failed")
};
assert_eq!(written, Header::SIZE + payload_len, "frame size disagrees");
&buf[..written]
}