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
171
172
173
174
175
176
177
178
179
use num_traits::Zero;

use crate::hash::{OpHash, RegionHash};

use super::RegionDataConstraints;

/// Take bitwise XOR of each element of both arrays
pub fn array_xor<const N: usize>(a: &mut [u8; N], b: &[u8; N]) {
    for i in 0..N {
        a[i] ^= b[i];
    }
}

/// Take bitwise XOR of each element of both slices
pub fn slice_xor(a: &mut [u8], b: &[u8]) {
    debug_assert_eq!(a.len(), b.len());
    for i in 0..a.len() {
        a[i] ^= b[i];
    }
}

impl RegionHash {
    /// Any null node hashes just get ignored.
    pub fn xor(&mut self, other: &Self) {
        array_xor(&mut *self, other);
    }
}

impl std::ops::Add for RegionHash {
    type Output = Self;

    fn add(mut self, rhs: Self) -> Self::Output {
        Self::xor(&mut self, &rhs);
        self
    }
}

impl num_traits::Zero for RegionHash {
    fn zero() -> Self {
        Self::new([0; 32])
    }

    fn is_zero(&self) -> bool {
        *self == Self::zero()
    }
}

impl std::iter::Sum for RegionHash {
    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
        iter.reduce(|a, b| a + b).unwrap_or_else(RegionHash::zero)
    }
}

impl From<OpHash> for RegionHash {
    fn from(h: OpHash) -> Self {
        Self::new(h.0)
    }
}

/// The pertinent data that we care about for each Region. This is what gets
/// sent over gossip so that nodes can discover which Regions are different
/// between them.
///
/// The size and count data can also act as heuristics to help us fine-tune the
/// gossip algorithm, although currently they are unused (except for the purpose
/// of disambiguation in the rare case of an XOR hash collision).
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(from = "RegionDataCompact")]
#[serde(into = "RegionDataCompact")]
pub struct RegionData {
    /// The XOR of hashes of all Ops in this Region
    pub hash: RegionHash,
    /// The total size of Op data contained in this Region
    pub size: u32,
    /// The number of Ops in this Region.
    pub count: u32,
}

impl RegionDataConstraints for RegionData {
    fn count(&self) -> u32 {
        self.count
    }

    fn size(&self) -> u32 {
        self.size
    }
}

impl num_traits::Zero for RegionData {
    fn zero() -> Self {
        Self {
            hash: RegionHash::zero(),
            size: 0,
            count: 0,
        }
    }

    fn is_zero(&self) -> bool {
        if self.count == 0 {
            debug_assert_eq!(self.size, 0);
            debug_assert_eq!(self.hash, RegionHash::zero());
            true
        } else {
            false
        }
    }
}

impl std::ops::AddAssign for RegionData {
    fn add_assign(&mut self, other: Self) {
        self.hash.xor(&other.hash);
        self.size += other.size;
        self.count += other.count;
    }
}

impl std::ops::Add for RegionData {
    type Output = Self;

    fn add(mut self, other: Self) -> Self::Output {
        self += other;
        self
    }
}

impl std::iter::Sum for RegionData {
    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
        iter.reduce(|a, b| a + b).unwrap_or_else(RegionData::zero)
    }
}

impl std::ops::SubAssign for RegionData {
    fn sub_assign(&mut self, other: Self) {
        // XOR works as both addition and subtraction
        self.hash.xor(&other.hash);
        self.size -= other.size;
        self.count -= other.count;
    }
}

impl std::ops::Sub for RegionData {
    type Output = Self;

    fn sub(mut self, other: Self) -> Self::Output {
        self -= other;
        self
    }
}

/// Tuple-based representation of RegionData, used for sending more compact
/// wire messages
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct RegionDataCompact(RegionHash, u32, u32);

impl From<RegionData> for RegionDataCompact {
    fn from(d: RegionData) -> Self {
        Self(d.hash, d.size, d.count)
    }
}

impl From<RegionDataCompact> for RegionData {
    fn from(RegionDataCompact(hash, size, count): RegionDataCompact) -> Self {
        Self { hash, size, count }
    }
}

#[test]
fn region_data_is_compact() {
    let hash: RegionHash = crate::hash::fake_hash().into();
    let original = holochain_serialized_bytes::encode(&RegionData {
        hash: hash.clone(),
        size: 1111,
        count: 11,
    })
    .unwrap();
    let compact =
        holochain_serialized_bytes::encode(&RegionDataCompact(hash.clone(), 1111, 11)).unwrap();
    assert_eq!(compact.len(), original.len());
}