1#[cfg(test)]
6mod auth_tests;
7mod auth_token;
8mod capabilities;
9mod message_delta;
10mod message_hosted;
11mod message_objects;
12mod message_pushpull;
13mod message_refs;
14mod message_status;
15mod native_pack;
16mod object_availability;
17mod object_graph;
18mod object_transfer;
19mod transfer_plan;
20
21pub use auth_token::AuthToken;
22pub use capabilities::{
23 CAPABILITY_CHUNKED_TRANSFER, CAPABILITY_PACK_TRANSFER, CAPABILITY_PARTIAL_FETCH,
24 CAPABILITY_RESUMABLE_TRANSFER, Capabilities, CapabilitySet,
25};
26pub use message_delta::{DeltaData, RequestDelta};
27pub use message_hosted::{
28 CreateHostedGrant, CreateHostedRepository, CreateNamespace, DeleteHostedGrant,
29 DeleteHostedRepository, DeleteNamespace, HarnessIdentity, HostedGrantCreated,
30 HostedGrantDeleted, HostedGrantInfo, HostedGrantUpdated, HostedGrantsList, HostedNamespaceInfo,
31 HostedRepositoryInfo, ListHostedGrants, ListHostedNamespaces, ListHostedRepositories,
32 NamespaceCreated, NamespaceDeleted, NamespaceUpdated, NamespacesList, ProgressCheckpoint,
33 RepositoriesList, RepositoryCreated, RepositoryDeleted, RepositoryUpdated, SessionDiffSummary,
34 SessionReportEnvelope, TranscriptAttachmentRef, UpdateHostedGrant, UpdateHostedRepository,
35 UpdateNamespace, UsageTotals, WorktreeChangeBaseline,
36};
37pub use message_objects::{HaveObjects, ObjectData, ObjectRequest, SendObjects, WantObjects};
38pub use message_pushpull::{PullComplete, PushComplete};
39pub use message_refs::{HeadInfo, ListRefs, RefEntry, RefFilter, RefUpdated, RefsList, UpdateRef};
40pub use message_status::{Error, ErrorCode, Status, StatusCode};
41pub use native_pack::{
42 GitPackChunkState, GrowingPackChunkReader, MAX_RECEIVED_GIT_PACK_SIZE, NativePackBundle,
43 NativePackFileBundle, NativePackStreamingWriter, PackChunkSpool, PackChunkState,
44 PackFileChunkReader, build_native_pack, install_received_pack, is_native_packable_object_type,
45 native_pack_excluded_object_types, next_pack_chunk, receive_pack_chunk,
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_with_options,
52 enumerate_state_closure_with_options, is_ancestor, missing_blobs_in_tree,
53};
54pub use object_transfer::{
55 MAX_PULL_DECODE_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 transfer_plan::{
60 GitLaneTransferIntent, RepositoryTransferPlan, TransferPartitions, TransferPlanStats,
61};
62
63pub const DEFAULT_PORT: u16 = 8421;
65
66pub const PROTOCOL_VERSION: u32 = 1;
68
69pub const MAX_MESSAGE_SIZE: usize = 64 * 1024 * 1024;
71
72#[derive(Debug, thiserror::Error)]
74pub enum ProtocolError {
75 #[error("io error: {0}")]
76 Io(#[from] std::io::Error),
77
78 #[error("serialization error: {0}")]
79 Serialization(String),
80
81 #[error("message too large: {size} bytes (max {max})")]
82 MessageTooLarge { size: usize, max: usize },
83
84 #[error("invalid message type: {0}")]
85 InvalidMessageType(u8),
86
87 #[error("protocol version mismatch: server={server}, client={client}")]
88 VersionMismatch { server: u32, client: u32 },
89
90 #[error("capability not supported: {0}")]
91 CapabilityNotSupported(String),
92
93 #[error("authentication failed: {0}")]
94 AuthenticationFailed(String),
95
96 #[error("authorization failed: {0}")]
97 AuthorizationFailed(String),
98
99 #[error("object not found: {0}")]
100 ObjectNotFound(String),
101
102 #[error("already exists: {0}")]
103 AlreadyExists(String),
104
105 #[error("invalid state: {0}")]
106 InvalidState(String),
107
108 #[error("remote error: {0}")]
109 Remote(String),
110
111 #[error("remote failure ({code:?}): {message}")]
112 RemoteFailure {
113 code: ErrorCode,
114 message: String,
115 details: Option<String>,
116 },
117
118 #[error("lock error: {0}")]
119 LockError(String),
120}
121
122impl From<rmp_serde::encode::Error> for ProtocolError {
123 fn from(e: rmp_serde::encode::Error) -> Self {
124 ProtocolError::Serialization(e.to_string())
125 }
126}
127
128impl From<rmp_serde::decode::Error> for ProtocolError {
129 fn from(e: rmp_serde::decode::Error) -> Self {
130 ProtocolError::Serialization(e.to_string())
131 }
132}
133
134impl From<objects::error::HeddleError> for ProtocolError {
135 fn from(e: objects::error::HeddleError) -> Self {
136 ProtocolError::Remote(e.to_string())
137 }
138}
139
140impl ProtocolError {
141 pub fn client_message(&self) -> String {
142 match self {
143 ProtocolError::Io(_) => "network error".to_string(),
144 ProtocolError::Serialization(_) => "protocol error".to_string(),
145 ProtocolError::MessageTooLarge { .. } => "message too large".to_string(),
146 ProtocolError::InvalidMessageType(_) => "protocol error".to_string(),
147 ProtocolError::VersionMismatch { .. } => "protocol version mismatch".to_string(),
148 ProtocolError::CapabilityNotSupported(_) => "capability not supported".to_string(),
149 ProtocolError::AuthenticationFailed(_) => "permission denied".to_string(),
150 ProtocolError::AuthorizationFailed(_) => "permission denied".to_string(),
151 ProtocolError::ObjectNotFound(_) => "object not found".to_string(),
152 ProtocolError::AlreadyExists(_) => "resource already exists".to_string(),
153 ProtocolError::InvalidState(_) => "invalid request state".to_string(),
154 ProtocolError::Remote(_) => "internal server error".to_string(),
155 ProtocolError::RemoteFailure { message, .. } => message.clone(),
156 ProtocolError::LockError(_) => "internal server error".to_string(),
157 }
158 }
159
160 pub fn error_code(&self) -> ErrorCode {
161 match self {
162 ProtocolError::Io(_) => ErrorCode::Network,
163 ProtocolError::Serialization(_) => ErrorCode::Protocol,
164 ProtocolError::MessageTooLarge { .. } => ErrorCode::Protocol,
165 ProtocolError::InvalidMessageType(_) => ErrorCode::Protocol,
166 ProtocolError::VersionMismatch { .. } => ErrorCode::Protocol,
167 ProtocolError::CapabilityNotSupported(_) => ErrorCode::Protocol,
168 ProtocolError::AuthenticationFailed(_) => ErrorCode::PermissionDenied,
169 ProtocolError::AuthorizationFailed(_) => ErrorCode::PermissionDenied,
170 ProtocolError::ObjectNotFound(_) => ErrorCode::NotFound,
171 ProtocolError::AlreadyExists(_) => ErrorCode::InvalidArgument,
172 ProtocolError::InvalidState(_) => ErrorCode::InvalidArgument,
173 ProtocolError::Remote(_) => ErrorCode::Server,
174 ProtocolError::RemoteFailure { code, .. } => *code,
175 ProtocolError::LockError(_) => ErrorCode::Server,
176 }
177 }
178
179 pub fn to_wire_error(&self, details: Option<String>) -> Error {
180 Error {
181 code: self.error_code(),
182 message: self.client_message(),
183 details,
184 }
185 }
186}
187
188pub type Result<T> = std::result::Result<T, ProtocolError>;
189
190#[cfg(test)]
191mod tests {
192 use std::io;
193
194 use super::{ErrorCode, ProtocolError};
195
196 #[test]
197 fn protocol_error_public_mapping_is_stable() {
198 let cases = vec![
199 (
200 ProtocolError::Io(io::Error::new(io::ErrorKind::TimedOut, "timeout")),
201 "network error",
202 ErrorCode::Network,
203 ),
204 (
205 ProtocolError::Serialization("bad msgpack".to_string()),
206 "protocol error",
207 ErrorCode::Protocol,
208 ),
209 (
210 ProtocolError::MessageTooLarge { size: 65, max: 64 },
211 "message too large",
212 ErrorCode::Protocol,
213 ),
214 (
215 ProtocolError::InvalidMessageType(42),
216 "protocol error",
217 ErrorCode::Protocol,
218 ),
219 (
220 ProtocolError::VersionMismatch {
221 server: 2,
222 client: 1,
223 },
224 "protocol version mismatch",
225 ErrorCode::Protocol,
226 ),
227 (
228 ProtocolError::CapabilityNotSupported("pack-v2".to_string()),
229 "capability not supported",
230 ErrorCode::Protocol,
231 ),
232 (
233 ProtocolError::AuthenticationFailed("bad token".to_string()),
234 "permission denied",
235 ErrorCode::PermissionDenied,
236 ),
237 (
238 ProtocolError::AuthorizationFailed("missing grant".to_string()),
239 "permission denied",
240 ErrorCode::PermissionDenied,
241 ),
242 (
243 ProtocolError::ObjectNotFound("abc123".to_string()),
244 "object not found",
245 ErrorCode::NotFound,
246 ),
247 (
248 ProtocolError::AlreadyExists("__users/luke/repo".to_string()),
249 "resource already exists",
250 ErrorCode::InvalidArgument,
251 ),
252 (
253 ProtocolError::InvalidState("bad resume".to_string()),
254 "invalid request state",
255 ErrorCode::InvalidArgument,
256 ),
257 (
258 ProtocolError::Remote("database unavailable".to_string()),
259 "internal server error",
260 ErrorCode::Server,
261 ),
262 (
263 ProtocolError::RemoteFailure {
264 code: ErrorCode::InvalidArgument,
265 message: "server supplied message".to_string(),
266 details: Some("remote details".to_string()),
267 },
268 "server supplied message",
269 ErrorCode::InvalidArgument,
270 ),
271 (
272 ProtocolError::LockError("ref locked".to_string()),
273 "internal server error",
274 ErrorCode::Server,
275 ),
276 ];
277
278 for (error, expected_message, expected_code) in cases {
279 assert_eq!(error.client_message(), expected_message);
280 assert_eq!(error.error_code(), expected_code);
281
282 let wire_error = error.to_wire_error(Some("trace id".to_string()));
283 assert_eq!(wire_error.code, expected_code);
284 assert_eq!(wire_error.message, expected_message);
285 assert_eq!(wire_error.details.as_deref(), Some("trace id"));
286 }
287 }
288}