diff --git a/example/1.6/cp/charge_point_sim.go b/example/1.6/cp/charge_point_sim.go index 60192617..eb4b3434 100644 --- a/example/1.6/cp/charge_point_sim.go +++ b/example/1.6/cp/charge_point_sim.go @@ -8,6 +8,7 @@ import ( "strconv" "time" + "github.com/lorenzodonini/ocpp-go/ocpp1.6/logging" "github.com/sirupsen/logrus" ocpp16 "github.com/lorenzodonini/ocpp-go/ocpp1.6" @@ -80,6 +81,17 @@ func exampleRoutine(chargePoint ocpp16.ChargePoint, stateHandler *ChargePointHan logDefault(bootConf.GetFeatureName()).Infof("status: %v, interval: %v, current time: %v", bootConf.Status, bootConf.Interval, bootConf.CurrentTime.String()) // Notify connector status updateStatus(stateHandler, 0, core.ChargePointStatusAvailable) + // Security event + _, err = chargePoint.SecurityEventNotification("Event", types.Now()) + checkError(err) + // Send log notification + _, err = chargePoint.LogStatusNotification(logging.UploadLogStatusUploading, 1) + checkError(err) + // Request cert signing + certificate, err := chargePoint.SignCertificate("adsad") + checkError(err) + logDefault(certificate.GetFeatureName()).Infof("status: %v", certificate.Status) + // Wait for some time ... time.Sleep(5 * time.Second) // Simulate charging for connector 1 @@ -166,6 +178,13 @@ func main() { chargePoint.SetReservationHandler(handler) chargePoint.SetRemoteTriggerHandler(handler) chargePoint.SetSmartChargingHandler(handler) + // OCPP 1.6j Security extension + chargePoint.SetCertificateHandler(handler) + chargePoint.SetLogHandler(handler) + chargePoint.SetSecureFirmwareHandler(handler) + chargePoint.SetExtendedTriggerMessageHandler(handler) + chargePoint.SetSecurityHandler(handler) + ocppj.SetLogger(log.WithField("logger", "ocppj")) ws.SetLogger(log.WithField("logger", "websocket")) // Connects to central system @@ -185,7 +204,7 @@ func init() { log = logrus.New() log.SetFormatter(&logrus.TextFormatter{FullTimestamp: true}) // Set this to DebugLevel if you want to retrieve verbose logs from the ocppj and websocket layers - log.SetLevel(logrus.InfoLevel) + log.SetLevel(logrus.ErrorLevel) } // Utility functions diff --git a/example/1.6/cp/handler.go b/example/1.6/cp/handler.go index a7e42de0..a0f34c70 100644 --- a/example/1.6/cp/handler.go +++ b/example/1.6/cp/handler.go @@ -8,11 +8,16 @@ import ( "time" ocpp16 "github.com/lorenzodonini/ocpp-go/ocpp1.6" + "github.com/lorenzodonini/ocpp-go/ocpp1.6/certificates" "github.com/lorenzodonini/ocpp-go/ocpp1.6/core" + "github.com/lorenzodonini/ocpp-go/ocpp1.6/extendedtriggermessage" "github.com/lorenzodonini/ocpp-go/ocpp1.6/firmware" "github.com/lorenzodonini/ocpp-go/ocpp1.6/localauth" + "github.com/lorenzodonini/ocpp-go/ocpp1.6/logging" "github.com/lorenzodonini/ocpp-go/ocpp1.6/remotetrigger" "github.com/lorenzodonini/ocpp-go/ocpp1.6/reservation" + "github.com/lorenzodonini/ocpp-go/ocpp1.6/securefirmware" + "github.com/lorenzodonini/ocpp-go/ocpp1.6/security" "github.com/lorenzodonini/ocpp-go/ocpp1.6/smartcharging" "github.com/lorenzodonini/ocpp-go/ocpp1.6/types" ) @@ -142,7 +147,7 @@ func (handler *ChargePointHandler) OnRemoteStopTransaction(request *core.RemoteS } func (handler *ChargePointHandler) OnReset(request *core.ResetRequest) (confirmation *core.ResetConfirmation, err error) { - //TODO: stop all ongoing transactions + // TODO: stop all ongoing transactions logDefault(request.GetFeatureName()).Warn("no reset logic implemented yet") return core.NewResetConfirmation(core.ResetStatusAccepted), nil } @@ -183,7 +188,7 @@ func (handler *ChargePointHandler) OnSendLocalList(request *localauth.SendLocalL // ------------- Firmware management profile callbacks ------------- func (handler *ChargePointHandler) OnGetDiagnostics(request *firmware.GetDiagnosticsRequest) (confirmation *firmware.GetDiagnosticsConfirmation, err error) { - //TODO: perform diagnostics upload out-of-band + // TODO: perform diagnostics upload out-of-band logDefault(request.GetFeatureName()).Warn("no diagnostics upload logic implemented yet") return firmware.NewGetDiagnosticsConfirmation(), nil } @@ -209,7 +214,7 @@ func (handler *ChargePointHandler) OnTriggerMessage(request *remotetrigger.Trigg status := remotetrigger.TriggerMessageStatusRejected switch request.RequestedMessage { case core.BootNotificationFeatureName: - //TODO: schedule boot notification message + // TODO: schedule boot notification message break case firmware.DiagnosticsStatusNotificationFeatureName: // Schedule diagnostics status notification request @@ -220,7 +225,7 @@ func (handler *ChargePointHandler) OnTriggerMessage(request *remotetrigger.Trigg }() status = remotetrigger.TriggerMessageStatusAccepted case firmware.FirmwareStatusNotificationFeatureName: - //TODO: schedule firmware status notification message + // TODO: schedule firmware status notification message break case core.HeartbeatFeatureName: // Schedule heartbeat request @@ -231,7 +236,7 @@ func (handler *ChargePointHandler) OnTriggerMessage(request *remotetrigger.Trigg }() status = remotetrigger.TriggerMessageStatusAccepted case core.MeterValuesFeatureName: - //TODO: schedule meter values message + // TODO: schedule meter values message break case core.StatusNotificationFeatureName: connectorID := *request.ConnectorId @@ -291,23 +296,58 @@ func (handler *ChargePointHandler) OnCancelReservation(request *reservation.Canc // ------------- Smart charging profile callbacks ------------- func (handler *ChargePointHandler) OnSetChargingProfile(request *smartcharging.SetChargingProfileRequest) (confirmation *smartcharging.SetChargingProfileConfirmation, err error) { - //TODO: handle logic + // TODO: handle logic logDefault(request.GetFeatureName()).Warn("no set charging profile logic implemented yet") return smartcharging.NewSetChargingProfileConfirmation(smartcharging.ChargingProfileStatusNotSupported), nil } func (handler *ChargePointHandler) OnClearChargingProfile(request *smartcharging.ClearChargingProfileRequest) (confirmation *smartcharging.ClearChargingProfileConfirmation, err error) { - //TODO: handle logic + // TODO: handle logic logDefault(request.GetFeatureName()).Warn("no clear charging profile logic implemented yet") return smartcharging.NewClearChargingProfileConfirmation(smartcharging.ClearChargingProfileStatusUnknown), nil } func (handler *ChargePointHandler) OnGetCompositeSchedule(request *smartcharging.GetCompositeScheduleRequest) (confirmation *smartcharging.GetCompositeScheduleConfirmation, err error) { - //TODO: handle logic + // TODO: handle logic logDefault(request.GetFeatureName()).Warn("no get composite schedule logic implemented yet") return smartcharging.NewGetCompositeScheduleConfirmation(smartcharging.GetCompositeScheduleStatusRejected), nil } +func (handler *ChargePointHandler) OnDeleteCertificate(request *certificates.DeleteCertificateRequest) (response *certificates.DeleteCertificateResponse, err error) { + logDefault(request.GetFeatureName()).Infof("certificate %v deleted", request.CertificateHashData) + return certificates.NewDeleteCertificateResponse(certificates.DeleteCertificateStatusAccepted), nil +} + +func (handler *ChargePointHandler) OnGetInstalledCertificateIds(request *certificates.GetInstalledCertificateIdsRequest) (response *certificates.GetInstalledCertificateIdsResponse, err error) { + logDefault(request.GetFeatureName()).Infof("returning installed certificate ids") + return certificates.NewGetInstalledCertificateIdsResponse(certificates.GetInstalledCertificateStatusAccepted), nil +} + +func (handler *ChargePointHandler) OnInstallCertificate(request *certificates.InstallCertificateRequest) (response *certificates.InstallCertificateResponse, err error) { + logDefault(request.GetFeatureName()).Infof("certificate installed") + return certificates.NewInstallCertificateResponse(certificates.CertificateStatusAccepted), nil +} + +func (handler *ChargePointHandler) OnGetLog(request *logging.GetLogRequest) (response *logging.GetLogResponse, err error) { + logDefault(request.GetFeatureName()).Infof("returning log") + return logging.NewGetLogResponse(logging.LogStatusAccepted), nil +} + +func (handler *ChargePointHandler) OnSignedUpdateFirmware(request *securefirmware.SignedUpdateFirmwareRequest) (response *securefirmware.SignedUpdateFirmwareResponse, err error) { + logDefault(request.GetFeatureName()).Infof("signed update firmware request received") + return securefirmware.NewSignedUpdateFirmwareResponse(securefirmware.UpdateFirmwareStatusAccepted), nil +} + +func (handler *ChargePointHandler) OnExtendedTriggerMessage(request *extendedtriggermessage.ExtendedTriggerMessageRequest) (response *extendedtriggermessage.ExtendedTriggerMessageResponse, err error) { + logDefault(request.GetFeatureName()).Infof("extended trigger message received") + return extendedtriggermessage.NewExtendedTriggerMessageResponse(extendedtriggermessage.ExtendedTriggerMessageStatusAccepted), nil +} + +func (handler *ChargePointHandler) OnCertificateSigned(request *security.CertificateSignedRequest) (response *security.CertificateSignedResponse, err error) { + logDefault(request.GetFeatureName()).Infof("certificate signed") + return security.NewCertificateSignedResponse(security.CertificateSignedStatusAccepted), nil +} + func checkError(err error) { if err != nil { log.Fatal(err) diff --git a/example/1.6/cs/central_system_sim.go b/example/1.6/cs/central_system_sim.go index fb6dd69b..6417abf7 100644 --- a/example/1.6/cs/central_system_sim.go +++ b/example/1.6/cs/central_system_sim.go @@ -215,6 +215,12 @@ func main() { centralSystem.SetReservationHandler(handler) centralSystem.SetRemoteTriggerHandler(handler) centralSystem.SetSmartChargingHandler(handler) + + // Add callbacks for OCPP 1.6 security profiles + centralSystem.SetSecurityHandler(handler) + centralSystem.SetSecureFirmwareHandler(handler) + centralSystem.SetLogHandler(handler) + // Add handlers for dis/connection of charge points centralSystem.SetNewChargePointHandler(func(chargePoint ocpp16.ChargePointConnection) { handler.chargePoints[chargePoint.ID()] = &ChargePointState{connectors: map[int]*ConnectorInfo{}, transactions: map[int]*TransactionInfo{}} @@ -237,5 +243,5 @@ func init() { log = logrus.New() log.SetFormatter(&logrus.TextFormatter{FullTimestamp: true}) // Set this to DebugLevel if you want to retrieve verbose logs from the ocppj and websocket layers - log.SetLevel(logrus.InfoLevel) + log.SetLevel(logrus.ErrorLevel) } diff --git a/example/1.6/cs/handler.go b/example/1.6/cs/handler.go index fe231ee8..5599d641 100644 --- a/example/1.6/cs/handler.go +++ b/example/1.6/cs/handler.go @@ -4,6 +4,9 @@ import ( "fmt" "time" + "github.com/lorenzodonini/ocpp-go/ocpp1.6/logging" + "github.com/lorenzodonini/ocpp-go/ocpp1.6/securefirmware" + "github.com/lorenzodonini/ocpp-go/ocpp1.6/security" "github.com/sirupsen/logrus" "github.com/lorenzodonini/ocpp-go/ocpp1.6/core" @@ -130,7 +133,7 @@ func (handler *CentralSystemHandler) OnStartTransaction(chargePointId string, re nextTransactionId += 1 connector.currentTransaction = transaction.id info.transactions[transaction.id] = transaction - //TODO: check billable clients + // TODO: check billable clients logDefault(chargePointId, request.GetFeatureName()).Infof("started transaction %v for connector %v", transaction.id, transaction.connectorId) return core.NewStartTransactionConfirmation(types.NewIdTagInfo(types.AuthorizationStatusAccepted), transaction.id), nil } @@ -146,7 +149,7 @@ func (handler *CentralSystemHandler) OnStopTransaction(chargePointId string, req connector.currentTransaction = -1 transaction.endTime = request.Timestamp transaction.endMeter = request.MeterStop - //TODO: bill charging period to client + // TODO: bill charging period to client } logDefault(chargePointId, request.GetFeatureName()).Infof("stopped transaction %v - %v", request.TransactionId, request.Reason) for _, mv := range request.TransactionData { @@ -179,6 +182,26 @@ func (handler *CentralSystemHandler) OnFirmwareStatusNotification(chargePointId // No callbacks for Local Auth management, Reservation, Remote trigger or Smart Charging profile on central system +func (handler *CentralSystemHandler) OnSecurityEventNotification(chargingStationID string, request *security.SecurityEventNotificationRequest) (response *security.SecurityEventNotificationResponse, err error) { + logDefault(chargingStationID, request.GetFeatureName()).Infof("security event notification received") + return security.NewSecurityEventNotificationResponse(), nil +} + +func (handler *CentralSystemHandler) OnSignCertificate(chargingStationID string, request *security.SignCertificateRequest) (response *security.SignCertificateResponse, err error) { + logDefault(chargingStationID, request.GetFeatureName()).Infof("certificate signing request received") + return security.NewSignCertificateResponse(types.GenericStatusAccepted), nil +} + +func (handler *CentralSystemHandler) OnSignedFirmwareStatusNotification(chargingStationID string, request *securefirmware.SignedFirmwareStatusNotificationRequest) (response *securefirmware.SignedFirmwareStatusNotificationResponse, err error) { + logDefault(chargingStationID, request.GetFeatureName()).Infof("signed firmware status notification received") + return securefirmware.NewFirmwareStatusNotificationResponse(), nil +} + +func (handler *CentralSystemHandler) OnLogStatusNotification(chargingStationID string, request *logging.LogStatusNotificationRequest) (response *logging.LogStatusNotificationResponse, err error) { + logDefault(chargingStationID, request.GetFeatureName()).Infof("log status notification received") + return logging.NewLogStatusNotificationResponse(), nil +} + // Utility functions func logDefault(chargePointId string, feature string) *logrus.Entry { diff --git a/example/1.6/docker-compose.yml b/example/1.6/docker-compose.yml index a249fbb1..292aa378 100644 --- a/example/1.6/docker-compose.yml +++ b/example/1.6/docker-compose.yml @@ -14,6 +14,9 @@ services: - sim tty: true charge-point: + depends_on: + central-system: + condition: service_started build: context: ../.. dockerfile: cp/Dockerfile diff --git a/go.mod b/go.mod index 7dba0b0b..e1761106 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,12 @@ go 1.16 require ( github.com/Shopify/toxiproxy v2.1.4+incompatible github.com/go-playground/locales v0.12.1 // indirect - github.com/go-playground/universal-translator v0.16.0 // indirect + github.com/go-playground/universal-translator v0.16.0 github.com/gorilla/mux v1.7.3 github.com/gorilla/websocket v1.4.1 github.com/kr/pretty v0.1.0 // indirect github.com/leodido/go-urn v1.1.0 // indirect - github.com/relvacode/iso8601 v1.3.0 // indirect + github.com/relvacode/iso8601 v1.3.0 github.com/sirupsen/logrus v1.4.2 github.com/stretchr/testify v1.8.0 golang.org/x/sys v0.0.0-20220804214406-8e32c043e418 // indirect diff --git a/ocpp1.6/central_system.go b/ocpp1.6/central_system.go index a7b0c29d..ef032021 100644 --- a/ocpp1.6/central_system.go +++ b/ocpp1.6/central_system.go @@ -6,11 +6,16 @@ import ( "github.com/lorenzodonini/ocpp-go/internal/callbackqueue" "github.com/lorenzodonini/ocpp-go/ocpp" + "github.com/lorenzodonini/ocpp-go/ocpp1.6/certificates" "github.com/lorenzodonini/ocpp-go/ocpp1.6/core" + "github.com/lorenzodonini/ocpp-go/ocpp1.6/extendedtriggermessage" "github.com/lorenzodonini/ocpp-go/ocpp1.6/firmware" "github.com/lorenzodonini/ocpp-go/ocpp1.6/localauth" + "github.com/lorenzodonini/ocpp-go/ocpp1.6/logging" "github.com/lorenzodonini/ocpp-go/ocpp1.6/remotetrigger" "github.com/lorenzodonini/ocpp-go/ocpp1.6/reservation" + "github.com/lorenzodonini/ocpp-go/ocpp1.6/securefirmware" + "github.com/lorenzodonini/ocpp-go/ocpp1.6/security" "github.com/lorenzodonini/ocpp-go/ocpp1.6/smartcharging" "github.com/lorenzodonini/ocpp-go/ocpp1.6/types" "github.com/lorenzodonini/ocpp-go/ocppj" @@ -18,15 +23,18 @@ import ( ) type centralSystem struct { - server *ocppj.Server - coreHandler core.CentralSystemHandler - localAuthListHandler localauth.CentralSystemHandler - firmwareHandler firmware.CentralSystemHandler - reservationHandler reservation.CentralSystemHandler - remoteTriggerHandler remotetrigger.CentralSystemHandler - smartChargingHandler smartcharging.CentralSystemHandler - callbackQueue callbackqueue.CallbackQueue - errC chan error + server *ocppj.Server + coreHandler core.CentralSystemHandler + localAuthListHandler localauth.CentralSystemHandler + firmwareHandler firmware.CentralSystemHandler + reservationHandler reservation.CentralSystemHandler + remoteTriggerHandler remotetrigger.CentralSystemHandler + smartChargingHandler smartcharging.CentralSystemHandler + logHandler logging.CentralSystemHandler + securityHandler security.CentralSystemHandler + secureFirmwareHandler securefirmware.CentralSystemHandler + callbackQueue callbackqueue.CallbackQueue + errC chan error } func newCentralSystem(server *ocppj.Server) centralSystem { @@ -338,6 +346,103 @@ func (cs *centralSystem) GetCompositeSchedule(clientId string, callback func(*sm return cs.SendRequestAsync(clientId, request, genericCallback) } +func (cs *centralSystem) TriggerMessageExtended(clientId string, callback func(*extendedtriggermessage.ExtendedTriggerMessageResponse, error), requestedMessage extendedtriggermessage.ExtendedTriggerMessageType, props ...func(request *extendedtriggermessage.ExtendedTriggerMessageRequest)) error { + request := extendedtriggermessage.NewExtendedTriggerMessageRequest(requestedMessage) + for _, fn := range props { + fn(request) + } + genericCallback := func(confirmation ocpp.Response, protoError error) { + confirmationCasted := confirmation.(*extendedtriggermessage.ExtendedTriggerMessageResponse) + callback(confirmationCasted, protoError) + } + return cs.SendRequestAsync(clientId, request, genericCallback) + +} + +func (cs *centralSystem) CertificateSigned(clientId string, callback func(*security.CertificateSignedResponse, error), csr string, props ...func(request *security.CertificateSignedRequest)) error { + request := security.NewCertificateSignedRequest(csr) + for _, fn := range props { + fn(request) + } + genericCallback := func(confirmation ocpp.Response, protoError error) { + confirmationCasted := confirmation.(*security.CertificateSignedResponse) + callback(confirmationCasted, protoError) + } + return cs.SendRequestAsync(clientId, request, genericCallback) +} + +func (cs *centralSystem) SignedUpdateFirmware(clientId string, callback func(*securefirmware.SignedUpdateFirmwareResponse, error), requestId int, firmware securefirmware.Firmware, props ...func(request *securefirmware.SignedUpdateFirmwareRequest)) error { + request := securefirmware.NewSignedUpdateFirmwareRequest(requestId, firmware) + for _, fn := range props { + fn(request) + } + genericCallback := func(confirmation ocpp.Response, protoError error) { + confirmationCasted := confirmation.(*securefirmware.SignedUpdateFirmwareResponse) + callback(confirmationCasted, protoError) + } + return cs.SendRequestAsync(clientId, request, genericCallback) +} + +func (cs *centralSystem) GetInstalledCertificateIds(clientId string, callback func(*certificates.GetInstalledCertificateIdsResponse, error), props ...func(request *certificates.GetInstalledCertificateIdsRequest)) error { + request := certificates.NewGetInstalledCertificateIdsRequest() + for _, fn := range props { + fn(request) + } + genericCallback := func(confirmation ocpp.Response, protoError error) { + confirmationCasted := confirmation.(*certificates.GetInstalledCertificateIdsResponse) + callback(confirmationCasted, protoError) + } + return cs.SendRequestAsync(clientId, request, genericCallback) +} + +func (cs *centralSystem) InstallCertificate(clientId string, callback func(*certificates.InstallCertificateResponse, error), certificateType types.CertificateUse, certificate string, props ...func(request *certificates.InstallCertificateRequest)) error { + request := certificates.NewInstallCertificateRequest(certificateType, certificate) + for _, fn := range props { + fn(request) + } + genericCallback := func(confirmation ocpp.Response, protoError error) { + confirmationCasted := confirmation.(*certificates.InstallCertificateResponse) + callback(confirmationCasted, protoError) + } + return cs.SendRequestAsync(clientId, request, genericCallback) +} + +func (cs *centralSystem) DeleteCertificate(clientId string, callback func(*certificates.DeleteCertificateResponse, error), certificateHashData types.CertificateHashData, props ...func(request *certificates.DeleteCertificateRequest)) error { + request := certificates.NewDeleteCertificateRequest(certificateHashData) + for _, fn := range props { + fn(request) + } + genericCallback := func(confirmation ocpp.Response, protoError error) { + confirmationCasted := confirmation.(*certificates.DeleteCertificateResponse) + callback(confirmationCasted, protoError) + } + return cs.SendRequestAsync(clientId, request, genericCallback) +} + +func (cs *centralSystem) GetLog(clientId string, callback func(*logging.GetLogResponse, error), logType logging.LogType, requestID int, logParameters logging.LogParameters, props ...func(request *logging.GetLogRequest)) error { + request := logging.NewGetLogRequest(logType, requestID, logParameters) + for _, fn := range props { + fn(request) + } + genericCallback := func(confirmation ocpp.Response, protoError error) { + confirmationCasted := confirmation.(*logging.GetLogResponse) + callback(confirmationCasted, protoError) + } + return cs.SendRequestAsync(clientId, request, genericCallback) +} + +func (cs *centralSystem) SetSecurityHandler(handler security.CentralSystemHandler) { + cs.securityHandler = handler +} + +func (cs *centralSystem) SetLogHandler(handler logging.CentralSystemHandler) { + cs.logHandler = handler +} + +func (cs *centralSystem) SetSecureFirmwareHandler(handler securefirmware.CentralSystemHandler) { + cs.secureFirmwareHandler = handler +} + func (cs *centralSystem) SetCoreHandler(handler core.CentralSystemHandler) { cs.coreHandler = handler } @@ -393,7 +498,13 @@ func (cs *centralSystem) SendRequestAsync(clientId string, request ocpp.Request, firmware.GetDiagnosticsFeatureName, firmware.UpdateFirmwareFeatureName, reservation.ReserveNowFeatureName, reservation.CancelReservationFeatureName, remotetrigger.TriggerMessageFeatureName, - smartcharging.SetChargingProfileFeatureName, smartcharging.ClearChargingProfileFeatureName, smartcharging.GetCompositeScheduleFeatureName: + smartcharging.SetChargingProfileFeatureName, smartcharging.ClearChargingProfileFeatureName, smartcharging.GetCompositeScheduleFeatureName, + // OCPP 1.6 Extension supported messages + security.CertificateSignedFeatureName, + securefirmware.SignedUpdateFirmwareFeatureName, + logging.GetLogFeatureName, + extendedtriggermessage.ExtendedTriggerMessageFeatureName, + certificates.GetInstalledCertificateIdsFeatureName, certificates.DeleteCertificateFeatureName, certificates.InstallCertificateFeatureName: default: return fmt.Errorf("unsupported action %v on central system, cannot send request", featureName) } @@ -504,6 +615,21 @@ func (cs *centralSystem) handleIncomingRequest(chargePoint ChargePointConnection cs.notSupportedError(chargePoint.ID(), requestId, action) return } + case logging.ProfileName: + if cs.logHandler == nil { + cs.notSupportedError(chargePoint.ID(), requestId, action) + return + } + case security.ProfileName: + if cs.securityHandler == nil { + cs.notSupportedError(chargePoint.ID(), requestId, action) + return + } + case securefirmware.ProfileName: + if cs.secureFirmwareHandler == nil { + cs.notSupportedError(chargePoint.ID(), requestId, action) + return + } } } var confirmation ocpp.Response @@ -531,6 +657,14 @@ func (cs *centralSystem) handleIncomingRequest(chargePoint ChargePointConnection confirmation, err = cs.firmwareHandler.OnDiagnosticsStatusNotification(chargePoint.ID(), request.(*firmware.DiagnosticsStatusNotificationRequest)) case firmware.FirmwareStatusNotificationFeatureName: confirmation, err = cs.firmwareHandler.OnFirmwareStatusNotification(chargePoint.ID(), request.(*firmware.FirmwareStatusNotificationRequest)) + case security.SignCertificateFeatureName: + confirmation, err = cs.securityHandler.OnSignCertificate(chargePoint.ID(), request.(*security.SignCertificateRequest)) + case security.SecurityEventNotificationFeatureName: + confirmation, err = cs.securityHandler.OnSecurityEventNotification(chargePoint.ID(), request.(*security.SecurityEventNotificationRequest)) + case logging.LogStatusNotificationFeatureName: + confirmation, err = cs.logHandler.OnLogStatusNotification(chargePoint.ID(), request.(*logging.LogStatusNotificationRequest)) + case securefirmware.SignedFirmwareStatusNotificationFeatureName: + confirmation, err = cs.secureFirmwareHandler.OnSignedFirmwareStatusNotification(chargePoint.ID(), request.(*securefirmware.SignedFirmwareStatusNotificationRequest)) default: cs.notSupportedError(chargePoint.ID(), requestId, action) return diff --git a/ocpp1.6/certificates/certificates.go b/ocpp1.6/certificates/certificates.go new file mode 100644 index 00000000..3ed15bf8 --- /dev/null +++ b/ocpp1.6/certificates/certificates.go @@ -0,0 +1,23 @@ +// The diagnostics functional block contains OCPP 2.0 features than enable remote diagnostics of problems with a charging station. +package certificates + +import "github.com/lorenzodonini/ocpp-go/ocpp" + +// Needs to be implemented by Charging stations for handling messages part of the OCPP 1.6j security extension. +type ChargePointHandler interface { + // OnDeleteCertificate is called on a charging station whenever a DeleteCertificateRequest is received from the CSMS. + OnDeleteCertificate(request *DeleteCertificateRequest) (response *DeleteCertificateResponse, err error) + // OnGetInstalledCertificateIds is called on a charging station whenever a GetInstalledCertificateIdsRequest is received from the CSMS. + OnGetInstalledCertificateIds(request *GetInstalledCertificateIdsRequest) (response *GetInstalledCertificateIdsResponse, err error) + // OnInstallCertificate is called on a charging station whenever an InstallCertificateRequest is received from the CSMS. + OnInstallCertificate(request *InstallCertificateRequest) (response *InstallCertificateResponse, err error) +} + +const ProfileName = "Certificates" + +var Profile = ocpp.NewProfile( + ProfileName, + InstallCertificateFeature{}, + DeleteCertificateFeature{}, + GetInstalledCertificateIdsFeature{}, +) diff --git a/ocpp1.6/certificates/delete_certificate.go b/ocpp1.6/certificates/delete_certificate.go new file mode 100644 index 00000000..25f6c5c8 --- /dev/null +++ b/ocpp1.6/certificates/delete_certificate.go @@ -0,0 +1,81 @@ +package certificates + +import ( + "reflect" + + "gopkg.in/go-playground/validator.v9" + + "github.com/lorenzodonini/ocpp-go/ocpp1.6/types" +) + +// -------------------- Delete Certificate (CSMS -> CS) -------------------- + +const DeleteCertificateFeatureName = "DeleteCertificate" + +// Status returned in response to DeleteCertificateRequest. +type DeleteCertificateStatus string + +const ( + DeleteCertificateStatusAccepted DeleteCertificateStatus = "Accepted" + DeleteCertificateStatusFailed DeleteCertificateStatus = "Failed" + DeleteCertificateStatusNotFound DeleteCertificateStatus = "NotFound" +) + +func isValidDeleteCertificateStatus(fl validator.FieldLevel) bool { + status := DeleteCertificateStatus(fl.Field().String()) + switch status { + case DeleteCertificateStatusAccepted, DeleteCertificateStatusFailed, DeleteCertificateStatusNotFound: + return true + default: + return false + } +} + +// The field definition of the DeleteCertificate request payload sent by the CSMS to the Charging Station. +type DeleteCertificateRequest struct { + CertificateHashData types.CertificateHashData `json:"certificateHashData" validate:"required"` +} + +// This field definition of the DeleteCertificate response payload, sent by the Charging Station to the CSMS in response to a DeleteCertificateRequest. +// In case the request was invalid, or couldn't be processed, an error will be sent instead. +type DeleteCertificateResponse struct { + Status DeleteCertificateStatus `json:"status" validate:"required,deleteCertificateStatus16"` +} + +// The CSMS requests the Charging Station to delete a specific installed certificate by sending a DeleteCertificateRequest. +// The Charging Station responds with a DeleteCertificateResponse. +type DeleteCertificateFeature struct{} + +func (f DeleteCertificateFeature) GetFeatureName() string { + return DeleteCertificateFeatureName +} + +func (f DeleteCertificateFeature) GetRequestType() reflect.Type { + return reflect.TypeOf(DeleteCertificateRequest{}) +} + +func (f DeleteCertificateFeature) GetResponseType() reflect.Type { + return reflect.TypeOf(DeleteCertificateResponse{}) +} + +func (r DeleteCertificateRequest) GetFeatureName() string { + return DeleteCertificateFeatureName +} + +func (c DeleteCertificateResponse) GetFeatureName() string { + return DeleteCertificateFeatureName +} + +// Creates a new DeleteCertificateRequest, containing all required fields. There are no optional fields for this message. +func NewDeleteCertificateRequest(certificateHashData types.CertificateHashData) *DeleteCertificateRequest { + return &DeleteCertificateRequest{CertificateHashData: certificateHashData} +} + +// Creates a new DeleteCertificateResponse, containing all required fields. Optional fields may be set afterwards. +func NewDeleteCertificateResponse(status DeleteCertificateStatus) *DeleteCertificateResponse { + return &DeleteCertificateResponse{Status: status} +} + +func init() { + _ = types.Validate.RegisterValidation("deleteCertificateStatus16", isValidDeleteCertificateStatus) +} diff --git a/ocpp1.6/certificates/get_installed_certificates.go b/ocpp1.6/certificates/get_installed_certificates.go new file mode 100644 index 00000000..9346202c --- /dev/null +++ b/ocpp1.6/certificates/get_installed_certificates.go @@ -0,0 +1,80 @@ +package certificates + +import ( + "reflect" + + "github.com/lorenzodonini/ocpp-go/ocpp1.6/types" + "gopkg.in/go-playground/validator.v9" +) + +// -------------------- Get Installed Certificate IDs (CSMS -> CS) -------------------- + +const GetInstalledCertificateIdsFeatureName = "GetInstalledCertificateIds" + +// Status returned in response to GetInstalledCertificateIdsRequest, that indicates whether certificate signing has been accepted or rejected. +type GetInstalledCertificateStatus string + +const ( + GetInstalledCertificateStatusAccepted GetInstalledCertificateStatus = "Accepted" // Normal successful completion (no errors). + GetInstalledCertificateStatusNotFound GetInstalledCertificateStatus = "NotFound" // Requested resource not found +) + +func isValidGetInstalledCertificateStatus(fl validator.FieldLevel) bool { + status := GetInstalledCertificateStatus(fl.Field().String()) + switch status { + case GetInstalledCertificateStatusAccepted, GetInstalledCertificateStatusNotFound: + return true + default: + return false + } +} + +// The field definition of the GetInstalledCertificateIdsRequest PDU sent by the CSMS to the Charging Station. +type GetInstalledCertificateIdsRequest struct { + CertificateTypes []types.CertificateUse `json:"certificateType" validate:"omitempty,dive,certificateUse16"` +} + +// The field definition of the GetInstalledCertificateIds response payload sent by the Charging Station to the CSMS in response to a GetInstalledCertificateIdsRequest. +type GetInstalledCertificateIdsResponse struct { + Status GetInstalledCertificateStatus `json:"status" validate:"required,getInstalledCertificateStatus16"` + CertificateHashData []types.CertificateHashData `json:"certificateHashData,omitempty" validate:"omitempty,dive"` +} + +// To facilitate the management of the Charging Station’s installed certificates, a method of retrieving the installed certificates is provided. +// The CSMS requests the Charging Station to send a list of installed certificates by sending a GetInstalledCertificateIdsRequest. +// The Charging Station responds with a GetInstalledCertificateIdsResponse. +type GetInstalledCertificateIdsFeature struct{} + +func (f GetInstalledCertificateIdsFeature) GetFeatureName() string { + return GetInstalledCertificateIdsFeatureName +} + +func (f GetInstalledCertificateIdsFeature) GetRequestType() reflect.Type { + return reflect.TypeOf(GetInstalledCertificateIdsRequest{}) +} + +func (f GetInstalledCertificateIdsFeature) GetResponseType() reflect.Type { + return reflect.TypeOf(GetInstalledCertificateIdsResponse{}) +} + +func (r GetInstalledCertificateIdsRequest) GetFeatureName() string { + return GetInstalledCertificateIdsFeatureName +} + +func (c GetInstalledCertificateIdsResponse) GetFeatureName() string { + return GetInstalledCertificateIdsFeatureName +} + +// Creates a new GetInstalledCertificateIdsRequest, containing all required fields. There are no optional fields for this message. +func NewGetInstalledCertificateIdsRequest() *GetInstalledCertificateIdsRequest { + return &GetInstalledCertificateIdsRequest{} +} + +// Creates a new NewGetInstalledCertificateIdsResponse, containing all required fields. Additional optional fields may be set afterwards. +func NewGetInstalledCertificateIdsResponse(status GetInstalledCertificateStatus) *GetInstalledCertificateIdsResponse { + return &GetInstalledCertificateIdsResponse{Status: status} +} + +func init() { + _ = types.Validate.RegisterValidation("getInstalledCertificateStatus16", isValidGetInstalledCertificateStatus) +} diff --git a/ocpp1.6/certificates/install_certificate.go b/ocpp1.6/certificates/install_certificate.go new file mode 100644 index 00000000..9f972827 --- /dev/null +++ b/ocpp1.6/certificates/install_certificate.go @@ -0,0 +1,83 @@ +package certificates + +import ( + "reflect" + + "github.com/lorenzodonini/ocpp-go/ocpp1.6/types" + "gopkg.in/go-playground/validator.v9" +) + +// -------------------- Install Certificate (CSMS -> CS) -------------------- + +const InstallCertificateFeatureName = "InstallCertificate" + +// Charging Station indicates if installation was successful. +type InstallCertificateStatus string + +const ( + CertificateStatusAccepted InstallCertificateStatus = "Accepted" + CertificateStatusRejected InstallCertificateStatus = "Rejected" + CertificateStatusFailed InstallCertificateStatus = "Failed" +) + +func isValidInstallCertificateStatus(fl validator.FieldLevel) bool { + status := InstallCertificateStatus(fl.Field().String()) + switch status { + case CertificateStatusAccepted, CertificateStatusRejected, CertificateStatusFailed: + return true + default: + return false + } +} + +// The field definition of the InstallCertificate request payload sent by the CSMS to the Charging Station. +type InstallCertificateRequest struct { + CertificateType types.CertificateUse `json:"certificateType" validate:"required,certificateUse16"` // Indicates the certificate type that is sent. + Certificate string `json:"certificate" validate:"required,max=5500"` // A PEM encoded X.509 certificate. +} + +// This field definition of the InstallCertificate response payload, sent by the Charging Station to the CSMS in response to a InstallCertificateRequest. +// In case the request was invalid, or couldn't be processed, an error will be sent instead. +type InstallCertificateResponse struct { + Status InstallCertificateStatus `json:"status" validate:"required,installCertificateStatus16"` +} + +// The CSMS requests the Charging Station to install a new certificate by sending an InstallCertificateRequest. +// The certificate may be a root CA certificate, a Sub-CA certificate for an eMobility Operator, Charging Station operator, or a V2G root certificate. +// +// The Charging Station responds with an InstallCertificateResponse. +type InstallCertificateFeature struct{} + +func (f InstallCertificateFeature) GetFeatureName() string { + return InstallCertificateFeatureName +} + +func (f InstallCertificateFeature) GetRequestType() reflect.Type { + return reflect.TypeOf(InstallCertificateRequest{}) +} + +func (f InstallCertificateFeature) GetResponseType() reflect.Type { + return reflect.TypeOf(InstallCertificateResponse{}) +} + +func (r InstallCertificateRequest) GetFeatureName() string { + return InstallCertificateFeatureName +} + +func (c InstallCertificateResponse) GetFeatureName() string { + return InstallCertificateFeatureName +} + +// Creates a new InstallCertificateRequest, containing all required fields. There are no optional fields for this message. +func NewInstallCertificateRequest(certificateType types.CertificateUse, certificate string) *InstallCertificateRequest { + return &InstallCertificateRequest{CertificateType: certificateType, Certificate: certificate} +} + +// Creates a new InstallCertificateResponse, containing all required fields. There are no optional fields for this message. +func NewInstallCertificateResponse(status InstallCertificateStatus) *InstallCertificateResponse { + return &InstallCertificateResponse{Status: status} +} + +func init() { + _ = types.Validate.RegisterValidation("installCertificateStatus16", isValidInstallCertificateStatus) +} diff --git a/ocpp1.6/charge_point.go b/ocpp1.6/charge_point.go index f09c700d..d96e0b40 100644 --- a/ocpp1.6/charge_point.go +++ b/ocpp1.6/charge_point.go @@ -6,29 +6,39 @@ import ( "github.com/lorenzodonini/ocpp-go/internal/callbackqueue" "github.com/lorenzodonini/ocpp-go/ocpp" + "github.com/lorenzodonini/ocpp-go/ocpp1.6/certificates" "github.com/lorenzodonini/ocpp-go/ocpp1.6/core" + "github.com/lorenzodonini/ocpp-go/ocpp1.6/extendedtriggermessage" "github.com/lorenzodonini/ocpp-go/ocpp1.6/firmware" "github.com/lorenzodonini/ocpp-go/ocpp1.6/localauth" + "github.com/lorenzodonini/ocpp-go/ocpp1.6/logging" "github.com/lorenzodonini/ocpp-go/ocpp1.6/remotetrigger" "github.com/lorenzodonini/ocpp-go/ocpp1.6/reservation" + "github.com/lorenzodonini/ocpp-go/ocpp1.6/securefirmware" + "github.com/lorenzodonini/ocpp-go/ocpp1.6/security" "github.com/lorenzodonini/ocpp-go/ocpp1.6/smartcharging" "github.com/lorenzodonini/ocpp-go/ocpp1.6/types" "github.com/lorenzodonini/ocpp-go/ocppj" ) type chargePoint struct { - client *ocppj.Client - coreHandler core.ChargePointHandler - localAuthListHandler localauth.ChargePointHandler - firmwareHandler firmware.ChargePointHandler - reservationHandler reservation.ChargePointHandler - remoteTriggerHandler remotetrigger.ChargePointHandler - smartChargingHandler smartcharging.ChargePointHandler - confirmationHandler chan ocpp.Response - errorHandler chan error - callbacks callbackqueue.CallbackQueue - stopC chan struct{} - errC chan error // external error channel + client *ocppj.Client + coreHandler core.ChargePointHandler + localAuthListHandler localauth.ChargePointHandler + firmwareHandler firmware.ChargePointHandler + reservationHandler reservation.ChargePointHandler + remoteTriggerHandler remotetrigger.ChargePointHandler + smartChargingHandler smartcharging.ChargePointHandler + securityHandler security.ChargePointHandler + logHandler logging.ChargePointHandler + extendedTriggerMessageHandler extendedtriggermessage.ChargePointHandler + secureFirmwareHandler securefirmware.ChargePointHandler + certificateHandler certificates.ChargePointHandler + confirmationHandler chan ocpp.Response + errorHandler chan error + callbacks callbackqueue.CallbackQueue + stopC chan struct{} + errC chan error // external error channel } func (cp *chargePoint) error(err error) { @@ -181,6 +191,54 @@ func (cp *chargePoint) FirmwareStatusNotification(status firmware.FirmwareStatus } } +func (cp *chargePoint) SecurityEventNotification(typ string, timestamp *types.DateTime, props ...func(request *security.SecurityEventNotificationRequest)) (*security.SecurityEventNotificationResponse, error) { + request := security.NewSecurityEventNotificationRequest(typ, timestamp) + for _, fn := range props { + fn(request) + } + confirmation, err := cp.SendRequest(request) + if err != nil { + return nil, err + } + return confirmation.(*security.SecurityEventNotificationResponse), err +} + +func (cp *chargePoint) SignCertificate(CSR string, props ...func(request *security.SignCertificateRequest)) (*security.SignCertificateResponse, error) { + request := security.NewSignCertificateRequest(CSR) + for _, fn := range props { + fn(request) + } + confirmation, err := cp.SendRequest(request) + if err != nil { + return nil, err + } + return confirmation.(*security.SignCertificateResponse), err +} + +func (cp *chargePoint) SignedUpdateFirmwareStatusNotification(status securefirmware.FirmwareStatus, props ...func(request *securefirmware.SignedFirmwareStatusNotificationRequest)) (*securefirmware.SignedFirmwareStatusNotificationResponse, error) { + request := securefirmware.NewFirmwareStatusNotificationRequest(status) + for _, fn := range props { + fn(request) + } + confirmation, err := cp.SendRequest(request) + if err != nil { + return nil, err + } + return confirmation.(*securefirmware.SignedFirmwareStatusNotificationResponse), err +} + +func (cp *chargePoint) LogStatusNotification(status logging.UploadLogStatus, requestId int, props ...func(request *logging.LogStatusNotificationRequest)) (*logging.LogStatusNotificationResponse, error) { + request := logging.NewLogStatusNotificationRequest(status, requestId) + for _, fn := range props { + fn(request) + } + confirmation, err := cp.SendRequest(request) + if err != nil { + return nil, err + } + return confirmation.(*logging.LogStatusNotificationResponse), err +} + func (cp *chargePoint) SetCoreHandler(handler core.ChargePointHandler) { cp.coreHandler = handler } @@ -205,6 +263,26 @@ func (cp *chargePoint) SetSmartChargingHandler(handler smartcharging.ChargePoint cp.smartChargingHandler = handler } +func (cp *chargePoint) SetSecurityHandler(handler security.ChargePointHandler) { + cp.securityHandler = handler +} + +func (cp *chargePoint) SetLogHandler(handler logging.ChargePointHandler) { + cp.logHandler = handler +} + +func (cp *chargePoint) SetExtendedTriggerMessageHandler(handler extendedtriggermessage.ChargePointHandler) { + cp.extendedTriggerMessageHandler = handler +} + +func (cp *chargePoint) SetSecureFirmwareHandler(handler securefirmware.ChargePointHandler) { + cp.secureFirmwareHandler = handler +} + +func (cp *chargePoint) SetCertificateHandler(handler certificates.ChargePointHandler) { + cp.certificateHandler = handler +} + func (cp *chargePoint) SendRequest(request ocpp.Request) (ocpp.Response, error) { featureName := request.GetFeatureName() if _, found := cp.client.GetProfileForFeature(featureName); !found { @@ -245,7 +323,10 @@ func (cp *chargePoint) SendRequestAsync(request ocpp.Request, callback func(conf } switch featureName { case core.AuthorizeFeatureName, core.BootNotificationFeatureName, core.DataTransferFeatureName, core.HeartbeatFeatureName, core.MeterValuesFeatureName, core.StartTransactionFeatureName, core.StopTransactionFeatureName, core.StatusNotificationFeatureName, - firmware.DiagnosticsStatusNotificationFeatureName, firmware.FirmwareStatusNotificationFeatureName: + firmware.DiagnosticsStatusNotificationFeatureName, firmware.FirmwareStatusNotificationFeatureName, + logging.LogStatusNotificationFeatureName, + securefirmware.SignedFirmwareStatusNotificationFeatureName, + security.SecurityEventNotificationFeatureName, security.SignCertificateFeatureName: break default: return fmt.Errorf("unsupported action %v on charge point, cannot send request", featureName) @@ -411,6 +492,31 @@ func (cp *chargePoint) handleIncomingRequest(request ocpp.Request, requestId str cp.notSupportedError(requestId, action) return } + case security.ProfileName: + if cp.securityHandler == nil { + cp.notSupportedError(requestId, action) + return + } + case logging.ProfileName: + if cp.logHandler == nil { + cp.notSupportedError(requestId, action) + return + } + case extendedtriggermessage.ProfileName: + if cp.extendedTriggerMessageHandler == nil { + cp.notSupportedError(requestId, action) + return + } + case securefirmware.ProfileName: + if cp.secureFirmwareHandler == nil { + cp.notSupportedError(requestId, action) + return + } + case certificates.ProfileName: + if cp.certificateHandler == nil { + cp.notSupportedError(requestId, action) + return + } } } // Process request @@ -456,6 +562,20 @@ func (cp *chargePoint) handleIncomingRequest(request ocpp.Request, requestId str confirmation, err = cp.smartChargingHandler.OnClearChargingProfile(request.(*smartcharging.ClearChargingProfileRequest)) case smartcharging.GetCompositeScheduleFeatureName: confirmation, err = cp.smartChargingHandler.OnGetCompositeSchedule(request.(*smartcharging.GetCompositeScheduleRequest)) + case security.CertificateSignedFeatureName: + confirmation, err = cp.securityHandler.OnCertificateSigned(request.(*security.CertificateSignedRequest)) + case logging.GetLogFeatureName: + confirmation, err = cp.logHandler.OnGetLog(request.(*logging.GetLogRequest)) + case securefirmware.SignedUpdateFirmwareFeatureName: + confirmation, err = cp.secureFirmwareHandler.OnSignedUpdateFirmware(request.(*securefirmware.SignedUpdateFirmwareRequest)) + case certificates.GetInstalledCertificateIdsFeatureName: + confirmation, err = cp.certificateHandler.OnGetInstalledCertificateIds(request.(*certificates.GetInstalledCertificateIdsRequest)) + case certificates.DeleteCertificateFeatureName: + confirmation, err = cp.certificateHandler.OnDeleteCertificate(request.(*certificates.DeleteCertificateRequest)) + case certificates.InstallCertificateFeatureName: + confirmation, err = cp.certificateHandler.OnInstallCertificate(request.(*certificates.InstallCertificateRequest)) + case extendedtriggermessage.ExtendedTriggerMessageFeatureName: + confirmation, err = cp.extendedTriggerMessageHandler.OnExtendedTriggerMessage(request.(*extendedtriggermessage.ExtendedTriggerMessageRequest)) default: cp.notSupportedError(requestId, action) return diff --git a/ocpp1.6/extendedtriggermessage/extended_trigger_message.go b/ocpp1.6/extendedtriggermessage/extended_trigger_message.go new file mode 100644 index 00000000..7bd97240 --- /dev/null +++ b/ocpp1.6/extendedtriggermessage/extended_trigger_message.go @@ -0,0 +1,105 @@ +package extendedtriggermessage + +import ( + "reflect" + + "github.com/lorenzodonini/ocpp-go/ocpp1.6/types" + "gopkg.in/go-playground/validator.v9" +) + +const ExtendedTriggerMessageFeatureName = "ExtendedTriggerMessage" + +type ExtendedTriggerMessageFeature struct{} + +func (e ExtendedTriggerMessageFeature) GetFeatureName() string { + return ExtendedTriggerMessageFeatureName +} + +func (e ExtendedTriggerMessageFeature) GetRequestType() reflect.Type { + return reflect.TypeOf(ExtendedTriggerMessageRequest{}) +} + +func (e ExtendedTriggerMessageFeature) GetResponseType() reflect.Type { + return reflect.TypeOf(ExtendedTriggerMessageResponse{}) +} + +type ExtendedTriggerMessageType string + +type ExtendedTriggerMessageStatus string + +const ( + ExtendedTriggerMessageTypeBootNotification ExtendedTriggerMessageType = "BootNotification" // This contains the field definition of a diagnostics log file + ExtendedTriggerMessageTypeLogStatusNotification ExtendedTriggerMessageType = "LogStatusNotification" // Sent by the CSMS to the Charging Station to request that the Charging Station uploads the security log + ExtendedTriggerMessageTypeHeartbeat ExtendedTriggerMessageType = "Heartbeat" // Accepted this log upload. This does not mean the log file is uploaded is successfully, the Charging Station will now start the log file upload. + ExtendedTriggerMessageTypeMeterValues ExtendedTriggerMessageType = "MeterValues" // Log update request rejected. + ExtendedTriggerMessageTypeSignChargingStationCertificate ExtendedTriggerMessageType = "SignChargePointCertificate" // Accepted this log upload, but in doing this has canceled an ongoing log file upload. + ExtendedTriggerMessageTypeFirmwareStatusNotification ExtendedTriggerMessageType = "FirmwareStatusNotification" // Accepted this log upload, but in doing this has canceled an ongoing log file upload. + ExtendedTriggerMessageTypeStatusNotification ExtendedTriggerMessageType = "StatusNotification" // Accepted this log upload, but in doing this has canceled an ongoing log file upload. + + ExtendedTriggerMessageStatusAccepted ExtendedTriggerMessageStatus = "Accepted" + ExtendedTriggerMessageStatusRejected ExtendedTriggerMessageStatus = "Rejected" + ExtendedTriggerMessageStatusNotImplemented ExtendedTriggerMessageStatus = "NotImplemented" +) + +func isValidExtendedTriggerMessageType(fl validator.FieldLevel) bool { + status := ExtendedTriggerMessageType(fl.Field().String()) + switch status { + case ExtendedTriggerMessageTypeBootNotification, + ExtendedTriggerMessageTypeLogStatusNotification, + ExtendedTriggerMessageTypeHeartbeat, + ExtendedTriggerMessageTypeMeterValues, + ExtendedTriggerMessageTypeSignChargingStationCertificate, + ExtendedTriggerMessageTypeFirmwareStatusNotification, + ExtendedTriggerMessageTypeStatusNotification: + return true + default: + return false + } +} + +func isValidExtendedTriggerMessageStatus(fl validator.FieldLevel) bool { + status := ExtendedTriggerMessageStatus(fl.Field().String()) + switch status { + case ExtendedTriggerMessageStatusAccepted, + ExtendedTriggerMessageStatusRejected, + ExtendedTriggerMessageStatusNotImplemented: + return true + default: + return false + } +} + +// The field definition of the LogStatusNotification request payload sent by a Charging Station to the CSMS. +type ExtendedTriggerMessageRequest struct { + RequestedMessage ExtendedTriggerMessageType `json:"requestedMessage" validate:"required,extendedTriggerMessageType"` + ConnectorId *int `json:"connectorId" validate:"gt=0,omitempty"` +} + +// This field definition of the LogStatusNotification response payload, sent by the CSMS to the Charging Station in response to a ExtendedTriggerMessageRequest. +// In case the request was invalid, or couldn't be processed, an error will be sent instead. +type ExtendedTriggerMessageResponse struct { + Status ExtendedTriggerMessageStatus `json:"status" validate:"required,extendedTriggerMessageStatus"` +} + +func (r ExtendedTriggerMessageRequest) GetFeatureName() string { + return ExtendedTriggerMessageFeatureName +} + +func (c ExtendedTriggerMessageResponse) GetFeatureName() string { + return ExtendedTriggerMessageFeatureName +} + +// Creates a new ExtendedTriggerMessageRequest, containing all required fields. There are no optional fields for this message. +func NewExtendedTriggerMessageRequest(requestedMessage ExtendedTriggerMessageType) *ExtendedTriggerMessageRequest { + return &ExtendedTriggerMessageRequest{RequestedMessage: requestedMessage} +} + +// Creates a new ExtendedTriggerMessageResponse, which doesn't contain any required or optional fields. +func NewExtendedTriggerMessageResponse(status ExtendedTriggerMessageStatus) *ExtendedTriggerMessageResponse { + return &ExtendedTriggerMessageResponse{status} +} + +func init() { + _ = types.Validate.RegisterValidation("extendedTriggerMessageType", isValidExtendedTriggerMessageType) + _ = types.Validate.RegisterValidation("extendedTriggerMessageStatus", isValidExtendedTriggerMessageStatus) +} diff --git a/ocpp1.6/extendedtriggermessage/trigger_message.go b/ocpp1.6/extendedtriggermessage/trigger_message.go new file mode 100644 index 00000000..3431e78c --- /dev/null +++ b/ocpp1.6/extendedtriggermessage/trigger_message.go @@ -0,0 +1,17 @@ +// The diagnostics functional block contains OCPP 2.0 features than enable remote diagnostics of problems with a charging station. +package extendedtriggermessage + +import "github.com/lorenzodonini/ocpp-go/ocpp" + +// Needs to be implemented by Charging stations for handling messages part of the OCPP 1.6j security extension. +type ChargePointHandler interface { + // OnExtendedTriggerMessage is called on a charging station whenever a ExtendedTriggerMessageRequest is received from the CSMS. + OnExtendedTriggerMessage(request *ExtendedTriggerMessageRequest) (response *ExtendedTriggerMessageResponse, err error) +} + +const ProfileName = "ExtendedTriggerMessage" + +var Profile = ocpp.NewProfile( + ProfileName, + ExtendedTriggerMessageFeature{}, +) diff --git a/ocpp1.6/logging/get_log.go b/ocpp1.6/logging/get_log.go new file mode 100644 index 00000000..0878c0bc --- /dev/null +++ b/ocpp1.6/logging/get_log.go @@ -0,0 +1,110 @@ +package logging + +import ( + "reflect" + + "github.com/lorenzodonini/ocpp-go/ocpp1.6/types" + "gopkg.in/go-playground/validator.v9" +) + +// -------------------- Get Log (CSMS -> CS) -------------------- + +const GetLogFeatureName = "GetLog" + +// LogType represents the type of log file that the Charging Station should send. It is used in GetLogRequest. +type LogType string + +// LogStatus represents the status returned by a Charging Station in a GetLogResponse. +type LogStatus string + +const ( + LogTypeDiagnostics LogType = "DiagnosticsLog" // This contains the field definition of a diagnostics log file + LogTypeSecurity LogType = "SecurityLog" // Sent by the CSMS to the Charging Station to request that the Charging Station uploads the security log + LogStatusAccepted LogStatus = "Accepted" // Accepted this log upload. This does not mean the log file is uploaded is successfully, the Charging Station will now start the log file upload. + LogStatusRejected LogStatus = "Rejected" // Log update request rejected. + LogStatusAcceptedCanceled LogStatus = "AcceptedCanceled" // Accepted this log upload, but in doing this has canceled an ongoing log file upload. +) + +func isValidLogType(fl validator.FieldLevel) bool { + status := LogType(fl.Field().String()) + switch status { + case LogTypeDiagnostics, LogTypeSecurity: + return true + default: + return false + } +} + +func isValidLogStatus(fl validator.FieldLevel) bool { + status := LogStatus(fl.Field().String()) + switch status { + case LogStatusAccepted, LogStatusRejected, LogStatusAcceptedCanceled: + return true + default: + return false + } +} + +// LogParameters specifies the requested log and the location to which the log should be sent. It is used in GetLogRequest. +type LogParameters struct { + RemoteLocation string `json:"remoteLocation" validate:"required,max=512,url"` + OldestTimestamp *types.DateTime `json:"oldestTimestamp,omitempty" validate:"omitempty"` + LatestTimestamp *types.DateTime `json:"latestTimestamp,omitempty" validate:"omitempty"` +} + +// The field definition of the GetLog request payload sent by the CSMS to the Charging Station. +type GetLogRequest struct { + LogType LogType `json:"logType" validate:"required,logType16"` + RequestID int `json:"requestId" validate:"gte=0"` + Retries *int `json:"retries,omitempty" validate:"omitempty,gte=0"` + RetryInterval *int `json:"retryInterval,omitempty" validate:"omitempty,gte=0"` + Log LogParameters `json:"log" validate:"required"` +} + +// This field definition of the GetLog response payload, sent by the Charging Station to the CSMS in response to a GetLogRequest. +// In case the request was invalid, or couldn't be processed, an error will be sent instead. +type GetLogResponse struct { + Status LogStatus `json:"status" validate:"required,logStatus16"` // This field indicates whether the Charging Station was able to accept the request. + Filename string `json:"filename,omitempty" validate:"omitempty,max=256"` // This contains the name of the log file that will be uploaded. This field is not present when no logging information is available. +} + +// The CSMS can request a Charging Station to upload a file with log information to a given location (URL). +// The format of this log file is not prescribed. +// The Charging Station responds with GetLogResponse. +// It then attempts to upload a log file asynchronously and gives information about the status of the upload by sending status notifications to the CSMS. +type GetLogFeature struct{} + +func (f GetLogFeature) GetFeatureName() string { + return GetLogFeatureName +} + +func (f GetLogFeature) GetRequestType() reflect.Type { + return reflect.TypeOf(GetLogRequest{}) +} + +func (f GetLogFeature) GetResponseType() reflect.Type { + return reflect.TypeOf(GetLogResponse{}) +} + +func (r GetLogRequest) GetFeatureName() string { + return GetLogFeatureName +} + +func (c GetLogResponse) GetFeatureName() string { + return GetLogFeatureName +} + +// Creates a new GetLogRequest, containing all required fields. Optional fields may be set afterwards. +func NewGetLogRequest(logType LogType, requestID int, logParameters LogParameters) *GetLogRequest { + return &GetLogRequest{LogType: logType, RequestID: requestID, Log: logParameters} +} + +// Creates a new GetLogResponse, containing all required fields. Optional fields may be set afterwards. +func NewGetLogResponse(status LogStatus) *GetLogResponse { + return &GetLogResponse{Status: status} +} + +func init() { + _ = types.Validate.RegisterValidation("logType16", isValidLogType) + _ = types.Validate.RegisterValidation("logStatus16", isValidLogStatus) +} diff --git a/ocpp1.6/logging/log.go b/ocpp1.6/logging/log.go new file mode 100644 index 00000000..7a8e875d --- /dev/null +++ b/ocpp1.6/logging/log.go @@ -0,0 +1,24 @@ +// The diagnostics functional block contains OCPP 2.0 features than enable remote diagnostics of problems with a charging station. +package logging + +import "github.com/lorenzodonini/ocpp-go/ocpp" + +// Needs to be implemented by a CSMS for handling messages part of the OCPP 1.6j security extension. +type CentralSystemHandler interface { + // OnLogStatusNotification is called on the CSMS whenever a LogStatusNotificationRequest is received from a Charging Station. + OnLogStatusNotification(chargingStationID string, request *LogStatusNotificationRequest) (response *LogStatusNotificationResponse, err error) +} + +// Needs to be implemented by Charging stations for handling messages part of the OCPP 1.6j security extension. +type ChargePointHandler interface { + // OnGetLog is called on a charging station whenever a GetLogRequest is received from the CSMS. + OnGetLog(request *GetLogRequest) (response *GetLogResponse, err error) +} + +const ProfileName = "Log" + +var Profile = ocpp.NewProfile( + ProfileName, + GetLogFeature{}, + LogStatusNotificationFeature{}, +) diff --git a/ocpp1.6/logging/log_status_notification.go b/ocpp1.6/logging/log_status_notification.go new file mode 100644 index 00000000..d9d62470 --- /dev/null +++ b/ocpp1.6/logging/log_status_notification.go @@ -0,0 +1,86 @@ +package logging + +import ( + "reflect" + + "github.com/lorenzodonini/ocpp-go/ocpp2.0.1/types" + "gopkg.in/go-playground/validator.v9" +) + +// -------------------- Log Status Notification (CS -> CSMS) -------------------- + +const LogStatusNotificationFeatureName = "LogStatusNotification" + +// UploadLogStatus represents the current status of the log-upload procedure, reported by a Charging Station in a LogStatusNotificationRequest. +type UploadLogStatus string + +const ( + UploadLogStatusBadMessage UploadLogStatus = "BadMessage" // A badly formatted packet or other protocol incompatibility was detected. + UploadLogStatusIdle UploadLogStatus = "Idle" // The Charging Station is not uploading a log file. Idle SHALL only be used when the message was triggered by a TriggerMessageRequest. + UploadLogStatusNotSupportedOp UploadLogStatus = "NotSupportedOperation" // The server does not support the operation. + UploadLogStatusPermissionDenied UploadLogStatus = "PermissionDenied" // Insufficient permissions to perform the operation. + UploadLogStatusUploaded UploadLogStatus = "Uploaded" // File has been uploaded successfully. + UploadLogStatusUploadFailure UploadLogStatus = "UploadFailure" // Failed to upload the requested file. + UploadLogStatusUploading UploadLogStatus = "Uploading" // File is being uploaded. +) + +func isValidUploadLogStatus(fl validator.FieldLevel) bool { + status := UploadLogStatus(fl.Field().String()) + switch status { + case UploadLogStatusBadMessage, UploadLogStatusIdle, UploadLogStatusNotSupportedOp, UploadLogStatusPermissionDenied, UploadLogStatusUploaded, UploadLogStatusUploadFailure, UploadLogStatusUploading: + return true + default: + return false + } +} + +// The field definition of the LogStatusNotification request payload sent by a Charging Station to the CSMS. +type LogStatusNotificationRequest struct { + Status UploadLogStatus `json:"status" validate:"required,uploadLogStatus16"` + RequestID int `json:"requestId" validate:"gte=0"` +} + +// This field definition of the LogStatusNotification response payload, sent by the CSMS to the Charging Station in response to a LogStatusNotificationRequest. +// In case the request was invalid, or couldn't be processed, an error will be sent instead. +type LogStatusNotificationResponse struct { +} + +// A Charging Station shall send LogStatusNotification requests to update the CSMS with the current status of a log-upload procedure. +// The CSMS shall respond with a LogStatusNotificationResponse acknowledging the status update request. +// +// After a successful log upload, the The Charging Station returns to Idle status. +type LogStatusNotificationFeature struct{} + +func (f LogStatusNotificationFeature) GetFeatureName() string { + return LogStatusNotificationFeatureName +} + +func (f LogStatusNotificationFeature) GetRequestType() reflect.Type { + return reflect.TypeOf(LogStatusNotificationRequest{}) +} + +func (f LogStatusNotificationFeature) GetResponseType() reflect.Type { + return reflect.TypeOf(LogStatusNotificationResponse{}) +} + +func (r LogStatusNotificationRequest) GetFeatureName() string { + return LogStatusNotificationFeatureName +} + +func (c LogStatusNotificationResponse) GetFeatureName() string { + return LogStatusNotificationFeatureName +} + +// Creates a new LogStatusNotificationRequest, containing all required fields. There are no optional fields for this message. +func NewLogStatusNotificationRequest(status UploadLogStatus, requestID int) *LogStatusNotificationRequest { + return &LogStatusNotificationRequest{Status: status, RequestID: requestID} +} + +// Creates a new LogStatusNotificationResponse, which doesn't contain any required or optional fields. +func NewLogStatusNotificationResponse() *LogStatusNotificationResponse { + return &LogStatusNotificationResponse{} +} + +func init() { + _ = types.Validate.RegisterValidation("uploadLogStatus16", isValidUploadLogStatus) +} diff --git a/ocpp1.6/securefirmware/secure_firmware.go b/ocpp1.6/securefirmware/secure_firmware.go new file mode 100644 index 00000000..dfd41ba6 --- /dev/null +++ b/ocpp1.6/securefirmware/secure_firmware.go @@ -0,0 +1,21 @@ +// The diagnostics functional block contains OCPP 1.6J extension features than enable remote firmware updates on charging stations. +package securefirmware + +import "github.com/lorenzodonini/ocpp-go/ocpp" + +type CentralSystemHandler interface { + OnSignedFirmwareStatusNotification(chargingStationID string, request *SignedFirmwareStatusNotificationRequest) (response *SignedFirmwareStatusNotificationResponse, err error) +} + +// Needs to be implemented by Charging stations for handling messages part of the OCPP 1.6j security extension. +type ChargePointHandler interface { + OnSignedUpdateFirmware(request *SignedUpdateFirmwareRequest) (response *SignedUpdateFirmwareResponse, err error) +} + +const ProfileName = "SecureFirmwareUpdate" + +var Profile = ocpp.NewProfile( + ProfileName, + SignedFirmwareStatusNotificationFeature{}, + SignedUpdateFirmwareFeature{}, +) diff --git a/ocpp1.6/securefirmware/signed_update_firmware.go b/ocpp1.6/securefirmware/signed_update_firmware.go new file mode 100644 index 00000000..b1a56a2e --- /dev/null +++ b/ocpp1.6/securefirmware/signed_update_firmware.go @@ -0,0 +1,94 @@ +package securefirmware + +import ( + "reflect" + + "github.com/lorenzodonini/ocpp-go/ocpp1.6/types" + "gopkg.in/go-playground/validator.v9" +) + +const SignedUpdateFirmwareFeatureName = "SignedUpdateFirmware" + +type SignedUpdateFirmwareFeature struct{} + +func (e SignedUpdateFirmwareFeature) GetFeatureName() string { + return SignedUpdateFirmwareFeatureName +} + +func (e SignedUpdateFirmwareFeature) GetRequestType() reflect.Type { + return reflect.TypeOf(SignedUpdateFirmwareRequest{}) +} + +func (e SignedUpdateFirmwareFeature) GetResponseType() reflect.Type { + return reflect.TypeOf(SignedUpdateFirmwareResponse{}) +} + +// Indicates whether the Charging Station was able to accept the request. +type UpdateFirmwareStatus string + +const ( + UpdateFirmwareStatusAccepted UpdateFirmwareStatus = "Accepted" + UpdateFirmwareStatusRejected UpdateFirmwareStatus = "Rejected" + UpdateFirmwareStatusAcceptedCanceled UpdateFirmwareStatus = "AcceptedCanceled" + UpdateFirmwareStatusInvalidCertificate UpdateFirmwareStatus = "InvalidCertificate" + UpdateFirmwareStatusRevokedCertificate UpdateFirmwareStatus = "RevokedCertificate" +) + +func isValidUpdateFirmwareStatus(fl validator.FieldLevel) bool { + status := UpdateFirmwareStatus(fl.Field().String()) + switch status { + case UpdateFirmwareStatusAccepted, + UpdateFirmwareStatusRejected, + UpdateFirmwareStatusAcceptedCanceled, + UpdateFirmwareStatusInvalidCertificate, + UpdateFirmwareStatusRevokedCertificate: + return true + default: + return false + } +} + +// The field definition of the LogStatusNotification request payload sent by a Charging Station to the CSMS. +type SignedUpdateFirmwareRequest struct { + Retries *int `json:"retries,omitempty" validate:"omitempty,gte=0"` // This specifies how many times Charging Station must try to download the firmware before giving up. If this field is not present, it is left to Charging Station to decide how many times it wants to retry. + RetryInterval *int `json:"retryInterval,omitempty" validate:"omitempty,gte=0"` // The interval in seconds after which a retry may be attempted. If this field is not present, it is left to Charging Station to decide how long to wait between attempts. + RequestID int `json:"requestId" validate:"gte=0"` // The Id of the request. + Firmware Firmware `json:"firmware" validate:"required"` // Specifies the firmware to be updated on the Charging Station. +} + +// Represents a copy of the firmware that can be loaded/updated on the Charging Station. +type Firmware struct { + Location string `json:"location" validate:"required,max=512,uri"` // URI defining the origin of the firmware. + RetrieveDateTime *types.DateTime `json:"retrieveDateTime" validate:"required"` // Date and time at which the firmware shall be retrieved. + InstallDateTime *types.DateTime `json:"installDateTime,omitempty" validate:"omitempty"` // Date and time at which the firmware shall be installed. + SigningCertificate string `json:"signingCertificate,omitempty" validate:"max=5500"` // Certificate with which the firmware was signed. PEM encoded X.509 certificate. + Signature string `json:"signature,omitempty" validate:"max=800"` // Base64 encoded firmware signature. +} + +// This field definition of the LogStatusNotification response payload, sent by the CSMS to the Charging Station in response to a SignedUpdateFirmwareRequest. +// In case the request was invalid, or couldn't be processed, an error will be sent instead. +type SignedUpdateFirmwareResponse struct { + Status UpdateFirmwareStatus `json:"status" validate:"required,signedUpdateFirmwareStatus"` +} + +func (r SignedUpdateFirmwareRequest) GetFeatureName() string { + return SignedUpdateFirmwareFeatureName +} + +func (c SignedUpdateFirmwareResponse) GetFeatureName() string { + return SignedUpdateFirmwareFeatureName +} + +// Creates a new SignedUpdateFirmwareRequest, containing all required fields. There are no optional fields for this message. +func NewSignedUpdateFirmwareRequest(requestId int, firmware Firmware) *SignedUpdateFirmwareRequest { + return &SignedUpdateFirmwareRequest{RequestID: requestId, Firmware: firmware} +} + +// Creates a new SignedUpdateFirmwareResponse, which doesn't contain any required or optional fields. +func NewSignedUpdateFirmwareResponse(status UpdateFirmwareStatus) *SignedUpdateFirmwareResponse { + return &SignedUpdateFirmwareResponse{Status: status} +} + +func init() { + _ = types.Validate.RegisterValidation("signedUpdateFirmwareStatus", isValidUpdateFirmwareStatus) +} diff --git a/ocpp1.6/securefirmware/signed_update_firmware_status_notitfication.go b/ocpp1.6/securefirmware/signed_update_firmware_status_notitfication.go new file mode 100644 index 00000000..b4d063e5 --- /dev/null +++ b/ocpp1.6/securefirmware/signed_update_firmware_status_notitfication.go @@ -0,0 +1,109 @@ +package securefirmware + +import ( + "reflect" + + "gopkg.in/go-playground/validator.v9" + + "github.com/lorenzodonini/ocpp-go/ocpp1.6/types" +) + +// -------------------- Firmware Status Notification (CS -> CSMS) -------------------- + +const SignedFirmwareStatusNotificationFeatureName = "SignedFirmwareStatusNotification" + +// Status reported in SignedFirmwareStatusNotificationRequest. +type FirmwareStatus string + +const ( + FirmwareStatusDownloaded FirmwareStatus = "Downloaded" + FirmwareStatusDownloadFailed FirmwareStatus = "DownloadFailed" + FirmwareStatusDownloading FirmwareStatus = "Downloading" + FirmwareStatusDownloadScheduled FirmwareStatus = "DownloadScheduled" + FirmwareStatusDownloadPaused FirmwareStatus = "DownloadPaused" + FirmwareStatusIdle FirmwareStatus = "Idle" + FirmwareStatusInstallationFailed FirmwareStatus = "InstallationFailed" + FirmwareStatusInstalling FirmwareStatus = "Installing" + FirmwareStatusInstalled FirmwareStatus = "Installed" + FirmwareStatusInstallRebooting FirmwareStatus = "InstallRebooting" + FirmwareStatusInstallScheduled FirmwareStatus = "InstallScheduled" + FirmwareStatusInstallVerificationFailed FirmwareStatus = "InstallVerificationFailed" + FirmwareStatusInvalidSignature FirmwareStatus = "InvalidSignature" + FirmwareStatusSignatureVerified FirmwareStatus = "SignatureVerified" + FirmwareStatusCertificateVerified FirmwareStatus = "CertificateVerified" + FirmwareStatusInvalidCertificate FirmwareStatus = "InvalidCertificate" + FirmwareStatusRevokedCertificate FirmwareStatus = "RevokedCertificate" +) + +func isValidFirmwareStatus(fl validator.FieldLevel) bool { + status := FirmwareStatus(fl.Field().String()) + switch status { + case FirmwareStatusDownloaded, + FirmwareStatusDownloadFailed, + FirmwareStatusDownloading, + FirmwareStatusDownloadScheduled, + FirmwareStatusDownloadPaused, + FirmwareStatusIdle, + FirmwareStatusInstallationFailed, + FirmwareStatusInstalling, + FirmwareStatusInstalled, + FirmwareStatusInstallRebooting, + FirmwareStatusInstallScheduled, + FirmwareStatusInstallVerificationFailed, + FirmwareStatusInvalidSignature, + FirmwareStatusSignatureVerified: + return true + default: + return false + } +} + +// The field definition of the FirmwareStatusNotification request payload sent by the Charging Station to the CSMS. +type SignedFirmwareStatusNotificationRequest struct { + Status FirmwareStatus `json:"status" validate:"required,signedFirmwareStatus"` + RequestID *int `json:"requestId,omitempty" validate:"omitempty,gte=0"` +} + +// This field definition of the FirmwareStatusNotification response payload, sent by the CSMS to the Charging Station in response to a SignedFirmwareStatusNotificationRequest. +// In case the request was invalid, or couldn't be processed, an error will be sent instead. +type SignedFirmwareStatusNotificationResponse struct { +} + +// The Charging Station sends a notification to inform the CSMS about the progress of the downloading and installation of a firmware update. +// The Charging Station SHALL only send the status Idle after receipt of a TriggerMessage for a Firmware Status Notification, when it is not busy downloading/installing firmware. +// The FirmwareStatusNotification requests SHALL be sent to keep the CSMS updated with the status of the update process. +type SignedFirmwareStatusNotificationFeature struct{} + +func (f SignedFirmwareStatusNotificationFeature) GetFeatureName() string { + return SignedFirmwareStatusNotificationFeatureName +} + +func (f SignedFirmwareStatusNotificationFeature) GetRequestType() reflect.Type { + return reflect.TypeOf(SignedFirmwareStatusNotificationRequest{}) +} + +func (f SignedFirmwareStatusNotificationFeature) GetResponseType() reflect.Type { + return reflect.TypeOf(SignedFirmwareStatusNotificationResponse{}) +} + +func (r SignedFirmwareStatusNotificationRequest) GetFeatureName() string { + return SignedFirmwareStatusNotificationFeatureName +} + +func (c SignedFirmwareStatusNotificationResponse) GetFeatureName() string { + return SignedFirmwareStatusNotificationFeatureName +} + +// Creates a new SignedFirmwareStatusNotificationRequest, containing all required fields. Optional fields may be set afterwards. +func NewFirmwareStatusNotificationRequest(status FirmwareStatus) *SignedFirmwareStatusNotificationRequest { + return &SignedFirmwareStatusNotificationRequest{Status: status} +} + +// Creates a new SignedFirmwareStatusNotificationResponse, which doesn't contain any required or optional fields. +func NewFirmwareStatusNotificationResponse() *SignedFirmwareStatusNotificationResponse { + return &SignedFirmwareStatusNotificationResponse{} +} + +func init() { + _ = types.Validate.RegisterValidation("signedFirmwareStatus", isValidFirmwareStatus) +} diff --git a/ocpp1.6/security/certificate_signed.go b/ocpp1.6/security/certificate_signed.go new file mode 100644 index 00000000..51832dee --- /dev/null +++ b/ocpp1.6/security/certificate_signed.go @@ -0,0 +1,82 @@ +package security + +import ( + "reflect" + + "gopkg.in/go-playground/validator.v9" + + "github.com/lorenzodonini/ocpp-go/ocpp1.6/types" +) + +// -------------------- Certificate Signed (CSMS -> CS) -------------------- + +const CertificateSignedFeatureName = "CertificateSigned" + +// Status returned in response to CertificateSignedRequest, that indicates whether certificate signing has been accepted or rejected. +type CertificateSignedStatus string + +const ( + CertificateSignedStatusAccepted CertificateSignedStatus = "Accepted" + CertificateSignedStatusRejected CertificateSignedStatus = "Rejected" +) + +func isValidCertificateSignedStatus(fl validator.FieldLevel) bool { + status := CertificateSignedStatus(fl.Field().String()) + switch status { + case CertificateSignedStatusAccepted, CertificateSignedStatusRejected: + return true + default: + return false + } +} + +// The field definition of the CertificateSignedRequest PDU sent by the CSMS to the Charging Station. +type CertificateSignedRequest struct { + CertificateChain string `json:"certificateChain" validate:"required,max=10000"` +} + +// The field definition of the CertificateSignedResponse payload sent by the Charging Station to the CSMS in response to a CertificateSignedRequest. +type CertificateSignedResponse struct { + Status CertificateSignedStatus `json:"status" validate:"required,certificateSignedStatus16"` +} + +// During the a certificate update procedure, the CSMS sends a new certificate, signed by a CA, +// to the Charging Station with a CertificateSignedRequest. +// The Charging Station verifies the signed certificate, installs it locally and responds with +// a CertificateSignedResponse to the the CSMS with the status Accepted or Rejected. +type CertificateSignedFeature struct{} + +func (f CertificateSignedFeature) GetFeatureName() string { + return CertificateSignedFeatureName +} + +func (f CertificateSignedFeature) GetRequestType() reflect.Type { + return reflect.TypeOf(CertificateSignedRequest{}) +} + +func (f CertificateSignedFeature) GetResponseType() reflect.Type { + return reflect.TypeOf(CertificateSignedResponse{}) +} + +func (r CertificateSignedRequest) GetFeatureName() string { + return CertificateSignedFeatureName +} + +func (c CertificateSignedResponse) GetFeatureName() string { + return CertificateSignedFeatureName +} + +// Creates a new CertificateSignedRequest, containing all required fields. Additional optional fields may be set afterwards. +// The maximum size of this field is be limited by the configuration key: CertificateSignedMaxSize +func NewCertificateSignedRequest(certificateChain string) *CertificateSignedRequest { + return &CertificateSignedRequest{CertificateChain: certificateChain} +} + +// Creates a new CertificateSignedResponse, containing all required fields. There are no optional fields for this message. +func NewCertificateSignedResponse(status CertificateSignedStatus) *CertificateSignedResponse { + return &CertificateSignedResponse{Status: status} +} + +func init() { + _ = types.Validate.RegisterValidation("certificateSignedStatus16", isValidCertificateSignedStatus) +} diff --git a/ocpp1.6/security/security.go b/ocpp1.6/security/security.go new file mode 100644 index 00000000..7a417222 --- /dev/null +++ b/ocpp1.6/security/security.go @@ -0,0 +1,27 @@ +// The security functional block contains OCPP 2.0 features aimed at providing E2E security between a CSMS and a Charging station. +package security + +import "github.com/lorenzodonini/ocpp-go/ocpp" + +// Needs to be implemented by a CSMS for handling messages part of the OCPP 2.0 Security profile. +type CentralSystemHandler interface { + // OnSecurityEventNotification is called on the CSMS whenever a SecurityEventNotificationRequest is received from a charging station. + OnSecurityEventNotification(chargingStationID string, request *SecurityEventNotificationRequest) (response *SecurityEventNotificationResponse, err error) + // OnSignCertificate is called on the CSMS whenever a SignCertificateRequest is received from a charging station. + OnSignCertificate(chargingStationID string, request *SignCertificateRequest) (response *SignCertificateResponse, err error) +} + +// Needs to be implemented by Charging stations for handling messages part of the OCPP 2.0 Security profile. +type ChargePointHandler interface { + // OnCertificateSigned is called on a charging station whenever a CertificateSignedRequest is received from the CSMS. + OnCertificateSigned(request *CertificateSignedRequest) (response *CertificateSignedResponse, err error) +} + +const ProfileName = "Security" + +var Profile = ocpp.NewProfile( + ProfileName, + CertificateSignedFeature{}, + SecurityEventNotificationFeature{}, + SignCertificateFeature{}, +) diff --git a/ocpp1.6/security/security_event_notification.go b/ocpp1.6/security/security_event_notification.go new file mode 100644 index 00000000..15a4ab02 --- /dev/null +++ b/ocpp1.6/security/security_event_notification.go @@ -0,0 +1,58 @@ +package security + +import ( + "reflect" + + "github.com/lorenzodonini/ocpp-go/ocpp1.6/types" +) + +// -------------------- Security Event Notification Status (CS -> CSMS) -------------------- + +const SecurityEventNotificationFeatureName = "SecurityEventNotification" + +// The field definition of the SecurityEventNotification request payload sent by the Charging Station to the CSMS. +type SecurityEventNotificationRequest struct { + Type string `json:"type" validate:"required,max=50"` // Type of the security event. This value should be taken from the Security events list. + Timestamp *types.DateTime `json:"timestamp" validate:"required"` // Date and time at which the event occurred. + TechInfo string `json:"techInfo,omitempty" validate:"omitempty,max=255"` // Additional information about the occurred security event. +} + +// This field definition of the SecurityEventNotification response payload, sent by the CSMS to the Charging Station in response to a SecurityEventNotificationRequest. +// In case the request was invalid, or couldn't be processed, an error will be sent instead. +type SecurityEventNotificationResponse struct { +} + +// In case of critical security events, a Charging Station may immediately inform the CSMS of such events, +// via a SecurityEventNotificationRequest. +// The CSMS responds with a SecurityEventNotificationResponse to the Charging Station. +type SecurityEventNotificationFeature struct{} + +func (f SecurityEventNotificationFeature) GetFeatureName() string { + return SecurityEventNotificationFeatureName +} + +func (f SecurityEventNotificationFeature) GetRequestType() reflect.Type { + return reflect.TypeOf(SecurityEventNotificationRequest{}) +} + +func (f SecurityEventNotificationFeature) GetResponseType() reflect.Type { + return reflect.TypeOf(SecurityEventNotificationResponse{}) +} + +func (r SecurityEventNotificationRequest) GetFeatureName() string { + return SecurityEventNotificationFeatureName +} + +func (c SecurityEventNotificationResponse) GetFeatureName() string { + return SecurityEventNotificationFeatureName +} + +// Creates a new SecurityEventNotificationRequest, containing all required fields. Optional fields may be set afterwards. +func NewSecurityEventNotificationRequest(typ string, timestamp *types.DateTime) *SecurityEventNotificationRequest { + return &SecurityEventNotificationRequest{Type: typ, Timestamp: timestamp} +} + +// Creates a new SecurityEventNotificationResponse, which doesn't contain any required or optional fields. +func NewSecurityEventNotificationResponse() *SecurityEventNotificationResponse { + return &SecurityEventNotificationResponse{} +} diff --git a/ocpp1.6/security/sign_certificate.go b/ocpp1.6/security/sign_certificate.go new file mode 100644 index 00000000..cdf79863 --- /dev/null +++ b/ocpp1.6/security/sign_certificate.go @@ -0,0 +1,61 @@ +package security + +import ( + "reflect" + + "github.com/lorenzodonini/ocpp-go/ocpp1.6/types" +) + +// -------------------- Sign Certificate (CS -> CSMS) -------------------- + +const SignCertificateFeatureName = "SignCertificate" + +// The field definition of the SignCertificate request payload sent by the Charging Station to the CSMS. +type SignCertificateRequest struct { + CSR string `json:"csr" validate:"required,max=5500"` // The Charging Station SHALL send the public key in form of a Certificate Signing Request (CSR) as described in RFC 2986 and then PEM encoded. + CertificateType types.CertificateSigningUse `json:"certificateType,omitempty" validate:"omitempty,certificateSigningUse16"` // Indicates the type of certificate that is to be signed. +} + +// This field definition of the SignCertificate response payload, sent by the CSMS to the Charging Station in response to a SignCertificateRequest. +// In case the request was invalid, or couldn't be processed, an error will be sent instead. +type SignCertificateResponse struct { + Status types.GenericStatus `json:"status" validate:"required,genericStatus16"` // Specifies whether the CSMS can process the request. +} + +// If a Charging Station detected, that its certificate is due to expire, it will generate a new public/private key pair, +// then send a SignCertificateRequest to the CSMS containing a valid Certificate Signing Request. +// +// The CSMS responds with a SignCertificateResponse and will then forward the CSR to a CA server. +// Once the CA has issues a valid certificate, the CSMS will send a CertificateSignedRequest to the +// charging station (asynchronously). +type SignCertificateFeature struct{} + +func (f SignCertificateFeature) GetFeatureName() string { + return SignCertificateFeatureName +} + +func (f SignCertificateFeature) GetRequestType() reflect.Type { + return reflect.TypeOf(SignCertificateRequest{}) +} + +func (f SignCertificateFeature) GetResponseType() reflect.Type { + return reflect.TypeOf(SignCertificateResponse{}) +} + +func (r SignCertificateRequest) GetFeatureName() string { + return SignCertificateFeatureName +} + +func (c SignCertificateResponse) GetFeatureName() string { + return SignCertificateFeatureName +} + +// Creates a new SignCertificateRequest, containing all required fields. Optional fields may be set afterwards. +func NewSignCertificateRequest(csr string) *SignCertificateRequest { + return &SignCertificateRequest{CSR: csr} +} + +// Creates a new SignCertificateResponse, containing all required fields. Optional fields may be set afterwards. +func NewSignCertificateResponse(status types.GenericStatus) *SignCertificateResponse { + return &SignCertificateResponse{Status: status} +} diff --git a/ocpp1.6/types/security_extension.go b/ocpp1.6/types/security_extension.go new file mode 100644 index 00000000..94ba3eb9 --- /dev/null +++ b/ocpp1.6/types/security_extension.go @@ -0,0 +1,88 @@ +package types + +import "gopkg.in/go-playground/validator.v9" + +// Indicates the type of the signed certificate that is returned. +// When omitted the certificate is used for both the 15118 connection (if implemented) and the Charging Station to CSMS connection. +// This field is required when a typeOfCertificate was included in the SignCertificateRequest that requested this certificate to be signed AND both the 15118 connection and the Charging Station connection are implemented. +type CertificateSigningUse string + +const ( + ChargingStationCert CertificateSigningUse = "ChargingStationCertificate" +) + +func isValidCertificateSigningUse(fl validator.FieldLevel) bool { + status := CertificateSigningUse(fl.Field().String()) + switch status { + case ChargingStationCert: + return true + default: + return false + } +} + +// Generic Status +type GenericStatus string + +const ( + GenericStatusAccepted GenericStatus = "Accepted" + GenericStatusRejected GenericStatus = "Rejected" +) + +func isValidGenericStatus(fl validator.FieldLevel) bool { + status := GenericStatus(fl.Field().String()) + switch status { + case GenericStatusAccepted, GenericStatusRejected: + return true + default: + return false + } +} + +// StatusInfo is an element providing more information about the message status. +type StatusInfo struct { + ReasonCode string `json:"reasonCode" validate:"required,max=20"` // A predefined code for the reason why the status is returned in this response. The string is case- insensitive. + AdditionalInfo string `json:"additionalInfo,omitempty" validate:"omitempty,max=512"` // Additional text to provide detailed information. +} + +// NewStatusInfo creates a StatusInfo struct. +// If no additional info need to be set, an empty string may be passed. +func NewStatusInfo(reasonCode string, additionalInfo string) *StatusInfo { + return &StatusInfo{ReasonCode: reasonCode, AdditionalInfo: additionalInfo} +} + +// Indicates the type of the requested certificate. +// It is used in GetInstalledCertificateIdsRequest and InstallCertificateRequest messages. +type CertificateUse string + +const ( + CentralSystemRootCertificate CertificateUse = "CentralSystemRootCertificate" + ManufacturerRootCertificate CertificateUse = "ManufacturerRootCertificate" +) + +func isValidCertificateUse(fl validator.FieldLevel) bool { + use := CertificateUse(fl.Field().String()) + switch use { + case CentralSystemRootCertificate, ManufacturerRootCertificate: + return true + default: + return false + } +} + +// Hash Algorithms +type HashAlgorithmType string + +const ( + SHA256 HashAlgorithmType = "SHA256" + SHA384 HashAlgorithmType = "SHA384" + SHA512 HashAlgorithmType = "SHA512" +) + +// CertificateHashDataType +type CertificateHashData struct { + HashAlgorithm HashAlgorithmType `json:"hashAlgorithm" validate:"required,hashAlgorithm"` + IssuerNameHash string `json:"issuerNameHash" validate:"required,max=128"` + IssuerKeyHash string `json:"issuerKeyHash" validate:"required,max=128"` + SerialNumber string `json:"serialNumber" validate:"required,max=40"` +} diff --git a/ocpp1.6/types/types.go b/ocpp1.6/types/types.go index c38f51e6..760360d6 100644 --- a/ocpp1.6/types/types.go +++ b/ocpp1.6/types/types.go @@ -11,7 +11,6 @@ const ( ) type PropertyViolation struct { - error Property string } @@ -330,4 +329,7 @@ func init() { _ = Validate.RegisterValidation("phase16", isValidPhase) _ = Validate.RegisterValidation("location16", isValidLocation) _ = Validate.RegisterValidation("unitOfMeasure", isValidUnitOfMeasure) + _ = Validate.RegisterValidation("certificateSigningUse16", isValidCertificateSigningUse) + _ = Validate.RegisterValidation("isValidCertificateUse", isValidCertificateUse) + _ = Validate.RegisterValidation("genericStatus16", isValidGenericStatus) } diff --git a/ocpp1.6/v16.go b/ocpp1.6/v16.go index 853ec147..5395db2c 100644 --- a/ocpp1.6/v16.go +++ b/ocpp1.6/v16.go @@ -7,11 +7,16 @@ import ( "github.com/lorenzodonini/ocpp-go/internal/callbackqueue" "github.com/lorenzodonini/ocpp-go/ocpp" + "github.com/lorenzodonini/ocpp-go/ocpp1.6/certificates" "github.com/lorenzodonini/ocpp-go/ocpp1.6/core" + "github.com/lorenzodonini/ocpp-go/ocpp1.6/extendedtriggermessage" "github.com/lorenzodonini/ocpp-go/ocpp1.6/firmware" "github.com/lorenzodonini/ocpp-go/ocpp1.6/localauth" + "github.com/lorenzodonini/ocpp-go/ocpp1.6/logging" "github.com/lorenzodonini/ocpp-go/ocpp1.6/remotetrigger" "github.com/lorenzodonini/ocpp-go/ocpp1.6/reservation" + "github.com/lorenzodonini/ocpp-go/ocpp1.6/securefirmware" + "github.com/lorenzodonini/ocpp-go/ocpp1.6/security" "github.com/lorenzodonini/ocpp-go/ocpp1.6/smartcharging" "github.com/lorenzodonini/ocpp-go/ocpp1.6/types" "github.com/lorenzodonini/ocpp-go/ocppj" @@ -67,6 +72,14 @@ type ChargePoint interface { // Notifies the central system of a status change during the download of a new firmware version. FirmwareStatusNotification(status firmware.FirmwareStatus, props ...func(request *firmware.FirmwareStatusNotificationRequest)) (*firmware.FirmwareStatusNotificationConfirmation, error) + SecurityEventNotification(typ string, timestamp *types.DateTime, props ...func(request *security.SecurityEventNotificationRequest)) (*security.SecurityEventNotificationResponse, error) + + SignCertificate(CSR string, props ...func(request *security.SignCertificateRequest)) (*security.SignCertificateResponse, error) + + SignedUpdateFirmwareStatusNotification(status securefirmware.FirmwareStatus, props ...func(request *securefirmware.SignedFirmwareStatusNotificationRequest)) (*securefirmware.SignedFirmwareStatusNotificationResponse, error) + + LogStatusNotification(status logging.UploadLogStatus, requestId int, props ...func(request *logging.LogStatusNotificationRequest)) (*logging.LogStatusNotificationResponse, error) + // Registers a handler for incoming core profile messages SetCoreHandler(listener core.ChargePointHandler) // Registers a handler for incoming local authorization profile messages @@ -79,6 +92,18 @@ type ChargePoint interface { SetRemoteTriggerHandler(listener remotetrigger.ChargePointHandler) // Registers a handler for incoming smart charging profile messages SetSmartChargingHandler(listener smartcharging.ChargePointHandler) + + // Registers a handler for incoming security profile messages (Extension of OCPP 1.6j). + SetSecurityHandler(handler security.ChargePointHandler) + // Registers a handler for incoming log profile messages (Extension of OCPP 1.6j). + SetLogHandler(handler logging.ChargePointHandler) + // Registers a handler for incoming extended trigger message profile messages (Extension of OCPP 1.6j). + SetExtendedTriggerMessageHandler(handler extendedtriggermessage.ChargePointHandler) + // Registers a handler for incoming secure firmware profile messages (Extension of OCPP 1.6j). + SetSecureFirmwareHandler(handler securefirmware.ChargePointHandler) + // Registers a handler for incoming certificate profile messages (Extension of OCPP 1.6j). + SetCertificateHandler(handler certificates.ChargePointHandler) + // Sends a request to the central system. // The central system will respond with a confirmation, or with an error if the request was invalid or could not be processed. // In case of network issues (i.e. the remote host couldn't be reached), the function also returns an error. @@ -142,7 +167,23 @@ func NewChargePoint(id string, endpoint *ocppj.Client, client ws.WsClient) Charg if endpoint == nil { dispatcher := ocppj.NewDefaultClientDispatcher(ocppj.NewFIFOClientQueue(0)) - endpoint = ocppj.NewClient(id, client, dispatcher, nil, core.Profile, localauth.Profile, firmware.Profile, reservation.Profile, remotetrigger.Profile, smartcharging.Profile) + endpoint = ocppj.NewClient( + id, + client, + dispatcher, + nil, + core.Profile, + localauth.Profile, + firmware.Profile, + reservation.Profile, + remotetrigger.Profile, + smartcharging.Profile, + logging.Profile, + security.Profile, + extendedtriggermessage.Profile, + certificates.Profile, + securefirmware.Profile, + ) } endpoint.SetDialect(ocpp.V16) @@ -230,6 +271,19 @@ type CentralSystem interface { ClearChargingProfile(clientId string, callback func(*smartcharging.ClearChargingProfileConfirmation, error), props ...func(request *smartcharging.ClearChargingProfileRequest)) error // Queries a charge point to the composite smart charging schedules and rules for a specified time interval. GetCompositeSchedule(clientId string, callback func(*smartcharging.GetCompositeScheduleConfirmation, error), connectorId int, duration int, props ...func(request *smartcharging.GetCompositeScheduleRequest)) error + TriggerMessageExtended(clientId string, callback func(*extendedtriggermessage.ExtendedTriggerMessageResponse, error), requestedMessage extendedtriggermessage.ExtendedTriggerMessageType, props ...func(request *extendedtriggermessage.ExtendedTriggerMessageRequest)) error + + CertificateSigned(clientId string, callback func(*security.CertificateSignedResponse, error), csr string, props ...func(request *security.CertificateSignedRequest)) error + + InstallCertificate(clientId string, callback func(*certificates.InstallCertificateResponse, error), certificateType types.CertificateUse, certificate string, props ...func(request *certificates.InstallCertificateRequest)) error + + GetInstalledCertificateIds(clientId string, callback func(*certificates.GetInstalledCertificateIdsResponse, error), props ...func(request *certificates.GetInstalledCertificateIdsRequest)) error + + DeleteCertificate(clientId string, callback func(*certificates.DeleteCertificateResponse, error), certificateHashData types.CertificateHashData, props ...func(request *certificates.DeleteCertificateRequest)) error + + GetLog(clientId string, callback func(*logging.GetLogResponse, error), logType logging.LogType, requestID int, logParameters logging.LogParameters, props ...func(request *logging.GetLogRequest)) error + + SignedUpdateFirmware(clientId string, callback func(*securefirmware.SignedUpdateFirmwareResponse, error), requestId int, firmware securefirmware.Firmware, props ...func(request *securefirmware.SignedUpdateFirmwareRequest)) error // Registers a handler for incoming core profile messages. SetCoreHandler(handler core.CentralSystemHandler) @@ -243,6 +297,13 @@ type CentralSystem interface { SetRemoteTriggerHandler(handler remotetrigger.CentralSystemHandler) // Registers a handler for incoming smart charging profile messages. SetSmartChargingHandler(handler smartcharging.CentralSystemHandler) + // Registers a handler for incoming security profile messages (Extension of OCPP 1.6j). + SetSecurityHandler(handler security.CentralSystemHandler) + // Registers a handler for incoming log profile messages (Extension of OCPP 1.6j). + SetLogHandler(handler logging.CentralSystemHandler) + // Registers a handler for incoming secure firmware profile messages (Extension of OCPP 1.6j). + SetSecureFirmwareHandler(handler securefirmware.CentralSystemHandler) + // Registers a handler for new incoming Charging station connections. SetNewChargingStationValidationHandler(handler ws.CheckClientHandler) // Registers a handler for new incoming charge point connections. @@ -283,7 +344,17 @@ func NewCentralSystem(endpoint *ocppj.Server, server ws.WsServer) CentralSystem } server.AddSupportedSubprotocol(types.V16Subprotocol) if endpoint == nil { - endpoint = ocppj.NewServer(server, nil, nil, core.Profile, localauth.Profile, firmware.Profile, reservation.Profile, remotetrigger.Profile, smartcharging.Profile) + endpoint = ocppj.NewServer(server, nil, nil, + core.Profile, + localauth.Profile, + firmware.Profile, + reservation.Profile, + remotetrigger.Profile, + smartcharging.Profile, + logging.Profile, + security.Profile, + securefirmware.Profile, + ) } cs := newCentralSystem(endpoint) cs.server.SetRequestHandler(func(client ws.Channel, request ocpp.Request, requestId string, action string) { diff --git a/ocpp1.6_test/ocpp16_extension_test.go b/ocpp1.6_test/ocpp16_extension_test.go new file mode 100644 index 00000000..6f5f9094 --- /dev/null +++ b/ocpp1.6_test/ocpp16_extension_test.go @@ -0,0 +1,109 @@ +package ocpp16_test + +import ( + "github.com/lorenzodonini/ocpp-go/ocpp1.6/certificates" + "github.com/lorenzodonini/ocpp-go/ocpp1.6/extendedtriggermessage" + "github.com/lorenzodonini/ocpp-go/ocpp1.6/securefirmware" + "github.com/lorenzodonini/ocpp-go/ocpp1.6/security" + "github.com/stretchr/testify/mock" +) + +// ---------------------- MOCK CP SECURITY HANDLER ---------------------- +type MockChargePointSecurityHandler struct { + mock.Mock +} + +func (m *MockChargePointSecurityHandler) OnCertificateSigned(request *security.CertificateSignedRequest) (response *security.CertificateSignedResponse, err error) { + args := m.MethodCalled("OnCertificateSigned", request) + conf := args.Get(0).(*security.CertificateSignedResponse) + return conf, args.Error(1) +} + +// ---------------------- MOCK CS SECURITY HANDLER ---------------------- +type MockCentralSystemSecurityListener struct { + mock.Mock +} + +func (m *MockCentralSystemSecurityListener) OnSecurityEventNotification(chargingStationID string, request *security.SecurityEventNotificationRequest) (response *security.SecurityEventNotificationResponse, err error) { + args := m.MethodCalled("OnSecurityEventNotification", request) + conf := args.Get(0).(*security.SecurityEventNotificationResponse) + return conf, args.Error(1) +} + +func (m *MockCentralSystemSecurityListener) OnSignCertificate(chargingStationID string, request *security.SignCertificateRequest) (response *security.SignCertificateResponse, err error) { + args := m.MethodCalled("OnSignCertificate", request) + conf := args.Get(0).(*security.SignCertificateResponse) + return conf, args.Error(1) +} + +// ---------------------- MOCK CS CERTIFICATE HANDLER ---------------------- + +type MockCentralSystemCertificateListener struct { + mock.Mock +} + +// ---------------------- MOCK CP CERTIFICATE HANDLER ---------------------- + +type MockChargePointCertificateHandler struct { + mock.Mock +} + +func (m *MockChargePointCertificateHandler) OnDeleteCertificate(request *certificates.DeleteCertificateRequest) (response *certificates.DeleteCertificateResponse, err error) { + args := m.MethodCalled("OnDeleteCertificate", request) + conf := args.Get(0).(*certificates.DeleteCertificateResponse) + return conf, args.Error(1) +} + +func (m *MockChargePointCertificateHandler) OnGetInstalledCertificateIds(request *certificates.GetInstalledCertificateIdsRequest) (response *certificates.GetInstalledCertificateIdsResponse, err error) { + args := m.MethodCalled("OnGetInstalledCertificateIds", request) + conf := args.Get(0).(*certificates.GetInstalledCertificateIdsResponse) + return conf, args.Error(1) +} + +func (m *MockChargePointCertificateHandler) OnInstallCertificate(request *certificates.InstallCertificateRequest) (response *certificates.InstallCertificateResponse, err error) { + args := m.MethodCalled("OnInstallCertificate", request) + conf := args.Get(0).(*certificates.InstallCertificateResponse) + return conf, args.Error(1) +} + +// ---------------------- MOCK CS EXTENDED TRIGGER MESSAGE HANDLER ---------------------- + +type MockCentralSystemExtendedTriggerMessageListener struct { + mock.Mock +} + +// ---------------------- MOCK CP EXTENDED TRIGGER MESSAGE HANDLER ---------------------- + +type MockChargePointExtendedTriggerMessageHandler struct { + mock.Mock +} + +func (m *MockChargePointExtendedTriggerMessageHandler) OnExtendedTriggerMessage(request *extendedtriggermessage.ExtendedTriggerMessageRequest) (response *extendedtriggermessage.ExtendedTriggerMessageResponse, err error) { + args := m.MethodCalled("OnExtendedTriggerMessage", request) + conf := args.Get(0).(*extendedtriggermessage.ExtendedTriggerMessageResponse) + return conf, args.Error(1) +} + +// ---------------------- MOCK CS SECURE FIRMWARE UPDATE HANDLER ---------------------- + +type MockCentralSystemSecureFirmwareUpdateListener struct { + mock.Mock +} + +func (m *MockCentralSystemSecureFirmwareUpdateListener) OnSignedFirmwareStatusNotification(chargingStationID string, request *securefirmware.SignedFirmwareStatusNotificationRequest) (response *securefirmware.SignedFirmwareStatusNotificationResponse, err error) { + args := m.MethodCalled("OnSignedFirmwareStatusNotification", request) + conf := args.Get(0).(*securefirmware.SignedFirmwareStatusNotificationResponse) + return conf, args.Error(1) +} + +// ---------------------- MOCK CP SECURE FIRMWARE UPDATE HANDLER ---------------------- + +type MockChargePointSecureFirmwareUpdateHandler struct { + mock.Mock +} + +func (m *MockChargePointSecureFirmwareUpdateHandler) OnSignedUpdateFirmware(request *securefirmware.SignedUpdateFirmwareRequest) (response *securefirmware.SignedUpdateFirmwareResponse, err error) { + args := m.MethodCalled("OnSignedUpdateFirmware", request) + conf := args.Get(0).(*securefirmware.SignedUpdateFirmwareResponse) + return conf, args.Error(1) +}