1pub const MAGIC: &str = "LRSP";
8pub const MAX_MESSAGE_SIZE: usize = 256 * 1024; #[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum CrdtType {
13 Loro,
15 LoroEphemeralStore,
17 LoroEphemeralStorePersisted,
19 Yjs,
21 YjsAwareness,
23 Elo,
25}
26
27impl CrdtType {
28 pub fn magic_bytes(self) -> [u8; 4] {
29 match self {
30 CrdtType::Loro => *b"%LOR",
31 CrdtType::LoroEphemeralStore => *b"%EPH",
32 CrdtType::LoroEphemeralStorePersisted => *b"%EPS",
33 CrdtType::Yjs => *b"%YJS",
34 CrdtType::YjsAwareness => *b"%YAW",
35 CrdtType::Elo => *b"%ELO",
36 }
37 }
38
39 pub fn from_magic_bytes(bytes: [u8; 4]) -> Option<Self> {
40 match &bytes {
41 b"%LOR" => Some(CrdtType::Loro),
42 b"%EPH" => Some(CrdtType::LoroEphemeralStore),
43 b"%EPS" => Some(CrdtType::LoroEphemeralStorePersisted),
44 b"%YJS" => Some(CrdtType::Yjs),
45 b"%YAW" => Some(CrdtType::YjsAwareness),
46 b"%ELO" => Some(CrdtType::Elo),
47 _ => None,
48 }
49 }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum Permission {
55 Read,
56 Write,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61#[repr(u8)]
62pub enum MessageType {
63 JoinRequest = 0x00,
64 JoinResponseOk = 0x01,
65 JoinError = 0x02,
66 DocUpdate = 0x03,
67 DocUpdateFragmentHeader = 0x04,
68 DocUpdateFragment = 0x05,
69 UpdateError = 0x06,
70 Leave = 0x07,
71}
72
73impl MessageType {
74 pub fn from_u8(v: u8) -> Option<Self> {
75 Some(match v {
76 0x00 => MessageType::JoinRequest,
77 0x01 => MessageType::JoinResponseOk,
78 0x02 => MessageType::JoinError,
79 0x03 => MessageType::DocUpdate,
80 0x04 => MessageType::DocUpdateFragmentHeader,
81 0x05 => MessageType::DocUpdateFragment,
82 0x06 => MessageType::UpdateError,
83 0x07 => MessageType::Leave,
84 _ => return None,
85 })
86 }
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91#[repr(u8)]
92pub enum JoinErrorCode {
93 Unknown = 0x00,
94 VersionUnknown = 0x01,
95 AuthFailed = 0x02,
96 AppError = 0x7f,
97}
98
99impl JoinErrorCode {
100 pub fn from_u8(v: u8) -> Option<Self> {
101 Some(match v {
102 0x00 => JoinErrorCode::Unknown,
103 0x01 => JoinErrorCode::VersionUnknown,
104 0x02 => JoinErrorCode::AuthFailed,
105 0x7f => JoinErrorCode::AppError,
106 _ => return None,
107 })
108 }
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113#[repr(u8)]
114pub enum UpdateErrorCode {
115 Unknown = 0x00,
116 PermissionDenied = 0x03,
117 InvalidUpdate = 0x04,
118 PayloadTooLarge = 0x05,
119 RateLimited = 0x06,
120 FragmentTimeout = 0x07,
121 AppError = 0x7f,
122}
123
124impl UpdateErrorCode {
125 pub fn from_u8(v: u8) -> Option<Self> {
126 Some(match v {
127 0x00 => UpdateErrorCode::Unknown,
128 0x03 => UpdateErrorCode::PermissionDenied,
129 0x04 => UpdateErrorCode::InvalidUpdate,
130 0x05 => UpdateErrorCode::PayloadTooLarge,
131 0x06 => UpdateErrorCode::RateLimited,
132 0x07 => UpdateErrorCode::FragmentTimeout,
133 0x7f => UpdateErrorCode::AppError,
134 _ => return None,
135 })
136 }
137}
138
139#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
141pub struct BatchId(pub [u8; 8]);
142
143impl BatchId {
144 pub fn from_hex(s: &str) -> Result<Self, String> {
145 let s = s.strip_prefix("0x").unwrap_or(s);
146 if s.len() != 16 {
147 return Err("batch id hex must be 16 chars".into());
148 }
149 let mut out = [0u8; 8];
150 for (i, slot) in out.iter_mut().enumerate() {
151 let idx = i * 2;
152 let byte =
153 u8::from_str_radix(&s[idx..idx + 2], 16).map_err(|_| "invalid hex".to_string())?;
154 *slot = byte;
155 }
156 Ok(BatchId(out))
157 }
158
159 pub fn to_hex(self) -> String {
160 let mut s = String::from("0x");
161 for b in self.0.iter() {
162 use std::fmt::Write as _;
163 let _ = write!(s, "{:02x}", b);
164 }
165 s
166 }
167}
168
169#[derive(Debug, Clone, PartialEq, Eq)]
172pub enum ProtocolMessage {
173 JoinRequest {
174 crdt: CrdtType,
175 room_id: String,
176 auth: Vec<u8>,
177 version: Vec<u8>,
178 },
179 JoinResponseOk {
180 crdt: CrdtType,
181 room_id: String,
182 permission: Permission,
183 version: Vec<u8>,
184 extra: Option<Vec<u8>>,
185 },
186 JoinError {
187 crdt: CrdtType,
188 room_id: String,
189 code: JoinErrorCode,
190 message: String,
191 receiver_version: Option<Vec<u8>>,
192 app_code: Option<String>,
193 },
194 DocUpdate {
195 crdt: CrdtType,
196 room_id: String,
197 updates: Vec<Vec<u8>>,
198 },
199 DocUpdateFragmentHeader {
200 crdt: CrdtType,
201 room_id: String,
202 batch_id: BatchId,
203 fragment_count: u64,
204 total_size_bytes: u64,
205 },
206 DocUpdateFragment {
207 crdt: CrdtType,
208 room_id: String,
209 batch_id: BatchId,
210 index: u64,
211 fragment: Vec<u8>,
212 },
213 UpdateError {
214 crdt: CrdtType,
215 room_id: String,
216 code: UpdateErrorCode,
217 message: String,
218 batch_id: Option<BatchId>,
219 app_code: Option<String>,
220 },
221 Leave {
222 crdt: CrdtType,
223 room_id: String,
224 },
225}