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