use std::time::Duration;
#[derive(Debug, Clone)]
pub struct ConnectParams {
pub rack: u8,
pub slot: u8,
pub pdu_size: u16,
pub connect_timeout: Duration,
pub request_timeout: Duration,
}
impl Default for ConnectParams {
fn default() -> Self {
Self {
rack: 0,
slot: 1,
pdu_size: 480,
connect_timeout: Duration::from_secs(5),
request_timeout: Duration::from_secs(10),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PlcStatus {
Unknown = 0x00,
Stop = 0x04,
Run = 0x08,
}
#[derive(Debug, Clone)]
pub struct OrderCode {
pub code: String,
}
#[derive(Debug, Clone)]
pub struct CpuInfo {
pub module_type: String,
pub serial_number: String,
pub as_name: String,
pub copyright: String,
pub module_name: String,
}
#[derive(Debug, Clone)]
pub struct CpInfo {
pub max_pdu_len: u32,
pub max_connections: u32,
pub max_mpi_rate: u32,
pub max_bus_rate: u32,
}
#[derive(Debug, Clone)]
pub struct Protection {
pub scheme_szl: u16,
pub scheme_module: u16,
pub scheme_bus: u16,
pub level: u16,
pub password_set: bool,
}
pub fn encrypt_password(password: &str) -> [u8; 8] {
let bytes = password.as_bytes();
let mut pw = [0x20u8; 8]; let len = bytes.len().min(8);
pw[..len].copy_from_slice(&bytes[..len]);
let mut result = [0u8; 8];
for i in 0..8 {
result[i] = (pw[i] << 4) | (pw[i] >> 4);
result[i] ^= 0x55;
}
result
}
#[derive(Debug, Clone)]
pub struct ModuleEntry {
pub module_type: u16,
}
#[derive(Debug, Clone)]
pub struct BlockListEntry {
pub block_type: u16,
pub count: u16,
}
#[derive(Debug, Clone)]
pub struct BlockList {
pub total_count: u32,
pub entries: Vec<BlockListEntry>,
}
#[derive(Debug, Clone)]
pub struct BlockData {
pub block_type: u16,
pub block_number: u16,
pub format: u16,
pub total_length: u32,
pub flags: u16,
pub crc1: u16,
pub crc2: u16,
pub payload: Vec<u8>,
}
impl BlockData {
pub fn from_bytes(data: &[u8]) -> Option<Self> {
if data.len() < 20 {
return None;
}
let block_type = u16::from_be_bytes([data[0], data[1]]);
let block_number = u16::from_be_bytes([data[2], data[3]]);
let format = u16::from_be_bytes([data[4], data[5]]);
let total_length = u32::from_be_bytes([data[6], data[7], data[8], data[9]]);
let flags = u16::from_be_bytes([data[10], data[11]]);
let crc1 = u16::from_be_bytes([data[12], data[13]]);
let crc2 = u16::from_be_bytes([data[14], data[15]]);
let payload = data[20..].to_vec();
Some(BlockData {
block_type,
block_number,
format,
total_length,
flags,
crc1,
crc2,
payload,
})
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(20 + self.payload.len());
buf.extend_from_slice(&self.block_type.to_be_bytes());
buf.extend_from_slice(&self.block_number.to_be_bytes());
buf.extend_from_slice(&self.format.to_be_bytes());
buf.extend_from_slice(&self.total_length.to_be_bytes());
buf.extend_from_slice(&self.flags.to_be_bytes());
buf.extend_from_slice(&self.crc1.to_be_bytes());
buf.extend_from_slice(&self.crc2.to_be_bytes());
buf.extend_from_slice(&[0u8; 4]); buf.extend_from_slice(&self.payload);
buf
}
}
#[derive(Debug, Clone)]
pub struct BlockInfo {
pub block_type: u16,
pub block_number: u16,
pub language: u16,
pub flags: u16,
pub size: u16,
pub size_ram: u16,
pub mc7_size: u16,
pub local_data: u16,
pub checksum: u16,
pub version: u16,
pub author: String,
pub family: String,
pub header: String,
pub date: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum BlockType {
OB = 0x38,
DB = 0x41,
SDB = 0x42,
FC = 0x43,
SFC = 0x44,
FB = 0x45,
SFB = 0x46,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn connect_params_default() {
let p = ConnectParams::default();
assert_eq!(p.rack, 0);
assert_eq!(p.slot, 1);
assert_eq!(p.pdu_size, 480);
}
#[test]
fn block_data_roundtrip() {
let bd = super::BlockData {
block_type: 0x41, block_number: 1,
format: 0,
total_length: 24,
flags: 0,
crc1: 0x1234,
crc2: 0x5678,
payload: vec![0xDE, 0xAD],
};
let bytes = bd.to_bytes();
assert_eq!(bytes.len(), 22); let parsed = super::BlockData::from_bytes(&bytes).unwrap();
assert_eq!(parsed.block_type, 0x41);
assert_eq!(parsed.block_number, 1);
assert_eq!(parsed.payload, vec![0xDE, 0xAD]);
}
#[test]
fn block_data_short_input_returns_none() {
let result = super::BlockData::from_bytes(&[0u8; 10]);
assert!(result.is_none());
}
#[test]
fn encrypt_8_char_password() {
let result = super::encrypt_password("PASSWORD");
assert_eq!(result.len(), 8);
let result2 = super::encrypt_password("PASSWORD");
assert_eq!(result, result2);
}
#[test]
fn encrypt_short_password_padded() {
let result = super::encrypt_password("abc");
assert_eq!((0x61u8 << 4) | (0x61u8 >> 4), 0x16);
assert_eq!(0x16 ^ 0x55, 0x43);
assert_eq!(result[0], 0x43);
assert_eq!((0x20u8 << 4) | (0x20u8 >> 4), 0x02);
assert_eq!(0x02 ^ 0x55, 0x57);
assert_eq!(result[3], 0x57);
}
#[test]
fn encrypt_long_password_truncated() {
let result = super::encrypt_password("1234567890");
assert_eq!(result.len(), 8);
let result8 = super::encrypt_password("12345678");
assert_eq!(result, result8);
}
#[test]
fn block_type_discriminants() {
assert_eq!(BlockType::DB as u8, 0x41);
assert_eq!(BlockType::OB as u8, 0x38);
}
}