1use matter_codec::{Element, Tag, TlvReader, TlvWriter, Value};
10
11use crate::error::{Error, Result};
12use crate::tlv_tags as tags;
13
14#[derive(Debug, Clone, PartialEq, Eq)]
16#[non_exhaustive]
17pub enum DnAttribute {
18 CommonName(String),
21 Surname(String),
23 SerialNumber(String),
25 CountryName(String),
27 LocalityName(String),
29 StateOrProvinceName(String),
31 OrganizationName(String),
33 OrganizationalUnitName(String),
35 Title(String),
37 Name(String),
39 GivenName(String),
41 Initials(String),
43 GenerationQualifier(String),
45 DnQualifier(String),
47 Pseudonym(String),
49 DomainComponent(String),
51
52 NodeId(u64),
55 IcacId(u64),
57 RcacId(u64),
59 FabricId(u64),
61 CaseAuthenticatedTag(u32),
63
64 VendorId(u16),
73
74 ProductId(u16),
83
84 Other {
88 tag: u8,
90 value: DnAttributeValue,
92 },
93}
94
95#[derive(Debug, Clone, PartialEq, Eq)]
101#[non_exhaustive]
102pub enum DnAttributeValue {
103 Utf8(String),
105 Uint(u64),
107 Bytes(Vec<u8>),
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, Default)]
113pub struct DistinguishedName(Vec<DnAttribute>);
114
115impl DistinguishedName {
116 #[must_use]
118 pub fn new(attrs: Vec<DnAttribute>) -> Self {
119 Self(attrs)
120 }
121
122 pub fn iter(&self) -> core::slice::Iter<'_, DnAttribute> {
124 self.0.iter()
125 }
126
127 #[must_use]
129 pub fn node_id(&self) -> Option<u64> {
130 self.0.iter().find_map(|a| match a {
131 DnAttribute::NodeId(v) => Some(*v),
132 _ => None,
133 })
134 }
135
136 #[must_use]
138 pub fn fabric_id(&self) -> Option<u64> {
139 self.0.iter().find_map(|a| match a {
140 DnAttribute::FabricId(v) => Some(*v),
141 _ => None,
142 })
143 }
144
145 #[must_use]
147 pub fn rcac_id(&self) -> Option<u64> {
148 self.0.iter().find_map(|a| match a {
149 DnAttribute::RcacId(v) => Some(*v),
150 _ => None,
151 })
152 }
153
154 #[must_use]
156 pub fn icac_id(&self) -> Option<u64> {
157 self.0.iter().find_map(|a| match a {
158 DnAttribute::IcacId(v) => Some(*v),
159 _ => None,
160 })
161 }
162
163 #[must_use]
165 pub fn common_name(&self) -> Option<&str> {
166 self.0.iter().find_map(|a| match a {
167 DnAttribute::CommonName(v) => Some(v.as_str()),
168 _ => None,
169 })
170 }
171
172 #[allow(dead_code)]
182 pub(crate) fn read(reader: &mut TlvReader<'_>) -> Result<Self> {
183 match reader.next()? {
184 Some(Element::ContainerStart {
185 kind: matter_codec::ContainerKind::List,
186 ..
187 }) => {}
188 _ => return Err(Error::WrongFieldType(0)),
189 }
190 Self::read_from_open_list(reader)
191 }
192
193 pub(crate) fn read_from_open_list(reader: &mut TlvReader<'_>) -> Result<Self> {
203 let mut attrs = Vec::new();
204 loop {
205 match reader.next()? {
206 None => return Err(matter_codec::Error::UnclosedContainer.into()),
207 Some(Element::ContainerEnd) => break,
208 Some(Element::Scalar { tag, value }) => {
209 let Tag::Context(tag_num) = tag else {
210 return Err(Error::InvalidDnAttribute(0));
211 };
212 attrs.push(decode_attribute(tag_num, value)?);
213 }
214 Some(Element::ContainerStart { .. }) => {
215 return Err(Error::WrongFieldType(0));
216 }
217 Some(_) => return Err(Error::WrongFieldType(0)),
220 }
221 }
222 Ok(Self(attrs))
223 }
224
225 pub(crate) fn write(&self, writer: &mut TlvWriter<'_>, outer_tag: Tag) -> Result<()> {
231 writer.start_list(outer_tag)?;
232 for attr in &self.0 {
233 encode_attribute(writer, attr)?;
234 }
235 writer.end_container()?;
236 Ok(())
237 }
238}
239
240impl<'a> IntoIterator for &'a DistinguishedName {
241 type Item = &'a DnAttribute;
242 type IntoIter = core::slice::Iter<'a, DnAttribute>;
243
244 fn into_iter(self) -> Self::IntoIter {
245 self.0.iter()
246 }
247}
248
249pub(crate) fn decode_attribute(tag: u8, value: Value) -> Result<DnAttribute> {
253 use DnAttribute as A;
254 match (tag, value) {
255 (tags::DN_COMMON_NAME, Value::Utf8(s)) => Ok(A::CommonName(s)),
256 (tags::DN_SURNAME, Value::Utf8(s)) => Ok(A::Surname(s)),
257 (tags::DN_SERIAL_NUMBER, Value::Utf8(s)) => Ok(A::SerialNumber(s)),
258 (tags::DN_COUNTRY_NAME, Value::Utf8(s)) => Ok(A::CountryName(s)),
259 (tags::DN_LOCALITY_NAME, Value::Utf8(s)) => Ok(A::LocalityName(s)),
260 (tags::DN_STATE_OR_PROVINCE, Value::Utf8(s)) => Ok(A::StateOrProvinceName(s)),
261 (tags::DN_ORGANIZATION_NAME, Value::Utf8(s)) => Ok(A::OrganizationName(s)),
262 (tags::DN_ORG_UNIT_NAME, Value::Utf8(s)) => Ok(A::OrganizationalUnitName(s)),
263 (tags::DN_TITLE, Value::Utf8(s)) => Ok(A::Title(s)),
264 (tags::DN_NAME, Value::Utf8(s)) => Ok(A::Name(s)),
265 (tags::DN_GIVEN_NAME, Value::Utf8(s)) => Ok(A::GivenName(s)),
266 (tags::DN_INITIALS, Value::Utf8(s)) => Ok(A::Initials(s)),
267 (tags::DN_GENERATION_QUALIFIER, Value::Utf8(s)) => Ok(A::GenerationQualifier(s)),
268 (tags::DN_DN_QUALIFIER, Value::Utf8(s)) => Ok(A::DnQualifier(s)),
269 (tags::DN_PSEUDONYM, Value::Utf8(s)) => Ok(A::Pseudonym(s)),
270 (tags::DN_DOMAIN_COMPONENT, Value::Utf8(s)) => Ok(A::DomainComponent(s)),
271 (tags::DN_MATTER_NODE_ID, Value::Uint(v)) => Ok(A::NodeId(v)),
272 (tags::DN_MATTER_ICAC_ID, Value::Uint(v)) => Ok(A::IcacId(v)),
273 (tags::DN_MATTER_RCAC_ID, Value::Uint(v)) => Ok(A::RcacId(v)),
274 (tags::DN_MATTER_FABRIC_ID, Value::Uint(v)) => Ok(A::FabricId(v)),
275 (tags::DN_MATTER_NOC_CAT, Value::Uint(v)) => {
276 let v32 = u32::try_from(v).map_err(|_| Error::FieldValueOutOfRange { tag })?;
277 Ok(A::CaseAuthenticatedTag(v32))
278 }
279
280 (n, Value::Utf8(s)) if is_untyped_dn_tag(n) => Ok(A::Other {
286 tag: n,
287 value: DnAttributeValue::Utf8(s),
288 }),
289 (n, Value::Uint(v)) if is_untyped_dn_tag(n) => Ok(A::Other {
290 tag: n,
291 value: DnAttributeValue::Uint(v),
292 }),
293 (n, Value::Bytes(b)) if is_untyped_dn_tag(n) => Ok(A::Other {
294 tag: n,
295 value: DnAttributeValue::Bytes(b),
296 }),
297
298 (n, _) if !is_dn_tag(n) => Err(Error::InvalidDnAttribute(n)),
300
301 (n, _) => Err(Error::InvalidDnAttributeType(n)),
303 }
304}
305
306fn encode_attribute(writer: &mut TlvWriter<'_>, attr: &DnAttribute) -> Result<()> {
307 use DnAttribute as A;
308 match attr {
309 A::CommonName(s) => writer.put_utf8(Tag::Context(tags::DN_COMMON_NAME), s)?,
310 A::Surname(s) => writer.put_utf8(Tag::Context(tags::DN_SURNAME), s)?,
311 A::SerialNumber(s) => writer.put_utf8(Tag::Context(tags::DN_SERIAL_NUMBER), s)?,
312 A::CountryName(s) => writer.put_utf8(Tag::Context(tags::DN_COUNTRY_NAME), s)?,
313 A::LocalityName(s) => writer.put_utf8(Tag::Context(tags::DN_LOCALITY_NAME), s)?,
314 A::StateOrProvinceName(s) => {
315 writer.put_utf8(Tag::Context(tags::DN_STATE_OR_PROVINCE), s)?;
316 }
317 A::OrganizationName(s) => {
318 writer.put_utf8(Tag::Context(tags::DN_ORGANIZATION_NAME), s)?;
319 }
320 A::OrganizationalUnitName(s) => {
321 writer.put_utf8(Tag::Context(tags::DN_ORG_UNIT_NAME), s)?;
322 }
323 A::Title(s) => writer.put_utf8(Tag::Context(tags::DN_TITLE), s)?,
324 A::Name(s) => writer.put_utf8(Tag::Context(tags::DN_NAME), s)?,
325 A::GivenName(s) => writer.put_utf8(Tag::Context(tags::DN_GIVEN_NAME), s)?,
326 A::Initials(s) => writer.put_utf8(Tag::Context(tags::DN_INITIALS), s)?,
327 A::GenerationQualifier(s) => {
328 writer.put_utf8(Tag::Context(tags::DN_GENERATION_QUALIFIER), s)?;
329 }
330 A::DnQualifier(s) => writer.put_utf8(Tag::Context(tags::DN_DN_QUALIFIER), s)?,
331 A::Pseudonym(s) => writer.put_utf8(Tag::Context(tags::DN_PSEUDONYM), s)?,
332 A::DomainComponent(s) => {
333 writer.put_utf8(Tag::Context(tags::DN_DOMAIN_COMPONENT), s)?;
334 }
335 A::NodeId(v) => writer.put_uint(Tag::Context(tags::DN_MATTER_NODE_ID), *v)?,
336 A::IcacId(v) => writer.put_uint(Tag::Context(tags::DN_MATTER_ICAC_ID), *v)?,
337 A::RcacId(v) => writer.put_uint(Tag::Context(tags::DN_MATTER_RCAC_ID), *v)?,
338 A::FabricId(v) => writer.put_uint(Tag::Context(tags::DN_MATTER_FABRIC_ID), *v)?,
339 A::CaseAuthenticatedTag(v) => {
340 writer.put_uint(Tag::Context(tags::DN_MATTER_NOC_CAT), u64::from(*v))?;
341 }
342 A::VendorId(_) => return Err(Error::DnAttributeNotTlvEncodable("VendorId")),
346 A::ProductId(_) => return Err(Error::DnAttributeNotTlvEncodable("ProductId")),
347 A::Other { tag, value } => match value {
348 DnAttributeValue::Utf8(s) => writer.put_utf8(Tag::Context(*tag), s)?,
349 DnAttributeValue::Uint(v) => writer.put_uint(Tag::Context(*tag), *v)?,
350 DnAttributeValue::Bytes(b) => writer.put_bytes(Tag::Context(*tag), b)?,
351 },
352 }
353 Ok(())
354}
355
356const fn is_dn_tag(tag: u8) -> bool {
360 matches!(tag, 1..=26)
361}
362
363const fn is_untyped_dn_tag(tag: u8) -> bool {
371 matches!(tag, 18 | 23..=26)
372}
373
374#[cfg(test)]
375#[allow(clippy::unwrap_used)] mod tests {
377 use super::*;
378
379 fn write_dn(dn: &DistinguishedName) -> Vec<u8> {
380 let mut buf = Vec::new();
381 let mut w = TlvWriter::new(&mut buf);
382 dn.write(&mut w, Tag::Anonymous).unwrap();
383 buf
384 }
385
386 fn read_dn(bytes: &[u8]) -> DistinguishedName {
387 let mut r = TlvReader::new(bytes);
388 DistinguishedName::read(&mut r).unwrap()
389 }
390
391 #[test]
392 fn round_trip_common_name() {
393 let dn = DistinguishedName::new(vec![DnAttribute::CommonName("CN".into())]);
394 let bytes = write_dn(&dn);
395 assert_eq!(read_dn(&bytes), dn);
396 }
397
398 #[test]
399 fn round_trip_matter_node_id() {
400 let dn = DistinguishedName::new(vec![DnAttribute::NodeId(0xDEAD_BEEF_CAFE_BABE)]);
401 let bytes = write_dn(&dn);
402 assert_eq!(read_dn(&bytes), dn);
403 }
404
405 #[test]
406 fn round_trip_multiple_attributes_preserves_order() {
407 let dn = DistinguishedName::new(vec![
408 DnAttribute::FabricId(1),
409 DnAttribute::NodeId(2),
410 DnAttribute::CommonName("device".into()),
411 ]);
412 let bytes = write_dn(&dn);
413 let parsed = read_dn(&bytes);
414 assert_eq!(parsed, dn);
415 assert!(matches!(
416 parsed.iter().next(),
417 Some(DnAttribute::FabricId(1))
418 ));
419 }
420
421 #[test]
422 fn round_trip_other_attribute_for_tag_18() {
423 let dn = DistinguishedName::new(vec![DnAttribute::Other {
424 tag: 18,
425 value: DnAttributeValue::Uint(42),
426 }]);
427 let bytes = write_dn(&dn);
428 assert_eq!(read_dn(&bytes), dn);
429 }
430
431 #[test]
432 fn read_rejects_unknown_dn_tag() {
433 let mut buf = Vec::new();
434 {
435 let mut w = TlvWriter::new(&mut buf);
436 w.start_list(Tag::Anonymous).unwrap();
437 w.put_utf8(Tag::Context(100), "bogus").unwrap();
438 w.end_container().unwrap();
439 }
440 let mut r = TlvReader::new(&buf);
441 assert!(matches!(
442 DistinguishedName::read(&mut r),
443 Err(Error::InvalidDnAttribute(100))
444 ));
445 }
446
447 #[test]
448 fn read_rejects_wrong_type_for_known_tag() {
449 let mut buf = Vec::new();
450 {
451 let mut w = TlvWriter::new(&mut buf);
452 w.start_list(Tag::Anonymous).unwrap();
453 w.put_uint(Tag::Context(tags::DN_COMMON_NAME), 42).unwrap();
454 w.end_container().unwrap();
455 }
456 let mut r = TlvReader::new(&buf);
457 assert!(matches!(
458 DistinguishedName::read(&mut r),
459 Err(Error::InvalidDnAttributeType(_))
460 ));
461 }
462
463 #[test]
464 fn typed_accessors_return_expected_values() {
465 let dn = DistinguishedName::new(vec![
466 DnAttribute::FabricId(7),
467 DnAttribute::NodeId(42),
468 DnAttribute::CommonName("device-007".into()),
469 ]);
470 assert_eq!(dn.node_id(), Some(42));
471 assert_eq!(dn.fabric_id(), Some(7));
472 assert_eq!(dn.common_name(), Some("device-007"));
473 assert_eq!(dn.rcac_id(), None);
474 }
475}