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