Skip to main content

snap7_client/proto/s7commplus/
multivar.rs

1use crate::proto::error::ProtoError;
2use crate::proto::s7commplus::data::DataArea;
3use crate::proto::s7commplus::session::{FC_GET_MULTI_VAR, FC_SET_MULTI_VAR, OPCODE_REQUEST};
4use bytes::{Buf, BufMut, Bytes, BytesMut};
5
6/// A single variable to read in a multi-var request.
7#[derive(Debug, Clone)]
8pub struct VarSpec {
9    pub crc: u32,
10    pub lid: u32,
11}
12
13/// A single variable in a multi-var response.
14#[derive(Debug, Clone)]
15pub struct VarResult {
16    pub return_code: u8,
17    pub value: Bytes,
18}
19
20/// Request to read multiple variables in one PDU.
21#[derive(Debug)]
22pub struct GetMultiVarRequest {
23    pub seqnum: u16,
24    pub session_id: u32,
25    pub items: Vec<VarSpec>,
26}
27
28impl GetMultiVarRequest {
29    pub fn encode(&self, buf: &mut BytesMut) {
30        let mut payload = BytesMut::new();
31        payload.put_u8(self.items.len() as u8);
32        for item in &self.items {
33            payload.put_u32(item.crc);
34            payload.put_u32(item.lid);
35        }
36        DataArea {
37            opcode: OPCODE_REQUEST,
38            function_code: FC_GET_MULTI_VAR,
39            seqnum: self.seqnum,
40            session_id: self.session_id,
41            transport_flags: 0,
42            payload: payload.freeze(),
43        }
44        .encode(buf);
45    }
46}
47
48/// Response from a multi-variable read.
49#[derive(Debug)]
50pub struct GetMultiVarResponse {
51    pub items: Vec<VarResult>,
52}
53
54impl GetMultiVarResponse {
55    pub fn decode(buf: &mut Bytes, item_count: usize) -> Result<Self, ProtoError> {
56        let da = DataArea::decode(buf)?;
57        let mut payload = da.payload;
58        let mut items = Vec::with_capacity(item_count);
59        for _ in 0..item_count {
60            if payload.remaining() < 3 {
61                return Err(ProtoError::BufferTooShort { need: 3, have: payload.remaining() });
62            }
63            let return_code = payload.get_u8();
64            let data_len = payload.get_u16() as usize;
65            if payload.remaining() < data_len {
66                return Err(ProtoError::BufferTooShort { need: data_len, have: payload.remaining() });
67            }
68            let value = payload.copy_to_bytes(data_len);
69            items.push(VarResult { return_code, value });
70        }
71        Ok(GetMultiVarResponse { items })
72    }
73}
74
75// Legacy single-var request (wraps GetMultiVarRequest with count=1)
76#[derive(Debug)]
77pub struct GetVarRequest {
78    pub seqnum: u16,
79    pub session_id: u32,
80    pub crc: u32,
81    pub lid: u32,
82}
83
84impl GetVarRequest {
85    pub fn encode(&self, buf: &mut BytesMut) {
86        let req = GetMultiVarRequest {
87            seqnum: self.seqnum,
88            session_id: self.session_id,
89            items: vec![VarSpec { crc: self.crc, lid: self.lid }],
90        };
91        req.encode(buf);
92    }
93}
94
95#[derive(Debug)]
96pub struct GetVarResponse {
97    pub return_code: u8,
98    pub value: Bytes,
99}
100
101impl GetVarResponse {
102    pub fn decode(buf: &mut Bytes) -> Result<Self, ProtoError> {
103        let multi = GetMultiVarResponse::decode(buf, 1)?;
104        if multi.items.is_empty() {
105            return Err(ProtoError::BufferTooShort { need: 3, have: 0 });
106        }
107        Ok(GetVarResponse {
108            return_code: multi.items[0].return_code,
109            value: multi.items[0].value.clone(),
110        })
111    }
112}
113
114/// Request to write multiple variables in one PDU.
115#[derive(Debug)]
116pub struct SetMultiVarRequest {
117    pub seqnum: u16,
118    pub session_id: u32,
119    pub items: Vec<SetVarItem>,
120}
121
122#[derive(Debug, Clone)]
123pub struct SetVarItem {
124    pub crc: u32,
125    pub lid: u32,
126    pub value: Bytes,
127}
128
129impl SetMultiVarRequest {
130    pub fn encode(&self, buf: &mut BytesMut) {
131        let mut payload = BytesMut::new();
132        payload.put_u8(self.items.len() as u8);
133        for item in &self.items {
134            payload.put_u32(item.crc);
135            payload.put_u32(item.lid);
136            payload.put_u16(item.value.len() as u16);
137            payload.put_slice(&item.value);
138        }
139        DataArea {
140            opcode: OPCODE_REQUEST,
141            function_code: FC_SET_MULTI_VAR,
142            seqnum: self.seqnum,
143            session_id: self.session_id,
144            transport_flags: 0,
145            payload: payload.freeze(),
146        }
147        .encode(buf);
148    }
149}
150
151// Legacy single-var request
152#[derive(Debug)]
153pub struct SetVarRequest {
154    pub seqnum: u16,
155    pub session_id: u32,
156    pub crc: u32,
157    pub lid: u32,
158    pub value: Bytes,
159}
160
161impl SetVarRequest {
162    pub fn encode(&self, buf: &mut BytesMut) {
163        let req = SetMultiVarRequest {
164            seqnum: self.seqnum,
165            session_id: self.session_id,
166            items: vec![SetVarItem {
167                crc: self.crc,
168                lid: self.lid,
169                value: self.value.clone(),
170            }],
171        };
172        req.encode(buf);
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179    use bytes::{Bytes, BytesMut};
180
181    #[test]
182    fn get_var_request_function_code() {
183        let req = GetVarRequest {
184            seqnum: 1,
185            session_id: 0xDEAD0001,
186            crc: 0xABCD1234,
187            lid: 2,
188        };
189        let mut buf = BytesMut::new();
190        req.encode(&mut buf);
191        assert_eq!(u16::from_be_bytes([buf[3], buf[4]]), 0x054C);
192    }
193
194    #[test]
195    fn get_var_request_session_id_position() {
196        let req = GetVarRequest {
197            seqnum: 2,
198            session_id: 0x12345678,
199            crc: 0xAABBCCDD,
200            lid: 1,
201        };
202        let mut buf = BytesMut::new();
203        req.encode(&mut buf);
204        // session_id at bytes 9-12 (opcode=1 + res=2 + fc=2 + res=2 + seq=2)
205        let sid = u32::from_be_bytes([buf[9], buf[10], buf[11], buf[12]]);
206        assert_eq!(sid, 0x12345678);
207    }
208
209    #[test]
210    fn set_var_request_function_code() {
211        let req = SetVarRequest {
212            seqnum: 3,
213            session_id: 5,
214            crc: 0x11223344,
215            lid: 1,
216            value: Bytes::from_static(&[0x3F, 0x80, 0x00, 0x00]),
217        };
218        let mut buf = BytesMut::new();
219        req.encode(&mut buf);
220        assert_eq!(u16::from_be_bytes([buf[3], buf[4]]), 0x0542);
221    }
222
223    #[test]
224    fn get_var_response_decode() {
225        use bytes::BufMut;
226        let mut buf = BytesMut::new();
227        buf.put_u8(0x32);
228        buf.put_u16(0x0000);
229        buf.put_u16(0x054C);
230        buf.put_u16(0x0000);
231        buf.put_u16(0x0001);
232        buf.put_u32(0x00000005);
233        buf.put_u8(0x00);
234        buf.put_u8(0x0A); // return_code OK
235        buf.put_u16(4);
236        buf.put_slice(&[0x3F, 0x80, 0x00, 0x00]);
237        let mut b = buf.freeze();
238        let resp = GetVarResponse::decode(&mut b).unwrap();
239        assert_eq!(resp.return_code, 0x0A);
240        assert_eq!(&resp.value[..], &[0x3F, 0x80, 0x00, 0x00]);
241    }
242}