snap7-client 0.1.2

Async Rust client for Siemens S7 PLCs over ISO-on-TCP (S7Comm and S7CommPlus)
Documentation
use crate::proto::error::ProtoError;
use bytes::{Buf, BufMut, Bytes, BytesMut};

#[derive(Debug, Clone)]
pub struct SzlRequest {
    pub szl_id: u16,
    pub szl_index: u16,
}

#[derive(Debug, Clone)]
pub struct SzlResponse {
    pub szl_id: u16,
    pub szl_index: u16,
    pub data: Bytes,
}

impl SzlRequest {
    pub fn encode(&self, buf: &mut BytesMut) {
        // UserData parameter block for SZL read
        buf.put_u8(0x00); // param head len placeholder
        buf.put_u8(0x01); // param head
        buf.put_u8(0x12); // param length
        buf.put_u8(0x04); // type + function class
        buf.put_u8(0x11); // function number (SZL read)
        buf.put_u8(0x44); // sequence + last
        buf.put_u8(0x01); // data unit ref
        buf.put_u8(0x00); // error code
        buf.put_u16(self.szl_id);
        buf.put_u16(self.szl_index);
    }

    pub fn decode(buf: &mut Bytes) -> Result<Self, ProtoError> {
        if buf.len() < 12 {
            return Err(ProtoError::BufferTooShort {
                need: 12,
                have: buf.len(),
            });
        }
        buf.advance(8); // skip param header
        let szl_id = buf.get_u16();
        let szl_index = buf.get_u16();
        Ok(SzlRequest { szl_id, szl_index })
    }
}

impl SzlResponse {
    pub fn decode(buf: &mut Bytes) -> Result<Self, ProtoError> {
        if buf.len() < 12 {
            return Err(ProtoError::BufferTooShort {
                need: 12,
                have: buf.len(),
            });
        }
        buf.advance(8); // skip param header
        let szl_id = buf.get_u16();
        let szl_index = buf.get_u16();
        let data = buf.split_to(buf.len());
        Ok(SzlResponse {
            szl_id,
            szl_index,
            data,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use bytes::{Bytes, BytesMut};

    #[test]
    fn szl_request_roundtrip() {
        let req = SzlRequest {
            szl_id: 0x0011,
            szl_index: 0x0000,
        };
        let mut buf = BytesMut::new();
        req.encode(&mut buf);
        let mut b = buf.freeze();
        let decoded = SzlRequest::decode(&mut b).unwrap();
        assert_eq!(decoded.szl_id, 0x0011);
        assert_eq!(decoded.szl_index, 0x0000);
    }

    #[test]
    fn szl_request_encode_length() {
        let req = SzlRequest {
            szl_id: 0x0011,
            szl_index: 0x0001,
        };
        let mut buf = BytesMut::new();
        req.encode(&mut buf);
        assert_eq!(buf.len(), 12);
    }

    #[test]
    fn szl_response_decode_with_data() {
        // 8-byte header + 2-byte szl_id + 2-byte szl_index + 4 bytes data
        let mut raw = vec![0x00u8, 0x01, 0x12, 0x04, 0x11, 0x44, 0x01, 0x00];
        raw.extend_from_slice(&[0x00, 0x11]); // szl_id
        raw.extend_from_slice(&[0x00, 0x00]); // szl_index
        raw.extend_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]); // data
        let mut b = Bytes::from(raw);
        let resp = SzlResponse::decode(&mut b).unwrap();
        assert_eq!(resp.szl_id, 0x0011);
        assert_eq!(resp.szl_index, 0x0000);
        assert_eq!(resp.data.as_ref(), &[0xDE, 0xAD, 0xBE, 0xEF]);
    }

    #[test]
    fn szl_request_truncated_returns_err() {
        let mut b = Bytes::from_static(b"\x00\x01\x12");
        assert!(SzlRequest::decode(&mut b).is_err());
    }
}