1use std::{
2 collections::HashMap,
3 error::Error,
4 fmt,
5 sync::{Mutex, MutexGuard},
6};
7
8use subc_protocol::manifest::{CapabilityDeclarations, ModuleManifest, ProviderRole};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub struct ConnectionId(u64);
13
14impl ConnectionId {
15 #[cfg(test)]
18 pub const LOCAL: Self = Self(0);
19
20 pub const fn new(raw: u64) -> Self {
21 Self(raw)
22 }
23
24 pub fn get(self) -> u64 {
25 self.0
26 }
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum ChannelState {
32 Active,
33 Closed,
34}
35
36#[derive(Debug, Clone, PartialEq)]
38pub struct ModuleRegistration {
39 pub manifest: ModuleManifest,
40 pub ready: bool,
41 pub negotiated_ver: u8,
42 pub state: ChannelState,
43 pub connection_id: ConnectionId,
44 pub control_ops: Vec<String>,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum RegistrationSlot<'a> {
57 Active(&'a str),
60 Candidate(&'a str),
62 Connection(ConnectionId),
64}
65
66#[derive(Debug, Clone, PartialEq)]
68pub struct RegistryCutover {
69 pub promoted: ModuleRegistration,
71 pub superseded: Option<ModuleRegistration>,
74}
75
76#[derive(Debug, Default)]
91pub struct Registry {
92 inner: Mutex<RegistryInner>,
93}
94
95#[derive(Debug, Default)]
96struct RegistryInner {
97 modules: HashMap<String, ModuleRegistration>,
98 candidates: HashMap<String, ModuleRegistration>,
100 superseded: Vec<ModuleRegistration>,
103 generation: u64,
104}
105
106impl Registry {
107 pub fn register_with_control_ops(
109 &self,
110 manifest: ModuleManifest,
111 negotiated_ver: u8,
112 connection_id: ConnectionId,
113 control_ops: Vec<String>,
114 ) -> Result<ModuleRegistration, RegistryError> {
115 let module_id = manifest.module_id.clone();
116 if let Err(reason) = module_id_path_hazard(&module_id) {
117 return Err(RegistryError::PathHazardModuleId { module_id, reason });
118 }
119 let mut inner = self.lock_inner()?;
120 if inner.modules.contains_key(&module_id) {
121 return Err(RegistryError::DuplicateModuleId { module_id });
122 }
123
124 let ready = manifest.ready.unwrap_or(true);
125 let registration = ModuleRegistration {
126 manifest,
127 ready,
128 negotiated_ver,
129 state: ChannelState::Active,
130 connection_id,
131 control_ops,
132 };
133
134 inner.modules.insert(module_id, registration.clone());
135 inner.bump_generation();
136 Ok(registration)
137 }
138
139 pub fn register_candidate_with_control_ops(
148 &self,
149 manifest: ModuleManifest,
150 negotiated_ver: u8,
151 connection_id: ConnectionId,
152 control_ops: Vec<String>,
153 ) -> Result<ModuleRegistration, RegistryError> {
154 let module_id = manifest.module_id.clone();
155 if let Err(reason) = module_id_path_hazard(&module_id) {
156 return Err(RegistryError::PathHazardModuleId { module_id, reason });
157 }
158 let mut inner = self.lock_inner()?;
159 if inner.candidates.contains_key(&module_id) {
160 return Err(RegistryError::DuplicateModuleId { module_id });
161 }
162 let ready = manifest.ready.unwrap_or(true);
163 let registration = ModuleRegistration {
164 manifest,
165 ready,
166 negotiated_ver,
167 state: ChannelState::Active,
168 connection_id,
169 control_ops,
170 };
171 inner.candidates.insert(module_id, registration.clone());
172 Ok(registration)
173 }
174
175 pub fn promote_candidate(
181 &self,
182 module_id: &str,
183 ) -> Result<Option<RegistryCutover>, RegistryError> {
184 let mut inner = self.lock_inner()?;
185 let Some(promoted) = inner.candidates.remove(module_id) else {
186 return Ok(None);
187 };
188 let superseded = inner
189 .modules
190 .insert(module_id.to_string(), promoted.clone());
191 if let Some(superseded) = superseded.clone() {
192 inner.superseded.push(superseded);
193 }
194 inner.bump_generation();
195 Ok(Some(RegistryCutover {
196 promoted,
197 superseded,
198 }))
199 }
200
201 pub fn get_module(&self, module_id: &str) -> Result<Option<ModuleRegistration>, RegistryError> {
204 Ok(self.lock_inner()?.modules.get(module_id).cloned())
205 }
206
207 pub fn get_candidate(
209 &self,
210 module_id: &str,
211 ) -> Result<Option<ModuleRegistration>, RegistryError> {
212 Ok(self.lock_inner()?.candidates.get(module_id).cloned())
213 }
214
215 pub fn registration(
217 &self,
218 slot: RegistrationSlot<'_>,
219 ) -> Result<Option<ModuleRegistration>, RegistryError> {
220 let inner = self.lock_inner()?;
221 Ok(match slot {
222 RegistrationSlot::Active(module_id) => inner.modules.get(module_id).cloned(),
223 RegistrationSlot::Candidate(module_id) => inner.candidates.get(module_id).cloned(),
224 RegistrationSlot::Connection(connection_id) => inner
225 .find_by_connection(connection_id)
226 .map(|(_, registration)| registration.clone()),
227 })
228 }
229
230 pub fn active_registration_count(&self) -> Result<usize, RegistryError> {
231 Ok(self.lock_inner()?.modules.len())
232 }
233
234 pub fn list_modules(&self) -> Result<(u64, Vec<ModuleRegistration>), RegistryError> {
235 let inner = self.lock_inner()?;
236 let mut modules = inner.modules.values().cloned().collect::<Vec<_>>();
237 modules.sort_by(|left, right| left.manifest.module_id.cmp(&right.manifest.module_id));
238 Ok((inner.generation, modules))
239 }
240
241 pub fn generation(&self) -> Result<u64, RegistryError> {
242 Ok(self.lock_inner()?.generation)
243 }
244
245 #[cfg(test)]
246 pub(crate) fn set_module_state_for_test(
247 &self,
248 module_id: &str,
249 state: ChannelState,
250 ) -> Result<bool, RegistryError> {
251 let mut inner = self.lock_inner()?;
252 let Some(registration) = inner.modules.get_mut(module_id) else {
253 return Ok(false);
254 };
255 registration.state = state;
256 Ok(true)
257 }
258
259 pub fn get_module_by_connection(
262 &self,
263 connection_id: ConnectionId,
264 ) -> Result<Option<ModuleRegistration>, RegistryError> {
265 Ok(self
266 .lock_inner()?
267 .find_by_connection(connection_id)
268 .map(|(_, registration)| registration.clone()))
269 }
270
271 pub fn replace_catalog_for_connection(
280 &self,
281 connection_id: ConnectionId,
282 provides: Vec<ProviderRole>,
283 capabilities: Option<CapabilityDeclarations>,
284 ready: Option<bool>,
285 ) -> Result<Option<ModuleRegistration>, RegistryError> {
286 let mut inner = self.lock_inner()?;
287 let Some((slot, _)) = inner.find_by_connection(connection_id) else {
288 return Ok(None);
289 };
290 let registration = inner
291 .registration_mut(slot, connection_id)
292 .expect("registration discovered under the same registry lock must still exist");
293 registration.manifest.provides = provides;
294 if let Some(capabilities) = capabilities {
295 registration.manifest.capabilities = Some(capabilities);
296 }
297 if let Some(ready) = ready {
298 registration.ready = ready;
299 registration.manifest.ready = Some(ready);
300 }
301 let updated = registration.clone();
302 if matches!(slot, SlotKind::Active) {
303 inner.bump_generation();
304 }
305 Ok(Some(updated))
306 }
307
308 pub fn deregister_connection(
310 &self,
311 connection_id: ConnectionId,
312 ) -> Result<Vec<ModuleRegistration>, RegistryError> {
313 let mut inner = self.lock_inner()?;
314 let module_ids: Vec<String> = inner
315 .modules
316 .iter()
317 .filter(|(_, registration)| registration.connection_id == connection_id)
318 .map(|(module_id, _)| module_id.clone())
319 .collect();
320
321 let mut closed: Vec<ModuleRegistration> = module_ids
322 .into_iter()
323 .filter_map(|module_id| inner.close_module(&module_id))
324 .collect();
325
326 let candidate_ids: Vec<String> = inner
327 .candidates
328 .iter()
329 .filter(|(_, registration)| registration.connection_id == connection_id)
330 .map(|(module_id, _)| module_id.clone())
331 .collect();
332 for module_id in candidate_ids {
333 if let Some(mut registration) = inner.candidates.remove(&module_id) {
334 registration.state = ChannelState::Closed;
335 closed.push(registration);
336 }
337 }
338
339 let (removed, kept): (Vec<_>, Vec<_>) = std::mem::take(&mut inner.superseded)
340 .into_iter()
341 .partition(|registration| registration.connection_id == connection_id);
342 inner.superseded = kept;
343 closed.extend(removed.into_iter().map(|mut registration| {
344 registration.state = ChannelState::Closed;
345 registration
346 }));
347 Ok(closed)
348 }
349
350 fn lock_inner(&self) -> Result<MutexGuard<'_, RegistryInner>, RegistryError> {
351 self.inner.lock().map_err(|_| RegistryError::Poisoned)
352 }
353}
354
355#[derive(Debug, Clone, Copy, PartialEq, Eq)]
357enum SlotKind {
358 Active,
359 Candidate,
360 Superseded,
361}
362
363impl RegistryInner {
364 fn find_by_connection(
365 &self,
366 connection_id: ConnectionId,
367 ) -> Option<(SlotKind, &ModuleRegistration)> {
368 let owned_by =
369 |registration: &&ModuleRegistration| registration.connection_id == connection_id;
370 self.modules
371 .values()
372 .find(owned_by)
373 .map(|registration| (SlotKind::Active, registration))
374 .or_else(|| {
375 self.candidates
376 .values()
377 .find(owned_by)
378 .map(|registration| (SlotKind::Candidate, registration))
379 })
380 .or_else(|| {
381 self.superseded
382 .iter()
383 .find(owned_by)
384 .map(|registration| (SlotKind::Superseded, registration))
385 })
386 }
387
388 fn registration_mut(
389 &mut self,
390 slot: SlotKind,
391 connection_id: ConnectionId,
392 ) -> Option<&mut ModuleRegistration> {
393 let owned_by =
394 |registration: &&mut ModuleRegistration| registration.connection_id == connection_id;
395 match slot {
396 SlotKind::Active => self.modules.values_mut().find(owned_by),
397 SlotKind::Candidate => self.candidates.values_mut().find(owned_by),
398 SlotKind::Superseded => self.superseded.iter_mut().find(owned_by),
399 }
400 }
401
402 fn close_module(&mut self, module_id: &str) -> Option<ModuleRegistration> {
403 let mut registration = self.modules.remove(module_id)?;
404 registration.state = ChannelState::Closed;
405 self.bump_generation();
406 Some(registration)
407 }
408
409 fn bump_generation(&mut self) {
410 self.generation = self.generation.wrapping_add(1);
411 }
412}
413
414#[derive(Debug, Clone, PartialEq, Eq)]
415pub enum RegistryError {
416 DuplicateModuleId {
417 module_id: String,
418 },
419 PathHazardModuleId {
428 module_id: String,
429 reason: String,
430 },
431 Poisoned,
432}
433
434impl fmt::Display for RegistryError {
435 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
436 match self {
437 Self::DuplicateModuleId { module_id } => {
438 write!(f, "module_id '{module_id}' is already registered")
439 }
440 Self::PathHazardModuleId { module_id, reason } => {
441 write!(
442 f,
443 "module_id '{}' is not usable as a path component: {reason}",
444 module_id.escape_debug()
445 )
446 }
447 Self::Poisoned => write!(f, "registry lock was poisoned"),
448 }
449 }
450}
451
452impl Error for RegistryError {}
453
454pub fn module_id_path_hazard(module_id: &str) -> Result<(), String> {
464 if module_id.is_empty() {
465 return Err("empty".to_string());
466 }
467 if module_id.contains('/') || module_id.contains('\\') {
468 return Err("contains a path separator".to_string());
469 }
470 if module_id == "." || module_id == ".." {
471 return Err("is a dot path component".to_string());
472 }
473 if module_id.chars().any(|c| c.is_control()) {
474 return Err("contains a control character".to_string());
475 }
476 if module_id.len() > 255 {
480 return Err("is longer than 255 bytes".to_string());
481 }
482 Ok(())
483}
484
485#[cfg(test)]
486mod path_hazard_tests {
487 use super::*;
488 use crate::ConnectionId;
489 use subc_protocol::manifest::ModuleManifest;
490
491 fn manifest(module_id: &str) -> ModuleManifest {
492 ModuleManifest::builder(module_id, "0.1.0")
493 .protocol_ver(1)
494 .build()
495 }
496
497 #[test]
498 fn path_hazard_ids_are_refused_and_nothing_registers() {
499 let registry = Registry::default();
500 for (bad, reason_fragment) in [
501 ("../escape", "path separator"),
502 ("a/b", "path separator"),
503 ("a\\b", "path separator"),
504 ("..", "dot path component"),
505 (".", "dot path component"),
506 ("", "empty"),
507 ("evil\u{0}id", "control character"),
508 ] {
509 let err = registry
510 .register_with_control_ops(manifest(bad), 1, ConnectionId::new(7), Vec::new())
511 .expect_err("path-hazard id must refuse");
512 assert!(
514 err.to_string().contains(reason_fragment),
515 "id {bad:?}: expected {reason_fragment:?} in {err}"
516 );
517 }
518 assert_eq!(registry.active_registration_count().unwrap(), 0);
521 assert_eq!(registry.generation().unwrap(), 0);
522 }
523
524 #[test]
525 fn module_id_path_component_length_matches_shared_refusal_vectors() {
526 let doc: serde_json::Value = serde_json::from_str(include_str!(
527 "../tests/golden/module_id_path_component_refusals.json"
528 ))
529 .expect("refusal fixture parses");
530
531 for case in doc["vectors"].as_array().expect("vectors array") {
532 let name = case["name"].as_str().expect("name");
533 let module_id = case["module_id"]["unit"]
534 .as_str()
535 .expect("module_id unit")
536 .repeat(
537 case["module_id"]["repeat"]
538 .as_u64()
539 .expect("module_id repeat") as usize,
540 );
541 assert_eq!(
542 module_id.len(),
543 case["utf8_bytes"].as_u64().expect("utf8 bytes") as usize
544 );
545
546 let expected = case["expect_reason"].as_str().map(str::to_owned);
547 assert_eq!(
548 module_id_path_hazard(&module_id).err(),
549 expected,
550 "shared refusal vector {name:?} diverged"
551 );
552 }
553 }
554
555 #[test]
556 fn working_id_shapes_register_including_namespace_colons() {
557 let registry = Registry::default();
558 for (i, good) in ["magic-context", "mcp:everything", "v1.2-module"]
559 .iter()
560 .enumerate()
561 {
562 registry
563 .register_with_control_ops(
564 manifest(good),
565 1,
566 ConnectionId::new(10 + i as u64),
567 Vec::new(),
568 )
569 .unwrap_or_else(|err| panic!("id {good:?} must register: {err}"));
570 }
571 assert_eq!(registry.active_registration_count().unwrap(), 3);
572 }
573}
574
575#[cfg(test)]
576mod swap_slot_tests {
577 use super::*;
578
579 fn manifest(module_id: &str, ready: Option<bool>) -> ModuleManifest {
580 let mut manifest = ModuleManifest::builder(module_id, "0.1.0").build();
581 manifest.ready = ready;
582 manifest
583 }
584
585 const INCUMBENT: ConnectionId = ConnectionId(1);
586 const CANDIDATE: ConnectionId = ConnectionId(2);
587
588 fn registry_with_candidate() -> Registry {
589 let registry = Registry::default();
590 registry
591 .register_with_control_ops(manifest("m", None), 1, INCUMBENT, Vec::new())
592 .unwrap();
593 registry
594 .register_candidate_with_control_ops(
595 manifest("m", Some(false)),
596 1,
597 CANDIDATE,
598 Vec::new(),
599 )
600 .unwrap();
601 registry
602 }
603
604 #[test]
605 fn candidate_is_invisible_to_by_id_lookups_and_listing() {
606 let registry = registry_with_candidate();
607 let generation = registry.generation().unwrap();
608 assert_eq!(
609 registry.get_module("m").unwrap().unwrap().connection_id,
610 INCUMBENT
611 );
612 let (listed_generation, listed) = registry.list_modules().unwrap();
613 assert_eq!(listed.len(), 1);
614 assert_eq!(listed[0].connection_id, INCUMBENT);
615 assert_eq!(listed_generation, generation);
616 assert_eq!(registry.active_registration_count().unwrap(), 1);
617 assert_eq!(
618 registry.get_candidate("m").unwrap().unwrap().connection_id,
619 CANDIDATE
620 );
621 assert_eq!(
622 registry
623 .register_candidate_with_control_ops(
624 manifest("m", None),
625 1,
626 ConnectionId(3),
627 Vec::new()
628 )
629 .unwrap_err(),
630 RegistryError::DuplicateModuleId {
631 module_id: "m".to_string()
632 }
633 );
634 }
635
636 #[test]
639 fn candidate_catalog_update_reaches_the_candidate_registration() {
640 let registry = registry_with_candidate();
641 assert!(!registry.get_candidate("m").unwrap().unwrap().ready);
642
643 let updated = registry
644 .replace_catalog_for_connection(CANDIDATE, Vec::new(), None, Some(true))
645 .unwrap()
646 .expect("the candidate's own connection finds its registration");
647
648 assert_eq!(updated.connection_id, CANDIDATE);
649 assert!(registry.get_candidate("m").unwrap().unwrap().ready);
650 assert_eq!(
651 registry.get_module_by_connection(CANDIDATE).unwrap(),
652 Some(updated)
653 );
654 assert_eq!(
655 registry.get_module("m").unwrap().unwrap().connection_id,
656 INCUMBENT,
657 "a candidate's update must not touch the active registration"
658 );
659 }
660
661 #[test]
662 fn promotion_swaps_slots_and_each_connection_still_deregisters_its_own() {
663 let registry = registry_with_candidate();
664 let before = registry.generation().unwrap();
665 let cutover = registry.promote_candidate("m").unwrap().unwrap();
666 assert_eq!(cutover.promoted.connection_id, CANDIDATE);
667 assert_eq!(cutover.superseded.unwrap().connection_id, INCUMBENT);
668 assert_ne!(registry.generation().unwrap(), before);
669 assert_eq!(registry.promote_candidate("m").unwrap(), None);
670
671 assert_eq!(
672 registry
673 .registration(RegistrationSlot::Active("m"))
674 .unwrap()
675 .unwrap()
676 .connection_id,
677 CANDIDATE
678 );
679 assert!(registry
680 .registration(RegistrationSlot::Candidate("m"))
681 .unwrap()
682 .is_none());
683 assert!(registry
684 .registration(RegistrationSlot::Connection(INCUMBENT))
685 .unwrap()
686 .is_some());
687
688 let closed = registry.deregister_connection(INCUMBENT).unwrap();
689 assert_eq!(closed.len(), 1);
690 assert_eq!(closed[0].connection_id, INCUMBENT);
691 assert_eq!(closed[0].state, ChannelState::Closed);
692 assert!(registry
693 .registration(RegistrationSlot::Connection(INCUMBENT))
694 .unwrap()
695 .is_none());
696 assert_eq!(
697 registry.get_module("m").unwrap().unwrap().connection_id,
698 CANDIDATE
699 );
700 }
701
702 #[test]
703 fn a_dropped_candidate_deregisters_from_the_candidate_slot_only() {
704 let registry = registry_with_candidate();
705 let closed = registry.deregister_connection(CANDIDATE).unwrap();
706 assert_eq!(closed.len(), 1);
707 assert_eq!(closed[0].connection_id, CANDIDATE);
708 assert!(registry.get_candidate("m").unwrap().is_none());
709 assert_eq!(
710 registry.get_module("m").unwrap().unwrap().connection_id,
711 INCUMBENT
712 );
713 }
714}