Skip to main content

rustbac_core/services/
who_has.rs

1use crate::apdu::UnconfirmedRequestHeader;
2use crate::encoding::{
3    primitives::{
4        decode_ctx_character_string, decode_unsigned, encode_ctx_character_string,
5        encode_ctx_object_id, encode_ctx_unsigned,
6    },
7    reader::Reader,
8    tag::Tag,
9    writer::Writer,
10};
11use crate::types::ObjectId;
12use crate::{DecodeError, EncodeError};
13
14pub const SERVICE_I_HAVE: u8 = 0x01;
15pub const SERVICE_WHO_HAS: u8 = 0x07;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum WhoHasObject<'a> {
19    ObjectId(ObjectId),
20    ObjectName(&'a str),
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct WhoHasRequest<'a> {
25    pub low_limit: Option<u32>,
26    pub high_limit: Option<u32>,
27    pub object: WhoHasObject<'a>,
28}
29
30impl<'a> WhoHasRequest<'a> {
31    pub const fn for_object_id(object_id: ObjectId) -> Self {
32        Self {
33            low_limit: None,
34            high_limit: None,
35            object: WhoHasObject::ObjectId(object_id),
36        }
37    }
38
39    pub const fn for_object_name(object_name: &'a str) -> Self {
40        Self {
41            low_limit: None,
42            high_limit: None,
43            object: WhoHasObject::ObjectName(object_name),
44        }
45    }
46
47    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
48        UnconfirmedRequestHeader {
49            service_choice: SERVICE_WHO_HAS,
50        }
51        .encode(w)?;
52
53        match (self.low_limit, self.high_limit) {
54            (Some(low), Some(high)) => {
55                encode_ctx_unsigned(w, 0, low)?;
56                encode_ctx_unsigned(w, 1, high)?;
57            }
58            (None, None) => {}
59            _ => {
60                return Err(EncodeError::Message(
61                    "low/high limits must be both set or absent",
62                ))
63            }
64        }
65
66        match self.object {
67            WhoHasObject::ObjectId(object_id) => encode_ctx_object_id(w, 2, object_id.raw()),
68            WhoHasObject::ObjectName(object_name) => encode_ctx_character_string(w, 3, object_name),
69        }
70    }
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub struct IHaveRequest<'a> {
75    pub device_id: ObjectId,
76    pub object_id: ObjectId,
77    pub object_name: &'a str,
78}
79
80impl<'a> IHaveRequest<'a> {
81    pub fn decode_after_header(r: &mut Reader<'a>) -> Result<Self, DecodeError> {
82        let device_id = decode_required_ctx_object_id(r, 0)?;
83        let object_id = decode_required_ctx_object_id(r, 1)?;
84        let object_name = match Tag::decode(r)? {
85            Tag::Context { tag_num: 2, len } => decode_ctx_character_string(r, len as usize)?,
86            _ => return Err(DecodeError::InvalidTag),
87        };
88        Ok(Self {
89            device_id,
90            object_id,
91            object_name,
92        })
93    }
94}
95
96fn decode_required_ctx_object_id(
97    r: &mut Reader<'_>,
98    expected_tag: u8,
99) -> Result<ObjectId, DecodeError> {
100    match Tag::decode(r)? {
101        Tag::Context { tag_num, len } if tag_num == expected_tag => {
102            if len != 4 {
103                return Err(DecodeError::InvalidLength);
104            }
105            Ok(ObjectId::from_raw(decode_unsigned(r, len as usize)?))
106        }
107        _ => Err(DecodeError::InvalidTag),
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::{IHaveRequest, WhoHasRequest, SERVICE_I_HAVE, SERVICE_WHO_HAS};
114    use crate::apdu::UnconfirmedRequestHeader;
115    use crate::encoding::{
116        primitives::{encode_ctx_character_string, encode_ctx_object_id},
117        reader::Reader,
118        writer::Writer,
119    };
120    use crate::types::{ObjectId, ObjectType};
121
122    #[test]
123    fn encode_who_has_request_by_id() {
124        let req = WhoHasRequest::for_object_id(ObjectId::new(ObjectType::AnalogInput, 2));
125        let mut buf = [0u8; 64];
126        let mut w = Writer::new(&mut buf);
127        req.encode(&mut w).unwrap();
128        let mut r = Reader::new(w.as_written());
129        let hdr = UnconfirmedRequestHeader::decode(&mut r).unwrap();
130        assert_eq!(hdr.service_choice, SERVICE_WHO_HAS);
131    }
132
133    #[test]
134    fn encode_who_has_request_by_name_with_limits() {
135        let req = WhoHasRequest {
136            low_limit: Some(1),
137            high_limit: Some(100),
138            object: super::WhoHasObject::ObjectName("AHU-1"),
139        };
140        let mut buf = [0u8; 64];
141        let mut w = Writer::new(&mut buf);
142        req.encode(&mut w).unwrap();
143        assert_eq!(w.as_written()[0], 0x10);
144    }
145
146    #[test]
147    fn decode_i_have_after_header() {
148        let mut buf = [0u8; 128];
149        let mut w = Writer::new(&mut buf);
150        UnconfirmedRequestHeader {
151            service_choice: SERVICE_I_HAVE,
152        }
153        .encode(&mut w)
154        .unwrap();
155        encode_ctx_object_id(&mut w, 0, ObjectId::new(ObjectType::Device, 5).raw()).unwrap();
156        encode_ctx_object_id(&mut w, 1, ObjectId::new(ObjectType::AnalogInput, 2).raw()).unwrap();
157        encode_ctx_character_string(&mut w, 2, "Zone Temp").unwrap();
158        let mut r = Reader::new(w.as_written());
159        let hdr = UnconfirmedRequestHeader::decode(&mut r).unwrap();
160        assert_eq!(hdr.service_choice, SERVICE_I_HAVE);
161        let decoded = IHaveRequest::decode_after_header(&mut r).unwrap();
162        assert_eq!(decoded.device_id, ObjectId::new(ObjectType::Device, 5));
163        assert_eq!(decoded.object_id, ObjectId::new(ObjectType::AnalogInput, 2));
164        assert_eq!(decoded.object_name, "Zone Temp");
165    }
166
167    #[test]
168    fn encode_who_has_request_rejects_partial_limits() {
169        let req = WhoHasRequest {
170            low_limit: Some(1),
171            high_limit: None,
172            object: super::WhoHasObject::ObjectName("bad"),
173        };
174        let mut buf = [0u8; 32];
175        let mut w = Writer::new(&mut buf);
176        assert!(req.encode(&mut w).is_err());
177    }
178}