use std::sync::Arc;
use dashmap::DashMap;
use indexmap::IndexMap;
use crate::reverse_channel::Upstream;
use crate::session::Session;
#[derive(Debug, Default)]
pub struct SessionManager {
sessions: DashMap<String, Arc<Session>>,
}
impl SessionManager {
pub fn new() -> Self {
Self::default()
}
pub fn add(&self, response_id: String, connections: Vec<Upstream>) {
let by_prefix = build_prefix_map(connections);
self.sessions
.insert(response_id, Arc::new(Session::new(by_prefix)));
}
pub fn get(&self, response_id: &str) -> Option<Arc<Session>> {
self.sessions.get(response_id).map(|e| e.value().clone())
}
pub fn remove(&self, response_id: &str) -> Option<Arc<Session>> {
self.sessions.remove(response_id).map(|(_, session)| session)
}
}
pub fn parse_key_env(s: &str) -> Result<Option<[u8; 32]>, String> {
use base64::Engine;
let trimmed = s.trim();
if trimmed.is_empty() {
return Ok(None);
}
let decoded = base64::engine::general_purpose::STANDARD
.decode(trimmed)
.map_err(|e| format!("MCP_ENCRYPTION_KEY: not valid base64: {e}"))?;
let key: [u8; 32] = decoded.try_into().map_err(|got: Vec<u8>| {
format!(
"MCP_ENCRYPTION_KEY: expected 32 bytes after base64-decode, got {}",
got.len(),
)
})?;
Ok(Some(key))
}
fn normalize_prefix_token(s: &str) -> String {
s.replace(['_', '.'], "-")
}
fn build_prefix_map(
mut connections: Vec<Upstream>,
) -> IndexMap<String, Upstream> {
connections.sort_by(|a, b| a.url().cmp(b.url()));
let n = connections.len();
let names: Vec<String> = connections
.iter()
.map(|c| normalize_prefix_token(c.server_name()))
.collect();
let versions: Vec<String> = connections
.iter()
.map(|c| normalize_prefix_token(c.server_version()))
.collect();
let prefix_at = |i: usize, tier: u8| -> String {
match tier {
1 => names[i].clone(),
2 => format!("{}-{}", names[i], versions[i]),
_ => format!("{}-{}-{}", names[i], versions[i], i),
}
};
let mut tier: Vec<u8> = vec![1; n];
loop {
let current: Vec<String> = (0..n).map(|i| prefix_at(i, tier[i])).collect();
let mut counts: std::collections::HashMap<&str, usize> =
std::collections::HashMap::new();
for p in ¤t {
*counts.entry(p.as_str()).or_insert(0) += 1;
}
let mut changed = false;
for i in 0..n {
if counts[current[i].as_str()] > 1 && tier[i] < 3 {
tier[i] += 1;
changed = true;
}
}
if !changed {
break;
}
}
let mut by_prefix: IndexMap<String, Upstream> = IndexMap::with_capacity(n);
for (i, connection) in connections.into_iter().enumerate() {
let key = prefix_at(i, tier[i]);
debug_assert!(
!by_prefix.contains_key(&key),
"duplicate routing prefix after escalation: {key}",
);
by_prefix.insert(key, connection);
}
by_prefix
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_key_env_round_trip() {
use base64::Engine;
let key = [0xAAu8; 32];
let env = base64::engine::general_purpose::STANDARD.encode(key);
let parsed = parse_key_env(&env).expect("parse").expect("Some");
assert_eq!(parsed, key);
assert!(parse_key_env("").unwrap().is_none());
assert!(parse_key_env(" ").unwrap().is_none());
assert!(parse_key_env("not-base64!@#").is_err());
let short =
base64::engine::general_purpose::STANDARD.encode(&[0u8; 16][..]);
assert!(parse_key_env(&short).is_err());
}
}