use scursor::WriteError;
#[derive(Clone, Copy, Debug)]
pub struct Shutdown;
impl std::error::Error for Shutdown {}
impl std::fmt::Display for Shutdown {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "task shutdown")
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RequestError {
Io(::std::io::ErrorKind),
Exception(crate::exception::ExceptionCode),
BadRequest(InvalidRequest),
BadFrame(FrameParseError),
BadResponse(AduParseError),
Internal(InternalError),
ResponseTimeout,
NoConnection,
Shutdown,
}
impl std::error::Error for RequestError {}
impl std::fmt::Display for RequestError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match self {
RequestError::Io(kind) => std::io::Error::from(*kind).fmt(f),
RequestError::Exception(err) => err.fmt(f),
RequestError::BadRequest(err) => err.fmt(f),
RequestError::BadFrame(err) => err.fmt(f),
RequestError::BadResponse(err) => err.fmt(f),
RequestError::Internal(err) => err.fmt(f),
RequestError::ResponseTimeout => f.write_str("response timeout"),
RequestError::NoConnection => f.write_str("no connection to server"),
RequestError::Shutdown => f.write_str("channel shutdown"),
}
}
}
impl From<WriteError> for RequestError {
fn from(err: WriteError) -> Self {
match err {
WriteError::WriteOverflow { remaining, written } => {
RequestError::Internal(InternalError::InsufficientWriteSpace(written, remaining))
}
WriteError::NumericOverflow | WriteError::BadSeek { .. } => {
RequestError::Internal(InternalError::BadSeekOperation)
}
}
}
}
impl From<std::io::Error> for RequestError {
fn from(err: std::io::Error) -> Self {
RequestError::Io(err.kind())
}
}
impl From<InvalidRequest> for RequestError {
fn from(err: InvalidRequest) -> Self {
RequestError::BadRequest(err)
}
}
impl From<InternalError> for RequestError {
fn from(err: InternalError) -> Self {
RequestError::Internal(err)
}
}
impl From<AduParseError> for RequestError {
fn from(err: AduParseError) -> Self {
RequestError::BadResponse(err)
}
}
impl From<crate::exception::ExceptionCode> for RequestError {
fn from(err: crate::exception::ExceptionCode) -> Self {
RequestError::Exception(err)
}
}
impl From<FrameParseError> for RequestError {
fn from(err: FrameParseError) -> Self {
RequestError::BadFrame(err)
}
}
impl From<InvalidRange> for InvalidRequest {
fn from(x: InvalidRange) -> Self {
InvalidRequest::BadRange(x)
}
}
impl<T> From<tokio::sync::mpsc::error::SendError<T>> for RequestError {
fn from(_: tokio::sync::mpsc::error::SendError<T>) -> Self {
RequestError::Shutdown
}
}
impl<T> From<tokio::sync::mpsc::error::SendError<T>> for Shutdown {
fn from(_: tokio::sync::mpsc::error::SendError<T>) -> Self {
Shutdown
}
}
impl From<tokio::sync::oneshot::error::RecvError> for RequestError {
fn from(_: tokio::sync::oneshot::error::RecvError) -> Self {
RequestError::Shutdown
}
}
impl From<InvalidRange> for RequestError {
fn from(x: InvalidRange) -> Self {
RequestError::BadRequest(x.into())
}
}
impl From<scursor::ReadError> for RequestError {
fn from(_: scursor::ReadError) -> Self {
RequestError::BadResponse(AduParseError::InsufficientBytes)
}
}
impl From<scursor::TrailingBytes> for RequestError {
fn from(x: scursor::TrailingBytes) -> Self {
RequestError::BadResponse(AduParseError::TrailingBytes(x.count.get()))
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum InvalidRange {
CountOfZero,
AddressOverflow(u16, u16),
CountTooLargeForType(u16, u16), }
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum InternalError {
InsufficientWriteSpace(usize, usize), FrameTooBig(usize, usize), InsufficientBytesForRead(usize, usize), BadSeekOperation,
BadByteCount(usize),
}
impl std::error::Error for InternalError {}
impl std::fmt::Display for InternalError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match self {
InternalError::InsufficientWriteSpace(written, remaining) => write!(
f,
"attempted to write {written} bytes with {remaining} bytes remaining"
),
InternalError::FrameTooBig(size, max) => write!(
f,
"Frame length of {size} exceeds the maximum allowed length of {max}"
),
InternalError::InsufficientBytesForRead(requested, remaining) => write!(
f,
"attempted to read {requested} bytes with only {remaining} remaining"
),
InternalError::BadSeekOperation => {
f.write_str("Cursor seek operation exceeded the bounds of the underlying buffer")
}
InternalError::BadByteCount(size) => {
write!(f, "Byte count of in ADU {size} exceeds maximum size of u8")
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq)]
pub enum FrameParseError {
MbapLengthZero,
FrameLengthTooBig(usize, usize), UnknownProtocolId(u16),
UnknownFunctionCode(u8),
CrcValidationFailure(u16, u16), }
impl std::error::Error for FrameParseError {}
impl std::fmt::Display for FrameParseError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match self {
FrameParseError::MbapLengthZero => {
f.write_str("Received TCP frame with the length field set to zero")
}
FrameParseError::FrameLengthTooBig(size, max) => write!(
f,
"Received TCP frame with length ({size}) that exceeds max allowed size ({max})"
),
FrameParseError::UnknownProtocolId(id) => {
write!(f, "Received TCP frame with non-Modbus protocol id: {id}")
}
FrameParseError::UnknownFunctionCode(code) => {
write!(f, "Received unknown function code ({code:#04X}), cannot determine the length of the message")
}
FrameParseError::CrcValidationFailure(received, expected) => {
write!(
f,
"Received incorrect CRC value {received:#06X}, expected {expected:#06X}"
)
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq)]
pub enum AduParseError {
InsufficientBytes,
InsufficientBytesForByteCount(usize, usize), TrailingBytes(usize),
ReplyEchoMismatch,
UnknownResponseFunction(u8, u8, u8), UnknownCoilState(u16),
}
impl std::error::Error for AduParseError {}
impl std::fmt::Display for AduParseError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match self {
AduParseError::InsufficientBytes => f.write_str("response is too short to be valid"),
AduParseError::InsufficientBytesForByteCount(count, remaining) => write!(
f,
"byte count ({count}) doesn't match the actual number of bytes remaining ({remaining})"
),
AduParseError::TrailingBytes(remaining) => {
write!(f, "response contains {remaining} extra trailing bytes")
}
AduParseError::ReplyEchoMismatch => {
f.write_str("a parameter expected to be echoed in the reply did not match")
}
AduParseError::UnknownResponseFunction(actual, expected, error) => write!(
f,
"received unknown response function code: {actual}. Expected {expected} or {error}"
),
AduParseError::UnknownCoilState(value) => write!(
f,
"received coil state with unspecified value: 0x{value:04X}"
),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum InvalidRequest {
BadRange(InvalidRange),
CountTooBigForU16(usize),
CountTooBigForType(u16, u16),
}
impl std::error::Error for InvalidRequest {}
impl std::fmt::Display for InvalidRequest {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match self {
InvalidRequest::BadRange(err) => write!(f, "{err}"),
InvalidRequest::CountTooBigForU16(count) => write!(
f,
"The requested count of objects exceeds the maximum value of u16: {count}"
),
InvalidRequest::CountTooBigForType(count, max) => write!(
f,
"the request count of {count} exceeds maximum allowed count of {max} for this type"
),
}
}
}
impl std::fmt::Display for InvalidRange {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match self {
InvalidRange::CountOfZero => f.write_str("range contains count == 0"),
InvalidRange::AddressOverflow(start, count) => write!(
f,
"start == {start} and count = {count} would overflow u16 representation"
),
InvalidRange::CountTooLargeForType(x, y) => write!(
f,
"count of {x} is too large for the specified type (max == {y})"
),
}
}
}