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
143pub const PROTOCOL_VERSION: u32 = 1;
145
146pub const MAX_CLIENT_ID_LEN: usize = 128;
148
149pub const MAX_TENANT_ID_LEN: usize = 64;
151
152#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
155#[serde(tag = "method")]
156pub enum Credentials {
157 #[serde(rename = "api_key")]
159 ApiKey { key: String },
160 #[serde(rename = "jwt")]
162 Jwt { token: String },
163 #[serde(rename = "basic")]
165 Basic { username: String, password: String },
166 #[serde(rename = "custom")]
168 Custom { data: serde_json::Value },
169}
170
171impl std::fmt::Debug for Credentials {
172 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173 match self {
174 Credentials::ApiKey { .. } => f
175 .debug_struct("ApiKey")
176 .field("key", &"<redacted>")
177 .finish(),
178 Credentials::Jwt { .. } => f.debug_struct("Jwt").field("token", &"<redacted>").finish(),
179 Credentials::Basic { username, .. } => f
180 .debug_struct("Basic")
181 .field("username", username)
182 .field("password", &"<redacted>")
183 .finish(),
184 Credentials::Custom { .. } => f
185 .debug_struct("Custom")
186 .field("data", &"<redacted>")
187 .finish(),
188 }
189 }
190}
191
192impl Credentials {
193 pub fn method(&self) -> &'static str {
195 match self {
196 Credentials::ApiKey { .. } => "api_key",
197 Credentials::Jwt { .. } => "jwt",
198 Credentials::Basic { .. } => "basic",
199 Credentials::Custom { .. } => "custom",
200 }
201 }
202}
203
204#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
211pub struct Authenticate {
212 pub request_id: String,
213 pub protocol_version: u32,
214 pub tenant_id: String,
215 #[serde(default, skip_serializing_if = "Option::is_none")]
216 pub client_version: Option<String>,
217 #[serde(default, skip_serializing_if = "Option::is_none")]
218 pub client_id: Option<String>,
219 #[serde(default, skip_serializing_if = "Option::is_none")]
220 pub dev_scopes: Option<Vec<String>>,
221 #[serde(flatten)]
222 pub credentials: Credentials,
223}
224
225impl Authenticate {
226 pub fn validate(&self) -> Result<(), &'static str> {
228 let t = &self.tenant_id;
229 if t.is_empty()
230 || t.len() > MAX_TENANT_ID_LEN
231 || !t
232 .chars()
233 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
234 {
235 return Err("tenant_id must be 1 to 64 characters of letters, digits, _ and -");
236 }
237 if let Some(c) = &self.client_id {
238 if c.is_empty()
239 || c.len() > MAX_CLIENT_ID_LEN
240 || !c.chars().all(|ch| {
241 ch.is_ascii_alphanumeric() || matches!(ch, '_' | '-' | '.' | '@' | ':')
242 })
243 {
244 return Err(
245 "client_id must be 1 to 128 characters of letters, digits and _ - . @ :",
246 );
247 }
248 }
249 match &self.credentials {
250 Credentials::ApiKey { key } if key.is_empty() => Err("the api key must not be empty"),
251 Credentials::Jwt { token } if token.is_empty() => Err("the token must not be empty"),
252 Credentials::Basic { username, password }
253 if username.is_empty() || password.is_empty() =>
254 {
255 Err("username and password must not be empty")
256 }
257 _ => Ok(()),
258 }
259 }
260}
261
262#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
264#[serde(deny_unknown_fields)]
265pub struct AuthenticationResult {
266 pub request_id: String,
267 pub success: bool,
268 pub message: String,
269 pub server_version: String,
270 #[serde(default, skip_serializing_if = "Option::is_none")]
272 pub client_id: Option<String>,
273 #[serde(default, skip_serializing_if = "Option::is_none")]
275 pub permissions: Option<Vec<String>>,
276 #[serde(default, skip_serializing_if = "Option::is_none")]
277 pub expires_in_seconds: Option<u64>,
278}
279
280#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
282#[serde(deny_unknown_fields)]
283pub struct FlushAck {
284 pub request_id: String,
285 pub file_id: u32,
286 pub seq: u64,
287}
288
289#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
291#[serde(deny_unknown_fields)]
292pub struct Resync {
293 pub request_id: String,
294 pub file_id: u32,
295}
296
297#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
299#[serde(deny_unknown_fields)]
300pub struct ResyncDone {
301 pub request_id: String,
302 pub file_id: u32,
303 pub seq: u64,
304 pub size: u64,
305}
306
307#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
309#[serde(deny_unknown_fields)]
310pub struct ErrorReply {
311 pub code: ErrorCode,
312 pub message: String,
313 #[serde(default, skip_serializing_if = "Option::is_none")]
314 pub request_id: Option<String>,
315}
316
317#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
319#[serde(deny_unknown_fields)]
320pub struct GetServiceFilePath {
321 pub request_id: String,
322 pub service_id: String,
323}
324
325#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
329#[serde(deny_unknown_fields)]
330pub struct ServiceFilePath {
331 pub request_id: String,
332 pub service_id: String,
333 pub file_id: u32,
334 #[serde(default, skip_serializing_if = "Option::is_none")]
335 pub path: Option<String>,
336 #[serde(default, skip_serializing_if = "Option::is_none")]
337 pub nonce_path: Option<String>,
338}
339
340#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
342#[serde(deny_unknown_fields)]
343pub struct ConfirmLocal {
344 pub request_id: String,
345 pub file_id: u32,
346 pub nonce: String,
347}
348
349impl std::fmt::Debug for ConfirmLocal {
350 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
352 f.debug_struct("ConfirmLocal")
353 .field("request_id", &self.request_id)
354 .field("file_id", &self.file_id)
355 .field("nonce", &"<redacted>")
356 .finish()
357 }
358}
359
360#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
363#[serde(deny_unknown_fields)]
364pub struct ConfirmLocalAck {
365 pub request_id: String,
366 pub file_id: u32,
367}
368
369#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
371#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
372#[non_exhaustive]
373pub enum RotateReason {
374 Resize,
376 Revocation,
378}
379
380#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
383#[serde(deny_unknown_fields)]
384pub struct RotateRequest {
385 pub request_id: String,
386 pub service_id: String,
387 pub reason: RotateReason,
388 #[serde(default, skip_serializing_if = "Option::is_none")]
389 pub new_size: Option<u64>,
390 #[serde(default, skip_serializing_if = "Option::is_none")]
391 pub revoke_client_id: Option<String>,
392}
393
394impl RotateRequest {
395 pub fn validate(&self) -> Result<(), &'static str> {
397 match self.reason {
398 RotateReason::Resize => match (self.new_size, &self.revoke_client_id) {
399 (Some(n), None) if n > 0 => Ok(()),
400 (Some(0), _) => Err("new_size must be at least 1"),
401 (None, _) => Err("RESIZE needs new_size"),
402 (_, Some(_)) => Err("RESIZE must not name a client"),
403 _ => Err("invalid RESIZE request"),
404 },
405 RotateReason::Revocation => match (self.new_size, &self.revoke_client_id) {
406 (None, Some(c)) if !c.is_empty() => Ok(()),
407 (_, None) => Err("REVOCATION needs revoke_client_id"),
408 (Some(_), _) => Err("REVOCATION must not set new_size"),
409 _ => Err("invalid REVOCATION request"),
410 },
411 }
412 }
413}
414
415#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
417#[serde(deny_unknown_fields)]
418pub struct RotationAccepted {
419 pub request_id: String,
420 pub rotation_id: String,
421}
422
423#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
429#[serde(deny_unknown_fields)]
430pub struct RotationPending {
431 pub rotation_id: String,
432 pub service_id: String,
433 pub file_id: u32,
434 pub deadline_ms: u64,
435 pub fill_ms: u64,
436}
437
438#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
443#[serde(deny_unknown_fields)]
444pub struct RotationPrepared {
445 pub rotation_id: String,
446 pub service_id: String,
447 pub old_file_id: u32,
448 pub new_file_id: u32,
449 pub size: u64,
450 #[serde(default, skip_serializing_if = "Option::is_none")]
451 pub path: Option<String>,
452}
453
454#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
456#[serde(deny_unknown_fields)]
457pub struct RotationFilled {
458 pub request_id: String,
459 pub rotation_id: String,
460}
461
462#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
464#[serde(deny_unknown_fields)]
465pub struct RotationAbort {
466 pub request_id: String,
467 pub rotation_id: String,
468}
469
470#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
472#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
473#[non_exhaustive]
474pub enum AbortReason {
475 RequesterAbort,
477 FillTimeout,
479 ServerError,
481 #[serde(other)]
483 Unknown,
484}
485
486#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
489#[serde(deny_unknown_fields)]
490pub struct RotationAborted {
491 pub rotation_id: String,
492 pub service_id: String,
493 pub file_id: u32,
494 pub reason: AbortReason,
495}
496
497#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
499#[serde(deny_unknown_fields)]
500pub struct RotationDrained {
501 pub request_id: String,
502 pub rotation_id: String,
503 pub file_id: u32,
504 pub seq: u64,
505}
506
507#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
512#[serde(deny_unknown_fields)]
513pub struct RotationCommit {
514 pub rotation_id: String,
515 pub service_id: String,
516 pub reason: RotateReason,
517 pub old_file_id: u32,
518 pub new_file_id: u32,
519 pub generation: u64,
520 pub size: u64,
521}
522
523#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
525#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
526#[non_exhaustive]
527pub enum LossKind {
528 RotationStraggler,
530 ResyncOverwrote,
532 Revoked,
534 #[serde(other)]
536 Unknown,
537}
538
539#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
541#[serde(deny_unknown_fields)]
542pub struct LossRange {
543 pub offset: u64,
544 pub len: u64,
545}
546
547#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
550#[serde(deny_unknown_fields)]
551pub struct Loss {
552 pub service_id: String,
553 pub file_id: u32,
554 pub kind: LossKind,
555 #[serde(default, skip_serializing_if = "Vec::is_empty")]
556 pub ranges: Vec<LossRange>,
557}
558
559#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
561#[serde(deny_unknown_fields)]
562pub struct ByteRange {
563 pub offset: u64,
564 pub len: u64,
565}
566
567pub const MAX_RANGES_PER_MESSAGE: usize = 1024;
570
571pub fn validate_ranges(ranges: &[ByteRange]) -> Result<(), &'static str> {
575 if ranges.is_empty() {
576 return Err("ranges must not be empty");
577 }
578 if ranges.len() > MAX_RANGES_PER_MESSAGE {
579 return Err("too many ranges in one message");
580 }
581 let mut prev_end = 0u64;
582 for r in ranges {
583 if r.len == 0 {
584 return Err("a range must cover at least one byte");
585 }
586 let Some(end) = r.offset.checked_add(r.len) else {
587 return Err("offset plus len overflows");
588 };
589 if r.offset < prev_end {
590 return Err("ranges must be sorted and must not overlap");
591 }
592 prev_end = end;
593 }
594 Ok(())
595}
596
597#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
601#[serde(deny_unknown_fields)]
602pub struct RangesChanged {
603 pub request_id: String,
604 pub file_id: u32,
605 pub ranges: Vec<ByteRange>,
606}
607
608#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
611#[serde(deny_unknown_fields)]
612pub struct RangesUpdated {
613 pub file_id: u32,
614 pub seq: u64,
615 pub ranges: Vec<ByteRange>,
616}
617
618#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
621#[serde(deny_unknown_fields)]
622pub struct FlushEnd {
623 pub request_id: String,
624 pub file_id: u32,
625}
626
627#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
631#[serde(deny_unknown_fields)]
632pub struct Grant {
633 pub request_id: String,
634 pub client_id: String,
635 #[serde(default, skip_serializing_if = "Option::is_none")]
636 pub service_id: Option<String>,
637 pub permissions: Vec<String>,
638}
639
640impl Grant {
641 pub fn validate(&self) -> Result<(), &'static str> {
643 validate_grant_fields(&self.client_id, Some(&self.permissions), true)
644 }
645}
646
647#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
651#[serde(deny_unknown_fields)]
652pub struct Revoke {
653 pub request_id: String,
654 pub client_id: String,
655 #[serde(default, skip_serializing_if = "Option::is_none")]
656 pub service_id: Option<String>,
657 #[serde(default, skip_serializing_if = "Vec::is_empty")]
658 pub permissions: Vec<String>,
659}
660
661impl Revoke {
662 pub fn validate(&self) -> Result<(), &'static str> {
664 validate_grant_fields(&self.client_id, Some(&self.permissions), false)
665 }
666}
667
668fn validate_grant_fields(
669 client_id: &str,
670 permissions: Option<&[String]>,
671 need_permissions: bool,
672) -> Result<(), &'static str> {
673 if client_id.trim().is_empty() {
674 return Err("client_id must not be empty");
675 }
676 if let Some(p) = permissions {
677 if need_permissions && p.is_empty() {
678 return Err("permissions must not be empty");
679 }
680 if p.iter().any(|name| name.trim().is_empty()) {
681 return Err("a permission name must not be blank");
682 }
683 }
684 Ok(())
685}
686
687#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
689#[serde(deny_unknown_fields)]
690pub struct GrantAck {
691 pub request_id: String,
692}
693
694#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
696#[serde(deny_unknown_fields)]
697pub struct ListGrants {
698 pub request_id: String,
699 #[serde(default, skip_serializing_if = "Option::is_none")]
700 pub client_id: Option<String>,
701 #[serde(default, skip_serializing_if = "Option::is_none")]
702 pub service_id: Option<String>,
703}
704
705#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
707#[serde(deny_unknown_fields)]
708pub struct GrantEntry {
709 pub client_id: String,
710 #[serde(default, skip_serializing_if = "Option::is_none")]
711 pub service_id: Option<String>,
712 pub permissions: Vec<String>,
713}
714
715#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
717#[serde(deny_unknown_fields)]
718pub struct Grants {
719 pub request_id: String,
720 pub entries: Vec<GrantEntry>,
721}
722
723#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
729#[serde(deny_unknown_fields)]
730pub struct SetLocalMapping {
731 pub request_id: String,
732 pub service_id: String,
733 pub enabled: bool,
734}
735
736#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
738#[serde(deny_unknown_fields)]
739pub struct LocalMappingAck {
740 pub request_id: String,
741 pub service_id: String,
742 pub enabled: bool,
743}
744
745#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
748#[serde(tag = "type", content = "data")]
749#[non_exhaustive]
750pub enum ControlMessage {
751 Authenticate(Authenticate),
752 AuthenticationResult(AuthenticationResult),
753 FlushAck(FlushAck),
754 Resync(Resync),
755 ResyncDone(ResyncDone),
756 Error(ErrorReply),
757 GetServiceFilePath(GetServiceFilePath),
758 ServiceFilePath(ServiceFilePath),
759 ConfirmLocal(ConfirmLocal),
760 ConfirmLocalAck(ConfirmLocalAck),
761 RotateRequest(RotateRequest),
762 RotationAccepted(RotationAccepted),
763 RotationPending(RotationPending),
764 RotationPrepared(RotationPrepared),
765 RotationFilled(RotationFilled),
766 RotationAbort(RotationAbort),
767 RotationAborted(RotationAborted),
768 RotationDrained(RotationDrained),
769 RotationCommit(RotationCommit),
770 Loss(Loss),
771 RangesChanged(RangesChanged),
772 RangesUpdated(RangesUpdated),
773 FlushEnd(FlushEnd),
774 Grant(Grant),
775 Revoke(Revoke),
776 GrantAck(GrantAck),
777 ListGrants(ListGrants),
778 Grants(Grants),
779 SetLocalMapping(SetLocalMapping),
780 LocalMappingAck(LocalMappingAck),
781}