1use serde::{Deserialize, Serialize};
8
9pub const KIND_RANGE: u8 = 1;
11
12pub const HEADER_LEN: usize = 25;
14
15pub const MAX_PAYLOAD: usize = 1024 * 1024;
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20#[non_exhaustive]
21pub enum FrameError {
22 Truncated,
24 BadKind,
26 LengthMismatch,
28 EmptyRange,
30 OffsetOverflow,
32 TooLarge,
34}
35
36#[derive(Clone, Debug, PartialEq, Eq)]
38pub struct RangeFrame {
39 pub file_id: u32,
40 pub seq: u64,
41 pub offset: u64,
42 pub data: Vec<u8>,
43}
44
45impl RangeFrame {
46 pub fn split(file_id: u32, seq: u64, offset: u64, data: &[u8]) -> Vec<RangeFrame> {
48 data.chunks(MAX_PAYLOAD)
49 .enumerate()
50 .map(|(i, chunk)| RangeFrame {
51 file_id,
52 seq,
53 offset: offset + (i * MAX_PAYLOAD) as u64,
54 data: chunk.to_vec(),
55 })
56 .collect()
57 }
58
59 pub fn encode(&self) -> Vec<u8> {
61 let mut out = Vec::with_capacity(HEADER_LEN + self.data.len());
62 out.push(KIND_RANGE);
63 out.extend_from_slice(&self.file_id.to_le_bytes());
64 out.extend_from_slice(&self.seq.to_le_bytes());
65 out.extend_from_slice(&self.offset.to_le_bytes());
66 out.extend_from_slice(&(self.data.len() as u32).to_le_bytes());
67 out.extend_from_slice(&self.data);
68 out
69 }
70
71 pub fn decode(bytes: &[u8]) -> Result<RangeFrame, FrameError> {
72 if bytes.len() < HEADER_LEN {
73 return Err(FrameError::Truncated);
74 }
75 if bytes[0] != KIND_RANGE {
76 return Err(FrameError::BadKind);
77 }
78 let file_id = u32::from_le_bytes(bytes[1..5].try_into().unwrap());
79 let seq = u64::from_le_bytes(bytes[5..13].try_into().unwrap());
80 let offset = u64::from_le_bytes(bytes[13..21].try_into().unwrap());
81 let len = u32::from_le_bytes(bytes[21..25].try_into().unwrap());
82 if len as usize > MAX_PAYLOAD {
83 return Err(FrameError::TooLarge);
84 }
85 let data = &bytes[HEADER_LEN..];
86 if data.len() as u64 != len as u64 {
87 return Err(FrameError::LengthMismatch);
88 }
89 if len == 0 {
90 return Err(FrameError::EmptyRange);
91 }
92 if offset.checked_add(len as u64).is_none() {
93 return Err(FrameError::OffsetOverflow);
94 }
95 Ok(RangeFrame {
96 file_id,
97 seq,
98 offset,
99 data: data.to_vec(),
100 })
101 }
102}
103
104#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
106#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
107#[non_exhaustive]
108pub enum ErrorCode {
109 InvalidRequest,
110 Unauthenticated,
111 PermissionDenied,
112 NotFound,
113 RateLimited,
114 UnsupportedVersion,
115 InternalError,
116}
117
118impl ErrorCode {
119 pub const ALL: [ErrorCode; 7] = [
121 ErrorCode::InvalidRequest,
122 ErrorCode::Unauthenticated,
123 ErrorCode::PermissionDenied,
124 ErrorCode::NotFound,
125 ErrorCode::RateLimited,
126 ErrorCode::UnsupportedVersion,
127 ErrorCode::InternalError,
128 ];
129
130 pub fn as_str(&self) -> &'static str {
131 match self {
132 ErrorCode::InvalidRequest => "INVALID_REQUEST",
133 ErrorCode::Unauthenticated => "UNAUTHENTICATED",
134 ErrorCode::PermissionDenied => "PERMISSION_DENIED",
135 ErrorCode::NotFound => "NOT_FOUND",
136 ErrorCode::RateLimited => "RATE_LIMITED",
137 ErrorCode::UnsupportedVersion => "UNSUPPORTED_VERSION",
138 ErrorCode::InternalError => "INTERNAL_ERROR",
139 }
140 }
141}
142
143#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
145#[serde(deny_unknown_fields)]
146pub struct Authenticate {
147 pub request_id: String,
148 pub protocol_version: u32,
149 pub tenant_id: String,
150 pub client_id: String,
151 pub auth_method: String,
152 pub credentials: String,
153}
154
155#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
157#[serde(deny_unknown_fields)]
158pub struct FlushAck {
159 pub request_id: String,
160 pub file_id: u32,
161 pub seq: u64,
162}
163
164#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
166#[serde(deny_unknown_fields)]
167pub struct Resync {
168 pub request_id: String,
169 pub file_id: u32,
170}
171
172#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
174#[serde(deny_unknown_fields)]
175pub struct ResyncDone {
176 pub request_id: String,
177 pub file_id: u32,
178 pub seq: u64,
179 pub size: u64,
180}
181
182#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
184#[serde(deny_unknown_fields)]
185pub struct ErrorReply {
186 pub code: ErrorCode,
187 pub message: String,
188 #[serde(default, skip_serializing_if = "Option::is_none")]
189 pub request_id: Option<String>,
190}
191
192#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
194#[serde(deny_unknown_fields)]
195pub struct GetServiceFilePath {
196 pub request_id: String,
197 pub service_id: String,
198}
199
200#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
204#[serde(deny_unknown_fields)]
205pub struct ServiceFilePath {
206 pub request_id: String,
207 pub service_id: String,
208 pub file_id: u32,
209 #[serde(default, skip_serializing_if = "Option::is_none")]
210 pub path: Option<String>,
211 #[serde(default, skip_serializing_if = "Option::is_none")]
212 pub nonce_path: Option<String>,
213}
214
215#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
217#[serde(deny_unknown_fields)]
218pub struct ConfirmLocal {
219 pub request_id: String,
220 pub file_id: u32,
221 pub nonce: String,
222}
223
224impl std::fmt::Debug for ConfirmLocal {
225 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
227 f.debug_struct("ConfirmLocal")
228 .field("request_id", &self.request_id)
229 .field("file_id", &self.file_id)
230 .field("nonce", &"<redacted>")
231 .finish()
232 }
233}
234
235#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
238#[serde(deny_unknown_fields)]
239pub struct ConfirmLocalAck {
240 pub request_id: String,
241 pub file_id: u32,
242}
243
244#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
246#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
247#[non_exhaustive]
248pub enum RotateReason {
249 Resize,
251 Revocation,
253}
254
255#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
258#[serde(deny_unknown_fields)]
259pub struct RotateRequest {
260 pub request_id: String,
261 pub service_id: String,
262 pub reason: RotateReason,
263 #[serde(default, skip_serializing_if = "Option::is_none")]
264 pub new_size: Option<u64>,
265 #[serde(default, skip_serializing_if = "Option::is_none")]
266 pub revoke_client_id: Option<String>,
267}
268
269impl RotateRequest {
270 pub fn validate(&self) -> Result<(), &'static str> {
272 match self.reason {
273 RotateReason::Resize => match (self.new_size, &self.revoke_client_id) {
274 (Some(n), None) if n > 0 => Ok(()),
275 (Some(0), _) => Err("new_size must be at least 1"),
276 (None, _) => Err("RESIZE needs new_size"),
277 (_, Some(_)) => Err("RESIZE must not name a client"),
278 _ => Err("invalid RESIZE request"),
279 },
280 RotateReason::Revocation => match (self.new_size, &self.revoke_client_id) {
281 (None, Some(c)) if !c.is_empty() => Ok(()),
282 (_, None) => Err("REVOCATION needs revoke_client_id"),
283 (Some(_), _) => Err("REVOCATION must not set new_size"),
284 _ => Err("invalid REVOCATION request"),
285 },
286 }
287 }
288}
289
290#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
292#[serde(deny_unknown_fields)]
293pub struct RotationAccepted {
294 pub request_id: String,
295 pub rotation_id: String,
296}
297
298#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
304#[serde(deny_unknown_fields)]
305pub struct RotationPending {
306 pub rotation_id: String,
307 pub service_id: String,
308 pub file_id: u32,
309 pub deadline_ms: u64,
310 pub fill_ms: u64,
311}
312
313#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
318#[serde(deny_unknown_fields)]
319pub struct RotationPrepared {
320 pub rotation_id: String,
321 pub service_id: String,
322 pub old_file_id: u32,
323 pub new_file_id: u32,
324 pub size: u64,
325 #[serde(default, skip_serializing_if = "Option::is_none")]
326 pub path: Option<String>,
327}
328
329#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
331#[serde(deny_unknown_fields)]
332pub struct RotationFilled {
333 pub request_id: String,
334 pub rotation_id: String,
335}
336
337#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
339#[serde(deny_unknown_fields)]
340pub struct RotationAbort {
341 pub request_id: String,
342 pub rotation_id: String,
343}
344
345#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
347#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
348#[non_exhaustive]
349pub enum AbortReason {
350 RequesterAbort,
352 FillTimeout,
354 ServerError,
356 #[serde(other)]
358 Unknown,
359}
360
361#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
364#[serde(deny_unknown_fields)]
365pub struct RotationAborted {
366 pub rotation_id: String,
367 pub service_id: String,
368 pub file_id: u32,
369 pub reason: AbortReason,
370}
371
372#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
374#[serde(deny_unknown_fields)]
375pub struct RotationDrained {
376 pub request_id: String,
377 pub rotation_id: String,
378 pub file_id: u32,
379 pub seq: u64,
380}
381
382#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
387#[serde(deny_unknown_fields)]
388pub struct RotationCommit {
389 pub rotation_id: String,
390 pub service_id: String,
391 pub reason: RotateReason,
392 pub old_file_id: u32,
393 pub new_file_id: u32,
394 pub generation: u64,
395 pub size: u64,
396}
397
398#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
400#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
401#[non_exhaustive]
402pub enum LossKind {
403 RotationStraggler,
405 ResyncOverwrote,
407 Revoked,
409 #[serde(other)]
411 Unknown,
412}
413
414#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
416#[serde(deny_unknown_fields)]
417pub struct LossRange {
418 pub offset: u64,
419 pub len: u64,
420}
421
422#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
425#[serde(deny_unknown_fields)]
426pub struct Loss {
427 pub service_id: String,
428 pub file_id: u32,
429 pub kind: LossKind,
430 #[serde(default, skip_serializing_if = "Vec::is_empty")]
431 pub ranges: Vec<LossRange>,
432}
433
434#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
436#[serde(deny_unknown_fields)]
437pub struct ByteRange {
438 pub offset: u64,
439 pub len: u64,
440}
441
442pub const MAX_RANGES_PER_MESSAGE: usize = 1024;
445
446pub fn validate_ranges(ranges: &[ByteRange]) -> Result<(), &'static str> {
450 if ranges.is_empty() {
451 return Err("ranges must not be empty");
452 }
453 if ranges.len() > MAX_RANGES_PER_MESSAGE {
454 return Err("too many ranges in one message");
455 }
456 let mut prev_end = 0u64;
457 for r in ranges {
458 if r.len == 0 {
459 return Err("a range must cover at least one byte");
460 }
461 let Some(end) = r.offset.checked_add(r.len) else {
462 return Err("offset plus len overflows");
463 };
464 if r.offset < prev_end {
465 return Err("ranges must be sorted and must not overlap");
466 }
467 prev_end = end;
468 }
469 Ok(())
470}
471
472#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
476#[serde(deny_unknown_fields)]
477pub struct RangesChanged {
478 pub request_id: String,
479 pub file_id: u32,
480 pub ranges: Vec<ByteRange>,
481}
482
483#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
486#[serde(deny_unknown_fields)]
487pub struct RangesUpdated {
488 pub file_id: u32,
489 pub seq: u64,
490 pub ranges: Vec<ByteRange>,
491}
492
493#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
496#[serde(deny_unknown_fields)]
497pub struct FlushEnd {
498 pub request_id: String,
499 pub file_id: u32,
500}
501
502#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
506#[serde(deny_unknown_fields)]
507pub struct Grant {
508 pub request_id: String,
509 pub client_id: String,
510 #[serde(default, skip_serializing_if = "Option::is_none")]
511 pub service_id: Option<String>,
512 pub permissions: Vec<String>,
513}
514
515impl Grant {
516 pub fn validate(&self) -> Result<(), &'static str> {
518 validate_grant_fields(&self.client_id, Some(&self.permissions), true)
519 }
520}
521
522#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
526#[serde(deny_unknown_fields)]
527pub struct Revoke {
528 pub request_id: String,
529 pub client_id: String,
530 #[serde(default, skip_serializing_if = "Option::is_none")]
531 pub service_id: Option<String>,
532 #[serde(default, skip_serializing_if = "Vec::is_empty")]
533 pub permissions: Vec<String>,
534}
535
536impl Revoke {
537 pub fn validate(&self) -> Result<(), &'static str> {
539 validate_grant_fields(&self.client_id, Some(&self.permissions), false)
540 }
541}
542
543fn validate_grant_fields(
544 client_id: &str,
545 permissions: Option<&[String]>,
546 need_permissions: bool,
547) -> Result<(), &'static str> {
548 if client_id.trim().is_empty() {
549 return Err("client_id must not be empty");
550 }
551 if let Some(p) = permissions {
552 if need_permissions && p.is_empty() {
553 return Err("permissions must not be empty");
554 }
555 if p.iter().any(|name| name.trim().is_empty()) {
556 return Err("a permission name must not be blank");
557 }
558 }
559 Ok(())
560}
561
562#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
564#[serde(deny_unknown_fields)]
565pub struct GrantAck {
566 pub request_id: String,
567}
568
569#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
571#[serde(deny_unknown_fields)]
572pub struct ListGrants {
573 pub request_id: String,
574 #[serde(default, skip_serializing_if = "Option::is_none")]
575 pub client_id: Option<String>,
576 #[serde(default, skip_serializing_if = "Option::is_none")]
577 pub service_id: Option<String>,
578}
579
580#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
582#[serde(deny_unknown_fields)]
583pub struct GrantEntry {
584 pub client_id: String,
585 #[serde(default, skip_serializing_if = "Option::is_none")]
586 pub service_id: Option<String>,
587 pub permissions: Vec<String>,
588}
589
590#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
592#[serde(deny_unknown_fields)]
593pub struct Grants {
594 pub request_id: String,
595 pub entries: Vec<GrantEntry>,
596}
597
598#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
601#[serde(tag = "type", content = "data")]
602#[non_exhaustive]
603pub enum ControlMessage {
604 Authenticate(Authenticate),
605 FlushAck(FlushAck),
606 Resync(Resync),
607 ResyncDone(ResyncDone),
608 Error(ErrorReply),
609 GetServiceFilePath(GetServiceFilePath),
610 ServiceFilePath(ServiceFilePath),
611 ConfirmLocal(ConfirmLocal),
612 ConfirmLocalAck(ConfirmLocalAck),
613 RotateRequest(RotateRequest),
614 RotationAccepted(RotationAccepted),
615 RotationPending(RotationPending),
616 RotationPrepared(RotationPrepared),
617 RotationFilled(RotationFilled),
618 RotationAbort(RotationAbort),
619 RotationAborted(RotationAborted),
620 RotationDrained(RotationDrained),
621 RotationCommit(RotationCommit),
622 Loss(Loss),
623 RangesChanged(RangesChanged),
624 RangesUpdated(RangesUpdated),
625 FlushEnd(FlushEnd),
626 Grant(Grant),
627 Revoke(Revoke),
628 GrantAck(GrantAck),
629 ListGrants(ListGrants),
630 Grants(Grants),
631}