@peerbit/native-backbone 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,180 @@
1
+ //! Send fusion for shared-log raw exchange-heads sync.
2
+ //!
3
+ //! The outbound counterpart of `wire_sync.rs`: the full
4
+ //! `PubSubData → RequestV0 → DecryptedThing → RawExchangeHeadsMessage` payload
5
+ //! is serialized inside this wasm module by `peerbit_wire::sync_payload`,
6
+ //! reading the entry block bytes straight from the native block store the
7
+ //! append/receive paths already committed them to. The only boundary crossing
8
+ //! is the single finished payload buffer handed to JS to become the
9
+ //! `DataMessage` payload — entry block bytes never materialize as JS values on
10
+ //! the send path.
11
+ //!
12
+ //! The store-reading core is `JsValue`-free so host `cargo test` covers it.
13
+
14
+ use js_sys::{Array, Uint32Array, Uint8Array};
15
+ use peerbit_log_rust::NativeLogBlockStore;
16
+ use peerbit_wire::sync_payload::{encode_raw_exchange_sync_payload_refs, SyncPayloadHeadRef};
17
+ use wasm_bindgen::prelude::*;
18
+
19
+ use crate::js_interop::{ensure_same_len, string_batches_from_array, strings_from_array};
20
+ use crate::NativePeerbitBackbone;
21
+
22
+ /// Marks a missing block in `block_byte_lengths` results (a real entry block
23
+ /// cannot reach this size: message payloads are bounded far below it).
24
+ pub(crate) const SYNC_SEND_MISSING_BLOCK: u32 = u32::MAX;
25
+
26
+ /// Encode the outbound raw exchange sync payload for `hashes`, resolving each
27
+ /// head's block bytes from `blocks`. Returns `None` when any block is missing
28
+ /// (callers fall back to the TS send path).
29
+ pub(crate) fn encode_sync_payload_from_store(
30
+ blocks: &NativeLogBlockStore,
31
+ topic: &str,
32
+ strict: bool,
33
+ hashes: &[String],
34
+ gid_refrences: &[Vec<String>],
35
+ reserved: [u8; 4],
36
+ ) -> Option<Vec<u8>> {
37
+ const EMPTY_REFS: &[String] = &[];
38
+ let mut heads = Vec::with_capacity(hashes.len());
39
+ for (index, hash) in hashes.iter().enumerate() {
40
+ let bytes = blocks.get_ref(hash)?;
41
+ heads.push(SyncPayloadHeadRef {
42
+ hash,
43
+ bytes,
44
+ gid_refrences: gid_refrences
45
+ .get(index)
46
+ .map(Vec::as_slice)
47
+ .unwrap_or(EMPTY_REFS),
48
+ });
49
+ }
50
+ Some(encode_raw_exchange_sync_payload_refs(
51
+ &[topic.to_string()],
52
+ strict,
53
+ &heads,
54
+ reserved,
55
+ ))
56
+ }
57
+
58
+ pub(crate) fn block_byte_lengths_core(blocks: &NativeLogBlockStore, hashes: &[String]) -> Vec<u32> {
59
+ hashes
60
+ .iter()
61
+ .map(|hash| {
62
+ blocks
63
+ .get_ref(hash)
64
+ .map(|bytes| bytes.len() as u32)
65
+ .unwrap_or(SYNC_SEND_MISSING_BLOCK)
66
+ })
67
+ .collect()
68
+ }
69
+
70
+ #[wasm_bindgen]
71
+ impl NativePeerbitBackbone {
72
+ /// Byte lengths of natively stored entry blocks for `hashes`;
73
+ /// `u32::MAX` marks a missing block. Used by the fused send path to plan
74
+ /// message chunking without materializing block bytes in JS.
75
+ pub fn sync_send_block_byte_lengths(&self, hashes: Array) -> Result<Uint32Array, JsValue> {
76
+ let hashes = strings_from_array(hashes)?;
77
+ Ok(Uint32Array::from(
78
+ block_byte_lengths_core(&self.blocks, &hashes).as_slice(),
79
+ ))
80
+ }
81
+
82
+ /// Serialize one outbound raw exchange sync payload (the full PubSubData
83
+ /// nesting) from the native block store. Returns `undefined` when any
84
+ /// head's block is not natively stored (the caller falls back to the TS
85
+ /// serialization path).
86
+ pub fn encode_raw_exchange_sync_payload(
87
+ &self,
88
+ topic: &str,
89
+ strict: bool,
90
+ hashes: Array,
91
+ gid_refrences: Array,
92
+ reserved: &[u8],
93
+ ) -> Result<JsValue, JsValue> {
94
+ let hashes = strings_from_array(hashes)?;
95
+ let gid_refrences = string_batches_from_array(gid_refrences, "sync send gid references")?;
96
+ ensure_same_len(hashes.len(), gid_refrences.len(), "sync send heads")?;
97
+ let reserved: [u8; 4] = reserved
98
+ .try_into()
99
+ .map_err(|_| JsValue::from_str("expected 4 reserved bytes"))?;
100
+ match encode_sync_payload_from_store(
101
+ &self.blocks,
102
+ topic,
103
+ strict,
104
+ &hashes,
105
+ &gid_refrences,
106
+ reserved,
107
+ ) {
108
+ Some(payload) => Ok(Uint8Array::from(payload.as_slice()).into()),
109
+ None => Ok(JsValue::UNDEFINED),
110
+ }
111
+ }
112
+ }
113
+
114
+ #[cfg(test)]
115
+ mod tests {
116
+ use super::*;
117
+ use peerbit_wire::sync_payload::{
118
+ encode_raw_exchange_sync_payload, parse_pubsub_data, parse_raw_exchange_rpc_request,
119
+ };
120
+
121
+ fn store_with(entries: &[(&str, Vec<u8>)]) -> NativeLogBlockStore {
122
+ let mut store = NativeLogBlockStore::new();
123
+ store.put_entries_core(
124
+ entries
125
+ .iter()
126
+ .map(|(hash, bytes)| (hash.to_string(), bytes.clone()))
127
+ .collect(),
128
+ );
129
+ store
130
+ }
131
+
132
+ #[test]
133
+ fn encodes_store_blocks_byte_identical_to_owned_encoder() {
134
+ let store = store_with(&[("zb2AA", vec![0xde, 0xad]), ("zb2BB", vec![1, 2, 3])]);
135
+ let hashes = vec!["zb2AA".to_string(), "zb2BB".to_string()];
136
+ let gid_refrences = vec![vec!["g1".to_string()], Vec::new()];
137
+ let payload = encode_sync_payload_from_store(
138
+ &store,
139
+ "topic",
140
+ true,
141
+ &hashes,
142
+ &gid_refrences,
143
+ [1, 0, 0, 0],
144
+ )
145
+ .unwrap();
146
+ let expected = encode_raw_exchange_sync_payload(
147
+ &["topic".to_string()],
148
+ true,
149
+ &[
150
+ (
151
+ "zb2AA".to_string(),
152
+ vec![0xde, 0xad],
153
+ vec!["g1".to_string()],
154
+ ),
155
+ ("zb2BB".to_string(), vec![1, 2, 3], Vec::new()),
156
+ ],
157
+ [1, 0, 0, 0],
158
+ );
159
+ assert_eq!(payload, expected);
160
+
161
+ let pubsub = parse_pubsub_data(&payload).unwrap();
162
+ let data = &payload[pubsub.data_offset..pubsub.data_offset + pubsub.data_length];
163
+ let parsed = parse_raw_exchange_rpc_request(data).unwrap();
164
+ assert_eq!(parsed.heads.len(), 2);
165
+ }
166
+
167
+ #[test]
168
+ fn missing_blocks_fall_back() {
169
+ let store = store_with(&[("zb2AA", vec![0xde])]);
170
+ let hashes = vec!["zb2AA".to_string(), "zb2MISSING".to_string()];
171
+ let refs = vec![Vec::new(), Vec::new()];
172
+ assert!(
173
+ encode_sync_payload_from_store(&store, "t", true, &hashes, &refs, [0; 4]).is_none()
174
+ );
175
+ assert_eq!(
176
+ block_byte_lengths_core(&store, &hashes),
177
+ vec![1, SYNC_SEND_MISSING_BLOCK]
178
+ );
179
+ }
180
+ }