use bytes::{BufMut, Bytes, BytesMut};
use snap7_client::proto::s7::{
header::{PduType, S7Header},
read_var::{DataItem, ReadVarRequest, ReadVarResponse, FUNC_READ_VAR},
write_var::{WriteVarRequest, FUNC_WRITE_VAR},
};
use tokio::io::{AsyncRead, AsyncWrite};
use crate::{
error::Result,
handshake::{recv_cotp_data, send_cotp_data},
store::DataStore,
};
pub async fn dispatch_loop<T>(mut transport: T, _pdu_size: u16, store: DataStore) -> Result<()>
where
T: AsyncRead + AsyncWrite + Unpin,
{
loop {
let mut payload = match recv_cotp_data(&mut transport).await {
Ok(p) => p,
Err(_) => return Ok(()),
};
let header = match S7Header::decode(&mut payload) {
Ok(h) => h,
Err(_) => {
send_error_response(&mut transport, 0, 0x81, 0x04).await?;
continue;
}
};
if payload.is_empty() {
send_error_response(&mut transport, header.pdu_ref, 0x81, 0x04).await?;
continue;
}
match header.pdu_type {
PduType::Job => {
let func = payload[0];
match func {
FUNC_READ_VAR => {
match handle_read_var(&mut payload, &store) {
Ok((item_count, response)) => {
send_ack_data(&mut transport, header.pdu_ref, FUNC_READ_VAR, item_count, response).await?;
}
Err(()) => send_error_response(&mut transport, header.pdu_ref, 0x81, 0x04).await?,
}
}
FUNC_WRITE_VAR => {
match handle_write_var(&mut payload, &store) {
Ok((item_count, response)) => {
send_ack_data(&mut transport, header.pdu_ref, FUNC_WRITE_VAR, item_count, response).await?;
}
Err(()) => send_error_response(&mut transport, header.pdu_ref, 0x81, 0x04).await?,
}
}
0x28 | 0x29 | 0x2A | 0x31 => {
let hdr = S7Header {
pdu_type: PduType::AckData,
reserved: 0,
pdu_ref: header.pdu_ref,
param_len: 2,
data_len: if func == 0x31 { 1 } else { 0 },
error_class: Some(0),
error_code: Some(0),
};
let mut buf = BytesMut::new();
hdr.encode(&mut buf);
buf.extend_from_slice(&[func, 0x00]);
if func == 0x31 {
buf.put_u8(0x08); }
send_cotp_data(&mut transport, buf.freeze()).await?;
}
0x11 | 0x12 => {
send_simple_ack(&mut transport, header.pdu_ref).await?;
}
_ => {
send_error_response(&mut transport, header.pdu_ref, 0x81, 0x04).await?;
}
}
}
PduType::UserData => {
if payload.len() >= 5 && payload[4] == 0x11 {
handle_user_data(&mut transport, header.pdu_ref, &payload, &store).await?;
} else {
send_simple_ack(&mut transport, header.pdu_ref).await?;
}
}
_ => {
send_error_response(&mut transport, header.pdu_ref, 0x81, 0x04).await?;
}
}
}
}
fn handle_read_var(payload: &mut Bytes, store: &DataStore) -> std::result::Result<(u8, Bytes), ()> {
let req = ReadVarRequest::decode(payload).map_err(|_| ())?;
let items: Vec<DataItem> = req
.items
.iter()
.map(|item| {
let area_byte = item.area as u8;
let data = store.read_area(area_byte, item.db_number, item.start, item.length as u32);
DataItem { return_code: 0xFF, data: Bytes::from(data) }
})
.collect();
let item_count = items.len() as u8;
let resp = ReadVarResponse { items };
let mut buf = BytesMut::new();
resp.encode(&mut buf);
Ok((item_count, buf.freeze()))
}
fn handle_write_var(payload: &mut Bytes, store: &DataStore) -> std::result::Result<(u8, Bytes), ()> {
let req = WriteVarRequest::decode(payload).map_err(|_| ())?;
for item in &req.items {
let area_byte = item.address.area as u8;
store.write_area(area_byte, item.address.db_number, item.address.start, &item.data);
}
let item_count = req.items.len() as u8;
let mut buf = BytesMut::new();
for _ in 0..item_count {
buf.put_u8(0xFF);
}
Ok((item_count, buf.freeze()))
}
async fn handle_user_data<T: AsyncWrite + Unpin>(
transport: &mut T,
pdu_ref: u16,
payload: &[u8],
store: &DataStore,
) -> Result<()> {
let tg = if payload.len() >= 6 { payload[5] } else { 0 };
let group = tg & 0x0F;
match group {
0x07 => handle_clock_user_data(transport, pdu_ref, payload, store).await,
_ => handle_szl_user_data(transport, pdu_ref, payload).await,
}
}
async fn handle_clock_user_data<T: AsyncWrite + Unpin>(
transport: &mut T,
pdu_ref: u16,
payload: &[u8],
store: &DataStore,
) -> Result<()> {
let subfn = if payload.len() >= 7 { payload[6] } else { 0 };
if subfn == 0x02 {
if payload.len() >= 20 {
let mut dt_bytes = [0u8; 8];
dt_bytes.copy_from_slice(&payload[12..20]);
store.set_clock(dt_bytes);
}
let header = S7Header {
pdu_type: PduType::AckData,
reserved: 0,
pdu_ref,
param_len: 0,
data_len: 0,
error_class: Some(0),
error_code: Some(0),
};
let mut buf = BytesMut::new();
header.encode(&mut buf);
return send_cotp_data(transport, buf.freeze()).await;
}
let clock = store.get_clock();
let mut buf = BytesMut::new();
let header = S7Header {
pdu_type: PduType::UserData,
reserved: 0,
pdu_ref,
param_len: 12,
data_len: 4,
error_class: None,
error_code: None,
};
header.encode(&mut buf);
buf.extend_from_slice(&[0x00, 0x01, 0x12, 0x08, 0x12, 0x87, 0x01, 0x00]);
buf.extend_from_slice(&clock[..4]);
buf.extend_from_slice(&clock[4..]);
send_cotp_data(transport, buf.freeze()).await
}
async fn handle_szl_user_data<T: AsyncWrite + Unpin>(
transport: &mut T,
pdu_ref: u16,
payload: &[u8],
) -> Result<()> {
let szl_id = if payload.len() >= 14 {
u16::from_be_bytes([payload[12], payload[13]])
} else {
0
};
let response_data = build_szl_response(szl_id);
let param_len = 12u16;
let data_len = response_data.len() as u16;
let header = S7Header {
pdu_type: PduType::AckData,
reserved: 0,
pdu_ref,
param_len,
data_len,
error_class: Some(0),
error_code: Some(0),
};
let mut buf = BytesMut::new();
header.encode(&mut buf);
if payload.len() >= 12 {
buf.extend_from_slice(&payload[..12]);
} else {
buf.resize(buf.len() + param_len as usize, 0);
}
buf.put_u8(0xFF);
buf.put_u8(0x04);
buf.put_u16(data_len);
buf.extend_from_slice(&response_data);
send_cotp_data(transport, buf.freeze()).await
}
fn szl_block(szl_id: u16, szl_index: u16, entry_len: u16, entries: &[u8]) -> Vec<u8> {
let entry_count = if entry_len > 0 { (entries.len() / entry_len as usize) as u16 } else { 0 };
let mut v = Vec::with_capacity(8 + entries.len());
v.extend_from_slice(&szl_id.to_be_bytes());
v.extend_from_slice(&szl_index.to_be_bytes());
v.extend_from_slice(&entry_len.to_be_bytes());
v.extend_from_slice(&entry_count.to_be_bytes());
v.extend_from_slice(entries);
v
}
fn build_szl_response(szl_id: u16) -> Vec<u8> {
match szl_id {
0x0011 => {
let mut entry = vec![0u8; 28];
entry[0] = 0x00; entry[1] = 0x01; let s = b"Simulated PLC "; entry[2..2 + s.len()].copy_from_slice(s);
entry[23] = 1; entry[24] = 0; entry[25] = 0;
szl_block(0x0011, 0x0000, 28, &entry)
}
0x0032 => {
let mut entry = vec![0u8; 16];
entry[0] = 3; entry[2] = 3; entry[4] = 3;
szl_block(0x0032, 0x0000, 16, &entry)
}
0x001C => {
const SLEN: usize = 32;
const ELEN: usize = 2 + SLEN;
let entry_len = ELEN as u16;
let make = |idx: u16, s: &[u8]| -> [u8; ELEN] {
let mut e = [b' '; ELEN];
e[0] = (idx >> 8) as u8;
e[1] = idx as u8;
let n = s.len().min(SLEN);
e[2..2 + n].copy_from_slice(&s[..n]);
e
};
let mut entries = Vec::with_capacity(7 * ELEN);
entries.extend_from_slice(&make(0x0001, b"SimPLC")); entries.extend_from_slice(&make(0x0002, b"CPU Simulated")); entries.extend_from_slice(&make(0x0003, b"SimPLC")); entries.extend_from_slice(&make(0x0004, b"(C) Simulated")); entries.extend_from_slice(&make(0x0005, b"SIM-0000000001")); entries.extend_from_slice(&make(0x0007, b"CPU Simulated")); entries.extend_from_slice(&make(0x0008, b"SimPLC")); szl_block(0x001C, 0x0000, entry_len, &entries)
}
0x0131 => {
let mut entry = vec![0u8; 14];
entry[0] = 0x00; entry[1] = 0x01; entry[2] = 0x01; entry[3] = 0xE0; entry[4] = 0x00; entry[5] = 0x20; entry[6] = 0x00; entry[7] = 0x02; entry[8] = 0xDC; entry[9] = 0x6C; entry[10] = 0x00; entry[11] = 0x00; entry[12] = 0x61; entry[13] = 0xA8; szl_block(0x0131, 0x0001, 14, &entry)
}
0x0424 => {
let mut data = vec![0u8; 12];
data[3] = 0x08; szl_block(0x0424, 0x0000, 12, &data)
}
_ => szl_block(szl_id, 0x0000, 0, &[]),
}
}
async fn send_simple_ack<T: AsyncWrite + Unpin>(transport: &mut T, pdu_ref: u16) -> Result<()> {
let header = S7Header {
pdu_type: PduType::AckData,
reserved: 0,
pdu_ref,
param_len: 0,
data_len: 0,
error_class: Some(0),
error_code: Some(0),
};
let mut buf = BytesMut::new();
header.encode(&mut buf);
send_cotp_data(transport, buf.freeze()).await
}
async fn send_ack_data<T: AsyncWrite + Unpin>(
transport: &mut T,
pdu_ref: u16,
func: u8,
item_count: u8,
data: Bytes,
) -> Result<()> {
let param: Bytes = Bytes::copy_from_slice(&[func, item_count]);
let header = S7Header {
pdu_type: PduType::AckData,
reserved: 0,
pdu_ref,
param_len: 2,
data_len: data.len() as u16,
error_class: Some(0),
error_code: Some(0),
};
let mut buf = BytesMut::new();
header.encode(&mut buf);
buf.extend_from_slice(¶m);
buf.extend_from_slice(&data);
send_cotp_data(transport, buf.freeze()).await
}
async fn send_error_response<T: AsyncWrite + Unpin>(
transport: &mut T,
pdu_ref: u16,
error_class: u8,
error_code: u8,
) -> Result<()> {
let header = S7Header {
pdu_type: PduType::AckData,
reserved: 0,
pdu_ref,
param_len: 0,
data_len: 0,
error_class: Some(error_class),
error_code: Some(error_code),
};
let mut buf = BytesMut::new();
header.encode(&mut buf);
send_cotp_data(transport, buf.freeze()).await
}