use std::collections::HashMap;
use std::hash::BuildHasher;
use std::sync::RwLock;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Duration;
use ocpp_core::{DateTime, OcppVersion, RpcError, Violation};
use serde::Serialize;
use tokio::sync::{mpsc, oneshot};
const SHARDS: usize = 64;
#[derive(Debug)]
pub enum Command {
Call {
action: String,
payload: serde_json::Value,
reply: oneshot::Sender<CallOutcome>,
},
Close { code: u16, reason: &'static str },
}
#[derive(Debug)]
pub enum CallFailure {
Error(RpcError),
InvalidResponse {
violation: Violation,
raw: serde_json::Value,
},
Timeout,
Disconnected,
}
pub type CallOutcome = Result<serde_json::Value, CallFailure>;
#[derive(Debug, Clone)]
pub struct ConnHandle {
pub conn_id: u64,
pub station_id: i64,
pub version: OcppVersion,
pub connected_at: DateTime,
pub remote_addr: Option<String>,
pub tx: mpsc::Sender<Command>,
}
#[derive(Debug, Serialize)]
pub struct ConnInfo {
pub ocpp_identity: String,
pub station_id: i64,
pub ocpp_version: String,
pub connected_at: DateTime,
pub remote_addr: Option<String>,
}
pub enum SendError {
NotConnected,
QueueFull,
}
pub struct Hub {
shards: Vec<RwLock<HashMap<String, ConnHandle>>>,
hasher: std::collections::hash_map::RandomState,
next_id: AtomicU64,
}
impl Default for Hub {
fn default() -> Self {
Self::new()
}
}
impl Hub {
pub fn new() -> Self {
Hub {
shards: (0..SHARDS).map(|_| RwLock::new(HashMap::new())).collect(),
hasher: Default::default(),
next_id: AtomicU64::new(1),
}
}
fn shard(&self, identity: &str) -> &RwLock<HashMap<String, ConnHandle>> {
&self.shards[self.hasher.hash_one(identity) as usize % SHARDS]
}
pub fn next_conn_id(&self) -> u64 {
self.next_id.fetch_add(1, Ordering::Relaxed)
}
pub fn register(&self, identity: &str, handle: ConnHandle) -> Option<ConnHandle> {
self.shard(identity)
.write()
.unwrap_or_else(|e| e.into_inner())
.insert(identity.to_owned(), handle)
}
pub fn unregister(&self, identity: &str, conn_id: u64) {
let mut shard = self
.shard(identity)
.write()
.unwrap_or_else(|e| e.into_inner());
if shard.get(identity).is_some_and(|h| h.conn_id == conn_id) {
shard.remove(identity);
}
}
pub fn get(&self, identity: &str) -> Option<ConnHandle> {
self.shard(identity)
.read()
.unwrap_or_else(|e| e.into_inner())
.get(identity)
.cloned()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn len(&self) -> usize {
self.shards
.iter()
.map(|s| s.read().unwrap_or_else(|e| e.into_inner()).len())
.sum()
}
pub fn list(&self) -> Vec<ConnInfo> {
let mut out = Vec::new();
for s in &self.shards {
for (id, h) in s.read().unwrap_or_else(|e| e.into_inner()).iter() {
out.push(ConnInfo {
ocpp_identity: id.clone(),
station_id: h.station_id,
ocpp_version: h.version.to_string(),
connected_at: h.connected_at,
remote_addr: h.remote_addr.clone(),
});
}
}
out.sort_by(|a, b| a.ocpp_identity.cmp(&b.ocpp_identity));
out
}
pub async fn call(
&self,
identity: &str,
action: &str,
payload: serde_json::Value,
wait: Duration,
) -> Result<CallOutcome, SendError> {
let handle = self.get(identity).ok_or(SendError::NotConnected)?;
let (reply, rx) = oneshot::channel();
handle
.tx
.try_send(Command::Call {
action: action.to_owned(),
payload,
reply,
})
.map_err(|e| match e {
mpsc::error::TrySendError::Full(_) => SendError::QueueFull,
mpsc::error::TrySendError::Closed(_) => SendError::NotConnected,
})?;
Ok(match tokio::time::timeout(wait, rx).await {
Ok(Ok(outcome)) => outcome,
Ok(Err(_)) => Err(CallFailure::Disconnected),
Err(_) => Err(CallFailure::Timeout),
})
}
}