1use std::time::Duration;
16
17use ocpp_core::rpc::{self, Frame, FrameError};
18use ocpp_core::{ErrorCode, OcppVersion, RpcError};
19use ocpp_ws::{DeflateConfig, DeflateParams, Message, WsConfig, WsReader, WsWriter, close_code};
20use tokio::io::{AsyncRead, AsyncWrite, ReadHalf, WriteHalf};
21use tokio::sync::{mpsc, oneshot};
22use tokio::time::{Instant, MissedTickBehavior};
23
24use super::{Registration, StationCtx, v16, v21, v201};
25use crate::db::{self, stations::Station};
26use crate::hub::{CallFailure, CallOutcome, Command, ConnHandle};
27use crate::state::App;
28
29pub struct SessionParams {
30 pub app: App,
31 pub station: Station,
32 pub version: Option<OcppVersion>,
35 pub deflate: Option<(DeflateParams, DeflateConfig)>,
36 pub remote_addr: Option<String>,
37}
38
39struct Pending {
40 id: String,
41 action: String,
42 reply: oneshot::Sender<CallOutcome>,
43 deadline: Instant,
44}
45
46struct Session<S> {
47 ctx: StationCtx,
48 writer: WsWriter<WriteHalf<S>>,
49 pending: Option<Pending>,
50 last_rx: Instant,
51}
52
53pub async fn run<S>(p: SessionParams, io: S)
54where
55 S: AsyncRead + AsyncWrite + Send + 'static,
56{
57 let app = p.app;
58 let ws_cfg = WsConfig {
59 max_message_size: app.cfg.max_message_size,
60 ..Default::default()
61 };
62 let (reader, mut writer) = ocpp_ws::server(io, ws_cfg, p.deflate);
63 let Some(version) = p.version else {
64 tracing::info!(station = %p.station.ocpp_identity, "no common OCPP version, closing");
65 let _ = writer
66 .send_close(
67 close_code::PROTOCOL_ERROR,
68 "no supported OCPP version offered",
69 )
70 .await;
71 return;
72 };
73
74 let identity = p.station.ocpp_identity.clone();
75 let station_id = p.station.id;
76 let conn_id = app.hub.next_conn_id();
77 let connection_id = format!("{}:{}", app.cfg.node_id, app.ids.next_id());
78 let (cmd_tx, cmd_rx) = mpsc::channel(app.cfg.command_queue_size.max(1));
79 let handle = ConnHandle {
80 conn_id,
81 station_id,
82 version,
83 connected_at: ocpp_core::DateTime::now(),
84 remote_addr: p.remote_addr.clone(),
85 tx: cmd_tx.clone(),
86 };
87 if let Some(old) = app.hub.register(&identity, handle) {
88 tracing::info!(station = %identity, "new connection replaces the previous one");
89 let _ = old.tx.try_send(Command::Close {
90 code: close_code::POLICY_VIOLATION,
91 reason: "replaced by a newer connection",
92 });
93 }
94 if let Err(e) = db::stations::mark_connected(
95 &app.db,
96 station_id,
97 &app.cfg.node_id,
98 &connection_id,
99 version.as_str(),
100 )
101 .await
102 {
103 tracing::error!(station = %identity, error = %e, "failed to record connection");
104 }
105 tracing::info!(station = %identity, version = %version, remote = ?p.remote_addr, deflate = p.deflate.is_some(), "station connected");
106
107 let registration = Registration::from_db(&p.station.registration_status);
108 if registration == Registration::Unknown {
109 let (reply, _) = oneshot::channel();
112 let _ = cmd_tx.try_send(Command::Call {
113 action: "TriggerMessage".into(),
114 payload: serde_json::json!({ "requestedMessage": "BootNotification" }),
115 reply,
116 });
117 }
118 drop(cmd_tx);
119
120 let ctx = StationCtx {
121 app: app.clone(),
122 station_id,
123 identity: identity.clone(),
124 station_evse_id: p.station.evse_id.clone(),
125 version,
126 registration,
127 };
128 let mut session = Session {
129 ctx,
130 writer,
131 pending: None,
132 last_rx: Instant::now(),
133 };
134 let reason = session.run_loop(reader, cmd_rx).await;
135 tracing::info!(station = %identity, reason, "station disconnected");
136
137 if let Some(p) = session.pending.take() {
138 let _ = p.reply.send(Err(CallFailure::Disconnected));
139 }
140 app.hub.unregister(&identity, conn_id);
141 if let Err(e) = db::stations::mark_disconnected(&app.db, station_id, &connection_id).await {
142 tracing::error!(station = %identity, error = %e, "failed to record disconnection");
143 }
144}
145
146impl<S: AsyncRead + AsyncWrite + Send + 'static> Session<S> {
147 async fn run_loop(
148 &mut self,
149 mut reader: WsReader<ReadHalf<S>>,
150 mut cmd_rx: mpsc::Receiver<Command>,
151 ) -> &'static str {
152 let app = self.ctx.app.clone();
153 let mut shutdown = app.shutdown.clone();
154 let ping_every = app.cfg.ping_interval;
155 let mut ping = tokio::time::interval(if ping_every.is_zero() {
156 Duration::from_secs(3600)
157 } else {
158 ping_every
159 });
160 ping.set_missed_tick_behavior(MissedTickBehavior::Delay);
161 ping.tick().await;
162 let far = Instant::now() + Duration::from_secs(86_400 * 365);
163
164 loop {
165 let deadline = self.pending.as_ref().map(|p| p.deadline).unwrap_or(far);
166 tokio::select! {
167 _ = shutdown.changed() => {
168 let _ = self.writer.send_close(close_code::GOING_AWAY, "server shutting down").await;
169 return "server shutdown";
170 }
171 msg = reader.recv() => {
172 self.last_rx = Instant::now();
173 match msg {
174 Ok(Some(Message::Text(text))) => {
175 if let Err(e) = self.on_text(&text).await {
176 tracing::debug!(station = %self.ctx.identity, error = %e, "write failed");
177 return "write error";
178 }
179 }
180 Ok(Some(Message::Ping(data))) => {
181 if self.writer.send_pong(&data).await.is_err() {
182 return "write error";
183 }
184 }
185 Ok(Some(Message::Pong(_))) => {}
186 Ok(Some(Message::Binary(_))) => {
187 let _ = self.writer.send_close(close_code::UNSUPPORTED_DATA, "OCPP-J uses text frames").await;
188 return "binary frame";
189 }
190 Ok(Some(Message::Close(frame))) => {
191 let code = frame.as_ref().map(|f| f.0).unwrap_or(close_code::NORMAL);
192 let _ = self.writer.send_close(code, "").await;
193 return "closed by station";
194 }
195 Ok(None) => return "connection lost",
196 Err(e) => {
197 tracing::info!(station = %self.ctx.identity, error = %e, "WebSocket error");
198 let _ = self.writer.send_close(e.close_code(), "").await;
199 return "protocol error";
200 }
201 }
202 }
203 cmd = cmd_rx.recv(), if self.pending.is_none() => match cmd {
204 Some(Command::Call { action, payload, reply }) => {
205 if reply.is_closed() && action != "TriggerMessage" {
206 continue; }
208 let id = app.ids.next_id();
209 let text = match rpc::encode_call(&id, &action, &payload) {
210 Ok(t) => t,
211 Err(e) => {
212 let _ = reply.send(Err(CallFailure::Error(RpcError::internal(e.to_string()))));
213 continue;
214 }
215 };
216 if self.writer.send_text(&text).await.is_err() {
217 let _ = reply.send(Err(CallFailure::Disconnected));
218 return "write error";
219 }
220 tracing::debug!(station = %self.ctx.identity, %action, %id, "CALL sent");
221 self.pending = Some(Pending { id, action, reply, deadline: Instant::now() + app.cfg.call_timeout });
222 }
223 Some(Command::Close { code, reason }) => {
224 let _ = self.writer.send_close(code, reason).await;
225 return reason;
226 }
227 None => {
228 let _ = self.writer.send_close(close_code::POLICY_VIOLATION, "connection replaced").await;
229 return "unregistered";
230 }
231 },
232 _ = tokio::time::sleep_until(deadline), if self.pending.is_some() => {
233 let p = self.pending.take().expect("pending checked");
234 tracing::warn!(station = %self.ctx.identity, action = %p.action, id = %p.id, "CALL timed out");
235 let _ = p.reply.send(Err(CallFailure::Timeout));
236 }
237 _ = ping.tick(), if !ping_every.is_zero() => {
238 let idle = self.last_rx.elapsed();
239 if idle >= ping_every * 3 {
240 let _ = self.writer.send_close(close_code::GOING_AWAY, "keep-alive timeout").await;
241 return "keep-alive timeout";
242 }
243 if idle >= ping_every && self.writer.send_ping(b"").await.is_err() {
244 return "write error";
245 }
246 }
247 }
248 }
249 }
250
251 async fn send(&mut self, text: String) -> Result<(), ocpp_ws::WsError> {
252 self.writer.send_text(&text).await
253 }
254
255 async fn send_error(
256 &mut self,
257 id: &str,
258 err: &RpcError,
259 result_error: bool,
260 ) -> Result<(), ocpp_ws::WsError> {
261 let text = rpc::encode_error(id, err, self.ctx.version.dialect(), result_error);
262 self.send(text).await
263 }
264
265 async fn on_text(&mut self, text: &str) -> Result<(), ocpp_ws::WsError> {
266 let version = self.ctx.version;
267 let frame = match Frame::parse(text) {
268 Ok(Frame::Send { .. } | Frame::CallResultError { .. })
269 if version != OcppVersion::V21 =>
270 {
271 return self.unknown_message_type(text, None).await;
273 }
274 Ok(f) => f,
275 Err(FrameError::UnknownMessageType { id, type_id }) => {
276 return self
277 .unknown_message_type(text, id.map(|i| (i, type_id)))
278 .await;
279 }
280 Err(e @ FrameError::Malformed { .. }) => {
281 tracing::warn!(station = %self.ctx.identity, error = %e, "malformed frame");
282 let code = if e.reply_id() == rpc::UNKNOWN_MESSAGE_ID {
283 ErrorCode::RpcFrameworkError
284 } else {
285 ErrorCode::FormatViolation
286 };
287 let id = e.reply_id().to_owned();
288 return self
289 .send_error(&id, &RpcError::new(code, e.to_string()), false)
290 .await;
291 }
292 };
293
294 match frame {
295 Frame::Call {
296 id,
297 action,
298 payload,
299 } => {
300 if !self.ctx.registration.allows(&action) {
301 let err = RpcError::new(
302 ErrorCode::SecurityError,
303 "Charging Station is not accepted by the CSMS; send BootNotification first",
304 );
305 return self.send_error(&id, &err, false).await;
306 }
307 let result = match version {
308 OcppVersion::V16 => v16::handle(&mut self.ctx, &action, payload.get()).await,
309 OcppVersion::V201 => v201::handle(&mut self.ctx, &action, payload.get()).await,
310 OcppVersion::V21 => v21::handle(&mut self.ctx, &action, payload.get()).await,
311 };
312 match result {
313 Ok(response) => match rpc::encode_result(&id, &response) {
314 Ok(t) => self.send(t).await,
315 Err(e) => {
316 self.send_error(&id, &RpcError::internal(e.to_string()), false)
317 .await
318 }
319 },
320 Err(err) => {
321 if err.code != ErrorCode::InternalError {
322 tracing::info!(station = %self.ctx.identity, %action, error = %err, "CALL rejected");
323 }
324 self.send_error(&id, &err, false).await
325 }
326 }
327 }
328 Frame::Send {
329 action, payload, ..
330 } => {
331 v21::handle_send(&mut self.ctx, &action, payload.get()).await;
332 Ok(())
333 }
334 Frame::CallResult { id, payload } => {
335 let Some(p) = self.take_pending(&id) else {
336 return Ok(());
337 };
338 let validated = match version {
339 OcppVersion::V16 => v16::validate_response(&p.action, payload.get()),
340 OcppVersion::V201 => v201::validate_response(&p.action, payload.get()),
341 OcppVersion::V21 => v21::validate_response(&p.action, payload.get()),
342 };
343 match validated {
344 Ok(v) => {
345 let _ = p.reply.send(Ok(v));
346 }
347 Err(violation) => {
348 tracing::warn!(station = %self.ctx.identity, action = %p.action, error = %violation, "invalid CALLRESULT");
349 let raw =
350 serde_json::from_str(payload.get()).unwrap_or(serde_json::Value::Null);
351 let err = RpcError::from(violation.clone());
352 let _ = p
353 .reply
354 .send(Err(CallFailure::InvalidResponse { violation, raw }));
355 if version == OcppVersion::V21 {
356 return self.send_error(&id, &err, true).await;
358 }
359 }
360 }
361 Ok(())
362 }
363 Frame::CallError {
364 id,
365 code,
366 description,
367 details,
368 } => {
369 if let Some(p) = self.take_pending(&id) {
370 let _ = p.reply.send(Err(CallFailure::Error(rpc::error_from_frame(
371 &code,
372 description,
373 &details,
374 ))));
375 } else {
376 tracing::warn!(station = %self.ctx.identity, %id, %code, %description, "CALLERROR for unknown message id");
377 }
378 Ok(())
379 }
380 Frame::CallResultError {
381 id,
382 code,
383 description,
384 ..
385 } => {
386 tracing::warn!(station = %self.ctx.identity, %id, %code, %description, "station could not process our CALLRESULT");
387 Ok(())
388 }
389 }
390 }
391
392 fn take_pending(&mut self, id: &str) -> Option<Pending> {
393 match &self.pending {
394 Some(p) if p.id == id => self.pending.take(),
395 _ => {
396 tracing::debug!(station = %self.ctx.identity, %id, "response for a call that is not pending (late or duplicate)");
397 None
398 }
399 }
400 }
401
402 async fn unknown_message_type(
404 &mut self,
405 text: &str,
406 id: Option<(String, u64)>,
407 ) -> Result<(), ocpp_ws::WsError> {
408 tracing::warn!(station = %self.ctx.identity, frame = %text.chars().take(120).collect::<String>(), "unsupported message type");
409 if self.ctx.version == OcppVersion::V201 {
410 let id = id
411 .map(|(i, _)| i)
412 .or_else(|| Frame::parse(text).ok().map(|f| f.id().to_owned()))
413 .unwrap_or_else(|| rpc::UNKNOWN_MESSAGE_ID.into());
414 let err = RpcError::new(
415 ErrorCode::MessageTypeNotSupported,
416 "message type not supported",
417 );
418 return self.send_error(&id, &err, false).await;
419 }
420 Ok(())
421 }
422}