1mod auth_context;
5#[cfg(test)]
6mod auth_tests;
7mod auth_token;
8mod capabilities;
9mod message_auth;
10mod message_delta;
11mod message_hosted;
12mod message_objects;
13mod message_pushpull;
14mod message_refs;
15mod message_status;
16mod native_pack;
17mod object_availability;
18mod object_graph;
19mod object_transfer;
20mod scope_match;
21mod transfer_plan;
22
23pub use auth_context::AuthContext;
24pub use auth_token::{AuthToken, TokenScope};
25pub use capabilities::{
26 CAPABILITY_CHUNKED_TRANSFER, CAPABILITY_PACK_TRANSFER, CAPABILITY_PARTIAL_FETCH,
27 CAPABILITY_RESUMABLE_TRANSFER, Capabilities, CapabilitySet,
28};
29pub use message_auth::{AuthMethod, Permission};
30pub use message_delta::{DeltaData, RequestDelta};
31pub use message_hosted::{
32 CreateHostedGrant, CreateHostedRepository, CreateNamespace, DeleteHostedGrant,
33 DeleteHostedRepository, DeleteNamespace, HarnessIdentity, HostedGrantCreated,
34 HostedGrantDeleted, HostedGrantInfo, HostedGrantUpdated, HostedGrantsList, HostedNamespaceInfo,
35 HostedRepositoryInfo, ListHostedGrants, ListHostedNamespaces, ListHostedRepositories,
36 NamespaceCreated, NamespaceDeleted, NamespaceUpdated, NamespacesList, ProgressCheckpoint,
37 RepositoriesList, RepositoryCreated, RepositoryDeleted, RepositoryUpdated, SessionDiffSummary,
38 SessionReportEnvelope, TranscriptAttachmentRef, UpdateHostedGrant, UpdateHostedRepository,
39 UpdateNamespace, UsageTotals, WorktreeChangeBaseline,
40};
41pub use message_objects::{HaveObjects, ObjectData, ObjectRequest, SendObjects, WantObjects};
42pub use message_pushpull::{PullComplete, PushComplete};
43pub use message_refs::{HeadInfo, ListRefs, RefEntry, RefFilter, RefUpdated, RefsList, UpdateRef};
44pub use message_status::{Error, ErrorCode, Status, StatusCode};
45pub use native_pack::{
46 GitPackChunkState, GrowingPackChunkReader, MAX_RECEIVED_GIT_PACK_SIZE, NativePackBundle,
47 NativePackFileBundle, NativePackStreamingWriter, PackChunkSpool, PackChunkState,
48 PackFileChunkReader, build_native_pack, install_received_pack, is_native_packable_object_type,
49 native_pack_excluded_object_types, next_pack_chunk, receive_pack_chunk,
50};
51pub use object_availability::{ObjectAvailabilityPlan, has_object, plan_object_availability};
52pub use object_graph::{
53 ObjectId, ObjectInfo, ObjectType, ObjectTypeBucket, PlannedObject, StateClosureOptions,
54 StateClosureTransferObjects, enumerate_state_closure, enumerate_state_closure_plan,
55 enumerate_state_closure_plan_with_options, enumerate_state_closure_transfer_with_options,
56 enumerate_state_closure_with_options, is_ancestor, missing_blobs_in_tree,
57};
58pub use object_transfer::{
59 MAX_PULL_DECODE_MESSAGE_SIZE, MAX_RECEIVED_REDACTIONS_BLOB_SIZE,
60 MAX_RECEIVED_STATE_VISIBILITY_BLOB_SIZE, check_received_transfer_blob_size, chunk_bounds,
61 chunk_count, chunk_offset, load_object_data, load_requested_object, store_received_object,
62};
63pub use scope_match::scope_contains;
64pub use transfer_plan::{
65 GitLaneTransferIntent, RepositoryTransferPlan, TransferPartitions, TransferPlanStats,
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: ErrorCode,
119 message: String,
120 details: Option<String>,
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 ProtocolError::Remote(e.to_string())
142 }
143}
144
145impl ProtocolError {
146 pub fn client_message(&self) -> String {
147 match self {
148 ProtocolError::Io(_) => "network error".to_string(),
149 ProtocolError::Serialization(_) => "protocol error".to_string(),
150 ProtocolError::MessageTooLarge { .. } => "message too large".to_string(),
151 ProtocolError::InvalidMessageType(_) => "protocol error".to_string(),
152 ProtocolError::VersionMismatch { .. } => "protocol version mismatch".to_string(),
153 ProtocolError::CapabilityNotSupported(_) => "capability not supported".to_string(),
154 ProtocolError::AuthenticationFailed(_) => "permission denied".to_string(),
155 ProtocolError::AuthorizationFailed(_) => "permission denied".to_string(),
156 ProtocolError::ObjectNotFound(_) => "object not found".to_string(),
157 ProtocolError::AlreadyExists(_) => "resource already exists".to_string(),
158 ProtocolError::InvalidState(_) => "invalid request state".to_string(),
159 ProtocolError::Remote(_) => "internal server error".to_string(),
160 ProtocolError::RemoteFailure { message, .. } => message.clone(),
161 ProtocolError::LockError(_) => "internal server error".to_string(),
162 }
163 }
164
165 pub fn error_code(&self) -> ErrorCode {
166 match self {
167 ProtocolError::Io(_) => ErrorCode::Network,
168 ProtocolError::Serialization(_) => ErrorCode::Protocol,
169 ProtocolError::MessageTooLarge { .. } => ErrorCode::Protocol,
170 ProtocolError::InvalidMessageType(_) => ErrorCode::Protocol,
171 ProtocolError::VersionMismatch { .. } => ErrorCode::Protocol,
172 ProtocolError::CapabilityNotSupported(_) => ErrorCode::Protocol,
173 ProtocolError::AuthenticationFailed(_) => ErrorCode::PermissionDenied,
174 ProtocolError::AuthorizationFailed(_) => ErrorCode::PermissionDenied,
175 ProtocolError::ObjectNotFound(_) => ErrorCode::NotFound,
176 ProtocolError::AlreadyExists(_) => ErrorCode::InvalidArgument,
177 ProtocolError::InvalidState(_) => ErrorCode::InvalidArgument,
178 ProtocolError::Remote(_) => ErrorCode::Server,
179 ProtocolError::RemoteFailure { code, .. } => *code,
180 ProtocolError::LockError(_) => ErrorCode::Server,
181 }
182 }
183
184 pub fn to_wire_error(&self, details: Option<String>) -> Error {
185 Error {
186 code: self.error_code(),
187 message: self.client_message(),
188 details,
189 }
190 }
191}
192
193pub type Result<T> = std::result::Result<T, ProtocolError>;
194
195#[cfg(test)]
196mod tests {
197 use std::io;
198
199 use super::{ErrorCode, ProtocolError};
200
201 #[test]
202 fn protocol_error_public_mapping_is_stable() {
203 let cases = vec![
204 (
205 ProtocolError::Io(io::Error::new(io::ErrorKind::TimedOut, "timeout")),
206 "network error",
207 ErrorCode::Network,
208 ),
209 (
210 ProtocolError::Serialization("bad msgpack".to_string()),
211 "protocol error",
212 ErrorCode::Protocol,
213 ),
214 (
215 ProtocolError::MessageTooLarge { size: 65, max: 64 },
216 "message too large",
217 ErrorCode::Protocol,
218 ),
219 (
220 ProtocolError::InvalidMessageType(42),
221 "protocol error",
222 ErrorCode::Protocol,
223 ),
224 (
225 ProtocolError::VersionMismatch {
226 server: 2,
227 client: 1,
228 },
229 "protocol version mismatch",
230 ErrorCode::Protocol,
231 ),
232 (
233 ProtocolError::CapabilityNotSupported("pack-v2".to_string()),
234 "capability not supported",
235 ErrorCode::Protocol,
236 ),
237 (
238 ProtocolError::AuthenticationFailed("bad token".to_string()),
239 "permission denied",
240 ErrorCode::PermissionDenied,
241 ),
242 (
243 ProtocolError::AuthorizationFailed("missing grant".to_string()),
244 "permission denied",
245 ErrorCode::PermissionDenied,
246 ),
247 (
248 ProtocolError::ObjectNotFound("abc123".to_string()),
249 "object not found",
250 ErrorCode::NotFound,
251 ),
252 (
253 ProtocolError::AlreadyExists("__users/luke/repo".to_string()),
254 "resource already exists",
255 ErrorCode::InvalidArgument,
256 ),
257 (
258 ProtocolError::InvalidState("bad resume".to_string()),
259 "invalid request state",
260 ErrorCode::InvalidArgument,
261 ),
262 (
263 ProtocolError::Remote("database unavailable".to_string()),
264 "internal server error",
265 ErrorCode::Server,
266 ),
267 (
268 ProtocolError::RemoteFailure {
269 code: ErrorCode::InvalidArgument,
270 message: "server supplied message".to_string(),
271 details: Some("remote details".to_string()),
272 },
273 "server supplied message",
274 ErrorCode::InvalidArgument,
275 ),
276 (
277 ProtocolError::LockError("ref locked".to_string()),
278 "internal server error",
279 ErrorCode::Server,
280 ),
281 ];
282
283 for (error, expected_message, expected_code) in cases {
284 assert_eq!(error.client_message(), expected_message);
285 assert_eq!(error.error_code(), expected_code);
286
287 let wire_error = error.to_wire_error(Some("trace id".to_string()));
288 assert_eq!(wire_error.code, expected_code);
289 assert_eq!(wire_error.message, expected_message);
290 assert_eq!(wire_error.details.as_deref(), Some("trace id"));
291 }
292 }
293}