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}