yrb-lite 0.1.0.beta6 → 0.1.0.beta7
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.
- checksums.yaml +4 -4
- data/ext/yrb_lite/src/lib.rs +15 -99
- data/ext/yrb_lite/src/protocol.rs +11 -323
- data/lib/yrb_lite/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ea20d7a00b104460b01812d463df581b45042d63491ff1e5494acd478fa2047b
|
|
4
|
+
data.tar.gz: 731010497572899b225ff36fd792e635738d05bad0f41640a4f991af56540d3e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 83682aa86e7753b9270274051e0b9850c41e35559817cbae981debe68de53d4b1cd56cff5b21b0d63b411e87ab550cd7ffc11c0ec0c3005845e02e5af145d078
|
|
7
|
+
data.tar.gz: 282965720af81ee289feb0f555ff828fd593ba24f869054a7792f420c61ba03c87168b963adb6259e143b103c398e70855365d48c1f4cd6a7e693ae8491026b9
|
data/ext/yrb_lite/src/lib.rs
CHANGED
|
@@ -5,13 +5,11 @@ use std::sync::Mutex;
|
|
|
5
5
|
use yrs::sync::{Awareness, DefaultProtocol, Message, Protocol, SyncMessage};
|
|
6
6
|
use yrs::updates::decoder::Decode;
|
|
7
7
|
use yrs::updates::encoder::{Encode, Encoder, EncoderV1};
|
|
8
|
-
use yrs::{
|
|
8
|
+
use yrs::{Doc, ReadTxn, Transact};
|
|
9
9
|
|
|
10
10
|
mod protocol;
|
|
11
11
|
use protocol::{
|
|
12
|
-
|
|
13
|
-
update_advances_doc, update_is_ready, validate_frame_client_ids,
|
|
14
|
-
validate_state_vector_client_ids, validate_update_client_ids,
|
|
12
|
+
classify_message, doc_has_pending, merged_doc_update, update_advances_doc, update_is_ready,
|
|
15
13
|
};
|
|
16
14
|
|
|
17
15
|
/// Wrapper around yrs Doc.
|
|
@@ -131,9 +129,9 @@ fn copy_bytes(s: RString) -> Vec<u8> {
|
|
|
131
129
|
}
|
|
132
130
|
|
|
133
131
|
/// Build a `YrbLite::Error` (the gem's own error class, defined in `init`) so
|
|
134
|
-
/// native decode/apply
|
|
135
|
-
///
|
|
136
|
-
///
|
|
132
|
+
/// native decode/apply failures surface as a project-specific error rather than
|
|
133
|
+
/// a generic RuntimeError. Falls back to RuntimeError only if the class somehow
|
|
134
|
+
/// can't be resolved.
|
|
137
135
|
fn yrb_error(msg: String) -> Error {
|
|
138
136
|
let ruby = Ruby::get().unwrap();
|
|
139
137
|
let class = ruby
|
|
@@ -142,26 +140,10 @@ fn yrb_error(msg: String) -> Error {
|
|
|
142
140
|
Error::new(class, msg)
|
|
143
141
|
}
|
|
144
142
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const MAX_SAFE_CLIENT_ID: u64 = (1 << 53) - 1;
|
|
150
|
-
|
|
151
|
-
/// Pure predicate (no Ruby), so the boundary is unit-testable without a VM.
|
|
152
|
-
pub(crate) fn is_safe_client_id(id: u64) -> bool {
|
|
153
|
-
id <= MAX_SAFE_CLIENT_ID
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
fn validate_client_id(id: u64) -> Result<u64, Error> {
|
|
157
|
-
if !is_safe_client_id(id) {
|
|
158
|
-
return Err(yrb_error(format!(
|
|
159
|
-
"client_id {id} exceeds the maximum safe integer ({MAX_SAFE_CLIENT_ID} = 2^53 - 1); \
|
|
160
|
-
Yjs client IDs must be JS-safe integers to avoid collisions"
|
|
161
|
-
)));
|
|
162
|
-
}
|
|
163
|
-
Ok(id)
|
|
164
|
-
}
|
|
143
|
+
// CLIENT IDs ARE NOT VALIDATED -- whoever supplies the id (the app via
|
|
144
|
+
// `Doc.new(id)` / `Awareness.new(id)`, or a remote peer over the wire) is
|
|
145
|
+
// responsible for keeping it JS-safe (<= 2^53 - 1). See the protocol.rs header
|
|
146
|
+
// for why (and `ClientID::try_new`, proposed upstream, for strict rejection).
|
|
165
147
|
|
|
166
148
|
// ============================================================================
|
|
167
149
|
// Doc Implementation
|
|
@@ -173,23 +155,20 @@ impl RbDoc {
|
|
|
173
155
|
let doc = if args.is_empty() {
|
|
174
156
|
Doc::new()
|
|
175
157
|
} else {
|
|
176
|
-
let client_id =
|
|
158
|
+
let client_id: u64 = TryConvert::try_convert(args[0])?;
|
|
177
159
|
Doc::with_client_id(client_id)
|
|
178
160
|
};
|
|
179
161
|
Ok(RbDoc(doc))
|
|
180
162
|
}
|
|
181
163
|
|
|
182
|
-
/// Get the client ID
|
|
183
164
|
fn client_id(&self) -> u64 {
|
|
184
165
|
self.0.client_id().get()
|
|
185
166
|
}
|
|
186
167
|
|
|
187
|
-
/// Get the document GUID
|
|
188
168
|
fn guid(&self) -> String {
|
|
189
169
|
self.0.guid().to_string()
|
|
190
170
|
}
|
|
191
171
|
|
|
192
|
-
/// Get the current state vector encoded as bytes
|
|
193
172
|
fn encode_state_vector(&self) -> RString {
|
|
194
173
|
let doc = &self.0;
|
|
195
174
|
let sv = nogvl(move || {
|
|
@@ -220,12 +199,10 @@ impl RbDoc {
|
|
|
220
199
|
Ok(binary_string(&update))
|
|
221
200
|
}
|
|
222
201
|
|
|
223
|
-
/// Apply a V1 update to the document
|
|
224
202
|
fn apply_update(&self, update: RString) -> Result<(), Error> {
|
|
225
203
|
let update_bytes = copy_bytes(update);
|
|
226
204
|
let doc = &self.0;
|
|
227
205
|
nogvl(move || -> Result<(), String> {
|
|
228
|
-
validate_update_client_ids(&update_bytes)?;
|
|
229
206
|
let update = yrs::Update::decode_v1(&update_bytes).map_err(|e| e.to_string())?;
|
|
230
207
|
let mut txn = doc.transact_mut();
|
|
231
208
|
txn.apply_update(update).map_err(|e| e.to_string())
|
|
@@ -274,7 +251,6 @@ impl RbDoc {
|
|
|
274
251
|
let sv_data = copy_bytes(sv_bytes);
|
|
275
252
|
let doc = &self.0;
|
|
276
253
|
let encoded = nogvl(move || -> Result<Vec<u8>, String> {
|
|
277
|
-
validate_state_vector_client_ids(&sv_data)?;
|
|
278
254
|
let sv = yrs::StateVector::decode_v1(&sv_data).map_err(|e| e.to_string())?;
|
|
279
255
|
let txn = doc.transact();
|
|
280
256
|
let update = txn.encode_state_as_update_v1(&sv);
|
|
@@ -292,7 +268,6 @@ impl RbDoc {
|
|
|
292
268
|
|
|
293
269
|
let (msg_type, sync_type, response) =
|
|
294
270
|
nogvl(move || -> Result<(u8, u8, Vec<u8>), String> {
|
|
295
|
-
validate_frame_client_ids(&data_bytes)?;
|
|
296
271
|
let msg = Message::decode_v1(&data_bytes).map_err(|e| e.to_string())?;
|
|
297
272
|
|
|
298
273
|
match msg {
|
|
@@ -350,19 +325,17 @@ impl RbAwareness {
|
|
|
350
325
|
let awareness = if args.is_empty() {
|
|
351
326
|
Awareness::new(Doc::new())
|
|
352
327
|
} else {
|
|
353
|
-
let client_id =
|
|
328
|
+
let client_id: u64 = TryConvert::try_convert(args[0])?;
|
|
354
329
|
Awareness::new(Doc::with_client_id(client_id))
|
|
355
330
|
};
|
|
356
331
|
Ok(RbAwareness(Mutex::new(awareness)))
|
|
357
332
|
}
|
|
358
333
|
|
|
359
|
-
/// Get the client ID of the underlying document
|
|
360
334
|
fn client_id(&self) -> u64 {
|
|
361
335
|
let awareness = &self.0;
|
|
362
336
|
nogvl(move || awareness.lock().unwrap().doc().client_id().get())
|
|
363
337
|
}
|
|
364
338
|
|
|
365
|
-
/// Get the document GUID
|
|
366
339
|
fn guid(&self) -> String {
|
|
367
340
|
let awareness = &self.0;
|
|
368
341
|
nogvl(move || awareness.lock().unwrap().doc().guid().to_string())
|
|
@@ -406,7 +379,6 @@ impl RbAwareness {
|
|
|
406
379
|
let awareness = &self.0;
|
|
407
380
|
|
|
408
381
|
let encoded = nogvl(move || -> Result<Vec<u8>, String> {
|
|
409
|
-
validate_frame_client_ids(&data_bytes)?;
|
|
410
382
|
let mut awareness = awareness.lock().unwrap();
|
|
411
383
|
let protocol = DefaultProtocol;
|
|
412
384
|
let responses = protocol
|
|
@@ -428,18 +400,12 @@ impl RbAwareness {
|
|
|
428
400
|
}
|
|
429
401
|
|
|
430
402
|
/// Encode an update message for broadcasting changes to peers.
|
|
431
|
-
fn encode_update(&self, update: RString) ->
|
|
403
|
+
fn encode_update(&self, update: RString) -> RString {
|
|
432
404
|
let update_bytes = copy_bytes(update);
|
|
433
|
-
nogvl({
|
|
434
|
-
let update_bytes = update_bytes.clone();
|
|
435
|
-
move || validate_update_client_ids(&update_bytes)
|
|
436
|
-
})
|
|
437
|
-
.map_err(yrb_error)?;
|
|
438
405
|
let msg = Message::Sync(SyncMessage::Update(update_bytes));
|
|
439
|
-
|
|
406
|
+
binary_string(&msg.encode_v1())
|
|
440
407
|
}
|
|
441
408
|
|
|
442
|
-
/// Get the current state vector encoded as bytes
|
|
443
409
|
fn encode_state_vector(&self) -> RString {
|
|
444
410
|
let awareness = &self.0;
|
|
445
411
|
let sv = nogvl(move || {
|
|
@@ -517,12 +483,10 @@ impl RbAwareness {
|
|
|
517
483
|
Ok(binary_string(&encoded))
|
|
518
484
|
}
|
|
519
485
|
|
|
520
|
-
/// Apply a raw update to the underlying document
|
|
521
486
|
fn apply_update(&self, update: RString) -> Result<(), Error> {
|
|
522
487
|
let update_bytes = copy_bytes(update);
|
|
523
488
|
let awareness = &self.0;
|
|
524
489
|
nogvl(move || -> Result<(), String> {
|
|
525
|
-
validate_update_client_ids(&update_bytes)?;
|
|
526
490
|
let update = yrs::Update::decode_v1(&update_bytes).map_err(|e| e.to_string())?;
|
|
527
491
|
let doc = awareness.lock().unwrap().doc().clone();
|
|
528
492
|
let mut txn = doc.transact_mut();
|
|
@@ -566,16 +530,6 @@ impl RbAwareness {
|
|
|
566
530
|
})
|
|
567
531
|
}
|
|
568
532
|
|
|
569
|
-
/// Decode the awareness client IDs referenced by a protocol message
|
|
570
|
-
/// (which may pack several sub-messages together). Sync sub-messages are
|
|
571
|
-
/// ignored. The ActionCable layer uses this to learn which presence
|
|
572
|
-
/// states arrived on a connection, so it can clear them when that
|
|
573
|
-
/// connection closes.
|
|
574
|
-
fn awareness_client_ids(&self, data: RString) -> Result<Vec<u64>, Error> {
|
|
575
|
-
let data_bytes = copy_bytes(data);
|
|
576
|
-
nogvl(move || awareness_client_ids_in(&data_bytes)).map_err(yrb_error)
|
|
577
|
-
}
|
|
578
|
-
|
|
579
533
|
/// Classify a frame for safe routing and relay. Returns a code only when
|
|
580
534
|
/// the frame is exactly one well-formed message that consumes the whole
|
|
581
535
|
/// buffer, so a malformed, truncated, multi-message, or trailing-garbage
|
|
@@ -594,46 +548,13 @@ impl RbAwareness {
|
|
|
594
548
|
/// Extract the document-update delta carried by a protocol message: the
|
|
595
549
|
/// payloads of any Update or SyncStep2 sub-messages, merged into a single
|
|
596
550
|
/// update. Returns nil if the message carries no document change (for
|
|
597
|
-
/// instance a SyncStep1 request or an awareness update). The
|
|
598
|
-
/// path records this exact delta before
|
|
551
|
+
/// instance a SyncStep1 request or an awareness update). The store-backed
|
|
552
|
+
/// path records this exact delta before relaying it.
|
|
599
553
|
fn update_from_message(&self, data: RString) -> Result<Option<RString>, Error> {
|
|
600
554
|
let data_bytes = copy_bytes(data);
|
|
601
555
|
let merged = nogvl(move || merged_doc_update(&data_bytes)).map_err(yrb_error)?;
|
|
602
556
|
Ok(merged.map(|b| binary_string(&b)))
|
|
603
557
|
}
|
|
604
|
-
|
|
605
|
-
/// Mark the given clients as disconnected and return an awareness protocol
|
|
606
|
-
/// message (null-state, bumped clock) announcing their removal to peers.
|
|
607
|
-
/// Only clients currently known to this Awareness are removed; unknown
|
|
608
|
-
/// IDs are skipped (so we never broadcast phantom removals). Returns an
|
|
609
|
-
/// empty string when nothing was removed.
|
|
610
|
-
fn remove_clients(&self, client_ids: Vec<u64>) -> Result<RString, Error> {
|
|
611
|
-
let client_ids = client_ids
|
|
612
|
-
.into_iter()
|
|
613
|
-
.map(validate_client_id)
|
|
614
|
-
.collect::<Result<Vec<_>, _>>()?;
|
|
615
|
-
let awareness = &self.0;
|
|
616
|
-
let encoded = nogvl(move || -> Result<Vec<u8>, String> {
|
|
617
|
-
let mut awareness = awareness.lock().unwrap();
|
|
618
|
-
let mut removed = Vec::new();
|
|
619
|
-
for id in client_ids {
|
|
620
|
-
let cid = ClientID::new(id);
|
|
621
|
-
if awareness.meta(cid).is_some() {
|
|
622
|
-
awareness.remove_state(cid);
|
|
623
|
-
removed.push(cid);
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
if removed.is_empty() {
|
|
627
|
-
return Ok(Vec::new());
|
|
628
|
-
}
|
|
629
|
-
let update = awareness
|
|
630
|
-
.update_with_clients(removed)
|
|
631
|
-
.map_err(|e| e.to_string())?;
|
|
632
|
-
Ok(Message::Awareness(update).encode_v1())
|
|
633
|
-
})
|
|
634
|
-
.map_err(yrb_error)?;
|
|
635
|
-
Ok(binary_string(&encoded))
|
|
636
|
-
}
|
|
637
558
|
}
|
|
638
559
|
|
|
639
560
|
// ============================================================================
|
|
@@ -707,11 +628,6 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
707
628
|
"encode_awareness_update",
|
|
708
629
|
method!(RbAwareness::encode_awareness_update, 0),
|
|
709
630
|
)?;
|
|
710
|
-
awareness_class.define_method(
|
|
711
|
-
"awareness_client_ids",
|
|
712
|
-
method!(RbAwareness::awareness_client_ids, 1),
|
|
713
|
-
)?;
|
|
714
|
-
awareness_class.define_method("remove_clients", method!(RbAwareness::remove_clients, 1))?;
|
|
715
631
|
awareness_class.define_method(
|
|
716
632
|
"update_from_message",
|
|
717
633
|
method!(RbAwareness::update_from_message, 1),
|
|
@@ -1,219 +1,18 @@
|
|
|
1
|
-
// Pure protocol helpers
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
use yrs::encoding::read::{Cursor,
|
|
8
|
-
use yrs::sync::protocol::
|
|
9
|
-
MessageReader, MSG_AUTH, MSG_AWARENESS, MSG_QUERY_AWARENESS, MSG_SYNC, MSG_SYNC_STEP_1,
|
|
10
|
-
MSG_SYNC_STEP_2, MSG_SYNC_UPDATE, PERMISSION_DENIED,
|
|
11
|
-
};
|
|
1
|
+
// Pure rust protocol helpers (ie, no Ruby interop).
|
|
2
|
+
//
|
|
3
|
+
// CLIENT IDs ARE NOT VALIDATED HERE -- on purpose. We deliberately don't police it -- every
|
|
4
|
+
// legitimate peer (browser Yjs, and yrs's own `ClientID::random`) already emits
|
|
5
|
+
// 53-bit ids, so it's the client's responsibility not to send a bad one, and we
|
|
6
|
+
// don't want to own that logic.
|
|
7
|
+
use yrs::encoding::read::{Cursor, Read};
|
|
8
|
+
use yrs::sync::protocol::MessageReader;
|
|
12
9
|
use yrs::sync::{Message, SyncMessage};
|
|
13
|
-
use yrs::updates::decoder::{Decode,
|
|
14
|
-
use yrs::{
|
|
15
|
-
|
|
16
|
-
fn unsafe_client_id_error(id: u64) -> ReadError {
|
|
17
|
-
ReadError::Custom(format!(
|
|
18
|
-
"client_id {id} exceeds the maximum safe integer (2^53 - 1); \
|
|
19
|
-
Yjs client IDs must be JS-safe integers to avoid collisions"
|
|
20
|
-
))
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
fn checked_client_id(id: u64) -> Result<ClientID, ReadError> {
|
|
24
|
-
if !is_safe_client_id(id) {
|
|
25
|
-
return Err(unsafe_client_id_error(id));
|
|
26
|
-
}
|
|
27
|
-
Ok(ClientID::new(id))
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
fn validate_raw_client_id(id: u64) -> Result<(), ReadError> {
|
|
31
|
-
if !is_safe_client_id(id) {
|
|
32
|
-
return Err(unsafe_client_id_error(id));
|
|
33
|
-
}
|
|
34
|
-
Ok(())
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
struct CheckedDecoderV1<'a> {
|
|
38
|
-
cursor: Cursor<'a>,
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
impl<'a> CheckedDecoderV1<'a> {
|
|
42
|
-
fn new(cursor: Cursor<'a>) -> Self {
|
|
43
|
-
CheckedDecoderV1 { cursor }
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
fn read_id(&mut self) -> Result<ID, ReadError> {
|
|
47
|
-
let client: u64 = self.read_var()?;
|
|
48
|
-
validate_raw_client_id(client)?;
|
|
49
|
-
let clock = self.read_var()?;
|
|
50
|
-
Ok(ID::new(ClientID::new(client), clock))
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
impl<'a> Read for CheckedDecoderV1<'a> {
|
|
55
|
-
#[inline]
|
|
56
|
-
fn read_u8(&mut self) -> Result<u8, ReadError> {
|
|
57
|
-
self.cursor.read_u8()
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
#[inline]
|
|
61
|
-
fn read_exact(&mut self, len: usize) -> Result<&[u8], ReadError> {
|
|
62
|
-
self.cursor.read_exact(len)
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
impl<'a> Decoder for CheckedDecoderV1<'a> {
|
|
67
|
-
#[inline]
|
|
68
|
-
fn reset_ds_cur_val(&mut self) {}
|
|
69
|
-
|
|
70
|
-
#[inline]
|
|
71
|
-
fn read_ds_clock(&mut self) -> Result<u32, ReadError> {
|
|
72
|
-
self.read_var()
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
#[inline]
|
|
76
|
-
fn read_ds_len(&mut self) -> Result<u32, ReadError> {
|
|
77
|
-
self.read_var()
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
#[inline]
|
|
81
|
-
fn read_left_id(&mut self) -> Result<ID, ReadError> {
|
|
82
|
-
self.read_id()
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
#[inline]
|
|
86
|
-
fn read_right_id(&mut self) -> Result<ID, ReadError> {
|
|
87
|
-
self.read_id()
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
#[inline]
|
|
91
|
-
fn read_client(&mut self) -> Result<ClientID, ReadError> {
|
|
92
|
-
let client: u64 = self.cursor.read_var()?;
|
|
93
|
-
checked_client_id(client)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
#[inline]
|
|
97
|
-
fn read_info(&mut self) -> Result<u8, ReadError> {
|
|
98
|
-
self.cursor.read_u8()
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
#[inline]
|
|
102
|
-
fn read_parent_info(&mut self) -> Result<bool, ReadError> {
|
|
103
|
-
let info: u32 = self.cursor.read_var()?;
|
|
104
|
-
Ok(info == 1)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
#[inline]
|
|
108
|
-
fn read_type_ref(&mut self) -> Result<u8, ReadError> {
|
|
109
|
-
self.cursor.read_u8()
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
#[inline]
|
|
113
|
-
fn read_len(&mut self) -> Result<u32, ReadError> {
|
|
114
|
-
self.read_var()
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
#[inline]
|
|
118
|
-
fn read_any(&mut self) -> Result<Any, ReadError> {
|
|
119
|
-
Any::decode(self)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
#[inline]
|
|
123
|
-
fn read_json(&mut self) -> Result<Any, ReadError> {
|
|
124
|
-
let src = self.read_string()?;
|
|
125
|
-
Any::from_json(src)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
#[inline]
|
|
129
|
-
fn read_key(&mut self) -> Result<Arc<str>, ReadError> {
|
|
130
|
-
let str: Arc<str> = self.read_string()?.into();
|
|
131
|
-
Ok(str)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
#[inline]
|
|
135
|
-
fn read_to_end(&mut self) -> Result<&[u8], ReadError> {
|
|
136
|
-
Ok(&self.cursor.buf[self.cursor.next..])
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
pub(crate) fn validate_state_vector_client_ids(bytes: &[u8]) -> Result<(), String> {
|
|
141
|
-
let mut cursor = Cursor::new(bytes);
|
|
142
|
-
let len: u32 = cursor.read_var().map_err(|e| e.to_string())?;
|
|
143
|
-
for _ in 0..len {
|
|
144
|
-
let client: u64 = cursor.read_var().map_err(|e| e.to_string())?;
|
|
145
|
-
validate_raw_client_id(client).map_err(|e| e.to_string())?;
|
|
146
|
-
let _: u32 = cursor.read_var().map_err(|e| e.to_string())?;
|
|
147
|
-
}
|
|
148
|
-
if cursor.has_content() {
|
|
149
|
-
return Err("state vector has trailing bytes".to_string());
|
|
150
|
-
}
|
|
151
|
-
Ok(())
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
pub(crate) fn validate_update_client_ids(update_bytes: &[u8]) -> Result<(), String> {
|
|
155
|
-
let mut decoder = CheckedDecoderV1::new(Cursor::new(update_bytes));
|
|
156
|
-
yrs::Update::decode(&mut decoder).map_err(|e| e.to_string())?;
|
|
157
|
-
if decoder.cursor.has_content() {
|
|
158
|
-
return Err("update has trailing bytes".to_string());
|
|
159
|
-
}
|
|
160
|
-
Ok(())
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
fn validate_awareness_update_client_ids(bytes: &[u8]) -> Result<(), String> {
|
|
164
|
-
let mut cursor = Cursor::new(bytes);
|
|
165
|
-
let len: u32 = cursor.read_var().map_err(|e| e.to_string())?;
|
|
166
|
-
for _ in 0..len {
|
|
167
|
-
let client: u64 = cursor.read_var().map_err(|e| e.to_string())?;
|
|
168
|
-
validate_raw_client_id(client).map_err(|e| e.to_string())?;
|
|
169
|
-
let _: u32 = cursor.read_var().map_err(|e| e.to_string())?;
|
|
170
|
-
let _ = cursor.read_string().map_err(|e| e.to_string())?;
|
|
171
|
-
}
|
|
172
|
-
if cursor.has_content() {
|
|
173
|
-
return Err("awareness update has trailing bytes".to_string());
|
|
174
|
-
}
|
|
175
|
-
Ok(())
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
pub(crate) fn validate_frame_client_ids(bytes: &[u8]) -> Result<(), String> {
|
|
179
|
-
let mut cursor = Cursor::new(bytes);
|
|
180
|
-
while cursor.has_content() {
|
|
181
|
-
let tag: u8 = cursor.read_var().map_err(|e| e.to_string())?;
|
|
182
|
-
match tag {
|
|
183
|
-
MSG_SYNC => {
|
|
184
|
-
let sync_tag: u8 = cursor.read_var().map_err(|e| e.to_string())?;
|
|
185
|
-
let payload = cursor.read_buf().map_err(|e| e.to_string())?;
|
|
186
|
-
match sync_tag {
|
|
187
|
-
MSG_SYNC_STEP_1 => validate_state_vector_client_ids(payload)?,
|
|
188
|
-
MSG_SYNC_STEP_2 | MSG_SYNC_UPDATE => validate_update_client_ids(payload)?,
|
|
189
|
-
_ => return Err("unknown sync message type".to_string()),
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
MSG_AWARENESS => {
|
|
193
|
-
let payload = cursor.read_buf().map_err(|e| e.to_string())?;
|
|
194
|
-
validate_awareness_update_client_ids(payload)?;
|
|
195
|
-
}
|
|
196
|
-
MSG_AUTH => {
|
|
197
|
-
let permission: u8 = cursor.read_var().map_err(|e| e.to_string())?;
|
|
198
|
-
if permission == PERMISSION_DENIED {
|
|
199
|
-
let _ = cursor.read_string().map_err(|e| e.to_string())?;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
MSG_QUERY_AWARENESS => {}
|
|
203
|
-
_ => {
|
|
204
|
-
let _ = cursor.read_buf().map_err(|e| e.to_string())?;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
Ok(())
|
|
209
|
-
}
|
|
10
|
+
use yrs::updates::decoder::{Decode, DecoderV1};
|
|
11
|
+
use yrs::{Doc, ReadTxn, Transact};
|
|
210
12
|
|
|
211
13
|
/// Classify a frame: a non-zero code only for exactly one well-formed message
|
|
212
14
|
/// that consumes the whole buffer (see `RbAwareness::message_kind` for codes).
|
|
213
15
|
pub(crate) fn classify_message(bytes: &[u8]) -> u8 {
|
|
214
|
-
if validate_frame_client_ids(bytes).is_err() {
|
|
215
|
-
return 0;
|
|
216
|
-
}
|
|
217
16
|
let mut decoder = DecoderV1::new(Cursor::new(bytes));
|
|
218
17
|
let msg = match Message::decode(&mut decoder) {
|
|
219
18
|
Ok(msg) => msg,
|
|
@@ -236,7 +35,6 @@ pub(crate) fn classify_message(bytes: &[u8]) -> u8 {
|
|
|
236
35
|
/// frame into one update, or `None` if the frame carries no document change
|
|
237
36
|
/// (a request, an awareness update, or a no-op handshake SyncStep2).
|
|
238
37
|
pub(crate) fn merged_doc_update(bytes: &[u8]) -> Result<Option<Vec<u8>>, String> {
|
|
239
|
-
validate_frame_client_ids(bytes)?;
|
|
240
38
|
let mut decoder = DecoderV1::new(Cursor::new(bytes));
|
|
241
39
|
let mut updates: Vec<Vec<u8>> = Vec::new();
|
|
242
40
|
for msg in MessageReader::new(&mut decoder) {
|
|
@@ -268,26 +66,12 @@ pub(crate) fn merged_doc_update(bytes: &[u8]) -> Result<Option<Vec<u8>>, String>
|
|
|
268
66
|
Ok(Some(merged))
|
|
269
67
|
}
|
|
270
68
|
|
|
271
|
-
/// Collect the awareness client IDs referenced by a frame's awareness messages.
|
|
272
|
-
pub(crate) fn awareness_client_ids_in(bytes: &[u8]) -> Result<Vec<u64>, String> {
|
|
273
|
-
validate_frame_client_ids(bytes)?;
|
|
274
|
-
let mut decoder = DecoderV1::new(Cursor::new(bytes));
|
|
275
|
-
let mut ids = Vec::new();
|
|
276
|
-
for msg in MessageReader::new(&mut decoder) {
|
|
277
|
-
if let Message::Awareness(update) = msg.map_err(|e| e.to_string())? {
|
|
278
|
-
ids.extend(update.clients.keys().map(|c| c.get()));
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
Ok(ids)
|
|
282
|
-
}
|
|
283
|
-
|
|
284
69
|
/// True if applying `update_bytes` to `doc` would integrate cleanly: every
|
|
285
70
|
/// dependency the update references is already present (the doc's state vector
|
|
286
71
|
/// covers the update's lower bound). A pure read; does not mutate the doc.
|
|
287
72
|
/// When false, applying it would park a pending struct -- the signal that an
|
|
288
73
|
/// earlier, causally-prior update is missing.
|
|
289
74
|
pub(crate) fn update_is_ready(doc: &Doc, update_bytes: &[u8]) -> Result<bool, String> {
|
|
290
|
-
validate_update_client_ids(update_bytes)?;
|
|
291
75
|
let update = yrs::Update::decode_v1(update_bytes).map_err(|e| e.to_string())?;
|
|
292
76
|
Ok(doc.transact().state_vector() >= update.state_vector_lower())
|
|
293
77
|
}
|
|
@@ -309,7 +93,6 @@ pub(crate) fn update_is_ready(doc: &Doc, update_bytes: &[u8]) -> Result<bool, St
|
|
|
309
93
|
/// double-record a pure-delete retry, but it NEVER drops a real deletion, which
|
|
310
94
|
/// is the safe direction. Assumes the update is already causally ready.
|
|
311
95
|
pub(crate) fn update_advances_doc(doc: &Doc, update_bytes: &[u8]) -> Result<bool, String> {
|
|
312
|
-
validate_update_client_ids(update_bytes)?;
|
|
313
96
|
let update = yrs::Update::decode_v1(update_bytes).map_err(|e| e.to_string())?;
|
|
314
97
|
if !update.delete_set().is_empty() {
|
|
315
98
|
return Ok(true); // can't cheaply prove a delete is a duplicate; record it
|
|
@@ -342,10 +125,8 @@ pub(crate) fn doc_has_pending(doc: &Doc) -> bool {
|
|
|
342
125
|
#[cfg(test)]
|
|
343
126
|
mod tests {
|
|
344
127
|
use super::*;
|
|
345
|
-
use crate::is_safe_client_id;
|
|
346
|
-
use yrs::encoding::write::Write;
|
|
347
128
|
use yrs::sync::Awareness;
|
|
348
|
-
use yrs::updates::encoder::
|
|
129
|
+
use yrs::updates::encoder::Encode;
|
|
349
130
|
use yrs::Text;
|
|
350
131
|
|
|
351
132
|
fn text_update(content: &str) -> Vec<u8> {
|
|
@@ -374,42 +155,6 @@ mod tests {
|
|
|
374
155
|
Message::Awareness(awareness.update().unwrap()).encode_v1()
|
|
375
156
|
}
|
|
376
157
|
|
|
377
|
-
fn unsafe_struct_client_update() -> Vec<u8> {
|
|
378
|
-
let mut update = EncoderV1::new();
|
|
379
|
-
update.write_var(1u32); // client count
|
|
380
|
-
update.write_var(0u32); // block count for this client
|
|
381
|
-
update.write_var(1u64 << 53); // unsafe client id
|
|
382
|
-
update.write_var(0u32); // clock
|
|
383
|
-
update.write_var(0u32); // delete-set client count
|
|
384
|
-
update.to_vec()
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
fn unsafe_awareness_frame() -> Vec<u8> {
|
|
388
|
-
let mut payload = EncoderV1::new();
|
|
389
|
-
payload.write_var(1u32); // client count
|
|
390
|
-
payload.write_var(1u64 << 53); // unsafe client id
|
|
391
|
-
payload.write_var(1u32); // clock
|
|
392
|
-
payload.write_string("{}");
|
|
393
|
-
|
|
394
|
-
let mut frame = EncoderV1::new();
|
|
395
|
-
frame.write_var(MSG_AWARENESS);
|
|
396
|
-
frame.write_buf(payload.to_vec());
|
|
397
|
-
frame.to_vec()
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
fn unsafe_step1_frame() -> Vec<u8> {
|
|
401
|
-
let mut sv = EncoderV1::new();
|
|
402
|
-
sv.write_var(1u32); // state-vector entry count
|
|
403
|
-
sv.write_var(1u64 << 53); // unsafe client id
|
|
404
|
-
sv.write_var(0u32); // clock
|
|
405
|
-
|
|
406
|
-
let mut frame = EncoderV1::new();
|
|
407
|
-
frame.write_var(MSG_SYNC);
|
|
408
|
-
frame.write_var(MSG_SYNC_STEP_1);
|
|
409
|
-
frame.write_buf(sv.to_vec());
|
|
410
|
-
frame.to_vec()
|
|
411
|
-
}
|
|
412
|
-
|
|
413
158
|
#[test]
|
|
414
159
|
fn classify_accepts_clean_single_messages() {
|
|
415
160
|
assert_eq!(classify_message(&step1_frame()), 1);
|
|
@@ -500,51 +245,6 @@ mod tests {
|
|
|
500
245
|
);
|
|
501
246
|
}
|
|
502
247
|
|
|
503
|
-
#[test]
|
|
504
|
-
fn client_id_safe_integer_boundary() {
|
|
505
|
-
assert!(is_safe_client_id(0), "zero is fine");
|
|
506
|
-
assert!(
|
|
507
|
-
is_safe_client_id((1 << 53) - 1),
|
|
508
|
-
"2^53 - 1 is the max safe id"
|
|
509
|
-
);
|
|
510
|
-
assert!(!is_safe_client_id(1 << 53), "2^53 is unsafe");
|
|
511
|
-
assert!(!is_safe_client_id(1 << 63), "2^63 is unsafe");
|
|
512
|
-
assert!(!is_safe_client_id(u64::MAX), "u64::MAX is unsafe");
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
#[test]
|
|
516
|
-
fn wire_client_id_validation_rejects_unsafe_sync_update_clients() {
|
|
517
|
-
let update = unsafe_struct_client_update();
|
|
518
|
-
assert!(
|
|
519
|
-
validate_update_client_ids(&update).is_err(),
|
|
520
|
-
"raw unsafe update client id is rejected before yrs can mask it"
|
|
521
|
-
);
|
|
522
|
-
|
|
523
|
-
let frame = Message::Sync(SyncMessage::Update(update)).encode_v1();
|
|
524
|
-
assert_eq!(
|
|
525
|
-
classify_message(&frame),
|
|
526
|
-
0,
|
|
527
|
-
"unsafe sync frame is not relayable"
|
|
528
|
-
);
|
|
529
|
-
assert!(merged_doc_update(&frame).is_err());
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
#[test]
|
|
533
|
-
fn wire_client_id_validation_rejects_unsafe_awareness_and_step1_clients() {
|
|
534
|
-
assert!(
|
|
535
|
-
validate_frame_client_ids(&unsafe_awareness_frame()).is_err(),
|
|
536
|
-
"raw unsafe awareness client id is rejected"
|
|
537
|
-
);
|
|
538
|
-
assert_eq!(classify_message(&unsafe_awareness_frame()), 0);
|
|
539
|
-
assert!(awareness_client_ids_in(&unsafe_awareness_frame()).is_err());
|
|
540
|
-
|
|
541
|
-
assert!(
|
|
542
|
-
validate_frame_client_ids(&unsafe_step1_frame()).is_err(),
|
|
543
|
-
"raw unsafe state-vector client id is rejected"
|
|
544
|
-
);
|
|
545
|
-
assert_eq!(classify_message(&unsafe_step1_frame()), 0);
|
|
546
|
-
}
|
|
547
|
-
|
|
548
248
|
#[test]
|
|
549
249
|
fn merged_doc_update_extracts_and_skips_no_ops() {
|
|
550
250
|
// A document update yields a delta that reconstructs the content.
|
|
@@ -582,18 +282,6 @@ mod tests {
|
|
|
582
282
|
assert!(yrs::Update::decode_v1(&merged).is_ok());
|
|
583
283
|
}
|
|
584
284
|
|
|
585
|
-
#[test]
|
|
586
|
-
fn awareness_client_ids_are_collected() {
|
|
587
|
-
assert_eq!(
|
|
588
|
-
awareness_client_ids_in(&awareness_frame(111)).unwrap(),
|
|
589
|
-
vec![111]
|
|
590
|
-
);
|
|
591
|
-
// A document frame has no awareness client ids.
|
|
592
|
-
assert!(awareness_client_ids_in(&update_frame("x"))
|
|
593
|
-
.unwrap()
|
|
594
|
-
.is_empty());
|
|
595
|
-
}
|
|
596
|
-
|
|
597
285
|
#[test]
|
|
598
286
|
fn update_readiness_and_pending_detect_a_causal_gap() {
|
|
599
287
|
// Three sequential single-char inserts from one client: A, then B, then
|
data/lib/yrb_lite/version.rb
CHANGED