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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9553ad44479cc56e9ccd2645017c8ebf3dba79a0d5905651396b5f397ca1ad6a
4
- data.tar.gz: 8c81c7a844a50def1af8b4a2cdbb1bbf95f576077976b7cc6fc4aef740173164
3
+ metadata.gz: ea20d7a00b104460b01812d463df581b45042d63491ff1e5494acd478fa2047b
4
+ data.tar.gz: 731010497572899b225ff36fd792e635738d05bad0f41640a4f991af56540d3e
5
5
  SHA512:
6
- metadata.gz: 0db8acba6f12e4f9f9289cb80b7d2aa4870ed2c26f090eea1529fd19fa1a1b07c6731ec36097c96a47735f5f6abbf91f592701512250487acf053edd734eb771
7
- data.tar.gz: d5c4e9e3a98f16735b150c76c68e5e7b584c329f554edf12c3949d4104e5b9a06c31fe9d3082bc2f510f67f17fa0ea9402d373ad926320c2f96eb2af744f60a1
6
+ metadata.gz: 83682aa86e7753b9270274051e0b9850c41e35559817cbae981debe68de53d4b1cd56cff5b21b0d63b411e87ab550cd7ffc11c0ec0c3005845e02e5af145d078
7
+ data.tar.gz: 282965720af81ee289feb0f555ff828fd593ba24f869054a7792f420c61ba03c87168b963adb6259e143b103c398e70855365d48c1f4cd6a7e693ae8491026b9
@@ -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::{ClientID, Doc, ReadTxn, Transact};
8
+ use yrs::{Doc, ReadTxn, Transact};
9
9
 
10
10
  mod protocol;
11
11
  use protocol::{
12
- awareness_client_ids_in, classify_message, doc_has_pending, merged_doc_update,
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/validation failures surface as a project-specific error
135
- /// rather than a generic RuntimeError. Falls back to RuntimeError only if the
136
- /// class somehow can't be resolved.
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
- /// Yjs/lib0 client IDs must be JS-safe integers (<= 2^53 - 1). Above that they
146
- /// round or collide when crossing the JS/Yjs boundary, and a client-id collision
147
- /// corrupts a CRDT. Explicit (Ruby-supplied) IDs are validated here; the random
148
- /// default IDs that yrs generates are already in range.
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 = validate_client_id(TryConvert::try_convert(args[0])?)?;
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 = validate_client_id(TryConvert::try_convert(args[0])?)?;
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) -> Result<RString, Error> {
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
- Ok(binary_string(&msg.encode_v1()))
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 strict audit
598
- /// path records this exact delta before applying it.
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: no Ruby, no GVL, no `unsafe`. Everything here operates
2
- // on plain byte slices and `yrs` types, so it's unit-tested directly (see the
3
- // `tests` module below) without a Ruby VM. The Ruby-facing wrappers in lib.rs
4
- // copy bytes out of Ruby strings and call into these under `nogvl`.
5
- use crate::is_safe_client_id;
6
- use std::sync::Arc;
7
- use yrs::encoding::read::{Cursor, Error as ReadError, Read};
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, Decoder, DecoderV1};
14
- use yrs::{Any, ClientID, Doc, ReadTxn, Transact, ID};
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::{Encode, Encoder, EncoderV1};
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module YrbLite
4
- VERSION = "0.1.0.beta6"
4
+ VERSION = "0.1.0.beta7"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yrb-lite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.beta6
4
+ version: 0.1.0.beta7
5
5
  platform: ruby
6
6
  authors:
7
7
  - JP Camara