cuprate_rpc_types/misc/
distribution.rs

1//! Output distributions for [`crate::json::GetOutputDistributionResponse`].
2
3//---------------------------------------------------------------------------------------------------- Use
4#[cfg(feature = "serde")]
5use serde::{Deserialize, Serialize};
6
7#[cfg(feature = "epee")]
8use cuprate_epee_encoding::{
9    epee_object, error,
10    macros::bytes::{Buf, BufMut},
11    read_epee_value, write_field, EpeeObject, EpeeObjectBuilder, EpeeValue,
12};
13
14//---------------------------------------------------------------------------------------------------- Free
15/// TODO: <https://github.com/Cuprate/cuprate/pull/229#discussion_r1690531904>.
16///
17/// Used for [`Distribution::CompressedBinary::distribution`].
18#[doc = crate::macros::monero_definition_link!(
19    cc73fe71162d564ffda8e549b79a350bca53c454,
20    "rpc/core_rpc_server_commands_defs.h",
21    45..=55
22)]
23#[cfg(any(feature = "epee", feature = "serde"))]
24fn compress_integer_array(_: &[u64]) -> Vec<u8> {
25    todo!()
26}
27
28/// TODO: <https://github.com/Cuprate/cuprate/pull/229#discussion_r1690531904>.
29///
30/// Used for [`Distribution::CompressedBinary::distribution`].
31#[doc = crate::macros::monero_definition_link!(
32    cc73fe71162d564ffda8e549b79a350bca53c454,
33    "rpc/core_rpc_server_commands_defs.h",
34    57..=72
35)]
36#[cfg(any(feature = "epee", feature = "serde"))]
37fn decompress_integer_array(_: &[u8]) -> Vec<u64> {
38    todo!()
39}
40
41//---------------------------------------------------------------------------------------------------- Distribution
42#[doc = crate::macros::monero_definition_link!(
43    cc73fe71162d564ffda8e549b79a350bca53c454,
44    "rpc/core_rpc_server_commands_defs.h",
45    2468..=2508
46)]
47/// Used in [`crate::json::GetOutputDistributionResponse`].
48///
49/// # Internals
50/// This type's (de)serialization depends on `monerod`'s (de)serialization.
51///
52/// During serialization:
53/// [`Self::Uncompressed`] will emit:
54/// - `compress: false`
55///
56/// [`Self::CompressedBinary`] will emit:
57/// - `binary: true`
58/// - `compress: true`
59///
60/// Upon deserialization, the presence of a `compressed_data`
61/// field signifies that the [`Self::CompressedBinary`] should
62/// be selected.
63#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
64#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
65#[cfg_attr(feature = "serde", serde(untagged))]
66pub enum Distribution {
67    /// Distribution data will be (de)serialized as either JSON or binary (uncompressed).
68    Uncompressed(DistributionUncompressed),
69    /// Distribution data will be (de)serialized as compressed binary.
70    CompressedBinary(DistributionCompressedBinary),
71}
72
73impl Default for Distribution {
74    fn default() -> Self {
75        Self::Uncompressed(DistributionUncompressed::default())
76    }
77}
78
79/// Data within [`Distribution::Uncompressed`].
80#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
81#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
82pub struct DistributionUncompressed {
83    pub start_height: u64,
84    pub base: u64,
85    /// TODO: this is a binary JSON string if `binary == true`.
86    pub distribution: Vec<u64>,
87    pub amount: u64,
88    pub binary: bool,
89}
90
91#[cfg(feature = "epee")]
92epee_object! {
93    DistributionUncompressed,
94    start_height: u64,
95    base: u64,
96    distribution: Vec<u64>,
97    amount: u64,
98    binary: bool,
99}
100
101/// Data within [`Distribution::CompressedBinary`].
102#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
103#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
104pub struct DistributionCompressedBinary {
105    pub start_height: u64,
106    pub base: u64,
107    #[cfg_attr(
108        feature = "serde",
109        serde(serialize_with = "serialize_distribution_as_compressed_data")
110    )]
111    #[cfg_attr(
112        feature = "serde",
113        serde(deserialize_with = "deserialize_compressed_data_as_distribution")
114    )]
115    #[cfg_attr(feature = "serde", serde(rename = "compressed_data"))]
116    pub distribution: Vec<u64>,
117    pub amount: u64,
118}
119
120#[cfg(feature = "epee")]
121epee_object! {
122    DistributionCompressedBinary,
123    start_height: u64,
124    base: u64,
125    distribution: Vec<u64>,
126    amount: u64,
127}
128
129/// Serializer function for [`DistributionCompressedBinary::distribution`].
130///
131/// 1. Compresses the distribution array
132/// 2. Serializes the compressed data
133#[cfg(feature = "serde")]
134#[expect(clippy::ptr_arg)]
135fn serialize_distribution_as_compressed_data<S>(v: &Vec<u64>, s: S) -> Result<S::Ok, S::Error>
136where
137    S: serde::Serializer,
138{
139    compress_integer_array(v).serialize(s)
140}
141
142/// Deserializer function for [`DistributionCompressedBinary::distribution`].
143///
144/// 1. Deserializes as `compressed_data` field.
145/// 2. Decompresses and returns the data
146#[cfg(feature = "serde")]
147fn deserialize_compressed_data_as_distribution<'de, D>(d: D) -> Result<Vec<u64>, D::Error>
148where
149    D: serde::Deserializer<'de>,
150{
151    Vec::<u8>::deserialize(d).map(|v| decompress_integer_array(&v))
152}
153
154//---------------------------------------------------------------------------------------------------- Epee
155#[cfg(feature = "epee")]
156/// [`EpeeObjectBuilder`] for [`Distribution`].
157///
158/// Not for public usage.
159#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
160#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
161pub struct __DistributionEpeeBuilder {
162    pub start_height: Option<u64>,
163    pub base: Option<u64>,
164    pub distribution: Option<Vec<u64>>,
165    pub amount: Option<u64>,
166    pub compressed_data: Option<Vec<u8>>,
167    pub binary: Option<bool>,
168    pub compress: Option<bool>,
169}
170
171#[cfg(feature = "epee")]
172impl EpeeObjectBuilder<Distribution> for __DistributionEpeeBuilder {
173    fn add_field<B: Buf>(&mut self, name: &str, r: &mut B) -> error::Result<bool> {
174        macro_rules! read_epee_field {
175            ($($field:ident),*) => {
176                match name {
177                    $(
178                        stringify!($field) => { self.$field = Some(read_epee_value(r)?); },
179                    )*
180                    _ => return Ok(false),
181                }
182            };
183        }
184
185        read_epee_field! {
186            start_height,
187            base,
188            amount,
189            binary,
190            compress,
191            compressed_data,
192            distribution
193        }
194
195        Ok(true)
196    }
197
198    fn finish(self) -> error::Result<Distribution> {
199        const ELSE: error::Error = error::Error::Format("Required field was not found!");
200
201        let start_height = self.start_height.ok_or(ELSE)?;
202        let base = self.base.ok_or(ELSE)?;
203        let amount = self.amount.ok_or(ELSE)?;
204
205        let distribution = if let Some(compressed_data) = self.compressed_data {
206            let distribution = decompress_integer_array(&compressed_data);
207            Distribution::CompressedBinary(DistributionCompressedBinary {
208                start_height,
209                base,
210                distribution,
211                amount,
212            })
213        } else if let Some(distribution) = self.distribution {
214            Distribution::Uncompressed(DistributionUncompressed {
215                binary: self.binary.ok_or(ELSE)?,
216                distribution,
217                start_height,
218                base,
219                amount,
220            })
221        } else {
222            return Err(ELSE);
223        };
224
225        Ok(distribution)
226    }
227}
228
229#[cfg(feature = "epee")]
230impl EpeeObject for Distribution {
231    type Builder = __DistributionEpeeBuilder;
232
233    fn number_of_fields(&self) -> u64 {
234        match self {
235            // Inner struct fields + `compress`.
236            Self::Uncompressed(s) => s.number_of_fields() + 1,
237            // Inner struct fields + `compress` + `binary`.
238            Self::CompressedBinary(s) => s.number_of_fields() + 2,
239        }
240    }
241
242    fn write_fields<B: BufMut>(self, w: &mut B) -> error::Result<()> {
243        match self {
244            Self::Uncompressed(s) => {
245                s.write_fields(w)?;
246                write_field(false, "compress", w)?;
247            }
248
249            Self::CompressedBinary(DistributionCompressedBinary {
250                start_height,
251                base,
252                distribution,
253                amount,
254            }) => {
255                let compressed_data = compress_integer_array(&distribution);
256
257                start_height.write(w)?;
258                base.write(w)?;
259                compressed_data.write(w)?;
260                amount.write(w)?;
261
262                write_field(true, "binary", w)?;
263                write_field(true, "compress", w)?;
264            }
265        }
266
267        Ok(())
268    }
269}
270
271//---------------------------------------------------------------------------------------------------- Tests
272#[cfg(test)]
273mod tests {
274    // use pretty_assertions::assert_eq;
275
276    // use super::*;
277
278    // TODO: re-enable tests after (de)compression functions are implemented.
279
280    // /// Tests that [`compress_integer_array`] outputs as expected.
281    // #[test]
282    // fn compress() {
283    //     let varints = &[16_384, 16_383, 16_382, 16_381];
284    //     let bytes = compress_integer_array(varints).unwrap();
285
286    //     let expected = [2, 0, 1, 0, 253, 255, 249, 255, 245, 255];
287    //     assert_eq!(expected, *bytes);
288    // }
289
290    // /// Tests that [`decompress_integer_array`] outputs as expected.
291    // #[test]
292    // fn decompress() {
293    //     let bytes = &[2, 0, 1, 0, 253, 255, 249, 255, 245, 255];
294    //     let varints = decompress_integer_array(bytes);
295
296    //     let expected = vec![16_384, 16_383, 16_382, 16_381];
297    //     assert_eq!(expected, varints);
298    // }
299}