Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
## v0.3.0 (unpublished)

- Added `Slave` and `SlaveContext`
- Merged `modbus_core::rtu::FrameLocation` and `modbus_core::tcp::FrameLocation` and moved it to `modbus_core::FrameLocation`
- Return a `FrameLocation` in addition to the parsed frame in `rtu::server::decode_response`,
`rtu::client::decode_response`, `tcp::server::decode_response` and `tcp::client::decode_request`
- Added `FrameLocation::end` helper.

## v0.2.0 (2025-09-30)

Expand Down
17 changes: 10 additions & 7 deletions src/codec/rtu/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ pub fn encode_request(adu: RequestAdu, buf: &mut [u8]) -> Result<usize> {
}

/// Decode an RTU response.
pub fn decode_response(buf: &[u8]) -> Result<Option<ResponseAdu<'_>>> {
pub fn decode_response(buf: &[u8]) -> Result<Option<(ResponseAdu<'_>, FrameLocation)>> {
if buf.is_empty() {
return Ok(None);
}
decode(DecoderType::Response, buf)
.and_then(|frame| {
let Some((DecodedFrame { slave, pdu }, _frame_pos)) = frame else {
let Some((DecodedFrame { slave, pdu }, frame_location)) = frame else {
return Ok(None);
};
let hdr = Header { slave };
Expand All @@ -38,7 +38,7 @@ pub fn decode_response(buf: &[u8]) -> Result<Option<ResponseAdu<'_>>> {
let response = ExceptionResponse::try_from(pdu)
.map(|er| ResponsePdu(Err(er)))
.or_else(|_| Response::try_from(pdu).map(|r| ResponsePdu(Ok(r))))
.map(|pdu| Some(ResponseAdu { hdr, pdu }));
.map(|pdu| Some((ResponseAdu { hdr, pdu }, frame_location)));
#[cfg(feature = "log")]
if let Err(error) = response {
// Unrecoverable error
Expand Down Expand Up @@ -108,10 +108,13 @@ mod tests {

assert!(matches!(
decode_response(rsp),
Ok(Some(ResponseAdu {
hdr: Header { slave: 0x12 },
pdu: ResponsePdu(Ok(Response::WriteSingleRegister(0x2222, 0xABCD)))
}))
Ok(Some((
ResponseAdu {
hdr: Header { slave: 0x12 },
pdu: ResponsePdu(Ok(Response::WriteSingleRegister(0x2222, 0xABCD)))
},
FrameLocation { start: 0, size: 8 }
)))
));
}

Expand Down
10 changes: 0 additions & 10 deletions src/codec/rtu/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,6 @@ pub struct DecodedFrame<'a> {
pub pdu: &'a [u8],
}

/// The location of all bytes that belong to the frame.
#[cfg_attr(all(feature = "defmt", target_os = "none"), derive(defmt::Format))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FrameLocation {
/// The index where the frame starts
pub start: usize,
/// Number of bytes that belong to the frame
pub size: usize,
}

/// Decode RTU PDU frames from a buffer.
pub fn decode(
decoder_type: DecoderType,
Expand Down
10 changes: 6 additions & 4 deletions src/codec/rtu/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
use super::*;

/// Decode an RTU request.
pub fn decode_request(buf: &[u8]) -> Result<Option<RequestAdu<'_>>> {
pub fn decode_request(buf: &[u8]) -> Result<Option<(RequestAdu<'_>, FrameLocation)>> {
if buf.is_empty() {
return Ok(None);
}
decode(DecoderType::Request, buf)
.and_then(|frame| {
let Some((DecodedFrame { slave, pdu }, _frame_pos)) = frame else {
let Some((DecodedFrame { slave, pdu }, frame_location)) = frame else {
return Ok(None);
};
let hdr = Header { slave };
Expand All @@ -20,7 +20,7 @@ pub fn decode_request(buf: &[u8]) -> Result<Option<RequestAdu<'_>>> {
// have already been verified with the CRC.
let request = Request::try_from(pdu)
.map(RequestPdu)
.map(|pdu| Some(RequestAdu { hdr, pdu }));
.map(|pdu| Some((RequestAdu { hdr, pdu }, frame_location)));

#[cfg(feature = "log")]
if let Err(error) = request {
Expand Down Expand Up @@ -84,7 +84,9 @@ mod tests {
0x9F, // crc
0xBE, // crc
];
let adu = decode_request(buf).unwrap().unwrap();
let (adu, frame_location) = decode_request(buf).unwrap().unwrap();
assert_eq!(frame_location.start, 0);
assert_eq!(frame_location.size, 8);
let RequestAdu { hdr, pdu } = adu;
let RequestPdu(pdu) = pdu;
assert_eq!(hdr.slave, 0x12);
Expand Down
21 changes: 12 additions & 9 deletions src/codec/tcp/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub fn encode_request(adu: RequestAdu, buf: &mut [u8]) -> Result<usize> {
}

/// Decode an TCP response.
pub fn decode_response(buf: &[u8]) -> Result<Option<ResponseAdu<'_>>> {
pub fn decode_response(buf: &[u8]) -> Result<Option<(ResponseAdu<'_>, FrameLocation)>> {
if buf.is_empty() {
return Ok(None);
}
Expand All @@ -34,7 +34,7 @@ pub fn decode_response(buf: &[u8]) -> Result<Option<ResponseAdu<'_>>> {
unit_id,
pdu,
},
_frame_pos,
frame_location,
)) = frame
else {
return Ok(None);
Expand All @@ -49,7 +49,7 @@ pub fn decode_response(buf: &[u8]) -> Result<Option<ResponseAdu<'_>>> {
let response = ExceptionResponse::try_from(pdu)
.map(|er| ResponsePdu(Err(er)))
.or_else(|_| Response::try_from(pdu).map(|r| ResponsePdu(Ok(r))))
.map(|pdu| Some(ResponseAdu { hdr, pdu }));
.map(|pdu| Some((ResponseAdu { hdr, pdu }, frame_location)));
#[cfg(feature = "log")]
if let Err(error) = response {
// Unrecoverable error
Expand Down Expand Up @@ -128,13 +128,16 @@ mod tests {

assert!(matches!(
decode_response(rsp),
Ok(Some(ResponseAdu {
hdr: Header {
transaction_id: 0x1234,
unit_id: 0x12
Ok(Some((
ResponseAdu {
hdr: Header {
transaction_id: 0x1234,
unit_id: 0x12
},
pdu: ResponsePdu(Ok(Response::WriteSingleRegister(0x2222, 0xABCD)))
},
pdu: ResponsePdu(Ok(Response::WriteSingleRegister(0x2222, 0xABCD)))
}))
FrameLocation { start: 0, size: 12 }
)))
));
}

Expand Down
10 changes: 0 additions & 10 deletions src/codec/tcp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,6 @@ pub struct DecodedFrame<'a> {
pub pdu: &'a [u8],
}

/// The location of all bytes that belong to the frame.
#[cfg_attr(all(feature = "defmt", target_os = "none"), derive(defmt::Format))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FrameLocation {
/// The index where the frame starts
pub start: usize,
/// Number of bytes that belong to the frame
pub size: usize,
}

/// Decode TCP PDU frames from a buffer.
pub fn decode(
decoder_type: DecoderType,
Expand Down
10 changes: 6 additions & 4 deletions src/codec/tcp/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
use super::*;

/// Decode an TCP request.<'_>
pub fn decode_request(buf: &[u8]) -> Result<Option<RequestAdu<'_>>> {
pub fn decode_request(buf: &[u8]) -> Result<Option<(RequestAdu<'_>, FrameLocation)>> {
if buf.is_empty() {
return Ok(None);
}
let frame = decode(DecoderType::Request, buf)?;
let Some((decoded_frame, _frame_pos)) = frame else {
let Some((decoded_frame, frame_location)) = frame else {
return Ok(None);
};
let DecodedFrame {
Expand All @@ -27,7 +27,7 @@ pub fn decode_request(buf: &[u8]) -> Result<Option<RequestAdu<'_>>> {
// have already been verified at the TCP level.
let request = Request::try_from(pdu)
.map(RequestPdu)
.map(|pdu| Some(RequestAdu { hdr, pdu }));
.map(|pdu| Some((RequestAdu { hdr, pdu }, frame_location)));
#[cfg(feature = "log")]
if let Err(error) = request {
// Unrecoverable error
Expand Down Expand Up @@ -148,7 +148,9 @@ mod tests {
0xAB, // value
0xCD, // value
];
let adu = decode_request(buf).unwrap().unwrap();
let (adu, frame_location) = decode_request(buf).unwrap().unwrap();
assert_eq!(frame_location.start, 0);
assert_eq!(frame_location.size, 12);
let RequestAdu { hdr, pdu } = adu;
let RequestPdu(pdu) = pdu;
assert_eq!(hdr.transaction_id, 42);
Expand Down
25 changes: 25 additions & 0 deletions src/frame/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,24 @@ pub(crate) mod tcp;
pub use self::{coils::*, data::*};
use byteorder::{BigEndian, ByteOrder};

/// The location of all bytes that belong to the frame.
#[cfg_attr(all(feature = "defmt", target_os = "none"), derive(defmt::Format))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FrameLocation {
/// The index where the frame starts
pub start: usize,
/// Number of bytes that belong to the frame
pub size: usize,
}

impl FrameLocation {
/// One past the last byte of the frame.
#[must_use]
pub const fn end(&self) -> usize {
self.start + self.size
}
}

/// A Modbus function code.
///
/// It is represented by an unsigned 8 bit integer.
Expand Down Expand Up @@ -542,4 +560,11 @@ mod tests {
);
// TODO: extend test
}

#[test]
fn frame_location_end() {
assert_eq!(FrameLocation { start: 0, size: 3 }.end(), 3);
assert_eq!(FrameLocation { start: 2, size: 3 }.end(), 5);
assert_eq!(FrameLocation { start: 2, size: 0 }.end(), 2);
}
}