1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
//! This crate implements the ssb
//! [legacy data format](https://spec.scuttlebutt.nz/datamodel.html),
//! i.e. the free-form data that forms the content of legacy messages.
//!
//! Two encodings are implemented: the
//! [signing encoding](https://spec.scuttlebutt.nz/datamodel.html#signing-encoding), and the
//! [json transport encoding](https://spec.scuttlebutt.nz/datamodel.html#json-transport-encoding).
#![warn(missing_docs)]

extern crate encode_unicode;
extern crate indexmap;
extern crate ryu_ecmascript;
extern crate serde;
extern crate strtod2;
#[macro_use]
extern crate serde_derive;
extern crate base64;

pub mod json;
pub mod value;

use std::cmp::Ordering;
use std::fmt;

/// A wrapper around `f64` to indicate that the float is compatible with the ssb legacy message
/// data model, i.e. it is [neither an infinity, nor `-0.0`, nor a `NaN`](https://spec.scuttlebutt.nz/datamodel.html#floats).
///
/// Because a `LegacyF64` is never `NaN`, it can implement `Eq` and `Ord`, which regular `f64`
/// can not.
///
/// To obtain the inner value, use the `From<LegacyF64> for f64` impl.
#[derive(Clone, Copy, PartialEq, PartialOrd, Default, Serialize, Deserialize)]
pub struct LegacyF64(f64);

impl LegacyF64 {
    /// Safe conversion of an arbitrary `f64` into a `LegacyF64`.
    ///
    /// ```
    /// use ssb_legacy_msg_data::LegacyF64;
    ///
    /// assert!(LegacyF64::from_f64(0.0).is_some());
    /// assert!(LegacyF64::from_f64(-1.1).is_some());
    /// assert!(LegacyF64::from_f64(-0.0).is_none());
    /// assert!(LegacyF64::from_f64(std::f64::INFINITY).is_none());
    /// assert!(LegacyF64::from_f64(std::f64::NEG_INFINITY).is_none());
    /// assert!(LegacyF64::from_f64(std::f64::NAN).is_none());
    /// ```
    pub fn from_f64(f: f64) -> Option<LegacyF64> {
        if LegacyF64::is_valid(f) {
            Some(LegacyF64(f))
        } else {
            None
        }
    }

    /// Wraps the given `f64` as a `LegacyF64` without checking if it is valid.
    ///
    /// When the `debug_assertions` feature is enabled (when compiling without optimizations),
    /// this function panics when given an invalid `f64`.
    ///
    /// # Safety
    /// You must not pass infinity, negative infinity, negative zero or a `NaN` to this
    /// function. Any method on the resulting `LegacyF64` could panic or exhibit undefined
    /// behavior.
    ///
    /// ```
    /// use ssb_legacy_msg_data::LegacyF64;
    ///
    /// let fine = unsafe { LegacyF64::from_f64_unchecked(1.1) };
    ///
    /// // Never do this:
    /// // let everything_is_terrible = unsafe { LegacyF64::from_f64_unchecked(-0.0) };
    /// ```
    pub unsafe fn from_f64_unchecked(f: f64) -> LegacyF64 {
        debug_assert!(LegacyF64::is_valid(f));
        LegacyF64(f)
    }

    /// Checks whether a given `f64`
    /// [may be used](https://spec.scuttlebutt.nz/datamodel.html#floats) as a `LegacyF64`.
    pub fn is_valid(f: f64) -> bool {
        if f == 0.0 {
            f.is_sign_positive()
        } else {
            f.is_finite() && (f != 0.0)
        }
    }
}

impl fmt::Display for LegacyF64 {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        self.0.fmt(f)
    }
}

impl fmt::Debug for LegacyF64 {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        self.0.fmt(f)
    }
}

impl Eq for LegacyF64 {}

impl Ord for LegacyF64 {
    fn cmp(&self, other: &Self) -> Ordering {
        self.partial_cmp(other).unwrap()
    }
}

impl From<LegacyF64> for f64 {
    fn from(f: LegacyF64) -> Self {
        f.0
    }
}

/// Checks whether a given `u64` is allowed for usage in ssb data (it is
/// not larger than 2^53).
pub fn is_u64_valid(n: u64) -> bool {
    n < 9007199254740992
}

/// Checks whether a given `i64` is allowed for usage in ssb data (its
/// absolute value is not larger than 2^53).
pub fn is_i64_valid(n: i64) -> bool {
    n < 9007199254740992 && n > -9007199254740992
}

/// An iterator that yields the
/// [bytes](https://spec.scuttlebutt.nz/datamodel.html#legacy-hash-computation) needed to compute
/// a hash of some legacy data.
///
/// Created by [`to_weird_encoding`](to_weird_encoding).
///
/// The total number of bytes yielded by this is also the
/// [length](https://spec.scuttlebutt.nz/datamodel.html#legacy-length-computation) of the data.
pub struct WeirdEncodingIterator<'a>(std::iter::Map<std::str::EncodeUtf16<'a>, fn(u16) -> u8>);

impl<'a> Iterator for WeirdEncodingIterator<'a> {
    type Item = u8;

    fn next(&mut self) -> Option<Self::Item> {
        self.0.next()
    }
}

/// Create an owned representation of the
/// [weird encoding](https://spec.scuttlebutt.nz/datamodel.html#legacy-hash-computation)
/// used for hash computation of legacy ssb messages. The number of bytes yielded by this
/// iterator coincides with the
/// [length](https://spec.scuttlebutt.nz/datamodel.html#legacy-length-computation)
/// of the data.
pub fn to_weird_encoding<'a>(s: &'a str) -> WeirdEncodingIterator<'a> {
    WeirdEncodingIterator(s.encode_utf16().map(|x| x as u8))
}

/// Compute the [length](https://spec.scuttlebutt.nz/datamodel.html#legacy-length-computation)
/// of some data. Note that this takes time linear in the length of the data,
/// so you might want to use a [`WeirdEncodingIterator`](WeirdEncodingIterator)
/// for computing hash and length in one go.
pub fn legacy_length(s: &str) -> usize {
    let mut len = 0;
    for c in s.chars() {
        if c as u32 <= 0xFFFF {
            len += 1;
        } else {
            len += 2;
        }
    }
    len
}