You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by jf...@apache.org on 2022/06/02 08:42:11 UTC
[plc4x] 01/01: Initial commit for plc4rust
This is an automated email from the ASF dual-hosted git repository.
jfeinauer pushed a commit to branch feature/plc4rs
in repository https://gitbox.apache.org/repos/asf/plc4x.git
commit 81d2342664552c60390cd56268026d2e864cfe3e
Author: julian <j....@pragmaticminds.de>
AuthorDate: Thu Jun 2 10:30:26 2022 +0200
Initial commit for plc4rust
---
plc4rust/.gitignore | 3 +
plc4rust/Cargo.toml | 8 ++
plc4rust/README.md | 3 +
plc4rust/src/lib.rs | 28 ++++
plc4rust/src/modbus.rs | 296 +++++++++++++++++++++++++++++++++++++++++++
plc4rust/src/read_buffer.rs | 54 ++++++++
plc4rust/src/write_buffer.rs | 215 +++++++++++++++++++++++++++++++
7 files changed, 607 insertions(+)
diff --git a/plc4rust/.gitignore b/plc4rust/.gitignore
new file mode 100644
index 0000000000..9b98999fc3
--- /dev/null
+++ b/plc4rust/.gitignore
@@ -0,0 +1,3 @@
+.idea/
+/target
+Cargo.lock
diff --git a/plc4rust/Cargo.toml b/plc4rust/Cargo.toml
new file mode 100644
index 0000000000..e5725986a6
--- /dev/null
+++ b/plc4rust/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "plc4rs"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/plc4rust/README.md b/plc4rust/README.md
new file mode 100644
index 0000000000..023b42d311
--- /dev/null
+++ b/plc4rust/README.md
@@ -0,0 +1,3 @@
+# Prototype API for plc4rs
+
+Some testing / prototyping for potential plc4rust
diff --git a/plc4rust/src/lib.rs b/plc4rust/src/lib.rs
new file mode 100644
index 0000000000..5ad9c4c8cd
--- /dev/null
+++ b/plc4rust/src/lib.rs
@@ -0,0 +1,28 @@
+use std::io::{Read, Write};
+
+use crate::read_buffer::ReadBuffer;
+use crate::write_buffer::WriteBuffer;
+
+mod write_buffer;
+mod modbus;
+mod read_buffer;
+
+#[allow(dead_code)]
+pub enum Endianess {
+ LittleEndian,
+ BigEndian
+}
+
+trait Message {
+ type M;
+
+ fn serialize<T: Write>(&self, writer: &mut WriteBuffer<T>) -> Result<usize, std::io::Error>;
+ fn deserialize<T: Read>(reader: &mut ReadBuffer<T>) -> Result<Self::M, std::io::Error>;
+
+}
+
+#[cfg(test)]
+#[allow(unused_must_use)]
+mod tests {
+
+}
diff --git a/plc4rust/src/modbus.rs b/plc4rust/src/modbus.rs
new file mode 100644
index 0000000000..cf7cce0919
--- /dev/null
+++ b/plc4rust/src/modbus.rs
@@ -0,0 +1,296 @@
+use std::io::{Error, ErrorKind, Read, Write};
+use std::io::ErrorKind::InvalidInput;
+
+use crate::Message;
+use crate::read_buffer::ReadBuffer;
+use crate::write_buffer::WriteBuffer;
+
+// [type ModbusConstants
+// [const uint 16 modbusTcpDefaultPort 502]
+// ]
+const MODBUS_TCP_DEFAULT_PORT: u16 = 502;
+
+// [enum DriverType
+// ['0x01' MODBUS_TCP ]
+// ['0x02' MODBUS_RTU ]
+// ['0x03' MODBUS_ASCII]
+// ]
+#[derive(Copy, Clone, PartialEq, Debug)]
+#[allow(non_camel_case_types)]
+enum DriverType {
+ MODBUS_TCP,
+ MODBUS_RTU,
+ MODBUS_ASCII
+}
+
+impl TryFrom<u8> for DriverType {
+ type Error = ();
+
+ fn try_from(value: u8) -> Result<Self, Self::Error> {
+ match value {
+ 0x01 => {
+ Ok(DriverType::MODBUS_TCP)
+ },
+ 0x02 => {
+ Ok(DriverType::MODBUS_RTU)
+ },
+ 0x03 => {
+ Ok(DriverType::MODBUS_ASCII)
+ }
+ _ => {
+ Err(())
+ }
+ }
+ }
+}
+
+impl Into<u8> for DriverType {
+ fn into(self) -> u8 {
+ match self {
+ DriverType::MODBUS_TCP => {
+ 0x01
+ }
+ DriverType::MODBUS_RTU => {
+ 0x02
+ }
+ DriverType::MODBUS_ASCII => {
+ 0x03
+ }
+ }
+ }
+}
+
+impl Message for DriverType {
+ type M = DriverType;
+
+ fn serialize<T: Write>(&self, writer: &mut WriteBuffer<T>) -> Result<usize, Error> {
+ writer.write_u8((*self).into())
+ }
+
+ fn deserialize<T: Read>(reader: &mut ReadBuffer<T>) -> Result<Self::M, Error> {
+ let result = reader.read_u8()?;
+ match DriverType::try_from(result) {
+ Ok(result) => {
+ Ok(result)
+ }
+ Err(_) => {
+ Err(Error::new(ErrorKind::InvalidInput, format!("Cannot parse {}", result)))
+ }
+ }
+ }
+}
+
+// [discriminatedType ModbusADU(DriverType driverType, bit response) byteOrder='BIG_ENDIAN'
+// [typeSwitch driverType
+// ['MODBUS_TCP' ModbusTcpADU
+// // It is used for transaction pairing, the MODBUS server copies in the response the transaction
+// // identifier of the request.
+// [simple uint 16 transactionIdentifier]
+//
+// // It is used for intra-system multiplexing. The MODBUS protocol is identified by the value 0.
+// [const uint 16 protocolIdentifier 0x0000]
+// // The length field is a byte count of the following fields, including the Unit Identifier and
+// // data fields.
+// [implicit uint 16 length 'pdu.lengthInBytes + 1']
+//
+// // This field is used for intra-system routing purpose. It is typically used to communicate to
+// // a MODBUS+ or a MODBUS serial line slave through a gateway between an Ethernet TCP-IP network
+// // and a MODBUS serial line. This field is set by the MODBUS Client in the request and must be
+// // returned with the same value in the response by the server.
+// [simple uint 8 unitIdentifier]
+//
+// // The actual modbus payload
+// [simple ModbusPDU('response') pdu]
+// ]
+// ['MODBUS_RTU' ModbusRtuADU
+// [simple uint 8 address]
+// // The actual modbus payload
+// [simple ModbusPDU('response') pdu ]
+//
+// [checksum uint 16 crc 'STATIC_CALL("rtuCrcCheck", address, pdu)']
+// ]
+// ['MODBUS_ASCII' ModbusAsciiADU
+// [simple uint 8 address]
+//
+// // The actual modbus payload
+// [simple ModbusPDU('response') pdu ]
+// [checksum uint 8 crc 'STATIC_CALL("asciiLrcCheck", address, pdu)']
+// ]
+// ]
+// ]
+
+// // It is used for transaction pairing, the MODBUS server copies in the response the transaction
+// // identifier of the request.
+// [simple uint 16 transactionIdentifier]
+//
+// // It is used for intra-system multiplexing. The MODBUS protocol is identified by the value 0.
+// [const uint 16 protocolIdentifier 0x0000]
+// // The length field is a byte count of the following fields, including the Unit Identifier and
+// // data fields.
+// [implicit uint 16 length 'pdu.lengthInBytes + 1']
+//
+// // This field is used for intra-system routing purpose. It is typically used to communicate to
+// // a MODBUS+ or a MODBUS serial line slave through a gateway between an Ethernet TCP-IP network
+// // and a MODBUS serial line. This field is set by the MODBUS Client in the request and must be
+// // returned with the same value in the response by the server.
+// [simple uint 8 unitIdentifier]
+//
+// // The actual modbus payload
+// [simple ModbusPDU('response') pdu]
+struct ModbusTcpADU {
+ transaction_identifier: u16,
+ protocol_identifier: u16,
+
+}
+
+struct ModbusADU {
+ driver_type: DriverType,
+ bit_response: bool
+}
+
+impl Message for ModbusADU {
+ type M = ModbusADU;
+
+ fn serialize<T: Write>(&self, writer: &mut WriteBuffer<T>) -> Result<usize, Error> {
+ todo!()
+ }
+
+ fn deserialize<T: Read>(reader: &mut ReadBuffer<T>) -> Result<Self::M, Error> {
+ let driver_type = DriverType::deserialize(reader)?;
+ let bit_response = reader.read_bit()?;
+
+ // Now the switch begins
+ match (driver_type, bit_response) {
+ (_, _) => {
+ Err(Error::new(InvalidInput, format!("Unable to deserialize from {:?}, {:?}", driver_type, bit_response)))
+ }
+ }
+ }
+}
+
+// [type ModbusPDUReadFileRecordRequestItem
+// [simple uint 8 referenceType]
+// [simple uint 16 fileNumber ]
+// [simple uint 16 recordNumber ]
+// [simple uint 16 recordLength ]
+// ]
+#[derive(PartialEq,Eq,Clone,Debug)]
+struct ModbusPDUReadFileRecordRequestItem {
+ reference_type: u8,
+ file_number: u16,
+ record_number: u16,
+ record_length: u16,
+}
+
+impl Message for ModbusPDUReadFileRecordRequestItem {
+ type M = ModbusPDUReadFileRecordRequestItem;
+
+ fn serialize<T: Write>(&self, writer: &mut WriteBuffer<T>) -> Result<usize, std::io::Error> {
+ let mut size = writer.write_u8(self.reference_type)?;
+ size += writer.write_u16(self.file_number)?;
+ size += writer.write_u16(self.record_number)?;
+ size += writer.write_u16(self.record_length)?;
+ Ok(size)
+ }
+
+ fn deserialize<T: Read>(reader: &mut ReadBuffer<T>) -> Result<Self::M, std::io::Error> {
+ let reference_type = reader.read_u8()?;
+ let file_number = reader.read_u16()?;
+ let record_number = reader.read_u16()?;
+ let record_length = reader.read_u16()?;
+
+ Ok(Self::M {
+ reference_type,
+ file_number,
+ record_number,
+ record_length,
+ })
+ }
+}
+
+// [type ModbusPDUWriteFileRecordResponseItem
+// [simple uint 8 referenceType]
+// [simple uint 16 fileNumber]
+// [simple uint 16 recordNumber]
+// [implicit uint 16 recordLength 'COUNT(recordData) / 2']
+// [array byte recordData length 'recordLength']
+// ]
+#[derive(PartialEq,Eq,Clone,Debug)]
+struct ModbusPDUWriteFileRecordResponseItem {
+ reference_type: u8,
+ file_number: u16,
+ record_number: u16,
+ record_data: Vec<u8>,
+}
+
+impl ModbusPDUWriteFileRecordResponseItem {
+ fn record_length(&self) -> u16 {
+ (self.record_data.len() / 2) as u16
+ }
+}
+
+impl Message for ModbusPDUWriteFileRecordResponseItem {
+ type M = ModbusPDUWriteFileRecordResponseItem;
+
+ fn serialize<T: Write>(&self, writer: &mut WriteBuffer<T>) -> Result<usize, std::io::Error> {
+ let mut size = writer.write_u8(self.reference_type)?;
+ size += writer.write_u16(self.file_number)?;
+ size += writer.write_u16(self.record_number)?;
+ size += writer.write_u16(self.record_length())?;
+ size += writer.write_bytes(&self.record_data)?;
+ Ok(size)
+ }
+
+ fn deserialize<T: Read>(reader: &mut ReadBuffer<T>) -> Result<Self::M, std::io::Error> {
+ let reference_type = reader.read_u8()?;
+ let file_number = reader.read_u16()?;
+ let record_number = reader.read_u16()?;
+ let record_length = reader.read_u16()?;
+ let record_data = reader.read_bytes(2 * record_length as usize)?;
+
+ Ok(Self::M {
+ reference_type,
+ file_number,
+ record_number,
+ record_data,
+ })
+ }
+}
+
+#[cfg(test)]
+#[allow(unused_must_use)]
+mod test {
+ use crate::{Endianess, Message, ReadBuffer};
+ use crate::modbus::ModbusPDUWriteFileRecordResponseItem;
+ use crate::write_buffer::{WriteBuffer};
+
+ #[test]
+ fn ser_deser() {
+ let message = ModbusPDUWriteFileRecordResponseItem {
+ reference_type: 0,
+ file_number: 0,
+ record_number: 0,
+ record_data: vec![1, 2, 3, 4],
+ };
+
+ let bytes: Vec<u8> = vec![];
+
+ let mut writer = WriteBuffer::new(Endianess::BigEndian, bytes);
+
+ message.serialize(&mut writer);
+
+ let bytes = writer.writer.clone();
+
+ assert_eq!(vec![0, 0, 0, 0, 0, 0, 2, 1, 2, 3, 4], bytes);
+
+ let bytes = writer.writer.clone();
+ let mut reader = ReadBuffer::new(Endianess::BigEndian, &*bytes);
+
+ if let Ok(msg) = ModbusPDUWriteFileRecordResponseItem::deserialize(&mut reader) {
+ assert_eq!(message, msg);
+ } else {
+ assert!(false);
+ }
+
+ }
+}
diff --git a/plc4rust/src/read_buffer.rs b/plc4rust/src/read_buffer.rs
new file mode 100644
index 0000000000..22f038ec3a
--- /dev/null
+++ b/plc4rust/src/read_buffer.rs
@@ -0,0 +1,54 @@
+use std::io::Read;
+use crate::Endianess;
+
+#[allow(dead_code)]
+pub struct ReadBuffer<T: Read> {
+ position: u64,
+ endianness: Endianess,
+ reader: T,
+}
+
+impl<T: Read> ReadBuffer<T> {
+ pub(crate) fn new(endianess: Endianess, reader: T) -> ReadBuffer<T> {
+ ReadBuffer {
+ position: 0,
+ endianness: endianess,
+ reader: reader
+ }
+ }
+}
+
+impl<T: Read> ReadBuffer<T> {
+
+ pub(crate) fn read_bit(&self) -> Result<bool, std::io::Error> {
+ todo!()
+ }
+
+ pub(crate) fn read_u8(&mut self) -> Result<u8, std::io::Error> {
+ let mut byte = [0_u8; 1];
+ self.reader.read(&mut byte)?;
+
+ Ok(byte[0])
+ }
+
+ pub(crate) fn read_u16(&mut self) -> Result<u16, std::io::Error> {
+ let mut bytes = [0_u8; 2];
+ self.reader.read(&mut bytes)?;
+
+ Ok(match self.endianness {
+ Endianess::BigEndian => {
+ u16::from_be_bytes(bytes)
+ },
+ Endianess::LittleEndian => {
+ u16::from_le_bytes(bytes)
+ },
+ })
+ }
+
+ pub(crate) fn read_bytes(&mut self, length: usize) -> Result<Vec<u8>, std::io::Error> {
+ let mut bytes = vec![0_8; length];
+ self.reader.read(&mut bytes)?;
+
+ Ok(bytes)
+ }
+}
diff --git a/plc4rust/src/write_buffer.rs b/plc4rust/src/write_buffer.rs
new file mode 100644
index 0000000000..86e1ba19b3
--- /dev/null
+++ b/plc4rust/src/write_buffer.rs
@@ -0,0 +1,215 @@
+use std::io::Write;
+use std::marker::PhantomData;
+
+use crate::Endianess;
+
+pub struct WriteBuffer<T: Write> {
+ pub(crate) position: u64,
+ pub(crate) endianness: Endianess,
+ pub(crate) bit_writer: BitWriter<T>,
+ pub(crate) writer: T,
+}
+
+pub struct BitWriter<T: Write> {
+ pub(crate) position: u8,
+ pub(crate) value: u8,
+ pub(crate) phantom_data: PhantomData<T>
+}
+
+impl<T: Write> BitWriter<T> {
+
+ fn new() -> BitWriter<T> {
+ BitWriter {
+ position: 0,
+ value: 0,
+ phantom_data: PhantomData::default()
+ }
+ }
+
+ // Writes the given value as the given number of bits to the Bitwriter
+ // If it "overflows" the "full" byte is returned
+ fn write(&mut self, value: u64, bits: u8, writer: &mut dyn Write) -> std::io::Result<usize> {
+ let mut results: usize = 0;
+ // Write until the byte is full or bits are over
+ let mut bit_index: u8 = 0;
+ loop {
+ if self.position == 8 {
+ // Flush and then go to 0 again
+ results += self.flush(writer)?;
+ }
+ if bit_index == bits {
+ break;
+ }
+ let mask = (((value >> bit_index) & (0x01)) << self.position) as u8;
+ self.value = self.value | mask;
+
+ bit_index += 1;
+ self.position += 1;
+ }
+ Ok(results)
+ }
+
+ fn flush(&mut self, writer: &mut dyn Write) -> std::io::Result<usize> {
+ let result = writer.write(&[self.value]);
+ self.position = 0;
+ self.value = 0;
+ result
+ }
+
+}
+
+#[macro_export]
+macro_rules! write_int {
+ ($func:ident, $type:ty) => {
+ pub fn $func(&mut self, x: $type) -> std::io::Result<usize> {
+ let bytes = match self.endianness {
+ Endianess::LittleEndian => {
+ x.to_le_bytes()
+ }
+ Endianess::BigEndian => {
+ x.to_be_bytes()
+ }
+ };
+ self.write(&bytes)
+ }
+ };
+}
+
+#[allow(dead_code)]
+impl<T: Write> WriteBuffer<T> {
+
+ pub fn new(endianess: Endianess, writer: T) -> WriteBuffer<T> {
+ WriteBuffer {
+ position: 0,
+ endianness: endianess,
+ bit_writer: BitWriter::new(),
+ writer: writer
+ }
+ }
+
+ fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
+ let bytes_written = self.writer.write(bytes)?;
+ self.position = self.position + bytes_written as u64;
+ Ok(bytes_written)
+ }
+
+ pub fn write_u_n(&mut self, num_bits: u8, value: u64) -> std::io::Result<usize> {
+ self.bit_writer.write(value, num_bits, &mut self.writer)
+ }
+
+ pub fn write_u8(&mut self, x: u8) -> std::io::Result<usize> {
+ self.write(&[x])
+ }
+
+ write_int!(write_u16, u16);
+ write_int!(write_u32, u32);
+
+ write_int!(write_u64, u64);
+ write_int!(write_u128, u128);
+
+ write_int!(write_i8, i8);
+ write_int!(write_i16, i16);
+ write_int!(write_i32, i32);
+ write_int!(write_i64, i64);
+ write_int!(write_i128, i128);
+
+ write_int!(write_f32, f32);
+ write_int!(write_f64, f64);
+
+ pub fn write_bytes(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
+ self.write(bytes)
+ }
+}
+
+#[cfg(test)]
+#[allow(unused_must_use)]
+mod test {
+ use std::io::Write;
+ use std::marker::PhantomData;
+
+ use crate::Endianess;
+ use crate::write_buffer::{BitWriter, WriteBuffer};
+
+ #[test]
+ fn test_it() {
+ let mut target: u8 = 0x1;
+
+ let value: u8 = 0x03;
+ let mut position: u8 = 1;
+ let num_bits = 2;
+
+ for bit_index in 0..num_bits {
+ let mask = ((value >> bit_index) & (0x01)) << position;
+ target = target | mask;
+ position += 1;
+ }
+
+ assert_eq!(target, 0x07);
+ }
+
+ #[test]
+ fn test_write() {
+ let mut writer: BitWriter<Vec<u8>> = BitWriter {
+ position: 0,
+ value: 0,
+ phantom_data: PhantomData::default()
+ };
+
+ let buffer: Vec<u8> = vec![];
+
+ let mut noop_writer: Box<dyn Write> = Box::new(buffer);
+ writer.write(0x01, 1, &mut noop_writer);
+ assert_eq!(writer.value, 0x01);
+ assert_eq!(writer.position, 1);
+
+ writer.write(0x01, 1, &mut noop_writer);
+ assert_eq!(writer.value, 0x03);
+ assert_eq!(writer.position, 2);
+
+ writer.write(0x01, 1, &mut noop_writer);
+ assert_eq!(writer.value, 0x07);
+ assert_eq!(writer.position, 3);
+
+ writer.write(0x03, 2, &mut noop_writer);
+ assert_eq!(writer.value, 31);
+ assert_eq!(writer.position, 5);
+
+ // Now overflow
+ writer.write(0x00, 3, &mut noop_writer);
+ assert_eq!(writer.value, 0);
+ assert_eq!(writer.position, 0);
+ }
+
+ #[test]
+ fn test_write_byte() {
+ let mut writer: BitWriter<Vec<u8>> = BitWriter {
+ position: 0,
+ value: 0,
+ phantom_data: PhantomData::default()
+ };
+
+ let mut bytes: Vec<u8> = vec![];
+
+ // Now overflow
+ writer.write(0xFF, 8, &mut bytes);
+ assert_eq!(writer.value, 0);
+ assert_eq!(writer.position, 0);
+ assert_eq!(*bytes.get(0).unwrap(), 0xFF);
+ }
+
+ #[test]
+ fn write_bit_via_writer() {
+ let bytes: Vec<u8> = vec![];
+
+ let mut writer = WriteBuffer::new(Endianess::BigEndian, bytes);
+
+ &writer.write_u_n(9, 0xFFFF);
+ assert_eq!(writer.bit_writer.position, 1);
+ assert_eq!(writer.bit_writer.value, 0x01);
+
+ let bytes = writer.writer;
+
+ assert_eq!(*bytes.get(0).unwrap(), 0xFF);
+ assert_eq!(bytes.get(1), None);
+ }
+}