mod alive_check_response;
pub use alive_check_response::AliveCheckResponse;
mod diagnostic_message;
pub use diagnostic_message::DiagnosticMessage;
#[cfg(feature = "alloc")]
pub use diagnostic_message::OwnedDiagnosticMessage;
mod diagnostic_message_ack;
#[cfg(feature = "alloc")]
pub use diagnostic_message_ack::OwnedDiagnosticMessageAck;
pub use diagnostic_message_ack::{DiagnosticAckCode, DiagnosticMessageAck};
mod entity_status_response;
pub use entity_status_response::{EntityStatusNodeType, EntityStatusResponse};
mod header;
pub use header::{Header, PayloadType, ProtocolVersion};
mod message_error;
pub use message_error::MessageError;
mod nack;
pub use nack::NackCode;
mod payload;
#[cfg(feature = "alloc")]
pub use payload::OwnedPayload;
pub use payload::Payload;
mod power_mode_info_response;
pub use power_mode_info_response::DiagnosticPowerModeCode;
mod routing_activation_request;
pub use routing_activation_request::{ActivationTypeCode, RoutingActivationRequest};
mod routing_activation_response;
pub use routing_activation_response::{RoutingActivationResponse, RoutingActivationResponseCode};
mod traits;
use traits::take;
pub use traits::{Decode, Encode};
mod vehicle_identification_response;
pub use vehicle_identification_response::{
FurtherActionRequired, VehicleIdentificationResponse, VinGidSyncStatus,
};
use crate::LogicalAddress;
fn payload_len(value: &impl Encode<Error = MessageError>) -> u32 {
u32::try_from(
value
.encoded_size()
.expect("DoIP message is always sizable"),
)
.expect("DoIP payload length exceeds u32::MAX")
}
#[derive(Clone, Debug, PartialEq)]
pub struct Message<'a> {
pub header: header::Header,
pub payload: Payload<'a>,
}
#[cfg(feature = "alloc")]
#[derive(Clone, Debug, PartialEq)]
pub struct OwnedMessage {
pub header: header::Header,
pub payload: OwnedPayload,
}
impl<'a> Message<'a> {
#[must_use]
pub fn is_response(&self, payload_type: PayloadType) -> bool {
match self.header.payload_type {
PayloadType::RoutingActivationRequest => {
payload_type == PayloadType::RoutingActivationResponse
}
PayloadType::AliveCheckRequest => payload_type == PayloadType::AliveCheckResponse,
PayloadType::DiagnosticMessage => {
payload_type == PayloadType::DiagnosticMessageNegativeAcknowledge
|| payload_type == PayloadType::DiagnosticMessagePositiveAcknowledge
|| payload_type == PayloadType::DiagnosticMessage
}
PayloadType::DoIPEntityStatusRequest => {
payload_type == PayloadType::DoIPEntityStatusResponse
}
PayloadType::DiagnosticPowerModeInfoRequest => {
payload_type == PayloadType::DiagnosticPowerModeInfoResponse
}
PayloadType::VehicleIdentificationRequest
| PayloadType::VehicleIdentificationRequestWithEID
| PayloadType::VehicleIdentificationRequestWithVIN => {
payload_type == PayloadType::VehicleAnnouncement
}
_ => false,
}
}
#[must_use]
pub fn alive_check_request(protocol_version: ProtocolVersion) -> Message<'a> {
Message {
header: Header::new(protocol_version, PayloadType::AliveCheckRequest, 0),
payload: Payload::AliveCheckRequest,
}
}
#[must_use]
pub fn alive_check_response(
protocol_version: ProtocolVersion,
source_address: LogicalAddress,
) -> Message<'a> {
let response = AliveCheckResponse { source_address };
Message {
header: Header::new(protocol_version, PayloadType::AliveCheckResponse, 2),
payload: Payload::AliveCheckResponse(response),
}
}
#[must_use]
pub fn diagnostic_message(
protocol_version: ProtocolVersion,
source_address: LogicalAddress,
target_address: LogicalAddress,
user_data: &'a [u8],
) -> Message<'a> {
let message = DiagnosticMessage {
source_address,
target_address,
user_data,
};
let payload_size = payload_len(&message);
Message {
header: Header::new(
protocol_version,
PayloadType::DiagnosticMessage,
payload_size,
),
payload: Payload::DiagnosticMessage(message),
}
}
#[must_use]
pub fn diagnostic_message_ack(
protocol_version: ProtocolVersion,
source_address: LogicalAddress,
target_address: LogicalAddress,
ack_code: DiagnosticAckCode,
previous_message_data: &'a [u8],
) -> Message<'a> {
let ack = DiagnosticMessageAck {
source_address,
target_address,
ack_code,
previous_message_data,
};
let payload_size = payload_len(&ack);
Message {
header: Header::new(
protocol_version,
PayloadType::DiagnosticMessagePositiveAcknowledge,
payload_size,
),
payload: Payload::DiagnosticMessageAck(ack),
}
}
#[must_use]
pub fn routing_activation_request(
protocol_version: ProtocolVersion,
source_address: LogicalAddress,
activation_type: ActivationTypeCode,
reserved_vehicle_manufacturer: Option<[u8; 4]>,
) -> Message<'a> {
let request = RoutingActivationRequest {
source_address,
activation_type,
reserved: [0, 0, 0, 0],
reserved_vehicle_manufacturer,
};
let header = Header::new(
protocol_version,
PayloadType::RoutingActivationRequest,
payload_len(&request),
);
Message {
header,
payload: Payload::RoutingActivationRequest(request),
}
}
#[must_use]
pub fn routing_activation_response(
protocol_version: ProtocolVersion,
logical_address_tester: LogicalAddress,
logical_address_of_doip_entity: LogicalAddress,
routing_activation_response_code: RoutingActivationResponseCode,
reserved_oem: [u8; 4],
oem_specific: Option<[u8; 4]>,
) -> Message<'a> {
let response = RoutingActivationResponse {
logical_address_tester,
logical_address_of_doip_entity,
routing_activation_response_code,
reserved_oem,
oem_specific,
};
let header = Header::new(
protocol_version,
PayloadType::RoutingActivationResponse,
payload_len(&response),
);
Message {
header,
payload: Payload::RoutingActivationResponse(response),
}
}
}
impl<'a> Decode<'a> for Message<'a> {
type Error = MessageError;
fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), MessageError> {
let (header, rest) = Header::decode(buf)?;
let (payload_bytes, rest) = take(rest, header.payload_length as usize)?;
let payload = Payload::decode(payload_bytes, header.payload_type)?;
Ok((Message { header, payload }, rest))
}
}
impl Encode for Message<'_> {
type Error = MessageError;
fn encoded_size(&self) -> Result<usize, MessageError> {
Ok(Header::SIZE + self.payload.encoded_size()?)
}
fn encode(&self, writer: &mut impl embedded_io::Write) -> Result<usize, MessageError> {
let payload_length = self.payload.encoded_size()?;
let header = Header::new(
self.header.protocol_version,
self.header.payload_type,
u32::try_from(payload_length).map_err(|_| MessageError::PayloadTooLarge {
size: payload_length,
})?,
);
let written = header.encode(writer)?;
Ok(written + self.payload.encode(writer)?)
}
}
#[cfg(feature = "alloc")]
impl Message<'_> {
#[must_use]
pub fn to_owned_message(&self) -> OwnedMessage {
OwnedMessage {
header: self.header.clone(),
payload: self.payload.to_owned_payload(),
}
}
}
#[cfg(feature = "alloc")]
impl OwnedMessage {
#[must_use]
pub fn as_ref(&self) -> Message<'_> {
Message {
header: self.header.clone(),
payload: self.payload.as_ref(),
}
}
#[must_use]
pub fn is_response(&self, payload_type: PayloadType) -> bool {
self.as_ref().is_response(payload_type)
}
#[must_use]
pub fn alive_check_request(protocol_version: ProtocolVersion) -> OwnedMessage {
Message::alive_check_request(protocol_version).to_owned_message()
}
#[must_use]
pub fn alive_check_response(
protocol_version: ProtocolVersion,
source_address: LogicalAddress,
) -> OwnedMessage {
Message::alive_check_response(protocol_version, source_address).to_owned_message()
}
#[must_use]
pub fn routing_activation_request(
protocol_version: ProtocolVersion,
source_address: LogicalAddress,
activation_type: ActivationTypeCode,
reserved_vehicle_manufacturer: Option<[u8; 4]>,
) -> OwnedMessage {
Message::routing_activation_request(
protocol_version,
source_address,
activation_type,
reserved_vehicle_manufacturer,
)
.to_owned_message()
}
#[must_use]
pub fn routing_activation_response(
protocol_version: ProtocolVersion,
logical_address_tester: LogicalAddress,
logical_address_of_doip_entity: LogicalAddress,
routing_activation_response_code: RoutingActivationResponseCode,
reserved_oem: [u8; 4],
oem_specific: Option<[u8; 4]>,
) -> OwnedMessage {
Message::routing_activation_response(
protocol_version,
logical_address_tester,
logical_address_of_doip_entity,
routing_activation_response_code,
reserved_oem,
oem_specific,
)
.to_owned_message()
}
#[must_use]
pub fn diagnostic_message(
protocol_version: ProtocolVersion,
source_address: LogicalAddress,
target_address: LogicalAddress,
user_data: alloc::vec::Vec<u8>,
) -> OwnedMessage {
let message = OwnedDiagnosticMessage {
source_address,
target_address,
user_data,
};
let payload_size = payload_len(&message.as_ref());
OwnedMessage {
header: Header::new(
protocol_version,
PayloadType::DiagnosticMessage,
payload_size,
),
payload: OwnedPayload::DiagnosticMessage(message),
}
}
#[must_use]
pub fn diagnostic_message_ack(
protocol_version: ProtocolVersion,
source_address: LogicalAddress,
target_address: LogicalAddress,
ack_code: DiagnosticAckCode,
previous_message_data: alloc::vec::Vec<u8>,
) -> OwnedMessage {
let ack = OwnedDiagnosticMessageAck {
source_address,
target_address,
ack_code,
previous_message_data,
};
let payload_size = payload_len(&ack.as_ref());
OwnedMessage {
header: Header::new(
protocol_version,
PayloadType::DiagnosticMessagePositiveAcknowledge,
payload_size,
),
payload: OwnedPayload::DiagnosticMessageAck(ack),
}
}
#[must_use]
pub fn vehicle_identification_response(
protocol_version: ProtocolVersion,
response: VehicleIdentificationResponse,
) -> OwnedMessage {
let payload_size = payload_len(&Payload::VehicleIdentificationResponse(response));
OwnedMessage {
header: Header::new(
protocol_version,
PayloadType::VehicleAnnouncement,
payload_size,
),
payload: OwnedPayload::VehicleIdentificationResponse(response),
}
}
}
#[cfg(feature = "alloc")]
impl Encode for OwnedMessage {
type Error = MessageError;
fn encoded_size(&self) -> Result<usize, MessageError> {
self.as_ref().encoded_size()
}
fn encode(&self, writer: &mut impl embedded_io::Write) -> Result<usize, MessageError> {
self.as_ref().encode(writer)
}
}
#[cfg(feature = "alloc")]
impl Default for OwnedMessage {
fn default() -> Self {
OwnedMessage {
header: Header::new(ProtocolVersion::V2012, PayloadType::DiagnosticMessage, 0),
payload: OwnedPayload::DiagnosticMessage(OwnedDiagnosticMessage {
source_address: LogicalAddress(0),
target_address: LogicalAddress(0),
user_data: alloc::vec::Vec::new(),
}),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use header::{PayloadType, ProtocolVersion};
#[test]
fn test_valid_messages() {
let buf: [u8; 9] = [0x02, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03];
let deserialized_message: Message<'_> = Message::decode(&buf).unwrap().0;
assert_eq!(
deserialized_message.header.protocol_version,
ProtocolVersion::V2012
);
assert_eq!(
deserialized_message.header.payload_type,
PayloadType::NegativeAcknowledge
);
assert_eq!(deserialized_message.header.payload_length, 1);
let buf: [u8; 15] = [
0x01, 0xFE, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,
];
let deserialized_message: Message<'_> = Message::decode(&buf).unwrap().0;
assert_eq!(
deserialized_message.header.protocol_version,
ProtocolVersion::V2010
);
assert_eq!(
deserialized_message.header.payload_type,
PayloadType::VehicleIdentificationRequest
);
assert_eq!(deserialized_message.header.payload_length, 7);
}
#[test]
fn test_invalid_inverse() {
let buf: [u8; 8] = [0x01, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
assert!(matches!(
Message::decode(&buf),
Err(MessageError::VersionInverseIncorrect { .. })
));
}
#[test]
fn test_no_std_stack_buffer_roundtrip() {
let message: Message<'_> = Message::diagnostic_message(
ProtocolVersion::V2012,
LogicalAddress(0x0E00),
LogicalAddress(0x1000),
&[0x10u8, 0x02][..],
);
let mut buf = [0u8; 64];
let written = {
let mut writer: &mut [u8] = &mut buf;
message.encode(&mut writer).unwrap()
};
let (frame, consumed) = crate::try_frame(&buf[..written]).unwrap().unwrap();
assert_eq!(consumed, written);
let payload = Payload::decode(frame.payload, frame.header.payload_type).unwrap();
let decoded = Message {
header: frame.header,
payload,
};
assert_eq!(decoded, message);
}
#[test]
fn test_diagnostic_message_ack_round_trip() {
let message: Message<'_> = Message::diagnostic_message_ack(
ProtocolVersion::V2012,
LogicalAddress(0x0E00),
LogicalAddress(0x1000),
DiagnosticAckCode::RoutingConfirmationAck,
&[0x10u8, 0x02][..],
);
assert_eq!(message.header.payload_length, 7);
let mut buf = [0u8; 64];
let written = {
let mut writer: &mut [u8] = &mut buf;
message.encode(&mut writer).unwrap()
};
let (frame, consumed) = crate::try_frame(&buf[..written]).unwrap().unwrap();
assert_eq!(consumed, written);
let payload = Payload::decode(frame.payload, frame.header.payload_type).unwrap();
let decoded = Message {
header: frame.header,
payload,
};
assert_eq!(decoded, message);
}
#[test]
fn test_routing_activation_response_oem_specific_round_trip() {
let message: Message<'_> = Message::routing_activation_response(
ProtocolVersion::V2012,
LogicalAddress(0x0E00),
LogicalAddress(0x1000),
RoutingActivationResponseCode::RoutingSuccessfullyActivated,
[0x00, 0x00, 0x00, 0x00],
Some([0xDE, 0xAD, 0xBE, 0xEF]),
);
assert_eq!(message.header.payload_length, 13);
let mut buf = [0u8; 64];
let written = {
let mut writer: &mut [u8] = &mut buf;
message.encode(&mut writer).unwrap()
};
let (frame, consumed) = crate::try_frame(&buf[..written]).unwrap().unwrap();
assert_eq!(consumed, written);
let payload = Payload::decode(frame.payload, frame.header.payload_type).unwrap();
let decoded = Message {
header: frame.header,
payload,
};
assert_eq!(decoded, message);
}
}
#[cfg(all(test, feature = "alloc"))]
mod alloc_conversion_tests {
use super::*;
use crate::messages::{
AliveCheckResponse, DiagnosticAckCode, DiagnosticMessage, DiagnosticMessageAck,
DiagnosticPowerModeCode, EntityStatusNodeType, EntityStatusResponse, FurtherActionRequired,
NackCode, RoutingActivationResponseCode, VehicleIdentificationResponse, VinGidSyncStatus,
};
use alloc::vec::Vec;
#[test]
fn payload_conversion_roundtrip_all_variants() {
let diag_data = [0x10u8, 0x02];
let ack_data = [0x3Eu8, 0x00, 0xAA];
let values: Vec<Payload<'_>> = alloc::vec![
Payload::DoIPNack(NackCode::IncorrectPatternFormat),
Payload::AliveCheckRequest,
Payload::AliveCheckResponse(AliveCheckResponse {
source_address: LogicalAddress(0x0E00),
}),
Payload::DiagnosticMessage(DiagnosticMessage {
source_address: LogicalAddress(0x0E00),
target_address: LogicalAddress(0x1000),
user_data: &diag_data[..],
}),
Payload::DiagnosticMessageAck(DiagnosticMessageAck {
source_address: LogicalAddress(0xFFFF),
target_address: LogicalAddress(0x0001),
ack_code: DiagnosticAckCode::TransportProtocolError,
previous_message_data: &ack_data[..],
}),
Payload::DiagnosticMessageNack,
Payload::EntityStatusRequest,
Payload::EntityStatusResponse(EntityStatusResponse {
node_type: EntityStatusNodeType::DoIPGateway,
max_concurrent_tcp_sockets: 4,
open_tcp_sockets: 0,
max_data_size: 0x0000_FFFF,
}),
Payload::PowerModeInfoResponse(DiagnosticPowerModeCode::Ready),
Payload::RoutingActivationRequest(RoutingActivationRequest {
source_address: LogicalAddress(0x0E00),
activation_type: ActivationTypeCode::Default,
reserved: [0, 0, 0, 0],
reserved_vehicle_manufacturer: Some([0xDE, 0xAD, 0xBE, 0xEF]),
}),
Payload::RoutingActivationResponse(RoutingActivationResponse {
logical_address_tester: LogicalAddress(0x0E00),
logical_address_of_doip_entity: LogicalAddress(0x1000),
routing_activation_response_code:
RoutingActivationResponseCode::RoutingSuccessfullyActivated,
reserved_oem: [0, 0, 0, 0],
oem_specific: Some([0xDE, 0xAD, 0xBE, 0xEF]),
}),
Payload::VehicleAnnouncement(VehicleIdentificationResponse {
vin: [0x41; 17],
logical_address: LogicalAddress(0x0E00),
entity_id: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06],
group_id: Some([0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F]),
further_action: FurtherActionRequired::NoFurtherActionRequired,
vin_gid_sync_status: VinGidSyncStatus::Synchronized,
}),
Payload::VehicleIdentificationRequest,
Payload::VehicleIdentificationResponse(VehicleIdentificationResponse {
vin: *b"1HGCM82633A004352",
logical_address: LogicalAddress(0x1000),
entity_id: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF],
group_id: None,
further_action: FurtherActionRequired::NoFurtherActionRequired,
vin_gid_sync_status: VinGidSyncStatus::Synchronized,
}),
];
assert_eq!(values.len(), 14, "expected one value per Payload variant");
for p in &values {
let owned = p.to_owned_payload();
assert_eq!(&owned.as_ref(), p, "conversion mismatch for {p:?}");
}
}
#[test]
fn owned_message_matches_borrowed_headers() {
let borrowed = Message::routing_activation_response(
ProtocolVersion::V2012,
LogicalAddress(0x0E00),
LogicalAddress(0x1000),
RoutingActivationResponseCode::RoutingSuccessfullyActivated,
[0x00, 0x00, 0x00, 0x00],
Some([0xDE, 0xAD, 0xBE, 0xEF]),
);
let owned = OwnedMessage::routing_activation_response(
ProtocolVersion::V2012,
LogicalAddress(0x0E00),
LogicalAddress(0x1000),
RoutingActivationResponseCode::RoutingSuccessfullyActivated,
[0x00, 0x00, 0x00, 0x00],
Some([0xDE, 0xAD, 0xBE, 0xEF]),
);
assert_eq!(borrowed.header, owned.header);
assert_eq!(borrowed.header.payload_length, 13);
let data = [0x22u8, 0xF1, 0x90, 0x00];
let borrowed = Message::diagnostic_message(
ProtocolVersion::V2012,
LogicalAddress(0xE400),
LogicalAddress(0x00FF),
&data[..],
);
let owned = OwnedMessage::diagnostic_message(
ProtocolVersion::V2012,
LogicalAddress(0xE400),
LogicalAddress(0x00FF),
data.to_vec(),
);
assert_eq!(borrowed.header, owned.header);
assert_eq!(borrowed.to_owned_message().as_ref().header, borrowed.header);
let borrowed = Message::routing_activation_request(
ProtocolVersion::V2012,
LogicalAddress(0x0E00),
ActivationTypeCode::Default,
Some([0xDE, 0xAD, 0xBE, 0xEF]),
);
let owned = OwnedMessage::routing_activation_request(
ProtocolVersion::V2012,
LogicalAddress(0x0E00),
ActivationTypeCode::Default,
Some([0xDE, 0xAD, 0xBE, 0xEF]),
);
assert_eq!(borrowed.header, owned.header);
}
}