#![allow(clippy::needless_pass_by_value)]
use bytes::Bytes;
use rusty_modbus_codec::error::DecodeError;
use rusty_modbus_codec::response::{
EncapsulatedInterfaceResponse, ExceptionResponse, GetCommEventCounterResponse,
GetCommEventLogResponse, MaskWriteRegisterResponse, ReadExceptionStatusResponse,
ReadFifoQueueResponse, ReadFileRecordResponse, WriteFileRecordResponse,
WriteMultipleCoilsResponse, WriteMultipleRegistersResponse, WriteSingleCoilResponse,
WriteSingleRegisterResponse,
};
use rusty_modbus_types::{DiagnosticSubFunction, FunctionCode, MeiType};
fn pdu_data(pdu: &Bytes) -> Result<&[u8], DecodeError> {
if pdu.is_empty() {
return Err(DecodeError::Truncated {
expected: 1,
actual: 0,
});
}
Ok(&pdu[1..])
}
#[derive(Debug, Clone)]
pub struct OwnedReadCoilsResponse {
pub byte_count: u8,
pub coil_status: Bytes,
}
impl OwnedReadCoilsResponse {
pub fn from_pdu(pdu: Bytes) -> Result<Self, DecodeError> {
let data = pdu_data(&pdu)?;
if data.is_empty() {
return Err(DecodeError::Truncated {
expected: 1,
actual: 0,
});
}
let byte_count = data[0];
let payload = &data[1..];
if payload.len() != usize::from(byte_count) {
return Err(DecodeError::ByteCountMismatch {
declared: usize::from(byte_count),
actual: payload.len(),
});
}
let coil_status = pdu.slice(2..2 + usize::from(byte_count));
Ok(Self {
byte_count,
coil_status,
})
}
#[must_use]
pub fn coil(&self, index: usize) -> bool {
let byte_idx = index / 8;
let bit_idx = index % 8;
(self.coil_status[byte_idx] >> bit_idx) & 1 == 1
}
}
#[derive(Debug, Clone)]
pub struct OwnedReadDiscreteInputsResponse {
pub byte_count: u8,
pub input_status: Bytes,
}
impl OwnedReadDiscreteInputsResponse {
pub fn from_pdu(pdu: Bytes) -> Result<Self, DecodeError> {
let data = pdu_data(&pdu)?;
if data.is_empty() {
return Err(DecodeError::Truncated {
expected: 1,
actual: 0,
});
}
let byte_count = data[0];
let payload = &data[1..];
if payload.len() != usize::from(byte_count) {
return Err(DecodeError::ByteCountMismatch {
declared: usize::from(byte_count),
actual: payload.len(),
});
}
let input_status = pdu.slice(2..2 + usize::from(byte_count));
Ok(Self {
byte_count,
input_status,
})
}
#[must_use]
pub fn coil(&self, index: usize) -> bool {
let byte_idx = index / 8;
let bit_idx = index % 8;
(self.input_status[byte_idx] >> bit_idx) & 1 == 1
}
}
#[derive(Debug, Clone)]
pub struct OwnedReadHoldingRegistersResponse {
pub byte_count: u8,
pub register_data: Bytes,
}
impl OwnedReadHoldingRegistersResponse {
pub fn from_pdu(pdu: Bytes) -> Result<Self, DecodeError> {
let data = pdu_data(&pdu)?;
if data.is_empty() {
return Err(DecodeError::Truncated {
expected: 1,
actual: 0,
});
}
let byte_count = data[0];
let payload = &data[1..];
if payload.len() != usize::from(byte_count) {
return Err(DecodeError::ByteCountMismatch {
declared: usize::from(byte_count),
actual: payload.len(),
});
}
let register_data = pdu.slice(2..2 + usize::from(byte_count));
Ok(Self {
byte_count,
register_data,
})
}
#[must_use]
pub fn count(&self) -> usize {
self.register_data.len() / 2
}
#[must_use]
pub fn register(&self, index: usize) -> u16 {
let off = index * 2;
u16::from_be_bytes([self.register_data[off], self.register_data[off + 1]])
}
pub fn registers(&self) -> impl Iterator<Item = u16> + '_ {
self.register_data
.chunks_exact(2)
.map(|c| u16::from_be_bytes([c[0], c[1]]))
}
#[must_use]
pub fn raw(&self) -> &[u8] {
&self.register_data
}
}
#[derive(Debug, Clone)]
pub struct OwnedReadInputRegistersResponse {
pub byte_count: u8,
pub register_data: Bytes,
}
impl OwnedReadInputRegistersResponse {
pub fn from_pdu(pdu: Bytes) -> Result<Self, DecodeError> {
let data = pdu_data(&pdu)?;
if data.is_empty() {
return Err(DecodeError::Truncated {
expected: 1,
actual: 0,
});
}
let byte_count = data[0];
let payload = &data[1..];
if payload.len() != usize::from(byte_count) {
return Err(DecodeError::ByteCountMismatch {
declared: usize::from(byte_count),
actual: payload.len(),
});
}
let register_data = pdu.slice(2..2 + usize::from(byte_count));
Ok(Self {
byte_count,
register_data,
})
}
#[must_use]
pub fn count(&self) -> usize {
self.register_data.len() / 2
}
#[must_use]
pub fn register(&self, index: usize) -> u16 {
let off = index * 2;
u16::from_be_bytes([self.register_data[off], self.register_data[off + 1]])
}
pub fn registers(&self) -> impl Iterator<Item = u16> + '_ {
self.register_data
.chunks_exact(2)
.map(|c| u16::from_be_bytes([c[0], c[1]]))
}
#[must_use]
pub fn raw(&self) -> &[u8] {
&self.register_data
}
}
#[derive(Debug, Clone)]
pub struct OwnedReadWriteMultipleRegistersResponse {
pub byte_count: u8,
pub register_data: Bytes,
}
impl OwnedReadWriteMultipleRegistersResponse {
pub fn from_pdu(pdu: Bytes) -> Result<Self, DecodeError> {
let data = pdu_data(&pdu)?;
if data.is_empty() {
return Err(DecodeError::Truncated {
expected: 1,
actual: 0,
});
}
let byte_count = data[0];
let payload = &data[1..];
if payload.len() != usize::from(byte_count) {
return Err(DecodeError::ByteCountMismatch {
declared: usize::from(byte_count),
actual: payload.len(),
});
}
let register_data = pdu.slice(2..2 + usize::from(byte_count));
Ok(Self {
byte_count,
register_data,
})
}
#[must_use]
pub fn count(&self) -> usize {
self.register_data.len() / 2
}
#[must_use]
pub fn register(&self, index: usize) -> u16 {
let off = index * 2;
u16::from_be_bytes([self.register_data[off], self.register_data[off + 1]])
}
pub fn registers(&self) -> impl Iterator<Item = u16> + '_ {
self.register_data
.chunks_exact(2)
.map(|c| u16::from_be_bytes([c[0], c[1]]))
}
#[must_use]
pub fn raw(&self) -> &[u8] {
&self.register_data
}
}
#[derive(Debug, Clone)]
pub struct OwnedReadFifoQueueResponse {
pub byte_count: u16,
pub fifo_count: u16,
pub fifo_values: Bytes,
}
impl OwnedReadFifoQueueResponse {
pub fn from_pdu(pdu: Bytes) -> Result<Self, DecodeError> {
let data = pdu_data(&pdu)?;
let decoded = ReadFifoQueueResponse::decode(data)?;
let fifo_values = pdu.slice(5..5 + decoded.fifo_values.len());
Ok(Self {
byte_count: decoded.byte_count,
fifo_count: decoded.fifo_count,
fifo_values,
})
}
}
#[derive(Debug, Clone)]
pub struct OwnedReadFileRecordResponse {
pub byte_count: u8,
pub data: Bytes,
}
impl OwnedReadFileRecordResponse {
pub fn from_pdu(pdu: Bytes) -> Result<Self, DecodeError> {
let data = pdu_data(&pdu)?;
let decoded = ReadFileRecordResponse::decode(data)?;
let owned_data = pdu.slice(2..2 + decoded.data.len());
Ok(Self {
byte_count: decoded.byte_count,
data: owned_data,
})
}
}
#[derive(Debug, Clone)]
pub struct OwnedWriteFileRecordResponse {
pub byte_count: u8,
pub data: Bytes,
}
impl OwnedWriteFileRecordResponse {
pub fn from_pdu(pdu: Bytes) -> Result<Self, DecodeError> {
let data = pdu_data(&pdu)?;
let decoded = WriteFileRecordResponse::decode(data)?;
let owned_data = pdu.slice(2..2 + decoded.data.len());
Ok(Self {
byte_count: decoded.byte_count,
data: owned_data,
})
}
}
#[derive(Debug, Clone)]
pub struct OwnedDiagnosticsResponse {
pub sub_function: DiagnosticSubFunction,
pub data: Bytes,
}
impl OwnedDiagnosticsResponse {
pub fn from_pdu(pdu: Bytes) -> Result<Self, DecodeError> {
let data = pdu_data(&pdu)?;
if data.len() < 2 {
return Err(DecodeError::Truncated {
expected: 2,
actual: data.len(),
});
}
let raw_sub = u16::from_be_bytes([data[0], data[1]]);
let sub_function = DiagnosticSubFunction::from_raw(raw_sub)
.ok_or(DecodeError::UnknownDiagnosticSubFunction(raw_sub))?;
let payload = &data[2..];
if !payload.len().is_multiple_of(2) {
return Err(DecodeError::InvalidDiagnosticDataLength {
length: payload.len(),
});
}
let owned_data = pdu.slice(3..);
Ok(Self {
sub_function,
data: owned_data,
})
}
}
#[derive(Debug, Clone)]
pub struct OwnedGetCommEventLogResponse {
pub byte_count: u8,
pub status: u16,
pub event_count: u16,
pub message_count: u16,
pub events: Bytes,
}
impl OwnedGetCommEventLogResponse {
pub fn from_pdu(pdu: Bytes) -> Result<Self, DecodeError> {
let data = pdu_data(&pdu)?;
let decoded = GetCommEventLogResponse::decode(data)?;
let events = pdu.slice(8..8 + decoded.events.len());
Ok(Self {
byte_count: decoded.byte_count,
status: decoded.status,
event_count: decoded.event_count,
message_count: decoded.message_count,
events,
})
}
}
#[derive(Debug, Clone)]
pub struct OwnedReportServerIdResponse {
pub byte_count: u8,
pub data: Bytes,
}
impl OwnedReportServerIdResponse {
pub fn from_pdu(pdu: Bytes) -> Result<Self, DecodeError> {
let data = pdu_data(&pdu)?;
if data.is_empty() {
return Err(DecodeError::Truncated {
expected: 1,
actual: 0,
});
}
let byte_count = data[0];
let payload = &data[1..];
if payload.len() != usize::from(byte_count) {
return Err(DecodeError::ByteCountMismatch {
declared: usize::from(byte_count),
actual: payload.len(),
});
}
let owned_data = pdu.slice(2..2 + usize::from(byte_count));
Ok(Self {
byte_count,
data: owned_data,
})
}
}
#[derive(Debug, Clone)]
pub struct OwnedEncapsulatedInterfaceResponse {
pub mei_type: MeiType,
pub data: Bytes,
}
impl OwnedEncapsulatedInterfaceResponse {
pub fn from_pdu(pdu: Bytes) -> Result<Self, DecodeError> {
let data = pdu_data(&pdu)?;
let decoded = EncapsulatedInterfaceResponse::decode(data)?;
let owned_data = pdu.slice(2..);
Ok(Self {
mei_type: decoded.mei_type,
data: owned_data,
})
}
}
#[derive(Debug, Clone, Default)]
pub struct OwnedDeviceIdentification {
pub vendor_name: Option<String>,
pub product_code: Option<String>,
pub major_minor_revision: Option<String>,
}
#[derive(Debug)]
pub enum OwnedResponsePdu {
ReadCoils(OwnedReadCoilsResponse),
ReadDiscreteInputs(OwnedReadDiscreteInputsResponse),
ReadHoldingRegisters(OwnedReadHoldingRegistersResponse),
ReadInputRegisters(OwnedReadInputRegistersResponse),
WriteSingleCoil(WriteSingleCoilResponse),
WriteSingleRegister(WriteSingleRegisterResponse),
ReadExceptionStatus(ReadExceptionStatusResponse),
Diagnostics(OwnedDiagnosticsResponse),
GetCommEventCounter(GetCommEventCounterResponse),
GetCommEventLog(OwnedGetCommEventLogResponse),
WriteMultipleCoils(WriteMultipleCoilsResponse),
WriteMultipleRegisters(WriteMultipleRegistersResponse),
ReportServerId(OwnedReportServerIdResponse),
ReadFileRecord(OwnedReadFileRecordResponse),
WriteFileRecord(OwnedWriteFileRecordResponse),
MaskWriteRegister(MaskWriteRegisterResponse),
ReadWriteMultipleRegisters(OwnedReadWriteMultipleRegistersResponse),
ReadFifoQueue(OwnedReadFifoQueueResponse),
EncapsulatedInterface(OwnedEncapsulatedInterfaceResponse),
Custom(u8, Bytes),
Exception(ExceptionResponse),
}
impl OwnedResponsePdu {
pub fn from_pdu(pdu: Bytes) -> Result<Self, DecodeError> {
if pdu.is_empty() {
return Err(DecodeError::Truncated {
expected: 1,
actual: 0,
});
}
let fc_byte = pdu[0];
if FunctionCode::is_exception_response(fc_byte) {
let resp = ExceptionResponse::decode(fc_byte, &pdu[1..])?;
return Ok(Self::Exception(resp));
}
let fc = FunctionCode::from_raw(fc_byte).unwrap_or(FunctionCode::Custom(fc_byte));
let data = &pdu[1..];
match fc {
FunctionCode::ReadCoils => OwnedReadCoilsResponse::from_pdu(pdu).map(Self::ReadCoils),
FunctionCode::ReadDiscreteInputs => {
OwnedReadDiscreteInputsResponse::from_pdu(pdu).map(Self::ReadDiscreteInputs)
}
FunctionCode::ReadHoldingRegisters => {
OwnedReadHoldingRegistersResponse::from_pdu(pdu).map(Self::ReadHoldingRegisters)
}
FunctionCode::ReadInputRegisters => {
OwnedReadInputRegistersResponse::from_pdu(pdu).map(Self::ReadInputRegisters)
}
FunctionCode::WriteSingleCoil => {
WriteSingleCoilResponse::decode(data).map(Self::WriteSingleCoil)
}
FunctionCode::WriteSingleRegister => {
WriteSingleRegisterResponse::decode(data).map(Self::WriteSingleRegister)
}
FunctionCode::ReadExceptionStatus => {
ReadExceptionStatusResponse::decode(data).map(Self::ReadExceptionStatus)
}
FunctionCode::Diagnostics => {
OwnedDiagnosticsResponse::from_pdu(pdu).map(Self::Diagnostics)
}
FunctionCode::GetCommEventCounter => {
GetCommEventCounterResponse::decode(data).map(Self::GetCommEventCounter)
}
FunctionCode::GetCommEventLog => {
OwnedGetCommEventLogResponse::from_pdu(pdu).map(Self::GetCommEventLog)
}
FunctionCode::WriteMultipleCoils => {
WriteMultipleCoilsResponse::decode(data).map(Self::WriteMultipleCoils)
}
FunctionCode::WriteMultipleRegisters => {
WriteMultipleRegistersResponse::decode(data).map(Self::WriteMultipleRegisters)
}
FunctionCode::ReportServerId => {
OwnedReportServerIdResponse::from_pdu(pdu).map(Self::ReportServerId)
}
FunctionCode::ReadFileRecord => {
OwnedReadFileRecordResponse::from_pdu(pdu).map(Self::ReadFileRecord)
}
FunctionCode::WriteFileRecord => {
OwnedWriteFileRecordResponse::from_pdu(pdu).map(Self::WriteFileRecord)
}
FunctionCode::MaskWriteRegister => {
MaskWriteRegisterResponse::decode(data).map(Self::MaskWriteRegister)
}
FunctionCode::ReadWriteMultipleRegisters => {
OwnedReadWriteMultipleRegistersResponse::from_pdu(pdu)
.map(Self::ReadWriteMultipleRegisters)
}
FunctionCode::ReadFifoQueue => {
OwnedReadFifoQueueResponse::from_pdu(pdu).map(Self::ReadFifoQueue)
}
FunctionCode::EncapsulatedInterfaceTransport => {
OwnedEncapsulatedInterfaceResponse::from_pdu(pdu).map(Self::EncapsulatedInterface)
}
FunctionCode::Custom(fc) => Ok(Self::Custom(fc, pdu.slice(1..))),
}
}
#[must_use]
pub fn function_code(&self) -> u8 {
match self {
Self::ReadCoils(_) => FunctionCode::ReadCoils.code(),
Self::ReadDiscreteInputs(_) => FunctionCode::ReadDiscreteInputs.code(),
Self::ReadHoldingRegisters(_) => FunctionCode::ReadHoldingRegisters.code(),
Self::ReadInputRegisters(_) => FunctionCode::ReadInputRegisters.code(),
Self::WriteSingleCoil(_) => FunctionCode::WriteSingleCoil.code(),
Self::WriteSingleRegister(_) => FunctionCode::WriteSingleRegister.code(),
Self::ReadExceptionStatus(_) => FunctionCode::ReadExceptionStatus.code(),
Self::Diagnostics(_) => FunctionCode::Diagnostics.code(),
Self::GetCommEventCounter(_) => FunctionCode::GetCommEventCounter.code(),
Self::GetCommEventLog(_) => FunctionCode::GetCommEventLog.code(),
Self::WriteMultipleCoils(_) => FunctionCode::WriteMultipleCoils.code(),
Self::WriteMultipleRegisters(_) => FunctionCode::WriteMultipleRegisters.code(),
Self::ReportServerId(_) => FunctionCode::ReportServerId.code(),
Self::ReadFileRecord(_) => FunctionCode::ReadFileRecord.code(),
Self::WriteFileRecord(_) => FunctionCode::WriteFileRecord.code(),
Self::MaskWriteRegister(_) => FunctionCode::MaskWriteRegister.code(),
Self::ReadWriteMultipleRegisters(_) => FunctionCode::ReadWriteMultipleRegisters.code(),
Self::ReadFifoQueue(_) => FunctionCode::ReadFifoQueue.code(),
Self::EncapsulatedInterface(_) => FunctionCode::EncapsulatedInterfaceTransport.code(),
Self::Custom(fc, _) => *fc,
Self::Exception(e) => e.function_code.exception_code(),
}
}
}