use bytes::{Buf, BufMut, Bytes, BytesMut};
use crate::proto::error::ProtoError;
use crate::proto::s7::header::{Area, TransportSize};
pub const FUNC_READ_VAR: u8 = 0x04;
pub const ITEM_SPEC: u8 = 0x12;
pub const ITEM_LEN: u8 = 0x0A;
pub const ADDR_ANY: u8 = 0x10;
#[derive(Debug, Clone)]
pub struct AddressItem {
pub area: Area,
pub db_number: u16,
pub start: u32, pub bit_offset: u8,
pub length: u16, pub transport: TransportSize,
}
#[derive(Debug, Clone)]
pub struct ReadVarRequest {
pub items: Vec<AddressItem>,
}
#[derive(Debug, Clone)]
pub struct DataItem {
pub return_code: u8,
pub data: Bytes,
}
#[derive(Debug, Clone)]
pub struct ReadVarResponse {
pub items: Vec<DataItem>,
}
impl ReadVarRequest {
pub fn encode(&self, buf: &mut BytesMut) {
buf.put_u8(FUNC_READ_VAR);
buf.put_u8(self.items.len() as u8);
for item in &self.items {
buf.put_u8(ITEM_SPEC);
buf.put_u8(ITEM_LEN);
buf.put_u8(ADDR_ANY);
buf.put_u8(item.transport as u8);
buf.put_u16(item.length);
buf.put_u16(item.db_number);
buf.put_u8(item.area as u8);
let addr_bits = (item.start * 8) | (item.bit_offset as u32);
buf.put_u8(((addr_bits >> 16) & 0xFF) as u8);
buf.put_u8(((addr_bits >> 8) & 0xFF) as u8);
buf.put_u8((addr_bits & 0xFF) as u8);
}
}
pub fn decode(buf: &mut Bytes) -> Result<Self, ProtoError> {
if buf.len() < 2 {
return Err(ProtoError::BufferTooShort {
need: 2,
have: buf.len(),
});
}
let func = buf.get_u8();
if func != FUNC_READ_VAR {
return Err(ProtoError::UnsupportedFunction(func));
}
let count = buf.get_u8() as usize;
let mut items = Vec::with_capacity(count);
for _ in 0..count {
if buf.len() < 12 {
return Err(ProtoError::BufferTooShort {
need: 12,
have: buf.len(),
});
}
let spec = buf.get_u8();
if spec != ITEM_SPEC {
return Err(ProtoError::InvalidMagic {
expected: ITEM_SPEC,
got: spec,
});
}
buf.get_u8(); let addr_type = buf.get_u8();
if addr_type != ADDR_ANY {
return Err(ProtoError::InvalidMagic {
expected: ADDR_ANY,
got: addr_type,
});
}
let transport = TransportSize::try_from(buf.get_u8())?;
let length = buf.get_u16();
let db_number = buf.get_u16();
let area = Area::try_from(buf.get_u8())?;
let b0 = buf.get_u8() as u32;
let b1 = buf.get_u8() as u32;
let b2 = buf.get_u8() as u32;
let addr_bits = (b0 << 16) | (b1 << 8) | b2;
let start = addr_bits >> 3;
let bit_offset = (addr_bits & 0x07) as u8;
items.push(AddressItem {
area,
db_number,
start,
bit_offset,
length,
transport,
});
}
Ok(ReadVarRequest { items })
}
}
impl ReadVarResponse {
pub fn encode(&self, buf: &mut BytesMut) {
for item in &self.items {
buf.put_u8(item.return_code);
buf.put_u8(0x04); let bit_len = (item.data.len() * 8) as u16;
buf.put_u16(bit_len);
buf.put_slice(&item.data);
if !item.data.len().is_multiple_of(2) {
buf.put_u8(0x00); }
}
}
pub fn decode(buf: &mut Bytes, item_count: usize) -> Result<Self, ProtoError> {
let mut items = Vec::with_capacity(item_count);
for _ in 0..item_count {
if buf.len() < 4 {
return Err(ProtoError::BufferTooShort {
need: 4,
have: buf.len(),
});
}
let return_code = buf.get_u8();
let _transport = buf.get_u8();
let bit_len = buf.get_u16() as usize;
let byte_len = bit_len.div_ceil(8);
if buf.len() < byte_len {
return Err(ProtoError::BufferTooShort {
need: byte_len,
have: buf.len(),
});
}
let data = buf.split_to(byte_len);
if !byte_len.is_multiple_of(2) && buf.has_remaining() {
buf.advance(1);
}
items.push(DataItem { return_code, data });
}
Ok(ReadVarResponse { items })
}
}
#[cfg(test)]
mod tests {
use super::*;
use bytes::{Bytes, BytesMut};
use crate::proto::s7::header::{Area, TransportSize};
fn make_item(db: u16, start: u32, len: u16) -> AddressItem {
AddressItem {
area: Area::DataBlock,
db_number: db,
start,
bit_offset: 0,
length: len,
transport: TransportSize::Byte,
}
}
#[test]
fn read_var_request_roundtrip() {
let req = ReadVarRequest {
items: vec![make_item(1, 0, 4)],
};
let mut buf = BytesMut::new();
req.encode(&mut buf);
let mut b = buf.freeze();
let decoded = ReadVarRequest::decode(&mut b).unwrap();
assert_eq!(decoded.items.len(), 1);
assert_eq!(decoded.items[0].db_number, 1);
assert_eq!(decoded.items[0].start, 0);
assert_eq!(decoded.items[0].length, 4);
}
#[test]
fn read_var_request_multi_item() {
let req = ReadVarRequest {
items: vec![make_item(1, 0, 2), make_item(2, 10, 4)],
};
let mut buf = BytesMut::new();
req.encode(&mut buf);
let mut b = buf.freeze();
let decoded = ReadVarRequest::decode(&mut b).unwrap();
assert_eq!(decoded.items.len(), 2);
assert_eq!(decoded.items[1].db_number, 2);
assert_eq!(decoded.items[1].start, 10);
}
#[test]
fn read_var_request_byte_address_encoding() {
let req = ReadVarRequest {
items: vec![make_item(1, 4, 1)],
};
let mut buf = BytesMut::new();
req.encode(&mut buf);
assert_eq!(buf[9 + 2], 0x00); assert_eq!(buf[10 + 2], 0x00); assert_eq!(buf[11 + 2], 0x20); }
#[test]
fn read_var_response_decode_ok() {
let raw: &[u8] = &[0xFF, 0x04, 0x00, 0x10, 0xDE, 0xAD];
let mut b = Bytes::copy_from_slice(raw);
let resp = ReadVarResponse::decode(&mut b, 1).unwrap();
assert_eq!(resp.items.len(), 1);
assert_eq!(resp.items[0].return_code, 0xFF);
assert_eq!(resp.items[0].data.as_ref(), &[0xDE, 0xAD]);
}
#[test]
fn read_var_response_decode_odd_length_padded() {
let raw: &[u8] = &[
0xFF, 0x02, 0x00, 0x08, 0xAB, 0x00, 0xFF, 0x02, 0x00, 0x08, 0xCD, 0x00,
];
let mut b = Bytes::copy_from_slice(raw);
let resp = ReadVarResponse::decode(&mut b, 2).unwrap();
assert_eq!(resp.items[0].data.as_ref(), &[0xAB]);
assert_eq!(resp.items[1].data.as_ref(), &[0xCD]);
}
#[test]
fn read_var_response_encode_even_length() {
let resp = ReadVarResponse {
items: vec![DataItem {
return_code: 0xFF,
data: Bytes::copy_from_slice(&[0xDE, 0xAD]),
}],
};
let mut buf = BytesMut::new();
resp.encode(&mut buf);
let mut b = buf.freeze();
let decoded = ReadVarResponse::decode(&mut b, 1).unwrap();
assert_eq!(decoded.items[0].return_code, 0xFF);
assert_eq!(decoded.items[0].data.as_ref(), &[0xDE, 0xAD]);
}
#[test]
fn read_var_response_encode_odd_length_padded() {
let resp = ReadVarResponse {
items: vec![DataItem {
return_code: 0xFF,
data: Bytes::copy_from_slice(&[0xAB]),
}],
};
let mut buf = BytesMut::new();
resp.encode(&mut buf);
assert_eq!(buf.len(), 6);
assert_eq!(buf[5], 0x00); }
#[test]
fn read_var_request_truncated_returns_err() {
let mut b = Bytes::from_static(b"\x04");
assert!(ReadVarRequest::decode(&mut b).is_err());
}
#[test]
fn read_var_request_wrong_func_returns_err() {
let raw: &[u8] = &[0x05, 0x01]; let mut b = Bytes::copy_from_slice(raw);
assert!(ReadVarRequest::decode(&mut b).is_err());
}
#[test]
fn read_var_request_wrong_item_spec_returns_err() {
let mut raw = vec![0x04u8, 0x01, 0xFF];
raw.extend_from_slice(&[
0x0A, 0x10, 0x04, 0x00, 0x04, 0x00, 0x01, 0x84, 0x00, 0x00, 0x00,
]);
let mut b = Bytes::copy_from_slice(&raw);
assert!(ReadVarRequest::decode(&mut b).is_err());
}
}