Skip to main content

amaru_kernel/utils/cbor/
serialised_as_pico.rs

1// Copyright 2026 PRAGMA
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::time::Duration;
16
17use num::BigUint;
18
19use crate::cbor;
20
21/// A newtype wrapper meant to facilitate CBOR encoding of time::Duration as integers with
22/// pico-precision. This may seem odd, but is necessary to mimic the encoding behavior of *some*
23/// Haskell types such as 'RelativeTime'.
24///
25/// Note that the Haskell RelativeTime truncates to whole seconds, which this helper does not.
26/// Haskell should be able to read values encoded by this helper, but will truncate to whole seconds
27/// when computing with the values.
28///
29/// TODO: Maybe consider promoting this as `RelativeTime`, for robustness and to avoid some
30/// confusing naming...
31#[derive(Debug)]
32#[repr(transparent)]
33pub struct SerialisedAsPico(Duration);
34
35impl From<SerialisedAsPico> for Duration {
36    fn from(t: SerialisedAsPico) -> Self {
37        t.0
38    }
39}
40
41impl From<Duration> for SerialisedAsPico {
42    fn from(d: Duration) -> Self {
43        Self(d)
44    }
45}
46
47impl SerialisedAsPico {
48    pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
49    where
50        D: serde::Deserializer<'de>,
51    {
52        Ok(Duration::from_secs(serde::Deserialize::deserialize(deserializer)?))
53    }
54
55    pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
56    where
57        S: serde::Serializer,
58    {
59        serde::Serialize::serialize(&duration.as_secs(), serializer)
60    }
61}
62
63impl<C> cbor::Encode<C> for SerialisedAsPico {
64    fn encode<W: cbor::encode::Write>(
65        &self,
66        e: &mut cbor::Encoder<W>,
67        ctx: &mut C,
68    ) -> Result<(), cbor::encode::Error<W::Error>> {
69        let s = self.0.as_nanos();
70        match s.checked_mul(1000).and_then(|s| u64::try_from(s).ok()) {
71            Some(t) => t.encode(e, ctx),
72            None => {
73                e.tag(cbor::IanaTag::PosBignum)?;
74                e.bytes(&(BigUint::from(s) * 1000u16).to_bytes_be())?;
75                Ok(())
76            }
77        }
78    }
79}
80
81impl<'b, C> cbor::Decode<'b, C> for SerialisedAsPico {
82    #[allow(clippy::wildcard_enum_match_arm)]
83    fn decode(d: &mut cbor::Decoder<'b>, _ctx: &mut C) -> Result<Self, cbor::decode::Error> {
84        use cbor::Type::*;
85        match d.datatype()? {
86            Tag => {
87                cbor::expect_tag(d, cbor::IanaTag::PosBignum)?;
88                let nanos = BigUint::from_bytes_be(d.bytes()?) / 1000u16;
89                match u128::try_from(nanos) {
90                    Ok(nanos) => {
91                        if nanos < (u64::MAX as u128) * 1_000_000_000 {
92                            Ok(Self(Duration::from_nanos_u128(nanos)))
93                        } else {
94                            Err(cbor::decode::Error::message(format!(
95                                "cannot convert to Duration, too large: {nanos}ns"
96                            )))
97                        }
98                    }
99                    Err(nanos) => Err(cbor::decode::Error::message(format!(
100                        "cannot convert to Duration, too large: {}ns",
101                        nanos.into_original()
102                    ))),
103                }
104            }
105            U64 | U32 | U16 | U8 => Ok(Self(Duration::from_nanos(d.u64()? / 1000))),
106            t => Err(cbor::decode::Error::message(format!("Unhandled type decoding SerialisedAsPico: {t}"))),
107        }
108    }
109}