ssb_legacy_msg_data/lib.rs
1//! This crate implements the ssb
2//! [legacy data format](https://spec.scuttlebutt.nz/feed/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/feed/datamodel.html#signing-encoding), and the
7//! [json transport encoding](https://spec.scuttlebutt.nz/feed/datamodel.html#json-transport-encoding).
8#![warn(missing_docs)]
9
10extern crate encode_unicode;
11extern crate indexmap;
12extern crate ryu_ecmascript;
13extern crate serde;
14extern crate strtod2;
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/feed/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, 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_legacy_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_legacy_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/feed/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 PartialOrd for LegacyF64 {
111 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
112 Some(self.cmp(other))
113 }
114}
115
116impl From<LegacyF64> for f64 {
117 fn from(f: LegacyF64) -> Self {
118 f.0
119 }
120}
121
122/// Checks whether a given `u64` is allowed for usage in ssb data (it is
123/// not larger than 2^53).
124pub fn is_u64_valid(n: u64) -> bool {
125 n < 9007199254740992
126}
127
128/// Checks whether a given `i64` is allowed for usage in ssb data (its
129/// absolute value is not larger than 2^53).
130pub fn is_i64_valid(n: i64) -> bool {
131 n < 9007199254740992 && n > -9007199254740992
132}
133
134/// An iterator that yields the
135/// [bytes](https://spec.scuttlebutt.nz/feed/datamodel.html#legacy-hash-computation) needed to compute
136/// a hash of some legacy data.
137///
138/// Created by [`to_weird_encoding`](to_weird_encoding).
139///
140/// The total number of bytes yielded by this is also the
141/// [length](https://spec.scuttlebutt.nz/feed/datamodel.html#legacy-length-computation) of the data.
142pub struct WeirdEncodingIterator<'a>(std::iter::Map<std::str::EncodeUtf16<'a>, fn(u16) -> u8>);
143
144impl<'a> Iterator for WeirdEncodingIterator<'a> {
145 type Item = u8;
146
147 fn next(&mut self) -> Option<Self::Item> {
148 self.0.next()
149 }
150}
151
152/// Create an owned representation of the
153/// [weird encoding](https://spec.scuttlebutt.nz/feed/datamodel.html#legacy-hash-computation)
154/// used for hash computation of legacy ssb messages. The number of bytes yielded by this
155/// iterator coincides with the
156/// [length](https://spec.scuttlebutt.nz/feed/datamodel.html#legacy-length-computation)
157/// of the data.
158pub fn to_weird_encoding(s: &str) -> WeirdEncodingIterator {
159 WeirdEncodingIterator(s.encode_utf16().map(|x| x as u8))
160}
161
162/// Compute the [length](https://spec.scuttlebutt.nz/feed/datamodel.html#legacy-length-computation)
163/// of some data. Note that this takes time linear in the length of the data,
164/// so you might want to use a [`WeirdEncodingIterator`](WeirdEncodingIterator)
165/// for computing hash and length in one go.
166pub fn legacy_length(s: &str) -> usize {
167 let mut len = 0;
168 for c in s.chars() {
169 if c as u32 <= 0xFFFF {
170 len += 1;
171 } else {
172 len += 2;
173 }
174 }
175 len
176}