#![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "README.md" ) ) ]
#![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "CHANGELOG.md" ) ) ]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(test)]
#[macro_use]
extern crate lazy_static;
pub mod client;
pub mod consts;
pub mod server;
mod vector;
pub use vector::VectorTrait;
mod error;
pub use error::ErrorKind;
#[cfg(test)]
mod tests;
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
pub enum ModbusProto {
Rtu,
Ascii,
TcpUdp,
}
pub type ModbusFrameBuf = [u8; 256];
pub fn parse_ascii_frame(
data: &[u8],
data_len: usize,
frame_buf: &mut ModbusFrameBuf,
frame_pos: u8,
) -> Result<u8, ErrorKind> {
let mut data_pos = if data[0] == 58 { 1 } else { 0 };
let mut cpos = frame_pos;
while data_pos < data_len {
if cpos == 255 {
return Err(ErrorKind::OOB);
}
let ch = data[data_pos];
if ch == 10 || ch == 13 || ch == 0 {
break;
}
let c = chr_to_hex(data[data_pos])?;
data_pos += 1;
if data_pos >= data_len {
return Err(ErrorKind::OOB);
}
let c2 = chr_to_hex(data[data_pos])?;
frame_buf[cpos as usize] = c * 0x10 + c2;
data_pos += 1;
cpos += 1;
}
Ok(cpos - frame_pos)
}
pub fn generate_ascii_frame<V: VectorTrait<u8>>(
data: &[u8],
result: &mut V,
) -> Result<(), ErrorKind> {
result.clear();
result.push(58)?;
for d in data {
result.push(hex_to_chr(d >> 4))?;
result.push(hex_to_chr(*d & 0xf))?;
}
result.push(0x0D)?;
result.push(0x0A)
}
fn calc_crc16(frame: &[u8], data_length: u8) -> u16 {
let mut crc: u16 = 0xffff;
for i in frame.iter().take(data_length as usize) {
crc ^= u16::from(*i);
for _ in (0..8).rev() {
if (crc & 0x0001) == 0 {
crc >>= 1;
} else {
crc >>= 1;
crc ^= 0xA001;
}
}
}
crc
}
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_sign_loss)]
fn calc_lrc(frame: &[u8], data_length: u8) -> u8 {
let mut lrc: i32 = 0;
for i in 0..data_length {
lrc -= i32::from(frame[i as usize]);
}
lrc as u8
}
fn chr_to_hex(c: u8) -> Result<u8, ErrorKind> {
match c {
48..=57 => Ok(c - 48),
65..=70 => Ok(c - 55),
_ => Err(ErrorKind::FrameBroken),
}
}
#[inline]
fn hex_to_chr(h: u8) -> u8 {
if h < 10 {
h + 48
} else {
h + 55
}
}
pub fn guess_response_frame_len(buf: &[u8], proto: ModbusProto) -> Result<u8, ErrorKind> {
let mut b: ModbusFrameBuf = [0; 256];
let (f, multiplier, extra) = match proto {
ModbusProto::TcpUdp => {
let proto = u16::from_be_bytes([buf[2], buf[3]]);
if proto == 0 {
let len = u16::from_be_bytes([buf[4], buf[5]]) + 6;
if len > u16::from(u8::MAX) {
return Err(ErrorKind::FrameBroken);
}
#[allow(clippy::cast_possible_truncation)]
return Ok(len as u8);
}
return Err(ErrorKind::FrameBroken);
}
ModbusProto::Rtu => (buf, 1, 2), ModbusProto::Ascii => {
parse_ascii_frame(buf, buf.len(), &mut b, 0)?;
(&b[..], 2, 5) }
};
let func = f[1];
let len: usize = if func < 0x80 {
match func {
1 | 2 | 3 | 4 => (f[2] as usize + 3) * multiplier + extra,
5 | 6 | 15 | 16 => 6 * multiplier + extra,
_ => {
return Err(ErrorKind::FrameBroken);
}
}
} else {
3 * multiplier + extra
};
if len > u8::MAX as usize {
Err(ErrorKind::FrameBroken)
} else {
#[allow(clippy::cast_possible_truncation)]
Ok(len as u8)
}
}
pub fn guess_request_frame_len(frame: &[u8], proto: ModbusProto) -> Result<u8, ErrorKind> {
let mut buf: ModbusFrameBuf = [0; 256];
let (f, extra, multiplier) = match proto {
ModbusProto::Rtu => (frame, 2, 1),
ModbusProto::Ascii => {
parse_ascii_frame(frame, frame.len(), &mut buf, 0)?;
(&buf[..], 5, 2)
}
ModbusProto::TcpUdp => {
let proto = u16::from_be_bytes([frame[2], frame[3]]);
if proto == 0 {
let len = u16::from_be_bytes([frame[4], frame[5]]) + 6;
if len > u16::from(u8::MAX) {
return Err(ErrorKind::FrameBroken);
}
#[allow(clippy::cast_possible_truncation)]
return Ok(len as u8);
}
return Err(ErrorKind::FrameBroken);
}
};
let len: usize = match f[1] {
15 | 16 => (f[6] as usize + 7) * multiplier + extra,
_ => 6 * multiplier + extra,
};
if len > u8::MAX as usize {
Err(ErrorKind::FrameBroken)
} else {
#[allow(clippy::cast_possible_truncation)]
Ok(len as u8)
}
}