Skip to main content

wire/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Shared protocol/auth transport types.
3//! Live authorization scope rules are owned by weft-server/src/access/scope.rs.
4
5#[cfg(test)]
6mod auth_tests;
7mod auth_token;
8mod key_binding;
9mod message_hosted;
10mod message_objects;
11mod message_pushpull;
12mod message_refs;
13mod message_status;
14mod native_pack;
15mod object_availability;
16mod object_graph;
17mod object_transfer;
18mod provider_pack;
19mod semantic_graph;
20mod transfer_plan;
21
22pub use auth_token::AuthToken;
23pub use key_binding::{
24    WireKeyBinding, WireKeyBindingLiveness, WireKeyBindingRegistry, decode_key_binding_registry,
25    encode_key_binding_registry,
26};
27pub use message_hosted::{
28    HarnessIdentity, HostedGrantInfo, HostedNamespaceInfo, HostedRepositoryInfo, HostedSpoolInfo,
29    HostedSpoolKind, ProgressCheckpoint, SessionDiffSummary, SessionReportEnvelope,
30    TranscriptAttachmentRef, UsageTotals, WorktreeChangeBaseline,
31};
32pub use message_objects::{ObjectData, ObjectRequest};
33pub use message_pushpull::{PullComplete, PushComplete};
34pub use message_refs::{
35    AdvertisedRef, AdvertisedRefError, HeadInfo, RefEntry, RefFilter, RefKind, RefUpdated, RefsList,
36};
37pub use message_status::{
38    Error, ErrorCode, RemoteCursorFailure, RemoteCursorReason, RemoteDuration, RemoteFailureCode,
39    RemoteFailureDetail, RemoteTimestamp,
40};
41pub use native_pack::{
42    GitPackChunkState, GrowingPackChunkReader, MAX_RECEIVED_GIT_PACK_SIZE,
43    MAX_RECEIVED_PACK_INDEX_SIZE, MAX_RECEIVED_PACK_SIZE, NativePackBundle, NativePackFileBundle,
44    NativePackStreamingWriter, PackChunkSpool, PackChunkState, PackFileChunkReader,
45    ReusedNativePackStats, build_native_pack, install_received_pack,
46    is_native_packable_object_type, native_pack_excluded_object_types, next_pack_chunk,
47    receive_pack_chunk, reuse_native_pack_encoded_subset_in,
48};
49pub use object_availability::{ObjectAvailabilityPlan, has_object, plan_object_availability};
50pub use object_graph::{
51    ObjectId, ObjectInfo, ObjectType, ObjectTypeBucket, PlannedObject, StateClosureOptions,
52    StateClosureTransferObjects, enumerate_state_closure, enumerate_state_closure_plan,
53    enumerate_state_closure_plan_with_options, enumerate_state_closure_transfer_from_boundaries,
54    enumerate_state_closure_transfer_with_options, enumerate_state_closure_with_options,
55    is_ancestor, missing_blobs_in_tree,
56};
57pub use object_transfer::{
58    MAX_PULL_FRAME_MESSAGE_SIZE, MAX_RECEIVED_REDACTIONS_BLOB_SIZE,
59    MAX_RECEIVED_STATE_VISIBILITY_BLOB_SIZE, admit_declared_received_len,
60    check_received_transfer_blob_size, chunk_bounds, chunk_count, chunk_offset, load_object_data,
61    load_requested_object, store_received_object,
62};
63pub use provider_pack::{
64    CompletedProviderPack, ProviderPackBundle, ProviderPackExtent, ProviderPackIndexEntry,
65    ProviderPackManifest, ProviderPackSpool, ProviderPackWriter, assemble_provider_pack,
66};
67pub use semantic_graph::{
68    SemanticGraphQueryKind, SemanticGraphQueryRequest, SemanticGraphQueryResponse, SemanticGraphRef,
69};
70pub use transfer_plan::{
71    GitLaneTransferIntent, RepositoryTransferPlan, TransferPartitions, TransferPlanStats,
72};
73
74/// Default port for Heddle protocol.
75pub const DEFAULT_PORT: u16 = 8421;
76
77/// Protocol version.
78pub const PROTOCOL_VERSION: u32 = 1;
79
80/// Maximum message size (64 MB).
81pub const MAX_MESSAGE_SIZE: usize = 64 * 1024 * 1024;
82
83/// Error type for protocol operations.
84#[derive(Debug, thiserror::Error)]
85pub enum ProtocolError {
86    #[error("io error: {0}")]
87    Io(#[from] std::io::Error),
88
89    #[error("serialization error: {0}")]
90    Serialization(String),
91
92    #[error("message too large: {size} bytes (max {max})")]
93    MessageTooLarge { size: usize, max: usize },
94
95    #[error("invalid message type: {0}")]
96    InvalidMessageType(u8),
97
98    #[error("protocol version mismatch: server={server}, client={client}")]
99    VersionMismatch { server: u32, client: u32 },
100
101    #[error("capability not supported: {0}")]
102    CapabilityNotSupported(String),
103
104    #[error("authentication failed: {0}")]
105    AuthenticationFailed(String),
106
107    #[error("authorization failed: {0}")]
108    AuthorizationFailed(String),
109
110    #[error("object not found: {0}")]
111    ObjectNotFound(String),
112
113    #[error("already exists: {0}")]
114    AlreadyExists(String),
115
116    #[error("invalid state: {0}")]
117    InvalidState(String),
118
119    #[error("remote error: {0}")]
120    Remote(String),
121
122    #[error("remote failure ({code:?}): {message}")]
123    RemoteFailure {
124        code: RemoteFailureCode,
125        message: String,
126        details: Vec<RemoteFailureDetail>,
127    },
128
129    #[error("lock error: {0}")]
130    LockError(String),
131}
132
133impl From<rmp_serde::encode::Error> for ProtocolError {
134    fn from(e: rmp_serde::encode::Error) -> Self {
135        ProtocolError::Serialization(e.to_string())
136    }
137}
138
139impl From<rmp_serde::decode::Error> for ProtocolError {
140    fn from(e: rmp_serde::decode::Error) -> Self {
141        ProtocolError::Serialization(e.to_string())
142    }
143}
144
145impl From<objects::error::HeddleError> for ProtocolError {
146    fn from(e: objects::error::HeddleError) -> Self {
147        ProtocolError::Remote(e.to_string())
148    }
149}
150
151impl ProtocolError {
152    pub fn client_message(&self) -> String {
153        match self {
154            ProtocolError::Io(_) => "network error".to_string(),
155            ProtocolError::Serialization(_) => "protocol error".to_string(),
156            ProtocolError::MessageTooLarge { .. } => "message too large".to_string(),
157            ProtocolError::InvalidMessageType(_) => "protocol error".to_string(),
158            ProtocolError::VersionMismatch { .. } => "protocol version mismatch".to_string(),
159            ProtocolError::CapabilityNotSupported(_) => "capability not supported".to_string(),
160            ProtocolError::AuthenticationFailed(_) => "permission denied".to_string(),
161            ProtocolError::AuthorizationFailed(_) => "permission denied".to_string(),
162            ProtocolError::ObjectNotFound(_) => "object not found".to_string(),
163            ProtocolError::AlreadyExists(_) => "resource already exists".to_string(),
164            ProtocolError::InvalidState(_) => "invalid request state".to_string(),
165            ProtocolError::Remote(_) => "internal server error".to_string(),
166            ProtocolError::RemoteFailure { message, .. } => message.clone(),
167            ProtocolError::LockError(_) => "internal server error".to_string(),
168        }
169    }
170
171    pub fn error_code(&self) -> ErrorCode {
172        match self {
173            ProtocolError::Io(_) => ErrorCode::Network,
174            ProtocolError::Serialization(_) => ErrorCode::Protocol,
175            ProtocolError::MessageTooLarge { .. } => ErrorCode::Protocol,
176            ProtocolError::InvalidMessageType(_) => ErrorCode::Protocol,
177            ProtocolError::VersionMismatch { .. } => ErrorCode::Protocol,
178            ProtocolError::CapabilityNotSupported(_) => ErrorCode::Protocol,
179            ProtocolError::AuthenticationFailed(_) => ErrorCode::PermissionDenied,
180            ProtocolError::AuthorizationFailed(_) => ErrorCode::PermissionDenied,
181            ProtocolError::ObjectNotFound(_) => ErrorCode::NotFound,
182            ProtocolError::AlreadyExists(_) => ErrorCode::InvalidArgument,
183            ProtocolError::InvalidState(_) => ErrorCode::InvalidArgument,
184            ProtocolError::Remote(_) => ErrorCode::Server,
185            ProtocolError::RemoteFailure { code, .. } => match code {
186                RemoteFailureCode::InvalidArgument
187                | RemoteFailureCode::AlreadyExists
188                | RemoteFailureCode::FailedPrecondition
189                | RemoteFailureCode::OutOfRange => ErrorCode::InvalidArgument,
190                RemoteFailureCode::NotFound => ErrorCode::NotFound,
191                RemoteFailureCode::PermissionDenied | RemoteFailureCode::Unauthenticated => {
192                    ErrorCode::PermissionDenied
193                }
194                RemoteFailureCode::DeadlineExceeded
195                | RemoteFailureCode::ResourceExhausted
196                | RemoteFailureCode::Aborted
197                | RemoteFailureCode::Unavailable
198                | RemoteFailureCode::Cancelled => ErrorCode::Network,
199                RemoteFailureCode::Unspecified
200                | RemoteFailureCode::Unknown
201                | RemoteFailureCode::Unimplemented
202                | RemoteFailureCode::Internal
203                | RemoteFailureCode::DataLoss => ErrorCode::Server,
204            },
205            ProtocolError::LockError(_) => ErrorCode::Server,
206        }
207    }
208
209    pub fn to_wire_error(&self, details: Option<String>) -> Error {
210        Error {
211            code: self.error_code(),
212            message: self.client_message(),
213            details,
214        }
215    }
216}
217
218pub type Result<T> = std::result::Result<T, ProtocolError>;
219
220#[cfg(test)]
221mod tests {
222    use std::io;
223
224    use super::{ErrorCode, ProtocolError, RemoteFailureCode};
225
226    #[test]
227    fn protocol_error_public_mapping_is_stable() {
228        let cases = vec![
229            (
230                ProtocolError::Io(io::Error::new(io::ErrorKind::TimedOut, "timeout")),
231                "network error",
232                ErrorCode::Network,
233            ),
234            (
235                ProtocolError::Serialization("bad msgpack".to_string()),
236                "protocol error",
237                ErrorCode::Protocol,
238            ),
239            (
240                ProtocolError::MessageTooLarge { size: 65, max: 64 },
241                "message too large",
242                ErrorCode::Protocol,
243            ),
244            (
245                ProtocolError::InvalidMessageType(42),
246                "protocol error",
247                ErrorCode::Protocol,
248            ),
249            (
250                ProtocolError::VersionMismatch {
251                    server: 2,
252                    client: 1,
253                },
254                "protocol version mismatch",
255                ErrorCode::Protocol,
256            ),
257            (
258                ProtocolError::CapabilityNotSupported("pack-v2".to_string()),
259                "capability not supported",
260                ErrorCode::Protocol,
261            ),
262            (
263                ProtocolError::AuthenticationFailed("bad token".to_string()),
264                "permission denied",
265                ErrorCode::PermissionDenied,
266            ),
267            (
268                ProtocolError::AuthorizationFailed("missing grant".to_string()),
269                "permission denied",
270                ErrorCode::PermissionDenied,
271            ),
272            (
273                ProtocolError::ObjectNotFound("abc123".to_string()),
274                "object not found",
275                ErrorCode::NotFound,
276            ),
277            (
278                ProtocolError::AlreadyExists("__users/luke/repo".to_string()),
279                "resource already exists",
280                ErrorCode::InvalidArgument,
281            ),
282            (
283                ProtocolError::InvalidState("bad resume".to_string()),
284                "invalid request state",
285                ErrorCode::InvalidArgument,
286            ),
287            (
288                ProtocolError::Remote("database unavailable".to_string()),
289                "internal server error",
290                ErrorCode::Server,
291            ),
292            (
293                ProtocolError::RemoteFailure {
294                    code: RemoteFailureCode::InvalidArgument,
295                    message: "server supplied message".to_string(),
296                    details: Vec::new(),
297                },
298                "server supplied message",
299                ErrorCode::InvalidArgument,
300            ),
301            (
302                ProtocolError::LockError("ref locked".to_string()),
303                "internal server error",
304                ErrorCode::Server,
305            ),
306        ];
307
308        for (error, expected_message, expected_code) in cases {
309            assert_eq!(error.client_message(), expected_message);
310            assert_eq!(error.error_code(), expected_code);
311
312            let wire_error = error.to_wire_error(Some("trace id".to_string()));
313            assert_eq!(wire_error.code, expected_code);
314            assert_eq!(wire_error.message, expected_message);
315            assert_eq!(wire_error.details.as_deref(), Some("trace id"));
316        }
317    }
318}