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);
+    }
+}