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:10 UTC

[plc4x] branch feature/plc4rs created (now 81d2342664)

This is an automated email from the ASF dual-hosted git repository.

jfeinauer pushed a change to branch feature/plc4rs
in repository https://gitbox.apache.org/repos/asf/plc4x.git


      at 81d2342664 Initial commit for plc4rust

This branch includes the following new commits:

     new 81d2342664 Initial commit for plc4rust

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[plc4x] 01/01: Initial commit for plc4rust

Posted by jf...@apache.org.
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);
+    }
+}