using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using OCPP.Core.Database; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.WebSockets; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; namespace OCPP.Core.Server { public partial class OCPPMiddleware { /// /// Waits for new OCPP V1.6 messages on the open websocket connection and delegates processing to a controller /// private async Task Receive16(ChargePointStatus chargePointStatus, HttpContext context, OCPPCoreContext dbContext) { ILogger logger = _logFactory.CreateLogger("OCPPMiddleware.OCPP16"); ControllerOCPP16 controller16 = new ControllerOCPP16(_configuration, _logFactory, chargePointStatus, dbContext); int maxMessageSizeBytes = _configuration.GetValue("MaxMessageSize", 0); byte[] buffer = new byte[1024 * 4]; MemoryStream memStream = new MemoryStream(buffer.Length); while (chargePointStatus.WebSocket.State == WebSocketState.Open) { WebSocketReceiveResult result = await chargePointStatus.WebSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); if (result != null && result.MessageType != WebSocketMessageType.Close) { logger.LogTrace("OCPPMiddleware.Receive16 => Receiving segment: {0} bytes (EndOfMessage={1} / MsgType={2})", result.Count, result.EndOfMessage, result.MessageType); memStream.Write(buffer, 0, result.Count); // max. allowed message size NOT exceeded - or limit deactivated? if (maxMessageSizeBytes == 0 || memStream.Length <= maxMessageSizeBytes) { if (result.EndOfMessage) { // read complete message into byte array byte[] bMessage = memStream.ToArray(); // reset memory stream für next message memStream = new MemoryStream(buffer.Length); string dumpDir = _configuration.GetValue("MessageDumpDir"); if (!string.IsNullOrWhiteSpace(dumpDir)) { string path = Path.Combine(dumpDir, string.Format("{0}_ocpp16-in.txt", DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss-ffff"))); try { // Write incoming message into dump directory File.WriteAllBytes(path, bMessage); } catch (Exception exp) { logger.LogError(exp, "OCPPMiddleware.Receive16 => Error dumping incoming message to path: '{0}'", path); } } string ocppMessage = UTF8Encoding.UTF8.GetString(bMessage); Match match = Regex.Match(ocppMessage, MessageRegExp); if (match != null && match.Groups != null && match.Groups.Count >= 3) { string messageTypeId = match.Groups[1].Value; string uniqueId = match.Groups[2].Value; string action = match.Groups[3].Value; string jsonPaylod = match.Groups[4].Value; logger.LogInformation("OCPPMiddleware.Receive16 => OCPP-Message: Type={0} / ID={1} / Action={2})", messageTypeId, uniqueId, action); OCPPMessage msgIn = new OCPPMessage(messageTypeId, uniqueId, action, jsonPaylod); if (msgIn.MessageType == "2") { // Request from chargepoint to OCPP server OCPPMessage msgOut = controller16.ProcessRequest(msgIn); // Send OCPP message with optional logging/dump await SendOcpp16Message(msgOut, logger, chargePointStatus.WebSocket); } else if (msgIn.MessageType == "3" || msgIn.MessageType == "4") { // Process answer from chargepoint if (_requestQueue.ContainsKey(msgIn.UniqueId)) { controller16.ProcessAnswer(msgIn, _requestQueue[msgIn.UniqueId]); _requestQueue.Remove(msgIn.UniqueId); } else { logger.LogError("OCPPMiddleware.Receive16 => HttpContext from caller not found / Msg: {0}", ocppMessage); } } else { // Unknown message type logger.LogError("OCPPMiddleware.Receive16 => Unknown message type: {0} / Msg: {1}", msgIn.MessageType, ocppMessage); } } else { logger.LogWarning("OCPPMiddleware.Receive16 => Error in RegEx-Matching: Msg={0})", ocppMessage); } } } else { // max. allowed message size exceeded => close connection (DoS attack?) logger.LogInformation("OCPPMiddleware.Receive16 => Allowed message size exceeded - close connection"); await chargePointStatus.WebSocket.CloseOutputAsync(WebSocketCloseStatus.MessageTooBig, string.Empty, CancellationToken.None); } } else { logger.LogInformation("OCPPMiddleware.Receive16 => WebSocket Closed: CloseStatus={0} / MessageType={1}", result?.CloseStatus, result?.MessageType); await chargePointStatus.WebSocket.CloseOutputAsync((WebSocketCloseStatus)3001, string.Empty, CancellationToken.None); } } logger.LogInformation("OCPPMiddleware.Receive16 => Websocket closed: State={0} / CloseStatus={1}", chargePointStatus.WebSocket.State, chargePointStatus.WebSocket.CloseStatus); ChargePointStatus dummy; _chargePointStatusDict.Remove(chargePointStatus.Id, out dummy); } /// /// Waits for new OCPP V1.6 messages on the open websocket connection and delegates processing to a controller /// private async Task Reset16(ChargePointStatus chargePointStatus, HttpContext apiCallerContext, OCPPCoreContext dbContext) { ILogger logger = _logFactory.CreateLogger("OCPPMiddleware.OCPP16"); ControllerOCPP16 controller16 = new ControllerOCPP16(_configuration, _logFactory, chargePointStatus, dbContext); Messages_OCPP16.ResetRequest resetRequest = new Messages_OCPP16.ResetRequest(); resetRequest.Type = Messages_OCPP16.ResetRequestType.Soft; string jsonResetRequest = JsonConvert.SerializeObject(resetRequest); OCPPMessage msgOut = new OCPPMessage(); msgOut.MessageType = "2"; msgOut.Action = "Reset"; msgOut.UniqueId = Guid.NewGuid().ToString("N"); msgOut.JsonPayload = jsonResetRequest; msgOut.TaskCompletionSource = new TaskCompletionSource(); // store HttpContext with MsgId for later answer processing (=> send anwer to API caller) _requestQueue.Add(msgOut.UniqueId, msgOut); // Send OCPP message with optional logging/dump await SendOcpp16Message(msgOut, logger, chargePointStatus.WebSocket); // Wait for asynchronous chargepoint response and processing string apiResult = await msgOut.TaskCompletionSource.Task; // apiCallerContext.Response.StatusCode = 200; apiCallerContext.Response.ContentType = "application/json"; await apiCallerContext.Response.WriteAsync(apiResult); } /// /// Sends a Unlock-Request to the chargepoint /// private async Task UnlockConnector16(ChargePointStatus chargePointStatus, HttpContext apiCallerContext, OCPPCoreContext dbContext) { ILogger logger = _logFactory.CreateLogger("OCPPMiddleware.OCPP16"); ControllerOCPP16 controller16 = new ControllerOCPP16(_configuration, _logFactory, chargePointStatus, dbContext); Messages_OCPP16.UnlockConnectorRequest unlockConnectorRequest = new Messages_OCPP16.UnlockConnectorRequest(); unlockConnectorRequest.ConnectorId = 0; string jsonResetRequest = JsonConvert.SerializeObject(unlockConnectorRequest); OCPPMessage msgOut = new OCPPMessage(); msgOut.MessageType = "2"; msgOut.Action = "UnlockConnector"; msgOut.UniqueId = Guid.NewGuid().ToString("N"); msgOut.JsonPayload = jsonResetRequest; msgOut.TaskCompletionSource = new TaskCompletionSource(); // store HttpContext with MsgId for later answer processing (=> send anwer to API caller) _requestQueue.Add(msgOut.UniqueId, msgOut); // Send OCPP message with optional logging/dump await SendOcpp16Message(msgOut, logger, chargePointStatus.WebSocket); // Wait for asynchronous chargepoint response and processing string apiResult = await msgOut.TaskCompletionSource.Task; // apiCallerContext.Response.StatusCode = 200; apiCallerContext.Response.ContentType = "application/json"; await apiCallerContext.Response.WriteAsync(apiResult); } private async Task SendOcpp16Message(OCPPMessage msg, ILogger logger, WebSocket webSocket) { string ocppTextMessage = null; if (string.IsNullOrEmpty(msg.ErrorCode)) { if (msg.MessageType == "2") { // OCPP-Request ocppTextMessage = string.Format("[{0},\"{1}\",\"{2}\",{3}]", msg.MessageType, msg.UniqueId, msg.Action, msg.JsonPayload); } else { // OCPP-Response ocppTextMessage = string.Format("[{0},\"{1}\",{2}]", msg.MessageType, msg.UniqueId, msg.JsonPayload); } } else { ocppTextMessage = string.Format("[{0},\"{1}\",\"{2}\",\"{3}\",{4}]", msg.MessageType, msg.UniqueId, msg.ErrorCode, msg.ErrorDescription, "{}"); } logger.LogTrace("OCPPMiddleware.OCPP16 => SendOcppMessage: {0}", ocppTextMessage); if (string.IsNullOrEmpty(ocppTextMessage)) { // invalid message ocppTextMessage = string.Format("[{0},\"{1}\",\"{2}\",\"{3}\",{4}]", "4", string.Empty, Messages_OCPP16.ErrorCodes.ProtocolError, string.Empty, "{}"); } string dumpDir = _configuration.GetValue("MessageDumpDir"); if (!string.IsNullOrWhiteSpace(dumpDir)) { // Write outgoing message into dump directory string path = Path.Combine(dumpDir, string.Format("{0}_ocpp16-out.txt", DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss-ffff"))); try { File.WriteAllText(path, ocppTextMessage); } catch (Exception exp) { logger.LogError(exp, "OCPPMiddleware.SendOcpp16Message=> Error dumping message to path: '{0}'", path); } } byte[] binaryMessage = UTF8Encoding.UTF8.GetBytes(ocppTextMessage); await webSocket.SendAsync(new ArraySegment(binaryMessage, 0, binaryMessage.Length), WebSocketMessageType.Text, true, CancellationToken.None); } } }