1use std::collections::HashSet;
10use std::sync::Arc;
11use std::sync::atomic::{AtomicUsize, Ordering};
12use std::time::{Duration, Instant};
13
14use parking_lot::Mutex;
15use serde::{Deserialize, Serialize};
16
17pub use turbomcp_core::SUPPORTED_VERSIONS as SUPPORTED_PROTOCOL_VERSIONS;
19pub use turbomcp_types::ProtocolVersion;
20
21pub const DEFAULT_MAX_CONNECTIONS: usize = 1000;
23
24pub const DEFAULT_RATE_LIMIT: u32 = 100;
26
27pub const DEFAULT_RATE_LIMIT_WINDOW: Duration = Duration::from_secs(1);
29
30pub const DEFAULT_MAX_MESSAGE_SIZE: usize = 10 * 1024 * 1024;
32
33pub const DEFAULT_HTTP_SESSION_IDLE_TIMEOUT: Duration = Duration::from_secs(60 * 60);
35
36pub const DEFAULT_MAX_HTTP_SESSIONS: usize = 10_000;
38
39#[derive(Debug, Clone)]
41pub struct OriginValidationConfig {
42 pub allowed_origins: HashSet<String>,
44 pub allow_localhost: bool,
46 pub allow_any: bool,
48 pub trusted_proxies: Vec<String>,
54 pub allow_missing_origin: bool,
62 pub cors: bool,
68}
69
70impl Default for OriginValidationConfig {
71 fn default() -> Self {
72 Self {
73 allowed_origins: HashSet::new(),
74 allow_localhost: true,
75 allow_any: false,
76 trusted_proxies: Vec::new(),
77 allow_missing_origin: false,
78 cors: false,
79 }
80 }
81}
82
83impl OriginValidationConfig {
84 #[must_use]
86 pub fn new() -> Self {
87 Self::default()
88 }
89}
90
91#[derive(Debug, Clone, Copy)]
99pub struct HttpSessionConfig {
100 pub idle_timeout: Duration,
106 pub max_sessions: usize,
109}
110
111impl Default for HttpSessionConfig {
112 fn default() -> Self {
113 Self {
114 idle_timeout: DEFAULT_HTTP_SESSION_IDLE_TIMEOUT,
115 max_sessions: DEFAULT_MAX_HTTP_SESSIONS,
116 }
117 }
118}
119
120#[derive(Debug, Clone)]
122pub struct ServerConfig {
123 pub protocol: ProtocolConfig,
125 pub rate_limit: Option<RateLimitConfig>,
127 pub connection_limits: ConnectionLimits,
129 pub required_capabilities: RequiredCapabilities,
131 pub max_message_size: usize,
133 pub origin_validation: OriginValidationConfig,
135 pub http_sessions: HttpSessionConfig,
137 #[cfg(feature = "http")]
140 pub authorization: Option<crate::HttpAuthorization>,
141}
142
143impl Default for ServerConfig {
144 fn default() -> Self {
145 Self {
146 protocol: ProtocolConfig::default(),
147 rate_limit: None,
148 connection_limits: ConnectionLimits::default(),
149 required_capabilities: RequiredCapabilities::default(),
150 max_message_size: DEFAULT_MAX_MESSAGE_SIZE,
151 origin_validation: OriginValidationConfig::default(),
152 http_sessions: HttpSessionConfig::default(),
153 #[cfg(feature = "http")]
154 authorization: None,
155 }
156 }
157}
158
159impl ServerConfig {
160 #[must_use]
162 pub fn new() -> Self {
163 Self::default()
164 }
165
166 #[must_use]
168 pub fn builder() -> ServerConfigBuilder {
169 ServerConfigBuilder::default()
170 }
171}
172
173#[derive(Debug, Clone, Default)]
175pub struct ServerConfigBuilder {
176 protocol: Option<ProtocolConfig>,
177 rate_limit: Option<RateLimitConfig>,
178 connection_limits: Option<ConnectionLimits>,
179 required_capabilities: Option<RequiredCapabilities>,
180 max_message_size: Option<usize>,
181 origin_validation: Option<OriginValidationConfig>,
182 http_sessions: Option<HttpSessionConfig>,
183 #[cfg(feature = "http")]
184 authorization: Option<crate::HttpAuthorization>,
185}
186
187impl ServerConfigBuilder {
188 #[must_use]
190 pub fn protocol(mut self, config: ProtocolConfig) -> Self {
191 self.protocol = Some(config);
192 self
193 }
194
195 #[must_use]
197 pub fn rate_limit(mut self, config: RateLimitConfig) -> Self {
198 self.rate_limit = Some(config);
199 self
200 }
201
202 #[must_use]
204 pub fn connection_limits(mut self, limits: ConnectionLimits) -> Self {
205 self.connection_limits = Some(limits);
206 self
207 }
208
209 #[must_use]
211 pub fn required_capabilities(mut self, caps: RequiredCapabilities) -> Self {
212 self.required_capabilities = Some(caps);
213 self
214 }
215
216 #[must_use]
221 pub fn max_message_size(mut self, size: usize) -> Self {
222 self.max_message_size = Some(size);
223 self
224 }
225
226 #[must_use]
228 pub fn origin_validation(mut self, config: OriginValidationConfig) -> Self {
229 self.origin_validation = Some(config);
230 self
231 }
232
233 #[must_use]
235 pub fn allow_origin(mut self, origin: impl Into<String>) -> Self {
236 self.origin_validation
237 .get_or_insert_with(OriginValidationConfig::default)
238 .allowed_origins
239 .insert(origin.into());
240 self
241 }
242
243 #[must_use]
245 pub fn allow_origins<I, S>(mut self, origins: I) -> Self
246 where
247 I: IntoIterator<Item = S>,
248 S: Into<String>,
249 {
250 let config = self
251 .origin_validation
252 .get_or_insert_with(OriginValidationConfig::default);
253 config
254 .allowed_origins
255 .extend(origins.into_iter().map(Into::into));
256 self
257 }
258
259 #[must_use]
261 pub fn allow_localhost_origins(mut self, allow: bool) -> Self {
262 self.origin_validation
263 .get_or_insert_with(OriginValidationConfig::default)
264 .allow_localhost = allow;
265 self
266 }
267
268 #[must_use]
270 pub fn allow_any_origin(mut self, allow: bool) -> Self {
271 self.origin_validation
272 .get_or_insert_with(OriginValidationConfig::default)
273 .allow_any = allow;
274 self
275 }
276
277 #[must_use]
282 pub fn allow_missing_origin(mut self, allow: bool) -> Self {
283 self.origin_validation
284 .get_or_insert_with(OriginValidationConfig::default)
285 .allow_missing_origin = allow;
286 self
287 }
288
289 #[must_use]
293 pub fn cors(mut self, enabled: bool) -> Self {
294 self.origin_validation
295 .get_or_insert_with(OriginValidationConfig::default)
296 .cors = enabled;
297 self
298 }
299
300 #[cfg(feature = "http")]
306 #[must_use]
307 pub fn authorization(mut self, authorization: crate::HttpAuthorization) -> Self {
308 self.authorization = Some(authorization);
309 self
310 }
311
312 #[must_use]
314 pub fn http_sessions(mut self, config: HttpSessionConfig) -> Self {
315 self.http_sessions = Some(config);
316 self
317 }
318
319 #[must_use]
323 pub fn http_session_idle_timeout(mut self, timeout: Duration) -> Self {
324 self.http_sessions
325 .get_or_insert_with(HttpSessionConfig::default)
326 .idle_timeout = timeout;
327 self
328 }
329
330 #[must_use]
334 pub fn max_http_sessions(mut self, max: usize) -> Self {
335 self.http_sessions
336 .get_or_insert_with(HttpSessionConfig::default)
337 .max_sessions = max;
338 self
339 }
340
341 #[must_use]
346 pub fn build(self) -> ServerConfig {
347 ServerConfig {
348 protocol: self.protocol.unwrap_or_default(),
349 rate_limit: self.rate_limit,
350 connection_limits: self.connection_limits.unwrap_or_default(),
351 required_capabilities: self.required_capabilities.unwrap_or_default(),
352 max_message_size: self.max_message_size.unwrap_or(DEFAULT_MAX_MESSAGE_SIZE),
353 origin_validation: self.origin_validation.unwrap_or_default(),
354 http_sessions: self.http_sessions.unwrap_or_default(),
355 #[cfg(feature = "http")]
356 authorization: self.authorization,
357 }
358 }
359
360 pub fn try_build(self) -> Result<ServerConfig, ConfigValidationError> {
387 let max_message_size = self.max_message_size.unwrap_or(DEFAULT_MAX_MESSAGE_SIZE);
388
389 if max_message_size < 1024 {
391 return Err(ConfigValidationError::InvalidMessageSize {
392 size: max_message_size,
393 min: 1024,
394 });
395 }
396
397 if let Some(ref rate_limit) = self.rate_limit {
399 if rate_limit.max_requests == 0 {
400 return Err(ConfigValidationError::InvalidRateLimit {
401 reason: "max_requests cannot be 0".to_string(),
402 });
403 }
404 if rate_limit.window.is_zero() {
405 return Err(ConfigValidationError::InvalidRateLimit {
406 reason: "rate limit window cannot be zero".to_string(),
407 });
408 }
409 }
410
411 let connection_limits = self.connection_limits.unwrap_or_default();
413 if connection_limits.max_tcp_connections == 0
414 && connection_limits.max_websocket_connections == 0
415 && connection_limits.max_http_concurrent == 0
416 && connection_limits.max_unix_connections == 0
417 {
418 return Err(ConfigValidationError::InvalidConnectionLimits {
419 reason: "at least one connection limit must be non-zero".to_string(),
420 });
421 }
422
423 let http_sessions = self.http_sessions.unwrap_or_default();
424 if http_sessions.max_sessions == 0 {
425 return Err(ConfigValidationError::InvalidConnectionLimits {
426 reason: "max_http_sessions cannot be 0".to_string(),
427 });
428 }
429
430 Ok(ServerConfig {
431 protocol: self.protocol.unwrap_or_default(),
432 rate_limit: self.rate_limit,
433 connection_limits,
434 required_capabilities: self.required_capabilities.unwrap_or_default(),
435 max_message_size,
436 origin_validation: self.origin_validation.unwrap_or_default(),
437 http_sessions,
438 #[cfg(feature = "http")]
439 authorization: self.authorization,
440 })
441 }
442}
443
444impl From<ServerConfig> for ServerConfigBuilder {
450 fn from(config: ServerConfig) -> Self {
451 let ServerConfig {
452 protocol,
453 rate_limit,
454 connection_limits,
455 required_capabilities,
456 max_message_size,
457 origin_validation,
458 http_sessions,
459 #[cfg(feature = "http")]
460 authorization,
461 } = config;
462 Self {
463 protocol: Some(protocol),
464 rate_limit,
465 connection_limits: Some(connection_limits),
466 required_capabilities: Some(required_capabilities),
467 max_message_size: Some(max_message_size),
468 origin_validation: Some(origin_validation),
469 http_sessions: Some(http_sessions),
470 #[cfg(feature = "http")]
471 authorization,
472 }
473 }
474}
475
476#[derive(Debug, Clone, thiserror::Error)]
478pub enum ConfigValidationError {
479 #[error("Invalid max_message_size: {size} bytes is below minimum of {min} bytes")]
481 InvalidMessageSize {
482 size: usize,
484 min: usize,
486 },
487
488 #[error("Invalid rate limit: {reason}")]
490 InvalidRateLimit {
491 reason: String,
493 },
494
495 #[error("Invalid connection limits: {reason}")]
497 InvalidConnectionLimits {
498 reason: String,
500 },
501}
502
503#[derive(Debug, Clone)]
505pub struct ProtocolConfig {
506 pub preferred_version: ProtocolVersion,
508 pub supported_versions: Vec<ProtocolVersion>,
510 pub allow_fallback: bool,
515}
516
517impl Default for ProtocolConfig {
518 fn default() -> Self {
534 Self {
535 preferred_version: ProtocolVersion::LATEST.clone(),
536 supported_versions: ProtocolVersion::STABLE.to_vec(),
537 allow_fallback: true,
538 }
539 }
540}
541
542impl ProtocolConfig {
543 #[must_use]
550 pub fn strict(version: impl Into<ProtocolVersion>) -> Self {
551 let v = version.into();
552 Self {
553 preferred_version: v.clone(),
554 supported_versions: vec![v],
555 allow_fallback: true,
556 }
557 }
558
559 #[must_use]
566 pub fn multi_version() -> Self {
567 Self {
568 preferred_version: ProtocolVersion::LATEST.clone(),
569 supported_versions: ProtocolVersion::STABLE.to_vec(),
570 allow_fallback: true,
571 }
572 }
573
574 #[must_use]
576 pub fn is_supported(&self, version: &ProtocolVersion) -> bool {
577 self.supported_versions.contains(version)
578 }
579
580 #[must_use]
584 pub fn negotiate(&self, client_version: Option<&str>) -> Option<ProtocolVersion> {
585 match client_version {
586 Some(version_str) => {
587 let version = ProtocolVersion::from(version_str);
588 if self.is_supported(&version) {
589 Some(version)
590 } else if self.allow_fallback {
591 Some(self.preferred_version.clone())
592 } else {
593 None
594 }
595 }
596 None => Some(self.preferred_version.clone()),
597 }
598 }
599}
600
601#[derive(Debug, Clone)]
603pub struct RateLimitConfig {
604 pub max_requests: u32,
606 pub window: Duration,
608 pub per_client: bool,
610}
611
612impl Default for RateLimitConfig {
613 fn default() -> Self {
614 Self {
615 max_requests: DEFAULT_RATE_LIMIT,
616 window: DEFAULT_RATE_LIMIT_WINDOW,
617 per_client: true,
618 }
619 }
620}
621
622impl RateLimitConfig {
623 #[must_use]
625 pub fn new(max_requests: u32, window: Duration) -> Self {
626 Self {
627 max_requests,
628 window,
629 per_client: true,
630 }
631 }
632
633 #[must_use]
635 pub fn per_client(mut self, enabled: bool) -> Self {
636 self.per_client = enabled;
637 self
638 }
639}
640
641#[derive(Debug, Clone)]
643pub struct ConnectionLimits {
644 pub max_tcp_connections: usize,
646 pub max_websocket_connections: usize,
648 pub max_http_concurrent: usize,
650 pub max_unix_connections: usize,
652}
653
654impl Default for ConnectionLimits {
655 fn default() -> Self {
656 Self {
657 max_tcp_connections: DEFAULT_MAX_CONNECTIONS,
658 max_websocket_connections: DEFAULT_MAX_CONNECTIONS,
659 max_http_concurrent: DEFAULT_MAX_CONNECTIONS,
660 max_unix_connections: DEFAULT_MAX_CONNECTIONS,
661 }
662 }
663}
664
665impl ConnectionLimits {
666 #[must_use]
668 pub fn new(max_connections: usize) -> Self {
669 Self {
670 max_tcp_connections: max_connections,
671 max_websocket_connections: max_connections,
672 max_http_concurrent: max_connections,
673 max_unix_connections: max_connections,
674 }
675 }
676}
677
678#[derive(Debug, Clone, Default, Serialize, Deserialize)]
682pub struct RequiredCapabilities {
683 #[serde(default)]
685 pub roots: bool,
686 #[serde(default)]
688 pub sampling: bool,
689 #[serde(default)]
691 pub extensions: HashSet<String>,
692 #[serde(default)]
694 pub experimental: HashSet<String>,
695}
696
697impl RequiredCapabilities {
698 #[must_use]
700 pub fn none() -> Self {
701 Self::default()
702 }
703
704 #[must_use]
706 pub fn with_roots(mut self) -> Self {
707 self.roots = true;
708 self
709 }
710
711 #[must_use]
713 pub fn with_sampling(mut self) -> Self {
714 self.sampling = true;
715 self
716 }
717
718 #[must_use]
720 pub fn with_extension(mut self, name: impl Into<String>) -> Self {
721 self.extensions.insert(name.into());
722 self
723 }
724
725 #[must_use]
727 pub fn with_experimental(mut self, name: impl Into<String>) -> Self {
728 self.experimental.insert(name.into());
729 self
730 }
731
732 #[must_use]
734 pub fn validate(&self, client_caps: &ClientCapabilities) -> CapabilityValidation {
735 let mut missing = Vec::new();
736
737 if self.roots && !client_caps.roots {
738 missing.push("roots".to_string());
739 }
740
741 if self.sampling && !client_caps.sampling {
742 missing.push("sampling".to_string());
743 }
744
745 for extension in &self.extensions {
746 if !client_caps.extensions.contains(extension) {
747 missing.push(format!("extensions/{}", extension));
748 }
749 }
750
751 for exp in &self.experimental {
752 if !client_caps.experimental.contains(exp) {
753 missing.push(format!("experimental/{}", exp));
754 }
755 }
756
757 if missing.is_empty() {
758 CapabilityValidation::Valid
759 } else {
760 CapabilityValidation::Missing(missing)
761 }
762 }
763}
764
765#[derive(Debug, Clone, Default, Serialize, Deserialize)]
767pub struct ClientCapabilities {
768 #[serde(default)]
770 pub roots: bool,
771 #[serde(default)]
773 pub sampling: bool,
774 #[serde(default)]
776 pub extensions: HashSet<String>,
777 #[serde(default)]
779 pub experimental: HashSet<String>,
780}
781
782impl ClientCapabilities {
783 #[must_use]
785 pub fn from_params(params: &serde_json::Value) -> Self {
786 let caps = params.get("capabilities").cloned().unwrap_or_default();
787
788 Self {
789 roots: caps.get("roots").map(|v| !v.is_null()).unwrap_or(false),
790 sampling: caps.get("sampling").map(|v| !v.is_null()).unwrap_or(false),
791 extensions: caps
792 .get("extensions")
793 .and_then(|v| v.as_object())
794 .map(|obj| obj.keys().cloned().collect())
795 .unwrap_or_default(),
796 experimental: caps
797 .get("experimental")
798 .and_then(|v| v.as_object())
799 .map(|obj| obj.keys().cloned().collect())
800 .unwrap_or_default(),
801 }
802 }
803}
804
805#[derive(Debug, Clone)]
807pub enum CapabilityValidation {
808 Valid,
810 Missing(Vec<String>),
812}
813
814impl CapabilityValidation {
815 #[must_use]
817 pub fn is_valid(&self) -> bool {
818 matches!(self, Self::Valid)
819 }
820
821 #[must_use]
823 pub fn missing(&self) -> Option<&[String]> {
824 match self {
825 Self::Valid => None,
826 Self::Missing(caps) => Some(caps),
827 }
828 }
829}
830
831#[derive(Debug)]
833pub struct RateLimiter {
834 config: RateLimitConfig,
835 global_bucket: Mutex<TokenBucket>,
837 client_buckets: Mutex<std::collections::HashMap<String, TokenBucket>>,
839 last_cleanup: Mutex<Instant>,
841}
842
843impl RateLimiter {
844 #[must_use]
846 pub fn new(config: RateLimitConfig) -> Self {
847 Self {
848 global_bucket: Mutex::new(TokenBucket::new(config.max_requests, config.window)),
849 client_buckets: Mutex::new(std::collections::HashMap::new()),
850 last_cleanup: Mutex::new(Instant::now()),
851 config,
852 }
853 }
854
855 pub fn check(&self, client_id: Option<&str>) -> bool {
859 let needs_cleanup = {
861 let last = self.last_cleanup.lock();
862 last.elapsed() > Duration::from_secs(60)
863 };
864 if needs_cleanup {
865 self.cleanup(Duration::from_secs(300));
866 *self.last_cleanup.lock() = Instant::now();
867 }
868
869 if self.config.per_client {
870 if let Some(id) = client_id {
871 let mut buckets = self.client_buckets.lock();
872 let bucket = buckets.entry(id.to_string()).or_insert_with(|| {
873 TokenBucket::new(self.config.max_requests, self.config.window)
874 });
875 bucket.try_acquire()
876 } else {
877 self.global_bucket.lock().try_acquire()
879 }
880 } else {
881 self.global_bucket.lock().try_acquire()
882 }
883 }
884
885 pub fn cleanup(&self, max_age: Duration) {
887 let mut buckets = self.client_buckets.lock();
888 let now = Instant::now();
889 buckets.retain(|_, bucket| now.duration_since(bucket.last_access) < max_age);
890 }
891
892 #[must_use]
894 pub fn client_bucket_count(&self) -> usize {
895 self.client_buckets.lock().len()
896 }
897}
898
899#[derive(Debug)]
901struct TokenBucket {
902 tokens: f64,
903 max_tokens: f64,
904 refill_rate: f64, last_refill: Instant,
906 last_access: Instant,
907}
908
909impl TokenBucket {
910 fn new(max_requests: u32, window: Duration) -> Self {
911 let max_tokens = max_requests as f64;
912 let refill_rate = max_tokens / window.as_secs_f64();
913 Self {
914 tokens: max_tokens,
915 max_tokens,
916 refill_rate,
917 last_refill: Instant::now(),
918 last_access: Instant::now(),
919 }
920 }
921
922 fn try_acquire(&mut self) -> bool {
923 let now = Instant::now();
924 let elapsed = now.duration_since(self.last_refill);
925
926 if elapsed >= Duration::from_millis(10) {
928 self.tokens =
929 (self.tokens + elapsed.as_secs_f64() * self.refill_rate).min(self.max_tokens);
930 self.last_refill = now;
931 }
932
933 self.last_access = now;
934
935 if self.tokens >= 1.0 {
936 self.tokens -= 1.0;
937 true
938 } else {
939 false
940 }
941 }
942}
943
944#[derive(Debug)]
949pub struct ConnectionCounter {
950 current: AtomicUsize,
951 max: usize,
952}
953
954impl ConnectionCounter {
955 #[must_use]
957 pub fn new(max: usize) -> Self {
958 Self {
959 current: AtomicUsize::new(0),
960 max,
961 }
962 }
963
964 pub fn try_acquire_arc(self: &Arc<Self>) -> Option<ConnectionGuard> {
974 loop {
975 let current = self.current.load(Ordering::Relaxed);
976 if current >= self.max {
977 return None;
978 }
979 if self
980 .current
981 .compare_exchange(current, current + 1, Ordering::SeqCst, Ordering::Relaxed)
982 .is_ok()
983 {
984 return Some(ConnectionGuard {
985 counter: Arc::clone(self),
986 });
987 }
988 std::hint::spin_loop();
990 }
991 }
992
993 #[must_use]
995 pub fn current(&self) -> usize {
996 self.current.load(Ordering::Relaxed)
997 }
998
999 #[must_use]
1001 pub fn max(&self) -> usize {
1002 self.max
1003 }
1004
1005 fn release(&self) {
1006 self.current.fetch_sub(1, Ordering::SeqCst);
1007 }
1008}
1009
1010#[derive(Debug)]
1014pub struct ConnectionGuard {
1015 counter: Arc<ConnectionCounter>,
1016}
1017
1018impl Drop for ConnectionGuard {
1019 fn drop(&mut self) {
1020 self.counter.release();
1021 }
1022}
1023
1024#[cfg(test)]
1025mod tests {
1026 use super::*;
1027
1028 #[test]
1029 fn test_protocol_negotiation_exact_match() {
1030 let config = ProtocolConfig::default();
1031 assert_eq!(
1032 config.negotiate(Some("2025-11-25")),
1033 Some(ProtocolVersion::V2025_11_25)
1034 );
1035 }
1036
1037 #[test]
1038 fn test_protocol_negotiation_default_accepts_stable_versions() {
1039 let config = ProtocolConfig::default();
1043 assert_eq!(
1044 config.negotiate(Some("2025-06-18")),
1045 Some(ProtocolVersion::V2025_06_18)
1046 );
1047 }
1048
1049 #[test]
1053 fn test_protocol_negotiation_strict_offers_its_version() {
1054 let config = ProtocolConfig::strict(ProtocolVersion::LATEST.clone());
1055 assert_eq!(
1056 config.negotiate(Some("2025-06-18")),
1057 Some(ProtocolVersion::LATEST)
1058 );
1059 }
1060
1061 #[test]
1062 fn test_protocol_negotiation_multi_version_accepts_older() {
1063 let config = ProtocolConfig::multi_version();
1064 assert_eq!(
1065 config.negotiate(Some("2025-06-18")),
1066 Some(ProtocolVersion::V2025_06_18)
1067 );
1068 assert_eq!(
1069 config.negotiate(Some("2025-11-25")),
1070 Some(ProtocolVersion::V2025_11_25)
1071 );
1072 }
1073
1074 #[test]
1075 fn test_protocol_negotiation_none_returns_preferred() {
1076 let config = ProtocolConfig::default();
1077 assert_eq!(config.negotiate(None), Some(ProtocolVersion::V2025_11_25));
1078 }
1079
1080 #[test]
1084 fn test_protocol_negotiation_unknown_version_falls_back() {
1085 let config = ProtocolConfig::default();
1086 assert_eq!(
1087 config.negotiate(Some("unknown-version")),
1088 Some(ProtocolVersion::V2025_11_25)
1089 );
1090 }
1091
1092 #[test]
1095 fn test_protocol_negotiation_retired_versions_fall_back() {
1096 let config = ProtocolConfig::default();
1097 for retired in ["2024-11-05", "2025-03-26"] {
1098 assert_eq!(
1099 config.negotiate(Some(retired)),
1100 Some(ProtocolVersion::V2025_11_25),
1101 "{retired} should be offered the latest supported version"
1102 );
1103 }
1104 }
1105
1106 #[test]
1109 fn test_protocol_negotiation_strict() {
1110 let config = ProtocolConfig::strict("2025-11-25");
1111 assert!(!config.is_supported(&ProtocolVersion::V2025_06_18));
1112
1113 let refusing = ProtocolConfig {
1114 allow_fallback: false,
1115 ..config
1116 };
1117 assert_eq!(refusing.negotiate(Some("2025-06-18")), None);
1118 }
1119
1120 #[test]
1121 fn test_capability_validation() {
1122 let required = RequiredCapabilities::none().with_roots();
1123 let client = ClientCapabilities {
1124 roots: true,
1125 ..Default::default()
1126 };
1127 assert!(required.validate(&client).is_valid());
1128
1129 let client_missing = ClientCapabilities::default();
1130 assert!(!required.validate(&client_missing).is_valid());
1131 }
1132
1133 #[test]
1134 fn test_extension_capability_validation() {
1135 let required = RequiredCapabilities::none().with_extension("trace");
1136 let client = ClientCapabilities {
1137 extensions: ["trace".to_string()].into_iter().collect(),
1138 ..Default::default()
1139 };
1140 assert!(required.validate(&client).is_valid());
1141
1142 let missing = ClientCapabilities::default();
1143 let validation = required.validate(&missing);
1144 assert!(!validation.is_valid());
1145 assert_eq!(
1146 validation.missing(),
1147 Some(&["extensions/trace".to_string()][..])
1148 );
1149 }
1150
1151 #[test]
1152 fn test_client_capabilities_parse_extensions() {
1153 let params = serde_json::json!({
1154 "capabilities": {
1155 "extensions": {
1156 "trace": {"version": "1"},
1157 "handoff": {}
1158 }
1159 }
1160 });
1161
1162 let caps = ClientCapabilities::from_params(¶ms);
1163 assert!(caps.extensions.contains("trace"));
1164 assert!(caps.extensions.contains("handoff"));
1165 }
1166
1167 #[test]
1168 fn test_rate_limiter() {
1169 let config = RateLimitConfig::new(2, Duration::from_secs(1));
1170 let limiter = RateLimiter::new(config);
1171
1172 assert!(limiter.check(None));
1173 assert!(limiter.check(None));
1174 assert!(!limiter.check(None)); }
1176
1177 #[test]
1178 fn test_connection_counter() {
1179 let counter = Arc::new(ConnectionCounter::new(2));
1180
1181 let guard1 = counter.try_acquire_arc();
1182 assert!(guard1.is_some());
1183 assert_eq!(counter.current(), 1);
1184
1185 let guard2 = counter.try_acquire_arc();
1186 assert!(guard2.is_some());
1187 assert_eq!(counter.current(), 2);
1188
1189 let guard3 = counter.try_acquire_arc();
1190 assert!(guard3.is_none()); drop(guard1);
1193 assert_eq!(counter.current(), 1);
1194
1195 let guard4 = counter.try_acquire_arc();
1196 assert!(guard4.is_some());
1197 }
1198
1199 #[test]
1204 fn test_builder_default_succeeds() {
1205 let config = ServerConfig::builder().build();
1207 assert_eq!(config.max_message_size, DEFAULT_MAX_MESSAGE_SIZE);
1208 assert!(config.origin_validation.allow_localhost);
1209 assert!(config.origin_validation.allowed_origins.is_empty());
1210 }
1211
1212 #[test]
1213 fn test_builder_origin_validation_overrides() {
1214 let config = ServerConfig::builder()
1215 .allow_origin("https://app.example.com")
1216 .allow_localhost_origins(false)
1217 .build();
1218
1219 assert!(!config.origin_validation.allow_localhost);
1220 assert!(
1221 config
1222 .origin_validation
1223 .allowed_origins
1224 .contains("https://app.example.com")
1225 );
1226 }
1227
1228 #[test]
1229 fn test_builder_try_build_valid() {
1230 let result = ServerConfig::builder()
1231 .max_message_size(1024 * 1024)
1232 .try_build();
1233 assert!(result.is_ok());
1234 }
1235
1236 #[test]
1237 fn test_builder_try_build_invalid_message_size() {
1238 let result = ServerConfig::builder()
1239 .max_message_size(100) .try_build();
1241 assert!(result.is_err());
1242 assert!(matches!(
1243 result.unwrap_err(),
1244 ConfigValidationError::InvalidMessageSize { .. }
1245 ));
1246 }
1247
1248 #[test]
1249 fn test_builder_try_build_invalid_rate_limit() {
1250 let result = ServerConfig::builder()
1251 .rate_limit(RateLimitConfig {
1252 max_requests: 0, window: Duration::from_secs(1),
1254 per_client: true,
1255 })
1256 .try_build();
1257 assert!(result.is_err());
1258 assert!(matches!(
1259 result.unwrap_err(),
1260 ConfigValidationError::InvalidRateLimit { .. }
1261 ));
1262 }
1263
1264 #[test]
1265 fn test_builder_try_build_zero_window() {
1266 let result = ServerConfig::builder()
1267 .rate_limit(RateLimitConfig {
1268 max_requests: 100,
1269 window: Duration::ZERO, per_client: true,
1271 })
1272 .try_build();
1273 assert!(result.is_err());
1274 assert!(matches!(
1275 result.unwrap_err(),
1276 ConfigValidationError::InvalidRateLimit { .. }
1277 ));
1278 }
1279
1280 #[test]
1281 fn test_builder_try_build_invalid_connection_limits() {
1282 let result = ServerConfig::builder()
1283 .connection_limits(ConnectionLimits {
1284 max_tcp_connections: 0,
1285 max_websocket_connections: 0,
1286 max_http_concurrent: 0,
1287 max_unix_connections: 0,
1288 })
1289 .try_build();
1290 assert!(result.is_err());
1291 assert!(matches!(
1292 result.unwrap_err(),
1293 ConfigValidationError::InvalidConnectionLimits { .. }
1294 ));
1295 }
1296}
1297
1298#[cfg(test)]
1299mod proptest_tests {
1300 use super::*;
1301 use proptest::prelude::*;
1302
1303 proptest! {
1304 #[test]
1305 fn config_builder_never_panics(
1306 max_msg_size in 0usize..10_000_000,
1307 ) {
1308 let _ = ServerConfig::builder()
1310 .max_message_size(max_msg_size)
1311 .try_build();
1312 }
1313
1314 #[test]
1315 fn connection_counter_bounded(max in 1usize..10000) {
1316 let counter = Arc::new(ConnectionCounter::new(max));
1317 let mut guards = Vec::new();
1318 for _ in 0..max + 10 {
1320 if let Some(guard) = counter.try_acquire_arc() {
1321 guards.push(guard);
1322 }
1323 }
1324 assert_eq!(guards.len(), max);
1325 assert_eq!(counter.current(), max);
1326 }
1327 }
1328}