Skip to content

Commit c0c6094

Browse files
authored
Merge branch 'lorenzodonini:master' into master
2 parents 943120d + d291e8c commit c0c6094

32 files changed

+3985
-1782
lines changed

.github/workflows/docker-publish.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
name: Publish examples
22
on:
33
push:
4+
paths-ignore:
5+
- "**.md"
46
branches:
57
- master
68
jobs:

.github/workflows/test.yaml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
name: Test
2-
on: [ push, pull_request, workflow_dispatch ]
2+
on:
3+
push:
4+
paths-ignore:
5+
- "**.md"
6+
- LICENSE
7+
8+
pull_request:
9+
paths-ignore:
10+
- "**.md"
11+
- LICENSE
12+
13+
workflow_dispatch:
14+
315
jobs:
416
unit:
517
runs-on: ubuntu-latest

README.md

Lines changed: 112 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ocpp-go
22

3-
[![Build Status](https://travis-ci.org/lorenzodonini/ocpp-go.svg?branch=master)](https://travis-ci.org/lorenzodonini/ocpp-go)
3+
[![Build Status](https://github.com/lorenzodonini/ocpp-go/actions/workflows/test.yaml/badge.svg)](https://github.com/lorenzodonini/ocpp-go/actions/workflows/test.yaml)
44
[![GoDoc](https://img.shields.io/badge/godoc-reference-5272B4)](https://godoc.org/github.com/lorenzodonini/ocpp-go)
55
[![Coverage Status](https://coveralls.io/repos/github/lorenzodonini/ocpp-go/badge.svg?branch=master)](https://coveralls.io/github/lorenzodonini/ocpp-go?branch=master)
66
[![Go report](https://goreportcard.com/badge/github.com/lorenzodonini/ocpp-go)](https://goreportcard.com/report/github.com/lorenzodonini/ocpp-go)
@@ -20,6 +20,7 @@ but naming changed.**
2020
Planned milestones and features:
2121

2222
- [x] OCPP 1.6
23+
- [x] OCPP 1.6 Security extension (experimental)
2324
- [x] OCPP 2.0.1 (examples working, but will need more real-world testing)
2425
- [ ] Dedicated package for configuration management
2526

@@ -346,6 +347,102 @@ Then run the following:
346347
docker-compose -f example/1.6/docker-compose.tls.yml up charge-point
347348
```
348349

350+
## OCPP 1.6 Security extension
351+
352+
The library supports the OCPP 1.6 Security extension, which adds support for additional messages that aren't a part of
353+
original OCPP 1.6 specification. The security extension is optional, but recommended to implement.
354+
355+
There aren't any clear examples how to determine if a charge point supports security extensions via `SupportedProfiles`
356+
configuration key or which profiles are required to be implemented in order to support the security extension.
357+
As of now, the security extension is split into multiple profiles/functional blocks:
358+
359+
- `ExtendedTriggerMessage`
360+
- `Certificates` (certificate management)
361+
- `Security` (event notifications, certificate signing)
362+
- `SecureFirmware` (secure firmware update)
363+
- `Logging`
364+
365+
### HTTP Basic Auth
366+
367+
The security extension specifies how to secure the communication between charge points and central systems
368+
using HTTP Basic Auth and/or certificates. These are already provided in the websocket server/client
369+
implementation.
370+
371+
Example charge point:
372+
373+
```go
374+
wsClient := ws.NewClient()
375+
wsClient.SetBasicAuth("foo", "bar")
376+
cp := ocpp16.NewChargePoint(chargePointID, nil, wsClient)
377+
```
378+
379+
Example central system:
380+
381+
```go
382+
server := ws.NewServer()
383+
server.SetBasicAuthHandler(func (username string, password string) bool {
384+
// todo Handle basic auth
385+
return true
386+
})
387+
cs := ocpp16.NewCentralSystem(nil, server)
388+
```
389+
390+
### Certificate-based authentication (mTLS)
391+
392+
The security extension specifies how to secure the communication between charge points and central systems
393+
using mTLS (client certificates). The library provides the necessary functionality to configure TLS,
394+
but mTLS itself is not in scope and should be handled by the user.
395+
396+
### Additional configuration keys
397+
398+
The OCPP 1.6 security extension introduces additional configuration keys.
399+
These are not a part of the standard library, but they impact how the charge point should behave.
400+
401+
The charge point websocket client should be restarted when the `AuthorizationKey` configuration changes.
402+
403+
### Central System
404+
405+
To add support for security extension in the central system, you have the following handlers:
406+
407+
```go
408+
// Support callbacks for all OCPP 1.6 profiles
409+
handler := &CentralSystemHandler{chargePoints: map[string]*ChargePointState{}}
410+
centralSystem.SetCoreHandler(handler)
411+
centralSystem.SetLocalAuthListHandler(handler)
412+
centralSystem.SetFirmwareManagementHandler(handler)
413+
centralSystem.SetReservationHandler(handler)
414+
centralSystem.SetRemoteTriggerHandler(handler)
415+
centralSystem.SetSmartChargingHandler(handler)
416+
417+
// Add callbacks for OCPP 1.6 security profiles
418+
centralSystem.SetSecurityHandler(handler)
419+
centralSystem.SetSecureFirmwareHandler(handler)
420+
centralSystem.SetLogHandler(handler)
421+
422+
```
423+
424+
### Charge Point
425+
426+
To add support for security extension in the charge point, you have the following handlers:
427+
428+
```go
429+
handler := &ChargePointHandler{}
430+
// Support callbacks for all OCPP 1.6 profiles
431+
chargePoint.SetCoreHandler(handler)
432+
chargePoint.SetFirmwareManagementHandler(handler)
433+
chargePoint.SetLocalAuthListHandler(handler)
434+
chargePoint.SetReservationHandler(handler)
435+
chargePoint.SetRemoteTriggerHandler(handler)
436+
chargePoint.SetSmartChargingHandler(handler)
437+
// OCPP 1.6j Security extension
438+
chargePoint.SetCertificateHandler(handler)
439+
chargePoint.SetLogHandler(handler)
440+
chargePoint.SetSecureFirmwareHandler(handler)
441+
chargePoint.SetExtendedTriggerMessageHandler(handler)
442+
chargePoint.SetSecurityHandler(handler)
443+
444+
```
445+
349446
## Advanced Features
350447

351448
The library offers several advanced features, especially at websocket and ocpp-j level.
@@ -389,18 +486,27 @@ own logging system.
389486

390487
### Websocket ping-pong
391488

392-
The websocket package currently supports client-initiated pings only.
489+
The websocket package supports configuring ping pong for both endpoints.
393490

394-
If your setup requires the server to be the initiator of a ping-pong (e.g. for web-based charge points),
395-
you may disable ping-pong entirely and just rely on the heartbeat mechanism:
491+
By default, the client sends a ping every 54 seconds and waits for a pong for 60 seconds, before timing out.
492+
The values can be configured as follows:
493+
```go
494+
cfg := ws.NewClientTimeoutConfig()
495+
cfg.PingPeriod = 10 * time.Second
496+
cfg.PongWait = 20 * time.Second
497+
websocketClient.SetTimeoutConfig(cfg)
498+
```
396499

500+
By default, the server does not send out any pings and waits for a ping from the client for 60 seconds, before timing out.
501+
To configure the server to send out pings, the `PingPeriod` and `PongWait` must be set to a value greater than 0:
397502
```go
398503
cfg := ws.NewServerTimeoutConfig()
399-
cfg.PingWait = 0 // this instructs the server to wait forever
504+
cfg.PingPeriod = 10 * time.Second
505+
cfg.PongWait = 20 * time.Second
400506
websocketServer.SetTimeoutConfig(cfg)
401507
```
402508

403-
> A server-initiated ping may be supported in a future release.
509+
To disable sending ping messages, set the `PingPeriod` value to `0`.
404510

405511
## OCPP 2.0.1 Usage
406512

example/1.6/cp/charge_point_sim.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,10 @@ func setupTlsChargePoint(chargePointID string) ocpp16.ChargePoint {
6464
}
6565
}
6666
// Create client with TLS config
67-
client := ws.NewTLSClient(&tls.Config{
67+
client := ws.NewClient(ws.WithClientTLSConfig(&tls.Config{
6868
RootCAs: certPool,
6969
Certificates: clientCertificates,
70-
})
70+
}))
7171
return ocpp16.NewChargePoint(chargePointID, nil, client)
7272
}
7373

example/1.6/cs/central_system_sim.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,10 @@ func setupTlsCentralSystem() ocpp16.CentralSystem {
6767
if !ok {
6868
log.Fatalf("no required %v found", envVarServerCertificateKey)
6969
}
70-
server := ws.NewTLSServer(certificate, key, &tls.Config{
70+
server := ws.NewServer(ws.WithServerTLSConfig(certificate, key, &tls.Config{
7171
ClientAuth: tls.RequireAndVerifyClientCert,
7272
ClientCAs: certPool,
73-
})
73+
}))
7474
return ocpp16.NewCentralSystem(nil, server)
7575
}
7676

example/2.0.1/chargingstation/charging_station_sim.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,10 @@ func setupTlsChargingStation(chargingStationID string) ocpp2.ChargingStation {
6666
}
6767
}
6868
// Create client with TLS config
69-
client := ws.NewTLSClient(&tls.Config{
69+
client := ws.NewClient(ws.WithClientTLSConfig(&tls.Config{
7070
RootCAs: certPool,
7171
Certificates: clientCertificates,
72-
})
72+
}))
7373
return ocpp2.NewChargingStation(chargingStationID, nil, client)
7474
}
7575

example/2.0.1/csms/csms_sim.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,10 @@ func setupTlsCentralSystem() ocpp2.CSMS {
7070
if !ok {
7171
log.Fatalf("no required %v found", envVarServerCertificateKey)
7272
}
73-
server := ws.NewTLSServer(certificate, key, &tls.Config{
73+
server := ws.NewServer(ws.WithServerTLSConfig(certificate, key, &tls.Config{
7474
ClientAuth: tls.RequireAndVerifyClientCert,
7575
ClientCAs: certPool,
76-
})
76+
}))
7777
return ocpp2.NewCSMS(nil, server)
7878
}
7979

ocpp1.6/v16.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,13 @@ type ChargePoint interface {
153153
// if !ok {
154154
// log.Fatal("couldn't parse PEM certificate")
155155
// }
156-
// cp := NewClient("someUniqueId", nil, ws.NewTLSClient(&tls.Config{
156+
// cp := NewClient("someUniqueId", nil, ws.NewClient(ws.WithClientTLSConfig(&tls.Config{
157157
// RootCAs: certPool,
158-
// })
158+
// }))
159159
//
160160
// For more advanced options, or if a customer networking/occpj layer is required,
161-
// please refer to ocppj.Client and ws.WsClient.
162-
func NewChargePoint(id string, endpoint *ocppj.Client, client ws.WsClient) ChargePoint {
161+
// please refer to ocppj.Client and ws.Client.
162+
func NewChargePoint(id string, endpoint *ocppj.Client, client ws.Client) ChargePoint {
163163
if client == nil {
164164
client = ws.NewClient()
165165
}
@@ -338,8 +338,8 @@ type CentralSystem interface {
338338
//
339339
// If you need a TLS server, you may use the following:
340340
//
341-
// cs := NewServer(nil, ws.NewTLSServer("certificatePath", "privateKeyPath"))
342-
func NewCentralSystem(endpoint *ocppj.Server, server ws.WsServer) CentralSystem {
341+
// cs := NewServer(nil, ws.NewServer(ws.WithServerTLSConfig("certificatePath", "privateKeyPath", nil)))
342+
func NewCentralSystem(endpoint *ocppj.Server, server ws.Server) CentralSystem {
343343
if server == nil {
344344
server = ws.NewServer()
345345
}

ocpp1.6_test/ocpp16_test.go

Lines changed: 9 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"crypto/tls"
55
"fmt"
66
"net"
7-
"net/http"
87
"reflect"
98
"testing"
109
"time"
@@ -50,14 +49,18 @@ func (websocket MockWebSocket) TLSConnectionState() *tls.ConnectionState {
5049
return nil
5150
}
5251

52+
func (websocket MockWebSocket) IsConnected() bool {
53+
return true
54+
}
55+
5356
func NewMockWebSocket(id string) MockWebSocket {
5457
return MockWebSocket{id: id}
5558
}
5659

5760
// ---------------------- MOCK WEBSOCKET SERVER ----------------------
5861
type MockWebsocketServer struct {
5962
mock.Mock
60-
ws.WsServer
63+
ws.Server
6164
MessageHandler func(ws ws.Channel, data []byte) error
6265
NewClientHandler func(ws ws.Channel)
6366
CheckClientHandler ws.CheckClientHandler
@@ -77,11 +80,11 @@ func (websocketServer *MockWebsocketServer) Write(webSocketId string, data []byt
7780
return args.Error(0)
7881
}
7982

80-
func (websocketServer *MockWebsocketServer) SetMessageHandler(handler func(ws ws.Channel, data []byte) error) {
83+
func (websocketServer *MockWebsocketServer) SetMessageHandler(handler ws.MessageHandler) {
8184
websocketServer.MessageHandler = handler
8285
}
8386

84-
func (websocketServer *MockWebsocketServer) SetNewClientHandler(handler func(ws ws.Channel)) {
87+
func (websocketServer *MockWebsocketServer) SetNewClientHandler(handler ws.ConnectedHandler) {
8588
websocketServer.NewClientHandler = handler
8689
}
8790

@@ -96,14 +99,14 @@ func (websocketServer *MockWebsocketServer) NewClient(websocketId string, client
9699
websocketServer.MethodCalled("NewClient", websocketId, client)
97100
}
98101

99-
func (websocketServer *MockWebsocketServer) SetCheckClientHandler(handler func(id string, r *http.Request) bool) {
102+
func (websocketServer *MockWebsocketServer) SetCheckClientHandler(handler ws.CheckClientHandler) {
100103
websocketServer.CheckClientHandler = handler
101104
}
102105

103106
// ---------------------- MOCK WEBSOCKET CLIENT ----------------------
104107
type MockWebsocketClient struct {
105108
mock.Mock
106-
ws.WsClient
109+
ws.Client
107110
MessageHandler func(data []byte) error
108111
ReconnectedHandler func()
109112
DisconnectedHandler func(err error)
@@ -445,41 +448,6 @@ func (smartChargingListener *MockChargePointSmartChargingListener) OnGetComposit
445448
}
446449

447450
// ---------------------- COMMON UTILITY METHODS ----------------------
448-
func NewWebsocketServer(t *testing.T, onMessage func(data []byte) ([]byte, error)) *ws.Server {
449-
wsServer := ws.Server{}
450-
wsServer.SetMessageHandler(func(ws ws.Channel, data []byte) error {
451-
assert.NotNil(t, ws)
452-
assert.NotNil(t, data)
453-
if onMessage != nil {
454-
response, err := onMessage(data)
455-
assert.Nil(t, err)
456-
if response != nil {
457-
err = wsServer.Write(ws.ID(), data)
458-
assert.Nil(t, err)
459-
}
460-
}
461-
return nil
462-
})
463-
return &wsServer
464-
}
465-
466-
func NewWebsocketClient(t *testing.T, onMessage func(data []byte) ([]byte, error)) *ws.Client {
467-
wsClient := ws.Client{}
468-
wsClient.SetMessageHandler(func(data []byte) error {
469-
assert.NotNil(t, data)
470-
if onMessage != nil {
471-
response, err := onMessage(data)
472-
assert.Nil(t, err)
473-
if response != nil {
474-
err = wsClient.Write(data)
475-
assert.Nil(t, err)
476-
}
477-
}
478-
return nil
479-
})
480-
return &wsClient
481-
}
482-
483451
type expectedCentralSystemOptions struct {
484452
clientId string
485453
rawWrittenMessage []byte

ocpp2.0.1/v2.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -202,13 +202,13 @@ type ChargingStation interface {
202202
// if !ok {
203203
// log.Fatal("couldn't parse PEM certificate")
204204
// }
205-
// cs := NewChargingStation("someUniqueId", nil, ws.NewTLSClient(&tls.Config{
205+
// cs := NewChargingStation("someUniqueId", nil, ws.NewClient(ws.WithClientTLSConfig(&tls.Config{
206206
// RootCAs: certPool,
207-
// })
207+
// }))
208208
//
209209
// For more advanced options, or if a custom networking/occpj layer is required,
210-
// please refer to ocppj.Client and ws.WsClient.
211-
func NewChargingStation(id string, endpoint *ocppj.Client, client ws.WsClient) ChargingStation {
210+
// please refer to ocppj.Client and ws.Client.
211+
func NewChargingStation(id string, endpoint *ocppj.Client, client ws.Client) ChargingStation {
212212
if client == nil {
213213
client = ws.NewClient()
214214
}
@@ -414,8 +414,8 @@ type CSMS interface {
414414
//
415415
// If you need a TLS server, you may use the following:
416416
//
417-
// csms := NewCSMS(nil, ws.NewTLSServer("certificatePath", "privateKeyPath"))
418-
func NewCSMS(endpoint *ocppj.Server, server ws.WsServer) CSMS {
417+
// csms := NewCSMS(nil, ws.NewServer(ws.WithServerTLSConfig("certificatePath", "privateKeyPath", nil)))
418+
func NewCSMS(endpoint *ocppj.Server, server ws.Server) CSMS {
419419
if server == nil {
420420
server = ws.NewServer()
421421
}

0 commit comments

Comments
 (0)