From 8875d9395bb581f8f089d7db5919d0262a02dbb1 Mon Sep 17 00:00:00 2001 From: "kevin.russ" Date: Thu, 10 Oct 2024 21:33:34 +0200 Subject: [PATCH] Parse DLT network-trace prefix for SOME/IP messages --- application/apps/indexer/parsers/Cargo.toml | 1 + .../apps/indexer/parsers/src/dlt/fmt.rs | 7 +- application/apps/indexer/parsers/src/lib.rs | 14 ++ .../apps/indexer/parsers/src/someip.rs | 121 ++++++++++++++++++ .../ts-bindings/spec/session.observe.spec.ts | 12 +- 5 files changed, 148 insertions(+), 7 deletions(-) diff --git a/application/apps/indexer/parsers/Cargo.toml b/application/apps/indexer/parsers/Cargo.toml index 334ac09117..4481254eb6 100644 --- a/application/apps/indexer/parsers/Cargo.toml +++ b/application/apps/indexer/parsers/Cargo.toml @@ -13,6 +13,7 @@ lazy_static.workspace = true log.workspace = true regex.workspace = true memchr = "2.4" +nom = "7.1" serde = { workspace = true , features = ["derive"] } thiserror.workspace = true rand.workspace = true diff --git a/application/apps/indexer/parsers/src/dlt/fmt.rs b/application/apps/indexer/parsers/src/dlt/fmt.rs index c712e82621..624f6bd741 100644 --- a/application/apps/indexer/parsers/src/dlt/fmt.rs +++ b/application/apps/indexer/parsers/src/dlt/fmt.rs @@ -604,7 +604,12 @@ impl<'a> fmt::Display for FormattableMessage<'a> { if let Some(slice) = slices.get(1) { match SomeipParser::parse_message(self.fibex_someip_metadata, slice, None) { Ok((_, message)) => { - return write!(f, "SOME/IP {:?}", message); + let prefix = slices.first().map_or_else(String::default, |s| { + SomeipParser::parse_prefix(s) + .ok() + .map_or_else(String::default, |p| format!("{} ", p.1)) + }); + return write!(f, "SOME/IP {}{:?}", prefix, message); } Err(error) => { return write!(f, "SOME/IP '{}' {:02X?}", error, slice); diff --git a/application/apps/indexer/parsers/src/lib.rs b/application/apps/indexer/parsers/src/lib.rs index 1b7fce8efe..ecdffd1865 100644 --- a/application/apps/indexer/parsers/src/lib.rs +++ b/application/apps/indexer/parsers/src/lib.rs @@ -18,6 +18,20 @@ pub enum Error { Eof, } +impl nom::error::ParseError<&[u8]> for Error { + fn from_error_kind(input: &[u8], kind: nom::error::ErrorKind) -> Self { + Error::Parse(format!( + "Nom error: {:?} ({} bytes left)", + kind, + input.len() + )) + } + + fn append(_: &[u8], _: nom::error::ErrorKind, other: Self) -> Self { + other + } +} + #[derive(Debug)] pub enum ParseYield { Message(T), diff --git a/application/apps/indexer/parsers/src/someip.rs b/application/apps/indexer/parsers/src/someip.rs index cadc742f76..ef4b646b02 100644 --- a/application/apps/indexer/parsers/src/someip.rs +++ b/application/apps/indexer/parsers/src/someip.rs @@ -5,6 +5,7 @@ use std::{ collections::HashMap, fmt::{self, Display}, io::Write, + net::Ipv4Addr, path::PathBuf, sync::Mutex, }; @@ -16,6 +17,13 @@ use someip_payload::{ som::{SOMParser, SOMType}, }; +use nom::{ + combinator::map, + number::streaming::{be_u16, be_u32, be_u8}, + sequence::tuple, + IResult, +}; + use lazy_static::lazy_static; use log::{debug, error}; use regex::Regex; @@ -191,6 +199,44 @@ impl SomeipParser { } } + /// Parses a DLT Network-Trace prefix for a SOME/IP message from the given input. + /// + /// A valid prefix will consist of: + /// - 4 bytes as IPv4 address + /// - 2 bytes as udp/tcp port + /// - 1 byte as protocol type (0 = local, 1 = tcp, 2 = udp) + /// - 1 byte as message direction (0 = incoming, 1 = outgoing) + /// - 1, 2 or 4 bytes as instance-id + pub(crate) fn parse_prefix(input: &[u8]) -> IResult<&[u8], std::string::String, Error> { + map( + tuple((be_u32, be_u16, be_u8, be_u8, Self::parse_instance)), + |(address, port, proto, direction, instance)| { + format!( + "{}{}:{} {}INST:{}", + Direction::try_from(direction) + .ok() + .map_or_else(String::default, |s| format!("{} ", s)), + Ipv4Addr::from(address), + port, + Proto::try_from(proto) + .ok() + .map_or_else(String::default, |s| format!("{} ", s)), + instance + ) + }, + )(input) + } + + pub(crate) fn parse_instance(input: &[u8]) -> IResult<&[u8], usize, Error> { + match input.len() { + 1 => map(be_u8, |i| i as usize)(input), + 2 => map(be_u16, |i| i as usize)(input), + 4 => map(be_u32, |i| i as usize)(input), + _ => Err(nom::Err::Error(Error::Incomplete)), + } + } + + /// Parses a SOME/IP message (header and payload) from the given input. pub(crate) fn parse_message<'a>( fibex_metadata: Option<&FibexMetadata>, input: &'a [u8], @@ -271,6 +317,61 @@ impl SomeipParser { } } +enum Proto { + Local, + Tcp, + Udp, +} + +impl Display for Proto { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Proto::Local => write!(f, "LOCAL"), + Proto::Tcp => write!(f, "TCP"), + Proto::Udp => write!(f, "UDP"), + } + } +} + +impl TryFrom for Proto { + type Error = (); + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Proto::Local), + 1 => Ok(Proto::Tcp), + 2 => Ok(Proto::Udp), + _ => Err(()), + } + } +} + +enum Direction { + Incoming, + Outgoing, +} + +impl Display for Direction { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Direction::Incoming => write!(f, "<<"), + Direction::Outgoing => write!(f, ">>"), + } + } +} + +impl TryFrom for Direction { + type Error = (); + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Direction::Incoming), + 1 => Ok(Direction::Outgoing), + _ => Err(()), + } + } +} + unsafe impl Send for SomeipParser {} unsafe impl Sync for SomeipParser {} @@ -1012,4 +1113,24 @@ mod test { assert!(meta_data.get_service(213, 1).is_none()); assert!(meta_data.get_service(321, 1).is_some()); } + + #[test] + fn test_parse_prefix() { + let input = [0x7F, 0x00, 0x00, 0x01, 0x30, 0x39, 0x01, 0x00, 0x01]; + let result = SomeipParser::parse_prefix(&input).expect("prefix"); + assert_eq!("<< 127.0.0.1:12345 TCP INST:1", result.1); + + let input = [0x7F, 0x00, 0x00, 0x01, 0x30, 0x39, 0x02, 0x01, 0x00, 0x02]; + let result = SomeipParser::parse_prefix(&input).expect("prefix"); + assert_eq!(">> 127.0.0.1:12345 UDP INST:2", result.1); + + let input = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x03, + ]; + let result = SomeipParser::parse_prefix(&input).expect("prefix"); + assert_eq!("0.0.0.0:0 LOCAL INST:3", result.1); + + let input = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + assert!(SomeipParser::parse_prefix(&input).is_err()) + } } diff --git a/application/apps/rustcore/ts-bindings/spec/session.observe.spec.ts b/application/apps/rustcore/ts-bindings/spec/session.observe.spec.ts index 00b6472013..bfc9550bed 100644 --- a/application/apps/rustcore/ts-bindings/spec/session.observe.spec.ts +++ b/application/apps/rustcore/ts-bindings/spec/session.observe.spec.ts @@ -627,7 +627,7 @@ describe('Observe', function () { expect(result.length).toEqual(6); expect(result[0].content.split('\u0004')).toEqual([ '2024-02-20T13:17:26.713537000Z', 'ECU1', '1', '571', '204', '28138506', 'ECU1', 'APP1', 'C1', 'IPC', - 'SOME/IP RPC SERV:123 METH:32773 LENG:16 CLID:0 SEID:58252 IVER:1 MSTP:2 RETC:0 [00, 00, 01, 88, 01, C3, C4, 1D]', + 'SOME/IP >> 0.0.0.0:0 INST:1 RPC SERV:123 METH:32773 LENG:16 CLID:0 SEID:58252 IVER:1 MSTP:2 RETC:0 [00, 00, 01, 88, 01, C3, C4, 1D]', ]); expect(result[5].content.split('\u0004')).toEqual([ '2024-02-20T13:17:26.713537000Z', 'ECU1', '1', '571', '209', '28138506', 'ECU1', 'APP1', 'C1', 'IPC', @@ -692,23 +692,23 @@ describe('Observe', function () { expect(result.length).toEqual(6); expect(result[0].content.split('\u0004')).toEqual([ '2024-02-20T13:17:26.713537000Z', 'ECU1', '1', '571', '204', '28138506', 'ECU1', 'APP1', 'C1', 'IPC', - 'SOME/IP RPC SERV:123 METH:32773 LENG:16 CLID:0 SEID:58252 IVER:1 MSTP:2 RETC:0 TestService::timeEvent {\u0006\ttimestamp (INT64) : 1683656786973,\u0006}', + 'SOME/IP >> 0.0.0.0:0 INST:1 RPC SERV:123 METH:32773 LENG:16 CLID:0 SEID:58252 IVER:1 MSTP:2 RETC:0 TestService::timeEvent {\u0006\ttimestamp (INT64) : 1683656786973,\u0006}', ]); expect(result[1].content.split('\u0004')).toEqual([ '2024-02-20T13:17:26.713537000Z', 'ECU1', '1', '571', '205', '28138506', 'ECU1', 'APP1', 'C1', 'IPC', - 'SOME/IP RPC SERV:124 METH:32773 LENG:16 CLID:0 SEID:58252 IVER:1 MSTP:2 RETC:0 UnknownService [00, 00, 01, 88, 01, C3, C4, 1D]', + 'SOME/IP >> 0.0.0.0:0 INST:1 RPC SERV:124 METH:32773 LENG:16 CLID:0 SEID:58252 IVER:1 MSTP:2 RETC:0 UnknownService [00, 00, 01, 88, 01, C3, C4, 1D]', ]); expect(result[2].content.split('\u0004')).toEqual([ '2024-02-20T13:17:26.713537000Z', 'ECU1', '1', '571', '206', '28138506', 'ECU1', 'APP1', 'C1', 'IPC', - 'SOME/IP RPC SERV:123 METH:32773 LENG:16 CLID:0 SEID:58252 IVER:3 MSTP:2 RETC:0 TestService<1?>::timeEvent {\u0006\ttimestamp (INT64) : 1683656786973,\u0006}', + 'SOME/IP >> 0.0.0.0:0 INST:1 RPC SERV:123 METH:32773 LENG:16 CLID:0 SEID:58252 IVER:3 MSTP:2 RETC:0 TestService<1?>::timeEvent {\u0006\ttimestamp (INT64) : 1683656786973,\u0006}', ]); expect(result[3].content.split('\u0004')).toEqual([ '2024-02-20T13:17:26.713537000Z', 'ECU1', '1', '571', '207', '28138506', 'ECU1', 'APP1', 'C1', 'IPC', - 'SOME/IP RPC SERV:123 METH:32774 LENG:16 CLID:0 SEID:58252 IVER:1 MSTP:2 RETC:0 TestService::UnknownMethod [00, 00, 01, 88, 01, C3, C4, 1D]', + 'SOME/IP >> 0.0.0.0:0 INST:1 RPC SERV:123 METH:32774 LENG:16 CLID:0 SEID:58252 IVER:1 MSTP:2 RETC:0 TestService::UnknownMethod [00, 00, 01, 88, 01, C3, C4, 1D]', ]); expect(result[4].content.split('\u0004')).toEqual([ '2024-02-20T13:17:26.713537000Z', 'ECU1', '1', '571', '208', '28138506', 'ECU1', 'APP1', 'C1', 'IPC', - 'SOME/IP RPC SERV:123 METH:32773 LENG:15 CLID:0 SEID:58252 IVER:1 MSTP:2 RETC:0 TestService::timeEvent \'SOME/IP Error: Parser exhausted at offset 0 for Object size 8\' [00, 00, 01, 88, 01, C3, C4]', + 'SOME/IP >> 0.0.0.0:0 INST:1 RPC SERV:123 METH:32773 LENG:15 CLID:0 SEID:58252 IVER:1 MSTP:2 RETC:0 TestService::timeEvent \'SOME/IP Error: Parser exhausted at offset 0 for Object size 8\' [00, 00, 01, 88, 01, C3, C4]', ]); expect(result[5].content.split('\u0004')).toEqual([ '2024-02-20T13:17:26.713537000Z', 'ECU1', '1', '571', '209', '28138506', 'ECU1', 'APP1', 'C1', 'IPC',