This commit is contained in:
@@ -3,7 +3,7 @@ use bytes::{Bytes, BytesMut};
|
|||||||
use sawp::error::Error as SawpError;
|
use sawp::error::Error as SawpError;
|
||||||
use sawp::error::ErrorKind;
|
use sawp::error::ErrorKind;
|
||||||
use sawp::parser::{Direction, Parse};
|
use sawp::parser::{Direction, Parse};
|
||||||
use sawp_modbus::{Data, Message, Modbus};
|
use sawp_modbus::{Data, Exception, ExceptionCode, Message, Modbus, Read, Write};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json::{Value, json};
|
use serde_json::{Value, json};
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
@@ -124,109 +124,114 @@ fn get_tcp_data_v6(buf: Bytes) -> Result<Bytes> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_tcp_payload_from_eth(eth: &EthernetPacket) -> Result<Bytes> {
|
|
||||||
let pkt = eth.packet(); // returns &[u8]
|
|
||||||
match eth.get_ethertype() {
|
|
||||||
EtherTypes::Ipv4 => {
|
|
||||||
let ipv4 = Ipv4Packet::new(pkt).ok_or_else(|| anyhow!("failed to parse ipv4"))?;
|
|
||||||
if ipv4.get_next_level_protocol() != IpNextHeaderProtocols::Tcp {
|
|
||||||
return Err(anyhow!("not a TCP packet"));
|
|
||||||
}
|
|
||||||
let ipv4_payload = ipv4.payload();
|
|
||||||
let tcp = TcpPacket::new(ipv4_payload).ok_or_else(|| anyhow!("failed to parse tcp"))?;
|
|
||||||
let tcp_payload = tcp.payload();
|
|
||||||
|
|
||||||
// compute byte offsets relative to the original packet slice
|
|
||||||
let base = pkt.as_ptr() as usize;
|
|
||||||
let start = tcp_payload.as_ptr() as usize - base;
|
|
||||||
let end = start + tcp_payload.len();
|
|
||||||
Ok(Bytes::copy_from_slice(&pkt[start..end]).slice(0..tcp_payload.len()))
|
|
||||||
}
|
|
||||||
|
|
||||||
EtherTypes::Ipv6 => {
|
|
||||||
let ipv6 = Ipv6Packet::new(pkt).ok_or_else(|| anyhow!("failed to parse ipv6"))?;
|
|
||||||
if ipv6.get_next_header() != IpNextHeaderProtocols::Tcp {
|
|
||||||
return Err(anyhow!("not a TCP packet"));
|
|
||||||
}
|
|
||||||
let ipv6_payload = ipv6.payload();
|
|
||||||
let tcp = TcpPacket::new(ipv6_payload).ok_or_else(|| anyhow!("failed to parse tcp"))?;
|
|
||||||
let tcp_payload = tcp.payload();
|
|
||||||
|
|
||||||
let base = pkt.as_ptr() as usize;
|
|
||||||
let start = tcp_payload.as_ptr() as usize - base;
|
|
||||||
let end = start + tcp_payload.len();
|
|
||||||
Ok(Bytes::copy_from_slice(&pkt[start..end]).slice(0..tcp_payload.len()))
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => Err(anyhow!("not IPv4/IPv6")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Top-level parse entry: takes sawp_modbus::Message and descriptor map.
|
|
||||||
/// Returns JSON object with parsed fields.
|
|
||||||
pub fn parse_sawp_message(
|
pub fn parse_sawp_message(
|
||||||
msg: &Message,
|
msg: &Message,
|
||||||
map: &FuncMap,
|
map: &FuncMap,
|
||||||
is_response: bool,
|
is_response: bool,
|
||||||
) -> Result<Value, String> {
|
) -> Result<Value, String> {
|
||||||
// obtain function code and exception flag from msg.function
|
|
||||||
let mut fn_code = msg.function.raw;
|
let mut fn_code = msg.function.raw;
|
||||||
let is_exception_fn = (fn_code & 0x80) != 0;
|
let is_exception_fn = (fn_code & 0x80) != 0;
|
||||||
if is_exception_fn {
|
if is_exception_fn {
|
||||||
fn_code &= 0x7F;
|
fn_code &= 0x7F;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract a byte-slice to feed generic PDU parser.
|
|
||||||
// Different Data variants carry bytes differently.
|
|
||||||
let pdu_bytes = match &msg.data {
|
let pdu_bytes = match &msg.data {
|
||||||
Data::Exception(exc) => {
|
Data::Exception(exc) => {
|
||||||
// Exception contains an exception code byte or similar; convert to vec
|
// Exception typically wraps a u8 code; adapt if Exception is different.
|
||||||
// If Exception type exposes code() or .0, adapt accordingly.
|
// try to extract a u8 from Exception; if it exposes .code() use that.
|
||||||
// Here we try to obtain a single byte; if Exception is an enum with u8 inside:
|
// Here we attempt pattern if Exception(pub u8)
|
||||||
let code_byte = exc.raw; // if Exception(pub u8)
|
let code: u8 = exc.code as u8;
|
||||||
vec![code_byte]
|
vec![code]
|
||||||
}
|
}
|
||||||
Data::Diagnostic { data, .. } => data.clone(),
|
Data::Diagnostic { data, .. } => data.clone(),
|
||||||
Data::MEI { data, .. } => data.clone(),
|
Data::MEI { data, .. } => data.clone(),
|
||||||
Data::Read(read) => {
|
Data::Read(read) => match read {
|
||||||
// If Read provides a bytes() method or inner Vec<u8>, extract it.
|
Read::Request { address, quantity } => {
|
||||||
// Adjust depending on actual Read struct — common shape: Read { byte_count, data: Vec<u8> }
|
let mut v = Vec::with_capacity(4);
|
||||||
// Try common fields:
|
v.extend_from_slice(&address.to_be_bytes());
|
||||||
if let Some(bytes) = try_extract_read_bytes(read) {
|
v.extend_from_slice(&quantity.to_be_bytes());
|
||||||
bytes
|
v
|
||||||
} else {
|
|
||||||
return Err("unsupported Read variant layout; adapt extraction".into());
|
|
||||||
}
|
}
|
||||||
|
Read::Response(bytes) => bytes.clone(),
|
||||||
|
},
|
||||||
|
Data::Write(write) => match write {
|
||||||
|
Write::MultReq {
|
||||||
|
address,
|
||||||
|
quantity,
|
||||||
|
data,
|
||||||
|
} => {
|
||||||
|
let mut v = Vec::with_capacity(4 + data.len());
|
||||||
|
v.extend_from_slice(&address.to_be_bytes());
|
||||||
|
v.extend_from_slice(&quantity.to_be_bytes());
|
||||||
|
v.extend_from_slice(data);
|
||||||
|
v
|
||||||
}
|
}
|
||||||
Data::Write(write) => {
|
Write::Mask {
|
||||||
if let Some(bytes) = try_extract_write_bytes(write) {
|
address,
|
||||||
bytes
|
and_mask,
|
||||||
} else {
|
or_mask,
|
||||||
return Err("unsupported Write variant layout; adapt extraction".into());
|
} => {
|
||||||
|
let mut v = Vec::with_capacity(6);
|
||||||
|
v.extend_from_slice(&address.to_be_bytes());
|
||||||
|
v.extend_from_slice(&and_mask.to_be_bytes());
|
||||||
|
v.extend_from_slice(&or_mask.to_be_bytes());
|
||||||
|
v
|
||||||
}
|
}
|
||||||
|
Write::Other { address, data } => {
|
||||||
|
let mut v = Vec::with_capacity(4);
|
||||||
|
v.extend_from_slice(&address.to_be_bytes());
|
||||||
|
v.extend_from_slice(&data.to_be_bytes());
|
||||||
|
v
|
||||||
}
|
}
|
||||||
|
},
|
||||||
Data::ReadWrite { read, write } => {
|
Data::ReadWrite { read, write } => {
|
||||||
// For ReadWrite, decide which side to parse based on is_response.
|
|
||||||
// If is_response==true, prefer read bytes; else use write bytes.
|
|
||||||
if is_response {
|
if is_response {
|
||||||
if let Some(bytes) = try_extract_read_bytes(read) {
|
match read {
|
||||||
bytes
|
Read::Request { address, quantity } => {
|
||||||
} else {
|
let mut v = Vec::with_capacity(4);
|
||||||
return Err("unsupported Read variant in ReadWrite".into());
|
v.extend_from_slice(&address.to_be_bytes());
|
||||||
|
v.extend_from_slice(&quantity.to_be_bytes());
|
||||||
|
v
|
||||||
|
}
|
||||||
|
Read::Response(bytes) => bytes.clone(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let Some(bytes) = try_extract_write_bytes(write) {
|
match write {
|
||||||
bytes
|
Write::MultReq {
|
||||||
} else {
|
address,
|
||||||
return Err("unsupported Write variant in ReadWrite".into());
|
quantity,
|
||||||
|
data,
|
||||||
|
} => {
|
||||||
|
let mut v = Vec::with_capacity(4 + data.len());
|
||||||
|
v.extend_from_slice(&address.to_be_bytes());
|
||||||
|
v.extend_from_slice(&quantity.to_be_bytes());
|
||||||
|
v.extend_from_slice(data);
|
||||||
|
v
|
||||||
|
}
|
||||||
|
Write::Mask {
|
||||||
|
address,
|
||||||
|
and_mask,
|
||||||
|
or_mask,
|
||||||
|
} => {
|
||||||
|
let mut v = Vec::with_capacity(6);
|
||||||
|
v.extend_from_slice(&address.to_be_bytes());
|
||||||
|
v.extend_from_slice(&and_mask.to_be_bytes());
|
||||||
|
v.extend_from_slice(&or_mask.to_be_bytes());
|
||||||
|
v
|
||||||
|
}
|
||||||
|
Write::Other { address, data } => {
|
||||||
|
let mut v = Vec::with_capacity(4);
|
||||||
|
v.extend_from_slice(&address.to_be_bytes());
|
||||||
|
v.extend_from_slice(&data.to_be_bytes());
|
||||||
|
v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Data::ByteVec(v) => v.clone(),
|
}
|
||||||
|
Data::ByteVec(b) => b.clone(),
|
||||||
Data::Empty => vec![],
|
Data::Empty => vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
// If exception function: treat as exception
|
|
||||||
if is_exception_fn {
|
if is_exception_fn {
|
||||||
if pdu_bytes.is_empty() {
|
if pdu_bytes.is_empty() {
|
||||||
return Err("exception message missing exception code".into());
|
return Err("exception message missing exception code".into());
|
||||||
@@ -238,7 +243,6 @@ pub fn parse_sawp_message(
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lookup descriptor and parse using generic byte-slice parser
|
|
||||||
let desc = map
|
let desc = map
|
||||||
.get(&fn_code)
|
.get(&fn_code)
|
||||||
.ok_or_else(|| format!("unknown function code: {}", fn_code))?;
|
.ok_or_else(|| format!("unknown function code: {}", fn_code))?;
|
||||||
@@ -252,7 +256,6 @@ pub fn parse_sawp_message(
|
|||||||
parse_with_descriptor(&pdu_bytes, msg.unit_id, msg.function.raw, fields)
|
parse_with_descriptor(&pdu_bytes, msg.unit_id, msg.function.raw, fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generic parser: parse bytes per FieldDescriptor sequence.
|
|
||||||
fn parse_with_descriptor(
|
fn parse_with_descriptor(
|
||||||
pdu: &[u8],
|
pdu: &[u8],
|
||||||
unit: u8,
|
unit: u8,
|
||||||
@@ -362,15 +365,3 @@ fn insert_mapped(
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper extraction functions: adapt these to the real Read/Write structs in your sawp_modbus version.
|
|
||||||
/// These try common shapes; replace with direct field access if necessary.
|
|
||||||
fn try_extract_read_bytes<T>(_read: &T) -> Option<Vec<u8>> {
|
|
||||||
// Replace with actual extraction, for example:
|
|
||||||
// Some(read.data.clone()) or Some(read.bytes.clone())
|
|
||||||
None
|
|
||||||
}
|
|
||||||
fn try_extract_write_bytes<T>(_write: &T) -> Option<Vec<u8>> {
|
|
||||||
// Replace with actual extraction
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user