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;
21
22pub use auth_context::AuthContext;
23pub use auth_token::{AuthToken, TokenScope};
24pub use capabilities::{
25 CAPABILITY_CHUNKED_TRANSFER, CAPABILITY_PACK_TRANSFER, CAPABILITY_PARTIAL_FETCH,
26 CAPABILITY_RESUMABLE_TRANSFER, Capabilities, CapabilitySet,
27};
28pub use message_auth::{AuthMethod, Permission};
29pub use message_delta::{DeltaData, RequestDelta};
30pub use message_hosted::{
31 CreateHostedGrant, CreateHostedRepository, CreateNamespace, DeleteHostedGrant,
32 DeleteHostedRepository, DeleteNamespace, HarnessIdentity, HostedGrantCreated,
33 HostedGrantDeleted, HostedGrantInfo, HostedGrantUpdated, HostedGrantsList, HostedNamespaceInfo,
34 HostedRepositoryInfo, ListHostedGrants, ListHostedNamespaces, ListHostedRepositories,
35 NamespaceCreated, NamespaceDeleted, NamespaceUpdated, NamespacesList, ProgressCheckpoint,
36 RepositoriesList, RepositoryCreated, RepositoryDeleted, RepositoryUpdated, SessionDiffSummary,
37 SessionReportEnvelope, TranscriptAttachmentRef, UpdateHostedGrant, UpdateHostedRepository,
38 UpdateNamespace, UsageTotals, WorktreeChangeBaseline,
39};
40pub use message_objects::{HaveObjects, ObjectData, ObjectRequest, SendObjects, WantObjects};
41pub use message_pushpull::{PullComplete, PushComplete};
42pub use message_refs::{HeadInfo, ListRefs, RefEntry, RefFilter, RefUpdated, RefsList, UpdateRef};
43pub use message_status::{Error, ErrorCode, Status, StatusCode};
44pub use native_pack::{
45 GitPackChunkState, GrowingPackChunkReader, MAX_RECEIVED_GIT_PACK_SIZE, NativePackBundle,
46 NativePackFileBundle, NativePackStreamingWriter, PackChunkSpool, PackChunkState,
47 PackFileChunkReader, build_native_pack, install_received_pack, is_native_packable_object_type,
48 native_pack_excluded_object_types, next_pack_chunk, receive_pack_chunk,
49};
50pub use object_availability::{ObjectAvailabilityPlan, has_object, plan_object_availability};
51pub use object_graph::{
52 ObjectId, ObjectInfo, ObjectType, PlannedObject, StateClosureOptions, enumerate_state_closure,
53 enumerate_state_closure_plan, enumerate_state_closure_plan_with_options,
54 enumerate_state_closure_with_options, is_ancestor,
55};
56pub use object_transfer::{
57 MAX_PULL_DECODE_MESSAGE_SIZE, MAX_RECEIVED_REDACTIONS_BLOB_SIZE,
58 MAX_RECEIVED_STATE_VISIBILITY_BLOB_SIZE, check_received_transfer_blob_size, chunk_bounds,
59 chunk_count, chunk_offset, load_object_data, load_requested_object, store_received_object,
60};
61pub use scope_match::scope_contains;
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}