1use crate::{InvalidVectorSizeError, ProtocolError};
2use bincode::{BorrowDecode, Encode};
3use serde::{Deserialize, Serialize};
4
5pub const EPOCH_KEY_OFFSET: u16 = 256;
7
8pub const MAX_EPOCH: u16 = u16::MAX - EPOCH_KEY_OFFSET;
10
11pub type EpochIndex = u16;
13
14pub const EPOCH_0: Epoch = Epoch {
15 index: 0,
16 key: [1, 0],
17};
18
19#[derive(Serialize, Clone, Eq, PartialEq, Copy, Debug)]
23#[serde(rename_all = "camelCase")]
24pub struct Epoch {
25 pub index: EpochIndex,
27
28 #[serde(skip)]
30 pub key: [u8; 2],
31}
32
33impl Default for Epoch {
34 fn default() -> Self {
35 Self::new(0).unwrap()
36 }
37}
38
39impl Epoch {
40 pub fn new(index: EpochIndex) -> Result<Self, ProtocolError> {
42 let index_with_offset = index
43 .checked_add(EPOCH_KEY_OFFSET)
44 .ok_or(ProtocolError::Overflow("stored epoch index too high"))?;
45 Ok(Self {
46 index,
47 key: index_with_offset.to_be_bytes(),
48 })
49 }
50}
51
52impl TryFrom<EpochIndex> for Epoch {
53 type Error = ProtocolError;
54
55 fn try_from(value: EpochIndex) -> Result<Self, Self::Error> {
56 Self::new(value)
57 }
58}
59
60impl TryFrom<&Vec<u8>> for Epoch {
61 type Error = ProtocolError;
62
63 fn try_from(value: &Vec<u8>) -> Result<Self, Self::Error> {
64 let key = value.clone().try_into().map_err(|_| {
65 ProtocolError::InvalidVectorSizeError(InvalidVectorSizeError::new(2, value.len()))
66 })?;
67 let index_with_offset = u16::from_be_bytes(key);
68 let index = index_with_offset
69 .checked_sub(EPOCH_KEY_OFFSET)
70 .ok_or(ProtocolError::Overflow("value too low, must have offset"))?;
71 Ok(Epoch { index, key })
72 }
73}
74
75impl Encode for Epoch {
76 fn encode<E: bincode::enc::Encoder>(
77 &self,
78 encoder: &mut E,
79 ) -> Result<(), bincode::error::EncodeError> {
80 self.index.encode(encoder)
81 }
82}
83
84impl<'de> Deserialize<'de> for Epoch {
89 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
90 where
91 D: serde::Deserializer<'de>,
92 {
93 #[derive(Deserialize)]
94 struct EpochData {
95 index: EpochIndex,
96 }
97
98 let data = EpochData::deserialize(deserializer)?;
99 Epoch::new(data.index).map_err(serde::de::Error::custom)
100 }
101}
102
103macro_rules! impl_epoch_decode {
105 ($decode:ident, $decoder:ident, $method:ident, $untrusted:expr) => {
106 impl<C> bincode::$decode<C> for Epoch {
107 fn $method<D: bincode::de::$decoder<Context = C>>(
108 decoder: &mut D,
109 ) -> Result<Self, bincode::error::DecodeError> {
110 let index = EpochIndex::$method(decoder)?;
111 Epoch::new(index)
112 .map_err(|e| bincode::error::DecodeError::OtherString(e.to_string()))
113 }
114 }
115 };
116}
117impl_epoch_decode!(Decode, Decoder, decode, false);
118impl_epoch_decode!(DecodeUntrusted, UntrustedDecoder, decode_untrusted, true);
119bincode::impl_borrow_decode_untrusted!(Epoch);
120
121impl<'de, C> BorrowDecode<'de, C> for Epoch {
122 fn borrow_decode<D: bincode::de::BorrowDecoder<'de, Context = C>>(
123 decoder: &mut D,
124 ) -> Result<Self, bincode::error::DecodeError> {
125 let index = EpochIndex::borrow_decode(decoder)?;
126 Epoch::new(index).map_err(|e| bincode::error::DecodeError::OtherString(e.to_string()))
127 }
128}
129
130#[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
132impl crate::serialization::JsonConvertible for Epoch {}
133
134#[cfg(all(feature = "value-conversion", feature = "serde-conversion"))]
135impl crate::serialization::ValueConvertible for Epoch {}
136
137#[cfg(all(
138 test,
139 feature = "json-conversion",
140 feature = "value-conversion",
141 feature = "serde-conversion"
142))]
143mod json_convertible_tests_epoch {
144 use super::*;
145 use platform_value::platform_value;
146 use serde_json::json;
147
148 fn fixture() -> Epoch {
149 Epoch::new(7).expect("epoch")
150 }
151
152 #[test]
153 fn json_round_trip_with_full_wire_shape() {
154 use crate::serialization::JsonConvertible;
155 let original = fixture();
156 let json = original.to_json().expect("to_json");
157 assert_eq!(json, json!({"index": 7}));
161 let recovered = Epoch::from_json(json).expect("from_json");
162 assert_eq!(original, recovered);
163 }
164
165 #[test]
166 fn value_round_trip_with_full_wire_shape() {
167 use crate::serialization::ValueConvertible;
168 let original = fixture();
169 let value = original.to_object().expect("to_object");
170 assert_eq!(value, platform_value!({"index": 7u16}));
172 let recovered = Epoch::from_object(value).expect("from_object");
173 assert_eq!(original, recovered);
174 }
175}