1#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
16pub struct VShardEnvelope {
17 pub version: u16,
19 pub msg_type: VShardMessageType,
21 pub source_node: u64,
23 pub target_node: u64,
25 pub vshard_id: u32,
27 pub payload: Vec<u8>,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
33#[repr(u16)]
34pub enum VShardMessageType {
35 SegmentChunk = 1,
37 SegmentComplete = 2,
39 WalTail = 3,
41 RoutingUpdate = 4,
43 RoutingAck = 5,
45 GhostCreate = 10,
47 GhostDelete = 11,
49 GhostVerifyRequest = 12,
51 GhostVerifyResponse = 13,
53 MigrationBaseCopy = 20,
55 GsiForward = 22,
57 EdgeValidation = 23,
59
60 TsScatterRequest = 40,
63 TsScatterResponse = 41,
65 TsRetentionCommand = 42,
67 TsRetentionAck = 43,
69 TsArchiveCommand = 44,
71 TsArchiveAck = 45,
73
74 VectorScatterRequest = 50,
77 VectorScatterResponse = 51,
79
80 VectorCoarseRouteRequest = 52,
86 VectorCoarseRouteResponse = 53,
89
90 VectorBuildExchangeRequest = 54,
95 VectorBuildExchangeResponse = 55,
98
99 VectorMemRegionRequest = 56,
105 VectorMemRegionResponse = 57,
108
109 SpatialScatterRequest = 60,
112 SpatialScatterResponse = 61,
114
115 CrossShardEvent = 70,
118 CrossShardEventAck = 71,
120 NotifyBroadcast = 72,
122 NotifyBroadcastAck = 73,
124
125 ArrayShardSliceReq = 80,
128 ArrayShardSliceResp = 81,
130 ArrayShardAggReq = 82,
132 ArrayShardAggResp = 83,
134 ArrayShardPutReq = 84,
136 ArrayShardPutResp = 85,
138 ArrayShardDeleteReq = 86,
140 ArrayShardDeleteResp = 87,
142 ArrayShardSurrogateBitmapReq = 88,
144 ArrayShardSurrogateBitmapResp = 89,
146}
147
148pub const WIRE_VERSION: u16 = 2;
153
154impl VShardEnvelope {
155 pub fn new(
156 msg_type: VShardMessageType,
157 source_node: u64,
158 target_node: u64,
159 vshard_id: u32,
160 payload: Vec<u8>,
161 ) -> Self {
162 Self {
163 version: WIRE_VERSION,
164 msg_type,
165 source_node,
166 target_node,
167 vshard_id,
168 payload,
169 }
170 }
171
172 pub fn to_bytes(&self) -> Vec<u8> {
177 let mut buf = Vec::with_capacity(28 + self.payload.len());
178 buf.extend_from_slice(&self.version.to_le_bytes());
179 buf.extend_from_slice(&(self.msg_type as u16).to_le_bytes());
180 buf.extend_from_slice(&self.source_node.to_le_bytes());
181 buf.extend_from_slice(&self.target_node.to_le_bytes());
182 buf.extend_from_slice(&self.vshard_id.to_le_bytes());
183 buf.extend_from_slice(&(self.payload.len() as u32).to_le_bytes());
184 buf.extend_from_slice(&self.payload);
185 buf
186 }
187
188 pub fn from_bytes(buf: &[u8]) -> Option<Self> {
190 if buf.len() < 28 {
191 return None;
192 }
193 let version = u16::from_le_bytes(buf[0..2].try_into().ok()?);
194 let msg_type_raw = u16::from_le_bytes(buf[2..4].try_into().ok()?);
195 let source_node = u64::from_le_bytes(buf[4..12].try_into().ok()?);
196 let target_node = u64::from_le_bytes(buf[12..20].try_into().ok()?);
197 let vshard_id = u32::from_le_bytes(buf[20..24].try_into().ok()?);
198 let payload_len = u32::from_le_bytes(buf[24..28].try_into().ok()?) as usize;
199
200 if buf.len() < 28 + payload_len {
201 return None;
202 }
203 let payload = buf[28..28 + payload_len].to_vec();
204
205 let msg_type = match msg_type_raw {
206 1 => VShardMessageType::SegmentChunk,
207 2 => VShardMessageType::SegmentComplete,
208 3 => VShardMessageType::WalTail,
209 4 => VShardMessageType::RoutingUpdate,
210 5 => VShardMessageType::RoutingAck,
211 10 => VShardMessageType::GhostCreate,
212 11 => VShardMessageType::GhostDelete,
213 12 => VShardMessageType::GhostVerifyRequest,
214 13 => VShardMessageType::GhostVerifyResponse,
215 20 => VShardMessageType::MigrationBaseCopy,
216 22 => VShardMessageType::GsiForward,
217 23 => VShardMessageType::EdgeValidation,
218 40 => VShardMessageType::TsScatterRequest,
220 41 => VShardMessageType::TsScatterResponse,
221 42 => VShardMessageType::TsRetentionCommand,
222 43 => VShardMessageType::TsRetentionAck,
223 44 => VShardMessageType::TsArchiveCommand,
224 45 => VShardMessageType::TsArchiveAck,
225 50 => VShardMessageType::VectorScatterRequest,
226 51 => VShardMessageType::VectorScatterResponse,
227 52 => VShardMessageType::VectorCoarseRouteRequest,
228 53 => VShardMessageType::VectorCoarseRouteResponse,
229 54 => VShardMessageType::VectorBuildExchangeRequest,
230 55 => VShardMessageType::VectorBuildExchangeResponse,
231 56 => VShardMessageType::VectorMemRegionRequest,
232 57 => VShardMessageType::VectorMemRegionResponse,
233 60 => VShardMessageType::SpatialScatterRequest,
234 61 => VShardMessageType::SpatialScatterResponse,
235 70 => VShardMessageType::CrossShardEvent,
236 71 => VShardMessageType::CrossShardEventAck,
237 72 => VShardMessageType::NotifyBroadcast,
238 73 => VShardMessageType::NotifyBroadcastAck,
239 80 => VShardMessageType::ArrayShardSliceReq,
240 81 => VShardMessageType::ArrayShardSliceResp,
241 82 => VShardMessageType::ArrayShardAggReq,
242 83 => VShardMessageType::ArrayShardAggResp,
243 84 => VShardMessageType::ArrayShardPutReq,
244 85 => VShardMessageType::ArrayShardPutResp,
245 86 => VShardMessageType::ArrayShardDeleteReq,
246 87 => VShardMessageType::ArrayShardDeleteResp,
247 88 => VShardMessageType::ArrayShardSurrogateBitmapReq,
248 89 => VShardMessageType::ArrayShardSurrogateBitmapResp,
249 _ => return None,
250 };
251
252 Some(Self {
253 version,
254 msg_type,
255 source_node,
256 target_node,
257 vshard_id,
258 payload,
259 })
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[test]
268 fn wire_version_is_2() {
269 assert_eq!(WIRE_VERSION, 2, "v2 widened vshard_id to u32");
270 }
271
272 #[test]
273 fn envelope_roundtrip() {
274 let env = VShardEnvelope::new(
275 VShardMessageType::SegmentChunk,
276 1,
277 2,
278 42,
279 b"segment data".to_vec(),
280 );
281 let bytes = env.to_bytes();
282 let decoded = VShardEnvelope::from_bytes(&bytes).unwrap();
283 assert_eq!(env, decoded);
284 }
285
286 #[test]
287 fn all_message_types_roundtrip() {
288 let types = [
289 VShardMessageType::SegmentChunk,
290 VShardMessageType::SegmentComplete,
291 VShardMessageType::WalTail,
292 VShardMessageType::RoutingUpdate,
293 VShardMessageType::RoutingAck,
294 VShardMessageType::GhostCreate,
295 VShardMessageType::GhostDelete,
296 VShardMessageType::GhostVerifyRequest,
297 VShardMessageType::GhostVerifyResponse,
298 VShardMessageType::TsScatterRequest,
299 VShardMessageType::TsScatterResponse,
300 VShardMessageType::TsRetentionCommand,
301 VShardMessageType::TsRetentionAck,
302 VShardMessageType::TsArchiveCommand,
303 VShardMessageType::TsArchiveAck,
304 VShardMessageType::VectorScatterRequest,
305 VShardMessageType::VectorScatterResponse,
306 VShardMessageType::VectorCoarseRouteRequest,
307 VShardMessageType::VectorCoarseRouteResponse,
308 VShardMessageType::VectorBuildExchangeRequest,
309 VShardMessageType::VectorBuildExchangeResponse,
310 VShardMessageType::VectorMemRegionRequest,
311 VShardMessageType::VectorMemRegionResponse,
312 VShardMessageType::SpatialScatterRequest,
313 VShardMessageType::SpatialScatterResponse,
314 VShardMessageType::CrossShardEvent,
315 VShardMessageType::CrossShardEventAck,
316 VShardMessageType::NotifyBroadcast,
317 VShardMessageType::NotifyBroadcastAck,
318 VShardMessageType::ArrayShardSliceReq,
319 VShardMessageType::ArrayShardSliceResp,
320 VShardMessageType::ArrayShardAggReq,
321 VShardMessageType::ArrayShardAggResp,
322 VShardMessageType::ArrayShardPutReq,
323 VShardMessageType::ArrayShardPutResp,
324 VShardMessageType::ArrayShardDeleteReq,
325 VShardMessageType::ArrayShardDeleteResp,
326 VShardMessageType::ArrayShardSurrogateBitmapReq,
327 VShardMessageType::ArrayShardSurrogateBitmapResp,
328 ];
329
330 for msg_type in types {
331 let env = VShardEnvelope::new(msg_type, 10, 20, 100, vec![1, 2, 3]);
332 let bytes = env.to_bytes();
333 let decoded = VShardEnvelope::from_bytes(&bytes).unwrap();
334 assert_eq!(decoded.msg_type, msg_type);
335 }
336 }
337
338 #[test]
339 fn truncated_buffer_returns_none() {
340 let env = VShardEnvelope::new(VShardMessageType::WalTail, 1, 2, 0, vec![0; 100]);
341 let bytes = env.to_bytes();
342 assert!(VShardEnvelope::from_bytes(&bytes[..50]).is_none());
344 assert!(VShardEnvelope::from_bytes(&bytes[..10]).is_none());
346 }
347
348 #[test]
349 fn empty_payload() {
350 let env = VShardEnvelope::new(VShardMessageType::RoutingAck, 5, 6, 999, vec![]);
351 let bytes = env.to_bytes();
352 assert_eq!(bytes.len(), 28); let decoded = VShardEnvelope::from_bytes(&bytes).unwrap();
354 assert!(decoded.payload.is_empty());
355 }
356
357 #[test]
358 fn unknown_message_type_returns_none() {
359 let mut env =
360 VShardEnvelope::new(VShardMessageType::SegmentChunk, 1, 2, 0, vec![]).to_bytes();
361 env[2] = 0xFF;
363 env[3] = 0xFF;
364 assert!(VShardEnvelope::from_bytes(&env).is_none());
365 }
366
367 #[test]
368 fn wire_format_is_transport_agnostic() {
369 let env = VShardEnvelope::new(VShardMessageType::SegmentChunk, 1, 2, 42, b"data".to_vec());
372
373 let rdma_bytes = env.to_bytes();
374 let quic_bytes = env.to_bytes();
375 assert_eq!(
376 rdma_bytes, quic_bytes,
377 "wire format must be transport-agnostic"
378 );
379 }
380
381 #[test]
382 fn large_vshard_id_roundtrip() {
383 let env = VShardEnvelope::new(
386 VShardMessageType::WalTail,
387 10,
388 20,
389 0x0001_FFFF,
390 b"payload".to_vec(),
391 );
392 let bytes = env.to_bytes();
393 let decoded = VShardEnvelope::from_bytes(&bytes).unwrap();
394 assert_eq!(decoded.vshard_id, 0x0001_FFFFu32);
395 }
396
397 #[test]
398 fn truncated_below_28_returns_none() {
399 let env = VShardEnvelope::new(VShardMessageType::RoutingAck, 1, 2, 0, vec![]);
401 let bytes = env.to_bytes();
402 assert_eq!(bytes.len(), 28);
403 assert!(VShardEnvelope::from_bytes(&bytes[..27]).is_none());
404 }
405}