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