ssb_json_msg_data/
lib.rs

1//! This crate implements the ssb
2//! [legacy data format](https://spec.scuttlebutt.nz/datamodel.html),
3//! i.e. the free-form data that forms the content of legacy messages.
4//!
5//! Two encodings are implemented: the
6//! [signing encoding](https://spec.scuttlebutt.nz/datamodel.html#signing-encoding), and the
7//! [json transport encoding](https://spec.scuttlebutt.nz/datamodel.html#json-transport-encoding).
8#![warn(missing_docs)]
9
10extern crate encode_unicode;
11extern crate indexmap;
12extern crate lexical_core;
13extern crate ryu_ecmascript;
14extern crate serde;
15#[macro_use]
16extern crate serde_derive;
17extern crate base64;
18
19pub mod json;
20pub mod value;
21
22use std::cmp::Ordering;
23use std::fmt;
24
25/// A wrapper around `f64` to indicate that the float is compatible with the ssb legacy message
26/// data model, i.e. it is [neither an infinity, nor `-0.0`, nor a `NaN`](https://spec.scuttlebutt.nz/datamodel.html#floats).
27///
28/// Because a `LegacyF64` is never `NaN`, it can implement `Eq` and `Ord`, which regular `f64`
29/// can not.
30///
31/// To obtain the inner value, use the `From<LegacyF64> for f64` impl.
32#[derive(Clone, Copy, PartialEq, PartialOrd, Default, Serialize, Deserialize)]
33pub struct LegacyF64(f64);
34
35impl LegacyF64 {
36    /// Safe conversion of an arbitrary `f64` into a `LegacyF64`.
37    ///
38    /// ```
39    /// use ssb_json_msg_data::LegacyF64;
40    ///
41    /// assert!(LegacyF64::from_f64(0.0).is_some());
42    /// assert!(LegacyF64::from_f64(-1.1).is_some());
43    /// assert!(LegacyF64::from_f64(-0.0).is_none());
44    /// assert!(LegacyF64::from_f64(std::f64::INFINITY).is_none());
45    /// assert!(LegacyF64::from_f64(std::f64::NEG_INFINITY).is_none());
46    /// assert!(LegacyF64::from_f64(std::f64::NAN).is_none());
47    /// ```
48    pub fn from_f64(f: f64) -> Option<LegacyF64> {
49        if LegacyF64::is_valid(f) {
50            Some(LegacyF64(f))
51        } else {
52            None
53        }
54    }
55
56    /// Wraps the given `f64` as a `LegacyF64` without checking if it is valid.
57    ///
58    /// When the `debug_assertions` feature is enabled (when compiling without optimizations),
59    /// this function panics when given an invalid `f64`.
60    ///
61    /// # Safety
62    /// You must not pass infinity, negative infinity, negative zero or a `NaN` to this
63    /// function. Any method on the resulting `LegacyF64` could panic or exhibit undefined
64    /// behavior.
65    ///
66    /// ```
67    /// use ssb_json_msg_data::LegacyF64;
68    ///
69    /// let fine = unsafe { LegacyF64::from_f64_unchecked(1.1) };
70    ///
71    /// // Never do this:
72    /// // let everything_is_terrible = unsafe { LegacyF64::from_f64_unchecked(-0.0) };
73    /// ```
74    pub unsafe fn from_f64_unchecked(f: f64) -> LegacyF64 {
75        debug_assert!(LegacyF64::is_valid(f));
76        LegacyF64(f)
77    }
78
79    /// Checks whether a given `f64`
80    /// [may be used](https://spec.scuttlebutt.nz/datamodel.html#floats) as a `LegacyF64`.
81    pub fn is_valid(f: f64) -> bool {
82        if f == 0.0 {
83            f.is_sign_positive()
84        } else {
85            f.is_finite() && (f != 0.0)
86        }
87    }
88}
89
90impl fmt::Display for LegacyF64 {
91    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
92        self.0.fmt(f)
93    }
94}
95
96impl fmt::Debug for LegacyF64 {
97    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
98        self.0.fmt(f)
99    }
100}
101
102impl Eq for LegacyF64 {}
103
104impl Ord for LegacyF64 {
105    fn cmp(&self, other: &Self) -> Ordering {
106        self.partial_cmp(other).unwrap()
107    }
108}
109
110impl From<LegacyF64> for f64 {
111    fn from(f: LegacyF64) -> Self {
112        f.0
113    }
114}
115
116/// Checks whether a given `u64` is allowed for usage in ssb data (it is
117/// not larger than 2^53).
118pub fn is_u64_valid(n: u64) -> bool {
119    n < 9007199254740992
120}
121
122/// Checks whether a given `i64` is allowed for usage in ssb data (its
123/// absolute value is not larger than 2^53).
124pub fn is_i64_valid(n: i64) -> bool {
125    n < 9007199254740992 && n > -9007199254740992
126}
127
128/// An iterator that yields the
129/// [bytes](https://spec.scuttlebutt.nz/datamodel.html#legacy-hash-computation) needed to compute
130/// a hash of some legacy data.
131///
132/// Created by [`to_weird_encoding`](to_weird_encoding).
133///
134/// The total number of bytes yielded by this is also the
135/// [length](https://spec.scuttlebutt.nz/datamodel.html#legacy-length-computation) of the data.
136pub struct WeirdEncodingIterator<'a>(std::iter::Map<std::str::EncodeUtf16<'a>, fn(u16) -> u8>);
137
138impl<'a> Iterator for WeirdEncodingIterator<'a> {
139    type Item = u8;
140
141    fn next(&mut self) -> Option<Self::Item> {
142        self.0.next()
143    }
144}
145
146/// Create an owned representation of the
147/// [weird encoding](https://spec.scuttlebutt.nz/datamodel.html#legacy-hash-computation)
148/// used for hash computation of legacy ssb messages. The number of bytes yielded by this
149/// iterator coincides with the
150/// [length](https://spec.scuttlebutt.nz/datamodel.html#legacy-length-computation)
151/// of the data.
152pub fn to_weird_encoding<'a>(s: &'a str) -> WeirdEncodingIterator<'a> {
153    WeirdEncodingIterator(s.encode_utf16().map(|x| x as u8))
154}
155
156/// Compute the [length](https://spec.scuttlebutt.nz/datamodel.html#legacy-length-computation)
157/// of some data. Note that this takes time linear in the length of the data,
158/// so you might want to use a [`WeirdEncodingIterator`](WeirdEncodingIterator)
159/// for computing hash and length in one go.
160pub fn legacy_length(s: &str) -> usize {
161    let mut len = 0;
162    for c in s.chars() {
163        if c as u32 <= 0xFFFF {
164            len += 1;
165        } else {
166            len += 2;
167        }
168    }
169    len
170}