1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
//! COSEM data types such as timestamps or fixed point numbers as per section 6.4.
//!
//! Guaranteed to either be parsed stack-compatible types or buffer references.

use serde::{Deserialize, Serialize};

use crate::{Error, Result};

/// Octet strings as defined by tag 9.
#[derive(Debug, Serialize, Deserialize)]
pub struct OctetString<'a>(&'a str);

impl<'a> OctetString<'a> {
    /// Parse a fixed length string from an OBIS body.
    pub fn parse(body: &'a str, length: usize) -> Result<OctetString<'a>> {
        Ok(OctetString(
            body.get(1..=length).ok_or(Error::InvalidFormat)?,
        ))
    }

    /// Parse a variable length string with a max length from an OBIS body.
    pub fn parse_max(body: &'a str, max_length: usize) -> Result<OctetString<'a>> {
        let end = body.find(')').ok_or(Error::InvalidFormat)? - 1;
        if end > max_length {
            return Err(Error::InvalidFormat);
        }

        OctetString::parse(body, end)
    }

    /// Yield this octet string as the underlying octets.
    pub fn as_octets(&'a self) -> impl core::iter::Iterator<Item = Result<u8>> + 'a {
        (0..self.0.len() / 2).map(move |i| {
            u8::from_str_radix(&self.0[i * 2..=i * 2 + 1], 16).map_err(|_| Error::InvalidFormat)
        })
    }
}

/// Timestamps.
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct TST {
    pub year: u8,
    pub month: u8,
    pub day: u8,
    pub hour: u8,
    pub minute: u8,
    pub second: u8,

    /// Daylight savings time.
    pub dst: bool,
}

impl TST {
    pub fn parse(body: &str) -> Result<TST> {
        if body.len() < 15 {
            return Err(Error::InvalidFormat);
        }

        let parsetwo = |i| body[i..=(i + 1)].parse().map_err(|_| Error::InvalidFormat);

        Ok(TST {
            year: parsetwo(1)?,
            month: parsetwo(3)?,
            day: parsetwo(5)?,
            hour: parsetwo(7)?,
            minute: parsetwo(9)?,
            second: parsetwo(11)?,
            dst: match &body[13..=13] {
                "S" => Ok(true),
                "W" => Ok(false),
                _ => Err(Error::InvalidFormat),
            }?,
        })
    }
}

/// Fixed length unsigned doubles as defined by tag 6.
#[derive(Debug, Serialize, Deserialize)]
pub struct UFixedDouble {
    buffer: u64,
    point: u8,
}

impl UFixedDouble {
    pub fn parse(body: &str, length: usize, point: u8) -> Result<UFixedDouble> {
        // Do not forget the extra '.'
        let buffer = body.get(1..length + 2).ok_or(Error::InvalidFormat)?;
        let (upper, lower) = buffer.split_at(length - point as usize);

        let upper: u64 = upper.parse().map_err(|_| Error::InvalidFormat)?;
        let lower: u64 = lower[1..].parse().map_err(|_| Error::InvalidFormat)?;

        Ok(UFixedDouble {
            buffer: upper * 10u64.pow(u32::from(point)) + lower,
            point,
        })
    }
}

impl core::convert::From<&UFixedDouble> for f64 {
    fn from(other: &UFixedDouble) -> Self {
        other.buffer as f64 / (10u64.pow(u32::from(other.point)) as f64)
    }
}

/// Fixed length unsigned integers as defined by tags 15-21.
#[derive(Debug)]
pub struct UFixedInteger(pub u64);

impl UFixedInteger {
    pub fn parse(body: &str, length: usize) -> Result<UFixedInteger> {
        let buffer = body.get(1..=length).ok_or(Error::InvalidFormat)?;
        let number = buffer.parse().map_err(|_| Error::InvalidFormat)?;

        Ok(UFixedInteger(number))
    }
}