server.rs (80674B)
1 //! Bounded HTTP/1.1 administration server over an owned Unix listener. 2 3 use core::fmt; 4 use serde::{Deserialize, Deserializer, Serialize, de, de::DeserializeOwned}; 5 use serde_json::value::RawValue; 6 use std::collections::{BTreeMap, BTreeSet}; 7 use std::convert::Infallible; 8 use std::error::Error; 9 use std::future::Future; 10 use std::io; 11 use std::pin::Pin; 12 use std::sync::{Arc, Mutex}; 13 14 use bytes::Bytes; 15 use http::header::{CONTENT_TYPE, HeaderMap, HeaderValue}; 16 use http::{Method, Request, Response, StatusCode, Version}; 17 use http_body_util::{BodyExt, Full, LengthLimitError, Limited}; 18 use hyper::body::Incoming; 19 use hyper::server::conn::http1; 20 use hyper::service::service_fn; 21 use hyper_util::rt::{TokioIo, TokioTimer}; 22 use tokio::sync::Semaphore; 23 use tokio::task::JoinSet; 24 25 #[cfg(test)] 26 use super::test_support; 27 use super::{ 28 ADMIN_CONTRACT_VERSION, AdminCorrelationId, AdminError, AdminErrorCode, AdminErrorMessage, 29 AdminFailureResponse, AdminMutationRequest, AdminTransportLimits, UnixAdminSocketBinding, 30 }; 31 use crate::{CancellationToken, EntropySource, SystemEntropy}; 32 33 /// Smallest configured response cap that can always carry a safe v1 failure envelope. 34 pub const ADMIN_MIN_RESPONSE_BODY_UTF8_BYTES: u32 = 512; 35 36 pub const ADMIN_ROUTE_PATH_MAX_UTF8_BYTES: usize = 256; 37 pub const ADMIN_ROUTE_PARAMETER_NAME_MAX_UTF8_BYTES: usize = 64; 38 pub const ADMIN_ROUTE_PARAMETER_VALUE_MAX_UTF8_BYTES: usize = 128; 39 const HTTP_MINIMUM_MAX_BUFFER_SIZE: usize = 8 * 1024; 40 const JSON_CONTENT_TYPE: &str = "application/json"; 41 const FALLBACK_CORRELATION_ID: &str = "correlation-unavailable"; 42 43 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] 44 pub enum AdminHttpMethod { 45 Get, 46 Post, 47 } 48 49 impl AdminHttpMethod { 50 fn from_http(method: &Method) -> Option<Self> { 51 if method == Method::GET { 52 Some(Self::Get) 53 } else if method == Method::POST { 54 Some(Self::Post) 55 } else { 56 None 57 } 58 } 59 } 60 61 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 62 pub enum AdminRoutePathError { 63 Empty, 64 TooLong, 65 WrongVersionPrefix, 66 InvalidCharacter, 67 EmptySegment, 68 InvalidParameter, 69 DuplicateParameter, 70 } 71 72 impl fmt::Display for AdminRoutePathError { 73 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 74 formatter.write_str("admin route path is not a canonical v1 route") 75 } 76 } 77 78 impl Error for AdminRoutePathError {} 79 80 #[derive(Clone, Debug, PartialEq, Eq)] 81 pub struct AdminRoutePath { 82 canonical: String, 83 segments: Vec<AdminRouteSegment>, 84 literal_count: usize, 85 } 86 87 #[derive(Clone, Debug, PartialEq, Eq)] 88 enum AdminRouteSegment { 89 Literal(String), 90 Parameter(String), 91 } 92 93 impl AdminRoutePath { 94 pub fn new(value: impl AsRef<str>) -> Result<Self, AdminRoutePathError> { 95 let value = value.as_ref(); 96 if value.is_empty() { 97 return Err(AdminRoutePathError::Empty); 98 } 99 if value.len() > ADMIN_ROUTE_PATH_MAX_UTF8_BYTES { 100 return Err(AdminRoutePathError::TooLong); 101 } 102 if !value.starts_with("/v1/") || value.ends_with('/') { 103 return Err(AdminRoutePathError::WrongVersionPrefix); 104 } 105 let mut parameter_names = BTreeSet::new(); 106 let mut literal_count = 0; 107 for segment in value[1..].split('/') { 108 if segment.is_empty() { 109 return Err(AdminRoutePathError::EmptySegment); 110 } 111 if let Some(parameter) = segment 112 .strip_prefix('{') 113 .and_then(|segment| segment.strip_suffix('}')) 114 { 115 if !valid_parameter_name(parameter) { 116 return Err(AdminRoutePathError::InvalidParameter); 117 } 118 if !parameter_names.insert(parameter) { 119 return Err(AdminRoutePathError::DuplicateParameter); 120 } 121 } else { 122 if !segment 123 .bytes() 124 .all(|byte| matches!(byte, b'a'..=b'z' | b'0'..=b'9' | b'_' | b'-')) 125 { 126 return Err(AdminRoutePathError::InvalidCharacter); 127 } 128 literal_count += 1; 129 } 130 } 131 let segments = value[1..] 132 .split('/') 133 .map(|segment| { 134 segment 135 .strip_prefix('{') 136 .and_then(|segment| segment.strip_suffix('}')) 137 .map_or_else( 138 || AdminRouteSegment::Literal(segment.to_owned()), 139 |parameter| AdminRouteSegment::Parameter(parameter.to_owned()), 140 ) 141 }) 142 .collect(); 143 Ok(Self { 144 canonical: value.to_owned(), 145 segments, 146 literal_count, 147 }) 148 } 149 150 #[must_use] 151 pub fn as_str(&self) -> &str { 152 &self.canonical 153 } 154 155 fn overlaps(&self, other: &Self) -> bool { 156 self.segments.len() == other.segments.len() 157 && self 158 .segments 159 .iter() 160 .zip(&other.segments) 161 .all(|(left, right)| match (left, right) { 162 (AdminRouteSegment::Literal(left), AdminRouteSegment::Literal(right)) => { 163 left == right 164 } 165 _ => true, 166 }) 167 } 168 169 fn match_path(&self, path: &str) -> Option<BTreeMap<String, String>> { 170 if !path.starts_with('/') || path.ends_with('/') || path.contains("//") { 171 return None; 172 } 173 let raw_segments = path[1..].split('/').collect::<Vec<_>>(); 174 if raw_segments.len() != self.segments.len() { 175 return None; 176 } 177 let mut parameters = BTreeMap::new(); 178 for (pattern, raw) in self.segments.iter().zip(raw_segments) { 179 match pattern { 180 AdminRouteSegment::Literal(literal) if literal != raw => return None, 181 AdminRouteSegment::Literal(_) => {} 182 AdminRouteSegment::Parameter(name) => { 183 parameters.insert(name.clone(), decode_route_parameter(raw)?); 184 } 185 } 186 } 187 Some(parameters) 188 } 189 } 190 191 fn valid_parameter_name(value: &str) -> bool { 192 value.len() <= ADMIN_ROUTE_PARAMETER_NAME_MAX_UTF8_BYTES 193 && matches!(value.as_bytes().first(), Some(b'a'..=b'z')) 194 && value 195 .bytes() 196 .all(|byte| matches!(byte, b'a'..=b'z' | b'0'..=b'9' | b'_')) 197 } 198 199 fn decode_route_parameter(raw: &str) -> Option<String> { 200 if raw.is_empty() { 201 return None; 202 } 203 let raw = raw.as_bytes(); 204 let mut decoded = Vec::with_capacity(raw.len().min(ADMIN_ROUTE_PARAMETER_VALUE_MAX_UTF8_BYTES)); 205 let mut index = 0; 206 while index < raw.len() { 207 let byte = if raw[index] == b'%' { 208 let high = *raw.get(index + 1)?; 209 let low = *raw.get(index + 2)?; 210 index += 3; 211 (hex_nibble(high)? << 4) | hex_nibble(low)? 212 } else { 213 let byte = raw[index]; 214 index += 1; 215 byte 216 }; 217 if byte == b'/' || byte == b'\\' || byte == 0 || byte.is_ascii_control() { 218 return None; 219 } 220 if decoded.len() == ADMIN_ROUTE_PARAMETER_VALUE_MAX_UTF8_BYTES { 221 return None; 222 } 223 decoded.push(byte); 224 } 225 String::from_utf8(decoded) 226 .ok() 227 .filter(|value| !value.is_empty()) 228 } 229 230 fn hex_nibble(value: u8) -> Option<u8> { 231 match value { 232 b'0'..=b'9' => Some(value - b'0'), 233 b'a'..=b'f' => Some(value - b'a' + 10), 234 b'A'..=b'F' => Some(value - b'A' + 10), 235 _ => None, 236 } 237 } 238 239 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 240 pub enum AdminRouteRegistrationError { 241 InvalidPath(AdminRoutePathError), 242 Duplicate, 243 Ambiguous, 244 } 245 246 impl fmt::Display for AdminRouteRegistrationError { 247 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 248 formatter.write_str("admin route registration is invalid") 249 } 250 } 251 252 impl Error for AdminRouteRegistrationError {} 253 254 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 255 pub enum AdminRequestDecodeError { 256 Empty, 257 Malformed, 258 } 259 260 impl fmt::Display for AdminRequestDecodeError { 261 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 262 formatter.write_str("admin request body is not valid JSON") 263 } 264 } 265 266 impl Error for AdminRequestDecodeError {} 267 268 /// Validated request metadata and a bounded raw JSON body. 269 pub struct AdminRequest { 270 method: AdminHttpMethod, 271 path: AdminRoutePath, 272 query: Option<String>, 273 correlation_id: AdminCorrelationId, 274 parameters: BTreeMap<String, String>, 275 body: Bytes, 276 response_body_limit: usize, 277 } 278 279 impl AdminRequest { 280 #[must_use] 281 pub const fn method(&self) -> AdminHttpMethod { 282 self.method 283 } 284 285 #[must_use] 286 pub const fn path(&self) -> &AdminRoutePath { 287 &self.path 288 } 289 290 #[must_use] 291 pub fn query(&self) -> Option<&str> { 292 self.query.as_deref() 293 } 294 295 #[must_use] 296 pub const fn correlation_id(&self) -> &AdminCorrelationId { 297 &self.correlation_id 298 } 299 300 #[must_use] 301 pub fn parameter(&self, name: &str) -> Option<&str> { 302 self.parameters.get(name).map(String::as_str) 303 } 304 305 #[must_use] 306 pub fn body(&self) -> &[u8] { 307 &self.body 308 } 309 310 pub fn decode_json<T>(&self) -> Result<T, AdminRequestDecodeError> 311 where 312 T: DeserializeOwned, 313 { 314 if self.body.is_empty() { 315 return Err(AdminRequestDecodeError::Empty); 316 } 317 serde_json::from_slice(&self.body).map_err(|_| AdminRequestDecodeError::Malformed) 318 } 319 320 pub fn success<T>(&self, result: &T) -> Result<AdminRouteOutcome, AdminRouteOutcomeError> 321 where 322 T: Serialize, 323 { 324 let encoded = 325 encode_bounded(result, self.response_body_limit).map_err(|error| match error { 326 BoundedEncodingError::Limit => AdminRouteOutcomeError::ResponseLimit, 327 BoundedEncodingError::Encoding => AdminRouteOutcomeError::Encoding, 328 })?; 329 let _: StrictJsonPayload = 330 serde_json::from_slice(&encoded).map_err(|_| AdminRouteOutcomeError::InvalidPayload)?; 331 let encoded = 332 String::from_utf8(encoded).expect("serde_json output must always be valid UTF-8"); 333 let result = 334 RawValue::from_string(encoded).map_err(|_| AdminRouteOutcomeError::Encoding)?; 335 Ok(AdminRouteOutcome(AdminRouteOutcomeKind::Success(result))) 336 } 337 } 338 339 impl fmt::Debug for AdminRequest { 340 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 341 formatter 342 .debug_struct("AdminRequest") 343 .field("method", &self.method) 344 .field("path", &self.path) 345 .field("query", &self.query.as_ref().map(|_| "[redacted]")) 346 .field("correlation_id", &self.correlation_id) 347 .field( 348 "parameter_names", 349 &self.parameters.keys().collect::<Vec<_>>(), 350 ) 351 .field("body", &"[redacted]") 352 .finish() 353 } 354 } 355 356 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 357 pub enum AdminRouteOutcomeError { 358 Encoding, 359 InvalidPayload, 360 ResponseLimit, 361 } 362 363 impl fmt::Display for AdminRouteOutcomeError { 364 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 365 formatter.write_str(match self { 366 Self::Encoding => "admin route result could not be encoded", 367 Self::InvalidPayload => "admin route result violates the JSON payload contract", 368 Self::ResponseLimit => "admin route result exceeds the response limit", 369 }) 370 } 371 } 372 373 impl Error for AdminRouteOutcomeError {} 374 375 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 376 pub enum AdminRouteFailureStatus { 377 BadRequest, 378 NotFound, 379 Conflict, 380 Unavailable, 381 Internal, 382 } 383 384 impl AdminRouteFailureStatus { 385 const fn http_status(self) -> StatusCode { 386 match self { 387 Self::BadRequest => StatusCode::BAD_REQUEST, 388 Self::NotFound => StatusCode::NOT_FOUND, 389 Self::Conflict => StatusCode::CONFLICT, 390 Self::Unavailable => StatusCode::SERVICE_UNAVAILABLE, 391 Self::Internal => StatusCode::INTERNAL_SERVER_ERROR, 392 } 393 } 394 } 395 396 #[derive(Clone, Debug, PartialEq, Eq)] 397 pub struct AdminRouteFailure { 398 status: AdminRouteFailureStatus, 399 error: AdminError, 400 } 401 402 impl AdminRouteFailure { 403 #[must_use] 404 pub const fn new(status: AdminRouteFailureStatus, error: AdminError) -> Self { 405 Self { status, error } 406 } 407 408 #[must_use] 409 pub const fn status(&self) -> AdminRouteFailureStatus { 410 self.status 411 } 412 413 #[must_use] 414 pub const fn error(&self) -> &AdminError { 415 &self.error 416 } 417 } 418 419 enum AdminRouteOutcomeKind { 420 Success(Box<RawValue>), 421 Failure(AdminRouteFailure), 422 } 423 424 pub struct AdminRouteOutcome(AdminRouteOutcomeKind); 425 426 impl AdminRouteOutcome { 427 #[must_use] 428 pub const fn failure(failure: AdminRouteFailure) -> Self { 429 Self(AdminRouteOutcomeKind::Failure(failure)) 430 } 431 } 432 433 impl fmt::Debug for AdminRouteOutcome { 434 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 435 match &self.0 { 436 AdminRouteOutcomeKind::Success(_) => { 437 formatter.write_str("AdminRouteOutcome::Success(<redacted>)") 438 } 439 AdminRouteOutcomeKind::Failure(failure) => { 440 formatter.debug_tuple("Failure").field(failure).finish() 441 } 442 } 443 } 444 } 445 446 #[derive(Serialize)] 447 struct ServerSuccessEnvelope<'a> { 448 contract_version: u32, 449 ok: bool, 450 correlation_id: &'a AdminCorrelationId, 451 result: &'a RawValue, 452 } 453 454 #[derive(Clone, Copy)] 455 struct StrictJsonPayload; 456 457 impl Serialize for StrictJsonPayload { 458 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 459 where 460 S: serde::Serializer, 461 { 462 serializer.serialize_bool(true) 463 } 464 } 465 466 impl<'de> Deserialize<'de> for StrictJsonPayload { 467 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 468 where 469 D: Deserializer<'de>, 470 { 471 deserializer.deserialize_any(StrictJsonVisitor) 472 } 473 } 474 475 struct StrictJsonVisitor; 476 477 impl<'de> de::Visitor<'de> for StrictJsonVisitor { 478 type Value = StrictJsonPayload; 479 480 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 481 formatter.write_str("JSON without duplicate object keys or null values") 482 } 483 484 fn visit_bool<E>(self, _value: bool) -> Result<Self::Value, E> { 485 Ok(StrictJsonPayload) 486 } 487 488 fn visit_i64<E>(self, _value: i64) -> Result<Self::Value, E> { 489 Ok(StrictJsonPayload) 490 } 491 492 fn visit_u64<E>(self, _value: u64) -> Result<Self::Value, E> { 493 Ok(StrictJsonPayload) 494 } 495 496 fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E> 497 where 498 E: de::Error, 499 { 500 if value.is_finite() { 501 Ok(StrictJsonPayload) 502 } else { 503 Err(E::custom("non-finite JSON number")) 504 } 505 } 506 507 fn visit_str<E>(self, _value: &str) -> Result<Self::Value, E> { 508 Ok(StrictJsonPayload) 509 } 510 511 fn visit_borrowed_str<E>(self, value: &'de str) -> Result<Self::Value, E> 512 where 513 E: de::Error, 514 { 515 self.visit_str(value) 516 } 517 518 fn visit_string<E>(self, _value: String) -> Result<Self::Value, E> { 519 Ok(StrictJsonPayload) 520 } 521 522 fn visit_none<E>(self) -> Result<Self::Value, E> 523 where 524 E: de::Error, 525 { 526 Err(E::custom("JSON null is forbidden")) 527 } 528 529 fn visit_unit<E>(self) -> Result<Self::Value, E> 530 where 531 E: de::Error, 532 { 533 Err(E::custom("JSON null is forbidden")) 534 } 535 536 fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error> 537 where 538 A: de::SeqAccess<'de>, 539 { 540 while sequence.next_element::<StrictJsonPayload>()?.is_some() {} 541 Ok(StrictJsonPayload) 542 } 543 544 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> 545 where 546 A: de::MapAccess<'de>, 547 { 548 let mut keys = BTreeSet::new(); 549 while let Some(key) = map.next_key::<String>()? { 550 if !keys.insert(key) { 551 return Err(de::Error::custom("duplicate JSON object key")); 552 } 553 map.next_value::<StrictJsonPayload>()?; 554 } 555 Ok(StrictJsonPayload) 556 } 557 } 558 559 type AdminRouteFuture = Pin<Box<dyn Future<Output = AdminRouteOutcome> + Send + 'static>>; 560 561 trait AdminRouteHandler: Send + Sync { 562 fn handle(&self, request: AdminRequest) -> AdminRouteFuture; 563 } 564 565 struct FunctionRouteHandler<F>(F); 566 567 // This impl only boxes and forwards a service-owned handler future. End-to-end dispatch remains 568 // covered by the server tests; generic closure instantiations add no host policy branches. 569 #[cfg_attr(coverage_nightly, coverage(off))] 570 impl<F, Fut> AdminRouteHandler for FunctionRouteHandler<F> 571 where 572 F: Fn(AdminRequest) -> Fut + Send + Sync + 'static, 573 Fut: Future<Output = AdminRouteOutcome> + Send + 'static, 574 { 575 fn handle(&self, request: AdminRequest) -> AdminRouteFuture { 576 Box::pin((self.0)(request)) 577 } 578 } 579 580 struct RegisteredRoute { 581 method: AdminHttpMethod, 582 path: AdminRoutePath, 583 handler: Arc<dyn AdminRouteHandler>, 584 } 585 586 /// Method/path registry supplied by the consuming service. 587 #[derive(Default)] 588 pub struct AdminRouter { 589 routes: Vec<RegisteredRoute>, 590 } 591 592 impl AdminRouter { 593 #[must_use] 594 pub fn new() -> Self { 595 Self::default() 596 } 597 598 pub fn route<F, Fut>( 599 &mut self, 600 method: AdminHttpMethod, 601 path: impl AsRef<str>, 602 handler: F, 603 ) -> Result<(), AdminRouteRegistrationError> 604 where 605 F: Fn(AdminRequest) -> Fut + Send + Sync + 'static, 606 Fut: Future<Output = AdminRouteOutcome> + Send + 'static, 607 { 608 let path = AdminRoutePath::new(path).map_err(AdminRouteRegistrationError::InvalidPath)?; 609 if self 610 .routes 611 .iter() 612 .any(|route| route.method == method && route.path == path) 613 { 614 return Err(AdminRouteRegistrationError::Duplicate); 615 } 616 if self.routes.iter().any(|route| { 617 route.method == method 618 && route.path.literal_count == path.literal_count 619 && route.path.overlaps(&path) 620 }) { 621 return Err(AdminRouteRegistrationError::Ambiguous); 622 } 623 self.routes.push(RegisteredRoute { 624 method, 625 path, 626 handler: Arc::new(FunctionRouteHandler(handler)), 627 }); 628 Ok(()) 629 } 630 } 631 632 impl fmt::Debug for AdminRouter { 633 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 634 formatter 635 .debug_struct("AdminRouter") 636 .field("route_count", &self.routes.len()) 637 .finish() 638 } 639 } 640 641 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 642 pub enum AdminServerConfigError { 643 NoRoutes, 644 ResponseLimitTooSmall, 645 } 646 647 impl fmt::Display for AdminServerConfigError { 648 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 649 formatter.write_str("admin server configuration is invalid") 650 } 651 } 652 653 impl Error for AdminServerConfigError {} 654 655 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 656 pub enum AdminServerError { 657 ListenerClone { kind: io::ErrorKind }, 658 ListenerRegistration { kind: io::ErrorKind }, 659 Accept { kind: io::ErrorKind }, 660 ConnectionTaskPanicked, 661 } 662 663 impl fmt::Display for AdminServerError { 664 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 665 formatter.write_str("bounded admin server failed") 666 } 667 } 668 669 impl Error for AdminServerError {} 670 671 struct AdminServerState { 672 routes: Vec<RegisteredRoute>, 673 limits: AdminTransportLimits, 674 entropy: Arc<dyn EntropySource>, 675 } 676 677 /// One bounded HTTP/1.1 server that can only consume a Unix admin binding. 678 pub struct AdminServer { 679 state: Arc<AdminServerState>, 680 } 681 682 impl fmt::Debug for AdminServer { 683 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 684 formatter 685 .debug_struct("AdminServer") 686 .field("route_count", &self.state.routes.len()) 687 .field("limits", &self.state.limits) 688 .field("entropy", &"[injected]") 689 .finish() 690 } 691 } 692 693 impl AdminServer { 694 pub fn new<E>( 695 router: AdminRouter, 696 limits: AdminTransportLimits, 697 entropy: E, 698 ) -> Result<Self, AdminServerConfigError> 699 where 700 E: EntropySource + 'static, 701 { 702 if router.routes.is_empty() { 703 return Err(AdminServerConfigError::NoRoutes); 704 } 705 if limits.response_body_utf8_bytes() < ADMIN_MIN_RESPONSE_BODY_UTF8_BYTES { 706 return Err(AdminServerConfigError::ResponseLimitTooSmall); 707 } 708 Ok(Self { 709 state: Arc::new(AdminServerState { 710 routes: router.routes, 711 limits, 712 entropy: Arc::new(entropy), 713 }), 714 }) 715 } 716 717 pub fn with_system_entropy( 718 router: AdminRouter, 719 limits: AdminTransportLimits, 720 ) -> Result<Self, AdminServerConfigError> { 721 Self::new(router, limits, SystemEntropy) 722 } 723 724 /// Serves until cancellation, then stops admission and drains every bounded connection task. 725 pub async fn serve( 726 self, 727 binding: UnixAdminSocketBinding, 728 cancellation: CancellationToken, 729 ) -> Result<(), AdminServerError> { 730 let listener = binding 731 .listener() 732 .try_clone() 733 .map_err(|error| AdminServerError::ListenerClone { kind: error.kind() })?; 734 listener 735 .set_nonblocking(true) 736 .map_err(|error| AdminServerError::ListenerRegistration { kind: error.kind() })?; 737 let listener = tokio::net::UnixListener::from_std(listener) 738 .map_err(|error| AdminServerError::ListenerRegistration { kind: error.kind() })?; 739 let peer_authorizer = binding.peer_authorizer(); 740 let permits = Arc::new(Semaphore::new( 741 self.state.limits.concurrent_connections() as usize 742 )); 743 let mut tasks = JoinSet::new(); 744 745 let mut result = loop { 746 tokio::select! { 747 biased; 748 () = cancellation.cancelled() => break Ok(()), 749 joined = tasks.join_next(), if !tasks.is_empty() => { 750 if joined.is_some_and(|result| result.is_err()) { 751 cancellation.cancel(); 752 break Err(AdminServerError::ConnectionTaskPanicked); 753 } 754 } 755 accepted = listener.accept() => { 756 let (stream, _) = match accepted { 757 Ok(accepted) => accepted, 758 Err(error) => { 759 cancellation.cancel(); 760 break Err(AdminServerError::Accept { kind: error.kind() }); 761 } 762 }; 763 if peer_authorizer.authorize(&stream).is_err() { 764 drop(stream); 765 continue; 766 } 767 let Ok(permit) = Arc::clone(&permits).try_acquire_owned() else { 768 drop(stream); 769 continue; 770 }; 771 let state = Arc::clone(&self.state); 772 let connection_cancellation = cancellation.clone(); 773 tasks.spawn(async move { 774 let _permit = permit; 775 serve_connection(stream, state, connection_cancellation).await; 776 }); 777 } 778 } 779 }; 780 781 while let Some(joined) = tasks.join_next().await { 782 if joined.is_err() { 783 result = Err(AdminServerError::ConnectionTaskPanicked); 784 } 785 } 786 drop(binding); 787 result 788 } 789 } 790 791 async fn serve_connection( 792 stream: tokio::net::UnixStream, 793 state: Arc<AdminServerState>, 794 cancellation: CancellationToken, 795 ) { 796 let service_state = Arc::clone(&state); 797 let service = service_fn(move |request| { 798 let state = Arc::clone(&service_state); 799 async move { Ok::<_, Infallible>(serve_request(request, state).await) } 800 }); 801 802 let mut builder = http1::Builder::new(); 803 builder 804 .keep_alive(false) 805 .auto_date_header(false) 806 .max_headers(state.limits.header_count() as usize) 807 .max_buf_size((state.limits.header_bytes() as usize).max(HTTP_MINIMUM_MAX_BUFFER_SIZE)) 808 .header_read_timeout(state.limits.idle_timeout()) 809 .timer(TokioTimer::new()); 810 811 let mut connection = Box::pin(builder.serve_connection(TokioIo::new(stream), service)); 812 let connection_deadline = state 813 .limits 814 .request_deadline() 815 .checked_add(state.limits.idle_timeout()) 816 .unwrap_or(state.limits.idle_timeout()); 817 tokio::select! { 818 biased; 819 () = cancellation.cancelled() => { 820 connection.as_mut().graceful_shutdown(); 821 let _ = tokio::time::timeout(connection_deadline, connection).await; 822 } 823 _ = tokio::time::timeout(connection_deadline, &mut connection) => {} 824 } 825 } 826 827 async fn serve_request( 828 request: Request<Incoming>, 829 state: Arc<AdminServerState>, 830 ) -> Response<Full<Bytes>> { 831 let (initial_correlation, deferred_entropy_failure) = 832 match generated_correlation(&*state.entropy) { 833 Ok(correlation) => (correlation, None), 834 Err(error) => (fallback_correlation(), Some(error)), 835 }; 836 let correlation = Arc::new(CorrelationSlot::new(initial_correlation)); 837 let deadline = state.limits.request_deadline(); 838 match tokio::time::timeout( 839 deadline, 840 process_request( 841 request, 842 Arc::clone(&state), 843 Arc::clone(&correlation), 844 deferred_entropy_failure, 845 ), 846 ) 847 .await 848 { 849 Ok(response) => response, 850 Err(_) => failure_response( 851 StatusCode::GATEWAY_TIMEOUT, 852 correlation.current(), 853 known_error("request_timeout", "admin request deadline elapsed"), 854 state.limits, 855 ), 856 } 857 } 858 859 async fn process_request( 860 request: Request<Incoming>, 861 state: Arc<AdminServerState>, 862 correlation: Arc<CorrelationSlot>, 863 deferred_entropy_failure: Option<AdminError>, 864 ) -> Response<Full<Bytes>> { 865 let correlation_id = correlation.current(); 866 if request.version() != Version::HTTP_11 { 867 return failure_response( 868 StatusCode::HTTP_VERSION_NOT_SUPPORTED, 869 correlation_id, 870 known_error( 871 "http_version_unsupported", 872 "admin transport requires HTTP/1.1", 873 ), 874 state.limits, 875 ); 876 } 877 if header_bytes(request.headers()) > u64::from(state.limits.header_bytes()) 878 || request.headers().len() > state.limits.header_count() as usize 879 { 880 return failure_response( 881 StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE, 882 correlation_id, 883 known_error( 884 "request_headers_too_large", 885 "admin request headers exceed the limit", 886 ), 887 state.limits, 888 ); 889 } 890 if query_item_count(request.uri().query()) > state.limits.query_items() as usize { 891 return failure_response( 892 StatusCode::BAD_REQUEST, 893 correlation_id, 894 known_error("query_limit_exceeded", "admin query exceeds the item limit"), 895 state.limits, 896 ); 897 } 898 899 if unknown_major_version(request.uri().path()) { 900 let response = AdminFailureResponse::unsupported_contract_version(correlation_id.clone()); 901 return bounded_json_response( 902 StatusCode::BAD_REQUEST, 903 &response, 904 correlation_id, 905 state.limits, 906 ); 907 } 908 let Some(method) = AdminHttpMethod::from_http(request.method()) else { 909 return failure_response( 910 StatusCode::NOT_FOUND, 911 correlation_id, 912 known_error("route_not_found", "admin route was not found"), 913 state.limits, 914 ); 915 }; 916 let route = state 917 .routes 918 .iter() 919 .filter(|route| route.method == method) 920 .filter_map(|route| { 921 route 922 .path 923 .match_path(request.uri().path()) 924 .map(|parameters| { 925 ( 926 route.path.literal_count, 927 route.path.clone(), 928 parameters, 929 Arc::clone(&route.handler), 930 ) 931 }) 932 }) 933 .max_by_key(|(literal_count, _, _, _)| *literal_count); 934 let Some((_, path, parameters, handler)) = route else { 935 return failure_response( 936 StatusCode::NOT_FOUND, 937 correlation_id, 938 known_error("route_not_found", "admin route was not found"), 939 state.limits, 940 ); 941 }; 942 943 if method == AdminHttpMethod::Post && !is_json_content_type(request.headers()) { 944 return failure_response( 945 StatusCode::UNSUPPORTED_MEDIA_TYPE, 946 correlation_id, 947 known_error( 948 "content_type_required", 949 "admin mutation requires JSON content type", 950 ), 951 state.limits, 952 ); 953 } 954 let query = request.uri().query().map(str::to_owned); 955 let body = match Limited::new( 956 request.into_body(), 957 state.limits.request_body_utf8_bytes() as usize, 958 ) 959 .collect() 960 .await 961 { 962 Ok(collected) => collected.to_bytes(), 963 Err(error) if error.downcast_ref::<LengthLimitError>().is_some() => { 964 return failure_response( 965 StatusCode::PAYLOAD_TOO_LARGE, 966 correlation_id, 967 known_error( 968 "request_body_too_large", 969 "admin request body exceeds the limit", 970 ), 971 state.limits, 972 ); 973 } 974 Err(_) => { 975 return failure_response( 976 StatusCode::BAD_REQUEST, 977 correlation_id, 978 known_error( 979 "request_body_unavailable", 980 "admin request body is unavailable", 981 ), 982 state.limits, 983 ); 984 } 985 }; 986 if method == AdminHttpMethod::Get && !body.is_empty() { 987 return failure_response( 988 StatusCode::BAD_REQUEST, 989 correlation_id, 990 known_error("malformed_json", "admin request body is not valid JSON"), 991 state.limits, 992 ); 993 } 994 995 if method == AdminHttpMethod::Post { 996 let envelope = 997 match serde_json::from_slice::<AdminMutationRequest<StrictJsonPayload>>(&body) { 998 Ok(envelope) => envelope, 999 Err(_) => { 1000 return failure_response( 1001 StatusCode::BAD_REQUEST, 1002 correlation.current(), 1003 known_error("malformed_json", "admin request body is not valid JSON"), 1004 state.limits, 1005 ); 1006 } 1007 }; 1008 if let Some(caller_correlation) = envelope.correlation_id().cloned() { 1009 correlation.replace(caller_correlation); 1010 } else if let Some(error) = deferred_entropy_failure { 1011 return failure_response( 1012 StatusCode::INTERNAL_SERVER_ERROR, 1013 correlation.current(), 1014 error, 1015 state.limits, 1016 ); 1017 } 1018 if envelope.validate_contract_version().is_err() { 1019 let correlation_id = correlation.current(); 1020 let response = 1021 AdminFailureResponse::unsupported_contract_version(correlation_id.clone()); 1022 return bounded_json_response( 1023 StatusCode::BAD_REQUEST, 1024 &response, 1025 correlation_id, 1026 state.limits, 1027 ); 1028 } 1029 } else if let Some(error) = deferred_entropy_failure { 1030 return failure_response( 1031 StatusCode::INTERNAL_SERVER_ERROR, 1032 correlation.current(), 1033 error, 1034 state.limits, 1035 ); 1036 } 1037 let correlation_id = correlation.current(); 1038 1039 let request = AdminRequest { 1040 method, 1041 path, 1042 query, 1043 correlation_id: correlation_id.clone(), 1044 parameters, 1045 body, 1046 response_body_limit: state.limits.response_body_utf8_bytes() as usize, 1047 }; 1048 match handler.handle(request).await.0 { 1049 AdminRouteOutcomeKind::Success(result) => { 1050 let envelope = ServerSuccessEnvelope { 1051 contract_version: ADMIN_CONTRACT_VERSION, 1052 ok: true, 1053 correlation_id: &correlation_id, 1054 result: &result, 1055 }; 1056 bounded_json_response( 1057 StatusCode::OK, 1058 &envelope, 1059 correlation_id.clone(), 1060 state.limits, 1061 ) 1062 } 1063 AdminRouteOutcomeKind::Failure(failure) => failure_response( 1064 failure.status.http_status(), 1065 correlation_id, 1066 failure.error, 1067 state.limits, 1068 ), 1069 } 1070 } 1071 1072 fn generated_correlation(entropy: &dyn EntropySource) -> Result<AdminCorrelationId, AdminError> { 1073 let mut bytes = [0_u8; 16]; 1074 if entropy.fill_bytes(&mut bytes).is_err() { 1075 return Err(known_error( 1076 "correlation_unavailable", 1077 "admin correlation identity is unavailable", 1078 )); 1079 } 1080 let value = bytes 1081 .iter() 1082 .map(|byte| format!("{byte:02x}")) 1083 .collect::<String>(); 1084 Ok(AdminCorrelationId::new(value).expect("hex correlation must be valid")) 1085 } 1086 1087 fn fallback_correlation() -> AdminCorrelationId { 1088 AdminCorrelationId::new(FALLBACK_CORRELATION_ID) 1089 .expect("static correlation fallback must be valid") 1090 } 1091 1092 fn unknown_major_version(path: &str) -> bool { 1093 path.strip_prefix('/') 1094 .and_then(|path| path.split('/').next()) 1095 .is_some_and(|segment| { 1096 segment != "v1" 1097 && segment.strip_prefix('v').is_some_and(|version| { 1098 !version.is_empty() && version.bytes().all(|byte| byte.is_ascii_digit()) 1099 }) 1100 }) 1101 } 1102 1103 struct CorrelationSlot(Mutex<AdminCorrelationId>); 1104 1105 impl CorrelationSlot { 1106 fn new(correlation_id: AdminCorrelationId) -> Self { 1107 Self(Mutex::new(correlation_id)) 1108 } 1109 1110 fn current(&self) -> AdminCorrelationId { 1111 self.0 1112 .lock() 1113 .unwrap_or_else(std::sync::PoisonError::into_inner) 1114 .clone() 1115 } 1116 1117 fn replace(&self, correlation_id: AdminCorrelationId) { 1118 *self 1119 .0 1120 .lock() 1121 .unwrap_or_else(std::sync::PoisonError::into_inner) = correlation_id; 1122 } 1123 } 1124 1125 fn header_bytes(headers: &HeaderMap) -> u64 { 1126 headers.iter().fold(0_u64, |total, (name, value)| { 1127 total 1128 .saturating_add(name.as_str().len() as u64) 1129 .saturating_add(value.as_bytes().len() as u64) 1130 }) 1131 } 1132 1133 fn query_item_count(query: Option<&str>) -> usize { 1134 match query { 1135 None | Some("") => 0, 1136 Some(query) => query.split('&').count(), 1137 } 1138 } 1139 1140 fn is_json_content_type(headers: &HeaderMap) -> bool { 1141 headers 1142 .get(CONTENT_TYPE) 1143 .and_then(|value| value.to_str().ok()) 1144 .is_some_and(|value| { 1145 value == JSON_CONTENT_TYPE || value == "application/json; charset=utf-8" 1146 }) 1147 } 1148 1149 fn failure_response( 1150 status: StatusCode, 1151 correlation_id: AdminCorrelationId, 1152 error: AdminError, 1153 limits: AdminTransportLimits, 1154 ) -> Response<Full<Bytes>> { 1155 let envelope = AdminFailureResponse::new(correlation_id.clone(), error); 1156 bounded_json_response(status, &envelope, correlation_id, limits) 1157 } 1158 1159 fn bounded_json_response<T>( 1160 status: StatusCode, 1161 value: &T, 1162 correlation_id: AdminCorrelationId, 1163 limits: AdminTransportLimits, 1164 ) -> Response<Full<Bytes>> 1165 where 1166 T: Serialize, 1167 { 1168 let limit = limits.response_body_utf8_bytes() as usize; 1169 if let Ok(encoded) = encode_bounded(value, limit) { 1170 return json_response(status, encoded); 1171 } 1172 let fallback = AdminFailureResponse::new( 1173 correlation_id, 1174 known_error( 1175 "response_body_too_large", 1176 "admin response body exceeds the limit", 1177 ), 1178 ); 1179 match encode_bounded(&fallback, limit) { 1180 Ok(encoded) => json_response(StatusCode::INTERNAL_SERVER_ERROR, encoded), 1181 Err(_) => json_response( 1182 StatusCode::INTERNAL_SERVER_ERROR, 1183 br#"{"contract_version":1,"ok":false,"correlation_id":"correlation-unavailable","error":{"code":"response_body_too_large","message":"admin response body exceeds the limit"}}"#.to_vec(), 1184 ), 1185 } 1186 } 1187 1188 enum BoundedEncodingError { 1189 Limit, 1190 Encoding, 1191 } 1192 1193 struct CappedWriter { 1194 bytes: Vec<u8>, 1195 limit: usize, 1196 exceeded: bool, 1197 } 1198 1199 impl CappedWriter { 1200 fn new(limit: usize) -> Self { 1201 Self { 1202 bytes: Vec::with_capacity(limit.min(4096)), 1203 limit, 1204 exceeded: false, 1205 } 1206 } 1207 } 1208 1209 impl io::Write for CappedWriter { 1210 fn write(&mut self, bytes: &[u8]) -> io::Result<usize> { 1211 if bytes.len() > self.limit.saturating_sub(self.bytes.len()) { 1212 self.exceeded = true; 1213 return Err(io::Error::other("bounded JSON output limit exceeded")); 1214 } 1215 self.bytes.extend_from_slice(bytes); 1216 Ok(bytes.len()) 1217 } 1218 1219 fn flush(&mut self) -> io::Result<()> { 1220 Ok(()) 1221 } 1222 } 1223 1224 fn encode_bounded<T>(value: &T, limit: usize) -> Result<Vec<u8>, BoundedEncodingError> 1225 where 1226 T: Serialize, 1227 { 1228 let mut writer = CappedWriter::new(limit); 1229 match serde_json::to_writer(&mut writer, value) { 1230 Ok(()) => Ok(writer.bytes), 1231 Err(_) if writer.exceeded => Err(BoundedEncodingError::Limit), 1232 Err(_) => Err(BoundedEncodingError::Encoding), 1233 } 1234 } 1235 1236 fn json_response(status: StatusCode, body: Vec<u8>) -> Response<Full<Bytes>> { 1237 let mut response = Response::new(Full::new(Bytes::from(body))); 1238 *response.status_mut() = status; 1239 response 1240 .headers_mut() 1241 .insert(CONTENT_TYPE, HeaderValue::from_static(JSON_CONTENT_TYPE)); 1242 response 1243 } 1244 1245 fn known_error(code: &'static str, message: &'static str) -> AdminError { 1246 AdminError::new( 1247 AdminErrorCode::new(code).expect("static admin error code must be valid"), 1248 AdminErrorMessage::new(message).expect("static admin error message must be valid"), 1249 ) 1250 } 1251 1252 #[cfg(test)] 1253 mod tests { 1254 use super::*; 1255 use serde::{Deserialize, Serialize}; 1256 use std::sync::atomic::{AtomicUsize, Ordering}; 1257 use tokio::io::{AsyncReadExt, AsyncWriteExt}; 1258 use tokio::sync::Notify; 1259 1260 #[derive(Clone, Copy)] 1261 struct FixedEntropy(u8); 1262 1263 impl EntropySource for FixedEntropy { 1264 fn fill_bytes(&self, destination: &mut [u8]) -> Result<(), crate::EntropyError> { 1265 destination.fill(self.0); 1266 Ok(()) 1267 } 1268 } 1269 1270 struct FailingEntropy; 1271 1272 impl EntropySource for FailingEntropy { 1273 fn fill_bytes(&self, _destination: &mut [u8]) -> Result<(), crate::EntropyError> { 1274 Err(crate::EntropyError::Unavailable) 1275 } 1276 } 1277 1278 struct CountingFailingEntropy(Arc<AtomicUsize>); 1279 1280 impl EntropySource for CountingFailingEntropy { 1281 fn fill_bytes(&self, _destination: &mut [u8]) -> Result<(), crate::EntropyError> { 1282 self.0.fetch_add(1, Ordering::SeqCst); 1283 Err(crate::EntropyError::Unavailable) 1284 } 1285 } 1286 1287 #[derive(Debug, Deserialize, Serialize)] 1288 #[serde(deny_unknown_fields)] 1289 struct EchoRequest { 1290 value: String, 1291 } 1292 1293 fn echo_router() -> AdminRouter { 1294 let mut router = AdminRouter::new(); 1295 router 1296 .route(AdminHttpMethod::Post, "/v1/echo", |request| async move { 1297 match request.decode_json::<AdminMutationRequest<EchoRequest>>() { 1298 Ok(envelope) => { 1299 let request_body = envelope.into_request(); 1300 request 1301 .success(&serde_json::json!({"value": request_body.value})) 1302 .expect("echo result should encode") 1303 } 1304 Err(_) => AdminRouteOutcome::failure(AdminRouteFailure::new( 1305 AdminRouteFailureStatus::BadRequest, 1306 known_error("malformed_echo", "echo request is malformed"), 1307 )), 1308 } 1309 }) 1310 .expect("echo route"); 1311 router 1312 } 1313 1314 fn limits_with( 1315 request_body: u32, 1316 concurrency: u32, 1317 deadline: std::time::Duration, 1318 ) -> AdminTransportLimits { 1319 let mut values = AdminTransportLimits::DEFAULT.values(); 1320 values.request_body_utf8_bytes = request_body; 1321 values.concurrent_connections = concurrency; 1322 values.request_deadline = deadline; 1323 AdminTransportLimits::new(values).expect("test limits") 1324 } 1325 1326 async fn binding( 1327 directory: &tempfile::TempDir, 1328 ) -> (std::path::PathBuf, UnixAdminSocketBinding) { 1329 let socket = directory.path().join("admin.sock"); 1330 let authority = super::super::UnixAdminSocketWriterAuthority::acquire(directory.path()) 1331 .expect("writer authority"); 1332 let binding = UnixAdminSocketBinding::bind(authority, &socket) 1333 .await 1334 .expect("socket binding"); 1335 (socket, binding) 1336 } 1337 1338 async fn exchange(socket: &std::path::Path, request: &[u8]) -> Vec<u8> { 1339 let mut stream = tokio::net::UnixStream::connect(socket) 1340 .await 1341 .expect("connect admin server"); 1342 stream.write_all(request).await.expect("write request"); 1343 let mut response = Vec::new(); 1344 stream 1345 .read_to_end(&mut response) 1346 .await 1347 .expect("read response"); 1348 response 1349 } 1350 1351 async fn exchange_allowing_reset(socket: &std::path::Path, request: &[u8]) -> Vec<u8> { 1352 let mut stream = tokio::net::UnixStream::connect(socket) 1353 .await 1354 .expect("connect admin server"); 1355 stream.write_all(request).await.expect("write request"); 1356 let mut response = Vec::new(); 1357 match stream.read_to_end(&mut response).await { 1358 Ok(_) => response, 1359 Err(error) if error.kind() == std::io::ErrorKind::ConnectionReset => response, 1360 Err(error) => panic!("read response: {error}"), 1361 } 1362 } 1363 1364 #[tokio::test] 1365 async fn serves_valid_json_with_exact_caller_correlation_and_no_web_headers() { 1366 let directory = super::test_support::short_tempdir(); 1367 let (socket, binding) = binding(&directory).await; 1368 let server = AdminServer::new( 1369 echo_router(), 1370 AdminTransportLimits::DEFAULT, 1371 FixedEntropy(7), 1372 ) 1373 .expect("admin server"); 1374 let cancellation = CancellationToken::new(); 1375 let task_cancellation = cancellation.clone(); 1376 let task = tokio::spawn(server.serve(binding, task_cancellation)); 1377 1378 let body = r#"{"contract_version":1,"operation_id":"echo-01","correlation_id":"caller-01","request":{"value":"ok"}}"#; 1379 let request = format!( 1380 "POST /v1/echo HTTP/1.1\r\nHost: local\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{body}", 1381 body.len() 1382 ); 1383 let response = exchange(&socket, request.as_bytes()).await; 1384 let text = String::from_utf8(response).expect("UTF-8 response"); 1385 assert!(text.starts_with("HTTP/1.1 200 OK\r\n"), "{text}"); 1386 assert!(text.contains("content-type: application/json")); 1387 assert!(!text.to_ascii_lowercase().contains("access-control-allow")); 1388 assert!(text.ends_with( 1389 r#"{"contract_version":1,"ok":true,"correlation_id":"caller-01","result":{"value":"ok"}}"# 1390 )); 1391 1392 let unsupported = 1393 exchange(&socket, b"GET /v2/status HTTP/1.1\r\nHost: local\r\n\r\n").await; 1394 let unsupported = String::from_utf8(unsupported).expect("unsupported response"); 1395 assert!(unsupported.starts_with("HTTP/1.1 400 Bad Request\r\n")); 1396 assert!(unsupported.contains("unsupported_contract_version")); 1397 assert!(unsupported.contains("07070707070707070707070707070707")); 1398 1399 cancellation.cancel(); 1400 task.await.expect("server task").expect("server shutdown"); 1401 assert!(!socket.exists()); 1402 } 1403 1404 #[tokio::test] 1405 async fn rejects_oversized_and_malformed_json_before_the_handler() { 1406 let directory = super::test_support::short_tempdir(); 1407 let (socket, binding) = binding(&directory).await; 1408 let calls = Arc::new(AtomicUsize::new(0)); 1409 let handler_calls = Arc::clone(&calls); 1410 let mut router = AdminRouter::new(); 1411 router 1412 .route(AdminHttpMethod::Post, "/v1/test", move |request| { 1413 let calls = Arc::clone(&handler_calls); 1414 async move { 1415 calls.fetch_add(1, Ordering::SeqCst); 1416 request 1417 .success(&serde_json::json!({"ok": true})) 1418 .expect("test result") 1419 } 1420 }) 1421 .expect("test route"); 1422 let server = AdminServer::new( 1423 router, 1424 limits_with(8, 4, std::time::Duration::from_secs(1)), 1425 FixedEntropy(8), 1426 ) 1427 .expect("admin server"); 1428 let cancellation = CancellationToken::new(); 1429 let task = tokio::spawn(server.serve(binding, cancellation.clone())); 1430 1431 let oversized = exchange( 1432 &socket, 1433 b"POST /v1/test HTTP/1.1\r\nHost: local\r\nContent-Type: application/json\r\nContent-Length: 9\r\n\r\n123456789", 1434 ) 1435 .await; 1436 assert!( 1437 String::from_utf8(oversized) 1438 .expect("response") 1439 .contains("request_body_too_large") 1440 ); 1441 let malformed = exchange( 1442 &socket, 1443 b"POST /v1/test HTTP/1.1\r\nHost: local\r\nContent-Type: application/json\r\nContent-Length: 1\r\n\r\n{", 1444 ) 1445 .await; 1446 assert!( 1447 String::from_utf8(malformed) 1448 .expect("response") 1449 .contains("malformed_json") 1450 ); 1451 assert_eq!(calls.load(Ordering::SeqCst), 0); 1452 1453 cancellation.cancel(); 1454 task.await.expect("server task").expect("server shutdown"); 1455 } 1456 1457 #[tokio::test] 1458 async fn rejects_invalid_mutation_envelopes_duplicates_and_nested_null_before_dispatch() { 1459 let directory = super::test_support::short_tempdir(); 1460 let (socket, binding) = binding(&directory).await; 1461 let calls = Arc::new(AtomicUsize::new(0)); 1462 let handler_calls = Arc::clone(&calls); 1463 let mut router = AdminRouter::new(); 1464 router 1465 .route(AdminHttpMethod::Post, "/v1/test", move |request| { 1466 let calls = Arc::clone(&handler_calls); 1467 async move { 1468 calls.fetch_add(1, Ordering::SeqCst); 1469 request.success(&true).expect("test result") 1470 } 1471 }) 1472 .expect("test route"); 1473 let server = AdminServer::new(router, AdminTransportLimits::DEFAULT, FixedEntropy(0x21)) 1474 .expect("admin server"); 1475 let cancellation = CancellationToken::new(); 1476 let task = tokio::spawn(server.serve(binding, cancellation.clone())); 1477 1478 for body in [ 1479 r#"{"operation_id":"missing-version","request":{"value":true}}"#, 1480 r#"{"contract_version":1,"operation_id":"missing-request"}"#, 1481 r#"{"contract_version":1,"operation_id":"duplicate-correlation","correlation_id":"first","correlation_id":"second","request":{"value":true}}"#, 1482 r#"{"contract_version":1,"operation_id":"nested-duplicate","request":{"value":true,"value":false}}"#, 1483 r#"{"contract_version":1,"operation_id":"nested-null","request":{"nested":[true,null]}}"#, 1484 ] { 1485 let request = format!( 1486 "POST /v1/test HTTP/1.1\r\nHost: local\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{body}", 1487 body.len() 1488 ); 1489 let response = String::from_utf8(exchange(&socket, request.as_bytes()).await) 1490 .expect("invalid envelope response"); 1491 assert!(response.contains("malformed_json"), "{response}"); 1492 } 1493 1494 let body = r#"{"contract_version":2,"operation_id":"future-version","correlation_id":"caller-v2","request":{"value":true}}"#; 1495 let request = format!( 1496 "POST /v1/test HTTP/1.1\r\nHost: local\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{body}", 1497 body.len() 1498 ); 1499 let response = String::from_utf8(exchange(&socket, request.as_bytes()).await) 1500 .expect("version response"); 1501 assert!(response.contains("unsupported_contract_version")); 1502 assert!(response.contains("caller-v2")); 1503 assert_eq!(calls.load(Ordering::SeqCst), 0); 1504 1505 cancellation.cancel(); 1506 task.await.expect("server task").expect("server shutdown"); 1507 } 1508 1509 #[tokio::test] 1510 async fn parameterized_routes_percent_decode_bounded_values_without_service_authority() { 1511 let directory = super::test_support::short_tempdir(); 1512 let (socket, binding) = binding(&directory).await; 1513 let calls = Arc::new(AtomicUsize::new(0)); 1514 let handler_calls = Arc::clone(&calls); 1515 let mut router = AdminRouter::new(); 1516 router 1517 .route( 1518 AdminHttpMethod::Post, 1519 "/v1/connections/{connection_id}/approve", 1520 move |request| { 1521 let calls = Arc::clone(&handler_calls); 1522 async move { 1523 calls.fetch_add(1, Ordering::SeqCst); 1524 let parameter = request 1525 .parameter("connection_id") 1526 .expect("decoded route parameter") 1527 .to_owned(); 1528 request 1529 .success(&serde_json::json!({"connection_id": parameter})) 1530 .expect("parameter response") 1531 } 1532 }, 1533 ) 1534 .expect("parameterized route"); 1535 let server = AdminServer::new(router, AdminTransportLimits::DEFAULT, FixedEntropy(0x22)) 1536 .expect("admin server"); 1537 let cancellation = CancellationToken::new(); 1538 let task = tokio::spawn(server.serve(binding, cancellation.clone())); 1539 let body = 1540 r#"{"contract_version":1,"operation_id":"approve-01","request":{"approve":true}}"#; 1541 1542 let request = format!( 1543 "POST /v1/connections/farm%2D01/approve HTTP/1.1\r\nHost: local\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{body}", 1544 body.len() 1545 ); 1546 let response = String::from_utf8(exchange(&socket, request.as_bytes()).await) 1547 .expect("parameter response"); 1548 assert!(response.starts_with("HTTP/1.1 200 OK"), "{response}"); 1549 assert!(response.contains(r#""connection_id":"farm-01""#)); 1550 1551 for path in [ 1552 "/v1/connections/farm%2/approve", 1553 "/v1/connections/farm%2F01/approve", 1554 &format!( 1555 "/v1/connections/{}/approve", 1556 "x".repeat(ADMIN_ROUTE_PARAMETER_VALUE_MAX_UTF8_BYTES + 1) 1557 ), 1558 ] { 1559 let request = format!( 1560 "POST {path} HTTP/1.1\r\nHost: local\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{body}", 1561 body.len() 1562 ); 1563 let response = String::from_utf8(exchange(&socket, request.as_bytes()).await) 1564 .expect("invalid parameter response"); 1565 assert!(response.contains("route_not_found"), "{response}"); 1566 } 1567 assert_eq!(calls.load(Ordering::SeqCst), 1); 1568 1569 cancellation.cancel(); 1570 task.await.expect("server task").expect("server shutdown"); 1571 } 1572 1573 #[tokio::test] 1574 async fn caller_correlation_precedes_entropy_and_survives_timeout_handoff() { 1575 let directory = super::test_support::short_tempdir(); 1576 let (socket, binding) = binding(&directory).await; 1577 let entropy_calls = Arc::new(AtomicUsize::new(0)); 1578 let mut router = AdminRouter::new(); 1579 router 1580 .route(AdminHttpMethod::Post, "/v1/slow", |request| async move { 1581 tokio::time::sleep(std::time::Duration::from_secs(60)).await; 1582 request.success(&true).expect("slow result") 1583 }) 1584 .expect("slow route"); 1585 let server = AdminServer::new( 1586 router, 1587 limits_with(1024, 4, std::time::Duration::from_millis(20)), 1588 CountingFailingEntropy(Arc::clone(&entropy_calls)), 1589 ) 1590 .expect("admin server"); 1591 let cancellation = CancellationToken::new(); 1592 let task = tokio::spawn(server.serve(binding, cancellation.clone())); 1593 1594 let body = r#"{"contract_version":1,"operation_id":"slow-01","correlation_id":"caller-timeout","request":{"wait":true}}"#; 1595 let request = format!( 1596 "POST /v1/slow HTTP/1.1\r\nHost: local\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{body}", 1597 body.len() 1598 ); 1599 let response = String::from_utf8(exchange(&socket, request.as_bytes()).await) 1600 .expect("timeout response"); 1601 assert!(response.starts_with("HTTP/1.1 504 Gateway Timeout")); 1602 assert!(response.contains("caller-timeout")); 1603 assert_eq!(entropy_calls.load(Ordering::SeqCst), 1); 1604 1605 let body = r#"{"contract_version":1,"operation_id":"slow-02","request":{"wait":true}}"#; 1606 let request = format!( 1607 "POST /v1/slow HTTP/1.1\r\nHost: local\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{body}", 1608 body.len() 1609 ); 1610 let response = String::from_utf8(exchange(&socket, request.as_bytes()).await) 1611 .expect("entropy response"); 1612 assert!(response.contains("correlation_unavailable")); 1613 assert_eq!(entropy_calls.load(Ordering::SeqCst), 2); 1614 1615 cancellation.cancel(); 1616 task.await.expect("server task").expect("server shutdown"); 1617 } 1618 1619 #[tokio::test] 1620 async fn rejects_http_1_0_before_dispatch() { 1621 let directory = super::test_support::short_tempdir(); 1622 let (socket, binding) = binding(&directory).await; 1623 let calls = Arc::new(AtomicUsize::new(0)); 1624 let handler_calls = Arc::clone(&calls); 1625 let mut router = AdminRouter::new(); 1626 router 1627 .route(AdminHttpMethod::Get, "/v1/status", move |request| { 1628 let calls = Arc::clone(&handler_calls); 1629 async move { 1630 calls.fetch_add(1, Ordering::SeqCst); 1631 request.success(&true).expect("status result") 1632 } 1633 }) 1634 .expect("status route"); 1635 let server = AdminServer::new(router, AdminTransportLimits::DEFAULT, FixedEntropy(0x23)) 1636 .expect("admin server"); 1637 let cancellation = CancellationToken::new(); 1638 let task = tokio::spawn(server.serve(binding, cancellation.clone())); 1639 1640 let response = String::from_utf8( 1641 exchange(&socket, b"GET /v1/status HTTP/1.0\r\nHost: local\r\n\r\n").await, 1642 ) 1643 .expect("HTTP/1.0 response"); 1644 assert!(response.contains("505 HTTP Version Not Supported")); 1645 assert!(response.contains("http_version_unsupported")); 1646 assert_eq!(calls.load(Ordering::SeqCst), 0); 1647 1648 cancellation.cancel(); 1649 task.await.expect("server task").expect("server shutdown"); 1650 } 1651 1652 #[tokio::test] 1653 async fn request_deadline_returns_a_safe_timeout_and_cancels_the_handler_future() { 1654 let directory = super::test_support::short_tempdir(); 1655 let (socket, binding) = binding(&directory).await; 1656 let mut router = AdminRouter::new(); 1657 router 1658 .route(AdminHttpMethod::Get, "/v1/slow", |request| async move { 1659 tokio::time::sleep(std::time::Duration::from_secs(60)).await; 1660 request 1661 .success(&serde_json::json!({"late": true})) 1662 .expect("late result") 1663 }) 1664 .expect("slow route"); 1665 let server = AdminServer::new( 1666 router, 1667 limits_with(1024, 4, std::time::Duration::from_millis(20)), 1668 FixedEntropy(9), 1669 ) 1670 .expect("admin server"); 1671 let cancellation = CancellationToken::new(); 1672 let task = tokio::spawn(server.serve(binding, cancellation.clone())); 1673 1674 let response = exchange(&socket, b"GET /v1/slow HTTP/1.1\r\nHost: local\r\n\r\n").await; 1675 let text = String::from_utf8(response).expect("response"); 1676 assert!(text.starts_with("HTTP/1.1 504 Gateway Timeout\r\n")); 1677 assert!(text.contains("request_timeout")); 1678 1679 cancellation.cancel(); 1680 task.await.expect("server task").expect("server shutdown"); 1681 } 1682 1683 #[tokio::test] 1684 async fn enforces_header_query_response_and_body_correlation_boundaries() { 1685 let directory = super::test_support::short_tempdir(); 1686 let (socket, binding) = binding(&directory).await; 1687 let calls = Arc::new(AtomicUsize::new(0)); 1688 let handler_calls = Arc::clone(&calls); 1689 let mut router = AdminRouter::new(); 1690 router 1691 .route(AdminHttpMethod::Get, "/v1/large", move |request| { 1692 let calls = Arc::clone(&handler_calls); 1693 async move { 1694 calls.fetch_add(1, Ordering::SeqCst); 1695 match request.success(&"x".repeat(1024)) { 1696 Ok(outcome) => outcome, 1697 Err(AdminRouteOutcomeError::ResponseLimit) => { 1698 AdminRouteOutcome::failure(AdminRouteFailure::new( 1699 AdminRouteFailureStatus::Internal, 1700 known_error( 1701 "response_body_too_large", 1702 "admin response body exceeds the limit", 1703 ), 1704 )) 1705 } 1706 Err(_) => panic!("large result should only exceed the cap"), 1707 } 1708 } 1709 }) 1710 .expect("large route"); 1711 router 1712 .route(AdminHttpMethod::Post, "/v1/input", |request| async move { 1713 request.success(&true).expect("input result") 1714 }) 1715 .expect("input route"); 1716 let mut values = AdminTransportLimits::DEFAULT.values(); 1717 values.header_bytes = 64; 1718 values.query_items = 1; 1719 values.response_body_utf8_bytes = ADMIN_MIN_RESPONSE_BODY_UTF8_BYTES; 1720 let limits = AdminTransportLimits::new(values).expect("boundary limits"); 1721 let server = AdminServer::new(router, limits, FixedEntropy(11)).expect("admin server"); 1722 let cancellation = CancellationToken::new(); 1723 let task = tokio::spawn(server.serve(binding, cancellation.clone())); 1724 1725 let headers = exchange( 1726 &socket, 1727 b"GET /v1/large HTTP/1.1\r\nHost: local\r\nX-Large: 0123456789012345678901234567890123456789012345678901234567890123456789\r\n\r\n", 1728 ) 1729 .await; 1730 assert!( 1731 String::from_utf8(headers) 1732 .expect("header response") 1733 .contains("request_headers_too_large") 1734 ); 1735 1736 let query = exchange( 1737 &socket, 1738 b"GET /v1/large?a=1&b=2 HTTP/1.1\r\nHost: local\r\n\r\n", 1739 ) 1740 .await; 1741 assert!( 1742 String::from_utf8(query) 1743 .expect("query response") 1744 .contains("query_limit_exceeded") 1745 ); 1746 1747 let response = exchange(&socket, b"GET /v1/large HTTP/1.1\r\nHost: local\r\n\r\n").await; 1748 let response = String::from_utf8(response).expect("bounded response"); 1749 assert!(response.contains("response_body_too_large")); 1750 assert!(response.len() < ADMIN_MIN_RESPONSE_BODY_UTF8_BYTES as usize + 256); 1751 assert_eq!(calls.load(Ordering::SeqCst), 1); 1752 1753 let body = r#"{"contract_version":1,"operation_id":"input-01","correlation_id":null,"request":{"value":true}}"#; 1754 let invalid_correlation = format!( 1755 "POST /v1/input HTTP/1.1\r\nHost: local\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{body}", 1756 body.len() 1757 ); 1758 let invalid_correlation = exchange(&socket, invalid_correlation.as_bytes()).await; 1759 assert!( 1760 String::from_utf8(invalid_correlation) 1761 .expect("correlation response") 1762 .contains("malformed_json") 1763 ); 1764 1765 cancellation.cancel(); 1766 task.await.expect("server task").expect("server shutdown"); 1767 } 1768 1769 #[tokio::test] 1770 async fn connection_admission_never_exceeds_the_configured_limit() { 1771 let directory = super::test_support::short_tempdir(); 1772 let (socket, binding) = binding(&directory).await; 1773 let entered = Arc::new(Notify::new()); 1774 let release = Arc::new(Notify::new()); 1775 let handler_entered = Arc::clone(&entered); 1776 let handler_release = Arc::clone(&release); 1777 let mut router = AdminRouter::new(); 1778 router 1779 .route(AdminHttpMethod::Get, "/v1/block", move |request| { 1780 let entered = Arc::clone(&handler_entered); 1781 let release = Arc::clone(&handler_release); 1782 async move { 1783 entered.notify_one(); 1784 release.notified().await; 1785 request 1786 .success(&serde_json::json!({"done": true})) 1787 .expect("blocking result") 1788 } 1789 }) 1790 .expect("blocking route"); 1791 let server = AdminServer::new( 1792 router, 1793 limits_with(1024, 1, std::time::Duration::from_secs(1)), 1794 FixedEntropy(10), 1795 ) 1796 .expect("admin server"); 1797 let cancellation = CancellationToken::new(); 1798 let task = tokio::spawn(server.serve(binding, cancellation.clone())); 1799 1800 let first_socket = socket.clone(); 1801 let first = tokio::spawn(async move { 1802 exchange( 1803 &first_socket, 1804 b"GET /v1/block HTTP/1.1\r\nHost: local\r\n\r\n", 1805 ) 1806 .await 1807 }); 1808 entered.notified().await; 1809 let second = tokio::time::timeout( 1810 std::time::Duration::from_millis(200), 1811 exchange_allowing_reset(&socket, b"GET /v1/block HTTP/1.1\r\nHost: local\r\n\r\n"), 1812 ) 1813 .await 1814 .expect("second connection must close without waiting"); 1815 assert!( 1816 second.is_empty(), 1817 "excess connection must receive no response" 1818 ); 1819 release.notify_one(); 1820 assert!( 1821 String::from_utf8(first.await.expect("first task")) 1822 .expect("first response") 1823 .starts_with("HTTP/1.1 200 OK") 1824 ); 1825 1826 cancellation.cancel(); 1827 task.await.expect("server task").expect("server shutdown"); 1828 } 1829 1830 #[tokio::test] 1831 async fn graceful_cancellation_stops_admission_and_drains_an_active_request() { 1832 let directory = super::test_support::short_tempdir(); 1833 let (socket, binding) = binding(&directory).await; 1834 let entered = Arc::new(Notify::new()); 1835 let release = Arc::new(Notify::new()); 1836 let handler_entered = Arc::clone(&entered); 1837 let handler_release = Arc::clone(&release); 1838 let mut router = AdminRouter::new(); 1839 router 1840 .route(AdminHttpMethod::Get, "/v1/drain", move |request| { 1841 let entered = Arc::clone(&handler_entered); 1842 let release = Arc::clone(&handler_release); 1843 async move { 1844 entered.notify_one(); 1845 release.notified().await; 1846 request.success(&true).expect("drained result") 1847 } 1848 }) 1849 .expect("drain route"); 1850 let server = AdminServer::new( 1851 router, 1852 limits_with(1024, 1, std::time::Duration::from_secs(1)), 1853 FixedEntropy(12), 1854 ) 1855 .expect("admin server"); 1856 let cancellation = CancellationToken::new(); 1857 let mut task = tokio::spawn(server.serve(binding, cancellation.clone())); 1858 let client_socket = socket.clone(); 1859 let client = tokio::spawn(async move { 1860 exchange( 1861 &client_socket, 1862 b"GET /v1/drain HTTP/1.1\r\nHost: local\r\n\r\n", 1863 ) 1864 .await 1865 }); 1866 1867 entered.notified().await; 1868 cancellation.cancel(); 1869 assert!( 1870 tokio::time::timeout(std::time::Duration::from_millis(20), &mut task) 1871 .await 1872 .is_err(), 1873 "graceful cancellation must retain active request work" 1874 ); 1875 release.notify_one(); 1876 assert!( 1877 String::from_utf8(client.await.expect("client task")) 1878 .expect("drained response") 1879 .starts_with("HTTP/1.1 200 OK") 1880 ); 1881 task.await.expect("server task").expect("server shutdown"); 1882 assert!(!socket.exists()); 1883 } 1884 1885 #[test] 1886 fn route_and_server_configuration_fail_closed() { 1887 assert!(matches!( 1888 AdminServer::new( 1889 AdminRouter::new(), 1890 AdminTransportLimits::DEFAULT, 1891 FixedEntropy(1), 1892 ), 1893 Err(AdminServerConfigError::NoRoutes) 1894 )); 1895 let mut router = AdminRouter::new(); 1896 assert!(matches!( 1897 router.route(AdminHttpMethod::Get, "/v2/status", |request| async move { 1898 request.success(&true).expect("bool") 1899 }), 1900 Err(AdminRouteRegistrationError::InvalidPath( 1901 AdminRoutePathError::WrongVersionPrefix 1902 )) 1903 )); 1904 router 1905 .route(AdminHttpMethod::Get, "/v1/status", |request| async move { 1906 request.success(&true).expect("bool") 1907 }) 1908 .expect("first route"); 1909 assert_eq!( 1910 router 1911 .route(AdminHttpMethod::Get, "/v1/status", |request| async move { 1912 request.success(&true).expect("bool") 1913 }) 1914 .expect_err("duplicate route"), 1915 AdminRouteRegistrationError::Duplicate 1916 ); 1917 1918 let mut values = AdminTransportLimits::DEFAULT.values(); 1919 values.response_body_utf8_bytes = ADMIN_MIN_RESPONSE_BODY_UTF8_BYTES - 1; 1920 let limits = AdminTransportLimits::new(values).expect("small response limit"); 1921 assert_eq!( 1922 AdminServer::new(router, limits, FixedEntropy(1)).expect_err("small response cap"), 1923 AdminServerConfigError::ResponseLimitTooSmall 1924 ); 1925 assert_eq!(ADMIN_MIN_RESPONSE_BODY_UTF8_BYTES, 512); 1926 let longest_correlation = AdminCorrelationId::new("x".repeat(128)).expect("correlation"); 1927 let fallback = AdminFailureResponse::new( 1928 longest_correlation, 1929 known_error( 1930 "response_body_too_large", 1931 "admin response body exceeds the limit", 1932 ), 1933 ); 1934 assert!(encode_bounded(&fallback, ADMIN_MIN_RESPONSE_BODY_UTF8_BYTES as usize).is_ok()); 1935 1936 let mut parameterized = AdminRouter::new(); 1937 parameterized 1938 .route( 1939 AdminHttpMethod::Get, 1940 "/v1/items/{item_id}", 1941 |request| async move { request.success(&true).expect("parameterized result") }, 1942 ) 1943 .expect("parameterized route"); 1944 parameterized 1945 .route( 1946 AdminHttpMethod::Get, 1947 "/v1/items/status", 1948 |request| async move { request.success(&true).expect("static result") }, 1949 ) 1950 .expect("more-specific static route"); 1951 assert_eq!( 1952 parameterized 1953 .route( 1954 AdminHttpMethod::Get, 1955 "/v1/items/{other_id}", 1956 |request| async move { request.success(&true).expect("ambiguous result") } 1957 ) 1958 .expect_err("ambiguous route"), 1959 AdminRouteRegistrationError::Ambiguous 1960 ); 1961 assert!(matches!( 1962 AdminRoutePath::new("/v1/items/{Bad}"), 1963 Err(AdminRoutePathError::InvalidParameter) 1964 )); 1965 } 1966 1967 #[test] 1968 fn capped_encoding_and_result_validation_fail_before_unbounded_response_allocation() { 1969 let oversized = "x".repeat(ADMIN_MIN_RESPONSE_BODY_UTF8_BYTES as usize + 1); 1970 assert!(matches!( 1971 encode_bounded(&oversized, ADMIN_MIN_RESPONSE_BODY_UTF8_BYTES as usize), 1972 Err(BoundedEncodingError::Limit) 1973 )); 1974 1975 let mut writer = CappedWriter::new(8); 1976 assert!(io::Write::write_all(&mut writer, b"123456789").is_err()); 1977 assert!(writer.exceeded); 1978 assert!(writer.bytes.len() <= 8); 1979 1980 let request = AdminRequest { 1981 method: AdminHttpMethod::Get, 1982 path: AdminRoutePath::new("/v1/test").expect("path"), 1983 query: None, 1984 correlation_id: AdminCorrelationId::new("safe-id").expect("correlation"), 1985 parameters: BTreeMap::new(), 1986 body: Bytes::new(), 1987 response_body_limit: ADMIN_MIN_RESPONSE_BODY_UTF8_BYTES as usize, 1988 }; 1989 assert!(matches!( 1990 request.success(&Option::<bool>::None), 1991 Err(AdminRouteOutcomeError::InvalidPayload) 1992 )); 1993 1994 let malformed = AdminRequest { 1995 body: Bytes::from_static(b"secret malformed JSON"), 1996 ..request 1997 } 1998 .decode_json::<bool>() 1999 .expect_err("malformed request must fail"); 2000 assert_eq!(malformed, AdminRequestDecodeError::Malformed); 2001 assert!(std::error::Error::source(&malformed).is_none()); 2002 assert!(!format!("{malformed:?}").contains("secret")); 2003 2004 let request = AdminRequest { 2005 method: AdminHttpMethod::Get, 2006 path: AdminRoutePath::new("/v1/test").expect("path"), 2007 query: None, 2008 correlation_id: AdminCorrelationId::new("safe-id").expect("correlation"), 2009 parameters: BTreeMap::new(), 2010 body: Bytes::new(), 2011 response_body_limit: ADMIN_MIN_RESPONSE_BODY_UTF8_BYTES as usize, 2012 }; 2013 let unsupported_json_map_key = BTreeMap::from([((1_u8, 2_u8), true)]); 2014 let encoding = request 2015 .success(&unsupported_json_map_key) 2016 .expect_err("tuple map key must not encode as JSON"); 2017 assert_eq!(encoding, AdminRouteOutcomeError::Encoding); 2018 assert!(std::error::Error::source(&encoding).is_none()); 2019 } 2020 2021 #[test] 2022 fn generated_correlation_is_exact_and_entropy_failure_is_safe() { 2023 let correlation = generated_correlation(&FixedEntropy(0xab)).expect("fixed entropy"); 2024 assert_eq!(correlation.as_str(), "abababababababababababababababab"); 2025 2026 let failure = generated_correlation(&FailingEntropy).expect_err("entropy failure"); 2027 assert_eq!(failure.code().as_str(), "correlation_unavailable"); 2028 assert_eq!(fallback_correlation().as_str(), FALLBACK_CORRELATION_ID); 2029 } 2030 2031 #[test] 2032 fn package_source_has_no_forbidden_listener_or_web_authority() { 2033 let source = include_str!("server.rs"); 2034 for forbidden in [ 2035 concat!("Tcp", "Listener"), 2036 concat!("Access-Control", "-Allow"), 2037 concat!("co", "rs"), 2038 concat!("browser", "_auth"), 2039 ] { 2040 assert!( 2041 !source.contains(forbidden), 2042 "forbidden server authority: {forbidden}" 2043 ); 2044 } 2045 } 2046 2047 #[test] 2048 fn request_and_outcome_debug_redact_body_query_and_result() { 2049 let request = AdminRequest { 2050 method: AdminHttpMethod::Post, 2051 path: AdminRoutePath::new("/v1/test").expect("path"), 2052 query: Some("secret=query".to_owned()), 2053 correlation_id: AdminCorrelationId::new("safe-id").expect("correlation"), 2054 parameters: BTreeMap::from([("item_id".to_owned(), "secret-item".to_owned())]), 2055 body: Bytes::from_static(b"{\"secret\":true}"), 2056 response_body_limit: 1024, 2057 }; 2058 let debug = format!("{request:?}"); 2059 assert!(!debug.contains("secret=query")); 2060 assert!(!debug.contains("secret\":true")); 2061 assert!(!debug.contains("secret-item")); 2062 let outcome = request 2063 .success(&serde_json::json!({"secret": true})) 2064 .expect("outcome"); 2065 assert!(!format!("{outcome:?}").contains("secret")); 2066 } 2067 2068 #[test] 2069 fn strict_json_routes_accessors_and_safe_errors_cover_the_full_value_surface() { 2070 assert!(valid_parameter_name("item_1")); 2071 for rejected in [ 2072 "", 2073 "1item", 2074 "Item", 2075 "item-name", 2076 &"x".repeat(ADMIN_ROUTE_PARAMETER_NAME_MAX_UTF8_BYTES + 1), 2077 ] { 2078 assert!(!valid_parameter_name(rejected)); 2079 } 2080 assert_eq!(decode_route_parameter(""), None); 2081 assert_eq!(decode_route_parameter("plain"), Some("plain".to_owned())); 2082 2083 for path in ["/", "/v", "/v1", "/version2", "/v2beta"] { 2084 assert!(!unknown_major_version(path), "{path}"); 2085 } 2086 for path in ["/v0", "/v2", "/v99/status"] { 2087 assert!(unknown_major_version(path), "{path}"); 2088 } 2089 assert_eq!(query_item_count(None), 0); 2090 assert_eq!(query_item_count(Some("")), 0); 2091 assert_eq!(query_item_count(Some("one")), 1); 2092 assert_eq!(query_item_count(Some("one&two")), 2); 2093 2094 let mut headers = HeaderMap::new(); 2095 assert!(!is_json_content_type(&headers)); 2096 headers.insert(CONTENT_TYPE, HeaderValue::from_static(JSON_CONTENT_TYPE)); 2097 assert!(is_json_content_type(&headers)); 2098 headers.insert( 2099 CONTENT_TYPE, 2100 HeaderValue::from_static("application/json; charset=utf-8"), 2101 ); 2102 assert!(is_json_content_type(&headers)); 2103 headers.insert(CONTENT_TYPE, HeaderValue::from_static("text/plain")); 2104 assert!(!is_json_content_type(&headers)); 2105 2106 for document in [ 2107 "true", 2108 "-7", 2109 "9", 2110 "1.5", 2111 r#""text""#, 2112 "[true,2]", 2113 r#"{"value":3}"#, 2114 ] { 2115 serde_json::from_str::<StrictJsonPayload>(document).unwrap(); 2116 } 2117 for rejected in ["null", "[1,null]", r#"{"same":1,"same":2}"#] { 2118 assert!(serde_json::from_str::<StrictJsonPayload>(rejected).is_err()); 2119 } 2120 2121 let route = AdminRoutePath::new("/v1/items/{item_id}").unwrap(); 2122 assert_eq!(route.as_str(), "/v1/items/{item_id}"); 2123 assert!(route.overlaps(&AdminRoutePath::new("/v1/items/static").unwrap())); 2124 assert!(!route.overlaps(&AdminRoutePath::new("/v1/other/static").unwrap())); 2125 assert!(!route.overlaps(&AdminRoutePath::new("/v1/items/static/more").unwrap())); 2126 assert_eq!( 2127 route.match_path("/v1/items/value%2D1").unwrap(), 2128 BTreeMap::from([("item_id".to_owned(), "value-1".to_owned())]) 2129 ); 2130 for rejected in [ 2131 "v1/items/value", 2132 "/v1/items/value/", 2133 "/v1//items/value", 2134 "/v1/items", 2135 "/v1/other/value", 2136 "/v1/items/%", 2137 "/v1/items/%GG", 2138 "/v1/items/%2F", 2139 "/v1/items/%5c", 2140 "/v1/items/%00", 2141 "/v1/items/%0A", 2142 "/v1/items/%FF", 2143 ] { 2144 assert!(route.match_path(rejected).is_none(), "{rejected}"); 2145 } 2146 assert!( 2147 route 2148 .match_path(&format!( 2149 "/v1/items/{}", 2150 "x".repeat(ADMIN_ROUTE_PARAMETER_VALUE_MAX_UTF8_BYTES + 1) 2151 )) 2152 .is_none() 2153 ); 2154 2155 let invalid_paths = [ 2156 ("", AdminRoutePathError::Empty), 2157 ("/v2/items", AdminRoutePathError::WrongVersionPrefix), 2158 ("/v1/items/", AdminRoutePathError::WrongVersionPrefix), 2159 ("/v1//items", AdminRoutePathError::EmptySegment), 2160 ("/v1/Items", AdminRoutePathError::InvalidCharacter), 2161 ("/v1/{Bad}", AdminRoutePathError::InvalidParameter), 2162 ("/v1/{item}/{item}", AdminRoutePathError::DuplicateParameter), 2163 ]; 2164 for (path, expected) in invalid_paths { 2165 assert_eq!(AdminRoutePath::new(path).unwrap_err(), expected); 2166 } 2167 let exact_maximum = format!( 2168 "/v1/{}", 2169 "x".repeat(ADMIN_ROUTE_PATH_MAX_UTF8_BYTES - "/v1/".len()) 2170 ); 2171 assert_eq!( 2172 AdminRoutePath::new(&exact_maximum) 2173 .expect("exact maximum route") 2174 .as_str(), 2175 exact_maximum 2176 ); 2177 assert_eq!( 2178 AdminRoutePath::new(format!( 2179 "/v1/{}", 2180 "x".repeat(ADMIN_ROUTE_PATH_MAX_UTF8_BYTES) 2181 )) 2182 .unwrap_err(), 2183 AdminRoutePathError::TooLong 2184 ); 2185 assert_eq!( 2186 AdminRoutePath::new(format!("/v1/{}", "x".repeat(4 * 1024 * 1024))), 2187 Err(AdminRoutePathError::TooLong) 2188 ); 2189 2190 assert_eq!( 2191 AdminHttpMethod::from_http(&Method::GET), 2192 Some(AdminHttpMethod::Get) 2193 ); 2194 assert_eq!( 2195 AdminHttpMethod::from_http(&Method::POST), 2196 Some(AdminHttpMethod::Post) 2197 ); 2198 assert_eq!(AdminHttpMethod::from_http(&Method::DELETE), None); 2199 2200 let request = AdminRequest { 2201 method: AdminHttpMethod::Post, 2202 path: route, 2203 query: Some("page=1".to_owned()), 2204 correlation_id: AdminCorrelationId::new("correlation-1").unwrap(), 2205 parameters: BTreeMap::from([("item_id".to_owned(), "item-1".to_owned())]), 2206 body: Bytes::from_static(b"true"), 2207 response_body_limit: 1024, 2208 }; 2209 assert_eq!(request.method(), AdminHttpMethod::Post); 2210 assert_eq!(request.path().as_str(), "/v1/items/{item_id}"); 2211 assert_eq!(request.query(), Some("page=1")); 2212 assert_eq!(request.correlation_id().as_str(), "correlation-1"); 2213 assert_eq!(request.parameter("item_id"), Some("item-1")); 2214 assert_eq!(request.parameter("missing"), None); 2215 assert_eq!(request.body(), b"true"); 2216 assert!(request.decode_json::<bool>().unwrap()); 2217 let empty = AdminRequest { 2218 body: Bytes::new(), 2219 ..request 2220 }; 2221 assert_eq!( 2222 empty.decode_json::<bool>().unwrap_err(), 2223 AdminRequestDecodeError::Empty 2224 ); 2225 2226 let error = known_error("stable_error", "stable message"); 2227 for status in [ 2228 AdminRouteFailureStatus::BadRequest, 2229 AdminRouteFailureStatus::NotFound, 2230 AdminRouteFailureStatus::Conflict, 2231 AdminRouteFailureStatus::Unavailable, 2232 AdminRouteFailureStatus::Internal, 2233 ] { 2234 assert!( 2235 status.http_status().is_client_error() || status.http_status().is_server_error() 2236 ); 2237 let failure = AdminRouteFailure::new(status, error.clone()); 2238 assert_eq!(failure.status(), status); 2239 assert_eq!(failure.error(), &error); 2240 assert!(format!("{:?}", AdminRouteOutcome::failure(failure)).contains("Failure")); 2241 } 2242 for rendered in [ 2243 AdminRoutePathError::Empty.to_string(), 2244 AdminRouteRegistrationError::Duplicate.to_string(), 2245 AdminRequestDecodeError::Malformed.to_string(), 2246 AdminRouteOutcomeError::Encoding.to_string(), 2247 AdminRouteOutcomeError::InvalidPayload.to_string(), 2248 AdminRouteOutcomeError::ResponseLimit.to_string(), 2249 ] { 2250 assert!(!rendered.is_empty()); 2251 } 2252 2253 use std::io::Write as _; 2254 let mut writer = CappedWriter::new(4); 2255 writer.flush().unwrap(); 2256 assert_eq!(writer.write(b"four").unwrap(), 4); 2257 assert!(writer.write(b"x").is_err()); 2258 } 2259 }