1use std::{collections::HashMap, convert::TryFrom, io};
204
205use byteorder::{ReadBytesExt, WriteBytesExt};
206use serde::{
207 de::{Deserializer, Error as DeError},
208 Deserialize,
209};
210use serde_json::Value;
211
212use crate::{
213 integer::{Bitfield, PlainInteger},
214 BooleanSchema, DataSchema, Decoder, Encoder, Error, InnerSchema, IntegerSchema, NumberSchema,
215 Result,
216};
217
218#[derive(Debug, thiserror::Error)]
220pub enum ValidationError {
221 #[error("Can not join bitfields as there are none")]
222 NoBitfields,
223 #[error("Can not join bitfields with different number of bytes")]
224 NotSameBytes,
225 #[error("Can not join bitfields as they are overlapping")]
226 OverlappingBitfields,
227 #[error("The position {0} has been used multiple times in the object schema but only bitfields are allowed to share a position")]
228 InvalidPosition(usize),
229}
230
231#[derive(Debug, thiserror::Error)]
233pub enum EncodingError {
234 #[error("The value '{value}' can not be encoded with an object schema")]
235 InvalidValue { value: String },
236 #[error("Writing to buffer failed: {0}")]
237 WriteFail(#[from] io::Error),
238 #[error("Expected the constant value {expected} but got {got}")]
239 InvalidConstValue { expected: String, got: String },
240 #[error("The field '{0}' is not present in the value to encode")]
241 MissingField(String),
242 #[error(transparent)]
243 Number(#[from] crate::number::EncodingError),
244 #[error(transparent)]
245 Integer(#[from] crate::integer::EncodingError),
246 #[error(transparent)]
247 Boolean(#[from] crate::boolean::EncodingError),
248 #[error("Encoding sub-schema failed: {0}")]
249 SubSchema(Box<Error>),
250}
251
252impl From<Error> for EncodingError {
253 fn from(e: Error) -> Self {
254 EncodingError::SubSchema(Box::new(e))
255 }
256}
257
258#[derive(Debug, thiserror::Error)]
260pub enum DecodingError {
261 #[error("Reading encoded data failed: {0}")]
262 ReadFail(#[from] io::Error),
263 #[error(transparent)]
264 Integer(#[from] crate::integer::DecodingError),
265 #[error("Decoding sub-schema failed: {0}")]
266 SubSchema(Box<Error>),
267}
268
269impl DecodingError {
270 pub fn due_to_eof(&self) -> bool {
271 match &self {
272 DecodingError::ReadFail(e) => e.kind() == std::io::ErrorKind::UnexpectedEof,
273 DecodingError::Integer(e) => e.due_to_eof(),
274 DecodingError::SubSchema(e) => e.due_to_eof(),
275 }
276 }
277}
278
279impl From<Error> for DecodingError {
280 fn from(e: Error) -> Self {
281 DecodingError::SubSchema(Box::new(e))
282 }
283}
284
285#[derive(Debug, Clone, Deserialize)]
287struct RawProperty {
288 #[serde(flatten)]
289 schema: DataSchema,
290 position: usize,
293}
294
295#[derive(Debug, Clone)]
297struct JoinedBitfield {
298 bytes: usize,
299 fields: HashMap<String, DataSchema>,
300}
301
302#[derive(Debug, Clone)]
303enum PropertySchema {
304 Simple { name: String, schema: DataSchema },
305 Merged(JoinedBitfield),
306}
307
308#[derive(Debug, Clone)]
309struct Property {
310 position: usize,
311 schema: PropertySchema,
312}
313
314#[derive(Debug, Clone, Deserialize)]
315struct RawObject {
316 properties: HashMap<String, RawProperty>,
317 #[serde(rename = "jsonld:context")]
318 context: Option<Value>,
319}
320
321#[derive(Debug, Clone)]
331pub struct ObjectSchema {
332 properties: Vec<Property>,
333 context: Option<Value>,
334}
335
336impl JoinedBitfield {
337 pub fn join(bfs: HashMap<String, DataSchema>) -> Result<Self, ValidationError> {
338 if bfs.values().any(|ds| !ds.is_bitfield()) {
339 return Err(ValidationError::NoBitfields);
340 }
341
342 let raw_bfs = bfs
343 .iter()
344 .map(|(name, ds)| {
345 let bf = ds.inner.bitfield().expect("ensured at beginning");
346 (name.as_str(), bf)
347 })
348 .collect::<HashMap<_, _>>();
349
350 let bytes = raw_bfs
351 .values()
352 .next()
353 .ok_or(ValidationError::NoBitfields)?
354 .bytes;
355
356 if raw_bfs.values().any(|bf| bf.bytes != bytes) {
357 return Err(ValidationError::NotSameBytes);
358 }
359
360 raw_bfs.values().try_fold(0u64, |state, bf| {
361 let mask = bf.mask();
362 if state & mask != 0 {
363 Err(ValidationError::OverlappingBitfields)
364 } else {
365 Ok(state | mask)
366 }
367 })?;
368
369 Ok(Self { bytes, fields: bfs })
370 }
371 fn raw_bfs(&self) -> impl Iterator<Item = (&'_ str, &'_ Bitfield)> {
372 self.fields.iter().map(|(name, ds)| {
373 let bf = ds.inner.bitfield().expect("ensured at constructor");
374 (name.as_str(), bf)
375 })
376 }
377 fn integer(&self) -> PlainInteger {
379 self.raw_bfs()
380 .map(|(_, bf)| bf)
381 .next()
382 .expect("Constuctor guarantees that there is at least one bitfield")
383 .integer()
384 }
385}
386
387impl Encoder for JoinedBitfield {
388 type Error = EncodingError;
389
390 fn encode<W>(&self, target: &mut W, value: &Value) -> Result<usize, Self::Error>
391 where
392 W: io::Write + WriteBytesExt,
393 {
394 let mut buffer = 0;
395 for (name, ds) in self.fields.iter() {
396 let value = match (value.get(name), ds.default_.as_ref()) {
397 (Some(val), Some(c)) if val == c => Ok(val),
398 (Some(val), Some(c)) => Err(EncodingError::InvalidConstValue {
399 expected: c.to_string(),
400 got: val.to_string(),
401 }),
402 (Some(val), None) => Ok(val),
403 (None, Some(c)) => Ok(c),
404 (None, None) => Err(EncodingError::MissingField(name.clone())),
405 }?;
406
407 let (bf, value) = match &ds.inner {
408 InnerSchema::Number(
409 ns
410 @
411 NumberSchema::Integer {
412 integer: IntegerSchema::Bitfield(_),
413 ..
414 },
415 ) => {
416 let value = value.as_f64().ok_or_else(|| {
417 crate::number::EncodingError::InvalidValue {
418 value: value.to_string(),
419 }
420 })?;
421 let value = ns.to_binary_value(value) as _;
422 if let NumberSchema::Integer {
423 integer: IntegerSchema::Bitfield(bf),
424 ..
425 } = ns
426 {
427 (bf, value)
428 } else {
429 unreachable!("ensured by match")
430 }
431 }
432 InnerSchema::Integer(IntegerSchema::Bitfield(bf)) => {
433 let value = value.as_u64().ok_or_else(|| {
434 crate::integer::EncodingError::InvalidValue {
435 value: value.to_string(),
436 }
437 })?;
438 (bf, value)
439 }
440 InnerSchema::Boolean(BooleanSchema { bf }) => {
441 let value = value.as_bool().ok_or_else(|| {
442 crate::boolean::EncodingError::InvalidValue {
443 value: value.to_string(),
444 }
445 })? as _;
446 (bf, value)
447 }
448 _ => unreachable!("ensured at constructor"),
449 };
450 bf.write(value, &mut buffer);
451 }
452
453 let int = self.integer();
454 int.encode(target, &buffer.into()).map_err(Into::into)
455 }
456}
457
458impl Decoder for JoinedBitfield {
459 type Error = DecodingError;
460
461 fn decode<R>(&self, target: &mut R) -> Result<Value, Self::Error>
462 where
463 R: io::Read + ReadBytesExt,
464 {
465 let int = self.integer();
466 let int = int.decode(target)?.as_u64().expect("Is always u64");
467 let mut res = Value::default();
468 for (name, ds) in self.fields.iter() {
469 let bf = ds.inner.bitfield().expect("ensured at consturctor");
470 let value = bf.read(int);
471 res[name] = match &ds.inner {
472 InnerSchema::Number(ns) => ns.from_binary_value(value as _).into(),
473 InnerSchema::Boolean(_) => (value != 0).into(),
474 _ => value.into(),
475 };
476 }
477
478 Ok(res)
479 }
480}
481
482impl Encoder for PropertySchema {
483 type Error = EncodingError;
484
485 fn encode<W>(&self, target: &mut W, value: &Value) -> Result<usize, Self::Error>
486 where
487 W: io::Write + WriteBytesExt,
488 {
489 match self {
490 PropertySchema::Simple { name, schema } => {
491 let value = match (value.get(name), schema.default_.as_ref()) {
492 (Some(val), Some(c)) if val == c => Ok(val),
493 (Some(val), Some(c)) => Err(EncodingError::InvalidConstValue {
494 expected: c.to_string(),
495 got: val.to_string(),
496 }),
497 (Some(val), None) => Ok(val),
498 (None, Some(c)) => Ok(c),
499 (None, None) => Err(EncodingError::MissingField(name.clone())),
500 }?;
501 schema.encode(target, value).map_err(Into::into)
502 }
503 PropertySchema::Merged(schema) => schema.encode(target, value).map_err(Into::into),
504 }
505 }
506}
507
508impl PropertySchema {
509 fn decode_into<R>(&self, target: &mut R, value: &mut Value) -> Result<(), DecodingError>
514 where
515 R: io::Read + ReadBytesExt,
516 {
517 match self {
518 PropertySchema::Simple { name, schema } => {
519 value[name] = schema.decode(target)?;
520 }
521 PropertySchema::Merged(schema) => {
522 let map = if let Value::Object(map) = schema.decode(target)? {
523 map
524 } else {
525 panic!("Should have always been a map");
526 };
527 for (name, bf_value) in map {
528 value[name] = bf_value;
529 }
530 }
531 }
532
533 Ok(())
534 }
535}
536
537impl TryFrom<RawObject> for ObjectSchema {
538 type Error = ValidationError;
539
540 fn try_from(raw: RawObject) -> Result<Self, Self::Error> {
541 let mut ordered = HashMap::with_capacity(raw.properties.len());
542 raw.properties.into_iter().for_each(|(name, raw)| {
543 let bucket = ordered.entry(raw.position).or_insert_with(Vec::new);
544 bucket.push((name, raw.schema));
545 });
546 let mut properties = Vec::with_capacity(ordered.len());
547 for (position, mut vec) in ordered {
548 let prop = if vec.len() == 1 {
549 let (name, schema) = vec.pop().expect("Ensured by .len() == 1");
550 Property {
551 position,
552 schema: PropertySchema::Simple { name, schema },
553 }
554 } else {
555 let fields: Result<HashMap<_, _>, _> = vec
556 .into_iter()
557 .map(|(name, schema)| {
558 if schema.is_bitfield() {
559 Ok((name, schema))
560 } else {
561 Err(ValidationError::InvalidPosition(position))
562 }
563 })
564 .collect();
565 let joined = JoinedBitfield::join(fields?)?;
566 Property {
567 position,
568 schema: PropertySchema::Merged(joined),
569 }
570 };
571 properties.push(prop);
572 }
573
574 properties.sort_by(|p1, p2| p1.position.cmp(&p2.position));
575 Ok(Self {
576 properties,
577 context: raw.context,
578 })
579 }
580}
581
582impl<'de> Deserialize<'de> for ObjectSchema {
583 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
584 where
585 D: Deserializer<'de>,
586 {
587 let raw = RawObject::deserialize(deserializer)?;
588 ObjectSchema::try_from(raw).map_err(D::Error::custom)
589 }
590}
591
592impl Encoder for ObjectSchema {
593 type Error = EncodingError;
594
595 fn encode<W>(&self, target: &mut W, value: &Value) -> Result<usize, Self::Error>
596 where
597 W: io::Write + WriteBytesExt,
598 {
599 let mut written = 0;
600 for p in self.properties.iter() {
601 written += p.schema.encode(target, &value)?;
602 }
603
604 Ok(written)
605 }
606}
607
608impl Decoder for ObjectSchema {
609 type Error = DecodingError;
610
611 fn decode<R>(&self, target: &mut R) -> Result<Value, Self::Error>
612 where
613 R: io::Read + ReadBytesExt,
614 {
615 let mut value = serde_json::json!({});
616 for p in self.properties.iter() {
617 p.schema.decode_into(target, &mut value)?;
618 }
619
620 if let Some(ctx) = self.context.as_ref() {
621 value["@context"] = ctx.clone();
622 }
623
624 Ok(value)
625 }
626}
627
628#[cfg(test)]
629mod test {
630 use super::*;
631 use crate::InnerSchema;
632 use anyhow::Result;
633 use serde_json::{from_value, json};
634
635 #[test]
636 fn xiaomi_thermometer() -> Result<()> {
637 let schema = json!({
638 "type": "object",
639 "properties": {
640 "temperature": {
641 "type": "number",
642 "position": 1,
643 "length": 2,
644 "scale": 0.01,
645 "byteorder": "littleendian",
646 "unit": "degree celcius"
647 },
648 "humidity": {
649 "type": "integer",
650 "position": 2,
651 "length": 1,
652 "unit": "percent"
653 },
654 "rest": {
655 "type": "integer",
656 "position": 5,
657 "length": 2
658 }
659 }
660 });
661 let schema = from_value::<DataSchema>(schema)?;
662 assert!(matches!(
663 schema,
664 DataSchema {
665 inner: InnerSchema::Object(_),
666 ..
667 }
668 ));
669 let value = json!({
670 "temperature": 22.1,
671 "humidity": 57,
672 "rest": 0
673 });
674 let mut buffer = Vec::new();
675 assert_eq!(5, schema.encode(&mut buffer, &value)?);
676 let expected = [0xA2, 0x08, 0x39, 0, 0];
677 assert_eq!(&expected, buffer.as_slice());
678 let mut cursor = std::io::Cursor::new(buffer);
679
680 let returned = schema.decode(&mut cursor)?;
681 assert_eq!(value, returned);
682
683 Ok(())
684 }
685
686 #[test]
687 fn ruuvi_tag() -> Result<()> {
688 let schema = json!({
689 "type": "object",
690 "properties": {
691 "version": {
692 "type": "integer",
693 "length": 1,
694 "default": 5,
695 "position": 1,
696 "description": "Version number of the protocol."
697 },
698 "temperature": {
699 "@type": "env:RoomTemperature",
700 "type": "number",
701 "length": 2,
702 "scale": 0.005,
703 "position": 10,
704 "unit": "degree celcius",
705 "description": "Temperature of the air surrounding the RuuviTag."
706 },
707 "humidity": {
708 "@type": "env:AirHumidity",
709 "type": "number",
710 "length": 2,
711 "scale": 0.0025,
712 "signed": false,
713 "position": 20,
714 "unit": "percent",
715 "description": "Relative humidity of the air surrounding the RuuviTag."
716 },
717 "pressure": {
718 "@type": "env:AtmosphericPressure",
719 "type": "number",
720 "length": 2,
721 "offset": 50000,
722 "signed": false,
723 "position": 30,
724 "unit": "Pa",
725 "description": "Atmospheric pressure on the RuuviTag."
726 },
727 "acceleration": {
728 "type": "object",
729 "position": 40,
730 "description": "3D accereration of the RuuviTag.",
731 "properties": {
732 "x": {
733 "type": "number",
734 "length": 2,
735 "scale": 0.001,
736 "unit": "G-force",
737 "position": 10,
738 "desription": "Acceleration in x-axis."
739 },
740 "y": {
741 "type": "number",
742 "length": 2,
743 "scale": 0.001,
744 "unit": "G-force",
745 "position": 20,
746 "desription": "Acceleration in y-axis."
747 },
748 "z": {
749 "type": "number",
750 "length": 2,
751 "scale": 0.001,
752 "unit": "G-force",
753 "position": 30,
754 "desription": "Acceleration in z-axis."
755 }
756 }
757 },
758 "battery": {
759 "type": "number",
760 "offset": 1.6,
761 "scale": 0.001,
762 "length": 2,
763 "bits": 11,
764 "bitoffset": 5,
765 "position": 50,
766 "unit": "volts",
767 "description": "Voltage of the battery powering the RuuviTag."
768 },
769 "txPower": {
770 "type": "number",
771 "offset": -40,
772 "scale": 2,
773 "length": 2,
774 "bits": 5,
775 "bitoffset": 0,
776 "position": 50,
777 "unit": "dBm",
778 "description": "Transmission power in 1m distance."
779 },
780 "moveCnt": {
781 "type": "integer",
782 "length": 1,
783 "signed": false,
784 "position": 60,
785 "description": "Number of movements (derived from accelerometer)."
786 },
787 "idx": {
788 "type": "integer",
789 "length": 2,
790 "signed": false,
791 "position": 70,
792 "description": "Measurement sequence number. Can be used to de-duplicate data."
793 },
794 "mac": {
795 "type": "string",
796 "format": "binary",
797 "minLength": 12,
798 "maxLength": 12,
799 "position": 80,
800 "description": "MAC address of the RuuviTag."
801 }
802 }
803 });
804 let _schema = from_value::<DataSchema>(schema)?;
805
806 Ok(())
807 }
808
809 #[test]
810 fn merge_bitfields() -> Result<()> {
811 let schema = json!({
812 "type": "object",
813 "properties": {
814 "battery": {
815 "type": "number",
816 "offset": 1.6,
817 "scale": 0.001,
818 "length": 2,
819 "bits": 11,
820 "bitoffset": 5,
821 "position": 50,
822 "unit": "volts",
823 "description": "Voltage of the battery powering the RuuviTag."
824 },
825 "txPower": {
826 "type": "number",
827 "offset": -40,
828 "scale": 2,
829 "length": 2,
830 "bits": 5,
831 "bitoffset": 0,
832 "position": 50,
833 "unit": "dBm",
834 "description": "Transmission power in 1m distance."
835 }
836 }
837 });
838 let schema = from_value::<DataSchema>(schema)?;
839 println!("schema:\n{:#?}", schema);
840 assert!(matches!(
841 schema,
842 DataSchema {
843 inner: InnerSchema::Object(_),
844 ..
845 }
846 ));
847
848 let value = json!({
849 "battery": 3.0,
850 "txPower": 4,
851 });
852 let mut buffer = Vec::new();
853 assert_eq!(2, schema.encode(&mut buffer, &value)?);
854 let _battery = 0b101_0111_1000; let _tx_power = 0b1_0110; let expected: [u8; 2] = [0b1010_1111, 0b0001_0110];
857 assert_eq!(&expected, buffer.as_slice());
858 let mut cursor = std::io::Cursor::new(buffer);
859
860 let _returned = schema.decode(&mut cursor)?;
861 Ok(())
865 }
866 #[test]
867 fn merge_different_bitfields() -> Result<()> {
868 let schema = json!({
869 "type": "object",
870 "properties": {
871 "num": {
872 "type": "number",
873 "position": 10,
874 "scale": 0.2,
875 "length": 1,
876 "bits": 4,
877 "bitoffset": 4,
878 },
879 "int": {
880 "type": "integer",
881 "position": 10,
882 "length": 1,
883 "bits": 3,
884 "bitoffset": 1,
885 },
886 "bool": {
887 "type": "boolean",
888 "position": 10,
889 }
890 }
891 });
892 let schema = from_value::<DataSchema>(schema)?;
893 println!("schema:\n{:#?}", schema);
894 assert!(matches!(
895 schema,
896 DataSchema {
897 inner: InnerSchema::Object(_),
898 ..
899 }
900 ));
901
902 let value = json!({
903 "num": 2.4,
904 "int": 5,
905 "bool": false
906 });
907 let mut buffer = Vec::new();
908 assert_eq!(1, schema.encode(&mut buffer, &value)?);
909 let num = 12 << 4;
910 let int = 5 << 1;
911 let bool_ = 0 << 0;
912 let expected: [u8; 1] = [num | int | bool_];
913 assert_eq!(&expected, buffer.as_slice());
914 let mut cursor = std::io::Cursor::new(buffer);
915
916 let _returned = schema.decode(&mut cursor)?;
917 Ok(())
921 }
922 #[test]
923 fn jsonld_context() -> Result<()> {
924 let schema = json!({
925 "type": "object",
926 "properties": {
927 "test": {
928 "type": "integer",
929 "length": 1,
930 "position": 10
931 }
932 },
933 "jsonld:context": "http://example.org/context.jsonld"
934 });
935 let schema = from_value::<DataSchema>(schema)?;
936 assert!(matches!(
937 schema,
938 DataSchema {
939 inner: InnerSchema::Object(ObjectSchema {
940 context: Some(_),
941 ..
942 }),
943 ..
944 }
945 ));
946
947 let buffer = vec![0x10];
948 let mut cursor = std::io::Cursor::new(buffer);
949
950 let returned = schema.decode(&mut cursor)?;
951 let expected = json!({
952 "@context": "http://example.org/context.jsonld",
953 "test": 16
954 });
955 assert_eq!(returned, expected);
956
957 Ok(())
958 }
959}