1#![allow(unused_assignments)]
2
3use std::{fmt::Display, io::Write};
4
5use arbitrary::Arbitrary;
6use enum_as_inner::EnumAsInner;
7
8use nonmax::{NonMaxI32, NonMaxU32};
9use serde::{Deserialize, Serialize};
10
11mod error;
12mod id;
13mod internal_string;
14mod logging;
15mod macros;
16mod span;
17mod value;
18
19pub use error::{LoroEncodeError, LoroError, LoroResult, LoroTreeError};
20pub use internal_string::InternalString;
21pub use logging::log::*;
22#[doc(hidden)]
23pub use rustc_hash::FxHashMap;
24pub use span::*;
25pub use value::{
26 to_value, LoroBinaryValue, LoroListValue, LoroMapValue, LoroStringValue, LoroValue,
27};
28
29pub type PeerID = u64;
31pub type Counter = i32;
33pub type Lamport = u32;
35
36#[derive(PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize)]
38pub struct ID {
39 pub peer: PeerID,
40 pub counter: Counter,
41}
42
43impl ID {
44 pub fn to_bytes(&self) -> [u8; 12] {
45 let mut bytes = [0; 12];
46 bytes[..8].copy_from_slice(&self.peer.to_be_bytes());
47 bytes[8..].copy_from_slice(&self.counter.to_be_bytes());
48 bytes
49 }
50
51 pub fn from_bytes(bytes: &[u8]) -> Self {
52 if bytes.len() != 12 {
53 panic!(
54 "Invalid ID bytes. Expected 12 bytes but got {} bytes",
55 bytes.len()
56 );
57 }
58
59 Self {
60 peer: u64::from_be_bytes(bytes[..8].try_into().unwrap()),
61 counter: i32::from_be_bytes(bytes[8..].try_into().unwrap()),
62 }
63 }
64}
65
66#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
68pub struct CompactId {
69 pub peer: PeerID,
70 pub counter: NonMaxI32,
71}
72
73pub const MERGEABLE_NAMESPACE_PREFIX: &str = "🤝:";
87
88fn write_len_prefixed_segment(out: &mut Vec<u8>, bytes: &[u8]) {
89 leb128::write::unsigned(out, bytes.len() as u64).unwrap();
90 out.extend_from_slice(bytes);
91}
92
93fn push_mergeable_escaped(out: &mut String, segment: &str) {
94 for ch in segment.chars() {
95 match ch {
96 '\\' => out.push_str("\\\\"),
97 '>' => out.push_str("\\>"),
98 '/' => out.push_str("\\s"),
99 '\0' => out.push_str("\\0"),
100 _ => out.push(ch),
101 }
102 }
103}
104
105fn decode_mergeable_segment(segment: &str) -> Option<String> {
106 let mut out = String::with_capacity(segment.len());
107 let mut chars = segment.chars();
108 while let Some(ch) = chars.next() {
109 match ch {
110 '\\' => {
111 let escaped = chars.next()?;
112 match escaped {
113 '\\' => out.push('\\'),
114 '>' => out.push('>'),
115 's' => out.push('/'),
116 '0' => out.push('\0'),
117 _ => return None,
118 }
119 }
120 '/' | '\0' => return None,
121 _ => out.push(ch),
122 }
123 }
124
125 Some(out)
126}
127
128fn find_first_mergeable_separator(payload: &str) -> Option<usize> {
129 let mut escaped = false;
130 for (idx, ch) in payload.char_indices() {
131 if escaped {
132 escaped = false;
133 continue;
134 }
135
136 match ch {
137 '\\' => escaped = true,
138 '>' => return Some(idx),
139 _ => {}
140 }
141 }
142
143 None
144}
145
146fn find_last_mergeable_separator(payload: &str) -> Option<usize> {
147 let mut last = None;
148 let mut escaped = false;
149 for (idx, ch) in payload.char_indices() {
150 if escaped {
151 escaped = false;
152 continue;
153 }
154
155 match ch {
156 '\\' => escaped = true,
157 '>' => last = Some(idx),
158 _ => {}
159 }
160 }
161
162 if escaped {
163 None
164 } else {
165 last
166 }
167}
168
169fn push_u64_base36(out: &mut String, mut value: u64) {
170 const DIGITS: &[u8; 36] = b"0123456789abcdefghijklmnopqrstuvwxyz";
171 if value == 0 {
172 out.push('0');
173 return;
174 }
175
176 let mut buf = [0u8; 13];
177 let mut idx = buf.len();
178 while value > 0 {
179 idx -= 1;
180 buf[idx] = DIGITS[(value % 36) as usize];
181 value /= 36;
182 }
183
184 out.push_str(std::str::from_utf8(&buf[idx..]).unwrap());
185}
186
187fn push_i32_base36(out: &mut String, value: i32) {
188 if value < 0 {
189 out.push('-');
190 }
191 let magnitude = if value < 0 {
192 (-(i64::from(value))) as u64
193 } else {
194 value as u64
195 };
196 push_u64_base36(out, magnitude);
197}
198
199fn parse_base36_digit(byte: u8) -> Option<u64> {
200 match byte {
201 b'0'..=b'9' => Some(u64::from(byte - b'0')),
202 b'a'..=b'z' => Some(u64::from(byte - b'a' + 10)),
203 _ => None,
204 }
205}
206
207fn parse_u64_base36(s: &str) -> Option<u64> {
208 if s.is_empty() {
209 return None;
210 }
211 if s.len() > 1 && s.starts_with('0') {
212 return None;
213 }
214
215 let mut value = 0u64;
216 for byte in s.bytes() {
217 value = value.checked_mul(36)?;
218 value = value.checked_add(parse_base36_digit(byte)?)?;
219 }
220 Some(value)
221}
222
223fn parse_i32_base36(s: &str) -> Option<i32> {
224 if let Some(rest) = s.strip_prefix('-') {
225 let value = parse_u64_base36(rest)?;
226 if value == 0 {
227 return None;
228 }
229 let signed = -(i64::try_from(value).ok()?);
230 i32::try_from(signed).ok()
231 } else {
232 let value = parse_u64_base36(s)?;
233 i32::try_from(value).ok()
234 }
235}
236
237fn parse_mergeable_base_parent(segment: &str) -> Option<ContainerID> {
238 let mut chars = segment.chars();
239 match chars.next()? {
240 '$' => {
241 let name = &segment['$'.len_utf8()..];
242 if !check_root_container_name(name) {
243 return None;
244 }
245 Some(ContainerID::Root {
246 name: name.into(),
247 container_type: ContainerType::Map,
248 })
249 }
250 '@' => {
251 let rest = &segment['@'.len_utf8()..];
252 let (peer, counter) = rest.split_once(':')?;
253 Some(ContainerID::Normal {
254 peer: parse_u64_base36(peer)?,
255 counter: parse_i32_base36(counter)?,
256 container_type: ContainerType::Map,
257 })
258 }
259 _ => None,
260 }
261}
262
263fn validate_mergeable_payload(payload: &str) -> Option<()> {
264 let separator = find_first_mergeable_separator(payload)?;
265 validate_mergeable_base_parent(&payload[..separator])?;
266 validate_mergeable_key_path(&payload[separator + '>'.len_utf8()..])
267}
268
269fn validate_mergeable_base_parent(segment: &str) -> Option<()> {
270 match segment.as_bytes().first().copied()? {
271 b'$' => validate_mergeable_root_base_parent(&segment['$'.len_utf8()..]),
272 b'@' => validate_mergeable_normal_base_parent(&segment['@'.len_utf8()..]),
273 _ => None,
274 }
275}
276
277fn validate_mergeable_root_base_parent(name: &str) -> Option<()> {
278 if name.is_empty() || name.starts_with(MERGEABLE_NAMESPACE_PREFIX) {
279 return None;
280 }
281
282 let mut chars = name.chars();
283 while let Some(ch) = chars.next() {
284 match ch {
285 '\\' => match chars.next()? {
286 '\\' | '>' => {}
287 's' | '0' => return None,
289 _ => return None,
290 },
291 '/' | '\0' => return None,
292 _ => {}
293 }
294 }
295
296 Some(())
297}
298
299fn validate_mergeable_normal_base_parent(segment: &str) -> Option<()> {
300 let (peer, counter) = segment.split_once(':')?;
301 parse_u64_base36(peer)?;
302 parse_i32_base36(counter)?;
303 Some(())
304}
305
306fn validate_mergeable_key_path(path: &str) -> Option<()> {
307 let mut chars = path.chars();
308 while let Some(ch) = chars.next() {
309 match ch {
310 '\\' => match chars.next()? {
311 '\\' | '>' | 's' | '0' => {}
312 _ => return None,
313 },
314 '/' | '\0' => return None,
315 _ => {}
316 }
317 }
318
319 Some(())
320}
321
322fn parse_mergeable_payload(payload: &str) -> Option<(ContainerID, String)> {
323 validate_mergeable_payload(payload)?;
324 let separator = find_last_mergeable_separator(payload)?;
325 let parent_payload = &payload[..separator];
326 let key = decode_mergeable_segment(&payload[separator + '>'.len_utf8()..])?;
327
328 let parent = if find_last_mergeable_separator(parent_payload).is_some() {
329 ContainerID::Root {
330 name: format!("{MERGEABLE_NAMESPACE_PREFIX}{parent_payload}").into(),
331 container_type: ContainerType::Map,
332 }
333 } else {
334 let parent_segment = decode_mergeable_segment(parent_payload)?;
335 parse_mergeable_base_parent(&parent_segment)?
336 };
337
338 Some((parent, key))
339}
340
341fn mergeable_payload_for_parent(parent: &ContainerID) -> Option<&str> {
342 let ContainerID::Root {
343 name,
344 container_type: ContainerType::Map,
345 } = parent
346 else {
347 return None;
348 };
349 let payload = name.as_str().strip_prefix(MERGEABLE_NAMESPACE_PREFIX)?;
350 validate_mergeable_payload(payload)?;
351 Some(payload)
352}
353
354fn push_mergeable_parent(out: &mut String, parent: &ContainerID) {
355 if let Some(payload) = mergeable_payload_for_parent(parent) {
356 out.push_str(payload);
357 return;
358 }
359
360 match parent {
361 ContainerID::Root { name, .. } => {
362 out.push('$');
363 push_mergeable_escaped(out, name.as_str());
364 }
365 ContainerID::Normal { peer, counter, .. } => {
366 out.push('@');
367 push_u64_base36(out, *peer);
368 out.push(':');
369 push_i32_base36(out, *counter);
370 }
371 }
372}
373
374pub fn check_root_container_name(name: &str) -> bool {
376 !name.is_empty()
377 && !name.starts_with(MERGEABLE_NAMESPACE_PREFIX)
378 && name.char_indices().all(|(_, x)| x != '/' && x != '\0')
379}
380
381pub const MERGEABLE_MARKER_MAGIC: [u8; 4] = [0x00, b'L', b'M', 0x01];
391
392const MERGEABLE_MARKER_DIGEST_LEN: usize = 3;
393const MERGEABLE_MARKER_LEN: usize = 4 + 1 + MERGEABLE_MARKER_DIGEST_LEN;
394const MERGEABLE_MARKER_CRC_DOMAIN: &[u8] = b"loro.mergeable.marker.v1";
395
396pub fn mergeable_marker(
400 parent: &ContainerID,
401 key: &str,
402 container_type: ContainerType,
403) -> LoroValue {
404 let mut marker = Vec::with_capacity(MERGEABLE_MARKER_LEN);
405 marker.extend_from_slice(&MERGEABLE_MARKER_MAGIC);
406 marker.push(container_type.to_u8());
407 let digest = mergeable_marker_crc24(parent, key, container_type);
408 marker.extend_from_slice(&digest);
409 LoroValue::Binary(marker.into())
410}
411
412pub fn parse_mergeable_marker(
418 parent: &ContainerID,
419 key: &str,
420 value: &LoroValue,
421) -> Option<ContainerType> {
422 let LoroValue::Binary(bytes) = value else {
423 return None;
424 };
425 if bytes.len() != MERGEABLE_MARKER_LEN || !bytes.starts_with(&MERGEABLE_MARKER_MAGIC) {
426 return None;
427 }
428
429 let kind = ContainerType::try_from_u8(bytes[MERGEABLE_MARKER_MAGIC.len()]).ok()?;
430 if matches!(kind, ContainerType::Unknown(_)) {
431 return None;
432 }
433
434 let digest_start = MERGEABLE_MARKER_MAGIC.len() + 1;
435 let expected = mergeable_marker_crc24(parent, key, kind);
436 if &bytes[digest_start..] != expected.as_slice() {
437 return None;
438 }
439
440 Some(kind)
441}
442
443pub fn translate_mergeable_marker_value(
450 parent: &ContainerID,
451 key: &str,
452 value: LoroValue,
453) -> LoroValue {
454 match parse_mergeable_marker(parent, key, &value) {
455 Some(kind) => LoroValue::Container(ContainerID::new_mergeable(parent, key, kind)),
456 None => value,
457 }
458}
459
460fn mergeable_marker_crc24(parent: &ContainerID, key: &str, kind: ContainerType) -> [u8; 3] {
461 let mut input = Vec::new();
462 input.extend_from_slice(MERGEABLE_MARKER_CRC_DOMAIN);
463 write_len_prefixed_segment(&mut input, &parent.to_bytes());
464 write_len_prefixed_segment(&mut input, key.as_bytes());
465 input.push(kind.to_u8());
466
467 let crc = crc32(&input) & 0x00ff_ffff;
468 [
469 ((crc >> 16) & 0xff) as u8,
470 ((crc >> 8) & 0xff) as u8,
471 (crc & 0xff) as u8,
472 ]
473}
474
475fn crc32(bytes: &[u8]) -> u32 {
476 let mut crc = 0xffff_ffff_u32;
477 for &byte in bytes {
478 crc ^= u32::from(byte);
479 for _ in 0..8 {
480 let mask = (crc & 1).wrapping_neg();
481 crc = (crc >> 1) ^ (0xedb8_8320 & mask);
482 }
483 }
484 !crc
485}
486
487impl CompactId {
488 pub fn new(peer: PeerID, counter: Counter) -> Self {
489 Self {
490 peer,
491 counter: NonMaxI32::new(counter).unwrap(),
492 }
493 }
494
495 pub fn to_id(&self) -> ID {
496 ID {
497 peer: self.peer,
498 counter: self.counter.get(),
499 }
500 }
501
502 pub fn inc(&self, start: i32) -> CompactId {
503 Self {
504 peer: self.peer,
505 counter: NonMaxI32::new(start + self.counter.get()).unwrap(),
506 }
507 }
508}
509
510impl TryFrom<ID> for CompactId {
511 type Error = ID;
512
513 fn try_from(id: ID) -> Result<Self, ID> {
514 if id.counter == i32::MAX {
515 return Err(id);
516 }
517
518 Ok(Self::new(id.peer, id.counter))
519 }
520}
521
522#[derive(PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize, PartialOrd, Ord)]
525pub struct IdLp {
526 pub lamport: Lamport,
527 pub peer: PeerID,
528}
529
530impl IdLp {
531 pub fn compact(self) -> CompactIdLp {
532 CompactIdLp::new(self.peer, self.lamport)
533 }
534}
535
536#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
538pub struct CompactIdLp {
539 pub peer: PeerID,
540 pub lamport: NonMaxU32,
541}
542
543impl CompactIdLp {
544 pub fn new(peer: PeerID, lamport: Lamport) -> Self {
545 Self {
546 peer,
547 lamport: NonMaxU32::new(lamport).unwrap(),
548 }
549 }
550
551 pub fn to_id(&self) -> IdLp {
552 IdLp {
553 peer: self.peer,
554 lamport: self.lamport.get(),
555 }
556 }
557}
558
559impl TryFrom<IdLp> for CompactIdLp {
560 type Error = IdLp;
561
562 fn try_from(id: IdLp) -> Result<Self, IdLp> {
563 if id.lamport == u32::MAX {
564 return Err(id);
565 }
566
567 Ok(Self::new(id.peer, id.lamport))
568 }
569}
570
571#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize)]
573pub struct IdFull {
574 pub peer: PeerID,
575 pub lamport: Lamport,
576 pub counter: Counter,
577}
578
579#[derive(Hash, PartialEq, Eq, Clone, Serialize, Deserialize, EnumAsInner)]
591pub enum ContainerID {
592 Root {
594 name: InternalString,
595 container_type: ContainerType,
596 },
597 Normal {
598 peer: PeerID,
599 counter: Counter,
600 container_type: ContainerType,
601 },
602}
603
604impl ContainerID {
605 pub fn encode<W: Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
606 match self {
607 Self::Root {
608 name,
609 container_type,
610 } => {
611 let first_byte = container_type.to_u8() | 0b10000000;
612 writer.write_all(&[first_byte])?;
613 leb128::write::unsigned(writer, name.len() as u64)?;
614 writer.write_all(name.as_bytes())?;
615 }
616 Self::Normal {
617 peer,
618 counter,
619 container_type,
620 } => {
621 let first_byte = container_type.to_u8();
622 writer.write_all(&[first_byte])?;
623 writer.write_all(&peer.to_le_bytes())?;
624 writer.write_all(&counter.to_le_bytes())?;
625 }
626 }
627
628 Ok(())
629 }
630
631 pub fn to_bytes(&self) -> Vec<u8> {
632 let mut bytes = Vec::with_capacity(13);
634 self.encode(&mut bytes).unwrap();
635 bytes
636 }
637
638 pub fn from_bytes(bytes: &[u8]) -> Self {
639 Self::try_from_bytes(bytes).unwrap()
640 }
641
642 pub fn try_from_bytes(bytes: &[u8]) -> LoroResult<Self> {
643 if bytes.is_empty() {
644 return Err(LoroError::DecodeError(
645 "Decode container id failed".to_string().into_boxed_str(),
646 ));
647 }
648
649 let first_byte = bytes[0];
650 let container_type = ContainerType::try_from_u8(first_byte & 0b01111111)?;
651 let is_root = (first_byte & 0b10000000) != 0;
652
653 let mut reader = &bytes[1..];
654 match is_root {
655 true => {
656 let name_len = leb128::read::unsigned(&mut reader).map_err(|_| {
657 LoroError::DecodeError(
658 "Decode container id failed".to_string().into_boxed_str(),
659 )
660 })?;
661 let name_len = usize::try_from(name_len).map_err(|_| {
662 LoroError::DecodeError(
663 "Decode container id failed".to_string().into_boxed_str(),
664 )
665 })?;
666 if reader.len() != name_len {
667 return Err(LoroError::DecodeError(
668 "Decode container id failed".to_string().into_boxed_str(),
669 ));
670 }
671
672 let name = std::str::from_utf8(&reader[..name_len]).map_err(|_| {
673 LoroError::DecodeError(
674 "Decode container id failed".to_string().into_boxed_str(),
675 )
676 })?;
677 Ok(Self::Root {
678 name: InternalString::from(name),
679 container_type,
680 })
681 }
682 false => {
683 if reader.len() != 12 {
684 return Err(LoroError::DecodeError(
685 "Decode container id failed".to_string().into_boxed_str(),
686 ));
687 }
688
689 let peer = PeerID::from_le_bytes(reader[..8].try_into().unwrap());
690 let counter = i32::from_le_bytes(reader[8..12].try_into().unwrap());
691 Ok(Self::Normal {
692 peer,
693 counter,
694 container_type,
695 })
696 }
697 }
698 }
699
700 const LORO_CONTAINER_ID_PREFIX: &str = "🦜:";
701 pub fn to_loro_value_string(&self) -> String {
702 format!("{}{}", Self::LORO_CONTAINER_ID_PREFIX, self)
703 }
704
705 pub fn try_from_loro_value_string(s: &str) -> Option<Self> {
706 if let Some(s) = s.strip_prefix(Self::LORO_CONTAINER_ID_PREFIX) {
707 Self::try_from(s).ok()
708 } else {
709 None
710 }
711 }
712}
713
714impl std::fmt::Debug for ContainerID {
715 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
716 match self {
717 Self::Root {
718 name,
719 container_type,
720 } => {
721 write!(f, "Root(\"{name}\" {container_type:?})")
722 }
723 Self::Normal {
724 peer,
725 counter,
726 container_type,
727 } => {
728 write!(f, "Normal({container_type:?} {counter}@{peer})")
729 }
730 }
731 }
732}
733
734#[derive(Arbitrary, Debug, PartialEq, Eq, Hash, Clone, Copy, PartialOrd, Ord)]
737pub enum ContainerType {
738 Text,
739 Map,
740 List,
741 MovableList,
742 Tree,
743 #[cfg(feature = "counter")]
744 Counter,
745 Unknown(u8),
746}
747
748impl ContainerType {
749 #[cfg(feature = "counter")]
750 pub const ALL_TYPES: [ContainerType; 6] = [
751 ContainerType::Map,
752 ContainerType::List,
753 ContainerType::Text,
754 ContainerType::Tree,
755 ContainerType::MovableList,
756 ContainerType::Counter,
757 ];
758 #[cfg(not(feature = "counter"))]
759 pub const ALL_TYPES: [ContainerType; 5] = [
760 ContainerType::Map,
761 ContainerType::List,
762 ContainerType::Text,
763 ContainerType::Tree,
764 ContainerType::MovableList,
765 ];
766
767 pub fn default_value(&self) -> LoroValue {
768 match self {
769 ContainerType::Map => LoroValue::Map(Default::default()),
770 ContainerType::List => LoroValue::List(Default::default()),
771 ContainerType::Text => LoroValue::String(Default::default()),
772 ContainerType::Tree => LoroValue::List(Default::default()),
773 ContainerType::MovableList => LoroValue::List(Default::default()),
774 #[cfg(feature = "counter")]
775 ContainerType::Counter => LoroValue::Double(0.),
776 ContainerType::Unknown(_) => unreachable!(),
777 }
778 }
779
780 pub fn to_u8(self) -> u8 {
781 match self {
782 ContainerType::Map => 0,
783 ContainerType::List => 1,
784 ContainerType::Text => 2,
785 ContainerType::Tree => 3,
786 ContainerType::MovableList => 4,
787 #[cfg(feature = "counter")]
788 ContainerType::Counter => 5,
789 ContainerType::Unknown(k) => k,
790 }
791 }
792
793 pub fn try_from_u8(v: u8) -> LoroResult<Self> {
794 match v {
795 0 => Ok(ContainerType::Map),
796 1 => Ok(ContainerType::List),
797 2 => Ok(ContainerType::Text),
798 3 => Ok(ContainerType::Tree),
799 4 => Ok(ContainerType::MovableList),
800 #[cfg(feature = "counter")]
801 5 => Ok(ContainerType::Counter),
802 x => Ok(ContainerType::Unknown(x)),
803 }
804 }
805}
806
807#[derive(Serialize, Deserialize)]
808#[serde(rename = "ContainerType")]
809enum ContainerTypeSerdeRepr {
810 Text,
811 Map,
812 List,
813 MovableList,
814 Tree,
815 #[cfg(feature = "counter")]
816 Counter,
817 Unknown(u8),
818}
819
820fn historical_container_type_to_byte(c: ContainerType) -> u8 {
823 match c {
824 ContainerType::Text => 0,
825 ContainerType::Map => 1,
826 ContainerType::List => 2,
827 ContainerType::MovableList => 3,
828 ContainerType::Tree => 4,
829 #[cfg(feature = "counter")]
830 ContainerType::Counter => 5,
831 ContainerType::Unknown(k) => k,
832 }
833}
834
835fn historical_try_byte_to_container(byte: u8) -> ContainerType {
836 match byte {
837 0 => ContainerType::Text,
838 1 => ContainerType::Map,
839 2 => ContainerType::List,
840 3 => ContainerType::MovableList,
841 4 => ContainerType::Tree,
842 #[cfg(feature = "counter")]
843 5 => ContainerType::Counter,
844 _ => ContainerType::Unknown(byte),
845 }
846}
847
848impl From<ContainerType> for ContainerTypeSerdeRepr {
849 fn from(value: ContainerType) -> Self {
850 match value {
851 ContainerType::Text => Self::Text,
852 ContainerType::Map => Self::Map,
853 ContainerType::List => Self::List,
854 ContainerType::MovableList => Self::MovableList,
855 ContainerType::Tree => Self::Tree,
856 #[cfg(feature = "counter")]
857 ContainerType::Counter => Self::Counter,
858 ContainerType::Unknown(value) => Self::Unknown(value),
859 }
860 }
861}
862
863impl From<ContainerTypeSerdeRepr> for ContainerType {
864 fn from(value: ContainerTypeSerdeRepr) -> Self {
865 match value {
866 ContainerTypeSerdeRepr::Text => ContainerType::Text,
867 ContainerTypeSerdeRepr::Map => ContainerType::Map,
868 ContainerTypeSerdeRepr::List => ContainerType::List,
869 ContainerTypeSerdeRepr::MovableList => ContainerType::MovableList,
870 ContainerTypeSerdeRepr::Tree => ContainerType::Tree,
871 #[cfg(feature = "counter")]
872 ContainerTypeSerdeRepr::Counter => ContainerType::Counter,
873 ContainerTypeSerdeRepr::Unknown(value) => ContainerType::Unknown(value),
874 }
875 }
876}
877
878impl Serialize for ContainerType {
879 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
880 where
881 S: serde::Serializer,
882 {
883 if serializer.is_human_readable() {
884 ContainerTypeSerdeRepr::from(*self).serialize(serializer)
885 } else {
886 serializer.serialize_u8(historical_container_type_to_byte(*self))
887 }
888 }
889}
890
891impl<'de> Deserialize<'de> for ContainerType {
892 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
893 where
894 D: serde::Deserializer<'de>,
895 {
896 if deserializer.is_human_readable() {
897 let repr = ContainerTypeSerdeRepr::deserialize(deserializer)?;
898 Ok(repr.into())
899 } else {
900 let value = u8::deserialize(deserializer)?;
901 Ok(historical_try_byte_to_container(value))
902 }
903 }
904}
905
906pub type IdSpanVector = rustc_hash::FxHashMap<PeerID, CounterSpan>;
907
908mod container {
909 use super::*;
910
911 impl Display for ContainerType {
912 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
913 f.write_str(match self {
914 ContainerType::Map => "Map",
915 ContainerType::List => "List",
916 ContainerType::MovableList => "MovableList",
917 ContainerType::Text => "Text",
918 ContainerType::Tree => "Tree",
919 #[cfg(feature = "counter")]
920 ContainerType::Counter => "Counter",
921 ContainerType::Unknown(k) => return f.write_fmt(format_args!("Unknown({k})")),
922 })
923 }
924 }
925
926 impl Display for ContainerID {
927 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
928 match self {
929 ContainerID::Root {
930 name,
931 container_type,
932 } => f.write_fmt(format_args!("cid:root-{name}:{container_type}"))?,
933 ContainerID::Normal {
934 peer,
935 counter,
936 container_type,
937 } => f.write_fmt(format_args!(
938 "cid:{id}:{container_type}",
939 id = ID::new(*peer, *counter),
940 container_type = container_type
941 ))?,
942 };
943 Ok(())
944 }
945 }
946
947 impl TryFrom<&str> for ContainerID {
948 type Error = ();
949
950 fn try_from(mut s: &str) -> Result<Self, Self::Error> {
951 if !s.starts_with("cid:") {
952 return Err(());
953 }
954
955 s = &s[4..];
956 if s.starts_with("root-") {
957 s = &s[5..];
959 let split = s.rfind(':').ok_or(())?;
960 if split == 0 {
961 return Err(());
962 }
963 let kind = ContainerType::try_from(&s[split + 1..]).map_err(|_| ())?;
964 let name = &s[..split];
965 Ok(ContainerID::Root {
966 name: name.into(),
967 container_type: kind,
968 })
969 } else {
970 let mut iter = s.split(':');
971 let id = iter.next().ok_or(())?;
972 let kind = iter.next().ok_or(())?;
973 if iter.next().is_some() {
974 return Err(());
975 }
976
977 let id = ID::try_from(id).map_err(|_| ())?;
978 let kind = ContainerType::try_from(kind).map_err(|_| ())?;
979 Ok(ContainerID::Normal {
980 peer: id.peer,
981 counter: id.counter,
982 container_type: kind,
983 })
984 }
985 }
986 }
987
988 impl ContainerID {
989 #[inline]
990 pub fn new_normal(id: ID, container_type: ContainerType) -> Self {
991 ContainerID::Normal {
992 peer: id.peer,
993 counter: id.counter,
994 container_type,
995 }
996 }
997
998 #[inline]
999 pub fn new_root(name: &str, container_type: ContainerType) -> Self {
1000 if !check_root_container_name(name) {
1001 panic!(
1002 "Invalid root container name, it should not be empty or contain '/' or '\\0'"
1003 );
1004 } else {
1005 ContainerID::Root {
1006 name: name.into(),
1007 container_type,
1008 }
1009 }
1010 }
1011
1012 #[inline]
1013 pub fn name(&self) -> &InternalString {
1014 match self {
1015 ContainerID::Root { name, .. } => name,
1016 ContainerID::Normal { .. } => unreachable!(),
1017 }
1018 }
1019
1020 #[inline]
1021 pub fn container_type(&self) -> ContainerType {
1022 match self {
1023 ContainerID::Root { container_type, .. } => *container_type,
1024 ContainerID::Normal { container_type, .. } => *container_type,
1025 }
1026 }
1027
1028 pub fn is_unknown(&self) -> bool {
1029 matches!(self.container_type(), ContainerType::Unknown(_))
1030 }
1031
1032 pub fn new_mergeable(
1048 parent: &ContainerID,
1049 key: &str,
1050 container_type: ContainerType,
1051 ) -> Self {
1052 assert_eq!(
1053 parent.container_type(),
1054 ContainerType::Map,
1055 "mergeable child parent must be a map"
1056 );
1057 let parent_len_hint = match parent {
1058 ContainerID::Root { name, .. } => name.len(),
1059 ContainerID::Normal { .. } => 24,
1060 };
1061 let cap = MERGEABLE_NAMESPACE_PREFIX.len() + parent_len_hint + key.len() + 16;
1062 let mut name = String::with_capacity(cap);
1063 name.push_str(MERGEABLE_NAMESPACE_PREFIX);
1064 push_mergeable_parent(&mut name, parent);
1065 name.push('>');
1066 push_mergeable_escaped(&mut name, key);
1067
1068 Self::Root {
1069 name: name.into(),
1070 container_type,
1071 }
1072 }
1073
1074 pub fn is_mergeable(&self) -> bool {
1086 let Self::Root { name, .. } = self else {
1087 return false;
1088 };
1089 name.as_str()
1090 .strip_prefix(MERGEABLE_NAMESPACE_PREFIX)
1091 .and_then(validate_mergeable_payload)
1092 .is_some()
1093 }
1094
1095 pub fn parse_mergeable(&self) -> Option<(ContainerID, String, ContainerType)> {
1101 let Self::Root {
1102 name,
1103 container_type,
1104 } = self
1105 else {
1106 return None;
1107 };
1108 let payload = name.strip_prefix(MERGEABLE_NAMESPACE_PREFIX)?;
1109 let (parent, key) = parse_mergeable_payload(payload)?;
1110 Some((parent, key, *container_type))
1111 }
1112 }
1113
1114 impl TryFrom<&str> for ContainerType {
1115 type Error = LoroError;
1116
1117 fn try_from(value: &str) -> Result<Self, Self::Error> {
1118 match value {
1119 "Map" | "map" => Ok(ContainerType::Map),
1120 "List" | "list" => Ok(ContainerType::List),
1121 "Text" | "text" => Ok(ContainerType::Text),
1122 "Tree" | "tree" => Ok(ContainerType::Tree),
1123 "MovableList" | "movableList" => Ok(ContainerType::MovableList),
1124 #[cfg(feature = "counter")]
1125 "Counter" | "counter" => Ok(ContainerType::Counter),
1126 a => {
1127 if a.ends_with(')') {
1128 let start = a.find('(').ok_or_else(|| {
1129 LoroError::DecodeError(
1130 format!("Invalid container type string \"{value}\"").into(),
1131 )
1132 })?;
1133 let k = a[start+1..a.len() - 1].parse().map_err(|_| {
1134 LoroError::DecodeError(
1135 format!("Unknown container type \"{value}\". The valid options are Map|List|Text|Tree|MovableList.").into(),
1136 )
1137 })?;
1138 match ContainerType::try_from_u8(k) {
1139 Ok(k) => Ok(k),
1140 Err(_) => Ok(ContainerType::Unknown(k)),
1141 }
1142 } else {
1143 Err(LoroError::DecodeError(
1144 format!("Unknown container type \"{value}\". The valid options are Map|List|Text|Tree|MovableList.").into(),
1145 ))
1146 }
1147 }
1148 }
1149 }
1150 }
1151}
1152
1153pub const DELETED_TREE_ROOT: TreeID = TreeID {
1157 peer: PeerID::MAX,
1158 counter: Counter::MAX,
1159};
1160
1161#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
1171
1172pub struct TreeID {
1173 pub peer: PeerID,
1174 pub counter: Counter,
1176}
1177
1178impl TreeID {
1179 #[inline(always)]
1180 pub fn new(peer: PeerID, counter: Counter) -> Self {
1181 Self { peer, counter }
1182 }
1183
1184 pub const fn delete_root() -> Self {
1186 DELETED_TREE_ROOT
1187 }
1188
1189 pub fn is_deleted_root(&self) -> bool {
1191 self == &DELETED_TREE_ROOT
1192 }
1193
1194 pub fn from_id(id: ID) -> Self {
1195 Self {
1196 peer: id.peer,
1197 counter: id.counter,
1198 }
1199 }
1200
1201 pub fn id(&self) -> ID {
1202 ID {
1203 peer: self.peer,
1204 counter: self.counter,
1205 }
1206 }
1207
1208 pub fn associated_meta_container(&self) -> ContainerID {
1209 ContainerID::new_normal(self.id(), ContainerType::Map)
1210 }
1211}
1212
1213impl Display for TreeID {
1214 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1215 self.id().fmt(f)
1216 }
1217}
1218
1219impl TryFrom<&str> for TreeID {
1220 type Error = LoroError;
1221 fn try_from(value: &str) -> Result<Self, Self::Error> {
1222 let id = ID::try_from(value)?;
1223 Ok(TreeID {
1224 peer: id.peer,
1225 counter: id.counter,
1226 })
1227 }
1228}
1229
1230#[cfg(feature = "wasm")]
1231pub mod wasm {
1232 use crate::{LoroError, TreeID};
1233 use wasm_bindgen::JsValue;
1234 impl From<TreeID> for JsValue {
1235 fn from(value: TreeID) -> Self {
1236 JsValue::from_str(&format!("{value}"))
1237 }
1238 }
1239
1240 impl TryFrom<JsValue> for TreeID {
1241 type Error = LoroError;
1242 fn try_from(value: JsValue) -> Result<Self, Self::Error> {
1243 let id = value.as_string().unwrap();
1244 TreeID::try_from(id.as_str())
1245 }
1246 }
1247}
1248
1249#[cfg(test)]
1250mod test {
1251 use crate::{
1252 mergeable_marker, parse_mergeable_marker, ContainerID, ContainerType, LoroValue, ID,
1253 MERGEABLE_MARKER_MAGIC,
1254 };
1255
1256 #[test]
1257 fn mergeable_marker_round_trips_every_kind() {
1258 let parent = ContainerID::new_root("state", ContainerType::Map);
1259 let key = "field";
1260 let kinds = [
1261 ContainerType::Map,
1262 ContainerType::List,
1263 ContainerType::Text,
1264 ContainerType::Tree,
1265 ContainerType::MovableList,
1266 #[cfg(feature = "counter")]
1267 ContainerType::Counter,
1268 ];
1269 for kind in kinds {
1270 assert_eq!(
1271 parse_mergeable_marker(&parent, key, &mergeable_marker(&parent, key, kind)),
1272 Some(kind),
1273 "marker value must parse back to {kind:?}"
1274 );
1275 }
1276 }
1277
1278 #[test]
1279 fn mergeable_marker_exact_layout() {
1280 let parent = ContainerID::new_root("state", ContainerType::Map);
1281 let value = mergeable_marker(&parent, "field", ContainerType::List);
1282 let LoroValue::Binary(bytes) = value else {
1283 panic!("mergeable marker must be a binary value");
1284 };
1285
1286 assert_eq!(bytes.len(), 8);
1287 assert_eq!(
1288 &bytes[..MERGEABLE_MARKER_MAGIC.len()],
1289 MERGEABLE_MARKER_MAGIC
1290 );
1291 assert_eq!(
1292 bytes[MERGEABLE_MARKER_MAGIC.len()],
1293 ContainerType::List.to_u8()
1294 );
1295 }
1296
1297 #[test]
1298 fn parse_mergeable_marker_rejects_non_markers() {
1299 let parent = ContainerID::new_root("state", ContainerType::Map);
1300
1301 assert_eq!(
1303 parse_mergeable_marker(&parent, "field", &LoroValue::String("Map".into())),
1304 None
1305 );
1306 assert_eq!(
1307 parse_mergeable_marker(&parent, "field", &LoroValue::String("not-a-marker".into())),
1308 None
1309 );
1310
1311 assert_eq!(
1313 parse_mergeable_marker(&parent, "field", &LoroValue::Double(1.0)),
1314 None
1315 );
1316 assert_eq!(
1317 parse_mergeable_marker(&parent, "field", &LoroValue::Null),
1318 None
1319 );
1320
1321 assert_eq!(
1323 parse_mergeable_marker(
1324 &parent,
1325 "field",
1326 &LoroValue::Binary(vec![0x00, b'L', b'M'].into()),
1327 ),
1328 None
1329 );
1330
1331 let mut wrong_magic = marker_bytes(mergeable_marker(&parent, "field", ContainerType::Map));
1332 wrong_magic[0] = 0xff;
1333 assert_eq!(
1334 parse_mergeable_marker(&parent, "field", &LoroValue::Binary(wrong_magic.into()),),
1335 None
1336 );
1337
1338 let marker = mergeable_marker(&parent, "field", ContainerType::Map);
1339 assert_eq!(
1340 parse_mergeable_marker(&parent, "other", &marker),
1341 None,
1342 "marker digest binds the marker to its map key"
1343 );
1344
1345 let other_parent = ContainerID::new_root("other", ContainerType::Map);
1346 assert_eq!(
1347 parse_mergeable_marker(&other_parent, "field", &marker),
1348 None,
1349 "marker digest binds the marker to its parent container"
1350 );
1351
1352 let mut wrong_digest = marker_bytes(marker);
1353 *wrong_digest.last_mut().unwrap() ^= 1;
1354 assert_eq!(
1355 parse_mergeable_marker(&parent, "field", &LoroValue::Binary(wrong_digest.into()),),
1356 None
1357 );
1358 }
1359
1360 #[test]
1361 fn is_mergeable_rejects_prefix_only_roots() {
1362 let missing_key = ContainerID::Root {
1363 name: "🤝:$state".into(),
1364 container_type: ContainerType::Map,
1365 };
1366 assert!(
1367 !missing_key.is_mergeable(),
1368 "payload without a key segment must not be mergeable"
1369 );
1370 assert!(missing_key.parse_mergeable().is_none());
1371
1372 let bad_escape = ContainerID::Root {
1373 name: "🤝:$state>not\\xvalid".into(),
1374 container_type: ContainerType::Map,
1375 };
1376 assert!(
1377 !bad_escape.is_mergeable(),
1378 "unknown escapes in the payload must not be mergeable"
1379 );
1380
1381 let parent = ContainerID::new_root("state", ContainerType::Map);
1382 let real = ContainerID::new_mergeable(&parent, "field", ContainerType::Map);
1383 assert!(
1384 real.is_mergeable(),
1385 "valid mergeable cid must remain mergeable"
1386 );
1387 }
1388
1389 #[test]
1392 fn parse_mergeable_rejects_malformed_path_payloads() {
1393 for name in [
1394 "🤝:",
1395 "🤝:$>key",
1396 "🤝:%state>key",
1397 "🤝:@1>key",
1398 "🤝:@:1>key",
1399 "🤝:@1:zzzzzzzzzzzzzz>key",
1400 "🤝:@Z:0>key",
1401 "🤝:@001:0>key",
1402 "🤝:@1:00>key",
1403 "🤝:@1:-0>key",
1404 "🤝:$root\\sname>key",
1405 "🤝:$root\\0name>key",
1406 "🤝:$state>dangling\\",
1407 "🤝:$state>unknown\\xescape",
1408 "🤝:$state>raw/slash",
1409 "🤝:$state>raw\0nul",
1410 ] {
1411 let cid = ContainerID::Root {
1412 name: name.into(),
1413 container_type: ContainerType::Map,
1414 };
1415 assert_eq!(cid.parse_mergeable(), None, "payload {name:?}");
1416 assert!(!cid.is_mergeable(), "payload {name:?}");
1417 }
1418 }
1419
1420 #[test]
1421 fn parse_mergeable_marker_rejects_unknown_kind() {
1422 let parent = ContainerID::new_root("state", ContainerType::Map);
1423 let mut marker = marker_bytes(mergeable_marker(&parent, "field", ContainerType::Map));
1424 marker[MERGEABLE_MARKER_MAGIC.len()] = u8::MAX;
1425
1426 assert_eq!(
1427 parse_mergeable_marker(&parent, "field", &LoroValue::Binary(marker.into())),
1428 None
1429 );
1430 }
1431
1432 fn marker_bytes(value: LoroValue) -> Vec<u8> {
1433 let LoroValue::Binary(bytes) = value else {
1434 panic!("expected binary mergeable marker");
1435 };
1436 bytes.to_vec()
1437 }
1438
1439 #[test]
1440 fn test_container_id_convert_to_and_from_str() {
1441 let id = ContainerID::Root {
1442 name: "name".into(),
1443 container_type: crate::ContainerType::Map,
1444 };
1445 let id_str = id.to_string();
1446 assert_eq!(id_str.as_str(), "cid:root-name:Map");
1447 assert_eq!(ContainerID::try_from(id_str.as_str()).unwrap(), id);
1448
1449 let id = ContainerID::Normal {
1450 counter: 10,
1451 peer: 255,
1452 container_type: crate::ContainerType::Map,
1453 };
1454 let id_str = id.to_string();
1455 assert_eq!(id_str.as_str(), "cid:10@255:Map");
1456 assert_eq!(ContainerID::try_from(id_str.as_str()).unwrap(), id);
1457
1458 let id = ContainerID::try_from("cid:root-a:b:c:Tree").unwrap();
1459 assert_eq!(
1460 id,
1461 ContainerID::new_root("a:b:c", crate::ContainerType::Tree)
1462 );
1463 }
1464
1465 #[test]
1466 fn test_convert_invalid_container_id_str() {
1467 assert!(ContainerID::try_from("cid:root-:Map").is_err());
1468 assert!(ContainerID::try_from("cid:0@:Map").is_err());
1469 assert!(ContainerID::try_from("cid:@:Map").is_err());
1470 assert!(ContainerID::try_from("cid:x@0:Map").is_err());
1471 assert!(ContainerID::try_from("id:0@0:Map").is_err());
1472 assert!(ContainerID::try_from("cid:0@0:Unknown(6)").is_ok());
1473 }
1474
1475 #[test]
1476 fn test_container_id_encode_and_decode() {
1477 let id = ContainerID::new_normal(ID::new(1, 2), ContainerType::Map);
1478 let bytes = id.to_bytes();
1479 assert_eq!(ContainerID::from_bytes(&bytes), id);
1480
1481 let id = ContainerID::new_normal(ID::new(u64::MAX, i32::MAX), ContainerType::Text);
1482 let bytes = id.to_bytes();
1483 assert_eq!(ContainerID::from_bytes(&bytes), id);
1484
1485 let id = ContainerID::new_root("test_root", ContainerType::List);
1486 let bytes = id.to_bytes();
1487 assert_eq!(ContainerID::from_bytes(&bytes), id);
1488
1489 let id = ContainerID::new_normal(ID::new(0, 0), ContainerType::MovableList);
1490 let bytes = id.to_bytes();
1491 assert_eq!(ContainerID::from_bytes(&bytes), id);
1492
1493 let id = ContainerID::new_root(&"x".repeat(1024), ContainerType::Tree);
1494 let bytes = id.to_bytes();
1495 assert_eq!(ContainerID::from_bytes(&bytes), id);
1496
1497 #[cfg(feature = "counter")]
1498 {
1499 let id = ContainerID::new_normal(ID::new(42, 100), ContainerType::Counter);
1500 let bytes = id.to_bytes();
1501 assert_eq!(ContainerID::from_bytes(&bytes), id);
1502 }
1503
1504 let id = ContainerID::new_normal(ID::new(1, 1), ContainerType::Unknown(100));
1505 let bytes = id.to_bytes();
1506 assert_eq!(ContainerID::from_bytes(&bytes), id);
1507 }
1508
1509 #[test]
1510 fn container_id_try_from_bytes_rejects_trailing_bytes() {
1511 let normal = ContainerID::new_normal(ID::new(1, 2), ContainerType::Map);
1512 let mut normal_bytes = normal.to_bytes();
1513 normal_bytes.push(0);
1514 assert!(ContainerID::try_from_bytes(&normal_bytes).is_err());
1515
1516 let root = ContainerID::new_root("root", ContainerType::List);
1517 let mut root_bytes = root.to_bytes();
1518 root_bytes.push(0);
1519 assert!(ContainerID::try_from_bytes(&root_bytes).is_err());
1520 }
1521}