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}