commit 86368253884120d5907edc11047352e136086db3 Author: Hlars Date: Sat Oct 16 17:17:09 2021 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0673509 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "s7_datatypes" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = { version = "1.0", features = [ "derive" ] } +anyhow = "1.0" +bit = "0.1" \ No newline at end of file diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..f7bffc7 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,13 @@ +use anyhow::{anyhow, Error}; + +pub fn serial_error(name: String, pos: u32) -> Error { + anyhow!( + "Cannot convert Serial data for type '{}' at position {}.", + name, + pos + ) +} + +pub fn s7_read_error(name: String, pos: u32) -> Error { + anyhow!("Cannot read Byte for type '{}' at position {}.", name, pos) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..03e458a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,11 @@ +mod errors; +pub mod sps_datatypes; +pub mod types; + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} diff --git a/src/sps_datatypes.rs b/src/sps_datatypes.rs new file mode 100644 index 0000000..2a52392 --- /dev/null +++ b/src/sps_datatypes.rs @@ -0,0 +1,105 @@ +use super::types::boolean::*; +use super::types::date_time::*; +use super::types::dint::*; +use super::types::int::*; +use super::types::real::*; +use super::types::string::*; +use super::types::time::*; +use super::types::udint::*; +use super::types::uint::*; +use serde::{Deserialize, Serialize}; + +use anyhow::Result; + +#[derive(Debug, Serialize, Copy, Clone)] +pub enum SPSDataTypes { + Boolean(BooleanType), + Int(IntType), + DInt(DIntType), + Real(RealType), + Time(TimeType), + DateTime(DateTimeType), + UDInt(UDIntType), + UInt(UIntType), + String(StringType), +} + +impl SPSDataTypes { + fn into(self) -> Box { + match self { + Self::Boolean(raw) => Box::new(raw), + Self::DInt(raw) => Box::new(raw), + Self::Int(raw) => Box::new(raw), + Self::Real(raw) => Box::new(raw), + Self::Time(raw) => Box::new(raw), + Self::DateTime(raw) => Box::new(raw), + Self::UDInt(raw) => Box::new(raw), + Self::UInt(raw) => Box::new(raw), + Self::String(raw) => Box::new(raw), + } + } + + pub fn get_end_byte(self) -> u32 { + self.into().get_end_byte() + } + + pub fn get_byte_positon(self) -> u32 { + self.into().get_byte_positon() + } + + pub fn get_length(self) -> u32 { + self.into().get_length() + } + + pub fn parse_serial_value(self, data: &[&str]) -> Result { + self.into().parse_serial(data) + } + + pub fn parse_s7_value(self, data: &[u8]) -> Result { + self.into().parse_s7(data) + } + + pub fn create_sql_data_type(self) -> SQLDataType { + self.into().create_sql_data_type() + } +} + +impl Into for SPSDataTypes { + fn into(self) -> UnparsedSPSDataType { + self.into().into_unparsed() + } +} + +pub struct UnparsedSPSDataType { + pub data_type: String, + pub data_byte: u32, + pub data_bit: Option, + pub data_length: Option, +} + +pub trait DataEvaluation { + fn into_unparsed(&self) -> UnparsedSPSDataType; + fn get_end_byte(&self) -> u32; + fn get_byte_positon(&self) -> u32; + fn get_length(&self) -> u32; + fn parse_serial(&self, data: &[&str]) -> Result; + fn parse_s7(&self, data: &[u8]) -> Result; + + fn sql_equivalent(&self) -> &str; + fn create_sql_data_type(&self) -> SQLDataType { + SQLDataType { + mysql_type: self.sql_equivalent().to_owned(), + } + } +} + +#[derive(Debug)] +pub struct SQLDataType { + pub mysql_type: String, +} + +#[derive(Debug, Serialize, Deserialize, Copy, Clone)] +pub struct BitPosition { + pub byte: u32, + pub bit: Option, +} diff --git a/src/types/boolean.rs b/src/types/boolean.rs new file mode 100644 index 0000000..ebb411b --- /dev/null +++ b/src/types/boolean.rs @@ -0,0 +1,95 @@ +use anyhow::{anyhow, Result}; +use bit::BitIndex; +use serde::{Deserialize, Serialize}; + +use super::super::errors::*; +use super::super::sps_datatypes::{BitPosition, DataEvaluation, UnparsedSPSDataType}; + +#[derive(Debug, Serialize, Deserialize, Copy, Clone)] +pub struct BooleanType { + position: BitPosition, +} + +impl BooleanType { + pub fn new(byte: u32, bit: Option) -> Self { + BooleanType { + position: BitPosition { byte, bit }, + } + } + fn into_string(self) -> String { + "boolean".to_string() + } +} + +impl DataEvaluation for BooleanType { + fn into_unparsed(&self) -> UnparsedSPSDataType { + UnparsedSPSDataType { + data_type: self.into_string(), + data_byte: self.position.byte, + data_bit: self.position.bit, + data_length: None, + } + } + fn get_end_byte(&self) -> u32 { + self.position.byte + } + fn get_byte_positon(&self) -> u32 { + self.position.byte + } + fn get_length(&self) -> u32 { + 1 + } + fn parse_serial(&self, data: &[&str]) -> Result { + Ok(data + .get((self.position.byte) as usize) + .ok_or_else(|| serial_error(self.into_string(), self.position.byte))? + .to_string()) + } + fn parse_s7(&self, data: &[u8]) -> Result { + let byte = data + .get((self.position.byte) as usize) + .ok_or_else(|| s7_read_error(self.into_string(), self.position.byte))?; + Ok(match byte.bit(self.position.bit.ok_or_else(|| { + anyhow!( + "Cannot read Bit {}.{}.", + self.position.byte, + self.position.bit.unwrap() + ) + })? as usize) + { + true => 1, + false => 0, + } + .to_string()) + } + + fn sql_equivalent(&self) -> &str { + "BOOLEAN DEFAULT false" + } +} + +#[test] +fn test() { + const BYTEPOS: u32 = 5; + const BITPOS: u32 = 4; + const VAL: bool = false; + + let test_item = BooleanType::new(BYTEPOS, Some(BITPOS)); + + let raw_data: [u8; 50] = [0; 50]; + let mut test_vec: Vec = raw_data.to_vec(); + test_vec[BYTEPOS as usize].set_bit(BITPOS as usize, VAL); + + let result = match test_item.parse_s7(&test_vec) { + Ok(res) => res, + Err(_) => "Error".to_string(), + }; + assert_eq!( + result, + match VAL { + true => 1, + false => 0, + } + .to_string() + ) +} diff --git a/src/types/date_time.rs b/src/types/date_time.rs new file mode 100644 index 0000000..a65a235 --- /dev/null +++ b/src/types/date_time.rs @@ -0,0 +1,125 @@ +use anyhow::{bail, Result}; +use serde::{Deserialize, Serialize}; + +use super::super::errors::*; +use super::super::sps_datatypes::{BitPosition, DataEvaluation, UnparsedSPSDataType}; + +#[derive(Debug, Serialize, Deserialize, Copy, Clone)] +pub struct DateTimeType { + length: u32, + position: BitPosition, +} +impl DateTimeType { + const LEN: usize = 8; + pub fn new(byte: u32, bit: Option) -> Self { + DateTimeType { + length: Self::LEN as u32, + position: BitPosition { byte, bit }, + } + } + fn into_string(self) -> String { + "datetime".to_string() + } + + fn assert_range_inclusive(&self, input: u8, min: u8, max: u8, field: &str) -> Result { + if input < min { + bail!( + "Value '{}' is lower than the minimum '{}' allowed for {}.", + input, + min, + field + ) + } + if input > max { + bail!( + "Value '{}' is higher than the maximum '{}' allowed for {}.", + input, + max, + field + ) + } + Ok(input) + } + + fn parse(&self, data: &[u8]) -> Result { + let year: Result = match data[0] { + e if e < 90 => Ok(e as u32 + 2000), + e if e < 100 => Ok(e as u32 + 1900), + e => bail!( + "Value '{}' is higher than the maximum '99' of S7 date and time representation.", + e + ), + }; + let month = self.assert_range_inclusive(data[1], 1, 12, "month")?; + let day = self.assert_range_inclusive(data[2], 1, 31, "day of month")?; + let hour = self.assert_range_inclusive(data[3], 0, 23, "hour")?; + let minute = self.assert_range_inclusive(data[4], 0, 59, "minute")?; + let second = self.assert_range_inclusive(data[5], 0, 59, "second")?; + let _sec = self.assert_range_inclusive(data[6], 0, 99, "first two millisecond digits")?; + let _msec = self.assert_range_inclusive(data[7] >> 4, 0, 9, "third millisecond digit")?; + let _day_of_week = + self.assert_range_inclusive(data[7] & 0b00001111, 1, 7, "day of week")?; + + Ok(format!( + "{}-{:02}-{:02} {:02}:{:02}:{:02}", + year?, month, day, hour, minute, second + )) + } +} +impl DataEvaluation for DateTimeType { + fn into_unparsed(&self) -> UnparsedSPSDataType { + UnparsedSPSDataType { + data_type: self.into_string(), + data_byte: self.position.byte, + data_bit: self.position.bit, + data_length: None, + } + } + fn get_end_byte(&self) -> u32 { + self.position.byte + self.length + } + fn get_byte_positon(&self) -> u32 { + self.position.byte + } + fn get_length(&self) -> u32 { + self.length + } + fn parse_serial(&self, data: &[&str]) -> Result { + Ok(data + .get((self.position.byte) as usize) + .ok_or_else(|| serial_error(self.into_string(), self.position.byte))? + .to_string()) + } + fn parse_s7(&self, data: &[u8]) -> Result { + let bytes = data + .get((self.position.byte) as usize..(self.position.byte + self.length) as usize) + .ok_or_else(|| s7_read_error(self.into_string(), self.position.byte))?; + self.parse(&bytes.to_vec()) + } + + fn sql_equivalent(&self) -> &str { + r"DATETIME DEFAULT NULL" + } +} + +#[test] +fn test() { + const DATEPOS: u32 = 5; + const LEN: u32 = 8; + const VAL: &str = "1993-12-25 08:12:34"; + + let test_item = DateTimeType::new(DATEPOS, None); + let raw_data: [u8; 50] = [255; 50]; + let mut test_vec: Vec = raw_data.to_vec(); + test_vec.splice( + DATEPOS as usize..DATEPOS as usize + LEN as usize, + [93, 12, 25, 8, 12, 34, 56, 7 << 4 | 7], + ); + + let result = match test_item.parse_s7(&test_vec) { + Ok(res) => res, + Err(_) => "Error".to_string(), + }; + + assert_eq!(result, VAL.to_string()) +} diff --git a/src/types/dint.rs b/src/types/dint.rs new file mode 100644 index 0000000..1819208 --- /dev/null +++ b/src/types/dint.rs @@ -0,0 +1,85 @@ +use anyhow::Result; +use serde::{Deserialize, Serialize}; + +use super::super::errors::*; +use super::super::sps_datatypes::{BitPosition, DataEvaluation, UnparsedSPSDataType}; + +#[derive(Debug, Serialize, Deserialize, Copy, Clone)] +pub struct DIntType { + length: u32, + position: BitPosition, +} + +impl DIntType { + const LEN: usize = 4; + + pub fn new(byte: u32, bit: Option) -> Self { + DIntType { + length: Self::LEN as u32, + position: BitPosition { byte, bit }, + } + } + + fn into_string(self) -> String { + "dint".to_string() + } +} + +impl DataEvaluation for DIntType { + fn into_unparsed(&self) -> UnparsedSPSDataType { + UnparsedSPSDataType { + data_type: self.into_string(), + data_byte: self.position.byte, + data_bit: self.position.bit, + data_length: None, + } + } + fn get_end_byte(&self) -> u32 { + self.position.byte + self.length + } + fn get_byte_positon(&self) -> u32 { + self.position.byte + } + fn get_length(&self) -> u32 { + self.length + } + fn parse_serial(&self, data: &[&str]) -> Result { + Ok(data + .get((self.position.byte) as usize) + .ok_or_else(|| serial_error(self.into_string(), self.position.byte))? + .to_string()) + } + fn parse_s7(&self, data: &[u8]) -> Result { + let bytes = data + .get((self.position.byte) as usize..self.position.byte as usize + Self::LEN) + .ok_or_else(|| s7_read_error(self.into_string(), self.position.byte))?; + let mut slice: [u8; Self::LEN] = Default::default(); + slice.copy_from_slice(bytes); + let parsed = i32::from_be_bytes(slice); + Ok(parsed.to_string()) + } + + fn sql_equivalent(&self) -> &str { + r"BIGINT DEFAULT 0" + } +} + +#[test] +fn test() { + const INTPOS: u32 = 5; + const INT: i32 = -2589090; + const LEN: usize = 4; + let test_item = DIntType::new(INTPOS, None); + let raw_data: [u8; 50] = [255; 50]; + let mut test_vec: Vec = raw_data.to_vec(); + test_vec.splice( + INTPOS as usize..INTPOS as usize + LEN, + INT.to_be_bytes().iter().cloned(), + ); + + let result = match test_item.parse_s7(&test_vec) { + Ok(res) => res, + Err(_) => "Error".to_string(), + }; + assert_eq!(result, INT.to_string()) +} diff --git a/src/types/int.rs b/src/types/int.rs new file mode 100644 index 0000000..0a32a22 --- /dev/null +++ b/src/types/int.rs @@ -0,0 +1,83 @@ +use anyhow::Result; +use serde::{Deserialize, Serialize}; + +use super::super::errors::*; +use super::super::sps_datatypes::{BitPosition, DataEvaluation, UnparsedSPSDataType}; + +#[derive(Debug, Serialize, Deserialize, Copy, Clone)] +pub struct IntType { + length: u32, + position: BitPosition, +} + +impl IntType { + const LEN: usize = 2; + pub fn new(byte: u32, bit: Option) -> Self { + IntType { + length: Self::LEN as u32, + position: BitPosition { byte, bit }, + } + } + fn into_string(self) -> String { + "int".to_string() + } +} + +impl DataEvaluation for IntType { + fn into_unparsed(&self) -> UnparsedSPSDataType { + UnparsedSPSDataType { + data_type: self.into_string(), + data_byte: self.position.byte, + data_bit: self.position.bit, + data_length: None, + } + } + fn get_end_byte(&self) -> u32 { + self.position.byte + self.length + } + fn get_byte_positon(&self) -> u32 { + self.position.byte + } + fn get_length(&self) -> u32 { + self.length + } + fn parse_serial(&self, data: &[&str]) -> Result { + Ok(data + .get((self.position.byte) as usize) + .ok_or_else(|| serial_error(self.into_string(), self.position.byte))? + .to_string()) + } + fn parse_s7(&self, data: &[u8]) -> Result { + let bytes = data + .get((self.position.byte) as usize..self.position.byte as usize + Self::LEN) + .ok_or_else(|| s7_read_error(self.into_string(), self.position.byte))?; + let mut slice: [u8; Self::LEN] = Default::default(); + slice.copy_from_slice(bytes); + let parsed = i16::from_be_bytes(slice); + Ok(parsed.to_string()) + } + + fn sql_equivalent(&self) -> &str { + r"INT DEFAULT 0" + } +} + +#[test] +fn test() { + const INTPOS: u32 = 3; + const INT: i16 = -12; + const LEN: usize = 2; + let test_item = IntType::new(INTPOS, None); + let raw_data: [u8; 50] = [255; 50]; + let mut test_vec: Vec = raw_data.to_vec(); + test_vec.splice( + INTPOS as usize..INTPOS as usize + LEN, + INT.to_be_bytes().iter().cloned(), + ); + + let result = match test_item.parse_s7(&test_vec) { + Ok(res) => res, + Err(_) => "Error".to_string(), + }; + assert_eq!(result, INT.to_string()) +} diff --git a/src/types/mod.rs b/src/types/mod.rs new file mode 100644 index 0000000..694ac88 --- /dev/null +++ b/src/types/mod.rs @@ -0,0 +1,9 @@ +pub mod boolean; +pub mod date_time; +pub mod dint; +pub mod int; +pub mod real; +pub mod string; +pub mod time; +pub mod udint; +pub mod uint; diff --git a/src/types/real.rs b/src/types/real.rs new file mode 100644 index 0000000..bf6d8bf --- /dev/null +++ b/src/types/real.rs @@ -0,0 +1,83 @@ +use anyhow::Result; +use serde::{Deserialize, Serialize}; + +use super::super::errors::*; +use super::super::sps_datatypes::{BitPosition, DataEvaluation, UnparsedSPSDataType}; + +#[derive(Debug, Serialize, Deserialize, Copy, Clone)] +pub struct RealType { + length: u32, + position: BitPosition, +} + +impl RealType { + const LEN: usize = 4; + pub fn new(byte: u32, bit: Option) -> Self { + RealType { + length: Self::LEN as u32, + position: BitPosition { byte, bit }, + } + } + fn into_string(self) -> String { + "real".to_string() + } +} + +impl DataEvaluation for RealType { + fn into_unparsed(&self) -> UnparsedSPSDataType { + UnparsedSPSDataType { + data_type: self.into_string(), + data_byte: self.position.byte, + data_bit: self.position.bit, + data_length: None, + } + } + fn get_end_byte(&self) -> u32 { + self.position.byte + self.length + } + fn get_byte_positon(&self) -> u32 { + self.position.byte + } + fn get_length(&self) -> u32 { + self.length + } + fn parse_serial(&self, data: &[&str]) -> Result { + Ok(data + .get((self.position.byte) as usize) + .ok_or_else(|| serial_error(self.into_string(), self.position.byte))? + .to_string()) + } + fn parse_s7(&self, data: &[u8]) -> Result { + let bytes = data + .get((self.position.byte) as usize..(self.position.byte + self.length) as usize) + .ok_or_else(|| s7_read_error(self.into_string(), self.position.byte))?; + let mut slice: [u8; 4] = Default::default(); + slice.copy_from_slice(bytes); + let parsed = f32::from_be_bytes(slice); + Ok(parsed.to_string()) + } + + fn sql_equivalent(&self) -> &str { + r"DOUBLE DEFAULT 0.00" + } +} + +#[test] +fn test() { + const INTPOS: u32 = 5; + const INT: f32 = 2.522529; + const LEN: usize = 4; + let test_item = RealType::new(INTPOS, None); + let raw_data: [u8; 50] = [255; 50]; + let mut test_vec: Vec = raw_data.to_vec(); + test_vec.splice( + INTPOS as usize..INTPOS as usize + LEN, + INT.to_be_bytes().iter().cloned(), + ); + + let result = match test_item.parse_s7(&test_vec) { + Ok(res) => res, + Err(_) => "Error".to_string(), + }; + assert_eq!(result, INT.to_string()) +} diff --git a/src/types/string.rs b/src/types/string.rs new file mode 100644 index 0000000..7f6a499 --- /dev/null +++ b/src/types/string.rs @@ -0,0 +1,92 @@ +use anyhow::{anyhow, Result}; +use serde::{Deserialize, Serialize}; + +use super::super::errors::*; +use super::super::sps_datatypes::{BitPosition, DataEvaluation, UnparsedSPSDataType}; + +#[derive(Debug, Serialize, Deserialize, Copy, Clone)] +pub struct StringType { + length: u32, + position: BitPosition, +} +impl StringType { + pub fn new(length: u32, byte: u32, bit: Option) -> Self { + StringType { + length, + position: BitPosition { byte, bit }, + } + } + fn into_string(self) -> String { + "string".to_string() + } +} +impl DataEvaluation for StringType { + fn into_unparsed(&self) -> UnparsedSPSDataType { + UnparsedSPSDataType { + data_type: self.into_string(), + data_byte: self.position.byte, + data_bit: self.position.bit, + data_length: Some(self.length), + } + } + fn get_end_byte(&self) -> u32 { + // first two bytes are header bytes + self.position.byte + self.length + 2 + } + fn get_byte_positon(&self) -> u32 { + self.position.byte + } + fn get_length(&self) -> u32 { + self.length + } + fn parse_serial(&self, data: &[&str]) -> Result { + Ok(data + .get((self.position.byte) as usize) + .ok_or_else(|| serial_error(self.into_string(), self.position.byte))? + .to_string()) + } + fn parse_s7(&self, data: &[u8]) -> Result { + // Headerbyte 1: Reservierte Stringlänge in Byte [0…254] + // Headerbyte 2: Anzahl der zu interpretierenden Zeichen im String ab Byte 3 + // Byte 3: 1. Nutzzeichen + let bytes = data + .get((self.position.byte) as usize..(self.position.byte + self.length) as usize) + .ok_or_else(|| s7_read_error(self.into_string(), self.position.byte))?; + match String::from_utf8(bytes[2..bytes[1] as usize + 2].to_vec()) { + Ok(string) => Ok(string), + Err(err) => Err(anyhow!( + "Could not convert data at byte {} with length {} into string: {}.", + self.position.byte, + self.length, + err + )), + } + } + + fn sql_equivalent(&self) -> &str { + r"VARCHAR(255)" + } +} + +#[test] +fn test() { + const BYTEPOS: u32 = 5; + const LEN: u32 = 20; + const VAL: &str = "ich bin ein pfau"; + + let test_item = StringType::new(LEN + 2, BYTEPOS, None); + let raw_data: [u8; 50] = [0; 50]; + let mut test_vec: Vec = raw_data.to_vec(); + test_vec[BYTEPOS as usize] = LEN as u8 + 2; + test_vec[BYTEPOS as usize + 1] = VAL.len() as u8; + test_vec.splice( + BYTEPOS as usize + 2..BYTEPOS as usize + LEN as usize, + VAL.as_bytes().iter().cloned(), + ); + + let result = match test_item.parse_s7(&test_vec) { + Ok(res) => res, + Err(_) => "Error".to_string(), + }; + assert_eq!(result, VAL.to_string()) +} diff --git a/src/types/time.rs b/src/types/time.rs new file mode 100644 index 0000000..d8dc4b7 --- /dev/null +++ b/src/types/time.rs @@ -0,0 +1,55 @@ +use anyhow::Result; +use serde::{Deserialize, Serialize}; + +use super::super::errors::*; +use super::super::sps_datatypes::{BitPosition, DataEvaluation, UnparsedSPSDataType}; + +#[derive(Debug, Serialize, Deserialize, Copy, Clone)] +pub struct TimeType { + length: u32, + position: BitPosition, +} +impl TimeType { + const LEN: usize = 4; + pub fn new(byte: u32, bit: Option) -> Self { + TimeType { + length: Self::LEN as u32, + position: BitPosition { byte, bit }, + } + } + fn into_string(self) -> String { + "time".to_string() + } +} +impl DataEvaluation for TimeType { + fn into_unparsed(&self) -> UnparsedSPSDataType { + UnparsedSPSDataType { + data_type: self.into_string(), + data_byte: self.position.byte, + data_bit: self.position.bit, + data_length: None, + } + } + fn get_end_byte(&self) -> u32 { + self.position.byte + self.length + } + fn get_byte_positon(&self) -> u32 { + self.position.byte + } + fn get_length(&self) -> u32 { + self.length + } + fn parse_serial(&self, data: &[&str]) -> Result { + Ok(data + .get((self.position.byte) as usize) + .ok_or_else(|| serial_error(self.into_string(), self.position.byte))? + .to_string()) + } + fn parse_s7(&self, _data: &[u8]) -> Result { + Ok("time".to_string()) + } + + fn sql_equivalent(&self) -> &str { + r"BIGINT UNSIGNED DEFAULT 0" + } +} diff --git a/src/types/udint.rs b/src/types/udint.rs new file mode 100644 index 0000000..f296675 --- /dev/null +++ b/src/types/udint.rs @@ -0,0 +1,81 @@ +use anyhow::Result; +use serde::{Deserialize, Serialize}; + +use super::super::errors::*; +use super::super::sps_datatypes::{BitPosition, DataEvaluation, UnparsedSPSDataType}; + +#[derive(Debug, Serialize, Deserialize, Copy, Clone)] +pub struct UDIntType { + length: u32, + position: BitPosition, +} +impl UDIntType { + const LEN: usize = 4; + pub fn new(byte: u32, bit: Option) -> Self { + UDIntType { + length: Self::LEN as u32, + position: BitPosition { byte, bit }, + } + } + fn into_string(self) -> String { + "udint".to_string() + } +} +impl DataEvaluation for UDIntType { + fn into_unparsed(&self) -> UnparsedSPSDataType { + UnparsedSPSDataType { + data_type: self.into_string(), + data_byte: self.position.byte, + data_bit: self.position.bit, + data_length: None, + } + } + fn get_end_byte(&self) -> u32 { + self.position.byte + self.length + } + fn get_byte_positon(&self) -> u32 { + self.position.byte + } + fn get_length(&self) -> u32 { + self.length + } + fn parse_serial(&self, data: &[&str]) -> Result { + Ok(data + .get((self.position.byte) as usize) + .ok_or_else(|| serial_error(self.into_string(), self.position.byte))? + .to_string()) + } + fn parse_s7(&self, data: &[u8]) -> Result { + let bytes = data + .get((self.position.byte) as usize..(self.position.byte + self.length) as usize) + .ok_or_else(|| s7_read_error(self.into_string(), self.position.byte))?; + let mut slice: [u8; Self::LEN] = Default::default(); + slice.copy_from_slice(bytes); + let parsed = u32::from_be_bytes(slice); + Ok(parsed.to_string()) + } + + fn sql_equivalent(&self) -> &str { + r"BIGINT UNSIGNED DEFAULT 0" + } +} + +#[test] +fn test() { + const INTPOS: u32 = 5; + const INT: u32 = 25890920; + const LEN: usize = 4; + let test_item = UDIntType::new(INTPOS, None); + let raw_data: [u8; 50] = [255; 50]; + let mut test_vec: Vec = raw_data.to_vec(); + test_vec.splice( + INTPOS as usize..INTPOS as usize + LEN, + INT.to_be_bytes().iter().cloned(), + ); + + let result = match test_item.parse_s7(&test_vec) { + Ok(res) => res, + Err(_) => "Error".to_string(), + }; + assert_eq!(result, INT.to_string()) +} diff --git a/src/types/uint.rs b/src/types/uint.rs new file mode 100644 index 0000000..8d04f6a --- /dev/null +++ b/src/types/uint.rs @@ -0,0 +1,81 @@ +use anyhow::Result; +use serde::{Deserialize, Serialize}; + +use super::super::errors::*; +use super::super::sps_datatypes::{BitPosition, DataEvaluation, UnparsedSPSDataType}; + +#[derive(Debug, Serialize, Deserialize, Copy, Clone)] +pub struct UIntType { + length: u32, + position: BitPosition, +} +impl UIntType { + const LEN: usize = 2; + pub fn new(byte: u32, bit: Option) -> Self { + UIntType { + length: Self::LEN as u32, + position: BitPosition { byte, bit }, + } + } + fn into_string(self) -> String { + "uint".to_string() + } +} +impl DataEvaluation for UIntType { + fn into_unparsed(&self) -> UnparsedSPSDataType { + UnparsedSPSDataType { + data_type: self.into_string(), + data_byte: self.position.byte, + data_bit: self.position.bit, + data_length: None, + } + } + fn get_end_byte(&self) -> u32 { + self.position.byte + self.length + } + fn get_byte_positon(&self) -> u32 { + self.position.byte + } + fn get_length(&self) -> u32 { + self.length + } + fn parse_serial(&self, data: &[&str]) -> Result { + Ok(data + .get((self.position.byte) as usize) + .ok_or_else(|| serial_error(self.into_string(), self.position.byte))? + .to_string()) + } + fn parse_s7(&self, data: &[u8]) -> Result { + let bytes = data + .get((self.position.byte) as usize..(self.position.byte + self.length) as usize) + .ok_or_else(|| s7_read_error(self.into_string(), self.position.byte))?; + let mut slice: [u8; Self::LEN] = Default::default(); + slice.copy_from_slice(bytes); + let parsed = u16::from_be_bytes(slice); + Ok(parsed.to_string()) + } + + fn sql_equivalent(&self) -> &str { + r"INT UNSIGNED DEFAULT 0" + } +} + +#[test] +fn test() { + const INTPOS: u32 = 15; + const INT: u16 = 8209; + const LEN: usize = 2; + let test_item = UIntType::new(INTPOS, None); + let raw_data: [u8; 50] = [255; 50]; + let mut test_vec: Vec = raw_data.to_vec(); + test_vec.splice( + INTPOS as usize..INTPOS as usize + LEN, + INT.to_be_bytes().iter().cloned(), + ); + + let result = match test_item.parse_s7(&test_vec) { + Ok(res) => res, + Err(_) => "Error".to_string(), + }; + assert_eq!(result, INT.to_string()) +}