Skip to content

Commit

Permalink
Incoming message schema validation (optional)
Browse files Browse the repository at this point in the history
  • Loading branch information
dallmann-consulting committed Nov 12, 2023
1 parent 70ce98b commit 7d1d590
Show file tree
Hide file tree
Showing 63 changed files with 2,659 additions and 654 deletions.
8 changes: 4 additions & 4 deletions OCPP.Core.Database/OCPP.Core.Database.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Version>1.1.0</Version>
<Version>1.2.0</Version>
<Company>dallmann consulting GmbH</Company>
<Product>OCPP.Core</Product>
<Authors>Ulrich Dallmann</Authors>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.13" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.13" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.13" />
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion OCPP.Core.Management/Controllers/ApiController.Reset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public async Task<IActionResult> Reset(string Id)
serverApiUrl += "/";
}
Uri uri = new Uri(serverApiUrl);
uri = new Uri(uri, $"Reset/{Uri.EscapeUriString(Id)}");
uri = new Uri(uri, $"Reset/{Uri.EscapeDataString(Id)}");
httpClient.Timeout = new TimeSpan(0, 0, 4); // use short timeout

// API-Key authentication?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public async Task<IActionResult> UnlockConnector(string Id)
serverApiUrl += "/";
}
Uri uri = new Uri(serverApiUrl);
uri = new Uri(uri, $"UnlockConnector/{Uri.EscapeUriString(Id)}");
uri = new Uri(uri, $"UnlockConnector/{Uri.EscapeDataString(Id)}");
httpClient.Timeout = new TimeSpan(0, 0, 4); // use short timeout

// API-Key authentication?
Expand Down
6 changes: 3 additions & 3 deletions OCPP.Core.Management/OCPP.Core.Management.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Version>1.1.0</Version>
<Version>1.2.0</Version>
<Company>dallmann consulting GmbH</Company>
<Authors>Ulrich Dallmann</Authors>
<Product>OCPP.Core</Product>
Expand All @@ -27,8 +27,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Karambolo.Extensions.Logging.File" Version="3.3.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Karambolo.Extensions.Logging.File" Version="3.5.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

<ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions OCPP.Core.Management/Views/Home/ChargePointDetail.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@
}
};
xmlhttp.open("GET", "@Html.Raw(Url.Content("~/API/Reset/" + Uri.EscapeUriString(Model.ChargePointId)))", true);
xmlhttp.open("GET", "@Html.Raw(Url.Content("~/API/Reset/" + Uri.EscapeDataString(Model.ChargePointId)))", true);
xmlhttp.send();
}
}, {
Expand Down Expand Up @@ -245,7 +245,7 @@
}
};
xmlhttp.open("GET", "@Html.Raw(Url.Content("~/API/UnlockConnector/" + Uri.EscapeUriString(Model.ChargePointId)))", true);
xmlhttp.open("GET", "@Html.Raw(Url.Content("~/API/UnlockConnector/" + Uri.EscapeDataString(Model.ChargePointId)))", true);
xmlhttp.send();
}
}, {
Expand Down
2 changes: 1 addition & 1 deletion OCPP.Core.Management/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

"ConnectionStrings": {
//"SQLite": "Filename=.\\..\\SQLite\\OCPP.Core.sqlite;"
"SqlServer": "Server=.;Database=OCPP.Core;Trusted_Connection=True;"
"SqlServer": "Server=.;Database=OCPP.Core;Trusted_Connection=True;Encrypt=false;TrustServerCertificate=false"
},

"ServerApiUrl": "http://localhost:8081/API",
Expand Down
64 changes: 64 additions & 0 deletions OCPP.Core.Server/ControllerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,26 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Schema;
using OCPP.Core.Database;
using OCPP.Core.Server.Messages_OCPP16;

namespace OCPP.Core.Server
{
public partial class ControllerBase
{
/// <summary>
/// Internal string for OCPP protocol version
/// </summary>
protected virtual string ProtocolVersion { get; }

/// <summary>
/// Configuration context for reading app settings
/// </summary>
Expand Down Expand Up @@ -62,6 +71,61 @@ public ControllerBase(IConfiguration config, ILoggerFactory loggerFactory, Charg
}
}

/// <summary>
/// Deserialize and validate JSON message (if schema file exists)
/// </summary>
protected T DeserializeMessage<T>(OCPPMessage msg)
{
string codeBase = Assembly.GetExecutingAssembly().Location;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
codeBase = Path.GetDirectoryName(path);

bool validateMessages = Configuration.GetValue<bool>("ValidateMessages", false);

string schemaJson = null;
if (validateMessages &&
!string.IsNullOrEmpty(codeBase) &&
Directory.Exists(codeBase))
{
string msgTypeName = typeof(T).Name;
string filename = Path.Combine(codeBase, $"Schema{ProtocolVersion}", $"{msgTypeName}.json");
if (File.Exists(filename))
{
Logger.LogTrace("DeserializeMessage => Using schema file: {0}", filename);
schemaJson = File.ReadAllText(filename);
}
}

JsonTextReader reader = new JsonTextReader(new StringReader(msg.JsonPayload));
JsonSerializer serializer = new JsonSerializer();

if (!string.IsNullOrEmpty(schemaJson))
{
JSchemaValidatingReader validatingReader = new JSchemaValidatingReader(reader);
validatingReader.Schema = JSchema.Parse(schemaJson);

IList<string> messages = new List<string>();
validatingReader.ValidationEventHandler += (o, a) => messages.Add(a.Message);
T obj = serializer.Deserialize<T>(validatingReader);
if (messages.Count > 0)
{
foreach (string err in messages)
{
Logger.LogWarning("DeserializeMessage {0} => Validation error: {1}", msg.Action, err);
}
throw new FormatException("Message validation failed");
}
return obj;
}
else
{
// Deserialization WITHOUT schema validation
Logger.LogTrace("DeserializeMessage => Deserialization without schema validation");
return serializer.Deserialize<T>(reader);
}
}


/// <summary>
/// Helper function for creating and updating the ConnectorStatus in then database
Expand Down
2 changes: 1 addition & 1 deletion OCPP.Core.Server/ControllerOCPP16.Authorize.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public string HandleAuthorize(OCPPMessage msgIn, OCPPMessage msgOut)
try
{
Logger.LogTrace("Processing authorize request...");
AuthorizeRequest authorizeRequest = JsonConvert.DeserializeObject<AuthorizeRequest>(msgIn.JsonPayload);
AuthorizeRequest authorizeRequest = DeserializeMessage<AuthorizeRequest>(msgIn);
Logger.LogTrace("Authorize => Message deserialized");
idTag = CleanChargeTagId(authorizeRequest.IdTag, Logger);

Expand Down
2 changes: 1 addition & 1 deletion OCPP.Core.Server/ControllerOCPP16.BootNotification.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public string HandleBootNotification(OCPPMessage msgIn, OCPPMessage msgOut)
try
{
Logger.LogTrace("Processing boot notification...");
BootNotificationRequest bootNotificationRequest = JsonConvert.DeserializeObject<BootNotificationRequest>(msgIn.JsonPayload);
BootNotificationRequest bootNotificationRequest = DeserializeMessage<BootNotificationRequest>(msgIn);
Logger.LogTrace("BootNotification => Message deserialized");

BootNotificationResponse bootNotificationResponse = new BootNotificationResponse();
Expand Down
2 changes: 1 addition & 1 deletion OCPP.Core.Server/ControllerOCPP16.DataTransfer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public string HandleDataTransfer(OCPPMessage msgIn, OCPPMessage msgOut)
try
{
Logger.LogTrace("Processing data transfer...");
DataTransferRequest dataTransferRequest = JsonConvert.DeserializeObject<DataTransferRequest>(msgIn.JsonPayload);
DataTransferRequest dataTransferRequest = DeserializeMessage<DataTransferRequest>(msgIn);
Logger.LogTrace("DataTransfer => Message deserialized");

if (ChargePointStatus != null)
Expand Down
2 changes: 1 addition & 1 deletion OCPP.Core.Server/ControllerOCPP16.MeterValues.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public string HandleMeterValues(OCPPMessage msgIn, OCPPMessage msgOut)
try
{
Logger.LogTrace("Processing meter values...");
MeterValuesRequest meterValueRequest = JsonConvert.DeserializeObject<MeterValuesRequest>(msgIn.JsonPayload);
MeterValuesRequest meterValueRequest = DeserializeMessage<MeterValuesRequest>(msgIn);
Logger.LogTrace("MeterValues => Message deserialized");

connectorId = meterValueRequest.ConnectorId;
Expand Down
2 changes: 1 addition & 1 deletion OCPP.Core.Server/ControllerOCPP16.Reset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public void HandleReset(OCPPMessage msgIn, OCPPMessage msgOut)

try
{
ResetResponse resetResponse = JsonConvert.DeserializeObject<ResetResponse>(msgIn.JsonPayload);
ResetResponse resetResponse = DeserializeMessage<ResetResponse>(msgIn);
Logger.LogInformation("Reset => Answer status: {0}", resetResponse?.Status);
WriteMessageLog(ChargePointStatus?.Id, null, msgOut.Action, resetResponse?.Status.ToString(), msgIn.ErrorCode);

Expand Down
2 changes: 1 addition & 1 deletion OCPP.Core.Server/ControllerOCPP16.StartTransaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public string HandleStartTransaction(OCPPMessage msgIn, OCPPMessage msgOut)
try
{
Logger.LogTrace("Processing startTransaction request...");
StartTransactionRequest startTransactionRequest = JsonConvert.DeserializeObject<StartTransactionRequest>(msgIn.JsonPayload);
StartTransactionRequest startTransactionRequest = DeserializeMessage<StartTransactionRequest>(msgIn);
Logger.LogTrace("StartTransaction => Message deserialized");

string idTag = CleanChargeTagId(startTransactionRequest.IdTag, Logger);
Expand Down
2 changes: 1 addition & 1 deletion OCPP.Core.Server/ControllerOCPP16.StatusNotification.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public string HandleStatusNotification(OCPPMessage msgIn, OCPPMessage msgOut)
try
{
Logger.LogTrace("Processing status notification...");
StatusNotificationRequest statusNotificationRequest = JsonConvert.DeserializeObject<StatusNotificationRequest>(msgIn.JsonPayload);
StatusNotificationRequest statusNotificationRequest = DeserializeMessage<StatusNotificationRequest>(msgIn);
Logger.LogTrace("StatusNotification => Message deserialized");

connectorId = statusNotificationRequest.ConnectorId;
Expand Down
2 changes: 1 addition & 1 deletion OCPP.Core.Server/ControllerOCPP16.StopTransaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public string HandleStopTransaction(OCPPMessage msgIn, OCPPMessage msgOut)
try
{
Logger.LogTrace("Processing stopTransaction request...");
StopTransactionRequest stopTransactionRequest = JsonConvert.DeserializeObject<StopTransactionRequest>(msgIn.JsonPayload);
StopTransactionRequest stopTransactionRequest = DeserializeMessage<StopTransactionRequest>(msgIn);
Logger.LogTrace("StopTransaction => Message deserialized");

string idTag = CleanChargeTagId(stopTransactionRequest.IdTag, Logger);
Expand Down
2 changes: 1 addition & 1 deletion OCPP.Core.Server/ControllerOCPP16.UnlockConnector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public void HandleUnlockConnector(OCPPMessage msgIn, OCPPMessage msgOut)

try
{
UnlockConnectorResponse unlockConnectorResponse = JsonConvert.DeserializeObject<UnlockConnectorResponse>(msgIn.JsonPayload);
UnlockConnectorResponse unlockConnectorResponse = DeserializeMessage<UnlockConnectorResponse>(msgIn);
Logger.LogInformation("HandleUnlockConnector => Answer status: {0}", unlockConnectorResponse?.Status);
WriteMessageLog(ChargePointStatus?.Id, null, msgOut.Action, unlockConnectorResponse?.Status.ToString(), msgIn.ErrorCode);

Expand Down
8 changes: 8 additions & 0 deletions OCPP.Core.Server/ControllerOCPP16.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ namespace OCPP.Core.Server
{
public partial class ControllerOCPP16 : ControllerBase
{
/// <summary>
/// Internal string for OCPP protocol version
/// </summary>
protected override string ProtocolVersion
{
get { return "16"; }
}

/// <summary>
/// Constructor
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion OCPP.Core.Server/ControllerOCPP20.Authorize.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public string HandleAuthorize(OCPPMessage msgIn, OCPPMessage msgOut)
try
{
Logger.LogTrace("Processing authorize request...");
AuthorizeRequest authorizeRequest = JsonConvert.DeserializeObject<AuthorizeRequest>(msgIn.JsonPayload);
AuthorizeRequest authorizeRequest = DeserializeMessage<AuthorizeRequest>(msgIn);
Logger.LogTrace("Authorize => Message deserialized");
idTag = CleanChargeTagId(authorizeRequest.IdToken?.IdToken, Logger);

Expand Down
2 changes: 1 addition & 1 deletion OCPP.Core.Server/ControllerOCPP20.BootNotification.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public string HandleBootNotification(OCPPMessage msgIn, OCPPMessage msgOut)
try
{
Logger.LogTrace("Processing boot notification...");
BootNotificationRequest bootNotificationRequest = JsonConvert.DeserializeObject<BootNotificationRequest>(msgIn.JsonPayload);
BootNotificationRequest bootNotificationRequest = DeserializeMessage<BootNotificationRequest>(msgIn);
Logger.LogTrace("BootNotification => Message deserialized");

bootReason = bootNotificationRequest?.Reason.ToString();
Expand Down
2 changes: 1 addition & 1 deletion OCPP.Core.Server/ControllerOCPP20.ClearedChargingLimit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public string HandleClearedChargingLimit(OCPPMessage msgIn, OCPPMessage msgOut)

try
{
ClearedChargingLimitRequest clearedChargingLimitRequest = JsonConvert.DeserializeObject<ClearedChargingLimitRequest>(msgIn.JsonPayload);
ClearedChargingLimitRequest clearedChargingLimitRequest = DeserializeMessage<ClearedChargingLimitRequest>(msgIn);
Logger.LogTrace("ClearedChargingLimit => Message deserialized");

if (ChargePointStatus != null)
Expand Down
2 changes: 1 addition & 1 deletion OCPP.Core.Server/ControllerOCPP20.DataTransfer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public string HandleDataTransfer(OCPPMessage msgIn, OCPPMessage msgOut)
try
{
Logger.LogTrace("Processing data transfer...");
DataTransferRequest dataTransferRequest = JsonConvert.DeserializeObject<DataTransferRequest>(msgIn.JsonPayload);
DataTransferRequest dataTransferRequest = DeserializeMessage<DataTransferRequest>(msgIn);
Logger.LogTrace("DataTransfer => Message deserialized");

if (ChargePointStatus != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public string HandleFirmwareStatusNotification(OCPPMessage msgIn, OCPPMessage ms

try
{
FirmwareStatusNotificationRequest firmwareStatusNotificationRequest = JsonConvert.DeserializeObject<FirmwareStatusNotificationRequest>(msgIn.JsonPayload);
FirmwareStatusNotificationRequest firmwareStatusNotificationRequest = DeserializeMessage<FirmwareStatusNotificationRequest>(msgIn);
Logger.LogTrace("FirmwareStatusNotification => Message deserialized");


Expand Down
2 changes: 1 addition & 1 deletion OCPP.Core.Server/ControllerOCPP20.LogStatusNotification.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public string HandleLogStatusNotification(OCPPMessage msgIn, OCPPMessage msgOut)

try
{
LogStatusNotificationRequest logStatusNotificationRequest = JsonConvert.DeserializeObject<LogStatusNotificationRequest>(msgIn.JsonPayload);
LogStatusNotificationRequest logStatusNotificationRequest = DeserializeMessage<LogStatusNotificationRequest>(msgIn);
Logger.LogTrace("LogStatusNotification => Message deserialized");


Expand Down
2 changes: 1 addition & 1 deletion OCPP.Core.Server/ControllerOCPP20.MeterValues.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public string HandleMeterValues(OCPPMessage msgIn, OCPPMessage msgOut)
try
{
Logger.LogTrace("Processing meter values...");
MeterValuesRequest meterValueRequest = JsonConvert.DeserializeObject<MeterValuesRequest>(msgIn.JsonPayload);
MeterValuesRequest meterValueRequest = DeserializeMessage<MeterValuesRequest>(msgIn);
Logger.LogTrace("MeterValues => Message deserialized");

connectorId = meterValueRequest.EvseId;
Expand Down
2 changes: 1 addition & 1 deletion OCPP.Core.Server/ControllerOCPP20.NotifyChargingLimit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public string HandleNotifyChargingLimit(OCPPMessage msgIn, OCPPMessage msgOut)

try
{
NotifyChargingLimitRequest notifyChargingLimitRequest = JsonConvert.DeserializeObject<NotifyChargingLimitRequest>(msgIn.JsonPayload);
NotifyChargingLimitRequest notifyChargingLimitRequest = DeserializeMessage<NotifyChargingLimitRequest>(msgIn);
Logger.LogTrace("NotifyChargingLimit => Message deserialized");


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public string HandleNotifyEVChargingSchedule(OCPPMessage msgIn, OCPPMessage msgO

try
{
NotifyEVChargingScheduleRequest notifyEVChargingScheduleRequest = JsonConvert.DeserializeObject<NotifyEVChargingScheduleRequest>(msgIn.JsonPayload);
NotifyEVChargingScheduleRequest notifyEVChargingScheduleRequest = DeserializeMessage<NotifyEVChargingScheduleRequest>(msgIn);
Logger.LogTrace("NotifyEVChargingSchedule => Message deserialized");


Expand Down
2 changes: 1 addition & 1 deletion OCPP.Core.Server/ControllerOCPP20.Reset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public void HandleReset(OCPPMessage msgIn, OCPPMessage msgOut)

try
{
ResetResponse resetResponse = JsonConvert.DeserializeObject<ResetResponse>(msgIn.JsonPayload);
ResetResponse resetResponse = DeserializeMessage<ResetResponse>(msgIn);
Logger.LogInformation("Reset => Answer status: {0}", resetResponse?.Status);
WriteMessageLog(ChargePointStatus?.Id, null, msgOut.Action, resetResponse?.Status.ToString(), msgIn.ErrorCode);

Expand Down
2 changes: 1 addition & 1 deletion OCPP.Core.Server/ControllerOCPP20.StatusNotification.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public string HandleStatusNotification(OCPPMessage msgIn, OCPPMessage msgOut)
try
{
Logger.LogTrace("Processing status notification...");
StatusNotificationRequest statusNotificationRequest = JsonConvert.DeserializeObject<StatusNotificationRequest>(msgIn.JsonPayload);
StatusNotificationRequest statusNotificationRequest = DeserializeMessage<StatusNotificationRequest>(msgIn);
Logger.LogTrace("StatusNotification => Message deserialized");

connectorId = statusNotificationRequest.ConnectorId;
Expand Down
2 changes: 1 addition & 1 deletion OCPP.Core.Server/ControllerOCPP20.TransactionEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public string HandleTransactionEvent(OCPPMessage msgIn, OCPPMessage msgOut)
try
{
Logger.LogTrace("TransactionEvent => Processing transactionEvent request...");
TransactionEventRequest transactionEventRequest = JsonConvert.DeserializeObject<TransactionEventRequest>(msgIn.JsonPayload);
TransactionEventRequest transactionEventRequest = DeserializeMessage<TransactionEventRequest>(msgIn);
Logger.LogTrace("TransactionEvent => Message deserialized");

string idTag = CleanChargeTagId(transactionEventRequest.IdToken?.IdToken, Logger);
Expand Down
Loading

0 comments on commit 7d1d590

Please sign in to comment.