1#![no_std]
5
6mod error;
7
8pub use error::{Error, Result};
9
10#[cfg(feature = "alloc")]
11extern crate alloc;
12#[cfg(feature = "alloc")]
13use alloc::{format, string::String, vec::Vec};
14#[cfg(not(feature = "alloc"))]
15use heapless::Vec;
16
17#[derive(Clone, Debug, PartialEq)]
18pub enum TypeNameFormat {
19 Empty,
20 NfcWellKnown,
21 Media,
22 AbsoluteUri,
23 NfcExternal,
24 Unknown,
25 Unchanged,
26 Reserved,
27}
28
29#[derive(Clone, Debug, Default, PartialEq)]
30struct Header(u8);
31
32impl Header {
33 fn set_message_begin(&mut self) {
37 self.0 |= 0x80;
38 }
39
40 fn set_message_end(&mut self) {
44 self.0 |= 0x40;
45 }
46 fn clr_message_end(&mut self) {
47 self.0 &= !0x40;
48 }
49
50 fn short_record(&self) -> bool {
55 self.0 & 0x10 == 0x10
56 }
57 fn set_short_record(&mut self) {
58 self.0 |= 0x10;
59 }
60
61 fn id_length(&self) -> bool {
62 self.0 & 0x08 == 0x08
63 }
64 fn set_id_length(&mut self) {
65 self.0 |= 0x08;
66 }
67
68 fn type_name_format(&self) -> TypeNameFormat {
69 match self.0 & 0x07 {
70 0 => TypeNameFormat::Empty,
71 1 => TypeNameFormat::NfcWellKnown,
72 2 => TypeNameFormat::Media,
73 3 => TypeNameFormat::AbsoluteUri,
74 4 => TypeNameFormat::NfcExternal,
75 5 => TypeNameFormat::Unknown,
76 6 => TypeNameFormat::Unchanged,
77 7 => TypeNameFormat::Reserved,
78 _ => unreachable!(),
79 }
80 }
81 fn set_type_name_format(&mut self, tnf: TypeNameFormat) {
82 self.0 &= !0x70;
83 self.0 |= match tnf {
84 TypeNameFormat::Empty => 0x00,
85 TypeNameFormat::NfcWellKnown => 0x01,
86 TypeNameFormat::Media => 0x02,
87 TypeNameFormat::AbsoluteUri => 0x03,
88 TypeNameFormat::NfcExternal => 0x04,
89 TypeNameFormat::Unknown => 0x05,
90 TypeNameFormat::Unchanged => 0x06,
91 TypeNameFormat::Reserved => 0x07,
92 };
93 }
94}
95
96#[derive(Clone, Debug, PartialEq)]
97pub enum RecordType<'a> {
98 #[cfg(not(feature = "alloc"))]
99 Text { enc: &'a str, txt: &'a str },
100 #[cfg(feature = "alloc")]
101 Text { enc: &'a str, txt: String },
102 External {
103 domain: &'a str,
104 type_: &'a str,
105 data: &'a [u8],
106 },
107 #[cfg(all(feature = "cbor", not(feature = "alloc")))]
108 Cbor(&'a [u8]),
109 #[cfg(all(feature = "cbor", feature = "alloc"))]
110 Cbor(Vec<u8>),
111}
112
113impl<'a> RecordType<'a> {
114 fn len(&self) -> usize {
115 match self {
116 RecordType::Text { enc, txt } => 1 + enc.len() + txt.len(),
117 RecordType::External { data, .. } => data.len(),
118 #[cfg(feature = "cbor")]
119 RecordType::Cbor(data) => data.len(),
120 }
121 }
122
123 #[cfg(feature = "alloc")]
124 fn to_vec(&self) -> Vec<u8> {
125 match self {
126 RecordType::Text { enc, txt } => {
127 let mut data = Vec::new();
128 data.push(enc.len() as u8);
130 data.extend_from_slice(enc.as_bytes());
131 data.extend_from_slice(txt.as_bytes());
132 data
133 }
134 RecordType::External { data, .. } => data.to_vec(),
135 #[cfg(feature = "cbor")]
136 RecordType::Cbor(data) => data.clone(),
137 }
138 }
139 #[cfg(not(feature = "alloc"))]
140 fn to_vec(&self) -> Result<Vec<u8, 256>> {
141 match self {
142 RecordType::Text { enc, txt } => {
143 let mut data = Vec::new();
144 data.push(enc.len() as u8)
146 .map_err(|_| Error::BufferTooSmall)?;
147 data.extend_from_slice(enc.as_bytes())
148 .map_err(|_| Error::BufferTooSmall)?;
149 data.extend_from_slice(txt.as_bytes())
150 .map_err(|_| Error::BufferTooSmall)?;
151 Ok(data)
152 }
153 RecordType::External { data, .. } => {
154 Vec::from_slice(data).map_err(|_| Error::BufferTooSmall)
155 }
156 #[cfg(feature = "cbor")]
157 RecordType::Cbor(data) => Vec::from_slice(data).map_err(|_| Error::BufferTooSmall),
158 }
159 }
160}
161
162#[derive(Clone, Debug, PartialEq)]
163pub enum Payload<'a> {
164 RTD(RecordType<'a>),
165}
166
167impl<'a> From<&Payload<'a>> for TypeNameFormat {
168 fn from(pl: &Payload<'a>) -> TypeNameFormat {
169 match pl {
170 Payload::RTD(RecordType::External { .. }) => TypeNameFormat::NfcExternal,
171 #[cfg(feature = "cbor")]
172 Payload::RTD(RecordType::Cbor(_)) => TypeNameFormat::NfcExternal,
173 Payload::RTD(_) => TypeNameFormat::NfcWellKnown,
174 }
175 }
176}
177
178impl<'a> Payload<'a> {
179 fn len(&self) -> usize {
180 match self {
181 Payload::RTD(rtd) => rtd.len(),
182 }
183 }
184
185 #[cfg(feature = "alloc")]
186 fn to_vec(&self) -> Vec<u8> {
187 match self {
188 Payload::RTD(rtd) => rtd.to_vec(),
189 }
190 }
191 #[cfg(not(feature = "alloc"))]
192 fn to_vec(&self) -> Result<Vec<u8, 256>> {
193 match self {
194 Payload::RTD(rtd) => rtd.to_vec(),
195 }
196 }
197 #[cfg(all(feature = "alloc", feature = "cbor"))]
198 pub fn from_cbor_encodable<T>(x: &T) -> Self
199 where
200 T: minicbor::Encode<()>,
201 {
202 Payload::RTD(RecordType::Cbor(minicbor::to_vec(x).unwrap()))
203 }
204}
205
206#[derive(Clone, Debug, PartialEq)]
207pub struct Record<'a> {
208 header: Header,
209 id: Option<&'a [u8]>,
210 pub payload: Payload<'a>,
211}
212
213impl<'a> Record<'a> {
214 pub fn new(id: Option<&'a [u8]>, payload: Payload<'a>) -> Self {
215 let mut header = Header::default();
216 header.set_type_name_format(TypeNameFormat::from(&payload));
217 if id.is_some() {
218 header.set_id_length();
219 }
220 if payload.len() < 256 {
221 header.set_short_record();
222 }
223 Self {
224 header,
225 id,
226 payload,
227 }
228 }
229
230 #[cfg(feature = "cbor")]
231 pub fn is_type_cbor(&self) -> bool {
232 matches!(&self.payload, Payload::RTD(RecordType::Cbor(_)))
233 }
234
235 #[cfg(feature = "alloc")]
236 pub fn get_type(&self) -> String {
237 use alloc::string::ToString;
238
239 match &self.payload {
240 Payload::RTD(rtd) => match rtd {
241 RecordType::Text { .. } => "T".to_string(),
242 RecordType::External { domain, type_, .. } => format!("{domain}:{type_}"),
243 #[cfg(feature = "cbor")]
244 RecordType::Cbor(_) => "cbor.io:cbor".to_string(),
245 },
246 }
247 }
248 #[cfg(not(feature = "alloc"))]
249 pub fn get_type(&self) -> &'a str {
250 match &self.payload {
251 Payload::RTD(rtd) => match rtd {
252 RecordType::Text { .. } => "T",
253 RecordType::External {
254 domain: _,
255 type_: _,
256 ..
257 } => unimplemented!("can't concat without alloc"),
258 #[cfg(feature = "cbor")]
259 RecordType::Cbor(_) => "cbor.io:cbor",
260 },
261 }
262 }
263
264 #[cfg(feature = "alloc")]
265 pub fn payload(&self) -> Vec<u8> {
266 self.payload.to_vec()
267 }
268 #[cfg(not(feature = "alloc"))]
269 pub fn payload(&self) -> Result<Vec<u8, 256>> {
270 self.payload.to_vec()
271 }
272}
273
274#[derive(Clone, Debug, Default, PartialEq)]
275pub struct Message<'a> {
276 #[cfg(feature = "alloc")]
277 pub records: Vec<Record<'a>>,
278 #[cfg(not(feature = "alloc"))]
279 pub records: Vec<Record<'a>, 8>,
280}
281
282impl<'a> Message<'a> {
283 #[cfg(feature = "alloc")]
284 pub fn append_record(&mut self, record: &mut Record<'a>) {
285 if self.records.is_empty() {
286 record.header.set_message_begin();
287 } else {
288 self.records.last_mut().unwrap().header.clr_message_end();
289 }
290 record.header.set_message_end();
291 self.records.push(record.clone());
292 }
293
294 #[cfg(not(feature = "alloc"))]
295 pub fn append_record(&mut self, record: &mut Record<'a>) -> Result<()> {
296 if self.records.is_empty() {
297 record.header.set_message_begin();
298 } else {
299 self.records.last_mut().unwrap().header.clr_message_end();
300 }
301 record.header.set_message_end();
302 self.records
303 .push(record.clone())
304 .map_err(|_| Error::BufferTooSmall)
305 }
306
307 #[cfg(feature = "alloc")]
308 pub fn to_vec(&self) -> Vec<u8> {
309 let mut buf = Vec::new();
310 for record in &self.records {
311 let type_ = record.get_type();
312 let payload_data = record.payload();
313 buf.push(record.header.0);
315 buf.push(type_.len() as u8);
317 buf.push(payload_data.len() as u8);
319 if let Some(id) = &record.id {
321 buf.push(id.len() as u8);
322 }
323 buf.extend_from_slice(type_.as_bytes());
325 if let Some(id) = &record.id {
327 buf.extend_from_slice(id);
328 }
329 buf.extend_from_slice(payload_data.as_slice());
331 }
332 buf
333 }
334
335 #[cfg(not(feature = "alloc"))]
336 pub fn to_vec(&self) -> Result<Vec<u8, 256>> {
337 let mut buf = Vec::new();
338 for record in &self.records {
339 let type_ = record.get_type();
340 let payload_data = record.payload()?;
341 buf.push(record.header.0)
343 .map_err(|_| Error::BufferTooSmall)?;
344 buf.push(type_.len() as u8)
346 .map_err(|_| Error::BufferTooSmall)?;
347 buf.push(payload_data.len() as u8)
349 .map_err(|_| Error::BufferTooSmall)?;
350 if let Some(id) = &record.id {
352 buf.push(id.len() as u8)
353 .map_err(|_| Error::BufferTooSmall)?;
354 }
355 buf.extend_from_slice(type_.as_bytes())
357 .map_err(|_| Error::BufferTooSmall)?;
358 if let Some(id) = &record.id {
360 buf.extend_from_slice(id)
361 .map_err(|_| Error::BufferTooSmall)?;
362 }
363 buf.extend_from_slice(payload_data.as_slice())
365 .map_err(|_| Error::BufferTooSmall)?;
366 }
367 Ok(buf)
368 }
369}
370
371impl<'a> TryFrom<&'a [u8]> for Message<'a> {
372 type Error = Error<'a>;
373
374 fn try_from(slice: &'a [u8]) -> Result<Self> {
375 if slice.is_empty() {
376 return Err(Error::SliceTooShort);
377 }
378 let mut records = Vec::new();
379 let mut offset = 0;
380 macro_rules! checked_add_offset {
381 ($inc:expr) => {{
382 if offset + $inc > slice.len() {
383 return Err(Error::SliceTooShort);
384 }
385 offset += $inc;
386 }};
387 }
388 while offset < slice.len() {
389 checked_add_offset!(1);
391 let header = Header(slice[offset - 1]);
392 checked_add_offset!(1);
394 let type_length = slice[offset - 1] as usize;
395 let payload_length = if header.short_record() {
397 checked_add_offset!(1);
398 slice[offset - 1] as usize
399 } else {
400 checked_add_offset!(4);
401 u32::from_be_bytes(slice[offset - 4..offset].try_into().unwrap()) as usize
402 };
403 let id_length = if header.id_length() {
405 checked_add_offset!(1);
406 slice[offset - 1] as usize
407 } else {
408 0
409 };
410 checked_add_offset!(type_length);
412 let type_ = core::str::from_utf8(&slice[offset - type_length..offset])?;
413 let id = if header.id_length() {
415 checked_add_offset!(id_length);
416 Some(&slice[offset - id_length..offset])
417 } else {
418 None
419 };
420 checked_add_offset!(payload_length);
422 let payload_data = &slice[offset - payload_length..offset];
423 let payload = match header.type_name_format() {
424 TypeNameFormat::NfcWellKnown => Payload::RTD(match type_ {
425 "T" => {
426 if payload_data.is_empty() {
427 return Err(Error::SliceTooShort);
428 }
429 let enc_len = (payload_data[0] & 0x1f) as usize;
430 let is_utf16 = (payload_data[0] & 0x80) != 0;
431 if payload_data.len() < enc_len + 1 {
432 return Err(Error::SliceTooShort);
433 }
434 let enc = core::str::from_utf8(&payload_data[1..enc_len + 1])?;
435 let txt = if is_utf16 {
436 #[cfg(not(feature = "alloc"))]
437 unimplemented!("UTF-16 decoding is not supported yet");
438 #[cfg(feature = "alloc")]
439 {
440 let utf16_bytes = &payload_data[enc_len + 1..];
441 if utf16_bytes.len() % 2 != 0 {
443 return Err(Error::UTF16OddLength(utf16_bytes.len()));
444 }
445 let utf16_units: Vec<u16> = utf16_bytes
447 .chunks(2)
448 .map(|chunk| u16::from_be_bytes(chunk.try_into().unwrap()))
449 .collect();
450 String::from_utf16(&utf16_units).map_err(|_| Error::UTF16Decode)?
451 }
452 } else {
453 #[cfg(not(feature = "alloc"))]
454 {
455 core::str::from_utf8(&payload_data[enc_len + 1..])?
456 }
457 #[cfg(feature = "alloc")]
458 String::from_utf8(payload_data[enc_len + 1..].to_vec())?
459 };
460 RecordType::Text { enc, txt }
461 }
462 t => return Err(Error::UnsupportedRecordType(t)),
463 }),
464 TypeNameFormat::NfcExternal => match type_ {
465 #[cfg(all(feature = "cbor", not(feature = "alloc")))]
466 "cbor.io:cbor" => Payload::RTD(RecordType::Cbor(payload_data)),
467 #[cfg(all(feature = "cbor", feature = "alloc"))]
468 "cbor.io:cbor" => Payload::RTD(RecordType::Cbor(payload_data.to_vec())),
469 _ => {
470 if let Some(index) = type_.find(':') {
471 let domain = &type_[..index];
472 let type_ = &type_[index + 1..];
473 Payload::RTD(RecordType::External {
474 domain,
475 type_,
476 data: payload_data,
477 })
478 } else {
479 return Err(Error::InvalidExternalType(type_));
480 }
481 }
482 },
483 tnf => return Err(Error::UnsupportedTypeNameFormat(tnf)),
484 };
485 #[cfg(feature = "alloc")]
486 records.push(Record {
487 header,
488 id,
489 payload,
490 });
491 #[cfg(not(feature = "alloc"))]
492 records
493 .push(Record {
494 header,
495 id,
496 payload,
497 })
498 .map_err(|_| Error::SliceTooShort)?;
499 }
500 Ok(Message { records })
501 }
502}
503
504#[cfg(test)]
505mod tests {
506 #[cfg(feature = "alloc")]
507 use alloc::string::ToString;
508
509 use super::*;
510
511 #[test]
512 fn test_rtd_text_utf8() {
513 let raw = [
514 0xD1, 0x01, 0x12, 0x54, 0x02, 0x66, 0x72, 0x55, 0x54, 0x46, 0x2D, 0x38, 0x20, 0x74,
515 0x65, 0x78, 0x74, 0x20, 0xf0, 0x9f, 0xa6, 0x80,
516 ];
517 let mut msg = Message::default();
518 let txt = "UTF-8 text 🦀";
519 #[cfg(feature = "alloc")]
520 let txt = txt.to_string();
521 let mut rec1 = Record::new(None, Payload::RTD(RecordType::Text { enc: "fr", txt }));
522 #[cfg(feature = "alloc")]
523 msg.append_record(&mut rec1);
524 #[cfg(not(feature = "alloc"))]
525 msg.append_record(&mut rec1).unwrap();
526 assert_eq!(msg, Message::try_from(raw.as_slice()).unwrap());
527 #[cfg(feature = "alloc")]
528 assert_eq!(&raw, msg.to_vec().as_slice());
529 #[cfg(not(feature = "alloc"))]
530 assert_eq!(&raw, msg.to_vec().unwrap().as_slice());
531 }
532 #[test]
533 #[cfg(feature = "alloc")]
534 fn test_rtd_text_utf16() {
535 let raw = [
536 0xD1, 0x01, 0x1F, 0x54, 0x82, 0x66, 0x72, 0x00, 0x55, 0x00, 0x54, 0x00, 0x46, 0x00,
537 0x2D, 0x00, 0x31, 0x00, 0x36, 0x00, 0x20, 0x00, 0x74, 0x00, 0x65, 0x00, 0x78, 0x00,
538 0x74, 0x00, 0x20, 0xd8, 0x3e, 0xdd, 0x80,
539 ];
540 let mut msg = Message::default();
541 let mut rec1 = Record::new(
542 None,
543 Payload::RTD(RecordType::Text {
544 enc: "fr",
545 txt: "UTF-16 text 🦀".to_string(),
546 }),
547 );
548 msg.append_record(&mut rec1);
549 assert_eq!(msg, Message::try_from(raw.as_slice()).unwrap());
550 }
551 #[test]
552 fn test_rtd_external() {
553 let raw = [
554 0xD4, 0x08, 0x01, 0x65, 0x78, 0x2e, 0x63, 0x6f, 0x6d, 0x3a, 0x74, 0x61,
555 ];
556 let mut msg = Message::default();
557 let mut rec1 = Record::new(
558 None,
559 Payload::RTD(RecordType::External {
560 domain: "ex.com",
561 type_: "t",
562 data: &[0x61],
563 }),
564 );
565 #[cfg(feature = "alloc")]
566 msg.append_record(&mut rec1);
567 #[cfg(not(feature = "alloc"))]
568 msg.append_record(&mut rec1).unwrap();
569 assert_eq!(msg, Message::try_from(raw.as_slice()).unwrap());
570 #[cfg(feature = "alloc")]
571 assert_eq!(&raw, msg.to_vec().as_slice());
572 }
573 #[test]
574 #[cfg(feature = "cbor")]
575 fn test_cbor() {
576 let raw = [
577 0xD4, 0x0c, 0x01, 0x63, 0x62, 0x6f, 0x72, 0x2e, 0x69, 0x6f, 0x3a, 0x63, 0x62, 0x6f,
578 0x72, 0x61,
579 ];
580 let mut msg = Message::default();
581 #[cfg(feature = "alloc")]
582 let mut rec1 = Record::new(None, Payload::RTD(RecordType::Cbor(alloc::vec![0x61])));
583 #[cfg(not(feature = "alloc"))]
584 let mut rec1 = Record::new(None, Payload::RTD(RecordType::Cbor(&[0x61])));
585 #[cfg(feature = "alloc")]
586 msg.append_record(&mut rec1);
587 #[cfg(not(feature = "alloc"))]
588 msg.append_record(&mut rec1).unwrap();
589 assert_eq!(msg, Message::try_from(raw.as_slice()).unwrap());
590 #[cfg(feature = "alloc")]
591 assert_eq!(&raw, msg.to_vec().as_slice());
592 #[cfg(not(feature = "alloc"))]
593 assert_eq!(&raw, msg.to_vec().unwrap().as_slice());
594 }
595}