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