add files

This commit is contained in:
ChiKyun Kim
2025-09-09 13:59:12 +09:00
parent e94b06888d
commit 747aabe224
791 changed files with 3888723 additions and 0 deletions

21
temp/RISE-V2G/LICENSE.txt Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2014-2017 V2G Clarity (Dr.-Ing. Marc Mültin)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

18
temp/RISE-V2G/README.md Normal file
View File

@@ -0,0 +1,18 @@
![RISE V2G logo](https://v2g-clarity.com/test/rise-v2g-logo-2/ "RISE V2G logo")
The open source reference implementation of the Vehicle-2-Grid communication interface ISO 15118
### About RISE V2G
RISE V2G is the **R**eference **I**mplementation **S**upporting the **E**volution of the **V**ehicle-**2**-**G**rid communication interface ISO 15118.
The international standard ISO 15118, entitled "Road vehicles - Vehicle to grid communication interface", defines a digital IP-based communication interface between an electric vehicle (EV) and a charging station (named Electric Vehicle Supply Equipment - EVSE). It allows for a user-friendly "Plug And Charge" mechanism for authentication, authorisation, billing, and flexible load control based on a wide set of information exchanged between the EV and EVSE.
A rise in the wide application of this standard is essential for reaching the goal of integrating EVs as flexible energy storage devices into a smart grid.
### Support Update
The RISE V2G project is no longer supported. Switch's new [Josev Community](https://github.com/SwitchEV/josev) project is replacing RISE V2G.
### License
RISE V2G is published under the [MIT License](https://github.com/V2GClarity/RISE-V2G/blob/master/LICENSE).

View File

@@ -0,0 +1,15 @@
[req]
prompt = no
distinguished_name = ca_dn
[ca_dn]
commonName = DE-ABC-C123ABC56
organizationName = RISE V2G Project
countryName = DE
domainComponent = MO
[ext]
basicConstraints = critical,CA:false
keyUsage = critical,digitalSignature,nonRepudiation,keyEncipherment,keyAgreement
subjectKeyIdentifier = hash

View File

@@ -0,0 +1,15 @@
[req]
prompt = no
distinguished_name = ca_dn
[ca_dn]
commonName = CPOSubCA1
organizationName = RISE V2G Project
countryName = DE
domainComponent = V2G
[ext]
basicConstraints = critical,CA:true,pathlen:1
keyUsage = critical,keyCertSign,cRLSign
subjectKeyIdentifier = hash

View File

@@ -0,0 +1,15 @@
[req]
prompt = no
distinguished_name = ca_dn
[ca_dn]
commonName = CPOSubCA2
organizationName = RISE V2G Project
countryName = DE
domainComponent = V2G
[ext]
basicConstraints = critical,CA:true,pathlen:0
keyUsage = critical,keyCertSign,cRLSign
subjectKeyIdentifier = hash

View File

@@ -0,0 +1,15 @@
[req]
prompt = no
distinguished_name = ca_dn
[ca_dn]
commonName = CPS Leaf
organizationName = RISE V2G Project
countryName = DE
domainComponent = CPS
[ext]
basicConstraints = critical,CA:false
keyUsage = critical,digitalSignature
subjectKeyIdentifier = hash

View File

@@ -0,0 +1,15 @@
[req]
prompt = no
distinguished_name = ca_dn
[ca_dn]
commonName = ProvSubCA1
organizationName = RISE V2G Project
countryName = DE
domainComponent = CPS
[ext]
basicConstraints = critical,CA:true,pathlen:1
keyUsage = critical,keyCertSign,cRLSign
subjectKeyIdentifier = hash

View File

@@ -0,0 +1,15 @@
[req]
prompt = no
distinguished_name = ca_dn
[ca_dn]
commonName = ProvSubCA2
organizationName = RISE V2G Project
countryName = DE
domainComponent = CPS
[ext]
basicConstraints = critical,CA:true,pathlen:0
keyUsage = critical,keyCertSign,cRLSign
subjectKeyIdentifier = hash

View File

@@ -0,0 +1,15 @@
[req]
prompt = no
distinguished_name = ca_dn
[ca_dn]
commonName = MORootCA
organizationName = RISE V2G Project
countryName = DE
domainComponent = MO
[ext]
basicConstraints = critical,CA:true
keyUsage = critical,keyCertSign,cRLSign
subjectKeyIdentifier = hash

View File

@@ -0,0 +1,15 @@
[req]
prompt = no
distinguished_name = ca_dn
[ca_dn]
commonName = MOSubCA1
organizationName = RISE V2G Project
countryName = DE
domainComponent = MO
[ext]
basicConstraints = critical,CA:true,pathlen:1
keyUsage = critical,keyCertSign,cRLSign
subjectKeyIdentifier = hash

View File

@@ -0,0 +1,15 @@
[req]
prompt = no
distinguished_name = ca_dn
[ca_dn]
commonName = MOSubCA2
organizationName = RISE V2G Project
countryName = DE
domainComponent = MO
[ext]
basicConstraints = critical,CA:true,pathlen:0
keyUsage = critical,digitalSignature,nonRepudiation,keyCertSign,cRLSign
subjectKeyIdentifier = hash

View File

@@ -0,0 +1,15 @@
[req]
prompt = no
distinguished_name = ca_dn
[ca_dn]
commonName = OEMProvCert
organizationName = RISE V2G Project
countryName = DE
domainComponent = OEM
[ext]
basicConstraints = critical,CA:false
keyUsage = critical,digitalSignature,keyAgreement
subjectKeyIdentifier = hash

View File

@@ -0,0 +1,15 @@
[req]
prompt = no
distinguished_name = ca_dn
[ca_dn]
commonName = OEMRootCA
organizationName = RISE V2G Project
countryName = DE
domainComponent = OEM
[ext]
basicConstraints = critical,CA:true
keyUsage = critical,keyCertSign,cRLSign
subjectKeyIdentifier = hash

View File

@@ -0,0 +1,15 @@
[req]
prompt = no
distinguished_name = ca_dn
[ca_dn]
commonName = OEMSubCA1
organizationName = RISE V2G Project
countryName = DE
domainComponent = OEM
[ext]
basicConstraints = critical,CA:true,pathlen:1
keyUsage = critical,keyCertSign,cRLSign
subjectKeyIdentifier = hash

View File

@@ -0,0 +1,15 @@
[req]
prompt = no
distinguished_name = ca_dn
[ca_dn]
commonName = OEMSubCA2
organizationName = RISE V2G Project
countryName = DE
domainComponent = OEM
[ext]
basicConstraints = critical,CA:true,pathlen:0
keyUsage = critical,keyCertSign,cRLSign
subjectKeyIdentifier = hash

View File

@@ -0,0 +1,15 @@
[req]
prompt = no
distinguished_name = ca_dn
[ca_dn]
commonName = SECCCert
organizationName = RISE V2G Project
countryName = DE
domainComponent = CPO
[ext]
basicConstraints = critical,CA:false
keyUsage = critical,digitalSignature,keyAgreement
subjectKeyIdentifier = hash

View File

@@ -0,0 +1,15 @@
[req]
prompt = no
distinguished_name = ca_dn
[ca_dn]
commonName = V2GRootCA
organizationName = RISE V2G Project
countryName = DE
domainComponent = V2G
[ext]
basicConstraints = critical,CA:true
keyUsage = critical,keyCertSign,cRLSign
subjectKeyIdentifier = hash

View File

@@ -0,0 +1,12 @@
@echo off
REM This is a useful small shell script to automatically copy the Java Keystores (.jks files), .p12 containers and the DER encoded Mobility Operator Sub-CA private key to the places in the RISE V2G project where they belong. Execute this script after you executed the generateCertificates.sh script.
copy keystores\evccKeystore.jks ..\RISE-V2G-EVCC
copy keystores\evccTruststore.jks ..\RISE-V2G-EVCC
copy keystores\seccKeystore.jks ..\RISE-V2G-SECC
copy keystores\seccTruststore.jks ..\RISE-V2G-SECC
copy certs\cpsCertChain.p12 ..\RISE-V2G-SECC
copy certs\moCertChain.p12 ..\RISE-V2G-SECC
copy privateKeys\moSubCA2.pkcs8.der ..\RISE-V2G-SECC

View File

@@ -0,0 +1,11 @@
# This is a useful small shell script to automatically copy the Java Keystores (.jks files), .p12 containers and the DER encoded Mobility Operator Sub-CA private key to the places in the RISE V2G project where they belong. Execute this script after you executed the generateCertificates.sh script.
cp keystores/evccKeystore.jks ../RISE-V2G-EVCC/
cp keystores/evccTruststore.jks ../RISE-V2G-EVCC/
cp keystores/seccKeystore.jks ../RISE-V2G-SECC/
cp keystores/seccTruststore.jks ../RISE-V2G-SECC/
cp certs/cpsCertChain.p12 ../RISE-V2G-SECC/
cp certs/moCertChain.p12 ../RISE-V2G-SECC/
cp privateKeys/moSubCA2.pkcs8.der ../RISE-V2G-SECC/

View File

@@ -0,0 +1,272 @@
@echo off
REM *******************************************************************************
REM The MIT License (MIT)
REM
REM Copyright (c) 2015-2018 V2G Clarity (Dr. Marc Mültin)
REM
REM Permission is hereby granted, free of charge, to any person obtaining a copy
REM of this software and associated documentation files (the "Software"), to deal
REM in the Software without restriction, including without limitation the rights
REM to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
REM copies of the Software, and to permit persons to whom the Software is
REM furnished to do so, subject to the following conditions:
REM
REM The above copyright notice and this permission notice shall be included in
REM all copies or substantial portions of the Software.
REM
REM THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
REM IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
REM FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
REM AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
REM LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
REM OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
REM THE SOFTWARE.
REM *******************************************************************************
REM ===============================================================================================================
REM This shell script can be used to create all necessary certificates and Keystores needed in order to
REM - successfully perform a TLS handshake between the EVCC (TLSClient) and the SECC (TLSServer) and
REM - install/update a contract certificate in the EVCC.
REM
REM This file shall serve you with all information needed to create your own certificate chains.
REM
REM Helpful information about using openssl is provided by Ivan Ristic's book "Bulletproof SSL and TLS".
REM Furthermore, you should have openssl 1.0.2 (or above) installed to comply with all security requirements
REM imposed by ISO 15118. For example, openssl 0.9.8 does not come with SHA-2 for SHA-256 signature algorithms.
REM Some MacOS X installations unfortunately still use openssl < v1.0.2. You could use Homebrew to install openssl.
REM Be aware that you probably then need to use an absolute path for your openssl commands, such as
REM /usr/local/Cellar/openssl/1.0.2h_1/bin/openssl.
REM
REM Author: Dr. Marc Mültin (marc.mueltin@v2g-clarity.com)
REM ===============================================================================================================
REM Some variables to create different outcomes of the PKI for testing purposes. Change the validity periods (given in number of days) to test
REM - valid certificates (e.g. contract certificate or Sub-CA certificate)
REM - expired certificates (e.g. contract certificate or Sub-CA certificates) -> you need to reset your system time to the past to create expired certificates
REM - a to be updated contract certificate
SET validity_contract_cert=730
SET validity_mo_subca1_cert=1460
SET validity_mo_subca2_cert=1460
SET validity_oem_prov_cert=1460
SET validity_oem_subca1_cert=1460
SET validity_oem_subca2_cert=1460
SET validity_cps_leaf_cert=90
SET validity_cps_subca1_cert=1460
SET validity_cps_subca2_cert=730
SET validity_secc_cert=60
SET validity_cpo_subca1_cert=1460
SET validity_cpo_subca2_cert=365
SET validity_v2g_root_cert=3650
SET validity_oem_root_cert=3650
SET validity_mo_root_cert=3650
REM FURTHER REMARKS:
REM 1. OpenSSL does not use the named curve 'secp256r1' but the equivalent 'prime256v1'. So this file uses only 'prime256v1'.
REM 2. The password used is stored in the file passphrase.txt. In it, you'll find two lines. The first line is used for the -passin option, the second line for the -passout option (especially when dealing with PKCS12 containers). See also https://www.openssl.org/docs/man1.0.2/apps/openssl.html, section "Pass Phrase Arguments"
REM 0) Create directories if not yet existing
if exist keystores rd /s /q keystores REM the keystores in the keystores folder (if existing) need to be deleted at first, so delete the complete folder
if not exist certs mkdir certs
if not exist csrs mkdir csrs
if not exist keystores mkdir keystores
if not exist privateKeys mkdir privateKeys
REM 1) Create a self-signed V2GRootCA certificate
REM 1.1) Create a private key
REM - private key -> -genkey
REM - with elliptic curve parameters -> ecparam
REM - using the named curve prime256v1 -> -name prime256v1
REM - encrypt the key with symmetric cipher AES-128-CBC using the 'ec' utility command -> ec -aes-128-cbc
REM - the passphrase for the encryption of the private key is provided in a file -> -passout file:passphrase.txt
REM - save the encrypted private key at the location provided -> -out
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys/v2gRootCA.key
REM 1.2) Create a CSR
REM - new -> -new
REM - certificate signing request -> req
REM - using the previously created private key from which the public key can be derived -> -key
REM - use the passwort stored in the file to decrypt the private key -> -passin
REM - take the values needed for the Distinguished Name (DN) from the configuration file -> -config
REM - save the CSR at the location provided -> -out
openssl req -new -key privateKeys\v2gRootCA.key -passin file:passphrase.txt -config configs\v2gRootCACert.cnf -out csrs\v2gRootCA.csr
REM 1.3) Create an X.509 certificate
REM - use the X.509 utility command -> x509
REM - requesting a new X.509 certificate ... -> -req
REM - ... using a CSR file that is located at -> -in
REM - we need an X.509v3 (version 3) certificate that allows for extensions. Those are specified in an extensions file ... -> -extfile
REM - ... that contains a section marked with 'ext' -> -extensions
REM - self-sign the certificate with the previously generated private key -> -signkey
REM - use the passwort stored in the file to decrypt the private key -> -passin
REM - tell OpenSSL to use SHA-256 for creating the digital signature (otherwise SHA1 would be used) -> -sha256
REM - each issued certificate must contain a unique serial number assigned by the CA (must be unique within the issuers number range) -> -set_serial
REM - save the certificate at the location provided -> -out
REM - make the certificate valid for 40 years (give in days) -> -days
openssl x509 -req -in csrs\v2gRootCA.csr -extfile configs\v2gRootCACert.cnf -extensions ext -signkey privateKeys\v2gRootCA.key -passin file:passphrase.txt -sha256 -set_serial 12345 -out certs\v2gRootCACert.pem -days %validity_v2g_root_cert%
REM 2) Create an intermediate CPO sub-CA 1 certificate which is directly signed by the V2GRootCA certificate
REM 2.1) Create a private key (same procedure as for V2GRootCA)
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys\cpoSubCA1.key
REM 2.2) Create a CSR (same procedure as for V2GRootCA)
openssl req -new -key privateKeys\cpoSubCA1.key -passin file:passphrase.txt -config configs\cpoSubCA1Cert.cnf -out csrs\cpoSubCA1.csr
REM 2.3) Create an X.509 certificate (same procedure as for V2GRootCA, but with the difference that we need the -CA switch to point to the CA certificate, followed by the -CAkey switch that tells OpenSSL where to find the CAs private key. We need the private key to create the signature and the public key certificate to make sure that the CAs certificate and private key match.
openssl x509 -req -in csrs\cpoSubCA1.csr -extfile configs\cpoSubCA1Cert.cnf -extensions ext -CA certs\v2gRootCACert.pem -CAkey privateKeys\v2gRootCA.key -passin file:passphrase.txt -set_serial 12345 -out certs\cpoSubCA1Cert.pem -days %validity_cpo_subca1_cert%
REM 3) Create a second intermediate CPO sub-CA certificate (sub-CA 2) just the way the previous intermedia certificate was created which is directly signed by the CPOSubCA1
REM Differences to CPOSubCA1
REM - basicConstraints in config file sets pathlength to 0 (meaning that no further sub-CA certificates may be signed with this certificate, a leaf certificate must follow this certificate in a certificate chain)
REM - validity is set to 1 year (1 - 2 years are allowed according to ISO 15118)
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys\cpoSubCA2.key
openssl req -new -key privateKeys\cpoSubCA2.key -passin file:passphrase.txt -config configs\cpoSubCA2Cert.cnf -out csrs\cpoSubCA2.csr
openssl x509 -req -in csrs\cpoSubCA2.csr -extfile configs\cpoSubCA2Cert.cnf -extensions ext -CA certs\cpoSubCA1Cert.pem -CAkey privateKeys\cpoSubCA1.key -passin file:passphrase.txt -set_serial 12345 -days %validity_cpo_subca2_cert% -out certs\cpoSubCA2Cert.pem
REM 4) Create an SECCCert certificate which is the leaf certificate belonging to the charging station which authenticates itself to the EVCC during a TLS handshake, signed by CPOSubCA2 certificate
REM Differences to CPOSubCA1 and CPOSubCA2
REM - basicConstraints sets CA to false, no pathlen is therefore set
REM - keyusage is set to digitalSignature instead of keyCertSign and cRLSign
REM - validity is set to 60 days (2 - 3 months are allowed according to ISO 15118)
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys\secc.key
openssl req -new -key privateKeys\secc.key -passin file:passphrase.txt -config configs\seccCert.cnf -out csrs\seccCert.csr
openssl x509 -req -in csrs\seccCert.csr -extfile configs\seccCert.cnf -extensions ext -CA certs\cpoSubCA2Cert.pem -CAkey privateKeys\cpoSubCA2.key -passin file:passphrase.txt -set_serial 12345 -days %validity_secc_cert% -out certs\seccCert.pem
REM 5) Create a self-signed OEMRootCA certificate (validity is up to the OEM, this example applies the same validity as the V2GRootCA)
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys\oemRootCA.key
openssl req -new -key privateKeys\oemRootCA.key -passin file:passphrase.txt -config configs\oemRootCACert.cnf -out csrs\oemRootCA.csr
openssl x509 -req -in csrs\oemRootCA.csr -extfile configs\oemRootCACert.cnf -extensions ext -signkey privateKeys\oemRootCA.key -passin file:passphrase.txt -sha256 -set_serial 12345 -out certs\oemRootCACert.pem -days %validity_oem_root_cert%
REM 6) Create an intermediate OEM sub-CA certificate which is directly signed by the OEMRootCA certificate (validity is up to the OEM, this example applies the same validity as the CPOSubCA1)
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys\oemSubCA1.key
openssl req -new -key privateKeys\oemSubCA1.key -passin file:passphrase.txt -config configs\oemSubCA1Cert.cnf -out csrs\oemSubCA1.csr
openssl x509 -req -in csrs\oemSubCA1.csr -extfile configs\oemSubCA1Cert.cnf -extensions ext -CA certs\oemRootCACert.pem -CAkey privateKeys\oemRootCA.key -passin file:passphrase.txt -set_serial 12345 -days %validity_oem_subca1_cert% -out certs\oemSubCA1Cert.pem
REM 7) Create a second intermediate OEM sub-CA certificate which is directly signed by the OEMSubCA1 certificate (validity is up to the OEM, this example applies the same validity as the CPOSubCA2)
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys\oemSubCA2.key
openssl req -new -key privateKeys\oemSubCA2.key -passin file:passphrase.txt -config configs\oemSubCA2Cert.cnf -out csrs\oemSubCA2.csr
openssl x509 -req -in csrs\oemSubCA2.csr -extfile configs\oemSubCA2Cert.cnf -extensions ext -CA certs\oemSubCA1Cert.pem -CAkey privateKeys\oemSubCA1.key -passin file:passphrase.txt -set_serial 12345 -days %validity_oem_subca2_cert% -out certs\oemSubCA2Cert.pem
REM 8) Create an OEM provisioning certificate which is the leaf certificate belonging to the OEM certificate chain (used for contract certificate installation)
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys\oemProv.key
openssl req -new -key privateKeys\oemProv.key -passin file:passphrase.txt -config configs\oemProvCert.cnf -out csrs\oemProvCert.csr
openssl x509 -req -in csrs\oemProvCert.csr -extfile configs\oemProvCert.cnf -extensions ext -CA certs\oemSubCA2Cert.pem -CAkey privateKeys\oemSubCA2.key -passin file:passphrase.txt -set_serial 12345 -days %validity_oem_prov_cert% -out certs\oemProvCert.pem
REM 9) Create a self-signed MORootCA (mobility operator) certificate (validity is up to the MO, this example applies the same validity as the V2GRootCA)
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys\moRootCA.key
openssl req -new -key privateKeys\moRootCA.key -passin file:passphrase.txt -config configs\moRootCACert.cnf -out csrs\moRootCA.csr
openssl x509 -req -in csrs\moRootCA.csr -extfile configs\moRootCACert.cnf -extensions ext -signkey privateKeys\moRootCA.key -passin file:passphrase.txt -sha256 -set_serial 12345 -out certs\moRootCACert.pem -days %validity_mo_root_cert%
REM 10) Create an intermediate MO sub-CA certificate which is directly signed by the MORootCA certificate (validity is up to the MO, this example applies the same validity as the CPOSubCA1)
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys\moSubCA1.key
openssl req -new -key privateKeys\moSubCA1.key -passin file:passphrase.txt -config configs\moSubCA1Cert.cnf -extensions ext -out csrs\moSubCA1.csr
openssl x509 -req -in csrs\moSubCA1.csr -extfile configs\moSubCA1Cert.cnf -extensions ext -CA certs\moRootCACert.pem -CAkey privateKeys\moRootCA.key -passin file:passphrase.txt -set_serial 12345 -days %validity_mo_subca1_cert% -out certs\moSubCA1Cert.pem
REM 11) Create a second intermediate MO sub-CA certificate which is directly signed by the MOSubCA1 certificate (validity is up to the MO, this example applies the same validity as the CPOSubCA2)
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys\moSubCA2.key
openssl req -new -key privateKeys\moSubCA2.key -passin file:passphrase.txt -config configs\moSubCA2Cert.cnf -out csrs\moSubCA2.csr
openssl x509 -req -in csrs\moSubCA2.csr -extfile configs\moSubCA2Cert.cnf -extensions ext -CA certs\moSubCA1Cert.pem -CAkey privateKeys\moSubCA1.key -passin file:passphrase.txt -set_serial 12345 -days %validity_mo_subca2_cert% -out certs\moSubCA2Cert.pem
REM 12) Create a contract certificate which is the leaf certificate belonging to the MO certificate chain (used for contract certificate installation)
REM Validity can be between 4 weeks and 2 years (restricted by the contract lifetime), for testing purposes the validity will be set to 2 years
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys\contract.key
openssl req -new -key privateKeys\contract.key -passin file:passphrase.txt -config configs\contractCert.cnf -out csrs\contractCert.csr
openssl x509 -req -in csrs\contractCert.csr -extfile configs\contractCert.cnf -extensions ext -CA certs\moSubCA2Cert.pem -CAkey privateKeys\moSubCA2.key -passin file:passphrase.txt -set_serial 12345 -days %validity_contract_cert% -out certs\contractCert.pem
type certs\moSubCA2Cert.pem certs\moSubCA1Cert.pem > certs\intermediateMOCACerts.pem
openssl pkcs12 -export -inkey privateKeys\contract.key -in certs\contractCert.pem -certfile certs\intermediateMOCACerts.pem -aes128 -passin file:passphrase.txt -passout file:passphrase.txt -name contract_cert -out certs\moCertChain.p12
REM 13) Create an intermediate provisioning service sub-CA certificate which is directly signed by the V2GRootCA certificate
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys\cpsSubCA1.key
openssl req -new -key privateKeys\cpsSubCA1.key -passin file:passphrase.txt -config configs\cpsSubCA1Cert.cnf -out csrs\cpsSubCA1.csr
openssl x509 -req -in csrs\cpsSubCA1.csr -extfile configs\cpsSubCA1Cert.cnf -extensions ext -CA certs\v2gRootCACert.pem -CAkey privateKeys\v2gRootCA.key -passin file:passphrase.txt -set_serial 12345 -days %validity_cps_subca1_cert% -out certs\cpsSubCA1Cert.pem
REM 14) Create a second intermediate provisioning sub-CA certificate which is directly signed by the CPSSubCA1 certificate (validity 1 - 2 years, we make it 2 years)
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys\cpsSubCA2.key
openssl req -new -key privateKeys\cpsSubCA2.key -passin file:passphrase.txt -config configs\cpsSubCA2Cert.cnf -out csrs\cpsSubCA2.csr
openssl x509 -req -in csrs\cpsSubCA2.csr -extfile configs\cpsSubCA2Cert.cnf -extensions ext -CA certs\cpsSubCA1Cert.pem -CAkey privateKeys\cpsSubCA1.key -passin file:passphrase.txt -set_serial 12345 -days %validity_cps_subca2_cert% -out certs\cpsSubCA2Cert.pem
REM 15) Create a provisioning service certificate which is the leaf certificate belonging to the provisioning certificate chain (used for contract certificate installation)
REM Validity can be between 2 - 3 months, we make it 3 months
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys\cpsLeaf.key
openssl req -new -key privateKeys\cpsLeaf.key -passin file:passphrase.txt -config configs\cpsLeafCert.cnf -out csrs\cpsLeafCert.csr
openssl x509 -req -in csrs\cpsLeafCert.csr -extfile configs\cpsLeafCert.cnf -extensions ext -CA certs\cpsSubCA2Cert.pem -CAkey privateKeys\cpsSubCA2.key -passin file:passphrase.txt -set_serial 12345 -days %validity_cps_leaf_cert% -out certs\cpsLeafCert.pem
type certs\cpsSubCA2Cert.pem certs\cpsSubCA1Cert.pem > certs\intermediateCPSCACerts.pem
openssl pkcs12 -export -inkey privateKeys\cpsLeaf.key -in certs\cpsLeafCert.pem -certfile certs\intermediateCPSCACerts.pem -aes128 -passin file:passphrase.txt -passout file:passphrase.txt -name cps_leaf_cert -out certs\cpsCertChain.p12
REM 16) Finally we need to convert the certificates from PEM format to DER format (PEM is the default format, but ISO 15118 only allows DER format)
openssl x509 -inform PEM -in certs\v2gRootCACert.pem -outform DER -out certs\v2gRootCACert.der
openssl x509 -inform PEM -in certs\cpsSubCA1Cert.pem -outform DER -out certs\cpsSubCA1Cert.der
openssl x509 -inform PEM -in certs\cpsSubCA2Cert.pem -outform DER -out certs\cpsSubCA2Cert.der
openssl x509 -inform PEM -in certs\cpsLeafCert.pem -outform DER -out certs\cpsLeafCert.der
openssl x509 -inform PEM -in certs\cpoSubCA1Cert.pem -outform DER -out certs\cpoSubCA1Cert.der
openssl x509 -inform PEM -in certs\cpoSubCA2Cert.pem -outform DER -out certs\cpoSubCA2Cert.der
openssl x509 -inform PEM -in certs\seccCert.pem -outform DER -out certs\seccCert.der
openssl x509 -inform PEM -in certs\oemRootCACert.pem -outform DER -out certs\oemRootCACert.der
openssl x509 -inform PEM -in certs\oemSubCA1Cert.pem -outform DER -out certs\oemSubCA1Cert.der
openssl x509 -inform PEM -in certs\oemSubCA2Cert.pem -outform DER -out certs\oemSubCA2Cert.der
openssl x509 -inform PEM -in certs\oemProvCert.pem -outform DER -out certs\oemProvCert.der
openssl x509 -inform PEM -in certs\moRootCACert.pem -outform DER -out certs\moRootCACert.der
openssl x509 -inform PEM -in certs\moSubCA1Cert.pem -outform DER -out certs\moSubCA1Cert.der
openssl x509 -inform PEM -in certs\moSubCA2Cert.pem -outform DER -out certs\moSubCA2Cert.der
openssl x509 -inform PEM -in certs\contractCert.pem -outform DER -out certs\contractCert.der
REM Since the intermediate certificates need to be in PEM format when putting them in a PKCS12 container and the resulting PKCS12 file is a binary format, it might be sufficient. Otherwise, I have currently no idea how to covert the intermediate certificates in DER without running into problems when creating the PKCS12 container.
REM 17) In case you want the private keys in PKCSREM8 file format and DER encoded, use this command. Especially necessary for the private key of MOSubCA2 in RISE V2G
openssl pkcs8 -topk8 -in privateKeys\moSubCA2.key -inform PEM -passin file:passphrase.txt -passout file:passphrase.txt -outform DER -out privateKeys\moSubCA2.pkcs8.der -v1 PBE-SHA1-3DES
REM 18) Create the keystores for the EVCC and SECC. We need to first create the PKCS12 files and then import them into the JKS using the 'keytool' command.
REM - create a PKCS12 file -> -export
REM - if no private keys are added, e.g. for a truststore that holds only root CA certificates -> -nokeys
REM - add a private key to the newly created PKCS12 container; must be in PEM format (not DER) -> -inkey
REM - add any certificate (leaf certificate or root certificate); must be in PEM format (not DER) -> -in
REM - add additional certificats (intermediate sub-CA certificates or more root CA certificates); must be in PEM format (not DER) -> -certfile
REM - use AES to encrypt private keys before outputting -> -aes128
REM - passphrase source to decrypt any private keys that are to be imported -> -passin
REM - passphrase to encrypt any outputted private keys with -> -passout
REM - provide the "friendly name" for the leaf certificate, which is used as the alias in Java -> -name
REM - provide the "friendly name" for CA certificates, which is used as the alias in Java;
REM this option may be used multiple times to specify names for all certificates in the order they appear -> -caname
REM - the file location to store the PKCS12 data container -> -out
REM
REM 18.1) SECC keystore needs to hold initially hold the OEM provisioning certificate (contract certificate will be installed with ISO 15118 message exchange)
REM First, concatenate the intermediate sub-CA certificates into one file intermediateCAs.pem
REM IMPORTANT: Concatenate in such a way that the chain leads from the leaf certificate to the root (excluding), this means here: first parameter of the type command is the sub-CA certificate which signs the leaf certificate (in this case cpoSubCA2.pem). Otherwise the Java method getCertificateChain() which is called on the keystore will only return the leaf certificate!
type certs\cpoSubCA2Cert.pem certs\cpoSubCA1Cert.pem > certs\intermediateCPOCACerts.pem
REM Put the seccCertificate, the private key associated with the seccCertificate as well as the intermediate sub-CA certificates in a PKCS12 container with the -certfile switch.
openssl pkcs12 -export -inkey privateKeys\secc.key -in certs\seccCert.pem -name secc_cert -certfile certs\intermediateCPOCACerts.pem -caname mo_subca_2 -caname mo_subca_1 -aes128 -passin file:passphrase.txt -passout file:passphrase.txt -out keystores\cpoCertChain.p12
keytool -importkeystore -srckeystore keystores\cpoCertChain.p12 -srcstoretype pkcs12 -srcstorepass:file passphrase.txt -srcalias secc_cert -destalias secc_cert -destkeystore keystores\seccKeystore.jks -storepass:file passphrase.txt -noprompt
REM
REM 18.2) EVCC keystore needs to initally hold the OEM provisioning certificate (contract certificate will be installed with ISO 15118 message exchange)
type certs\oemSubCA2Cert.pem certs\oemSubCA1Cert.pem > certs\intermediateOEMCACerts.pem
openssl pkcs12 -export -inkey privateKeys\oemProv.key -in certs\oemProvCert.pem -name oem_prov_cert -certfile certs\intermediateOEMCACerts.pem -caname oem_subca_2 -caname oem_subca_1 -aes128 -passin file:passphrase.txt -passout file:passphrase.txt -out keystores\oemCertChain.p12
keytool -importkeystore -srckeystore keystores\oemCertChain.p12 -srcstoretype pkcs12 -srcstorepass:file passphrase.txt -srcalias oem_prov_cert -destalias oem_prov_cert -destkeystore keystores\evccKeystore.jks -storepass:file passphrase.txt -noprompt
REM 19) Create truststores for EVCC and SECC. Storing trust anchors in PKCS12 is not supported in Java 8. For creating trusstores we need a Java KeyStore (JKS) and import the DER encoded root CA certificates.
REM
REM 19.1) EVCC truststore needs to hold the V2GRootCA certificate
keytool -import -keystore keystores\evccTruststore.jks -alias v2g_root_ca -file certs\v2gRootCACert.der -storepass:file passphrase.txt -noprompt
REM
REM 19.2) SECC truststore needs to hold the V2G root CA certificate and MO root CA certificate.
REM According to ISO 15118-2, MO root CA is not necessarily needed as the MO sub-CA 1 could instead be signed by a V2G root CA.
keytool -import -keystore keystores\seccTruststore.jks -alias v2g_root_ca -file certs\v2gRootCACert.der -storepass:file passphrase.txt -noprompt
keytool -import -keystore keystores\seccTruststore.jks -alias mo_root_ca -file certs\moRootCACert.der -storepass:file passphrase.txt -noprompt
REM Side notes for OCSP stapling in Java: see http://openjdk.java.net/jeps/249

View File

@@ -0,0 +1,270 @@
#*******************************************************************************
# The MIT License (MIT)
#
# Copyright (c) 2015-2018 V2G Clarity (Dr. Marc Mültin)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#*******************************************************************************
# ===============================================================================================================
# This shell script can be used to create all necessary certificates and Keystores needed in order to
# - successfully perform a TLS handshake between the EVCC (TLSClient) and the SECC (TLSServer) and
# - install/update a contract certificate in the EVCC.
#
# This file shall serve you with all information needed to create your own certificate chains.
#
# Helpful information about using openssl is provided by Ivan Ristic's book "Bulletproof SSL and TLS".
# Furthermore, you should have openssl 1.0.2 (or above) installed to comply with all security requirements
# imposed by ISO 15118. For example, openssl 0.9.8 does not come with SHA-2 for SHA-256 signature algorithms.
# Some MacOS X installations unfortunately still use openssl < v1.0.2. You could use Homebrew to install openssl.
# Be aware that you probably then need to use an absolute path for your openssl commands, such as
# /usr/local/Cellar/openssl/1.0.2h_1/bin/openssl.
#
# Author: Dr. Marc Mültin (marc.mueltin@v2g-clarity.com)
# ===============================================================================================================
# Some variables to create different outcomes of the PKI for testing purposes. Change the validity periods (given in number of days) to test
# - valid certificates (e.g. contract certificate or Sub-CA certificate)
# - expired certificates (e.g. contract certificate or Sub-CA certificates) -> you need to reset your system time to the past to create expired certificates
# - a to be updated contract certificate
validity_contract_cert=730
validity_mo_subca1_cert=1460
validity_mo_subca2_cert=1460
validity_oem_prov_cert=1460
validity_oem_subca1_cert=1460
validity_oem_subca2_cert=1460
validity_cps_leaf_cert=90
validity_cps_subca1_cert=1460
validity_cps_subca2_cert=730
validity_secc_cert=60
validity_cpo_subca1_cert=1460
validity_cpo_subca2_cert=365
validity_v2g_root_cert=3650
validity_oem_root_cert=3650
validity_mo_root_cert=3650
# FURTHER REMARKS:
# 1. OpenSSL does not use the named curve 'secp256r1' but the equivalent 'prime256v1'. So this file uses only 'prime256v1'.
# 2. The password used is stored in the file passphrase.txt. In it, you'll find two lines. The first line is used for the -passin option, the second line for the -passout option (especially when dealing with PKCS12 containers). See also https://www.openssl.org/docs/man1.0.2/apps/openssl.html, section "Pass Phrase Arguments"
# 0) Create directories if not yet existing
rm -r keystores # the keystores in the keystores folder (if existing) need to be deleted at first, so delete the complete folder
mkdir -p certs
mkdir -p csrs
mkdir -p keystores
mkdir -p privateKeys
# 1) Create a self-signed V2GRootCA certificate
# 1.1) Create a private key
# - private key -> -genkey
# - with elliptic curve parameters -> ecparam
# - using the named curve prime256v1 -> -name prime256v1
# - encrypt the key with symmetric cipher AES-128-CBC using the 'ec' utility command -> ec -aes-128-cbc
# - the passphrase for the encryption of the private key is provided in a file -> -passout file:passphrase.txt
# - save the encrypted private key at the location provided -> -out
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys/v2gRootCA.key
# 1.2) Create a CSR
# - new -> -new
# - certificate signing request -> req
# - using the previously created private key from which the public key can be derived -> -key
# - use the passwort stored in the file to decrypt the private key -> -passin
# - take the values needed for the Distinguished Name (DN) from the configuration file -> -config
# - save the CSR at the location provided -> -out
openssl req -new -key privateKeys/v2gRootCA.key -passin file:passphrase.txt -config configs/v2gRootCACert.cnf -out csrs/v2gRootCA.csr
# 1.3) Create an X.509 certificate
# - use the X.509 utility command -> x509
# - requesting a new X.509 certificate ... -> -req
# - ... using a CSR file that is located at -> -in
# - we need an X.509v3 (version 3) certificate that allows for extensions. Those are specified in an extensions file ... -> -extfile
# - ... that contains a section marked with 'ext' -> -extensions
# - self-sign the certificate with the previously generated private key -> -signkey
# - use the passwort stored in the file to decrypt the private key -> -passin
# - tell OpenSSL to use SHA-256 for creating the digital signature (otherwise SHA1 would be used) -> -sha256
# - each issued certificate must contain a unique serial number assigned by the CA (must be unique within the issuers number range) -> -set_serial
# - save the certificate at the location provided -> -out
# - make the certificate valid for 40 years (give in days) -> -days
openssl x509 -req -in csrs/v2gRootCA.csr -extfile configs/v2gRootCACert.cnf -extensions ext -signkey privateKeys/v2gRootCA.key -passin file:passphrase.txt -sha256 -set_serial 12345 -out certs/v2gRootCACert.pem -days $validity_v2g_root_cert
# 2) Create an intermediate CPO sub-CA 1 certificate which is directly signed by the V2GRootCA certificate
# 2.1) Create a private key (same procedure as for V2GRootCA)
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys/cpoSubCA1.key
# 2.2) Create a CSR (same procedure as for V2GRootCA)
openssl req -new -key privateKeys/cpoSubCA1.key -passin file:passphrase.txt -config configs/cpoSubCA1Cert.cnf -out csrs/cpoSubCA1.csr
# 2.3) Create an X.509 certificate (same procedure as for V2GRootCA, but with the difference that we need the -CA switch to point to the CA certificate, followed by the -CAkey switch that tells OpenSSL where to find the CAs private key. We need the private key to create the signature and the public key certificate to make sure that the CAs certificate and private key match.
openssl x509 -req -in csrs/cpoSubCA1.csr -extfile configs/cpoSubCA1Cert.cnf -extensions ext -CA certs/v2gRootCACert.pem -CAkey privateKeys/v2gRootCA.key -passin file:passphrase.txt -set_serial 12345 -out certs/cpoSubCA1Cert.pem -days $validity_cpo_subca1_cert
# 3) Create a second intermediate CPO sub-CA certificate (sub-CA 2) just the way the previous intermedia certificate was created which is directly signed by the CPOSubCA1
# Differences to CPOSubCA1
# - basicConstraints in config file sets pathlength to 0 (meaning that no further sub-CA certificates may be signed with this certificate, a leaf certificate must follow this certificate in a certificate chain)
# - validity is set to 1 year (1 - 2 years are allowed according to ISO 15118)
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys/cpoSubCA2.key
openssl req -new -key privateKeys/cpoSubCA2.key -passin file:passphrase.txt -config configs/cpoSubCA2Cert.cnf -out csrs/cpoSubCA2.csr
openssl x509 -req -in csrs/cpoSubCA2.csr -extfile configs/cpoSubCA2Cert.cnf -extensions ext -CA certs/cpoSubCA1Cert.pem -CAkey privateKeys/cpoSubCA1.key -passin file:passphrase.txt -set_serial 12345 -days $validity_cpo_subca2_cert -out certs/cpoSubCA2Cert.pem
# 4) Create an SECCCert certificate which is the leaf certificate belonging to the charging station which authenticates itself to the EVCC during a TLS handshake, signed by CPOSubCA2 certificate
# Differences to CPOSubCA1 and CPOSubCA2
# - basicConstraints sets CA to false, no pathlen is therefore set
# - keyusage is set to digitalSignature instead of keyCertSign and cRLSign
# - validity is set to 60 days (2 - 3 months are allowed according to ISO 15118)
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys/secc.key
openssl req -new -key privateKeys/secc.key -passin file:passphrase.txt -config configs/seccCert.cnf -out csrs/seccCert.csr
openssl x509 -req -in csrs/seccCert.csr -extfile configs/seccCert.cnf -extensions ext -CA certs/cpoSubCA2Cert.pem -CAkey privateKeys/cpoSubCA2.key -passin file:passphrase.txt -set_serial 12345 -days $validity_secc_cert -out certs/seccCert.pem
# 5) Create a self-signed OEMRootCA certificate (validity is up to the OEM, this example applies the same validity as the V2GRootCA)
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys/oemRootCA.key
openssl req -new -key privateKeys/oemRootCA.key -passin file:passphrase.txt -config configs/oemRootCACert.cnf -out csrs/oemRootCA.csr
openssl x509 -req -in csrs/oemRootCA.csr -extfile configs/oemRootCACert.cnf -extensions ext -signkey privateKeys/oemRootCA.key -passin file:passphrase.txt -sha256 -set_serial 12345 -out certs/oemRootCACert.pem -days $validity_oem_root_cert
# 6) Create an intermediate OEM sub-CA certificate which is directly signed by the OEMRootCA certificate (validity is up to the OEM, this example applies the same validity as the CPOSubCA1)
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys/oemSubCA1.key
openssl req -new -key privateKeys/oemSubCA1.key -passin file:passphrase.txt -config configs/oemSubCA1Cert.cnf -out csrs/oemSubCA1.csr
openssl x509 -req -in csrs/oemSubCA1.csr -extfile configs/oemSubCA1Cert.cnf -extensions ext -CA certs/oemRootCACert.pem -CAkey privateKeys/oemRootCA.key -passin file:passphrase.txt -set_serial 12345 -days $validity_oem_subca1_cert -out certs/oemSubCA1Cert.pem
# 7) Create a second intermediate OEM sub-CA certificate which is directly signed by the OEMSubCA1 certificate (validity is up to the OEM, this example applies the same validity as the CPOSubCA2)
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys/oemSubCA2.key
openssl req -new -key privateKeys/oemSubCA2.key -passin file:passphrase.txt -config configs/oemSubCA2Cert.cnf -out csrs/oemSubCA2.csr
openssl x509 -req -in csrs/oemSubCA2.csr -extfile configs/oemSubCA2Cert.cnf -extensions ext -CA certs/oemSubCA1Cert.pem -CAkey privateKeys/oemSubCA1.key -passin file:passphrase.txt -set_serial 12345 -days $validity_oem_subca2_cert -out certs/oemSubCA2Cert.pem
# 8) Create an OEM provisioning certificate which is the leaf certificate belonging to the OEM certificate chain (used for contract certificate installation)
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys/oemProv.key
openssl req -new -key privateKeys/oemProv.key -passin file:passphrase.txt -config configs/oemProvCert.cnf -out csrs/oemProvCert.csr
openssl x509 -req -in csrs/oemProvCert.csr -extfile configs/oemProvCert.cnf -extensions ext -CA certs/oemSubCA2Cert.pem -CAkey privateKeys/oemSubCA2.key -passin file:passphrase.txt -set_serial 12345 -days $validity_oem_prov_cert -out certs/oemProvCert.pem
# 9) Create a self-signed MORootCA (mobility operator) certificate (validity is up to the MO, this example applies the same validity as the V2GRootCA)
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys/moRootCA.key
openssl req -new -key privateKeys/moRootCA.key -passin file:passphrase.txt -config configs/moRootCACert.cnf -out csrs/moRootCA.csr
openssl x509 -req -in csrs/moRootCA.csr -extfile configs/moRootCACert.cnf -extensions ext -signkey privateKeys/moRootCA.key -passin file:passphrase.txt -sha256 -set_serial 12345 -out certs/moRootCACert.pem -days $validity_mo_root_cert
# 10) Create an intermediate MO sub-CA certificate which is directly signed by the MORootCA certificate (validity is up to the MO, this example applies the same validity as the CPOSubCA1)
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys/moSubCA1.key
openssl req -new -key privateKeys/moSubCA1.key -passin file:passphrase.txt -config configs/moSubCA1Cert.cnf -extensions ext -out csrs/moSubCA1.csr
openssl x509 -req -in csrs/moSubCA1.csr -extfile configs/moSubCA1Cert.cnf -extensions ext -CA certs/moRootCACert.pem -CAkey privateKeys/moRootCA.key -passin file:passphrase.txt -set_serial 12345 -days $validity_mo_subca1_cert -out certs/moSubCA1Cert.pem
# 11) Create a second intermediate MO sub-CA certificate which is directly signed by the MOSubCA1 certificate (validity is up to the MO, this example applies the same validity as the CPOSubCA2)
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys/moSubCA2.key
openssl req -new -key privateKeys/moSubCA2.key -passin file:passphrase.txt -config configs/moSubCA2Cert.cnf -out csrs/moSubCA2.csr
openssl x509 -req -in csrs/moSubCA2.csr -extfile configs/moSubCA2Cert.cnf -extensions ext -CA certs/moSubCA1Cert.pem -CAkey privateKeys/moSubCA1.key -passin file:passphrase.txt -set_serial 12345 -days $validity_mo_subca2_cert -out certs/moSubCA2Cert.pem
# 12) Create a contract certificate which is the leaf certificate belonging to the MO certificate chain (used for contract certificate installation)
# Validity can be between 4 weeks and 2 years (restricted by the contract lifetime), for testing purposes the validity will be set to 2 years
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys/contract.key
openssl req -new -key privateKeys/contract.key -passin file:passphrase.txt -config configs/contractCert.cnf -out csrs/contractCert.csr
openssl x509 -req -in csrs/contractCert.csr -extfile configs/contractCert.cnf -extensions ext -CA certs/moSubCA2Cert.pem -CAkey privateKeys/moSubCA2.key -passin file:passphrase.txt -set_serial 12345 -days $validity_contract_cert -out certs/contractCert.pem
cat certs/moSubCA2Cert.pem certs/moSubCA1Cert.pem > certs/intermediateMOCACerts.pem
openssl pkcs12 -export -inkey privateKeys/contract.key -in certs/contractCert.pem -certfile certs/intermediateMOCACerts.pem -aes128 -passin file:passphrase.txt -passout file:passphrase.txt -name contract_cert -out certs/moCertChain.p12
# 13) Create an intermediate provisioning service sub-CA certificate which is directly signed by the V2GRootCA certificate
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys/cpsSubCA1.key
openssl req -new -key privateKeys/cpsSubCA1.key -passin file:passphrase.txt -config configs/cpsSubCA1Cert.cnf -out csrs/cpsSubCA1.csr
openssl x509 -req -in csrs/cpsSubCA1.csr -extfile configs/cpsSubCA1Cert.cnf -extensions ext -CA certs/v2gRootCACert.pem -CAkey privateKeys/v2gRootCA.key -passin file:passphrase.txt -set_serial 12345 -days $validity_cps_subca1_cert -out certs/cpsSubCA1Cert.pem
# 14) Create a second intermediate provisioning sub-CA certificate which is directly signed by the CPSSubCA1 certificate (validity 1 - 2 years, we make it 2 years)
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys/cpsSubCA2.key
openssl req -new -key privateKeys/cpsSubCA2.key -passin file:passphrase.txt -config configs/cpsSubCA2Cert.cnf -out csrs/cpsSubCA2.csr
openssl x509 -req -in csrs/cpsSubCA2.csr -extfile configs/cpsSubCA2Cert.cnf -extensions ext -CA certs/cpsSubCA1Cert.pem -CAkey privateKeys/cpsSubCA1.key -passin file:passphrase.txt -set_serial 12345 -days $validity_cps_subca2_cert -out certs/cpsSubCA2Cert.pem
# 15) Create a provisioning service certificate which is the leaf certificate belonging to the provisioning certificate chain (used for contract certificate installation)
# Validity can be between 2 - 3 months, we make it 3 months
openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -passout file:passphrase.txt -out privateKeys/cpsLeaf.key
openssl req -new -key privateKeys/cpsLeaf.key -passin file:passphrase.txt -config configs/cpsLeafCert.cnf -out csrs/cpsLeafCert.csr
openssl x509 -req -in csrs/cpsLeafCert.csr -extfile configs/cpsLeafCert.cnf -extensions ext -CA certs/cpsSubCA2Cert.pem -CAkey privateKeys/cpsSubCA2.key -passin file:passphrase.txt -set_serial 12345 -days $validity_cps_leaf_cert -out certs/cpsLeafCert.pem
cat certs/cpsSubCA2Cert.pem certs/cpsSubCA1Cert.pem > certs/intermediateCPSCACerts.pem
openssl pkcs12 -export -inkey privateKeys/cpsLeaf.key -in certs/cpsLeafCert.pem -certfile certs/intermediateCPSCACerts.pem -aes128 -passin file:passphrase.txt -passout file:passphrase.txt -name cps_leaf_cert -out certs/cpsCertChain.p12
# 16) Finally we need to convert the certificates from PEM format to DER format (PEM is the default format, but ISO 15118 only allows DER format)
openssl x509 -inform PEM -in certs/v2gRootCACert.pem -outform DER -out certs/v2gRootCACert.der
openssl x509 -inform PEM -in certs/cpsSubCA1Cert.pem -outform DER -out certs/cpsSubCA1Cert.der
openssl x509 -inform PEM -in certs/cpsSubCA2Cert.pem -outform DER -out certs/cpsSubCA2Cert.der
openssl x509 -inform PEM -in certs/cpsLeafCert.pem -outform DER -out certs/cpsLeafCert.der
openssl x509 -inform PEM -in certs/cpoSubCA1Cert.pem -outform DER -out certs/cpoSubCA1Cert.der
openssl x509 -inform PEM -in certs/cpoSubCA2Cert.pem -outform DER -out certs/cpoSubCA2Cert.der
openssl x509 -inform PEM -in certs/seccCert.pem -outform DER -out certs/seccCert.der
openssl x509 -inform PEM -in certs/oemRootCACert.pem -outform DER -out certs/oemRootCACert.der
openssl x509 -inform PEM -in certs/oemSubCA1Cert.pem -outform DER -out certs/oemSubCA1Cert.der
openssl x509 -inform PEM -in certs/oemSubCA2Cert.pem -outform DER -out certs/oemSubCA2Cert.der
openssl x509 -inform PEM -in certs/oemProvCert.pem -outform DER -out certs/oemProvCert.der
openssl x509 -inform PEM -in certs/moRootCACert.pem -outform DER -out certs/moRootCACert.der
openssl x509 -inform PEM -in certs/moSubCA1Cert.pem -outform DER -out certs/moSubCA1Cert.der
openssl x509 -inform PEM -in certs/moSubCA2Cert.pem -outform DER -out certs/moSubCA2Cert.der
openssl x509 -inform PEM -in certs/contractCert.pem -outform DER -out certs/contractCert.der
# Since the intermediate certificates need to be in PEM format when putting them in a PKCS12 container and the resulting PKCS12 file is a binary format, it might be sufficient. Otherwise, I have currently no idea how to covert the intermediate certificates in DER without running into problems when creating the PKCS12 container.
# 17) In case you want the private keys in PKCS#8 file format and DER encoded, use this command. Especially necessary for the private key of MOSubCA2 in RISE V2G
openssl pkcs8 -topk8 -in privateKeys/moSubCA2.key -inform PEM -passin file:passphrase.txt -passout file:passphrase.txt -outform DER -out privateKeys/moSubCA2.pkcs8.der -v1 PBE-SHA1-3DES
# 18) Create the keystores for the EVCC and SECC. We need to first create the PKCS12 files and then import them into the JKS using the 'keytool' command.
# - create a PKCS12 file -> -export
# - if no private keys are added, e.g. for a truststore that holds only root CA certificates -> -nokeys
# - add a private key to the newly created PKCS12 container; must be in PEM format (not DER) -> -inkey
# - add any certificate (leaf certificate or root certificate); must be in PEM format (not DER) -> -in
# - add additional certificats (intermediate sub-CA certificates or more root CA certificates); must be in PEM format (not DER) -> -certfile
# - use AES to encrypt private keys before outputting -> -aes128
# - passphrase source to decrypt any private keys that are to be imported -> -passin
# - passphrase to encrypt any outputted private keys with -> -passout
# - provide the "friendly name" for the leaf certificate, which is used as the alias in Java -> -name
# - provide the "friendly name" for CA certificates, which is used as the alias in Java;
# this option may be used multiple times to specify names for all certificates in the order they appear -> -caname
# - the file location to store the PKCS12 data container -> -out
#
# 18.1) SECC keystore needs to hold initially hold the OEM provisioning certificate (contract certificate will be installed with ISO 15118 message exchange)
# First, concatenate the intermediate sub-CA certificates into one file intermediateCAs.pem
# IMPORTANT: Concatenate in such a way that the chain leads from the leaf certificate to the root (excluding), this means here: first parameter of the cat command is the sub-CA certificate which signs the leaf certificate (in this case cpoSubCA2.pem). Otherwise the Java method getCertificateChain() which is called on the keystore will only return the leaf certificate!
cat certs/cpoSubCA2Cert.pem certs/cpoSubCA1Cert.pem > certs/intermediateCPOCACerts.pem
# Put the seccCertificate, the private key associated with the seccCertificate as well as the intermediate sub-CA certificates in a PKCS12 container with the -certfile switch.
openssl pkcs12 -export -inkey privateKeys/secc.key -in certs/seccCert.pem -name secc_cert -certfile certs/intermediateCPOCACerts.pem -caname mo_subca_2 -caname mo_subca_1 -aes128 -passin file:passphrase.txt -passout file:passphrase.txt -out keystores/cpoCertChain.p12
keytool -importkeystore -srckeystore keystores/cpoCertChain.p12 -srcstoretype pkcs12 -srcstorepass:file passphrase.txt -srcalias secc_cert -destalias secc_cert -destkeystore keystores/seccKeystore.jks -storepass:file passphrase.txt -noprompt
#
# 18.2) EVCC keystore needs to initally hold the OEM provisioning certificate (contract certificate will be installed with ISO 15118 message exchange)
cat certs/oemSubCA2Cert.pem certs/oemSubCA1Cert.pem > certs/intermediateOEMCACerts.pem
openssl pkcs12 -export -inkey privateKeys/oemProv.key -in certs/oemProvCert.pem -name oem_prov_cert -certfile certs/intermediateOEMCACerts.pem -caname oem_subca_2 -caname oem_subca_1 -aes128 -passin file:passphrase.txt -passout file:passphrase.txt -out keystores/oemCertChain.p12
keytool -importkeystore -srckeystore keystores/oemCertChain.p12 -srcstoretype pkcs12 -srcstorepass:file passphrase.txt -srcalias oem_prov_cert -destalias oem_prov_cert -destkeystore keystores/evccKeystore.jks -storepass:file passphrase.txt -noprompt
# 19) Create truststores for EVCC and SECC. Storing trust anchors in PKCS12 is not supported in Java 8. For creating trusstores we need a Java KeyStore (JKS) and import the DER encoded root CA certificates.
#
# 19.1) EVCC truststore needs to hold the V2GRootCA certificate
keytool -import -keystore keystores/evccTruststore.jks -alias v2g_root_ca -file certs/v2gRootCACert.der -storepass:file passphrase.txt -noprompt
#
# 19.2) SECC truststore needs to hold the V2G root CA certificate and MO root CA certificate.
# According to ISO 15118-2, MO root CA is not necessarily needed as the MO sub-CA 1 could instead be signed by a V2G root CA.
keytool -import -keystore keystores/seccTruststore.jks -alias v2g_root_ca -file certs/v2gRootCACert.der -storepass:file passphrase.txt -noprompt
keytool -import -keystore keystores/seccTruststore.jks -alias mo_root_ca -file certs/moRootCACert.der -storepass:file passphrase.txt -noprompt
# Side notes for OCSP stapling in Java: see http://openjdk.java.net/jeps/249

View File

@@ -0,0 +1,2 @@
123456
123456

View File

@@ -0,0 +1,190 @@
# Test Data To Verify Your CertificateInstallationRes Signature
This file provides test data needed to reproduce the same digest and signature values for a CertificateInstallationRes. This test data is provided for your convenience to verify that your implementation of creating and verifying digital XML-based signatures is correct.
Further explanation is given in the ISO 15118 Manual in section 3.12.3 "Test Data To Verify Your CertificateInstallationRes".
For further explanation why the different usage of XML namespace matters with regards to the resulting digest and signature values, have a look at section 3.12.4 "Pitfalls with Signatures And XML Namespaces" in the [ISO 15118 Manual](http://v2g-clarity.com/iso15118-manual/).
## TEST DATA SETUP
### Private key for signature creation
The private key used to create the signature for the CertificateInstallationRes is the one belonging to the Sub-CA 2 of the certificate provisioning service (CPS). See file cpsSubCA2.key.
169FE1CBCE6CED08391F3235FD530DB84430F165E474117C0AC587D6554F581C
### Public key for signature verification
The public key used to verify the signature of the CertificateInstallationRes is part of the Sub-CA 2 certificate of the certificate provisioning service (CPS). The 64 bytes represent a point on the elliptic curve, thus the raw x-coordinate and y-coordinate. The public key has an additional byte 0x04 in the beginning to represent the uncompressed form as demanded by the ISO 15118-2, resulting in 65 bytes in total.
0491CAA2FE68797277C9FEADEC61DC7AE7DC334DA59DD82D82290327AF105771F7307B9573870A8DA09CA4CE2F293B23D9F8AEED3AE49A9C177C6F710C55089CE4
### ContractSignatureCertChain
The certificate chain provided by the Mobility Operator (MO) comprises the contract certificate and the intermediate sub-CA certificates.
All MO certificates are packaged in the PKCS#12 container file moCertChain.p12. But you can also access every single certificate by its own, if you want: the contractCert, moSubCA2, and moSubCA1 (each provided in .pem and .der format). Be aware that the order in which the certificates are placed in the SubCertificates element is important. The first element is the Sub-CA 2 certificate, followed by the Sub-CA 1 certificate.
Also, the certificates' validity period might already have expired by the time you use this test data. But that is not a problem for this issue.
### ContractSignatureEncryptedPrivateKey
This field holds the encrypted private key that belongs to the contract certificate as well as a so-called initialization vector (IV) of 16 bytes length that is needed for the AES cipher. The IV is represented by the first (also known as most significant) 16 bytes of this field.
Use this hexadecimal representation of a ContractSignatureEncryptedPrivateKey to get the same results.
803340C03FEFBC0CF47613E50D74C660EF471D2104C2EB1C1EAE39BAE700EAB0DC9AE909CE234FF2619DD3A721C60AA0
### DHpublickey
The DHpublickey is the public key of a generated elliptic curve Diffie-Hellman key pair. The key pair is used to create the session key with which the private key that belongs to the contract certificate is encrypted. Again, with an additional byte 0x04 prepended as demanded by ISO 15118-2 to represent the uncompressed form of a public key.
Use this hexadecimal representation of a DHpublickey to get the same results.
04BE426A534EBCD5444476C0809425F9A593875AA7A4C2A2167C8A295B2B9069E054AD61801552FAB1F7C710D9506890120354C763800891DA595A1619E06254E9
### eMAID
Let's use the following eMAID for this test case: DEABCC123ABC56
### SAProvisioningCertificateChain
The signature is built over the four fields mentioned above. The Certificate Provisioning Service's (CPS) certificate chain is not part of the signature. However, the CPS's Sub-CA 2 certificate holds the public key (printed further above in hexadecimal notation for your convenience) with which you need to verify the signature. If you also want to validate the CPS's chain of certificates all the way up to the V2G root certificate, then use the PKCS#12 container file cpsCertChain.p12 and the v2gRootCA.pem or v2gRootCA.der file. All certificates in cpsCertChain.p12 are also provided as single certificates in this folder.
## XML REFERENCE ELEMENT GENERATION WITH CORRECT XML NAMESPACE "urn:iso:15118:2:2013:MsgBody"
The following values are created using the XML namespace "urn:iso:15118:2:2013:MsgBody".
### ContractSignatureCertChain
EXI:
8021015A590C4D703308201D33082017AA00302010202023039300906072A8648CE3D0401304F3111300F06035504030C084D4F53756243413231193017060355040A0C1052495345205632472050726F6A656374310B300906035504061302444531123010060A0992268993F22C64011916024D4F301E170D3138303831353132333433355A170D3230303831343132333433355A30573119301706035504030C1044452D4142432D43313233414243353631193017060355040A0C1052495345205632472050726F6A656374310B300906035504061302444531123010060A0992268993F22C64011916024D4F3059301306072A8648CE3D020106082A8648CE3D0301070342000473FEBA248E8E0D246F4ECC79D46E57184BF86ACBC01644F7AE453D8C721B8AF9442723892948A6E48A6E81229363DFBAEF37F4631FC3592E868BEF2E5884185EA33F303D300C0603551D130101FF04023000300E0603551D0F0101FF0404030203E8301D0603551D0E0416041446EC52B4D9BFC688275F5B0A872DD47D6960448A300906072A8648CE3D04010348003045022100E97C0514FAE75296B5951D06EAC507922167C725780951869AA5E3DF43E618F5022059F81CAFEDDA35B5C73DF80EA1522C53951EF2361232E35442B16D6B77909D8A06A81984100E8984100BC50018100810101181C98048303954324671E82009827988898078301AA8201860426A7A9BAB121A098988C980B8301AA820506082924A9A2902B192390283937B532B1BA188598048301AA82030981222298891808030504C91344C9F91632008C8B0126A7980F0B86989C181C189A9899199A199AAD0B869919181C189A1899199A199AAD1827988898078301AA8201860426A7A9BAB121A099188C980B8301AA820506082924A9A2902B192390283937B532B1BA188598048301AA82030981222298891808030504C91344C9F91632008C8B0126A7982C98098303954324671E81008304154324671E81808381A100024A1FD0FC8C3F56A5E5CFC49A0FDF110400AAD7E8C8B33AC646EF0B93A4C74CA034FEDAB0900313607E24849385E45905DDB22B5DD9E88A9C1076CEFC266A848F51A2982198090301AA8E898080FF820418030080FF81008018070301AA8E878080FF8202018100E3180E8301AA8E87020B020A256D0F08428AADE1FB88C3BF4C1E7E8EE606C1D118048303954324671E820081A40018228110151FDB8466FA92DB3E5AE0AC81B4BC3AAB7C18FF1CA54DD7CDB4E18C0CAAE8BD8110806ABF339E3F7C85EAD2FAA175F61DDBE6E015876FD8BFE225B04169563377128D86B01984100E9184100BC50018100810101181C98048303954324671E82009827988898078301AA8201860426A7A937B7BA21A0988C980B8301AA820506082924A9A2902B192390283937B532B1BA188598048301AA82030981222298891808030504C91344C9F91632008C8B0126A7980F0B86989C181C189A9899199A199AAD0B869919181C189A1899199A199AAD1827988898078301AA8201860426A7A9BAB121A098988C980B8301AA820506082924A9A2902B192390283937B532B1BA188598048301AA82030981222298891808030504C91344C9F91632008C8B0126A7982C98098303954324671E81008304154324671E81808381A1000262BE27D8CCBAA40F456878995C3E34CDE5EF3C4B435FC84C4A57B6F84212FCCFC195290B3B265255BC4CF067A3294E73516D54B255838C9C35F67F5F77B64EE6D1A2982198090301AA8E898080FF820418030080FF81008098070301AA8E878080FF820201810083180E8301AA8E87020B020A6876F21C15AB68AE79919AF9807C85B49B1DD2C718048303954324671E820081A48018230110806656378B705F6308BDA5749D1CC6AAB30FC93390A01E608C8C99DE16D0DF09430110807B4806A1E2B9B762C65B7C8783321E83B185CD00FA57EEAC334A27965CF6B9D817A00
SHA-256:
8EFA2B8D3BDED68CF6F4DF02D80BADCC4036AEA8F2D4E4124CA69A64DBC192B0
Base64 encoded SHA-256:
jvorjTve1oz29N8C2AutzEA2rqjy1OQSTKaaZNvBkrA=
### ContractSignatureEncryptedPrivateKey
EXI:
802202B4B2190C200CD0300FFBEF033D1D84F9435D31983BD1C7484130BAC707AB8E6EB9C03AAC3726BA427388D3FC986774E9C87182A81E80
SHA-256:
5F0EEDE44720B6AE9F7A4EC27E49C8F28C5F0A31FDAE012FE4E3C8FCEEC231F2
Base64 encoded SHA-256:
Xw7t5Ecgtq6fek7CfknI8oxfCjH9rgEv5OPI/O7CMfI=
### DHpublickey
EXI:
802D02B4B21990412F909A94D3AF3551111DB02025097E6964E1D6A9E930A8859F228A56CAE41A78152B58600554BEAC7DF1C436541A240480D531D8E0022476965685867818953A5E80
SHA-256:
02003E52F83579B57A72D4C9514B1480956D71B5C669A63CD633B309B040CE78
Base64 encoded SHA-256:
AgA+Uvg1ebV6ctTJUUsUgJVtcbXGaaY81jOzCbBAzng=
### eMAID
EXI:
80EC0202B4B21A40041111505090D0CC4C8CD05090CD4DBD3D00
SHA-256:
939AF54F14396EC0D827F753C896AC0762AE1E00ACAB57E19AF100243CDD5A6B
Base64 encoded SHA-256:
k5r1TxQ5bsDYJ/dTyJasB2KuHgCsq1fhmvEAJDzdWms=
## SIGNATURE GENERATION WITH CORRECT XML NAMESPACE "urn:iso:15118:2:2013:MsgBody"
EXI:
808112B43A3A381D1797BBBBBB973B999737B93397AA2917B1B0B737B734B1B0B616B2BC3497A1AB43A3A381D1797BBBBBB973B999737B933979918181897981A17BC36B63239B4B396B6B7B93291B2B1B239B096B9B430991A9B220623696432025687474703A2F2F7777772E77332E6F72672F54522F63616E6F6E6963616C2D6578692F4852D0E8E8E0745E5EEEEEEE5CEE665CDEE4CE5E646060625E60685EF0DAD8CADCC646E6D0C2646A6C840BE1DDBC88E416D5D3EF49D84FC9391E518BE1463FB5C025FC9C791F9DD8463E408188DA590C4095A1D1D1C0E8BCBDDDDDDCB9DCCCB9BDC99CBD5148BD8D85B9BDB9A58D85B0B595E1A4BD214B43A3A381D1797BBBBBB973B999737B933979918181897981A17BC36B632B73191B9B430991A9B210477D15C69DEF6B467B7A6F816C05D6E6201B5754796A720926534D326DE0C958020623696434025687474703A2F2F7777772E77332E6F72672F54522F63616E6F6E6963616C2D6578692F4852D0E8E8E0745E5EEEEEEE5CEE665CDEE4CE5E646060625E60685EF0DAD8CADCC646E6D0C2646A6C8412735EA9E2872DD81B04FEEA7912D580EC55C3C015956AFC335E2004879BAB4D608188DA590CC095A1D1D1C0E8BCBDDDDDDCB9DCCCB9BDC99CBD5148BD8D85B9BDB9A58D85B0B595E1A4BD214B43A3A381D1797BBBBBB973B999737B933979918181897981A17BC36B632B73191B9B430991A9B21001001F297C1ABCDABD396A64A8A58A404AB6B8DAE334D31E6B19D984D820673C0DC
The signature value will never be the same because ECDSA always uses a random seed to generate the signature. Therefore, it does not make sense to provide a signature value here for comparison.
HINT: Do not make the mistake to hash the EXI binary stream before you run ECDSA on it. ECDSA already performs a SHA-256 hashing operation!
## XML REFERENCE ELEMENT GENERATION WITH INCORRECT XML NAMESPACE ""
There was a discussion going on whether the XML elements for a CertificateInstallationRes/CertificateUpdateRes need to be created using the namespace "urn:iso:15118:2:2013:MsgBody" or if using no namespace (the same as using the empty namespace "") is also a possible solution. The [ISO 15118 User Group issue #72](http://extmgmt.kn.e-technik.tu-dortmund.de/issues/172) further elaborates on that and makes clear that the namespace "urn:iso:15118:2:2013:MsgBody" shall be used. Using the empty namespace would NOT conform to the standard's requirements.
However, just to show the difference in the EXI encoding result as well as the difference in message size, the following values are created using the empty XML namespace "". As you can see, those EXI encoding results are bigger in size. This is due to a so-called schema deviation encoding for those message elements.
### ContractSignatureCertChain
EXI:
80F311B436F6E74726163745369676E617475726543657274436861696E4C028006070052056964312E00109805000C7EC089A92928460F4868682B0E2CE82EE928482CE92869A88D6EE86A2B29096DEB492F4D4608A82A884A09AA48AEE88EEB288ACA2A2888882D09CA8629C62B2D69C849AD48AB49A84C68E8262AA8A86CEEEA2AAD6D8A8A4A684AE9AD6C6CEAA9094ECC2DAACD4C8888A989A82D68E8262AA8A84D09A86A48AAAF08AD482A284CEDE94D6D2C294D65E92E6B4828AB48CCE949CA8F482CA8CEE60F09E8882689AA8AAF09AD49A609AF4ACC28CEE60F29A8882689AA8A2F09AD49A609AF4ACC29A8CC6F08EA882B084CE9CAC84829A9A8A8AA48C98AA8C86A2F262889AA892F4A2AA94889CA8B2F08EA882B084CE9CAC8482DE9A8A8C9494AA60AACEACD49490928C84F2C464E0D8B266A2F086F4829484CE9CAC8482B2A882D6A48C9AA492EE8A82B29686B492DAD2B4A0F2988EA2848EA4B286A8AA70EEAEA882A884CEC6E2D0D6D49EA0A2928484CECEE2D0D6D49EA0A29A8484EE9C868282A4F45EE4DED6D4DE689C948E729EF490DCAAC4D8C6B2A65ED0E2F27082AEA4A0CAEAA4A8649AC6D0EA9656AAA2DC9268D6E0A696C4D6D2DA6C8492E09CD4666EE4EC9C5EA4D490709CB498DEC2986EF26AB2D084D0CADEF470EEA0A8829A84CE9CAC90A49A8482CC708A82D482829A82688E8262AAC888EE8A845EEEA28A82EE92886C8882C884CE9CAC90A2688A8CCEA2AAA4EAF0A6E89CDA5EF0DECEDCB062E696D0F266AACCAED8CEA492DEEE86A2B29096DEB492F4D4608A82A29C928288848C82D28A826CB0EE8C8CA0E4DCAAE0C262D8A4608E6CE6AA90D6D28CDCF0F2AC6886AC8E8EDAE2B0D46660A0DA8EA0AA86928CDC6890965EE864D4AE62F0F4666888E28CA6988C9EAC90EC92648AD498D4AC8A96F0C4AEE866D69464968E01169805000C2E00114C0280063F6044D494942305443434158696741774942416749434D446B77435159484B6F5A497A6A3045415442504D52457744775944565151444441684E54314E31596B4E424D54455A4D4263474131554543677751556B6C54525342574D6B636755484A76616D566A6444454C4D416B474131554542684D4352455578456A415142676F4A6B69614A6B2F49735A41455A46674A4E547A4165467730784F4441344D5455784D6A4D304D7A5661467730794D6A41344D5451784D6A4D304D7A56614D4538784554415042674E5642414D4D4345315055335669513045794D526B77467759445651514B4442425353564E464946597952794251636D39715A574E304D517377435159445651514745774A45525445534D42414743676D534A6F6D543869786B41526B57416B31504D466B77457759484B6F5A497A6A3043415159494B6F5A497A6A304441516344516741456C442B682B52682B7255764C6E346B304837346943414656723947525A6E574D6A6434584A306D4F6D5542702F6256684941596D7750784A4353634C794C494C753252577537505246546767375A3334544E554A48714E464D454D7745675944565230544151482F42416777426745422F7749424144414F42674E56485138424166384542414D4341635977485159445652304F4242594546457261486843464656764439784748667067382F52334D44594F694D416B4742797147534D34394241454453414177525149674B6A2B33434D33314A625A387463465A41326C34645662344D663435537075766D326E4447426C563058734349514456666D633866766B4C31615831517576734F37664E7743734F3337462F7845746767744B735A75346C47773D3D470008A60140031FB0226A4A4A1183521A1A0AC34B3A0BBA4A120B3A4A1A6A235BBA1A8ACA425B7AD24BD351822A0AA212826A922BBA23BACA22B28A8A22220B4272A18A53B3119A92228AA22AD26A131A3A098AAA2A1B3BBA8AAB5B62A2929A12BA6B5B1B3AAA4253B30B6AB35322222A626A0B5A3A098AAA2A13426A1A922AABC22B520A8A133B7A535B4B0A53597A4B9AD20A2AD2333A5272A3D20B2A33B983C27A2209A26AA2ABC26B5269826BD2B30A33B983CA6B5209A26AA28BC26B5269826BD2B30A6A29C3C22AA20A82133A72B2120A6A6A1A298A82A99AB34A89822BC26A935BBA33BACA22B28A8A5A2212129A9AB272324A32CBCA93CA128B1B69CB8AD2BA71826A8B9BBA1A8ACA22B28A8A3A2BBA522A92A22A9A6A120A3A1B3B6A9A537B6AA1C34BC35A0A935ABA0B598A826A335BBA2BBACA425B7AD24BD351821A0A8ACA4A5B7AD24BD35182220A8B1A228B3A0A2BC2C3C2839AD3618A9A11B25982822BCBAA43C38369C3B32B2A530A3BB1AA1ACB6259CBA1C24A8B615AD15A225B624ABB235BCB5B899B4AD1A269CA3AAB83D36B7BA38B82D25B9A423AA34391BA81B159B993CB23D30A72326A2A6BBA2B3ACA22B29182A20A8A417A120B3BBA133A2A117BBA4A120AA20A7A133A72B24289C2120B31C22A120A6A1A0A8ACBBA428ACA22B291827A1212CA2A327223A1AA233B92B3A23319C3CA6989C3BA21AA1993599279B2BA7A6A0B5A3A13CB8A3A9A69A1CA120A2A229A8A0BBA933A4B420A6BCB9B13C3133BB39ACA93298393827B536A72B2BACB335B6B1B428A23D2123A935BD3B2199343B3425A3A0B4A2A09CB820A7289C2B3D3139ABA6BA3B35A82136A89CA11926A636B3A418391C98ACAD3829282626373A319BA09EABE8
SHA-256:
8A624F06F7D29216B15462F188D4217A811E2D8D18EEB25CE2252004EFA0BC62
Base64 encoded SHA-256:
imJPBvfSkhaxVGLxiNQheoEeLY0Y7rJc4iUgBO+gvGI=
### ContractSignatureEncryptedPrivateKey
EXI:
80F3125436F6E74726163745369676E6174757265456E63727970746564507269766174654B65794C02800607005205696432684CE889C82EE885EECEC82F460C8D0A0D888B0A88EB29E729090A68A8AEEEAE6C690E2686AEAEAC6826CE488C6DAEAD694F4D29CA070DA8EC8606CC6D0F0CEE2CEFA00
SHA-256:
59B9F0CBF4E7D528A3E233EC55A3ED41C0971E032727704829E64ED56F2CC829
Base64 encoded SHA-256:
Wbnwy/Tn1Sij4jPsVaPtQcCXHgMnJ3BIKeZO1W8syCk=
### DHpublickey
EXI:
80F310C44487075626C69636B65794C028006070052056964336B484986A86C2D89C9EEC9CAC8AA490C482CE94A2D856C2AEA8D062E2DCE09A96D28CDCF29696ACE6E4D68EDCCEAC9662D0CE84ACA656E49066F0F088B4AA8ED2A28ACE9CAAF0649E82869490C2AEACDEAE8ECA84D2AC9ED67AFA00
SHA-256:
086D79FE8E5990F26ACC99ED0095B18156D4F1C221D1F72D3C7206B7D5514F81
Base64 encoded SHA-256:
CG15/o5ZkPJqzJntAJWxgVbU8cIh0fctPHIGt9VRT4E=
### eMAID
EXI:
80F3106654D4149444C02800607005205696434620888A828486866264668284866A6CFA00
SHA-256:
45CD81DFFD1A56BB5F4A796B804CA71721AF434587E0ED803A09EAEBADBB8479
Base64 encoded SHA-256:
Rc2B3/0aVrtfSnlrgEynFyGvQ0WH4O2AOgnq6627hHk=
## SIGNATURE GENERATION WITH INCORRECT XML NAMESPACE ""
EXI:
808112B43A3A381D1797BBBBBB973B999737B93397AA2917B1B0B737B734B1B0B616B2BC3497A1AB43A3A381D1797BBBBBB973B999737B933979918181897981A17BC36B63239B4B396B6B7B93291B2B1B239B096B9B430991A9B220623696432025687474703A2F2F7777772E77332E6F72672F54522F63616E6F6E6963616C2D6578692F4852D0E8E8E0745E5EEEEEEE5CEE665CDEE4CE5E646060625E60685EF0DAD8CADCC646E6D0C2646A6C840B373E197E9CFAA5147C467D8AB47DA83812E3C064E4EE09053CC9DAADE59905208188DA590C4095A1D1D1C0E8BCBDDDDDDCB9DCCCB9BDC99CBD5148BD8D85B9BDB9A58D85B0B595E1A4BD214B43A3A381D1797BBBBBB973B999737B933979918181897981A17BC36B632B73191B9B430991A9B210453127837BE9490B58AA3178C46A10BD408F16C68C77592E7112900277D05E31020623696434025687474703A2F2F7777772E77332E6F72672F54522F63616E6F6E6963616C2D6578692F4852D0E8E8E0745E5EEEEEEE5CEE665CDEE4CE5E646060625E60685EF0DAD8CADCC646E6D0C2646A6C8408B9B03BFFA34AD76BE94F2D700994E2E435E868B0FC1DB007413D5D75B7708F208188DA590CC095A1D1D1C0E8BCBDDDDDDCB9DCCCB9BDC99CBD5148BD8D85B9BDB9A58D85B0B595E1A4BD214B43A3A381D1797BBBBBB973B999737B933979918181897981A17BC36B632B73191B9B430991A9B2100436BCFF472CC87935664CF6804AD8C0AB6A78E110E8FB969E39035BEAA8A7C08DC
The signature value will never be the same because ECDSA always uses a random seed to generate the signature. Therefore, it does not make sense to provide a signature value here for comparison.
HINT: Do not make the mistake to hash the EXI binary stream before you run ECDSA on it. ECDSA already performs a SHA-256 hashing operation!

View File

@@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIB0zCCAXqgAwIBAgICMDkwCQYHKoZIzj0EATBPMREwDwYDVQQDDAhNT1N1YkNB
MjEZMBcGA1UECgwQUklTRSBWMkcgUHJvamVjdDELMAkGA1UEBhMCREUxEjAQBgoJ
kiaJk/IsZAEZFgJNTzAeFw0xODA4MTUxMjM0MzVaFw0yMDA4MTQxMjM0MzVaMFcx
GTAXBgNVBAMMEERFLUFCQy1DMTIzQUJDNTYxGTAXBgNVBAoMEFJJU0UgVjJHIFBy
b2plY3QxCzAJBgNVBAYTAkRFMRIwEAYKCZImiZPyLGQBGRYCTU8wWTATBgcqhkjO
PQIBBggqhkjOPQMBBwNCAARz/rokjo4NJG9OzHnUblcYS/hqy8AWRPeuRT2MchuK
+UQnI4kpSKbkim6BIpNj37rvN/RjH8NZLoaL7y5YhBheoz8wPTAMBgNVHRMBAf8E
AjAAMA4GA1UdDwEB/wQEAwID6DAdBgNVHQ4EFgQURuxStNm/xognX1sKhy3UfWlg
RIowCQYHKoZIzj0EAQNIADBFAiEA6XwFFPrnUpa1lR0G6sUHkiFnxyV4CVGGmqXj
30PmGPUCIFn4HK/t2jW1xz34DqFSLFOVHvI2EjLjVEKxbWt3kJ2K
-----END CERTIFICATE-----

View File

@@ -0,0 +1,8 @@
-----BEGIN EC PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,380BED020885167EDF788D043AA87CE0
vZ4x8xI8BuaQoWn84sC0EZ1nMJkc9vcomYZujt/EFkx9Pet0maaBSUC4gAp+Vmuv
6CTIiCIg4Nqfr0bIDG0Fc1sR7qbzwjdbxdTyb30gxAYZXkrg9k1YBHYBkJDFNgiK
01YxM+KvvOklzasS3E6N8O18aCLr073nQ3/GeCoyBgM=
-----END EC PRIVATE KEY-----

View File

@@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIBzjCCAXagAwIBAgICMDkwCQYHKoZIzj0EATBSMRMwEQYDVQQDDApQcm92U3Vi
Q0EyMRkwFwYDVQQKDBBSSVNFIFYyRyBQcm9qZWN0MQswCQYDVQQGEwJERTETMBEG
CgmSJomT8ixkARkWA0NQUzAeFw0xODA4MTUxMjM0MzVaFw0xODExMTMxMjM0MzVa
MFAxETAPBgNVBAMMCENQUyBMZWFmMRkwFwYDVQQKDBBSSVNFIFYyRyBQcm9qZWN0
MQswCQYDVQQGEwJERTETMBEGCgmSJomT8ixkARkWA0NQUzBZMBMGByqGSM49AgEG
CCqGSM49AwEHA0IABJHKov5oeXJ3yf6t7GHceufcM02lndgtgikDJ68QV3H3MHuV
c4cKjaCcpM4vKTsj2fiu7TrkmpwXfG9xDFUInOSjPzA9MAwGA1UdEwEB/wQCMAAw
DgYDVR0PAQH/BAQDAgeAMB0GA1UdDgQWBBQoXojyIBpvdgAXOaYQeTUMq5ag9TAJ
BgcqhkjOPQQBA0cAMEQCIAS8mjlsl/1Tpl5AkRj2fk6NwQjWT+WrCYn8hewTHvpA
AiBRj+hg0I04i3sKspaBv6NADWNIbdyYlvBVSApUQwy1Iw==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIB1zCCAX2gAwIBAgICMDkwCQYHKoZIzj0EATBRMRIwEAYDVQQDDAlWMkdSb290
Q0ExGTAXBgNVBAoMEFJJU0UgVjJHIFByb2plY3QxCzAJBgNVBAYTAkRFMRMwEQYK
CZImiZPyLGQBGRYDVjJHMB4XDTE4MDgxNTEyMzQzNVoXDTIyMDgxNDEyMzQzNVow
UjETMBEGA1UEAwwKUHJvdlN1YkNBMTEZMBcGA1UECgwQUklTRSBWMkcgUHJvamVj
dDELMAkGA1UEBhMCREUxEzARBgoJkiaJk/IsZAEZFgNDUFMwWTATBgcqhkjOPQIB
BggqhkjOPQMBBwNCAAS6olYOW2Eqj8UbE8qK4kw66+v77/wHfa8fuxiziYz9/C8T
auZVSzmLKZVhSxgot/aEM9arcR6z6pm6IUhooRHCo0UwQzASBgNVHRMBAf8ECDAG
AQH/AgEBMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUiPeAh9VKGtrLvJx+te9q
Eq8re2wwCQYHKoZIzj0EAQNJADBGAiEAkLiSEhwZhW2J1HkWyWCsDfI8FSWZzkgQ
WdRpPf6wQfICIQDkB4W/cXLTh2muLSvANTv5gAZBpbFnLCDLtWMIcEwO7w==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIB1zCCAX6gAwIBAgICMDkwCQYHKoZIzj0EATBSMRMwEQYDVQQDDApQcm92U3Vi
Q0ExMRkwFwYDVQQKDBBSSVNFIFYyRyBQcm9qZWN0MQswCQYDVQQGEwJERTETMBEG
CgmSJomT8ixkARkWA0NQUzAeFw0xODA4MTUxMjM0MzVaFw0yMDA4MTQxMjM0MzVa
MFIxEzARBgNVBAMMClByb3ZTdWJDQTIxGTAXBgNVBAoMEFJJU0UgVjJHIFByb2pl
Y3QxCzAJBgNVBAYTAkRFMRMwEQYKCZImiZPyLGQBGRYDQ1BTMFkwEwYHKoZIzj0C
AQYIKoZIzj0DAQcDQgAEb7ni77kvihibEYfe38rkdViqbQZ2DdaWTuSgb8f5MYBt
0X7Yd2EmMNggBA/y6b/qdy7i/CZtO4XoxhWdxR27VqNFMEMwEgYDVR0TAQH/BAgw
BgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMzOBrGZABRuhmOnhZeM
j7IRE5bXMAkGByqGSM49BAEDSAAwRQIhAP2QVgZFV2At/5En2inD6xPbDR8fGYTi
LSs+aVKerEb6AiAqhCcm3aCBHqkuc/AIP5QRFLVcw8k7nKyNVsvEmE//FA==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIB0jCCAXigAwIBAgICMDkwCQYHKoZIzj0EATBPMREwDwYDVQQDDAhNT1Jvb3RD
QTEZMBcGA1UECgwQUklTRSBWMkcgUHJvamVjdDELMAkGA1UEBhMCREUxEjAQBgoJ
kiaJk/IsZAEZFgJNTzAeFw0xODA4MTUxMjM0MzVaFw0yMjA4MTQxMjM0MzVaME8x
ETAPBgNVBAMMCE1PU3ViQ0ExMRkwFwYDVQQKDBBSSVNFIFYyRyBQcm9qZWN0MQsw
CQYDVQQGEwJERTESMBAGCgmSJomT8ixkARkWAk1PMFkwEwYHKoZIzj0CAQYIKoZI
zj0DAQcDQgAExXxPsZl1SB6K0PEyuHxpm8veeJaGv5CYlK9t8IQl+Z+DKlIWdkyk
q3iZ4M9GUpzmotqpZKsHGThr7P6+72ydzaNFMEMwEgYDVR0TAQH/BAgwBgEB/wIB
ATAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFNDt5DgrVtFc8yM18wD5C2k2O6WO
MAkGByqGSM49BAEDSQAwRgIhAMysbxbgvsYRe0rpOjmNVWYfkmchQDzBGRkzvC2h
vhKGAiEA9pANQ8VzbsWMtvkPBmQ9B2MLmgH0r91YZpRPLLntc7A=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIB0TCCAXigAwIBAgICMDkwCQYHKoZIzj0EATBPMREwDwYDVQQDDAhNT1N1YkNB
MTEZMBcGA1UECgwQUklTRSBWMkcgUHJvamVjdDELMAkGA1UEBhMCREUxEjAQBgoJ
kiaJk/IsZAEZFgJNTzAeFw0xODA4MTUxMjM0MzVaFw0yMjA4MTQxMjM0MzVaME8x
ETAPBgNVBAMMCE1PU3ViQ0EyMRkwFwYDVQQKDBBSSVNFIFYyRyBQcm9qZWN0MQsw
CQYDVQQGEwJERTESMBAGCgmSJomT8ixkARkWAk1PMFkwEwYHKoZIzj0CAQYIKoZI
zj0DAQcDQgAElD+h+Rh+rUvLn4k0H74iCAFVr9GRZnWMjd4XJ0mOmUBp/bVhIAYm
wPxJCScLyLILu2RWu7PRFTgg7Z34TNUJHqNFMEMwEgYDVR0TAQH/BAgwBgEB/wIB
ADAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0OBBYEFEraHhCFFVvD9xGHfpg8/R3MDYOi
MAkGByqGSM49BAEDSAAwRQIgKj+3CM31JbZ8tcFZA2l4dVb4Mf45Spuvm2nDGBlV
0XsCIQDVfmc8fvkL1aX1QuvsO7fNwCsO37F/xEtggtKsZu4lGw==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIB0TCCAXigAwIBAgICMDkwCQYHKoZIzj0EATBRMRIwEAYDVQQDDAlPRU1TdWJD
QTIxGTAXBgNVBAoMEFJJU0UgVjJHIFByb2plY3QxCzAJBgNVBAYTAkRFMRMwEQYK
CZImiZPyLGQBGRYDT0VNMB4XDTE4MDgxNTEyMzQzNVoXDTIyMDgxNDEyMzQzNVow
UzEUMBIGA1UEAwwLT0VNUHJvdkNlcnQxGTAXBgNVBAoMEFJJU0UgVjJHIFByb2pl
Y3QxCzAJBgNVBAYTAkRFMRMwEQYKCZImiZPyLGQBGRYDT0VNMFkwEwYHKoZIzj0C
AQYIKoZIzj0DAQcDQgAED4hImL7PFPU7Nxsgt/+SyNSkbhBxOJMseZNyc6KNTixb
l2mA08AixhhPu0f+2mTWyT8qBHKc18ekD4AjCnR1uKM/MD0wDAYDVR0TAQH/BAIw
ADAOBgNVHQ8BAf8EBAMCA4gwHQYDVR0OBBYEFKOPm6bvahIJjUgSnCXGpaiG2NHO
MAkGByqGSM49BAEDSAAwRQIhAPDzK9lM60lDuac8M03gZFip6l8k5ozEYe3qy5r1
UQQxAiAmFmlrWIrxAiUvkEL88p13WKPE/mII6G4tF35m+TzUHw==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIB1TCCAXqgAwIBAgICMDkwCgYIKoZIzj0EAwIwUTESMBAGA1UEAwwJVjJHUm9v
dENBMRkwFwYDVQQKDBBSSVNFIFYyRyBQcm9qZWN0MQswCQYDVQQGEwJERTETMBEG
CgmSJomT8ixkARkWA1YyRzAeFw0xODA4MTUxMjM0MzVaFw0yODA4MTIxMjM0MzVa
MFExEjAQBgNVBAMMCVYyR1Jvb3RDQTEZMBcGA1UECgwQUklTRSBWMkcgUHJvamVj
dDELMAkGA1UEBhMCREUxEzARBgoJkiaJk/IsZAEZFgNWMkcwWTATBgcqhkjOPQIB
BggqhkjOPQMBBwNCAARm86LOcZnbwSKBrBH1rAaOXTnyMZFYyard0rSMgMHpN/kD
jluhBnvvAgpk4wmVBx0YBqfVRaIPretAGCmqO5Jso0IwQDAPBgNVHRMBAf8EBTAD
AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUrToVah3wryPZxFzBuIyFRzal
adcwCgYIKoZIzj0EAwIDSQAwRgIhAKU6GMgF0Njd5nkcBLVAIx/3EzTkKunt/7xH
qd00Gn6PAiEAkr/8pOHqZ82Zq3h2dTrJNGlhuIS2jBl+7P4wDzSNXyg=
-----END CERTIFICATE-----

View File

@@ -0,0 +1 @@
/target

View File

@@ -0,0 +1,144 @@
###############################################################################
# The MIT License (MIT)
#
# Copyright (c) 2015 - 2019 Dr. Marc M<>ltin (V2G Clarity)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
###############################################################################
# ===========================================================
# Configuration properties for a unique electric vehicle (EV)
# ===========================================================
# Network interface
#------------------
#
# The network interface name like en3 or eth1 of the network interface on which to communicate with the SECC via a
# link-local IPv6 address
network.interface = en0
# Security
#---------
#
# Possible values:
# - true
# - false
# If this value is set to 'false', TCP will be used on transport layer
# If no correct value is provided here, 'false' will be chosen
tls = false
# Contract certificate update time span
#-------------------------------------
#
# Integer value defining the time span in days which precedes the expiration of a contract certificate
# and during which an update of the contract certificate needs to be performed
contract.certificate.update.timespan = 14
# SessionID
#----------
#
# Hexadecimal string representing a byte array. If this value is unequal to "00", then it represents a
# previously paused V2G communication session
session.id = 00
# Selected payment option
#------------------------
#
# This (single!) value needs to be provided from a previous charging session if charging has been paused.
# Possible values are:
# - Contract
# - ExternalPayment
authentication.mode =
# Requested energy transfer mode
#--------------------------------
#
# This (single!) value needs to be provided from a previous charging session if charging has been paused.
# Possible values are:
# - AC_single_phase_core
# - AC_three_phase_core
# - DC_core
# - DC_extended
# - DC_combo_core
# - DC_unique
energy.transfermode.requested = AC_three_phase_core
#
# Implementation classes
#---------------------------------------------
# If you want to replace the implementation then set the following value
# to the name of your class
# When omitted default dummy implementation will be used
implementation.evcc.controller = com.v2gclarity.risev2g.evcc.evController.DummyEVController
# XML representation of messages
#-------------------------------
#
# Possible values:
# - true
# - false
# If this value is set to 'true', the EXICodec will print each message's XML representation (for debugging purposes)
# If no correct value is provided here, 'false' will be chosen
exi.messages.showxml = true
# Hexadecimal and Base64 representation of messages
#--------------------------------------------------
#
# Possible values:
# - true
# - false
# If this value is set to 'true', the EXICodec will print each message's hexadecimal and Base64 representation (for debugging purposes)
# If no correct value is provided here, 'false' will be chosen
exi.messages.showhex = true
# Extended logging of signature verification
#-------------------------------------------
#
# Possible values:
# - true
# - false
# If this value is set to 'true', extended logging will be printed upon verification of signatures (for debugging purposes)
# If no correct value is provided here, 'false' will be chosen
signature.verification.showlog = true
# EXI codec
#--------------------------------
#
# This (single!) value tells the program which EXI codec to use to en-/decode EXI messages
# Possible values are:
# - exificient
# - open_exi
# If no correct value is provided here, 'exificient' will be used
exi.codec = exificient
# Voltage accuracy
#----------
#
# Used for the PreCharge target voltage. The present voltage indicated by the charging station in PreChargeRes can deviate from the present voltage
# set in PreChargeReq by an EV-specific deviation factor. This value is given in percent.
# Example: voltage.accuracy = 10 means: present voltage may deviate from target voltage by 10 percent in order to successfully stop PreCharge
voltage.accuracy = 5

View File

@@ -0,0 +1,23 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015-2017 V2G Clarity (Dr. Marc Mültin)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/

View File

@@ -0,0 +1,95 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>com.v2gclarity.risev2g</groupId>
<artifactId>rise-v2g-parent</artifactId>
<version>1.2.6</version>
<relativePath>../RISE-V2G-PARENT</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>rise-v2g-evcc</artifactId>
<name>rise-v2g-evcc</name>
<dependencies>
<dependency>
<groupId>com.v2gclarity.risev2g</groupId>
<artifactId>rise-v2g-shared</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.version>1.2.5</project.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId> <!-- See http://maven.apache.org/plugins/maven-assembly-plugin/usage.html -->
<version>3.1.0</version>
<configuration>
<finalName>rise-v2g-evcc-${project.version}</finalName>
<appendAssemblyId>false</appendAssemblyId>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>com.v2gclarity.risev2g.evcc.main.StartEVCC</mainClass>
</manifest>
</archive>
<descriptors> <!-- Same as jar-with-dependencies descriptorRef, but can be adapted if needed -->
<descriptor>src/assembly/bin.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>one-jar-only</id>
<phase>package</phase> <!-- bind to the packaging phase -->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin> <!-- For copying the keystores and private key to the target folder next to the JAR file -->
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>install</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/target</outputDirectory>
<resources>
<resource>
<directory>${basedir}</directory>
<includes>
<include>*.p12</include>
<include>*.jks</include>
<include>*.properties</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.v2gclarity.risev2g</groupId>
<artifactId>rise-v2g-shared</artifactId>
<version>1.2.4</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>

View File

@@ -0,0 +1,18 @@
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
<!-- TODO: a jarjar format would be better -->
<id>jar-with-dependencies</id>
<formats>
<format>jar</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<outputDirectory>/</outputDirectory>
<useProjectArtifact>true</useProjectArtifact>
<unpack>true</unpack>
<scope>runtime</scope>
</dependencySet>
</dependencySets>
</assembly>

View File

@@ -0,0 +1,362 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.evController;
import java.util.List;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.CPStates;
import com.v2gclarity.risev2g.shared.utils.MiscUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ACEVChargeParameterType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingProfileType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVChargeParameterType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVErrorCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVPowerDeliveryParameterType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVStatusType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EnergyTransferModeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PMaxScheduleEntryType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PMaxScheduleType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionListType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PhysicalValueType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ProfileEntryType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.RelativeTimeIntervalType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SAScheduleListType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SAScheduleTupleType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.UnitSymbolType;
public class DummyEVController implements IACEVController, IDCEVController {
private Logger logger = LogManager.getLogger(this.getClass().getSimpleName());
private V2GCommunicationSessionEVCC commSessionContext;
private CPStates cpState;
private int chargingLoopCounter;
public DummyEVController() {
setCPState(CPStates.STATE_B); // should be signaled before ISO/IEC 15118 stack initializes
setChargingLoopCounter((short) 0);
}
@Override
public PaymentOptionType getPaymentOption() {
/*
* The payment options offered by the SECC should probably be displayed on a HMI in the EV.
* A request to the EVController should then be initiated here in order to let the user
* choose which offered payment option to use.
*
* TODO check [V2G2-828] (selecting payment option related to state B, C)
*/
// Set default to Plug & Charge
return PaymentOptionType.CONTRACT;
}
@Override
public EnergyTransferModeType getRequestedEnergyTransferMode() {
// Set default to AC_THREE_PHASE_CORE. Should normally depend on type of cable plugged into the vehicle inlet
EnergyTransferModeType requestedEnergyTransferMode = (EnergyTransferModeType) MiscUtils.getPropertyValue("energy.transfermode.requested");
if (requestedEnergyTransferMode == null)
return EnergyTransferModeType.AC_THREE_PHASE_CORE;
else
return requestedEnergyTransferMode;
}
@Override
public JAXBElement<ACEVChargeParameterType> getACEVChargeParamter() {
ACEVChargeParameterType acEVChargeParameter = new ACEVChargeParameterType();
acEVChargeParameter.setDepartureTime((long) 7200); // offset in seconds from sending request
PhysicalValueType eAmount = new PhysicalValueType();
eAmount.setMultiplier(new Byte("0"));
eAmount.setUnit(UnitSymbolType.WH);
eAmount.setValue((short) 5000);
acEVChargeParameter.setEAmount(eAmount);
PhysicalValueType evMaxVoltage = new PhysicalValueType();
evMaxVoltage.setMultiplier(new Byte("0"));
evMaxVoltage.setUnit(UnitSymbolType.V);
evMaxVoltage.setValue((short) 400);
acEVChargeParameter.setEVMaxVoltage(evMaxVoltage);
PhysicalValueType evMaxCurrent = new PhysicalValueType();
evMaxCurrent.setMultiplier(new Byte("0"));
evMaxCurrent.setUnit(UnitSymbolType.A);
evMaxCurrent.setValue((short) 32);
acEVChargeParameter.setEVMaxCurrent(evMaxCurrent);
PhysicalValueType evMinCurrent = new PhysicalValueType();
evMinCurrent.setMultiplier(new Byte("0"));
evMinCurrent.setUnit(UnitSymbolType.A);
evMinCurrent.setValue((short) 5);
acEVChargeParameter.setEVMinCurrent(evMinCurrent);
return new JAXBElement<ACEVChargeParameterType>(new QName("urn:iso:15118:2:2013:MsgDataTypes", "AC_EVChargeParameter"),
ACEVChargeParameterType.class,
acEVChargeParameter);
}
@Override
public JAXBElement<DCEVChargeParameterType> getDCEVChargeParamter() {
PhysicalValueType evMaxCurrent = new PhysicalValueType();
evMaxCurrent.setMultiplier(new Byte("0"));
evMaxCurrent.setUnit(UnitSymbolType.A);
evMaxCurrent.setValue((short) 200);
PhysicalValueType evMaxVoltage = new PhysicalValueType();
evMaxVoltage.setMultiplier(new Byte("0"));
evMaxVoltage.setUnit(UnitSymbolType.V);
evMaxVoltage.setValue((short) 400);
PhysicalValueType eAmount = new PhysicalValueType();
eAmount.setMultiplier(new Byte("0"));
eAmount.setUnit(UnitSymbolType.WH);
eAmount.setValue((short) 5000);
DCEVChargeParameterType dcEVChargeParameter = new DCEVChargeParameterType();
dcEVChargeParameter.setDCEVStatus(getDCEVStatus());
dcEVChargeParameter.setEVMaximumCurrentLimit(evMaxCurrent);
dcEVChargeParameter.setEVMaximumVoltageLimit(evMaxVoltage);
dcEVChargeParameter.setEVEnergyRequest(eAmount);
return new JAXBElement<DCEVChargeParameterType>(new QName("urn:iso:15118:2:2013:MsgDataTypes", "DC_EVChargeParameter"),
DCEVChargeParameterType.class,
dcEVChargeParameter);
}
@Override
public ChargingProfileType getChargingProfile() {
ChargingProfileType chargingProfile = new ChargingProfileType();
SAScheduleListType saScheduleList = (SAScheduleListType) getCommSessionContext().getSaSchedules();
// Simply use the first scheduleTuple
SAScheduleTupleType saScheduleTuple = saScheduleList.getSAScheduleTuple().get(0);
// Just follow the PMaxSchedule
PMaxScheduleType pMaxSchedule = (PMaxScheduleType) saScheduleTuple.getPMaxSchedule();
List<PMaxScheduleEntryType> pMaxScheduleEntries = pMaxSchedule.getPMaxScheduleEntry();
// Just copy the provided PMaxSchedule
for (PMaxScheduleEntryType pMaxScheduleEntry : pMaxScheduleEntries) {
ProfileEntryType chargingProfileEntry = new ProfileEntryType();
PhysicalValueType maxPower = new PhysicalValueType();
maxPower.setMultiplier(pMaxScheduleEntry.getPMax().getMultiplier());
maxPower.setUnit(UnitSymbolType.W);
maxPower.setValue(pMaxScheduleEntry.getPMax().getValue());
chargingProfileEntry.setChargingProfileEntryMaxPower(maxPower);
chargingProfileEntry.setChargingProfileEntryMaxNumberOfPhasesInUse(new Byte("3"));
chargingProfileEntry.setChargingProfileEntryStart(
((RelativeTimeIntervalType) pMaxScheduleEntry.getTimeInterval().getValue()).getStart()
);
chargingProfile.getProfileEntry().add(chargingProfileEntry);
}
return chargingProfile;
}
@Override
public short getChosenSAScheduleTupleID() {
return getCommSessionContext().getSaSchedules().getSAScheduleTuple().get(0).getSAScheduleTupleID();
}
public V2GCommunicationSessionEVCC getCommSessionContext() {
return commSessionContext;
}
@Override
public void setCommSessionContext(V2GCommunicationSessionEVCC commSessionContext) {
this.commSessionContext = commSessionContext;
}
@Override
public boolean setCPState(CPStates state) {
getLogger().debug("Changing to state " + state.toString());
this.cpState = state;
return true;
}
@Override
public CPStates getCPState() {
return cpState;
}
@Override
public DCEVStatusType getDCEVStatus() {
DCEVStatusType dcEvStatus = new DCEVStatusType();
dcEvStatus.setEVErrorCode(DCEVErrorCodeType.NO_ERROR);
dcEvStatus.setEVReady(true);
dcEvStatus.setEVRESSSOC(new Byte("50"));
return dcEvStatus;
}
@Override
public PhysicalValueType getTargetVoltage() {
PhysicalValueType targetVoltage = new PhysicalValueType();
targetVoltage.setMultiplier(new Byte("0"));
targetVoltage.setUnit(UnitSymbolType.V);
targetVoltage.setValue((short) 400);
return targetVoltage;
}
@Override
public PhysicalValueType getTargetCurrent() {
PhysicalValueType targetCurrent = new PhysicalValueType();
targetCurrent.setMultiplier(new Byte("0"));
targetCurrent.setUnit(UnitSymbolType.A);
targetCurrent.setValue((short) 2); // according to IEC 61851-23, this value should be limited to 2A as it seems (see https://github.com/V2GClarity/RISE-V2G/issues/20)
return targetCurrent;
}
@Override
public boolean isBulkChargingComplete() {
return false;
}
@Override
public boolean isChargingComplete() {
return false;
}
@Override
public PhysicalValueType getMaximumVoltageLimit() {
PhysicalValueType maxVoltageLimit = new PhysicalValueType();
maxVoltageLimit.setMultiplier(new Byte("0"));
maxVoltageLimit.setUnit(UnitSymbolType.V);
maxVoltageLimit.setValue((short) 400);
return maxVoltageLimit;
}
@Override
public PhysicalValueType getMaximumCurrentLimit() {
PhysicalValueType maxCurrentLimit = new PhysicalValueType();
maxCurrentLimit.setMultiplier(new Byte("0"));
maxCurrentLimit.setUnit(UnitSymbolType.A);
maxCurrentLimit.setValue((short) 32);
return maxCurrentLimit;
}
@Override
public PhysicalValueType getMaximumPowerLimit() {
PhysicalValueType maxPowerLimit = new PhysicalValueType();
maxPowerLimit.setMultiplier(new Byte("3"));
maxPowerLimit.setUnit(UnitSymbolType.W);
maxPowerLimit.setValue((short) 63);
return maxPowerLimit;
}
@Override
public PhysicalValueType getRemainingTimeToFullSOC() {
PhysicalValueType remainingTimeToFullSOC = new PhysicalValueType();
remainingTimeToFullSOC.setMultiplier(new Byte("0"));
remainingTimeToFullSOC.setUnit(UnitSymbolType.S);
remainingTimeToFullSOC.setValue((short) 1800);
return remainingTimeToFullSOC;
}
@Override
public PhysicalValueType getRemainingTimeToBulkSOC() {
PhysicalValueType remainingTimeToBulkSOC = new PhysicalValueType();
remainingTimeToBulkSOC.setMultiplier(new Byte("0"));
remainingTimeToBulkSOC.setUnit(UnitSymbolType.S);
remainingTimeToBulkSOC.setValue((short) 900);
return remainingTimeToBulkSOC;
}
public Logger getLogger() {
return logger;
}
@Override
public void adjustMaxCurrent(PhysicalValueType evseMaxCurrent) {
short multiplier = (short) (evseMaxCurrent.getMultiplier() & 0xFF);
getLogger().info("Adjusting max current to " + evseMaxCurrent.getValue() * Math.pow(10, multiplier) + " A");
}
@Override
public boolean isChargingLoopActive() {
// Keep charging until 100 charging loops are finished
if (getChargingLoopCounter() < 100) {
setChargingLoopCounter(getChargingLoopCounter() + 1);
/*
* OPTIONAL:
* Trigger a renegotiation after 50 charging loops (for testing purposes); you can comment this out if you do not want to test an EV-triggered renegotiation
*/
if (getChargingLoopCounter() == 50) {
getCommSessionContext().setRenegotiationRequested(true);
getLogger().debug("EV triggered a renegotiation (for testing purposes)");
}
return true;
} else
/*
* OPTIONAL:
* If you want to trigger a pause of the charging session, then uncomment this line
*/
//getCommSessionContext().setChargingSession(ChargingSessionType.PAUSE);
return false;
}
public int getChargingLoopCounter() {
return chargingLoopCounter;
}
public void setChargingLoopCounter(int chargingLoopCounter) {
this.chargingLoopCounter = chargingLoopCounter;
}
@Override
public DCEVPowerDeliveryParameterType getEVPowerDeliveryParameter() {
DCEVPowerDeliveryParameterType dcEvPowerDeliveryParameter = new DCEVPowerDeliveryParameterType();
dcEvPowerDeliveryParameter.setBulkChargingComplete(false);
dcEvPowerDeliveryParameter.setChargingComplete(false);
dcEvPowerDeliveryParameter.setDCEVStatus(getDCEVStatus());
return dcEvPowerDeliveryParameter;
}
}

View File

@@ -0,0 +1,45 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.evController;
import javax.xml.bind.JAXBElement;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ACEVChargeParameterType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PhysicalValueType;
public interface IACEVController extends IEVController {
/**
* Returns the charge parameter for AC charging
* @return The battery specific charge parameter for the current charging session
*/
public JAXBElement<ACEVChargeParameterType> getACEVChargeParamter();
/**
* Indicates the maximum line current per phase the EV can draw (as allowed by the SECC)
* @param evseMaxCurrent
*/
public void adjustMaxCurrent(PhysicalValueType evseMaxCurrent);
}

View File

@@ -0,0 +1,118 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.evController;
import javax.xml.bind.JAXBElement;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVChargeParameterType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVPowerDeliveryParameterType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVStatusType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PhysicalValueType;
public interface IDCEVController extends IEVController {
/**
* Returns the charge parameter for AC charging
* @return The battery specific charge parameter for the current charging session
*/
public JAXBElement<DCEVChargeParameterType> getDCEVChargeParamter();
/**
* Returns the EV status parameter for DC charging
* @return The EV status for DC charging for the current charging session
*/
public DCEVStatusType getDCEVStatus();
/**
* Returns the target voltage for DC charging
* @return Target voltage given as a PhysicalValueType
*/
public PhysicalValueType getTargetVoltage();
/**
* Returns the target current for DC charging
* @return Target current given as a PhysicalValueType
*/
public PhysicalValueType getTargetCurrent();
/**
* If set to TRUE, the EV indicates that bulk charging (approx. 80% SOC) is complete.
* @return True, if bulk charge is complete, false otherwise
*/
public boolean isBulkChargingComplete();
/**
* If set to TRUE, the EV indicates that charging process is complete.
* @return True, if charging process is complete, false otherwise
*/
public boolean isChargingComplete();
/**
* Returns the maximum voltage limit for DC charging
* @return The maximum voltage given as a PhysicalValueType
*/
public PhysicalValueType getMaximumVoltageLimit();
/**
* Returns the maximum current limit for DC charging
* @return The maximum current given as a PhysicalValueType
*/
public PhysicalValueType getMaximumCurrentLimit();
/**
* Returns the maximum power limit for DC charging
* @return The maximum power given as a PhysicalValueType
*/
public PhysicalValueType getMaximumPowerLimit();
/**
* Returns the estimated or calculated time until full charge (100% SOC) is complete
* @return The estimated time given as a PhysicalValueType
*/
public PhysicalValueType getRemainingTimeToFullSOC();
/**
* Returns the estimated or calculated time until bulk charge (approx. 80% SOC) is complete
* @return The estimated time given as a PhysicalValueType
*/
public PhysicalValueType getRemainingTimeToBulkSOC();
/**
* Returns the DC_EVPowerDeliverParameter
* @return The DC_EVPowerDeliverParameter
*/
public DCEVPowerDeliveryParameterType getEVPowerDeliveryParameter();
}

View File

@@ -0,0 +1,95 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.evController;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.CPStates;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingProfileType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EnergyTransferModeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionListType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
public interface IEVController {
/**
* Provides a reference to the current communication session for
* this controller instance.
* @param commSessionContext The active communication session
*/
public void setCommSessionContext(V2GCommunicationSessionEVCC commSessionContext);
/**
* Returns the user-chosen payment method, either external identification means (EIM) such as an
* RFID card or via Plug-and-Charge (PnC)
* @return The payment option Contract or ExternalPayment
*/
public PaymentOptionType getPaymentOption();
/**
* Returns the EnergyTransferMode chosen by the driver
* @return The chosen EnergyTransferMode
*/
public EnergyTransferModeType getRequestedEnergyTransferMode();
/**
* Returns the specific charging profile for the current charging session
* (i.e. maximum amount of power drawn over time)
* @return The charging profile with a list of profile entries
*/
public ChargingProfileType getChargingProfile();
/**
* Returns the unique identifier within a charging session for a SAScheduleTuple element
* contained in the list of SASchedules delivered by the EVSE. An SAScheduleTupleID remains a
* unique identifier for one schedule throughout a charging session.
* @return The unique ID given as a short value
*/
public short getChosenSAScheduleTupleID();
/**
* Signals a CP state according to IEC 61851-1 (State A, B, C or D)
* @param state
* @return True, if the state signaling was successful, false otherwise
*/
public boolean setCPState(CPStates state);
/**
* Returns the current CP state according IEC 61851-1 (State A, B, C or D)
* @return The respective CP state
*/
public CPStates getCPState();
/**
* Provides information on whether the charging loop should be active to charge the EV's battery, or not
*
* @return True, if charging process should be continued, false otherwise
*/
public boolean isChargingLoopActive();
}

View File

@@ -0,0 +1,37 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.main;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionHandlerEVCC;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.utils.MiscUtils;
public class StartEVCC {
public static void main(String[] args) {
MiscUtils.loadProperties(GlobalValues.EVCC_CONFIG_PROPERTIES_PATH.toString());
new V2GCommunicationSessionHandlerEVCC();
}
}

View File

@@ -0,0 +1,50 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.misc;
import com.v2gclarity.risev2g.evcc.evController.DummyEVController;
import com.v2gclarity.risev2g.evcc.evController.IEVController;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.misc.V2GImplementationFactory;
/**
* Implementation factory for the EVCC controller
*
*/
public class EVCCImplementationFactory extends V2GImplementationFactory {
/**
* Creates the controller for the EVCC application
* @param commSessionContext the session the backend will be connected to
* @return
*/
public static IEVController createEVController(V2GCommunicationSessionEVCC commSessionContext) {
IEVController instance = buildFromProperties("implementation.evcc.controller", IEVController.class);
if (instance == null) {
instance = new DummyEVController();
}
instance.setCommSessionContext(commSessionContext);
return instance;
}
}

View File

@@ -0,0 +1,522 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.session;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import com.v2gclarity.risev2g.evcc.evController.IEVController;
import com.v2gclarity.risev2g.evcc.misc.EVCCImplementationFactory;
import com.v2gclarity.risev2g.evcc.states.WaitForAuthorizationRes;
import com.v2gclarity.risev2g.evcc.states.WaitForCableCheckRes;
import com.v2gclarity.risev2g.evcc.states.WaitForCertificateInstallationRes;
import com.v2gclarity.risev2g.evcc.states.WaitForCertificateUpdateRes;
import com.v2gclarity.risev2g.evcc.states.WaitForChargeParameterDiscoveryRes;
import com.v2gclarity.risev2g.evcc.states.WaitForChargingStatusRes;
import com.v2gclarity.risev2g.evcc.states.WaitForCurrentDemandRes;
import com.v2gclarity.risev2g.evcc.states.WaitForMeteringReceiptRes;
import com.v2gclarity.risev2g.evcc.states.WaitForPaymentDetailsRes;
import com.v2gclarity.risev2g.evcc.states.WaitForPaymentServiceSelectionRes;
import com.v2gclarity.risev2g.evcc.states.WaitForPowerDeliveryRes;
import com.v2gclarity.risev2g.evcc.states.WaitForPreChargeRes;
import com.v2gclarity.risev2g.evcc.states.WaitForServiceDetailRes;
import com.v2gclarity.risev2g.evcc.states.WaitForServiceDiscoveryRes;
import com.v2gclarity.risev2g.evcc.states.WaitForSessionSetupRes;
import com.v2gclarity.risev2g.evcc.states.WaitForSessionStopRes;
import com.v2gclarity.risev2g.evcc.states.WaitForSupportedAppProtocolRes;
import com.v2gclarity.risev2g.evcc.states.WaitForWeldingDetectionRes;
import com.v2gclarity.risev2g.evcc.transportLayer.StatefulTransportLayerClient;
import com.v2gclarity.risev2g.evcc.transportLayer.TCPClient;
import com.v2gclarity.risev2g.evcc.transportLayer.TLSClient;
import com.v2gclarity.risev2g.shared.enumerations.CPStates;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ChangeProcessingState;
import com.v2gclarity.risev2g.shared.messageHandling.PauseSession;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.SendMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.misc.V2GCommunicationSession;
import com.v2gclarity.risev2g.shared.misc.V2GTPMessage;
import com.v2gclarity.risev2g.shared.utils.ByteUtils;
import com.v2gclarity.risev2g.shared.utils.MiscUtils;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils.ContractCertificateStatus;
import com.v2gclarity.risev2g.shared.v2gMessages.appProtocol.AppProtocolType;
import com.v2gclarity.risev2g.shared.v2gMessages.appProtocol.SupportedAppProtocolRes;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscoveryReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingProfileType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EnergyTransferModeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SAScheduleListType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SelectedServiceListType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SelectedServiceType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceListType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
// The state machine
public class V2GCommunicationSessionEVCC extends V2GCommunicationSession implements Observer {
private long v2gEVCCCommunicationSetupTimer;
private String evseID;
private PaymentOptionType selectedPaymentOption;
private ReactionToIncomingMessage reactionToIncomingMessage;
/*
* chargeParameterDiscoveryReq is saved in the session because it might need to be resent in case
* the EVSEProcessing parameter of the respective response message is set to "Ongoing"
* (saves some processing time)
*/
private ChargeParameterDiscoveryReqType chargeParameterDiscoveryReq;
private ChargingSessionType chargingSession;
private boolean renegotiationRequested;
private ChargingProfileType chargingProfile;
private ServiceListType offeredServices;
private SelectedServiceListType selectedServices;
private ArrayList<Integer> serviceDetailsToBeRequested;
private EnergyTransferModeType requestedEnergyTransferMode;
private long evseScheduleReceived; // The timestamp of receiving the SAScheduleList from the EVSE, is used as a reference
private List<AppProtocolType> supportedAppProtocols;
private AppProtocolType chosenAppProtocol;
private boolean oldSessionJoined;
private IEVController evController;
private long evseTimeStamp;
private SAScheduleListType saSchedules;
private long saSchedulesReceived;
private CPStates changeToState; // signals a needed state change (checked when sending the request message)
private StatefulTransportLayerClient transportLayerClient;
private ContractCertificateStatus contractCertStatus;
private long ongoingTimer; // used for V2G_EVCC_Ongoing_Timer, V2G_EVCC_PreCharge_Timer, V2G_EVCC_CableCheck_Timer
private boolean isOngoingTimerActive;
private byte[] sentGenChallenge;
public V2GCommunicationSessionEVCC(StatefulTransportLayerClient transportLayerClient) {
setTransportLayerClient(transportLayerClient);
getStates().put(V2GMessages.SUPPORTED_APP_PROTOCOL_RES, new WaitForSupportedAppProtocolRes(this));
getStates().put(V2GMessages.SESSION_SETUP_RES, new WaitForSessionSetupRes(this));
getStates().put(V2GMessages.SERVICE_DISCOVERY_RES, new WaitForServiceDiscoveryRes(this));
getStates().put(V2GMessages.SERVICE_DETAIL_RES, new WaitForServiceDetailRes(this));
getStates().put(V2GMessages.PAYMENT_SERVICE_SELECTION_RES, new WaitForPaymentServiceSelectionRes(this));
getStates().put(V2GMessages.CERTIFICATE_INSTALLATION_RES, new WaitForCertificateInstallationRes(this));
getStates().put(V2GMessages.CERTIFICATE_UPDATE_RES, new WaitForCertificateUpdateRes(this));
getStates().put(V2GMessages.PAYMENT_DETAILS_RES, new WaitForPaymentDetailsRes(this));
getStates().put(V2GMessages.AUTHORIZATION_RES, new WaitForAuthorizationRes(this));
getStates().put(V2GMessages.CHARGE_PARAMETER_DISCOVERY_RES, new WaitForChargeParameterDiscoveryRes(this));
getStates().put(V2GMessages.CABLE_CHECK_RES, new WaitForCableCheckRes(this));
getStates().put(V2GMessages.PRE_CHARGE_RES, new WaitForPreChargeRes(this));
getStates().put(V2GMessages.POWER_DELIVERY_RES, new WaitForPowerDeliveryRes(this));
getStates().put(V2GMessages.CHARGING_STATUS_RES, new WaitForChargingStatusRes(this));
getStates().put(V2GMessages.CURRENT_DEMAND_RES, new WaitForCurrentDemandRes(this));
getStates().put(V2GMessages.METERING_RECEIPT_RES, new WaitForMeteringReceiptRes(this));
getStates().put(V2GMessages.WELDING_DETECTION_RES, new WaitForWeldingDetectionRes(this));
getStates().put(V2GMessages.SESSION_STOP_RES, new WaitForSessionStopRes(this));
setStartState(getStates().get(V2GMessages.SUPPORTED_APP_PROTOCOL_RES));
setCurrentState(getStartState());
// configure which EV controller implementation to use
// TODO the EV controller needs to run as a separate Thread (to receive notifications from the EV and to avoid blocking calls to the controller)
setEvController(EVCCImplementationFactory.createEVController(this));
/*
* Is needed for measuring the time span between transition to state B (plug-in) and receipt
* of a SessionSetupRes (see V2G_EVCC_COMMUNICATION_SETUP_TIMEOUT in TimeRestrictions.java)
* TODO check if this timing requirement is still up to date
*/
setV2gEVCCCommunicationSetupTimer(System.nanoTime());
// Set default value for contract certificate status to UNKNOWN
setContractCertStatus(ContractCertificateStatus.UNKNOWN);
// ChargingSessionType only takes enum values "Pause" and "Terminate". Therefore, set it to null at beginning of charging session
setChargingSession(null);
getLogger().debug("\n*******************************************" +
"\n* New V2G communication session initialized" +
"\n*******************************************");
}
@Override
public void update(Observable obs, Object obj) {
if ((obs instanceof TCPClient || obs instanceof TLSClient) && obj instanceof byte[]) {
setV2gTpMessage(new V2GTPMessage((byte[]) obj));
if (getMessageHandler().isV2GTPMessageValid(getV2gTpMessage())) {
/*
* We need to decide which schema to use for decoding the EXI encoded message. Only
* the supportedAppProtocolReq/Res message uses a different schema
*/
if (getCurrentState().equals(getStates().get(V2GMessages.SUPPORTED_APP_PROTOCOL_RES))) {
obj = (SupportedAppProtocolRes) getMessageHandler().exiToSuppAppProtocolMsg(getV2gTpMessage().getPayload());
} else {
try {
obj = (V2GMessage) getMessageHandler().exiToV2gMsg(getV2gTpMessage().getPayload());
} catch (ClassCastException e) {
terminateSession("Received incoming message is not a valid V2GTPMessage\n" + e, false);
}
}
processReaction(getCurrentState().processIncomingMessage(obj));
} else {
terminateSession("Received incoming message is not a valid V2GTPMessage", false);
}
} else if ((obs instanceof TCPClient || obs instanceof TLSClient) && obj == null) {
terminateSession("Transport layer has notified an error", false);
} else {
terminateSession("Notification received, but sending entity or received object not identifiable", false);
}
}
private void processReaction(ReactionToIncomingMessage reactionToIncomingMessage) {
if (reactionToIncomingMessage instanceof SendMessage) {
send((SendMessage) reactionToIncomingMessage);
} else if (reactionToIncomingMessage instanceof TerminateSession) {
deleteSessionProperties();
terminateSession((TerminateSession) reactionToIncomingMessage);
} else if (reactionToIncomingMessage instanceof PauseSession) {
saveSessionProperties();
pauseSession((PauseSession) reactionToIncomingMessage);
} else if (reactionToIncomingMessage instanceof ChangeProcessingState) {
setCurrentState(((ChangeProcessingState) reactionToIncomingMessage).getNewState());
processReaction(
getCurrentState().processIncomingMessage(reactionToIncomingMessage)
); // TODO ist das korrekt?! bspw. wenn Renegotiation angefragt wird von EVSE?
} else {
terminateSession("Reaction to incoming message is undefined", false);
}
}
public void send(SendMessage sendingParams) {
// Only EXI encoded messages starting from SessionSetupReq will be sent here
setV2gTpMessage(new V2GTPMessage(GlobalValues.V2GTP_VERSION_1_IS.getByteValue(),
GlobalValues.V2GTP_PAYLOAD_TYPE_EXI_ENCODED_V2G_MESSAGE.getByteArrayValue(),
(byte[]) getMessageHandler().v2gMsgToExi(sendingParams.getPayload()))
);
getTransportLayerClient().send(getV2gTpMessage(), sendingParams.getTimeout());
// Check for necessary CP state change (see [V2G2-847])
if (getChangeToState() != null) {
if (getEvController().setCPState(getChangeToState())) setChangeToState(null);
else terminateSession("State change to " + getChangeToState().toString() + " not successful", false);
}
if (sendingParams.getNextState() != null) {
setCurrentState(sendingParams.getNextState());
} else {
terminateSession("State machine interrupted, no new state provided", true);
}
}
private void saveSessionProperties() {
// According to [V2G2-740]
MiscUtils.getProperties().setProperty("session.id", "" + ByteUtils.toHexString(getSessionID()));
MiscUtils.getProperties().setProperty("authentication.mode", getSelectedPaymentOption().value());
MiscUtils.getProperties().setProperty("energy.transfermode.requested", getRequestedEnergyTransferMode().value());
MiscUtils.storeProperties(GlobalValues.EVCC_CONFIG_PROPERTIES_PATH.toString());
}
private void deleteSessionProperties() {
// Reset the session ID and the authentication mode
MiscUtils.getProperties().setProperty("session.id", "00");
MiscUtils.getProperties().setProperty("authentication.mode", "");
MiscUtils.storeProperties(GlobalValues.EVCC_CONFIG_PROPERTIES_PATH.toString());
}
private void setV2gEVCCCommunicationSetupTimer(
long v2gEVCCCommunicationSetupTimer) {
this.v2gEVCCCommunicationSetupTimer = v2gEVCCCommunicationSetupTimer;
}
public long getV2gEVCCCommunicationSetupTimer() {
return v2gEVCCCommunicationSetupTimer;
}
public String getEvseID() {
return evseID;
}
public void setEvseID(String evseID) {
this.evseID = evseID;
}
public PaymentOptionType getSelectedPaymentOption() {
return selectedPaymentOption;
}
public void setSelectedPaymentOption(PaymentOptionType selectedPaymentOption) {
this.selectedPaymentOption = selectedPaymentOption;
}
public ChargeParameterDiscoveryReqType getChargeParameterDiscoveryReq() {
return chargeParameterDiscoveryReq;
}
public void setChargeParameterDiscoveryReq(
ChargeParameterDiscoveryReqType chargeParameterDiscoveryReq) {
this.chargeParameterDiscoveryReq = chargeParameterDiscoveryReq;
}
public ReactionToIncomingMessage getReactionToIncomingMessage() {
return reactionToIncomingMessage;
}
public void setReactionToIncomingMessage(ReactionToIncomingMessage reactionToIncomingMessage) {
this.reactionToIncomingMessage = reactionToIncomingMessage;
}
public boolean isRenegotiationRequested() {
return renegotiationRequested;
}
public void setRenegotiationRequested(boolean renegotiationRequested) {
this.renegotiationRequested = renegotiationRequested;
}
public long getEvseScheduleReceived() {
return evseScheduleReceived;
}
public void setEvseScheduleReceived(long evseScheduleReceived) {
this.evseScheduleReceived = evseScheduleReceived;
}
public ChargingProfileType getChargingProfile() {
return chargingProfile;
}
public void setChargingProfile(ChargingProfileType chargingProfile) {
this.chargingProfile = chargingProfile;
}
public List<AppProtocolType> getSupportedAppProtocols() {
return supportedAppProtocols;
}
public void setSupportedAppProtocols(List<AppProtocolType> supportedAppProtocols) {
this.supportedAppProtocols = supportedAppProtocols;
}
public AppProtocolType getChosenAppProtocol() {
return chosenAppProtocol;
}
public void setChosenAppProtocol(AppProtocolType chosenAppProtocol) {
this.chosenAppProtocol = chosenAppProtocol;
}
public boolean isOldSessionJoined() {
return oldSessionJoined;
}
public void setOldSessionJoined(boolean oldSessionJoined) {
this.oldSessionJoined = oldSessionJoined;
}
public IEVController getEvController() {
return evController;
}
public void setEvController(IEVController evController) {
this.evController = evController;
}
public long getEvseTimeStamp() {
return evseTimeStamp;
}
public void setEvseTimeStamp(long evseTimeStamp) {
this.evseTimeStamp = evseTimeStamp;
}
public EnergyTransferModeType getRequestedEnergyTransferMode() {
return requestedEnergyTransferMode;
}
public void setRequestedEnergyTransferMode(
EnergyTransferModeType requestedEnergyTransferMode) {
this.requestedEnergyTransferMode = requestedEnergyTransferMode;
}
public SAScheduleListType getSaSchedules() {
return saSchedules;
}
public void setSaSchedules(SAScheduleListType saSchedules) {
this.saSchedules = saSchedules;
this.saSchedulesReceived = System.nanoTime();
}
public long getSaSchedulesReceived() {
return saSchedulesReceived;
}
public CPStates getChangeToState() {
return changeToState;
}
public void setChangeToState(CPStates changeToState) {
this.changeToState = changeToState;
}
public StatefulTransportLayerClient getTransportLayerClient() {
return transportLayerClient;
}
public void setTransportLayerClient(StatefulTransportLayerClient transportLayerClient) {
this.transportLayerClient = transportLayerClient;
}
public SelectedServiceListType getSelectedServices() {
if (selectedServices == null) setSelectedServices(new SelectedServiceListType());
return selectedServices;
}
public void setSelectedServices(SelectedServiceListType selectedServices) {
this.selectedServices = selectedServices;
}
public ServiceListType getOfferedServices() {
if (offeredServices == null) setOfferedServices(new ServiceListType());
return offeredServices;
}
public void setOfferedServices(ServiceListType offeredServices) {
this.offeredServices = offeredServices;
}
public ArrayList<Integer> getServiceDetailsToBeRequested() {
if (serviceDetailsToBeRequested == null) {
serviceDetailsToBeRequested = new ArrayList<Integer>();
}
return serviceDetailsToBeRequested;
}
public void setServiceDetailsToBeRequested(
ArrayList<Integer> serviceDetailsToBeRequested) {
this.serviceDetailsToBeRequested = serviceDetailsToBeRequested;
}
/**
* Checks if the respective service for installing or updating a certificate is offered by the SECC and
* has been selected by the EVCC.
*
* @param parameterSetID 1 for installing a certificate, 2 for updating a certificate
* @return True, if the respective certificate service is available, false otherwise
*/
public boolean isCertificateServiceAvailable(short parameterSetID) {
for (SelectedServiceType service : getSelectedServices().getSelectedService()) {
if (service.getServiceID() == 2 && // ServiceID 2 refers to the 'Certificate' service
service.getParameterSetID() != null &&
service.getParameterSetID() == parameterSetID)
return true;
}
return false;
}
public ContractCertificateStatus getContractCertStatus() {
return contractCertStatus;
}
public void setContractCertStatus(ContractCertificateStatus contractCertStatus) {
this.contractCertStatus = contractCertStatus;
}
public long getOngoingTimer() {
return ongoingTimer;
}
public void setOngoingTimer(long ongoingTimer) {
this.ongoingTimer = ongoingTimer;
}
public boolean isOngoingTimerActive() {
return isOngoingTimerActive;
}
public void setOngoingTimerActive(boolean isOngoingTimerActive) {
this.isOngoingTimerActive = isOngoingTimerActive;
}
public byte[] getSentGenChallenge() {
return sentGenChallenge;
}
public void setSentGenChallenge(byte[] sentGenChallenge) {
this.sentGenChallenge = sentGenChallenge;
}
public ChargingSessionType getChargingSession() {
return chargingSession;
}
public void setChargingSession(ChargingSessionType chargingSession) {
this.chargingSession = chargingSession;
}
}

View File

@@ -0,0 +1,409 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.session;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.v2gclarity.risev2g.evcc.transportLayer.StatefulTransportLayerClient;
import com.v2gclarity.risev2g.evcc.transportLayer.TCPClient;
import com.v2gclarity.risev2g.evcc.transportLayer.TLSClient;
import com.v2gclarity.risev2g.evcc.transportLayer.UDPClient;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.MessageHandler;
import com.v2gclarity.risev2g.shared.messageHandling.PauseSession;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.misc.TimeRestrictions;
import com.v2gclarity.risev2g.shared.misc.V2GTPMessage;
import com.v2gclarity.risev2g.shared.utils.ByteUtils;
import com.v2gclarity.risev2g.shared.utils.MiscUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.SECCDiscoveryReq;
import com.v2gclarity.risev2g.shared.v2gMessages.SECCDiscoveryRes;
import com.v2gclarity.risev2g.shared.v2gMessages.appProtocol.AppProtocolType;
import com.v2gclarity.risev2g.shared.v2gMessages.appProtocol.SupportedAppProtocolReq;
public class V2GCommunicationSessionHandlerEVCC implements Observer {
private Logger logger = LogManager.getLogger(this.getClass().getSimpleName());
private MessageHandler messageHandler;
private int seccDiscoveryRequestCounter;
private int sessionRetryCounter;
private byte security;
private V2GCommunicationSessionEVCC v2gCommunicationSessionEVCC;
private V2GTPMessage v2gTpMessage;
private Thread transportLayerThread;
private StatefulTransportLayerClient transportLayerClient;
public V2GCommunicationSessionHandlerEVCC() {
setMessageHandler(MessageHandler.getInstance());
setSecurity(
(MiscUtils.getPropertyValue("tls") != null ?
(byte) MiscUtils.getPropertyValue("tls") :
GlobalValues.V2G_SECURITY_WITHOUT_TLS.getByteValue())
);
setSessionRetryCounter(0);
initialize();
}
private void initialize() {
UDPClient udpClient = UDPClient.getInstance();
if (!udpClient.initialize()) {
logger.fatal("UDP client cannot be initialized, ISO 15118 communication cannot continue");
return;
};
byte[] udpResponse = null;
SECCDiscoveryRes seccDiscoveryRes = null;
setSessionRetryCounter(getSeccDiscoveryRequestCounter() + 1);
// Create SECCDiscoveryReq and check response
while (getSeccDiscoveryRequestCounter() < TimeRestrictions.SDP_REQUEST_MAX_COUNTER) {
udpResponse = sendSECCDiscoveryReq(getSecurity());
if (udpResponse == null) {
getLogger().warn("Number of SECCDiscoveryReq messages so far: " + getSeccDiscoveryRequestCounter());
} else {
setV2gTpMessage(new V2GTPMessage(udpResponse));
if (getMessageHandler().isV2GTPMessageValid(getV2gTpMessage())) {
seccDiscoveryRes = new SECCDiscoveryRes(getV2gTpMessage().getPayload());
break; // if everything is OK and a valid SDP response is received, the while loop is stopped here
} else {
continue;
}
}
}
// SECCDiscoveryRes was successful, establish a new V2GCommunicationSessionEVCC and initiate the respective TCP client connection
startNewSession(seccDiscoveryRes);
}
private boolean startNewSession(SECCDiscoveryRes seccDiscoveryRes) {
/*
* Establish a new V2GCommunicationSessionEVCC if SECCDiscoveryRes was successful and initiate
* the respective TCP client connection
*/
if (seccDiscoveryRes != null) {
// Reset SECCDiscoveryReq retry counter
setSeccDiscoveryRequestCounter(0);
Inet6Address seccAddress;
try {
// TODO seems to work, but is the needed scope ID really the one of the UDP client?
seccAddress = Inet6Address.getByAddress(
InetAddress.getByAddress(seccDiscoveryRes.getSeccIPAddress()).getHostAddress(),
seccDiscoveryRes.getSeccIPAddress(),
UDPClient.getInstance().getUdpClientAddress().getScopeId()
);
} catch (UnknownHostException e) {
getLogger().fatal("SECC address could not be resolved", e);
return false;
}
getLogger().info("UDP server responded: SECC reachable at address " +
seccAddress.getHostAddress() + " and port " +
ByteUtils.toIntFromByteArray(seccDiscoveryRes.getSeccPort()));
if (!startTransportLayerClient(seccDiscoveryRes, seccAddress)) return false;
setV2gCommunicationSessionEVCC(new V2GCommunicationSessionEVCC(getTransportLayerClient()));
/*
* Tell the TCP- or TLSClient to notify if
* - a new V2GTPMessage has arrived
* - a timeout has occurred while waiting for the respective response message
*/
getTransportLayerClient().addObserver(getV2gCommunicationSessionEVCC());
getV2gCommunicationSessionEVCC().addObserver(this);
// Set TLS security flag for communication session
boolean secureConn = (((Byte) getSecurity()).compareTo((Byte) GlobalValues.V2G_SECURITY_WITH_TLS.getByteValue()) == 0) ? true : false;
getV2gCommunicationSessionEVCC().setTlsConnection(secureConn);
sendSupportedAppProtocolReq();
} else {
getLogger().fatal("Maximum number of SECCDiscoveryReq messages reached");
return false;
}
return true;
}
private boolean startTransportLayerClient(SECCDiscoveryRes seccDiscoveryRes, Inet6Address seccAddress) {
boolean securityAgreement = Byte.compare(seccDiscoveryRes.getSecurity(), getSecurity()) == 0 ? true : false;
/*
* Note 8 of ISO/IEC 15118-2 states:
* "Not supporting TLS in the SECC might lead in general to aborted charging sessions
* with particular EVs as it is in the responsibility of the EV to accept sessions
* without TLS"
*
* This implementation of an EVCC will only accept TLS connections to the SECC if requested on
* EVCC-side. However, this is the place to change the implementation if wanted. It is however
* strongly recommended to always choose TLS.
*/
if (securityAgreement && isSecureCommunication()) {
if (TLSClient.getInstance().initialize(
seccAddress,
ByteUtils.toIntFromByteArray(seccDiscoveryRes.getSeccPort()))) {
setTransportLayerClient(TLSClient.getInstance());
} else {
getLogger().fatal("TLS client could not be initialized");
return false;
}
} else if (securityAgreement && !isSecureCommunication()) {
if (TCPClient.getInstance().initialize(
seccAddress,
ByteUtils.toIntFromByteArray(seccDiscoveryRes.getSeccPort()))) {
setTransportLayerClient(TCPClient.getInstance());
} else {
getLogger().fatal("TCP client could not be initialized");
return false;
}
} else {
getLogger().fatal("EVCC and SECC could not agree on security level of transport layer");
return false;
}
setTransportLayerThread(new Thread(getTransportLayerClient()));
getTransportLayerThread().start();
return true;
}
@Override
public void update(Observable obs, Object obj) {
if (obs instanceof V2GCommunicationSessionEVCC &&
(obj instanceof PauseSession || obj instanceof TerminateSession)) {
// In case of pausing or terminating a session the transport layer client must be stopped
getTransportLayerClient().stop();
getTransportLayerThread().interrupt();
if (obj instanceof PauseSession) {
/*
* If some action is needed by the sessionHandler when pausing, it can be done here.
* If TCP/TLS client sends notification, it should always be a TerminateSession instance
* (because a failure of the connection to the TCP/TLS server is its only reason for
* notification).
*/
} else if (obj instanceof TerminateSession) {
terminate((TerminateSession) obj);
}
} else if (obs instanceof TCPClient || obs instanceof TLSClient) {
// TCP- and TLSClient already stop themselves and interrupt their threads before notifying
terminate((TerminateSession) obj);
} else {
getLogger().warn("Notification coming from " + obs.getClass().getSimpleName() +
" unknown: " + obj.getClass().getSimpleName());
}
}
private void terminate(TerminateSession terminationObject) {
getTransportLayerThread().interrupt();
setV2gCommunicationSessionEVCC(null);
UDPClient.getInstance().stop();;
if (!terminationObject.isSuccessfulTermination()) {
// TODO should there be a retry of the communication session, and if yes, how often?
}
}
private byte[] sendSECCDiscoveryReq(byte security) {
/*
* The standard in principle allows to set UDP as requested transport protocol, however,
* there is no good reason for actually not using TCP (or TLS). Therefore this is not a
* configurable option.
*/
SECCDiscoveryReq seccDiscoveryReq =
new SECCDiscoveryReq(security, GlobalValues.V2G_TRANSPORT_PROTOCOL_TCP.getByteValue());
setV2gTpMessage(
new V2GTPMessage(GlobalValues.V2GTP_VERSION_1_IS.getByteValue(),
GlobalValues.V2GTP_PAYLOAD_TYPE_SDP_REQUEST_MESSAGE.getByteArrayValue(),
seccDiscoveryReq.getPayload())
);
getLogger().debug("Preparing to send SECCDiscoveryReq ...");
setSeccDiscoveryRequestCounter(getSeccDiscoveryRequestCounter() + 1);
return UDPClient.getInstance().send(getV2gTpMessage());
}
/**
* All supported versions of the ISO/IEC 15118-2 protocol are listed here.
* Currently, only IS version of April 2014 is supported (see [V2G2-098]), more could be provided here.
*
* @return A list of supported of AppProtocol entries
*/
private void sendSupportedAppProtocolReq() {
List<AppProtocolType> supportedAppProtocols = new ArrayList<AppProtocolType>();
AppProtocolType appProtocol1 = new AppProtocolType();
appProtocol1.setProtocolNamespace(GlobalValues.V2G_CI_MSG_DEF_NAMESPACE.toString());
appProtocol1.setVersionNumberMajor(2);
appProtocol1.setVersionNumberMinor(0);
appProtocol1.setSchemaID((short) 10);
appProtocol1.setPriority((short) 1);
supportedAppProtocols.add(appProtocol1);
SupportedAppProtocolReq supportedAppProtocolReq = new SupportedAppProtocolReq();
supportedAppProtocolReq.getAppProtocol().add(appProtocol1);
// Save the list of supported protocols
getV2gCommunicationSessionEVCC().setSupportedAppProtocols(supportedAppProtocols);
setV2gTpMessage(
new V2GTPMessage(
GlobalValues.V2GTP_VERSION_1_IS.getByteValue(),
GlobalValues.V2GTP_PAYLOAD_TYPE_EXI_ENCODED_V2G_MESSAGE.getByteArrayValue(),
(byte[]) getMessageHandler().suppAppProtocolMsgToExi(supportedAppProtocolReq)
)
);
getLogger().debug("Preparing to send SupportedAppProtocolReq ...");
if (isSecureCommunication()) {
TLSClient.getInstance().send(
getV2gTpMessage(),
TimeRestrictions.getV2gEvccMsgTimeout(V2GMessages.SUPPORTED_APP_PROTOCOL_RES));
} else {
TCPClient.getInstance().send(
getV2gTpMessage(),
TimeRestrictions.getV2gEvccMsgTimeout(V2GMessages.SUPPORTED_APP_PROTOCOL_RES));
}
}
public Logger getLogger() {
return logger;
}
public void setLogger(Logger logger) {
this.logger = logger;
}
public MessageHandler getMessageHandler() {
return messageHandler;
}
public void setMessageHandler(MessageHandler messageHandler) {
this.messageHandler = messageHandler;
}
public int getSeccDiscoveryRequestCounter() {
return seccDiscoveryRequestCounter;
}
public void setSeccDiscoveryRequestCounter(int seccDiscoveryRequestCounter) {
this.seccDiscoveryRequestCounter = seccDiscoveryRequestCounter;
}
public byte getSecurity() {
return security;
}
public boolean isSecureCommunication() {
return Byte.compare(getSecurity(), GlobalValues.V2G_SECURITY_WITH_TLS.getByteValue()) == 0 ? true : false;
}
public void setSecurity(byte security) {
this.security = security;
getLogger().info("Security level " +
((Byte.compare(security, GlobalValues.V2G_SECURITY_WITH_TLS.getByteValue()) == 0) ? "TLS" : "TCP") +
" was chosen");
}
public V2GCommunicationSessionEVCC getV2gCommunicationSessionEVCC() {
return v2gCommunicationSessionEVCC;
}
public void setV2gCommunicationSessionEVCC(
V2GCommunicationSessionEVCC v2gCommunicationSessionEVCC) {
this.v2gCommunicationSessionEVCC = v2gCommunicationSessionEVCC;
}
public int getSessionRetryCounter() {
return sessionRetryCounter;
}
public void setSessionRetryCounter(int sessionRetryCounter) {
this.sessionRetryCounter = sessionRetryCounter;
}
public V2GTPMessage getV2gTpMessage() {
return v2gTpMessage;
}
public void setV2gTpMessage(V2GTPMessage v2gTpMessage) {
this.v2gTpMessage = v2gTpMessage;
}
public Thread getTransportLayerThread() {
return transportLayerThread;
}
public void setTransportLayerThread(Thread transportLayerThread) {
this.transportLayerThread = transportLayerThread;
}
public StatefulTransportLayerClient getTransportLayerClient() {
return transportLayerClient;
}
public void setTransportLayerClient(StatefulTransportLayerClient transportLayerClient) {
this.transportLayerClient = transportLayerClient;
}
}

View File

@@ -0,0 +1,471 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import java.security.KeyStore;
import java.util.Arrays;
import java.util.ListIterator;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import com.v2gclarity.risev2g.evcc.evController.IACEVController;
import com.v2gclarity.risev2g.evcc.evController.IDCEVController;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.CPStates;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.misc.State;
import com.v2gclarity.risev2g.shared.utils.ByteUtils;
import com.v2gclarity.risev2g.shared.utils.MiscUtils;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.AuthorizationReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.AuthorizationResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CableCheckReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CableCheckResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateInstallationResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateUpdateResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscoveryReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscoveryResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeProgressType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingProfileType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingStatusResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CurrentDemandReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CurrentDemandResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVPowerDeliveryParameterType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EMAIDType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EnergyTransferModeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.MessageHeaderType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.MeteringReceiptResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentDetailsReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentDetailsResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentServiceSelectionReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentServiceSelectionResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PowerDeliveryReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PowerDeliveryResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PreChargeResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceDetailReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceDetailResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceDiscoveryResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SessionSetupResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SessionStopReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SessionStopResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.WeldingDetectionResType;
/**
* Some request messages are to be sent from different states which makes it more convenient (having
* less code and being less error-prone) to keep the creation of those messages in one single class.
*/
public abstract class ClientState extends State {
public ClientState(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
public V2GCommunicationSessionEVCC getCommSessionContext() {
return (V2GCommunicationSessionEVCC) super.getCommSessionContext();
}
protected boolean isIncomingMessageValid(Object incomingMessage, Class<? extends BodyBaseType> expectedMessage) {
V2GMessage v2gMessage = null;
if (incomingMessage instanceof V2GMessage) {
v2gMessage = (V2GMessage) incomingMessage;
if (!expectedMessage.isAssignableFrom(v2gMessage.getBody().getBodyElement().getValue().getClass())) {
getLogger().fatal("Invalid message (" + v2gMessage.getBody().getBodyElement().getValue().getClass().getSimpleName() +
") at this state (" + this.getClass().getSimpleName() + ")");
return false;
} else {
getLogger().debug(v2gMessage.getBody().getBodyElement().getValue().getClass().getSimpleName().replace("Type", "") + " received");
if (!isHeaderOK(v2gMessage.getHeader())) return false;
if (!isResponseCodeOK(v2gMessage)) return false;
return true;
}
} else {
getLogger().fatal("Incoming message is not a V2GMessage");
return false;
}
}
/**
* Performs the following checks:
* - is the returned session ID the same as the one saved by the EVCC?
* - does the EVCC need to react to a possibly set notification?
* - does the EVCC need to check the signature?
*
* @param header The header of the V2GMessage
*/
private boolean isHeaderOK(MessageHeaderType header) {
// Check sessionID (only if not at state WaitForSessionSetupRes)
if (!this.equals(getCommSessionContext().getStates().get(V2GMessages.SESSION_SETUP_RES)) &&
!Arrays.equals(header.getSessionID(), getCommSessionContext().getSessionID())) {
getLogger().error("Session ID is invalid: " +
"expected " + ByteUtils.toLongFromByteArray(getCommSessionContext().getSessionID()) +
", received " + ByteUtils.toLongFromByteArray(header.getSessionID()));
return false;
}
if (header.getNotification() != null) {
// TODO react on the several notifications
}
/*
* If a signature is present, it is placed in the header. However, not all messages have a
* signature. Therefore, the signature validation is to be done in the respective state itself.
*/
return true;
}
private boolean isResponseCodeOK(V2GMessage responseMessage) {
BodyBaseType bbt = ((V2GMessage) responseMessage).getBody().getBodyElement().getValue();
ResponseCodeType v2gMessageRCT = null;
switch (bbt.getClass().getSimpleName()) {
case "SessionSetupResType":
v2gMessageRCT = ((SessionSetupResType) bbt).getResponseCode();
break;
case "ServiceDiscoveryResType":
v2gMessageRCT = ((ServiceDiscoveryResType) bbt).getResponseCode();
break;
case "ServiceDetailResType":
v2gMessageRCT = ((ServiceDetailResType) bbt).getResponseCode();
break;
case "PaymentServiceSelectionResType":
v2gMessageRCT = ((PaymentServiceSelectionResType) bbt).getResponseCode();
break;
case "PaymentDetailsResType":
v2gMessageRCT = ((PaymentDetailsResType) bbt).getResponseCode();
break;
case "CertificateInstallationResType":
v2gMessageRCT = ((CertificateInstallationResType) bbt).getResponseCode();
break;
case "CertificateUpdateResType":
v2gMessageRCT = ((CertificateUpdateResType) bbt).getResponseCode();
break;
case "AuthorizationResType":
v2gMessageRCT = ((AuthorizationResType) bbt).getResponseCode();
break;
case "ChargeParameterDiscoveryResType":
v2gMessageRCT = ((ChargeParameterDiscoveryResType) bbt).getResponseCode();
break;
case "CableCheckResType":
v2gMessageRCT = ((CableCheckResType) bbt).getResponseCode();
break;
case "PreChargeResType":
v2gMessageRCT = ((PreChargeResType) bbt).getResponseCode();
break;
case "PowerDeliveryResType":
v2gMessageRCT = ((PowerDeliveryResType) bbt).getResponseCode();
break;
case "ChargingStatusResType":
v2gMessageRCT = ((ChargingStatusResType) bbt).getResponseCode();
break;
case "CurrentDemandResType":
v2gMessageRCT = ((CurrentDemandResType) bbt).getResponseCode();
break;
case "MeteringReceiptResType":
v2gMessageRCT = ((MeteringReceiptResType) bbt).getResponseCode();
break;
case "WeldingDetectionResType":
v2gMessageRCT = ((WeldingDetectionResType) bbt).getResponseCode();
break;
case "SessionStopResType":
v2gMessageRCT = ((SessionStopResType) bbt).getResponseCode();
break;
default:
getLogger().error("Response message could not be identified");
return false;
}
if (v2gMessageRCT.toString().startsWith("OK")) return true;
else {
getLogger().error("Negative response code " + v2gMessageRCT.toString());
return false;
}
}
/**
* A ServiceDetailReq needs to be generated from several states:
* - WaitForServiceDiscoveryRes
* - WaitForServiceDetailRes
*
* Checks if the list of value added services (VAS) which are to be used contains service IDs. Those
* service IDs can be used in a ServiceDetailReq to request more details about the service.
* Each time a ServiceDetailReq is created, the respective service ID is deleted from the list.
*
* @return A ServiceDetailReq with a service ID whose details are requested, if the list of service IDs
* is not empty. Null otherwise.
*/
protected ServiceDetailReqType getServiceDetailReq() {
if (getCommSessionContext().getServiceDetailsToBeRequested().size() > 0) {
ListIterator<Integer> listIterator = getCommSessionContext().getServiceDetailsToBeRequested().listIterator();
ServiceDetailReqType serviceDetailReq = new ServiceDetailReqType();
serviceDetailReq.setServiceID(listIterator.next());
listIterator.remove();
return serviceDetailReq;
}
return null;
}
/**
* A ServiceDetailReq needs to be generated from several states:
* - WaitForServiceDiscoveryRes
* - WaitForServiceDetailRes
*/
protected PaymentServiceSelectionReqType paymentServiceSelectionReq() {
PaymentServiceSelectionReqType paymentServiceSelectionReq = new PaymentServiceSelectionReqType();
paymentServiceSelectionReq.setSelectedPaymentOption(getCommSessionContext().getSelectedPaymentOption());
return paymentServiceSelectionReq;
}
/**
* An AuthorizationReq needs to be generated from several states:
* - WaitForPaymentServiceSelectionRes (no genChallege)
* - WaitForPaymentDetailsRes (genChallenge)
* - WaitForAuthorizationRes (no genChallenge, EVSE is still processing)
*
* @return An AuthorizationReq, either empty or with a set genChallenge and ID depending on input parameter
*/
protected AuthorizationReqType getAuthorizationReq(byte[] genChallenge) {
AuthorizationReqType authorizationReq = new AuthorizationReqType();
if (genChallenge != null) {
authorizationReq.setGenChallenge(genChallenge);
/*
* Experience from the test symposium in San Diego (April 2016):
* The Id element of the signature is not restricted in size by the standard itself. But on embedded
* systems, the memory is very limited which is why we should not use long IDs for the signature reference
* element. A good size would be 3 characters max (like the example in the ISO 15118-2 annex J)
*/
authorizationReq.setId("ID1");
}
return authorizationReq;
}
/**
* A CableCheckReq needs to be generated from several states:
* - WaitForChargeParameterDiscoveryRes
* - WaitForCableCheckRes (EVSEProcessing = ONGOING)
*
* @return A CableCheckReq
*/
protected CableCheckReqType getCableCheckReq() {
CableCheckReqType cableCheckReq = new CableCheckReqType();
cableCheckReq.setDCEVStatus(((IDCEVController) getCommSessionContext().getEvController()).getDCEVStatus());
return cableCheckReq;
}
/**
* A CurrentDemandReq needs to be generated from several states:
* - WaitForCurrentDemandRes (the initial CurrentDemandReq message)
* - WaitForMeteringReceiptRes
*
* @return A CurrentDemandReq message
*/
protected CurrentDemandReqType getCurrentDemandReq() {
IDCEVController evController = (IDCEVController) getCommSessionContext().getEvController();
CurrentDemandReqType currentDemandReq = new CurrentDemandReqType();
currentDemandReq.setBulkChargingComplete(evController.isBulkChargingComplete());
currentDemandReq.setChargingComplete(evController.isChargingComplete());
currentDemandReq.setDCEVStatus(evController.getDCEVStatus());
currentDemandReq.setEVMaximumCurrentLimit(evController.getMaximumCurrentLimit());
currentDemandReq.setEVMaximumPowerLimit(evController.getMaximumPowerLimit());
currentDemandReq.setEVMaximumVoltageLimit(evController.getMaximumVoltageLimit());
currentDemandReq.setEVTargetCurrent(evController.getTargetCurrent());
currentDemandReq.setEVTargetVoltage(evController.getTargetVoltage());
currentDemandReq.setRemainingTimeToBulkSoC(evController.getRemainingTimeToBulkSOC());
currentDemandReq.setRemainingTimeToFullSoC(evController.getRemainingTimeToFullSOC());
return currentDemandReq;
}
/**
* A ChargeParameterDiscoveryReq needs to be generated from several states:
* - WaitForAuthorizationRes (the initial ChargeParameterDiscoveryReq)
* - WaitForPowerDeliveryRes (in case AC_EVSEStatus requests a renegotiation)
* - WaitForChargingStatusRes (in case AC_EVSEStatus requests a renegotiation)
*
* @return A ChargeParameterDiscoveryReq which itself consists of several complex datatypes.
*/
protected ChargeParameterDiscoveryReqType getChargeParameterDiscoveryReq() {
ChargeParameterDiscoveryReqType chargeParameterDiscoveryReq = new ChargeParameterDiscoveryReqType();
// Optionally limit the number of entries in the SAScheduleTuple by setting MaxEntriesSAScheduleTuple
chargeParameterDiscoveryReq.setRequestedEnergyTransferMode(getRequestedEnergyTransferMode());
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC"))
chargeParameterDiscoveryReq.setEVChargeParameter(((IACEVController) getCommSessionContext().getEvController()).getACEVChargeParamter());
else
chargeParameterDiscoveryReq.setEVChargeParameter(((IDCEVController) getCommSessionContext().getEvController()).getDCEVChargeParamter());
return chargeParameterDiscoveryReq;
}
/**
* A PaymentServiceSelectionReq needs to be generated from several states:
* - WaitForServiceDiscoveryRes
* - WaitForServiceDetailRes
*
* @return A PaymentServiceSelectionReq
*/
protected PaymentServiceSelectionReqType getPaymentServiceSelectionReq() {
PaymentServiceSelectionReqType paymentServiceSelectionReq = new PaymentServiceSelectionReqType();
paymentServiceSelectionReq.setSelectedPaymentOption(getCommSessionContext().getSelectedPaymentOption());
paymentServiceSelectionReq.setSelectedServiceList(getCommSessionContext().getSelectedServices());
return paymentServiceSelectionReq;
}
/**
* A PaymentDetailsReq needs to be generated from several states:
* - WaitForPaymentServiceSelectionRes
* - WaitForCertificateInstallationRes
* - WaitForCertificateUpdateRes
*
* @return A PaymentDetailsReq
*/
protected PaymentDetailsReqType getPaymentDetailsReq() {
KeyStore evccKeyStore = SecurityUtils.getKeyStore(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
PaymentDetailsReqType paymentDetailsReq = new PaymentDetailsReqType();
EMAIDType emaid = SecurityUtils.getEMAID(GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
if (emaid != null) {
paymentDetailsReq.setEMAID(SecurityUtils.getEMAID(GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()).getValue());
paymentDetailsReq.setContractSignatureCertChain(SecurityUtils.getCertificateChain(
evccKeyStore, GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString()));
}
return paymentDetailsReq;
}
/**
* A PowerDeliveryReq needs to be generated from several states:
* - WaitForChargeParameterDiscoveryRes
* - WaitForChargingStatusRes
* - WaitForMeteringReceiptRes
*
* @param chargeProgress Indicates whether to START a charging session, RENEGOTIATE charing parameters
* or STOP the charging session
* @return A ChargeParameterDiscoveryReq which itself consists of several complex datatypes.
*/
protected PowerDeliveryReqType getPowerDeliveryReq(ChargeProgressType chargeProgress) {
PowerDeliveryReqType powerDeliveryReq = new PowerDeliveryReqType();
if (chargeProgress.equals(ChargeProgressType.START)) {
// Signal needed state change after sending PowerDeliveryReq in AC charging mode
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC"))
getCommSessionContext().setChangeToState(CPStates.STATE_C);
ChargingProfileType chargingProfile = getCommSessionContext().getEvController().getChargingProfile();
powerDeliveryReq.setChargingProfile(chargingProfile);
getCommSessionContext().setChargingProfile(chargingProfile);
} else if (chargeProgress.equals(ChargeProgressType.STOP)) {
// Signal needed state change after sending PowerDeliveryReq in AC charging mode
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC"))
getCommSessionContext().setChangeToState(CPStates.STATE_B);
}
powerDeliveryReq.setChargeProgress(chargeProgress);
powerDeliveryReq.setSAScheduleTupleID(getCommSessionContext().getEvController().getChosenSAScheduleTupleID());
// Set DC_EVPowerDeliveryParameter if in DC charging mode
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("DC")) {
/*
* The MessageHandler method getJAXBElement() cannot be used here because of the difference in the
* class name (DCEVPowerDeliveryParameter) and the name in the XSD (DC_EVPowerDeliveryParameter)
*/
JAXBElement<DCEVPowerDeliveryParameterType> jaxbDcEvPowerDeliveryParameter = new JAXBElement<>(new QName("urn:iso:15118:2:2013:MsgDataTypes", "DC_EVPowerDeliveryParameter"),
DCEVPowerDeliveryParameterType.class,
((IDCEVController) getCommSessionContext().getEvController()).getEVPowerDeliveryParameter());
powerDeliveryReq.setEVPowerDeliveryParameter(jaxbDcEvPowerDeliveryParameter);
}
return powerDeliveryReq;
}
/**
* A SessionStopReq needs to be generated from several states:
* - WaitForPowerDeliveryRes
* - WaitForWeldingDetectionRes
*
* @return A SessionStopReq message
*/
protected SessionStopReqType getSessionStopReq(ChargingSessionType chargingSessionType) {
SessionStopReqType sessionStopReq = new SessionStopReqType();
sessionStopReq.setChargingSession(chargingSessionType);
return sessionStopReq;
}
protected EnergyTransferModeType getRequestedEnergyTransferMode() {
EnergyTransferModeType requestedEnergyTransferMode = null;
// Check if an EnergyTransferModeType has been requested in a previously paused session
if (getCommSessionContext().isOldSessionJoined())
requestedEnergyTransferMode = (EnergyTransferModeType) MiscUtils.getPropertyValue("energy.transfermode.requested");
if (requestedEnergyTransferMode == null)
requestedEnergyTransferMode = getCommSessionContext().getEvController().getRequestedEnergyTransferMode();
// We need to save the requested energy transfer mode in the session variable to be able to store in the properties file during pausing
getCommSessionContext().setRequestedEnergyTransferMode(requestedEnergyTransferMode);
return requestedEnergyTransferMode;
}
}

View File

@@ -0,0 +1,98 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import java.util.concurrent.TimeUnit;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.misc.TimeRestrictions;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.AuthorizationReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.AuthorizationResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscoveryReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSEProcessingType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForAuthorizationRes extends ClientState {
public WaitForAuthorizationRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, AuthorizationResType.class)) {
V2GMessage v2gMessageRes = (V2GMessage) message;
AuthorizationResType authorizationRes =
(AuthorizationResType) v2gMessageRes.getBody().getBodyElement().getValue();
if (authorizationRes.getEVSEProcessing() == null)
return new TerminateSession("EVSEProcessing parameter of AuthorizationRes is null. Parameter is mandatory.");
if (authorizationRes.getEVSEProcessing().equals(EVSEProcessingType.FINISHED)) {
getLogger().debug("EVSEProcessing was set to FINISHED");
getCommSessionContext().setOngoingTimer(0L);
getCommSessionContext().setOngoingTimerActive(false);
ChargeParameterDiscoveryReqType chargeParameterDiscoveryReq = getChargeParameterDiscoveryReq();
/*
* Save this request in case the ChargeParameterDiscoveryRes indicates that the EVSE is
* still processing. Then this request can just be resent instead of asking the EV again.
*/
getCommSessionContext().setChargeParameterDiscoveryReq(chargeParameterDiscoveryReq);
return getSendMessage(chargeParameterDiscoveryReq, V2GMessages.CHARGE_PARAMETER_DISCOVERY_RES);
} else {
getLogger().debug("EVSEProcessing was set to ONGOING");
long elapsedTimeInMs = 0;
if (getCommSessionContext().isOngoingTimerActive()) {
long elapsedTime = System.nanoTime() - getCommSessionContext().getOngoingTimer();
elapsedTimeInMs = TimeUnit.MILLISECONDS.convert(elapsedTime, TimeUnit.NANOSECONDS);
if (elapsedTimeInMs > TimeRestrictions.V2G_EVCC_ONGOING_TIMEOUT)
return new TerminateSession("Ongoing timer timed out for AuthorizationReq");
} else {
getCommSessionContext().setOngoingTimer(System.nanoTime());
getCommSessionContext().setOngoingTimerActive(true);
}
// [V2G2-684] demands to send an empty AuthorizationReq if the field EVSEProcessing is set to 'Ongoing'
AuthorizationReqType authorizationReq = getAuthorizationReq(null);
return getSendMessage(authorizationReq, V2GMessages.AUTHORIZATION_RES, Math.min((TimeRestrictions.V2G_EVCC_ONGOING_TIMEOUT - (int) elapsedTimeInMs), TimeRestrictions.getV2gEvccMsgTimeout(V2GMessages.AUTHORIZATION_RES)));
}
} else {
return new TerminateSession("Incoming message raised an error");
}
}
}

View File

@@ -0,0 +1,88 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import java.util.concurrent.TimeUnit;
import com.v2gclarity.risev2g.evcc.evController.IDCEVController;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.misc.TimeRestrictions;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CableCheckResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSEProcessingType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PreChargeReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForCableCheckRes extends ClientState {
public WaitForCableCheckRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, CableCheckResType.class)) {
V2GMessage v2gMessageRes = (V2GMessage) message;
CableCheckResType cableCheckRes =
(CableCheckResType) v2gMessageRes.getBody().getBodyElement().getValue();
if (cableCheckRes.getEVSEProcessing().equals(EVSEProcessingType.FINISHED)) {
getLogger().debug("EVSEProcessing was set to FINISHED");
IDCEVController dcEvController = (IDCEVController) getCommSessionContext().getEvController();
PreChargeReqType preChargeReq = new PreChargeReqType();
preChargeReq.setDCEVStatus(dcEvController.getDCEVStatus());
preChargeReq.setEVTargetCurrent(dcEvController.getTargetCurrent());
preChargeReq.setEVTargetVoltage(dcEvController.getTargetVoltage());
getCommSessionContext().setOngoingTimer(System.nanoTime());
getCommSessionContext().setOngoingTimerActive(true);
return getSendMessage(preChargeReq, V2GMessages.PRE_CHARGE_RES);
} else {
getLogger().debug("EVSEProcessing was set to ONGOING");
long elapsedTimeInMs = 0;
if (getCommSessionContext().isOngoingTimerActive()) {
long elapsedTime = System.nanoTime() - getCommSessionContext().getOngoingTimer();
elapsedTimeInMs = TimeUnit.MILLISECONDS.convert(elapsedTime, TimeUnit.NANOSECONDS);
if (elapsedTimeInMs > TimeRestrictions.V2G_EVCC_CABLE_CHECK_TIMEOUT)
return new TerminateSession("CableCheck timer timed out for CableCheckReq");
} else {
getCommSessionContext().setOngoingTimer(System.nanoTime());
getCommSessionContext().setOngoingTimerActive(true);
}
return getSendMessage(getCableCheckReq(), V2GMessages.CABLE_CHECK_RES, Math.min((TimeRestrictions.V2G_EVCC_CABLE_CHECK_TIMEOUT - (int) elapsedTimeInMs), TimeRestrictions.getV2gEvccMsgTimeout(V2GMessages.CABLE_CHECK_RES)));
}
} else {
return new TerminateSession("Incoming message raised an error");
}
}
}

View File

@@ -0,0 +1,125 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import java.security.interfaces.ECPrivateKey;
import java.util.HashMap;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.enumerations.PKI;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateInstallationResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SignatureType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForCertificateInstallationRes extends ClientState {
public WaitForCertificateInstallationRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, CertificateInstallationResType.class)) {
V2GMessage v2gMessageRes = (V2GMessage) message;
CertificateInstallationResType certificateInstallationRes =
(CertificateInstallationResType) v2gMessageRes.getBody().getBodyElement().getValue();
if (!verifySignature(certificateInstallationRes, v2gMessageRes.getHeader().getSignature())) {
return new TerminateSession("Signature verification failed");
}
// Check complete CPS certificate chain
ResponseCodeType certChainResponseCode = SecurityUtils.verifyCertificateChain(
certificateInstallationRes.getSAProvisioningCertificateChain(),
GlobalValues.EVCC_TRUSTSTORE_FILEPATH.toString(),
PKI.CPS);
if (!certChainResponseCode.equals(ResponseCodeType.OK)) {
return new TerminateSession("Provisioning certificate chain is not valid");
}
ECPrivateKey oemProvCertPrivateKey = SecurityUtils.getPrivateKey(
SecurityUtils.getKeyStore(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()),
GlobalValues.ALIAS_OEM_PROV_CERTIFICATE.toString());
// Save contract certificate chain
if (!SecurityUtils.saveContractCertificateChain(
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString(),
certificateInstallationRes.getContractSignatureCertChain(),
SecurityUtils.decryptContractCertPrivateKey(
certificateInstallationRes.getDHpublickey().getValue(),
certificateInstallationRes.getContractSignatureEncryptedPrivateKey().getValue(),
oemProvCertPrivateKey))) {
return new TerminateSession("Contract certificate chain could not be saved");
}
return getSendMessage(getPaymentDetailsReq(), V2GMessages.PAYMENT_DETAILS_RES);
} else {
return new TerminateSession("Incoming message raised an error");
}
}
private boolean verifySignature(CertificateInstallationResType certificateInstallationRes, SignatureType signature) {
HashMap<String, byte[]> verifyXMLSigRefElements = new HashMap<String, byte[]>();
verifyXMLSigRefElements.put(
certificateInstallationRes.getContractSignatureCertChain().getId(),
SecurityUtils.generateDigest(
certificateInstallationRes.getContractSignatureCertChain().getId(),
getMessageHandler().getJaxbElement(certificateInstallationRes.getContractSignatureCertChain())));
verifyXMLSigRefElements.put(
certificateInstallationRes.getContractSignatureEncryptedPrivateKey().getId(),
SecurityUtils.generateDigest(
certificateInstallationRes.getContractSignatureEncryptedPrivateKey().getId(),
getMessageHandler().getJaxbElement(certificateInstallationRes.getContractSignatureEncryptedPrivateKey())));
verifyXMLSigRefElements.put(
certificateInstallationRes.getDHpublickey().getId(),
SecurityUtils.generateDigest(
certificateInstallationRes.getDHpublickey().getId(),
getMessageHandler().getJaxbElement(certificateInstallationRes.getDHpublickey())));
verifyXMLSigRefElements.put(
certificateInstallationRes.getEMAID().getId(),
SecurityUtils.generateDigest(
certificateInstallationRes.getEMAID().getId(),
getMessageHandler().getJaxbElement(certificateInstallationRes.getEMAID())));
if (!SecurityUtils.verifySignature(
signature,
getMessageHandler().getJaxbElement(signature.getSignedInfo()),
verifyXMLSigRefElements,
certificateInstallationRes.getSAProvisioningCertificateChain().getCertificate())) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,124 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import java.security.interfaces.ECPrivateKey;
import java.util.HashMap;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.enumerations.PKI;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateUpdateResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SignatureType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForCertificateUpdateRes extends ClientState {
public WaitForCertificateUpdateRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, CertificateUpdateResType.class)) {
V2GMessage v2gMessageRes = (V2GMessage) message;
CertificateUpdateResType certificateUpdateRes =
(CertificateUpdateResType) v2gMessageRes.getBody().getBodyElement().getValue();
if (!verifySignature(certificateUpdateRes, v2gMessageRes.getHeader().getSignature())) {
return new TerminateSession("Signature verification failed");
}
// Check complete CPS certificate chain
ResponseCodeType certChainResponseCode = SecurityUtils.verifyCertificateChain(
certificateUpdateRes.getSAProvisioningCertificateChain(),
GlobalValues.EVCC_TRUSTSTORE_FILEPATH.toString(),
PKI.CPS);
if (!certChainResponseCode.equals(ResponseCodeType.OK)) {
return new TerminateSession("Provisioning certificate chain is not valid");
}
ECPrivateKey contractCertPrivateKey = SecurityUtils.getPrivateKey(
SecurityUtils.getKeyStore(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()),
GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString());
// Save contract certificate chain
if (!SecurityUtils.saveContractCertificateChain(
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString(),
certificateUpdateRes.getContractSignatureCertChain(),
SecurityUtils.decryptContractCertPrivateKey(
certificateUpdateRes.getDHpublickey().getValue(),
certificateUpdateRes.getContractSignatureEncryptedPrivateKey().getValue(),
contractCertPrivateKey))) {
return new TerminateSession("Contract certificate chain could not be saved");
}
return getSendMessage(getPaymentDetailsReq(), V2GMessages.PAYMENT_DETAILS_RES);
} else {
return new TerminateSession("Incoming message raised an error");
}
}
private boolean verifySignature(CertificateUpdateResType certificateUpdateRes, SignatureType signature) {
HashMap<String, byte[]> verifyXMLSigRefElements = new HashMap<String, byte[]>();
verifyXMLSigRefElements.put(
certificateUpdateRes.getContractSignatureCertChain().getId(),
SecurityUtils.generateDigest(
certificateUpdateRes.getContractSignatureCertChain().getId(),
getMessageHandler().getJaxbElement(certificateUpdateRes.getContractSignatureCertChain())));
verifyXMLSigRefElements.put(
certificateUpdateRes.getContractSignatureEncryptedPrivateKey().getId(),
SecurityUtils.generateDigest(
certificateUpdateRes.getContractSignatureEncryptedPrivateKey().getId(),
getMessageHandler().getJaxbElement(certificateUpdateRes.getContractSignatureEncryptedPrivateKey())));
verifyXMLSigRefElements.put(
certificateUpdateRes.getDHpublickey().getId(),
SecurityUtils.generateDigest(
certificateUpdateRes.getDHpublickey().getId(),
getMessageHandler().getJaxbElement(certificateUpdateRes.getDHpublickey())));
verifyXMLSigRefElements.put(
certificateUpdateRes.getEMAID().getId(),
SecurityUtils.generateDigest(
certificateUpdateRes.getEMAID().getId(),
getMessageHandler().getJaxbElement(certificateUpdateRes.getEMAID())));
if (!SecurityUtils.verifySignature(
signature,
getMessageHandler().getJaxbElement(signature.getSignedInfo()),
verifyXMLSigRefElements,
certificateUpdateRes.getSAProvisioningCertificateChain().getCertificate())) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,276 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.CPStates;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.misc.TimeRestrictions;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ACEVSEChargeParameterType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscoveryResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeProgressType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVSEChargeParameterType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSEProcessingType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SAScheduleListType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SAScheduleTupleType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SignatureType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForChargeParameterDiscoveryRes extends ClientState {
public WaitForChargeParameterDiscoveryRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, ChargeParameterDiscoveryResType.class)) {
V2GMessage v2gMessageRes = (V2GMessage) message;
ChargeParameterDiscoveryResType chargeParameterDiscoveryRes =
(ChargeParameterDiscoveryResType) v2gMessageRes.getBody().getBodyElement().getValue();
if (chargeParameterDiscoveryRes.getEVSEProcessing() == null)
return new TerminateSession("EVSEProcessing field of ChargeParameterDiscoveryRes is null. This field is mandatory.");
if (chargeParameterDiscoveryRes.getEVSEProcessing().equals(EVSEProcessingType.ONGOING)) {
getLogger().debug("EVSEProcessing was set to ONGOING");
long elapsedTimeInMs = 0;
if (getCommSessionContext().isOngoingTimerActive()) {
long elapsedTime = System.nanoTime() - getCommSessionContext().getOngoingTimer();
elapsedTimeInMs = TimeUnit.MILLISECONDS.convert(elapsedTime, TimeUnit.NANOSECONDS);
if (elapsedTimeInMs > TimeRestrictions.V2G_EVCC_ONGOING_TIMEOUT)
return new TerminateSession("Ongoing timer timed out for ChargeParameterDiscoveryReq");
} else {
getCommSessionContext().setOngoingTimer(System.nanoTime());
getCommSessionContext().setOngoingTimerActive(true);
}
return getSendMessage(getCommSessionContext().getChargeParameterDiscoveryReq(), V2GMessages.CHARGE_PARAMETER_DISCOVERY_RES, Math.min((TimeRestrictions.V2G_EVCC_ONGOING_TIMEOUT - (int) elapsedTimeInMs), TimeRestrictions.getV2gEvccMsgTimeout(V2GMessages.CHARGE_PARAMETER_DISCOVERY_RES)));
} else {
getLogger().debug("EVSEProcessing was set to FINISHED");
getCommSessionContext().setOngoingTimer(0L);
getCommSessionContext().setOngoingTimerActive(false);
// Check for the EVSENotification
EVSENotificationType evseNotification = null;
try {
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC"))
evseNotification = ((ACEVSEChargeParameterType) chargeParameterDiscoveryRes
.getEVSEChargeParameter().getValue())
.getACEVSEStatus().getEVSENotification();
else
evseNotification = ((DCEVSEChargeParameterType) chargeParameterDiscoveryRes
.getEVSEChargeParameter().getValue())
.getDCEVSEStatus().getEVSENotification();
} catch (ClassCastException e) {
return new TerminateSession("Sent EVSEChargeParameter do not match requested energy transfer mode " +
getCommSessionContext().getRequestedEnergyTransferMode().toString());
}
if (evseNotification.equals(EVSENotificationType.STOP_CHARGING)) {
getLogger().debug("The EVSE requested to stop the charging process");
getCommSessionContext().setChargingSession(ChargingSessionType.TERMINATE);
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.STOP), V2GMessages.POWER_DELIVERY_RES);
} else {
/*
* The case RE_NEGOTIATION is to be ignored according to [V2G2-841] and [V2G2-680].
* An SECC triggered renegotiation is only to be reacted on in the messages
* - ChargingStatusRes
* - MeteringReceiptRes
* - CurrentDemandRes
*/
if (chargeParameterDiscoveryRes.getSASchedules() == null)
return new TerminateSession("SASchedules field of ChargeParameterDiscoveryRes is null although EVSEProcessing is set to FINISHED. SASchedules is mandatory in this case.");
SAScheduleListType saSchedules = (SAScheduleListType) chargeParameterDiscoveryRes.getSASchedules().getValue();
// If TLS is used, verify each sales tariff (if present) with the mobility operator sub 2 certificate
if (getCommSessionContext().isTlsConnection() && saSchedules != null) {
if (!verifySalesTariffs(saSchedules, v2gMessageRes.getHeader().getSignature())) {
getLogger().warn("The SalesTariff will be ignored for the charge process due to "
+ "failed signature verification during TLS communication.");
deleteUnverifiedSalesTariffs(saSchedules);
}
}
// Save the list of SASchedules (saves the time of reception as well)
getCommSessionContext().setSaSchedules(saSchedules);
/*
* The following states are possible (and will not raise the termination of a charging session):
* - State B:
* - In AC charging, when exchanging the first ChargeParameterDiscoveryReq/Res message pair, before the charging loop
* was initiated
* - State C:
* - In DC charging, when exchanging the first ChargeParameterDiscoveryReq/Res message pair, before the charging loop
* was initiated
* - In AC charging, after the charging loop was initiated and a renegotiation was triggered
*/
if (getCommSessionContext().getEvController().getCPState().equals(CPStates.STATE_B) ||
(getCommSessionContext().getEvController().getCPState().equals(CPStates.STATE_C) &&
getCommSessionContext().isRenegotiationRequested())) {
// We need to reset the renegotiation trigger (in case of State C and a renegotiation was triggered)
getCommSessionContext().setRenegotiationRequested(false);
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC")) {
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.START), V2GMessages.POWER_DELIVERY_RES);
} else if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("DC")) {
// CP state C signaling BEFORE sending CableCheckReq message in DC
if (getCommSessionContext().getEvController().setCPState(CPStates.STATE_C)) {
// Set timer for CableCheck
getCommSessionContext().setOngoingTimer(System.nanoTime());
getCommSessionContext().setOngoingTimerActive(true);
return getSendMessage(getCableCheckReq(), V2GMessages.CABLE_CHECK_RES);
} else
return new TerminateSession("CP state C not ready (current state = " +
getCommSessionContext().getEvController().getCPState() +
")");
} else {
return new TerminateSession("RequestedEnergyTransferMode '" + getCommSessionContext().getRequestedEnergyTransferMode().toString() +
"is neither of type AC nor DC");
}
} else {
return new TerminateSession("CP state B not ready (current state = " +
getCommSessionContext().getEvController().getCPState() +
")");
}
}
}
} else {
return new TerminateSession("Incoming message raised an error");
}
}
/**
* Verifies each sales tariff given with the ChargeParameterDiscoveryRes message with the
* mobility operator sub 2 certificate.
*
* @param saSchedules The SASchedule list which holds all PMaxSchedules and SalesTariffs
* @param signature The signature for the sales tariffs
* @return True, if the verification of the sales tariffs was successful, false otherwise
*/
private boolean verifySalesTariffs(SAScheduleListType saSchedules, SignatureType signature) {
/*
* Some important requirements:
*
* 1. In case of PnC, and if a SalesTariff is used by the secondary actor, the secondary actor SHALL
* sign the field SalesTariff of type SalesTariffType. In case of EIM, the secondary actor MAY sign
* this field.
*
* 2. If the EVCC treats the SalesTariff as invalid, it shall ignore the SalesTariff, i.e. the
* behavior of the EVCC shall be the same as if no SalesTariff was received. Furthermore, the
* EVCC MAY close the connection. It then may reopen the connection again.
*/
boolean salesTariffSignatureAvailable = (signature == null) ? false : true;
boolean ignoreSalesTariffs = (getCommSessionContext().isTlsConnection() && !salesTariffSignatureAvailable) ? true : false;
short ignoredSalesTariffs = 0;
HashMap<String, byte[]> verifyXMLSigRefElements = new HashMap<String, byte[]>();
List<SAScheduleTupleType> saScheduleTuples = saSchedules.getSAScheduleTuple();
int salesTariffCounter = 0;
for (SAScheduleTupleType saScheduleTuple : saScheduleTuples) {
// verification regards only sales tariffs, not PMaxSchedules
if (saScheduleTuple.getSalesTariff() == null) continue;
// Check if signature is given during TLS communication. If no signature is given, delete SalesTariff
if (ignoreSalesTariffs) {
ignoredSalesTariffs++;
saScheduleTuple.setSalesTariff(null);
continue;
}
salesTariffCounter++;
verifyXMLSigRefElements.put(
saScheduleTuple.getSalesTariff().getId(),
SecurityUtils.generateDigest(
saScheduleTuple.getSalesTariff().getId(),
getMessageHandler().getJaxbElement(saScheduleTuple.getSalesTariff())));
}
if (salesTariffCounter > 0) {
X509Certificate moSubCA2Certificate = SecurityUtils.getMOSubCA2Certificate(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString());
if (moSubCA2Certificate == null) {
getLogger().error("No MOSubCA2 certificate found, signature of SalesTariff could therefore not be verified");
return false;
} else {
if (!SecurityUtils.verifySignature(
signature,
getMessageHandler().getJaxbElement(signature.getSignedInfo()),
verifyXMLSigRefElements,
moSubCA2Certificate)) {
getLogger().warn("Verification of SalesTariff failed using certificate with distinguished name '" +
moSubCA2Certificate.getSubjectX500Principal().getName() + "'");
return false;
}
}
}
if (ignoredSalesTariffs > 0) {
getLogger().info("SalesTariffs could not be verified because of missing signature and will therefore be ignored");
return false;
}
return true;
}
/**
* If the signature of one ore more sales tariffs cannot be verified, then the sales tariffs should be ignored
* rather than terminating the charge process. The charge process can then proceed based solely on the
* PMaxSchedule
*
* @param saSchedules The schedule(s) from the secondary actor including PMaxSchedule and potential SalesTariff
* elements.
*/
private void deleteUnverifiedSalesTariffs(SAScheduleListType saSchedules) {
List<SAScheduleTupleType> saScheduleTuples = saSchedules.getSAScheduleTuple();
for (SAScheduleTupleType saScheduleTuple : saScheduleTuples) {
saScheduleTuple.setSalesTariff(null);
}
}
}

View File

@@ -0,0 +1,138 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import com.v2gclarity.risev2g.evcc.evController.IACEVController;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeProgressType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingStatusReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingStatusResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.MeteringReceiptReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForChargingStatusRes extends ClientState {
public WaitForChargingStatusRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, ChargingStatusResType.class)) {
V2GMessage v2gMessageRes = (V2GMessage) message;
ChargingStatusResType chargingStatusRes =
(ChargingStatusResType) v2gMessageRes.getBody().getBodyElement().getValue();
/*
* ReceiptRequired has higher priority than a possible EVSENotification=Renegotiate
*
* Check if communication is secured with TLS before reacting upon a possible request from the SECC to send
* a MeteringReceiptRequest. If no TLS is used, a MeteringReceiptRequest may not be sent because
* a signature cannot be applied without private key of the contract certificate.
*/
if (chargingStatusRes.isReceiptRequired() != null && chargingStatusRes.isReceiptRequired() && getCommSessionContext().isTlsConnection()) {
MeteringReceiptReqType meteringReceiptReq = new MeteringReceiptReqType();
/*
* Experience from the test symposium in San Diego (April 2016):
* The Id element of the signature is not restricted in size by the standard itself. But on embedded
* systems, the memory is very limited which is why we should not use long IDs for the signature reference
* element. A good size would be 3 characters max (like the example in the ISO 15118-2 annex J)
*/
meteringReceiptReq.setId("id1");
meteringReceiptReq.setMeterInfo(chargingStatusRes.getMeterInfo());
meteringReceiptReq.setSAScheduleTupleID(chargingStatusRes.getSAScheduleTupleID());
meteringReceiptReq.setSessionID(getCommSessionContext().getSessionID());
// Set xml reference element
getXMLSignatureRefElements().put(
meteringReceiptReq.getId(),
SecurityUtils.generateDigest(
meteringReceiptReq.getId(),
getMessageHandler().getJaxbElement(meteringReceiptReq)));
// Set signing private key
setSignaturePrivateKey(SecurityUtils.getPrivateKey(
SecurityUtils.getKeyStore(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()),
GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString())
);
return getSendMessage(meteringReceiptReq, V2GMessages.METERING_RECEIPT_RES);
}
// Check for EVSEMaxCurrent and tell the EV
if (chargingStatusRes.getEVSEMaxCurrent() != null)
((IACEVController) getCommSessionContext().getEvController())
.adjustMaxCurrent(chargingStatusRes.getEVSEMaxCurrent());
switch (chargingStatusRes.getACEVSEStatus().getEVSENotification()) {
case STOP_CHARGING:
getCommSessionContext().setChargingSession(ChargingSessionType.TERMINATE);
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.STOP),
V2GMessages.POWER_DELIVERY_RES,
" (ChargeProgress = STOP_CHARGING)");
case RE_NEGOTIATION:
getCommSessionContext().setRenegotiationRequested(true);
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.RENEGOTIATE),
V2GMessages.POWER_DELIVERY_RES,
" (ChargeProgress = RE_NEGOTIATION)");
default:
// TODO regard [V2G2-305] (new SalesTariff if EAmount not yet met and tariff finished)
if (getCommSessionContext().getEvController().isChargingLoopActive()) {
// Check whether or not the EV controller triggered a renegotiation
if (getCommSessionContext().isRenegotiationRequested()) {
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.RENEGOTIATE),
V2GMessages.POWER_DELIVERY_RES,
" (ChargeProgress = RE_NEGOTIATION)");
} else {
ChargingStatusReqType chargingStatusReq = new ChargingStatusReqType();
return getSendMessage(chargingStatusReq, V2GMessages.CHARGING_STATUS_RES);
}
} else {
/* Check if the EV controller triggered a pause of a charging session.
* If not, indicate a termination of the charging session. This will be
* evaluated in the state WaitForPowerDeliveryRes
*/
if (getCommSessionContext().getChargingSession() == null)
getCommSessionContext().setChargingSession(ChargingSessionType.TERMINATE);
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.STOP),
V2GMessages.POWER_DELIVERY_RES,
" (ChargeProgress = STOP_CHARGING)");
}
}
} else {
return new TerminateSession("Incoming message raised an error");
}
}
}

View File

@@ -0,0 +1,131 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeProgressType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingStatusReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CurrentDemandResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVSEStatusType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.MeteringReceiptReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForCurrentDemandRes extends ClientState {
public WaitForCurrentDemandRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, CurrentDemandResType.class)) {
V2GMessage v2gMessageRes = (V2GMessage) message;
CurrentDemandResType currentDemandRes =
(CurrentDemandResType) v2gMessageRes.getBody().getBodyElement().getValue();
// ReceiptRequired has higher priority than a possible EVSENotification=Renegotiate
if (currentDemandRes.isReceiptRequired() != null && currentDemandRes.isReceiptRequired() && getCommSessionContext().isTlsConnection()) {
MeteringReceiptReqType meteringReceiptReq = new MeteringReceiptReqType();
/*
* Experience from the test symposium in San Diego (April 2016):
* The Id element of the signature is not restricted in size by the standard itself. But on embedded
* systems, the memory is very limited which is why we should not use long IDs for the signature reference
* element. A good size would be 3 characters max (like the example in the ISO 15118-2 annex J)
*/
meteringReceiptReq.setId("id1");
meteringReceiptReq.setMeterInfo(currentDemandRes.getMeterInfo());
meteringReceiptReq.setSAScheduleTupleID(currentDemandRes.getSAScheduleTupleID());
meteringReceiptReq.setSessionID(getCommSessionContext().getSessionID());
// Set xml reference element
getXMLSignatureRefElements().put(
meteringReceiptReq.getId(),
SecurityUtils.generateDigest(
meteringReceiptReq.getId(),
getMessageHandler().getJaxbElement(meteringReceiptReq)));
// Set signing private key
setSignaturePrivateKey(SecurityUtils.getPrivateKey(
SecurityUtils.getKeyStore(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()),
GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString())
);
return getSendMessage(meteringReceiptReq, V2GMessages.METERING_RECEIPT_RES);
}
// TODO check for the other parameters in the currentDemandRes and react accordingly
DCEVSEStatusType dcEVSEStatus = currentDemandRes.getDCEVSEStatus();
switch ((EVSENotificationType) dcEVSEStatus.getEVSENotification()) {
case STOP_CHARGING:
getCommSessionContext().setChargingSession(ChargingSessionType.TERMINATE);
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.STOP),
V2GMessages.POWER_DELIVERY_RES,
" (ChargeProgress = STOP_CHARGING)");
case RE_NEGOTIATION:
getCommSessionContext().setRenegotiationRequested(true);
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.RENEGOTIATE),
V2GMessages.POWER_DELIVERY_RES,
" (ChargeProgress = RE_NEGOTIATION)");
default:
// TODO regard [V2G2-305] (new SalesTariff if EAmount not yet met and tariff finished)
if (getCommSessionContext().getEvController().isChargingLoopActive()) {
// Check whether or not the EV controller triggered a renegotiation
if (getCommSessionContext().isRenegotiationRequested()) {
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.RENEGOTIATE),
V2GMessages.POWER_DELIVERY_RES,
" (ChargeProgress = RE_NEGOTIATION)");
} else {
return getSendMessage(getCurrentDemandReq(), V2GMessages.CURRENT_DEMAND_RES);
}
} else {
/* Check if the EV controller triggered a pause of a charging session.
* If not, indicate a termination of the charging session. This will be
* evaluated in the state WaitForPowerDeliveryRes
*/
if (getCommSessionContext().getChargingSession() == null)
getCommSessionContext().setChargingSession(ChargingSessionType.TERMINATE);
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.STOP),
V2GMessages.POWER_DELIVERY_RES,
" (ChargeProgress = STOP_CHARGING)");
}
}
} else {
return new TerminateSession("Incoming message raised an error");
}
}
}

View File

@@ -0,0 +1,128 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ACEVSEStatusType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeProgressType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingStatusReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVSEStatusType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.MeteringReceiptResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForMeteringReceiptRes extends ClientState {
private boolean acCharging;
public WaitForMeteringReceiptRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, MeteringReceiptResType.class)) {
V2GMessage v2gMessageRes = (V2GMessage) message;
MeteringReceiptResType meteringReceiptRes =
(MeteringReceiptResType) v2gMessageRes.getBody().getBodyElement().getValue();
EVSENotificationType evseNotification = null;
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC")) {
setAcCharging(true);
evseNotification = ((ACEVSEStatusType) meteringReceiptRes.getEVSEStatus().getValue()).getEVSENotification();
} else if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("DC")) {
setAcCharging(false);
evseNotification = ((DCEVSEStatusType) meteringReceiptRes.getEVSEStatus().getValue()).getEVSENotification();
} else {
return new TerminateSession("RequestedEnergyTransferMode '" + getCommSessionContext().getRequestedEnergyTransferMode().toString() +
"is neither of type AC nor DC");
}
switch (evseNotification) {
case STOP_CHARGING:
getCommSessionContext().setChargingSession(ChargingSessionType.TERMINATE);
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.STOP),
V2GMessages.POWER_DELIVERY_RES,
" (ChargeProgress = STOP_CHARGING)");
case RE_NEGOTIATION:
getCommSessionContext().setRenegotiationRequested(true);
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.RENEGOTIATE),
V2GMessages.POWER_DELIVERY_RES,
" (ChargeProgress = RE_NEGOTIATION)");
default:
// TODO regard [V2G2-305] (new SalesTariff if EAmount not yet met and tariff finished)
if (isAcCharging() && getCommSessionContext().getEvController().isChargingLoopActive()) {
// Check whether or not the EV controller triggered a renegotiation
if (getCommSessionContext().isRenegotiationRequested()) {
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.RENEGOTIATE),
V2GMessages.POWER_DELIVERY_RES,
" (ChargeProgress = RE_NEGOTIATION)");
} else {
ChargingStatusReqType chargingStatusReq = new ChargingStatusReqType();
return getSendMessage(chargingStatusReq, V2GMessages.CHARGING_STATUS_RES);
}
} else if (getCommSessionContext().getEvController().isChargingLoopActive()) {
// Check whether or not the EV controller triggered a renegotiation
if (getCommSessionContext().isRenegotiationRequested()) {
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.RENEGOTIATE),
V2GMessages.POWER_DELIVERY_RES,
" (ChargeProgress = RE_NEGOTIATION)");
} else {
return getSendMessage(getCurrentDemandReq(), V2GMessages.CURRENT_DEMAND_RES);
}
} else {
/* Check if the EV controller triggered a pause of a charging session.
* If not, indicate a termination of the charging session. This will be
* evaluated in the state WaitForPowerDeliveryRes
*/
if (getCommSessionContext().getChargingSession() == null)
getCommSessionContext().setChargingSession(ChargingSessionType.TERMINATE);
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.STOP),
V2GMessages.POWER_DELIVERY_RES,
" (ChargeProgress = STOP_CHARGING)");
}
}
} else {
return new TerminateSession("Incoming message raised an error");
}
}
private boolean isAcCharging() {
return acCharging;
}
private void setAcCharging(boolean acCharging) {
this.acCharging = acCharging;
}
}

View File

@@ -0,0 +1,84 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.AuthorizationReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentDetailsResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForPaymentDetailsRes extends ClientState {
public WaitForPaymentDetailsRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, PaymentDetailsResType.class)) {
V2GMessage v2gMessageRes = (V2GMessage) message;
PaymentDetailsResType paymentDetailsRes =
(PaymentDetailsResType) v2gMessageRes.getBody().getBodyElement().getValue();
/*
* A reaction on the response code OK_CERTIFICATE_EXPIRES_SOON is not needed as this check
* is already done by EVCC itself before deciding to send CertificateUpdateReq/CertificateInstallationReq
*/
if (paymentDetailsRes.getGenChallenge() == null) {
return new TerminateSession("GenChallenge not provided in PaymentDetailsRes");
} else {
// Save the sent genChallenge for the state WaitForAuthorizationRes if EVSEProcessing is set to ONGOING
getCommSessionContext().setSentGenChallenge(paymentDetailsRes.getGenChallenge());
AuthorizationReqType authorizationReq = getAuthorizationReq(paymentDetailsRes.getGenChallenge());
// Set xml reference element
getXMLSignatureRefElements().put(
authorizationReq.getId(),
SecurityUtils.generateDigest(
authorizationReq.getId(),
getMessageHandler().getJaxbElement(authorizationReq)));
// Set signing private key
setSignaturePrivateKey(SecurityUtils.getPrivateKey(
SecurityUtils.getKeyStore(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()),
GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString())
);
return getSendMessage(authorizationReq, V2GMessages.AUTHORIZATION_RES);
}
} else {
return new TerminateSession("Incoming message raised an error");
}
}
}

View File

@@ -0,0 +1,161 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import java.security.KeyStore;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils.ContractCertificateStatus;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateInstallationReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateUpdateReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentServiceSelectionResType;
public class WaitForPaymentServiceSelectionRes extends ClientState {
public WaitForPaymentServiceSelectionRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, PaymentServiceSelectionResType.class)) {
if (getCommSessionContext().getSelectedPaymentOption().equals(PaymentOptionType.CONTRACT)) {
if (getCommSessionContext().getContractCertStatus().equals(ContractCertificateStatus.UNKNOWN)) {
getCommSessionContext().setContractCertStatus(SecurityUtils.getContractCertificateStatus());
}
// 1. Check if certificate installation is needed
if (getCommSessionContext().getContractCertStatus().equals(ContractCertificateStatus.INSTALLATION_NEEDED)) {
if (getCommSessionContext().isCertificateServiceAvailable((short) 1)) {
getLogger().info("Trying to install new contract certificate");
return getSendMessage(getCertificateInstallationReq(), V2GMessages.CERTIFICATE_INSTALLATION_RES);
} else return new TerminateSession("Certificate installation needed but service is not available");
}
// 2. Check if certificate update is needed (means: certificate is available but expires soon)
if (getCommSessionContext().getContractCertStatus().equals(ContractCertificateStatus.UPDATE_NEEDED)) {
if (getCommSessionContext().isCertificateServiceAvailable((short) 2)) {
getLogger().info("Trying to update contract certificate");
return getSendMessage(getCertificateUpdateReq(), V2GMessages.CERTIFICATE_UPDATE_RES);
} else return new TerminateSession("Certificate update needed but service is not available");
}
return getSendMessage(getPaymentDetailsReq(), V2GMessages.PAYMENT_DETAILS_RES);
} else if (getCommSessionContext().getSelectedPaymentOption().equals(PaymentOptionType.EXTERNAL_PAYMENT)) {
return getSendMessage(getAuthorizationReq(null), V2GMessages.AUTHORIZATION_RES);
} else {
return new TerminateSession("No valid PaymentOptionType available");
}
} else {
return new TerminateSession("Incoming message raised an error");
}
}
private CertificateInstallationReqType getCertificateInstallationReq() {
KeyStore evccKeyStore = SecurityUtils.getKeyStore(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
CertificateInstallationReqType certInstallationReq = new CertificateInstallationReqType();
/*
* Experience from the test symposium in San Diego (April 2016):
* The Id element of the signature is not restricted in size by the standard itself. But on embedded
* systems, the memory is very limited which is why we should not use long IDs for the signature reference
* element. A good size would be 3 characters max (like the example in the ISO 15118-2 annex J)
*/
certInstallationReq.setId("ID1");
certInstallationReq.setListOfRootCertificateIDs(
SecurityUtils.getListOfRootCertificateIDs(
GlobalValues.EVCC_TRUSTSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()));
certInstallationReq.setOEMProvisioningCert(
SecurityUtils.getCertificateChain(
evccKeyStore, GlobalValues.ALIAS_OEM_PROV_CERTIFICATE.toString()).getCertificate());
// Set xml reference element
getXMLSignatureRefElements().put(
certInstallationReq.getId(),
SecurityUtils.generateDigest(
certInstallationReq.getId(),
getMessageHandler().getJaxbElement(certInstallationReq)));
// Set signing private key
setSignaturePrivateKey(SecurityUtils.getPrivateKey(
SecurityUtils.getKeyStore(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()),
GlobalValues.ALIAS_OEM_PROV_CERTIFICATE.toString())
);
return certInstallationReq;
}
private CertificateUpdateReqType getCertificateUpdateReq() {
CertificateUpdateReqType certificateUpdateReq = new CertificateUpdateReqType();
certificateUpdateReq.setContractSignatureCertChain(
SecurityUtils.getCertificateChain(
SecurityUtils.getKeyStore(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()),
GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString()));
certificateUpdateReq.setEMAID(SecurityUtils.getEMAID(GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()).getValue());
/*
* Experience from the test symposium in San Diego (April 2016):
* The Id element of the signature is not restricted in size by the standard itself. But on embedded
* systems, the memory is very limited which is why we should not use long IDs for the signature reference
* element. A good size would be 3 characters max (like the example in the ISO 15118-2 annex J)
*/
certificateUpdateReq.setId("ID1");
certificateUpdateReq.setListOfRootCertificateIDs(
SecurityUtils.getListOfRootCertificateIDs(
GlobalValues.EVCC_TRUSTSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()));
// Set xml reference element
getXMLSignatureRefElements().put(
certificateUpdateReq.getId(),
SecurityUtils.generateDigest(
certificateUpdateReq.getId(),
getMessageHandler().getJaxbElement(certificateUpdateReq)));
// Set signing private key
setSignaturePrivateKey(SecurityUtils.getPrivateKey(
SecurityUtils.getKeyStore(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()),
GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString())
);
return certificateUpdateReq;
}
}

View File

@@ -0,0 +1,118 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import com.v2gclarity.risev2g.evcc.evController.IDCEVController;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.CPStates;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscoveryReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingStatusReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVStatusType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PowerDeliveryResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.WeldingDetectionReqType;
public class WaitForPowerDeliveryRes extends ClientState {
public WaitForPowerDeliveryRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, PowerDeliveryResType.class)) {
PowerDeliveryResType powerDeliveryRes =
(PowerDeliveryResType) ((V2GMessage) message).getBody().getBodyElement().getValue();
/*
* TODO clarification needed on how to react on EVSENotification
*
* EVSENotification=RE_NEGOATION is ignored, because an SECC triggered renegotiation is only
* to be reacted on in the messages
* - ChargingStatusRes
* - MeteringReceiptRes
* - CurrentDemandRes
*
* But how to react on EVSENotification=STOP?
*/
if (getCommSessionContext().isRenegotiationRequested()) {
// In DC charging, we need to switch to state B during renegotiation because we need to go through CableCheckReq and PreChargeReq again for which state B is required
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("DC")) {
getCommSessionContext().setChangeToState(CPStates.STATE_B);
}
ChargeParameterDiscoveryReqType chargeParameterDiscoveryReq = getChargeParameterDiscoveryReq();
/*
* Save this request in case the ChargeParameterDiscoveryRes indicates that the EVSE is
* still processing. Then this request can just be resent instead of asking the EV again.
*/
getCommSessionContext().setChargeParameterDiscoveryReq(chargeParameterDiscoveryReq);
return getSendMessage(chargeParameterDiscoveryReq, V2GMessages.CHARGE_PARAMETER_DISCOVERY_RES);
} else if (getCommSessionContext().getChargingSession() != null &&
getCommSessionContext().getChargingSession() == ChargingSessionType.TERMINATE) {
return getSendMessage(ChargingSessionType.TERMINATE);
} else if (getCommSessionContext().getChargingSession() != null &&
getCommSessionContext().getChargingSession() == ChargingSessionType.PAUSE) {
return getSendMessage(ChargingSessionType.PAUSE);
} else {
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC")) {
ChargingStatusReqType chargingStatusReq = new ChargingStatusReqType();
return getSendMessage(chargingStatusReq, V2GMessages.CHARGING_STATUS_RES);
} else {
return getSendMessage(getCurrentDemandReq(), V2GMessages.CURRENT_DEMAND_RES);
}
}
} else {
return new TerminateSession("Incoming message raised an error");
}
}
private ReactionToIncomingMessage getSendMessage(ChargingSessionType chargingSessionType) {
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("DC")) {
// CP state B signaling BEFORE sending WeldingDetectionReq message in DC
if (getCommSessionContext().getEvController().setCPState(CPStates.STATE_B)) {
WeldingDetectionReqType weldingDetectionReq = new WeldingDetectionReqType();
DCEVStatusType dcEVStatus = ((IDCEVController) getCommSessionContext().getEvController()).getDCEVStatus();
weldingDetectionReq.setDCEVStatus(dcEVStatus);
return getSendMessage(weldingDetectionReq, V2GMessages.WELDING_DETECTION_RES);
} else {
return new TerminateSession("CP state B not ready (current state = " +
getCommSessionContext().getEvController().getCPState() +
")");
}
} else {
return getSendMessage(getSessionStopReq(chargingSessionType),
V2GMessages.SESSION_STOP_RES, "(ChargingSession = " +
chargingSessionType.toString() + ")");
}
}
}

View File

@@ -0,0 +1,87 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import java.util.concurrent.TimeUnit;
import com.v2gclarity.risev2g.evcc.evController.IDCEVController;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.misc.TimeRestrictions;
import com.v2gclarity.risev2g.shared.utils.MiscUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeProgressType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PreChargeReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PreChargeResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForPreChargeRes extends ClientState {
public WaitForPreChargeRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, PreChargeResType.class)) {
V2GMessage v2gMessageRes = (V2GMessage) message;
PreChargeResType preChargeRes =
(PreChargeResType) v2gMessageRes.getBody().getBodyElement().getValue();
// TODO how to react to DC_EVSEStatus
IDCEVController dcEvController = (IDCEVController) getCommSessionContext().getEvController();
double targetVoltage = dcEvController.getTargetVoltage().getValue() * Math.pow(10, dcEvController.getTargetVoltage().getMultiplier());
double presentVoltage = preChargeRes.getEVSEPresentVoltage().getValue() * Math.pow(10, preChargeRes.getEVSEPresentVoltage().getMultiplier());
// Each EV has an EV-specific allowed deviation when measuring a target voltage
int voltageAccuracy = (int) MiscUtils.getPropertyValue("voltage.accuracy");
if (presentVoltage >= targetVoltage * (1 - voltageAccuracy / 100) && presentVoltage <= targetVoltage * (1 + voltageAccuracy / 100)) {
getCommSessionContext().setOngoingTimerActive(false);
getCommSessionContext().setOngoingTimer(0L);
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.START), V2GMessages.POWER_DELIVERY_RES);
} else {
long elapsedTime = System.nanoTime() - getCommSessionContext().getOngoingTimer();
long elapsedTimeInMs = TimeUnit.MILLISECONDS.convert(elapsedTime, TimeUnit.NANOSECONDS);
if (elapsedTimeInMs > TimeRestrictions.V2G_EVCC_PRE_CHARGE_TIMEOUT)
return new TerminateSession("PreCharge timer timed out for PreChargeReq");
else {
getLogger().debug("Target voltage of " + targetVoltage + " V not yet reached. Present voltage at EVSE is " + presentVoltage);
PreChargeReqType preChargeReq = new PreChargeReqType();
preChargeReq.setDCEVStatus(dcEvController.getDCEVStatus());
preChargeReq.setEVTargetCurrent(dcEvController.getTargetCurrent());
preChargeReq.setEVTargetVoltage(dcEvController.getTargetVoltage());
return getSendMessage(preChargeReq, V2GMessages.PRE_CHARGE_RES, Math.min((TimeRestrictions.V2G_EVCC_PRE_CHARGE_TIMEOUT - (int) elapsedTimeInMs), TimeRestrictions.getV2gEvccMsgTimeout(V2GMessages.PRE_CHARGE_RES)));
}
}
} else {
return new TerminateSession("Incoming message raised an error");
}
}
}

View File

@@ -0,0 +1,61 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceDetailReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceDetailResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForServiceDetailRes extends ClientState {
public WaitForServiceDetailRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, ServiceDetailResType.class)) {
ServiceDetailResType serviceDetailRes =
(ServiceDetailResType) ((V2GMessage) message).getBody().getBodyElement().getValue();
/*
* Currently there seems to be no need to check the service details, the parameterSets are clearly
* defined in Table 105 of ISO/IEC 15118. If no negative response code was received for the
* requested details of a serviceID, then the EVCC as well as SECC should offer the same parameterSets
*/
ServiceDetailReqType serviceDetailReq = getServiceDetailReq();
if (serviceDetailReq != null)
return getSendMessage(serviceDetailReq, V2GMessages.SERVICE_DETAIL_RES);
else
return getSendMessage(getPaymentServiceSelectionReq(), V2GMessages.PAYMENT_SERVICE_SELECTION_RES);
} else {
return new TerminateSession("Incoming message raised an error");
}
}
}

View File

@@ -0,0 +1,196 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.evcc.transportLayer.TLSClient;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.utils.MiscUtils;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils.ContractCertificateStatus;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EnergyTransferModeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionListType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SelectedServiceType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceCategoryType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceDiscoveryResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceListType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForServiceDiscoveryRes extends ClientState {
public WaitForServiceDiscoveryRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, ServiceDiscoveryResType.class)) {
ServiceDiscoveryResType serviceDiscoveryRes =
(ServiceDiscoveryResType) ((V2GMessage) message).getBody().getBodyElement().getValue();
getCommSessionContext().getSelectedServices().getSelectedService().clear(); // just to be sure
/*
* For every service whose details need to be requested, a ServiceDetailReq message must be sent.
* First by WaitForServiceDiscoveryRes, then by WaitForServiceDetailRes. In order to keep track of
* which service details still need to be requested, we use this helper list.
*/
getCommSessionContext().getServiceDetailsToBeRequested().clear(); // just to be sure
// Save the list containing information on all other services than charging services offered by the charging station
getCommSessionContext().setOfferedServices(serviceDiscoveryRes.getServiceList());
if (serviceDiscoveryRes.getChargeService() != null) {
// Check if requested energy transfer mode is supported
EnergyTransferModeType requestedEnergyTransferMode = getRequestedEnergyTransferMode();
if (serviceDiscoveryRes.getChargeService().getSupportedEnergyTransferMode()
.getEnergyTransferMode().contains(requestedEnergyTransferMode)) {
getCommSessionContext().getOfferedServices().getService().add(serviceDiscoveryRes.getChargeService());
addSelectedService(1, null); // Assumption: a charge service is always used
} else {
return new TerminateSession("Offered EnergyTransferModes not compatible with the requested one, which is " + requestedEnergyTransferMode.toString());
}
} else return new TerminateSession("No charge service available");
getCommSessionContext().setSelectedPaymentOption(getSelectedPaymentOption(serviceDiscoveryRes.getPaymentOptionList()));
// Check for the usage of value added services (VAS)
if (useVAS(serviceDiscoveryRes)) {
return getSendMessage(getServiceDetailReq(), V2GMessages.SERVICE_DETAIL_RES);
} else {
return getSendMessage(getPaymentServiceSelectionReq(), V2GMessages.PAYMENT_SERVICE_SELECTION_RES);
}
} else {
return new TerminateSession("Incoming message raised an error");
}
}
protected PaymentOptionType getSelectedPaymentOption(PaymentOptionListType authenticationOptions) {
/*
* Note that although the type is called "PaymentOptionListType", it's not a list of payment options, but authorization options,
* namely either Plug & Charge ("Contract") or external identification means (EIM) like an RFID card ("ExternalPayment"). This is
* why the parameter for this function is called "authenticationOptions" for clarity.
*/
PaymentOptionType selectedPaymentOption = null;
// Check if a PaymentOptionType has been requested in a previously paused session
if (getCommSessionContext().isOldSessionJoined())
selectedPaymentOption = (PaymentOptionType) MiscUtils.getPropertyValue("authentication.mode");
if (selectedPaymentOption == null)
selectedPaymentOption = getCommSessionContext().getEvController().getPaymentOption();
// Contract payment option may only be chosen if offered by SECC AND if communication is secured by TLS
if (selectedPaymentOption.equals(PaymentOptionType.CONTRACT) &&
authenticationOptions.getPaymentOption().contains(PaymentOptionType.CONTRACT)) {
if (!getCommSessionContext().isTlsConnection()) {
getLogger().warn("SECC offered 'Contract' based payment although no TLS connectionis used. Choosing 'ExternalPayment' instead");
getCommSessionContext().setSelectedPaymentOption(PaymentOptionType.EXTERNAL_PAYMENT);
return PaymentOptionType.EXTERNAL_PAYMENT;
} else {
getCommSessionContext().setSelectedPaymentOption(PaymentOptionType.CONTRACT);
return PaymentOptionType.CONTRACT;
}
} else {
getCommSessionContext().setSelectedPaymentOption(PaymentOptionType.EXTERNAL_PAYMENT);
return PaymentOptionType.EXTERNAL_PAYMENT;
}
}
/**
* According to [V2G2-422] a ServiceDetailsReq is needed in case VAS (value added services)
* such as certificate installation/update are to be used and offered by the SECC.
* Furthermore, it must be checked if VAS are allowed (-> only if TLS connection is used)
*/
private boolean useVAS(ServiceDiscoveryResType serviceDiscoveryRes) {
if (serviceDiscoveryRes.getServiceList() != null &&
getCommSessionContext().getTransportLayerClient() instanceof TLSClient) {
getLogger().debug("List of offered value added services: ");
for (ServiceType service : serviceDiscoveryRes.getServiceList().getService()) {
getLogger().debug("ID = " + service.getServiceID() + ", name = " + service.getServiceName());
}
// Check if certificate service is needed
if (isCertificateServiceOffered(serviceDiscoveryRes.getServiceList())) {
getCommSessionContext().setContractCertStatus(SecurityUtils.getContractCertificateStatus());
if (getCommSessionContext().getContractCertStatus().equals(ContractCertificateStatus.INSTALLATION_NEEDED))
addSelectedService(2, (short) 1);
else if (getCommSessionContext().getContractCertStatus().equals(ContractCertificateStatus.UPDATE_NEEDED))
addSelectedService(2, (short) 2);
}
// Optionally, other value added services can be checked for here ...
} else return false;
return (getCommSessionContext().getServiceDetailsToBeRequested().size() > 0) ? true : false;
}
private void addSelectedService(int serviceID, Short parameterSetID) {
/*
* The SelectedServiceType holds an optional parameter for parameterSetID. This parameterSetID
* will be retrieved later by the ServiceDetailRes. However, in case of certificate installation/update
* a check for the needed parameterSetID is already done at this state (and the values are defined
* by Table 105 in ISO/IEC 15118) which is why we already save the parameterSetID for certificate here.
*/
SelectedServiceType selectedService = new SelectedServiceType();
selectedService.setServiceID(serviceID);
selectedService.setParameterSetID(parameterSetID);
getCommSessionContext().getSelectedServices().getSelectedService().add(selectedService);
/*
* There are no service details (a ServiceParameterList) yet defined for a charge service,
* so don't send a ServiceDetailReq for a ChargeService
*/
if (serviceID != 1)
getCommSessionContext().getServiceDetailsToBeRequested().add(serviceID);
}
private boolean isCertificateServiceOffered(ServiceListType offeredServiceList) {
if (offeredServiceList == null) {
getLogger().debug("No value added services offered by EVCC");
return false;
}
for (ServiceType service : offeredServiceList.getService()) {
if (service.getServiceCategory().equals(ServiceCategoryType.CONTRACT_CERTIFICATE))
return true;
}
return false;
}
}

View File

@@ -0,0 +1,89 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.utils.ByteUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceDiscoveryReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SessionSetupResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForSessionSetupRes extends ClientState {
public WaitForSessionSetupRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, SessionSetupResType.class)) {
byte[] receivedSessionID = ((V2GMessage) message).getHeader().getSessionID();
SessionSetupResType sessionSetupRes =
(SessionSetupResType) ((V2GMessage) message).getBody().getBodyElement().getValue();
if (sessionSetupRes.getResponseCode().equals(ResponseCodeType.OK_NEW_SESSION_ESTABLISHED)) {
getLogger().debug("Negotiated session ID is " + ByteUtils.toHexString(receivedSessionID));
getCommSessionContext().setOldSessionJoined(false);
} else if (sessionSetupRes.getResponseCode().equals(ResponseCodeType.OK_OLD_SESSION_JOINED)) {
getLogger().debug("Previous charging session joined (session ID = " + ByteUtils.toHexString(receivedSessionID) + ")");
/*
* Mark that the old session was joined in order to resend
* - SelectedPaymentOption and
* - RequestedEnergyTransferMode
* according to 8.4.2. Those values should be persisted in the properties file.
*/
getCommSessionContext().setOldSessionJoined(true);
} else {
getCommSessionContext().setOldSessionJoined(false);
getLogger().error("No negative response code received, but positive response code '" +
sessionSetupRes.getResponseCode().toString() + "' is " +
"neither OK_NEW_SESSION_ESTABLISHED nor OK_OLD_SESSION_JOINED");
return new TerminateSession("Positive response code invalid in state WaitForSessionSetupRes");
}
getCommSessionContext().setSessionID(receivedSessionID);
getCommSessionContext().setEvseID(sessionSetupRes.getEVSEID());
// EVSETimeStamp is optional
if (sessionSetupRes.getEVSETimeStamp() != null)
getCommSessionContext().setEvseTimeStamp(sessionSetupRes.getEVSETimeStamp());
ServiceDiscoveryReqType serviceDiscoveryReq = new ServiceDiscoveryReqType();
/*
* If it is desired to restrict the services to a certain scope and/or category (optional),
* then this is the place to do it.
*/
return getSendMessage(serviceDiscoveryReq, V2GMessages.SERVICE_DISCOVERY_RES);
} else {
getCommSessionContext().setOldSessionJoined(false);
return new TerminateSession("Incoming message raised an error");
}
}
}

View File

@@ -0,0 +1,51 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.messageHandling.PauseSession;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SessionStopResType;
public class WaitForSessionStopRes extends ClientState {
public WaitForSessionStopRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, SessionStopResType.class)) {
if (getCommSessionContext().getChargingSession() != null &&
getCommSessionContext().getChargingSession().equals(ChargingSessionType.PAUSE))
return new PauseSession();
else
return new TerminateSession("V2G communication session will be stopped successfully", true);
} else {
return new TerminateSession("Incoming message raised an error");
}
}
}

View File

@@ -0,0 +1,112 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import java.util.concurrent.TimeUnit;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.misc.TimeRestrictions;
import com.v2gclarity.risev2g.shared.utils.ByteUtils;
import com.v2gclarity.risev2g.shared.utils.MiscUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.appProtocol.AppProtocolType;
import com.v2gclarity.risev2g.shared.v2gMessages.appProtocol.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.appProtocol.SupportedAppProtocolRes;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SessionSetupReqType;
public class WaitForSupportedAppProtocolRes extends ClientState {
public WaitForSupportedAppProtocolRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (message instanceof SupportedAppProtocolRes) {
getLogger().debug("SupportedAppProtocolRes received");
SupportedAppProtocolRes supportedAppProtocolRes = (SupportedAppProtocolRes) message;
SessionSetupReqType sessionSetupReq = new SessionSetupReqType();
boolean match = false;
if (supportedAppProtocolRes.getResponseCode().equals(ResponseCodeType.OK_SUCCESSFUL_NEGOTIATION) ||
supportedAppProtocolRes.getResponseCode().equals(ResponseCodeType.OK_SUCCESSFUL_NEGOTIATION_WITH_MINOR_DEVIATION)) {
// Check which schemaID is to be chosen
for (AppProtocolType evccAppProtocol : getCommSessionContext().getSupportedAppProtocols()) {
if (evccAppProtocol.getSchemaID() == supportedAppProtocolRes.getSchemaID()) {
/*
* If the EVCC supports more than one appProtocol or even minor deviations,
* then the EVCC must in some way be able to react accordingly to those different
* versions. Currently, only IS version of April 2014 is supported (see [V2G2-098]).
*/
getCommSessionContext().setChosenAppProtocol(evccAppProtocol);
match = true;
break;
}
}
} else {
return new TerminateSession("No supported appProtocol found (negative response code)");
}
// Double check if - despite an OK_ response code - a valid schemaID has been sent
if (match) {
sessionSetupReq.setEVCCID(MiscUtils.getMacAddress());
/*
* The session ID is taken from the properties file. If a previous charging session has been
* paused, then the previously valid session ID has been written to the properties file
* in order persist the value when the ISO 15118 controller is shut down for energy
* saving reasons.
* The initial value for a completely new charging session must be 00.
*/
String sessionID = (String) MiscUtils.getPropertyValue("session.id");
try {
getCommSessionContext().setSessionID(ByteUtils.toByteArrayFromHexString(sessionID));
} catch (IllegalArgumentException e) {
getLogger().warn("Stored session ID '" + sessionID + "' contains illegal character(s) which are not hexadecimal. " +
"Will reset session ID to '00'");
getCommSessionContext().setSessionID(ByteUtils.toByteArrayFromHexString("00"));
}
} else {
return new TerminateSession("No supported appProtocol found (positive response code received, " +
"but no valid schemaID. Received schema ID is: " +
supportedAppProtocolRes.getSchemaID());
}
long elapsedTime = System.nanoTime() - getCommSessionContext().getV2gEVCCCommunicationSetupTimer();
long elapsedTimeInMs = TimeUnit.MILLISECONDS.convert(elapsedTime, TimeUnit.NANOSECONDS);
return getSendMessage(sessionSetupReq, V2GMessages.SESSION_SETUP_RES, (int) Math.min(
TimeRestrictions.getV2gEvccMsgTimeout(V2GMessages.SESSION_SETUP_RES),
TimeRestrictions.V2G_EVCC_COMMUNICATION_SETUP_TIMEOUT - elapsedTimeInMs
));
} else {
String className = (message != null) ? message.getClass().getSimpleName() : "<unkown message type>";
return new TerminateSession("Invalid message (" + className + ") at this state (" + this.getClass().getSimpleName() + ")");
}
}
}

View File

@@ -0,0 +1,69 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.WeldingDetectionResType;
public class WaitForWeldingDetectionRes extends ClientState {
public WaitForWeldingDetectionRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, WeldingDetectionResType.class)) {
WeldingDetectionResType weldingDetectionRes =
(WeldingDetectionResType) ((V2GMessage) message).getBody().getBodyElement().getValue();
/*
* [V2G2-620] states that the EVCC shall send a WeldingDetectionReq again if the
* Welding Detection function has not finished on EV side. But how is this checked?
*
* How to react on DCEVSEStatus values?
*/
if (getCommSessionContext().getChargingSession() != null &&
getCommSessionContext().getChargingSession() == ChargingSessionType.PAUSE) {
return getSendMessage(getSessionStopReq(ChargingSessionType.PAUSE),
V2GMessages.SESSION_STOP_RES, "(ChargingSession = " +
ChargingSessionType.PAUSE.toString() + ")");
} else {
return getSendMessage(getSessionStopReq(ChargingSessionType.TERMINATE),
V2GMessages.SESSION_STOP_RES, "(ChargingSession = " +
ChargingSessionType.TERMINATE.toString() + ")");
}
} else {
return new TerminateSession("Incoming message raised an error");
}
}
}

View File

@@ -0,0 +1,251 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.transportLayer;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Inet6Address;
import java.util.Arrays;
import java.util.Observable;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.misc.TimeRestrictions;
import com.v2gclarity.risev2g.shared.misc.V2GTPMessage;
import com.v2gclarity.risev2g.shared.utils.ByteUtils;
import com.v2gclarity.risev2g.shared.utils.MiscUtils;
public abstract class StatefulTransportLayerClient extends Observable implements Runnable {
private Logger logger = LogManager.getLogger(this.getClass().getSimpleName());
private byte[] v2gTPHeader;
private byte[] v2gTPPayload;
private byte[] v2gTPMessage;
private InputStream inStream;
private OutputStream outStream;
private int payloadLength;
private int bytesReadFromInputStream;
private Inet6Address clientAddress;
private int clientPort;
private int timeout;
private boolean stopAlreadyInitiated;
protected void initialize() {
getLogger().debug("Initializing client connection ...");
setClientPort(MiscUtils.getRandomPortNumber());
setClientAddress(MiscUtils.getLinkLocalAddress());
setV2gTPHeader(new byte[8]);
setTimeout(TimeRestrictions.getV2gEvccMsgTimeout(V2GMessages.SUPPORTED_APP_PROTOCOL_RES)); // Needed for the supportedAppProtocol timeout
}
protected boolean processIncomingMessage() throws IOException {
/*
* Read header (8 bytes) of incoming V2GTPMessage to further allocate a byte array with
* the appropriate length.
*/
try {
setBytesReadFromInputStream(getInStream().read(getV2gTPHeader()));
} catch (IOException e) {
/*
* If there are no bytes buffered on the socket, or all buffered bytes have been consumed by read,
* then all subsequent calls to read will throw an IOException.
*/
stopAndNotify("IOExeption occurred while trying to read the header of the incoming message. "
+ "Maybe timeout occurred?", e);
return false;
} catch (NullPointerException e2) {
stopAndNotify("NullPointerException occurred while trying to read the header of the incoming message", e2);
return false;
}
if (getBytesReadFromInputStream() < 0) {
stopAndNotify("No bytes read from input stream, server socket seems to be closed", null);
return false;
}
/*
* The payload length is written to the last 4 bytes (v2gTPHeader[4] to v2gTPHeader[7])
* of the V2GTP header. The biggest ISO 15118 message in size is the CertificateInstallationRes, which in EXI
* consumes usually 3.000 to 4.000 bytes max. Let's use a threshold of 10.000 just to be safe to
* check for unreasonably high payload lengths.
*
* Change this value if you use a V2GTP payload type for a proprietary, manufacturer-specific use (see Table 10 of ISO 15118-2)
* that requires bigger payloads!
*/
setPayloadLength(ByteUtils.toIntFromByteArray(Arrays.copyOfRange(getV2gTPHeader(), 4, 8)));
if (getPayloadLength() > 10000) {
stopAndNotify("Payload length of V2GTP message is inappropiately high (" + getPayloadLength() + " bytes)! " +
"There must be an error in the V2GTP message header!", null);
return false;
} else {
getLogger().debug("Length of V2GTP payload in bytes according to V2GTP header: " + getPayloadLength());
setV2gTPPayload(new byte[getPayloadLength()]);
getInStream().read(getV2gTPPayload());
getLogger().debug("Message received");
setV2gTPMessage(new byte[getV2gTPHeader().length + getV2gTPPayload().length]);
System.arraycopy(getV2gTPHeader(), 0, getV2gTPMessage(), 0, getV2gTPHeader().length);
System.arraycopy(getV2gTPPayload(), 0, getV2gTPMessage(), getV2gTPHeader().length, getV2gTPPayload().length);
}
// Block another while-run before the new Socket timeout has been provided by send()
// TODO is there a more elegant way of blocking (this is rather resource-consuming)?
setTimeout(-1);
setChanged();
notifyObservers(getV2gTPMessage());
return true;
}
public abstract void send(V2GTPMessage message, int timeout);
/**
* If an error occurred in the run()-method, the TCP client will be stopped by closing all streams
* and the socket and interrupting the Thread. V2GCommunicationSessionEVCC will be notified as well.
* The method's statements will not be executed if a stop of the TCP client has already been
* initiated by the V2GCommunicationSessionEVCC (which might induce an error in the run()-method).
*
* @param errorMessage An error message explaining the reason for the error
* @param e An optional exception
*/
protected void stopAndNotify(String errorMessage, Exception e) {
if (!isStopAlreadyInitiated()) {
getLogger().error(errorMessage, e);
stop();
setStopAlreadyInitiated(true);
// Notify V2GCommunicationSessionEVCC about termination of session
setChanged();
notifyObservers(new TerminateSession(errorMessage));
}
}
public abstract void stop();
public Logger getLogger() {
return logger;
}
public void setLogger(Logger logger) {
this.logger = logger;
}
public byte[] getV2gTPHeader() {
return v2gTPHeader;
}
public void setV2gTPHeader(byte[] v2gTPHeader) {
this.v2gTPHeader = v2gTPHeader;
}
public byte[] getV2gTPPayload() {
return v2gTPPayload;
}
public void setV2gTPPayload(byte[] v2gTPPayload) {
this.v2gTPPayload = v2gTPPayload;
}
public byte[] getV2gTPMessage() {
return v2gTPMessage;
}
public void setV2gTPMessage(byte[] v2gTPMessage) {
this.v2gTPMessage = v2gTPMessage;
}
public InputStream getInStream() {
return inStream;
}
public void setInStream(InputStream inStream) {
this.inStream = inStream;
}
public OutputStream getOutStream() {
return outStream;
}
public void setOutStream(OutputStream outStream) {
this.outStream = outStream;
}
public int getPayloadLength() {
return payloadLength;
}
public void setPayloadLength(int payloadLength) {
this.payloadLength = payloadLength;
}
public int getBytesReadFromInputStream() {
return bytesReadFromInputStream;
}
public void setBytesReadFromInputStream(int bytesReadFromInputStream) {
this.bytesReadFromInputStream = bytesReadFromInputStream;
}
public Inet6Address getClientAddress() {
return clientAddress;
}
public void setClientAddress(Inet6Address clientAddress) {
this.clientAddress = clientAddress;
}
public int getClientPort() {
return clientPort;
}
public void setClientPort(int clientPort) {
this.clientPort = clientPort;
}
public int getTimeout() {
return timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public boolean isStopAlreadyInitiated() {
return stopAlreadyInitiated;
}
public void setStopAlreadyInitiated(boolean stopAlreadyInitiated) {
this.stopAlreadyInitiated = stopAlreadyInitiated;
}
}

View File

@@ -0,0 +1,168 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.transportLayer;
import java.io.IOException;
import java.net.Inet6Address;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import com.v2gclarity.risev2g.shared.misc.V2GTPMessage;
public class TCPClient extends StatefulTransportLayerClient {
/*
* Lazy instantiation of the Singleton since a TCP connection might not be
* initialized if the SECCDiscovery message exchange failed.
* The volatile keyword ensures that multiple threads handle the uniqueTCPClientInstance
* variable correctly when it is being initialized to the TCPClient instance.
*/
private static volatile TCPClient uniqueTCPClientInstance;
private Socket tcpSocketToServer;
public TCPClient() {}
/**
* Checks for an instance and creates one if there isn't one already.
* The synchronized block is only entered once as long as there is no existing instance of the
* TCPClient (safes valuable resource).
* @return
*/
public static TCPClient getInstance() {
if (uniqueTCPClientInstance == null) {
synchronized (TCPClient.class) {
if (uniqueTCPClientInstance == null) {
uniqueTCPClientInstance = new TCPClient();
}
}
}
return uniqueTCPClientInstance;
}
/**
* Initializes the TCP client as soon as a SECCDiscoveryRes message arrived.
*
* @param host The address of the SECC's TCP server to connect to
* @param port The port of the SECC's TCP server to connect to
*/
public boolean initialize(Inet6Address host, int port) {
super.initialize();
try {
setTcpSocketToServer(new Socket(host, port));
setInStream(getTcpSocketToServer().getInputStream());
setOutStream(getTcpSocketToServer().getOutputStream());
getLogger().info("TCP client connection established \n\t from link-local address " +
getClientAddress() + " and port " + getClientPort() +
"\n\t to host " + host.getHostAddress() + " and port " + port);
return true;
} catch (UnknownHostException e) {
getLogger().error("TCP client connection failed (UnknownHostException)!", e);
} catch (IOException e) {
getLogger().error("TCP client connection failed (IOException)!", e);
}
return false;
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
if (getTimeout() > 0) {
try {
getSocketToServer().setSoTimeout(getTimeout());
if (!processIncomingMessage()) break;
} catch (SocketTimeoutException e) {
stopAndNotify("A timeout occurred while waiting for response message", null);
break;
} catch (IOException e2) {
stopAndNotify("An IOException occurred while trying to read message", e2);
break;
}
}
}
stop();
}
@Override
public void send(V2GTPMessage message, int timeout) {
setV2gTPMessage(null);
try {
getOutStream().write(message.getMessage());
getOutStream().flush();
getLogger().debug("Message sent");
setTimeout(timeout);
} catch (IOException e) {
getLogger().error("An undefined IOException occurred while trying to send message", e);
}
}
@Override
public void stop() {
if (!isStopAlreadyInitiated()) {
getLogger().debug("Stopping TCP client ...");
setStopAlreadyInitiated(true);
try {
getInStream().close();
getOutStream().close();
getTcpSocketToServer().close();
uniqueTCPClientInstance = null;
Thread.currentThread().interrupt();
} catch (IOException e) {
getLogger().error("Error occurred while trying to close TCP socket to server", e);
}
getLogger().debug("TCP client stopped");
}
}
public Socket getTcpSocketToServer() {
return tcpSocketToServer;
}
public void setTcpSocketToServer(Socket tcpSocketToServer) {
this.tcpSocketToServer = tcpSocketToServer;
}
public Socket getSocketToServer() {
return tcpSocketToServer;
}
public void setSocketToServer(Socket socketToServer) {
this.tcpSocketToServer = socketToServer;
}
}

View File

@@ -0,0 +1,242 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.transportLayer;
import java.io.IOException;
import java.net.Inet6Address;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.misc.TimeRestrictions;
import com.v2gclarity.risev2g.shared.misc.V2GTPMessage;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils;
public class TLSClient extends StatefulTransportLayerClient {
/*
* Lazy instantiation of the Singleton since a TCP connection might not be
* initialized if the SECCDiscovery message exchange failed.
* The volatile keyword ensures that multiple threads handle the uniqueTCPClientInstance
* variable correctly when it is being initialized to the TCPClient instance.
*/
private static volatile TLSClient uniqueTLSClientInstance;
private SSLSocket tlsSocketToServer;
public TLSClient() {}
/**
* Checks for an instance and creates one if there isn't one already.
* The synchronized block is only entered once as long as there is no existing instance of the
* TLSClient (safes valuable resource).
* @return
*/
public static TLSClient getInstance() {
if (uniqueTLSClientInstance == null) {
synchronized (TLSClient.class) {
if (uniqueTLSClientInstance == null) {
uniqueTLSClientInstance = new TLSClient();
}
}
}
return uniqueTLSClientInstance;
}
/**
* Initializes the TLS client as soon as a SECCDiscoveryRes message arrived.
*
* @param host The address of the SECC's TLS server to connect to
* @param port The port of the SECC's TLS server to connect to
*/
public boolean initialize(Inet6Address host, int port) {
super.initialize();
try {
/*
* Setting the system property for the keystore and truststore via
* - System.setProperty("javax.net.ssl.keyStore", [filePath given as a String])
* - System.setProperty("javax.net.ssl.trustStore", [filePath given as a String])
* does not work in a JAR file since only getResourceAsStream works there (which on the other
* hand only returns an InputStream, not a file resource). Thus use setSSLFactories()
*/
SecurityUtils.setSSLContext(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
GlobalValues.EVCC_TRUSTSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
SSLSocketFactory sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
getLogger().debug("Creating socket to TLS server ...");
setTlsSocketToServer((SSLSocket) sslSocketFactory.createSocket(host, port));
getLogger().debug("TLS socket to server created");
setInStream(getTlsSocketToServer().getInputStream());
setOutStream(getTlsSocketToServer().getOutputStream());
/*
* The EVCC shall support at least one cipher suite as listed below according to
* the standard. An implementer may decide to choose only one of them:
* - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
* - TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256
*
* In ISO 15118-2, only the named elliptic curve "secp256r1" is allowed for ECDH(E). The jdk.tls.namedGroups property
* contains a comma-separated list within quotation marks of enabled named groups in preference order. The list of default
* named groups varies depending on what JDK release you are using. Set it on your Java command-line as follows:
*
* $ java -Djdk.tls.namedGroups="secp256r1"
*
* As it turns out, "secp256r1" is already the default first entry for Java 8 (and higher versions), but you should deactivate
* the other elliptic curves by reducing the list to this one entry only.
*/
String[] enabledCipherSuites = {
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256" // this cipher suite should be avoided, ECDH does not support perfect forward secrecy
};
getTlsSocketToServer().setEnabledCipherSuites(enabledCipherSuites);
// Set the supported TLS protocol
String[] enabledProtocols = {"TLSv1.2"};
getTlsSocketToServer().setEnabledProtocols(enabledProtocols);
/*
* The communication session setup timeout needs to be set here in case there is any problem with the
* TLS handshake.
* The timeout value will be overwritten with every new message being sent
*/
getTlsSocketToServer().setSoTimeout(TimeRestrictions.V2G_EVCC_COMMUNICATION_SETUP_TIMEOUT);
getLogger().debug("Starting TLS handshake ...");
getTlsSocketToServer().startHandshake();
getLogger().debug("TLS handshake finished");
Certificate[] seccCertificates = getTlsSocketToServer().getSession().getPeerCertificates();
X509Certificate seccLeafCertificate = (X509Certificate) seccCertificates[0];
// Check domain component of SECC certificate
if (!SecurityUtils.verifyDomainComponent(seccLeafCertificate, "CPO")) {
getLogger().error("TLS client connection failed. \n\t" +
"Reason: Domain component of SECC certificate not valid, expected 'DC=CPO'. \n\t" +
"Distinuished name of SECC certificate: " + seccLeafCertificate.getSubjectX500Principal().getName());
return false;
}
getLogger().info("TLS client connection established \n\t from link-local address " +
getClientAddress() + " and port " + getClientPort() +
"\n\t to host " + host.getHostAddress() + " and port " + port);
return true;
} catch (UnknownHostException e) {
getLogger().error("TLS client connection failed (UnknownHostException)!", e);
} catch (SSLHandshakeException e) {
getLogger().error("TLS client connection failed (SSLHandshakeException)", e);
} catch (SocketTimeoutException e) {
getLogger().fatal("TLS client connection failed (SocketTimeoutException) due to session setup timeout", e);
} catch (IOException e) {
getLogger().error("TLS client connection failed (IOException)!", e);
} catch (NullPointerException e) {
getLogger().fatal("NullPointerException while trying to set keystores, resource path to keystore/truststore might be incorrect");
}
return false;
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
if (getTimeout() >= 0) {
try {
getTlsSocketToServer().setSoTimeout(getTimeout());
if (!processIncomingMessage()) break;
} catch (SocketTimeoutException e) {
stopAndNotify("A timeout occurred while waiting for response message", null);
break;
} catch (IOException e2) {
stopAndNotify("An IOException occurred while trying to read message", e2);
break;
}
} else {
stopAndNotify("Timeout value is negative: " + getTimeout(), null);
break;
}
}
stop();
}
@Override
public void send(V2GTPMessage message, int timeout) {
setV2gTPMessage(null);
try {
getOutStream().write(message.getMessage());
getOutStream().flush();
getLogger().debug("Message sent");
setTimeout(timeout);
} catch (SSLHandshakeException e1) {
stopAndNotify("An SSLHandshakeException occurred", e1);
} catch (IOException e2) {
stopAndNotify("An undefined IOException occurred while trying to send message", e2);
}
}
@Override
public void stop() {
if (!isStopAlreadyInitiated()) {
getLogger().debug("Stopping TLS client ...");
setStopAlreadyInitiated(true);
try {
getInStream().close();
getOutStream().close();
getTlsSocketToServer().close();
uniqueTLSClientInstance = null;
Thread.currentThread().interrupt();
} catch (IOException e) {
getLogger().error("Error occurred while trying to close TCP socket to server", e);
}
getLogger().debug("TLS client stopped");
}
}
public SSLSocket getTlsSocketToServer() {
return tlsSocketToServer;
}
public void setTlsSocketToServer(SSLSocket tlsSocketToServer) {
this.tlsSocketToServer = tlsSocketToServer;
}
}

View File

@@ -0,0 +1,215 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.transportLayer;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.Inet6Address;
import java.net.MulticastSocket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.misc.TimeRestrictions;
import com.v2gclarity.risev2g.shared.misc.V2GTPMessage;
import com.v2gclarity.risev2g.shared.utils.MiscUtils;
/**
* The UDP client is handling the SECCDiscovery messages only. The standard does not
* foresee any further communication to be done via UDP but TCP.
* Therefore, the size of the UPD packet to be received is restricted to 28 bytes
* (8 bytes header of V2GTP message + 20 byte SECCDiscoveryRes payload).
*/
public class UDPClient {
/*
* Eager instantiation of the singleton, since a UDP client is always needed upfront.
* The JVM creates the unique instance when the class is loaded and before any thread tries to
* access the instance variable -> thread safe.
*/
private Logger logger = LogManager.getLogger(this.getClass().getSimpleName());
private static UDPClient uniqueUDPClientInstance = new UDPClient();
private int multicastSocketPort;
private Inet6Address multicastAddress;
private MulticastSocket socketToUDPServer;
private byte[] udpServerResponse;
private DatagramPacket udpServerPacket;
private Inet6Address udpClientAddress;
private UDPClient() {
setUdpServerResponse(new byte[28]);
}
/**
* Used to check the correct initialization of a UDP client which is a prerequisite for establishing
* a V2G communication session.
* @return True if the initialization of the UDP client was successful, false otherwise
*/
public boolean initialize() {
setSocketToUDPServer(null);
// Try to find a free port within the range of V2G_UDP_SDP_Client (see Table 15)
while (getSocketToUDPServer() == null) {
try {
setUdpClientAddress(MiscUtils.getLinkLocalAddress());
if (getUdpClientAddress() == null) return false;
setMulticastAddress((Inet6Address) Inet6Address.getByName(GlobalValues.SDP_MULTICAST_ADDRESS.toString()));
setMulticastSocketPort(MiscUtils.getRandomPortNumber());
setSocketToUDPServer(new MulticastSocket(getMulticastSocketPort()));
// Without setting the interface, the UDP server will not receive the requests
getSocketToUDPServer().setInterface(getUdpClientAddress());
getSocketToUDPServer().joinGroup(getMulticastAddress());
getLogger().info("UDP client initialized at address " +
getUdpClientAddress().getHostAddress() + " and port " + getMulticastSocketPort());
return true;
} catch (UnknownHostException e) {
getLogger().error("Initialization of UDPClient failed (Unknown host exception)!", e);
return false;
} catch (IOException e) {
getLogger().debug("MulticastSocket creation failed, UDPClient port " + multicastSocketPort +
" may not be free, trying another port.", e);
setSocketToUDPServer(null);
return false;
}
}
return false;
}
public static synchronized UDPClient getInstance() {
if (uniqueUDPClientInstance == null) {
uniqueUDPClientInstance = new UDPClient();
}
return uniqueUDPClientInstance;
}
public byte[] send(V2GTPMessage v2gTPMessage) {
// Set up the UDP packet containing the V2GTP message to be sent to the UDP server
DatagramPacket udpClientPacket = new DatagramPacket(v2gTPMessage.getMessage(),
v2gTPMessage.getMessage().length,
getMulticastAddress(),
GlobalValues.V2G_UDP_SDP_SERVER_PORT.getShortValue());
setUdpServerPacket(new DatagramPacket(getUdpServerResponse(), getUdpServerResponse().length));
try {
getSocketToUDPServer().setSoTimeout(TimeRestrictions.SDP_RESPONSE_TIMEOUT);
getSocketToUDPServer().send(udpClientPacket);
getLogger().debug("Message sent");
// Wait for the UDP Response (receive() blocks until the data arrives)
getSocketToUDPServer().receive(getUdpServerPacket());
getLogger().debug("Message received");
return getUdpServerPacket().getData();
} catch (SocketTimeoutException e) {
getLogger().error("A SocketTimeoutException was thrown while waiting for input stream from UDPServer");
} catch (UnknownHostException e) {
getLogger().error("UDP request failed (UnknownHostException)!", e);
} catch (IOException e) {
getLogger().error("UDP request failed (IOException)!", e);
}
return null;
}
public void stop() {
getSocketToUDPServer().close();
getLogger().debug("UDP client stopped");
uniqueUDPClientInstance = null;
}
public Logger getLogger() {
return logger;
}
public int getMulticastSocketPort() {
return multicastSocketPort;
}
private void setMulticastSocketPort(int multicastSocketPort) {
this.multicastSocketPort = multicastSocketPort;
}
public Inet6Address getMulticastAddress() {
return multicastAddress;
}
private void setMulticastAddress(Inet6Address multicastAddress) {
this.multicastAddress = multicastAddress;
}
public MulticastSocket getSocketToUDPServer() {
return socketToUDPServer;
}
private void setSocketToUDPServer(MulticastSocket socketToUDPServer) {
this.socketToUDPServer = socketToUDPServer;
}
public byte[] getUdpServerResponse() {
return udpServerResponse;
}
private void setUdpServerResponse(byte[] udpServerResponse) {
this.udpServerResponse = udpServerResponse;
}
public DatagramPacket getUdpServerPacket() {
return udpServerPacket;
}
private void setUdpServerPacket(DatagramPacket udpServerPacket) {
this.udpServerPacket = udpServerPacket;
}
public Inet6Address getUdpClientAddress() {
return udpClientAddress;
}
private void setUdpClientAddress(Inet6Address udpClientAddress) {
this.udpClientAddress = udpClientAddress;
}
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Configuration>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{ISO8601} %level [%t] %c: %m%n" />
</Console>
</Appenders>
<Loggers>
<Root level="debug">
<AppenderRef ref="Console" />
</Root>
</Loggers>
</Configuration>
<!-- see http://logging.apache.org/log4j/2.x/manual/configuration.html -->

View File

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.v2gclarity.risev2g</groupId>
<artifactId>rise-v2g-parent</artifactId>
<name>rise-v2g-parent</name>
<version>1.2.6</version>
<packaging>pom</packaging>
<modules>
<module>../RISE-V2G-EVCC</module>
<module>../RISE-V2G-SECC</module>
<module>../RISE-V2G-Shared</module>
</modules>
<url>https://v2g-clarity.com/rise-v2g/</url>
<description>RISE V2G is a complete reference implementation of ISO 15118-2, both implementing the EV (EVCC) and charging station (SECC) side. Messages are provided for AC as well as DC charging. Both identification modes, EIM (External Identification Means) as well as Plug and Charge are supported, including all the security relevant features such as TLS, X.509 certificates and digital signatures.</description>
<organization>
<name>V2G Clarity</name>
<url>https://www.v2g-clarity.com</url>
</organization>
<issueManagement>
<system>GitHub</system>
<url>https://github.com/V2GClarity/RISE-V2G/issues</url>
</issueManagement>
<developers>
<developer>
<name>Dr. Marc Mültin</name>
<email>marc.mueltin@v2g-clarity.com</email>
<organization>V2G Clarity</organization>
</developer>
</developers>
<licenses>
<license>
<name>MIT License</name>
<url>https://opensource.org/licenses/MIT</url>
</license>
</licenses>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.version>${project.version}</project.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source> <!-- Which Java version to use -->
<target>1.8</target> <!-- Which Java version to use -->
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId> <!-- See http://maven.apache.org/plugins/maven-assembly-plugin/usage.html -->
<version>3.1.0</version>
<configuration>
<finalName>rise-v2g-${project.version}-src</finalName>
<appendAssemblyId>false</appendAssemblyId>
<descriptors>
<descriptor>src/assembly/src.xml</descriptor> <!-- Generate the release .zip with the sources -->
</descriptors>
</configuration>
<executions>
<execution>
<id>one-jar-only</id>
<phase>package</phase> <!-- bind to the packaging phase -->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,65 @@
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
<id>project</id>
<formats>
<format>zip</format>
</formats>
<fileSets>
<fileSet> <!-- LICENSE.txt and README.md from root directory -->
<directory>../</directory>
<outputDirectory></outputDirectory>
<includes>
<include>LICENSE.txt</include>
<include>README.md</include>
</includes>
</fileSet>
<fileSet> <!-- rise-v2g-parent -->
<directory>${project.basedir}</directory>
<outputDirectory>rise-v2g-parent</outputDirectory>
<excludes>
<exclude>**/*.log</exclude>
<exclude>**/${project.build.directory}/**</exclude>
</excludes>
</fileSet>
<fileSet> <!-- rise-v2g-certificates -->
<directory>../RISE-V2G-Certificates</directory>
<outputDirectory>rise-v2g-certificates</outputDirectory>
<excludes>
<exclude>/certs/**</exclude>
<exclude>/csrs/**</exclude>
<exclude>/keystores/**</exclude>
<exclude>/privateKeys/**</exclude>
<exclude>/testing-symposia/**</exclude>
</excludes>
</fileSet>
</fileSets>
<moduleSets> <!-- rise-v2g-evcc and rise-v2g-secc -->
<moduleSet>
<!-- Enable access to all projects in the current multi-module build.
Includes all submodules (rise-v2g-evcc and rise-v2g-secc) -->
<useAllReactorProjects>true</useAllReactorProjects>
<sources>
<fileSets>
<fileSet>
<directory>${project.basedir}</directory>
<outputDirectory>/</outputDirectory>
<useDefaultExcludes>true</useDefaultExcludes>
<excludes>
<exclude>**/*.log</exclude>
<exclude>**/${project.build.directory}/**</exclude>
<exclude>**/.classpath</exclude>
<exclude>**/.project</exclude>
<exclude>**/.settings</exclude>
<exclude>/src/main/resources/*.p12</exclude>
<exclude>/src/main/resources/*.jks</exclude>
<exclude>/src/main/resources/*.pkcs8.der</exclude>
</excludes>
</fileSet>
</fileSets>
</sources>
</moduleSet>
</moduleSets>
</assembly>

View File

@@ -0,0 +1,23 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015-2017 V2G Clarity (Dr. Marc Mültin)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/

View File

@@ -0,0 +1,130 @@
###############################################################################
# The MIT License (MIT)
#
# Copyright (c) 2015 - 2019 Dr. Marc M<>ltin (V2G Clarity)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
###############################################################################
# ==============================================================================
# Configuration properties for a unique electric vehicle supply equipment (EVSE)
# ==============================================================================
# Network interface
#------------------
#
# The network interface name like en3 or eth1 of the network interface on which to communicate with the EVCC via a
# link-local IPv6 address
network.interface = en0
# Supported energy transfer modes
# -------------------------------
#
# Refer to table 63 "Semantics for EnergyTransferModeType"
# Select one value or a comma-separated list of the following values:
# - AC_single_phase_core
# - AC_three_phase_core
# - DC_core
# - DC_extended
# - DC_combo_core
# - DC_unique
energy.transfermodes.supported = AC_three_phase_core, AC_single_phase_core, DC_core, DC_extended, DC_combo_core
# Is charging a free service?
#----------------------------
#
# Possible values:
# - true
# - false
charging.free = false
# PaymentOptions
# --------------
#
# Select from the following values:
# - Contract
# - ExternalPayment
# The supported values must be separated by the comma delimiter (","). It does not matter
# if you add white spaces between the values or not.
authentication.modes.supported = Contract, ExternalPayment
# Is the SECC located in a private environment?
#---------------------------------------------
#In a private environment, TLS mechanisms work a bit differently than in a public environment.
# Possible values:
# - true
# - false
environment.private = false
#
# Implementation classes
#---------------------------------------------
# If you want to replace the implementations then set the following values
# to the name of your classes
# When omitted default dummy implementations will be used
implementation.secc.backend = com.v2gclarity.risev2g.secc.backend.DummyBackendInterface
implementation.secc.acevsecontroller = com.v2gclarity.risev2g.secc.evseController.DummyACEVSEController
implementation.secc.dcevsecontroller = com.v2gclarity.risev2g.secc.evseController.DummyDCEVSEController
# XML representation of messages
#-------------------------------
#
# Possible values:
# - true
# - false
# If this value is set to 'true', the EXICodec will print each message's XML representation (for debugging purposes)
# If no correct value is provided here, 'false' will be chosen
exi.messages.showxml = true
# Hexadecimal and Base64 representation of messages
#--------------------------------------------------
#
# Possible values:
# - true
# - false
# If this value is set to 'true', the EXICodec will print each message's hexadecimal and Base64 representation (for debugging purposes)
# If no correct value is provided here, 'false' will be chosen
exi.messages.showhex = true
# Extended logging of signature verification
#-------------------------------------------
#
# Possible values:
# - true
# - false
# If this value is set to 'true', extended logging will be printed upon verification of signatures (for debugging purposes)
# If no correct value is provided here, 'false' will be chosen
signature.verification.showlog = true
# EXI codec
#--------------------------------
#
# This (single!) value tells the program which EXI codec to use to en-/decode EXI messages
# Possible values are:
# - exificient
# - open_exi
# If no correct value is provided here, 'exificient' will be used
exi.codec = exificient

View File

@@ -0,0 +1,87 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>com.v2gclarity.risev2g</groupId>
<artifactId>rise-v2g-parent</artifactId>
<version>1.2.6</version>
<relativePath>../RISE-V2G-PARENT</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>rise-v2g-secc</artifactId>
<name>rise-v2g-secc</name>
<dependencies>
<dependency>
<groupId>com.v2gclarity.risev2g</groupId>
<artifactId>rise-v2g-shared</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.version>${project.version}</project.version>
</properties>
<build>
<plugins>
<plugin> <!-- For creating the JAR file -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId> <!-- See http://maven.apache.org/plugins/maven-assembly-plugin/usage.html -->
<version>3.1.0</version>
<configuration>
<finalName>rise-v2g-secc-${project.version}</finalName>
<appendAssemblyId>false</appendAssemblyId>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>com.v2gclarity.risev2g.secc.main.StartSECC</mainClass>
</manifest>
</archive>
<descriptors> <!-- Same as jar-with-dependencies descriptorRef, but can be adapted if needed -->
<descriptor>src/assembly/bin.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>one-jar-only</id>
<phase>package</phase> <!-- bind to the packaging phase -->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin> <!-- For copying the keystores and private key to the target folder next to the JAR file -->
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>install</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/target</outputDirectory>
<resources>
<resource>
<directory>${basedir}</directory>
<includes>
<include>*.p12</include>
<include>*.jks</include>
<include>*.der</include>
<include>*.properties</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,18 @@
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
<!-- TODO: a jarjar format would be better -->
<id>jar-with-dependencies</id>
<formats>
<format>jar</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<outputDirectory>/</outputDirectory>
<useProjectArtifact>true</useProjectArtifact>
<unpack>true</unpack>
<scope>runtime</scope>
</dependencySet>
</dependencySets>
</assembly>

View File

@@ -0,0 +1,342 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.backend;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPrivateKey;
import java.util.ArrayList;
import java.util.HashMap;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateChainType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EMAIDType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PMaxScheduleEntryType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PMaxScheduleType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PhysicalValueType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.RelativeTimeIntervalType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SAScheduleListType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SAScheduleTupleType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SalesTariffEntryType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SalesTariffType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.UnitSymbolType;
public class DummyBackendInterface implements IBackendInterface {
private V2GCommunicationSessionSECC commSessionContext;
private Logger logger = LogManager.getLogger(this.getClass().getSimpleName());
private ECPrivateKey moSubCA2PrivateKey;
public void setMoSubCA2PrivateKey(ECPrivateKey moSubCA2PrivateKey) {
this.moSubCA2PrivateKey = moSubCA2PrivateKey;
}
public DummyBackendInterface() {
/*
* In order to reduce timing problems with handling ChargeParameterDiscoveryReq, reading the private key of the MO Sub-CA2
* from the keystore to sign the SalesTariff will be done during initialization of this class.
*/
ECPrivateKey privateKey = SecurityUtils.getPrivateKey("./moSubCA2.pkcs8.der");
if (privateKey == null)
getLogger().warn("No private key available from MO Sub-CA 2 PKCS#8 file. Signing a SalesTariff will therefore not be possible");
else
setMoSubCA2PrivateKey(privateKey);
}
@Override
public SAScheduleListType getSAScheduleList(
int maxEntriesSAScheduleTuple,
long departureTime,
HashMap<String, byte[]> xmlSignatureRefElements) {
return getSAScheduleList(maxEntriesSAScheduleTuple, departureTime, xmlSignatureRefElements, (short) -1);
}
@Override
public SAScheduleListType getSAScheduleList(
int maxEntriesSAScheduleTuple,
long departureTime,
HashMap<String, byte[]> xmlSignatureRefElements,
short selectedSAScheduleTupleId) {
/*
* This is a static list of SASchedules. This means that we always offer the same list and ignore the
* processing of the parameter selectedSAScheduleTupleId.
*
*
* Some important requirements:
*
* 1. The sum of the individual time intervals described in the PMaxSchedule and
* SalesTariff provided in the ChargeParameterDiscoveryRes message shall match
* the period of time indicated by the EVCC in the message element DepartureTime of the
* ChargeParameterDiscoveryReq message.
*
* 2. If the EVCC did not provide a DepartureTime Target Setting in the ChargeParameterDiscoveryReq
* message, the sum of the individual time intervals described in the PMaxSchedule and SalesTariff
* provided in the ChargeParameterDiscoveryRes message, shall be greater or equal to 24 hours.
*
* 3. If the number of SalesTariffEntry elements in the SalesTariff or the number of
* PMaxScheduleEntry elements in the PMaxSchedule provided by the secondary actor(s) are not
* covering the entire period of time until DepartureTime, the Target Setting EAmount has not
* been met and the communication session has not been finished, it is the responsibility of
* the EVCC to request a new element of type SAScheduleListType as soon as the last
* SalesTariffEntry element or the last PMaxScheduleEntry element becomes active by sending
* a new ChargeParameterDiscoveryReq message.
*
* 4. In case of PnC, and if a Tariff Table is used by the secondary actor, the secondary actor SHALL
* sign the field SalesTariff of type SalesTariffType. In case of EIM, the secondary actor MAY sign
* this field.
*
* 5. The SECC shall 'copy' (not change!) the signature value received from the SA and transmit this value in the
* header of the ChargeParameterDiscoveryRes message.
*
* 6.
* If the element SalesTariff is signed, it shall be signed by the same private key that was used to
* issue the leaf contract certificate that the EVCC used during this connection for contract
* authentication (PnC).
*
* 7. An EVCC shall support 12 entries for PMaxScheduleEntry and SalesTariffEntry elements inside
* one SAScheduleTuple if MaxEntriesSAScheduleTuple is not transmitted in ChargeParameterDiscoveryReq.
*
* 8. The valid range for the value of EPriceLevel element shall be defined as being between 0 and
* the value of NumEPriceLevels element including the boundary values.
*/
// PMaxSchedule
// IMPORTANT: check that you do not add more pMax entries than parameter maxEntriesSAScheduleTuple
PMaxScheduleType pMaxSchedule = new PMaxScheduleType();
if (departureTime != 0)
pMaxSchedule.getPMaxScheduleEntry().add(createPMaxScheduleEntry("3", (short) 11, 0, departureTime));
else
pMaxSchedule.getPMaxScheduleEntry().add(createPMaxScheduleEntry("3", (short) 11, 0, 86400L));
/*
* SalesTariff (add some meaningful things)
* But: If it is instantiated, it must be filled with meaningful data, otherwise there will
* occur an error with the EXIDecoder (at least at Vector)
*
* IMPORTANT: check that you do not add more sales tariff entries than parameter maxEntriesSAScheduleTuple
*/
SalesTariffType salesTariff = new SalesTariffType();
/*
* Experience from the test symposium in San Diego (April 2016):
* The Id element of the signature is not restricted in size by the standard itself. But on embedded
* systems, the memory is very limited which is why we should not use long IDs for the signature reference
* element. A good size would be 3 characters max (like the example in the ISO 15118-2 annex J)
*/
salesTariff.setId("ID1");
salesTariff.setSalesTariffID((short) 1);
salesTariff.getSalesTariffEntry().add(createSalesTariffEntry(0L, (short) 1));
salesTariff.getSalesTariffEntry().add(createSalesTariffEntry(1800L, (short) 4));
salesTariff.getSalesTariffEntry().add(createSalesTariffEntry(3600L, (short) 2));
salesTariff.getSalesTariffEntry().add(createSalesTariffEntry(5400L, (short) 3));
// Put 'em all together
SAScheduleTupleType saScheduleTuple = new SAScheduleTupleType();
saScheduleTuple.setSAScheduleTupleID((short) 1);
saScheduleTuple.setPMaxSchedule(pMaxSchedule);
saScheduleTuple.setSalesTariff(salesTariff);
SAScheduleListType saScheduleList = new SAScheduleListType();
saScheduleList.getSAScheduleTuple().add(saScheduleTuple);
// Set XML reference elements for SalesTariff elements (repeat this for every sales tariff) if they are sent
if (saScheduleTuple.getSalesTariff() != null) {
xmlSignatureRefElements.put(
salesTariff.getId(),
SecurityUtils.generateDigest(
salesTariff.getId(),
getCommSessionContext().getMessageHandler().getJaxbElement(salesTariff)));
}
return saScheduleList;
}
private SalesTariffEntryType createSalesTariffEntry(long start, short ePriceLevel) {
RelativeTimeIntervalType salesTariffTimeInterval = new RelativeTimeIntervalType();
salesTariffTimeInterval.setStart(start);
SalesTariffEntryType salesTariffEntry = new SalesTariffEntryType();
salesTariffEntry.setTimeInterval(new JAXBElement<RelativeTimeIntervalType>(
new QName("urn:iso:15118:2:2013:MsgDataTypes", "RelativeTimeInterval"),
RelativeTimeIntervalType.class,
salesTariffTimeInterval));
salesTariffEntry.setEPriceLevel(ePriceLevel);
return salesTariffEntry;
}
private PMaxScheduleEntryType createPMaxScheduleEntry(String multiplier, short pMax, long start) {
PhysicalValueType pMaxValue = new PhysicalValueType();
pMaxValue.setMultiplier(new Byte(multiplier));
pMaxValue.setUnit(UnitSymbolType.W);
pMaxValue.setValue(pMax);
RelativeTimeIntervalType pMaxTimeInterval = new RelativeTimeIntervalType();
pMaxTimeInterval.setStart(start);
PMaxScheduleEntryType pMaxScheduleEntry = new PMaxScheduleEntryType();
pMaxScheduleEntry.setTimeInterval(new JAXBElement<RelativeTimeIntervalType>(
new QName("urn:iso:15118:2:2013:MsgDataTypes", "RelativeTimeInterval"),
RelativeTimeIntervalType.class,
pMaxTimeInterval));
pMaxScheduleEntry.setPMax(pMaxValue);
return pMaxScheduleEntry;
}
private PMaxScheduleEntryType createPMaxScheduleEntry(String multiplier, short pMax, long start, long duration) {
PMaxScheduleEntryType pMaxScheduleEntry = createPMaxScheduleEntry(multiplier, pMax, start);
((RelativeTimeIntervalType) pMaxScheduleEntry.getTimeInterval().getValue()).setDuration(duration);
return pMaxScheduleEntry;
}
@Override
public CertificateChainType getContractCertificateChain(X509Certificate oemProvisioningCert) {
/*
* Normally, a backend protocol such as OCPP would be used to retrieve the contract certificate chain
* based on the OEM provisioning certificate
*/
return SecurityUtils.getCertificateChain("./moCertChain.p12");
}
@Override
public CertificateChainType getContractCertificateChain(CertificateChainType oldContractCertChain) {
/*
* Normally, a backend protocol such as OCPP would be used to retrieve the new contract certificate chain
* based on the to-be-updated old contract certificate chain
*/
EMAIDType providedEMAID = SecurityUtils.getEMAID(oldContractCertChain);
/*
* NOTE 1: You need to agree with your test partner on valid, authorized EMAIDs that you put into this list.
*
* NOTE 2: Not the EMAID given as a parameter of CertificateUpdateReq is checked (error prone), but the EMAID
* provided in the common name field of the to-be-updated contract certificate
*/
ArrayList<EMAIDType> authorizedEMAIDs = new ArrayList<EMAIDType>();
// This is a list of EMAIDs used for testing purposes, like a whitelist
EMAIDType authorizedEMAID1 = new EMAIDType();
authorizedEMAID1.setId("id1");
authorizedEMAID1.setValue("DE1ABCD2EF357A");
EMAIDType authorizedEMAID2 = new EMAIDType();
authorizedEMAID2.setId("id2");
authorizedEMAID2.setValue("DE1ABCD2EF357C");
EMAIDType authorizedEMAID3 = new EMAIDType();
authorizedEMAID3.setId("id2");
authorizedEMAID3.setValue("DE1230000000021");
authorizedEMAIDs.add(authorizedEMAID1);
authorizedEMAIDs.add(authorizedEMAID2);
authorizedEMAIDs.add(authorizedEMAID3);
boolean emaidFound = false;
for (EMAIDType emaid : authorizedEMAIDs) {
if (emaid.getValue().equals(providedEMAID.getValue()))
emaidFound = true;
}
if (emaidFound)
return SecurityUtils.getCertificateChain("./moCertChain.p12");
else {
getLogger().warn("EMAID '" + providedEMAID.getValue() + "' (read from common name field of contract "
+ "certificate) is not authorized");
return null;
}
}
@Override
public ECPrivateKey getContractCertificatePrivateKey() {
KeyStore keyStore = SecurityUtils.getPKCS12KeyStore(
"./moCertChain.p12",
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
ECPrivateKey privateKey = SecurityUtils.getPrivateKey(keyStore);
if (privateKey == null)
getLogger().error("No private key available from contract certificate keystore");
return privateKey;
}
@Override
public ECPrivateKey getCPSLeafPrivateKey() {
KeyStore keyStore = SecurityUtils.getPKCS12KeyStore(
"./cpsCertChain.p12",
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
ECPrivateKey privateKey = SecurityUtils.getPrivateKey(keyStore);
if (privateKey == null)
getLogger().error("No private key available from Certificate Provisioning Service keystore");
return privateKey;
}
@Override
public ECPrivateKey getMOSubCA2PrivateKey() {
return this.moSubCA2PrivateKey;
}
@Override
public CertificateChainType getCPSCertificateChain() {
return SecurityUtils.getCertificateChain("./cpsCertChain.p12");
}
public V2GCommunicationSessionSECC getCommSessionContext() {
return commSessionContext;
}
@Override
public void setCommSessionContext(V2GCommunicationSessionSECC commSessionContext) {
this.commSessionContext = commSessionContext;
}
public Logger getLogger() {
return logger;
}
public void setLogger(Logger logger) {
this.logger = logger;
}
}

View File

@@ -0,0 +1,136 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.backend;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPrivateKey;
import java.util.HashMap;
import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateChainType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SAScheduleListType;
public interface IBackendInterface {
/**
* Provides a reference to the current communication session for
* this backend interface.
* @param commSessionContext The active communication session
*/
public void setCommSessionContext(V2GCommunicationSessionSECC commSessionContext);
/**
* Provides a list of schedules coming from a secondary actor (SAScheduleList) with pMax values
* and optional tariff incentives which shall influence the charging behaviour of the EV.
*
* @param maxEntriesSAScheduleTuple The maximum number of PMaxEntries and SalesTariff entries allowed by EVCC
* @param departureTime The departure time provided by the EV
* @param xmlSignatureRefElements Signature reference parameter provided to put sales tariff IDs and sales tariffs in
*
* @return An SASchedulesType element with a list of secondary actor schedules
*/
public SAScheduleListType getSAScheduleList(
int maxEntriesSAScheduleTuple,
long departureTime,
HashMap<String, byte[]> xmlSignatureRefElements);
/**
* Provides a list of schedules coming from a secondary actor (SAScheduleList) with pMax values
* and optional tariff incentives which shall influence the charging behaviour of the EV.
*
* The parameter selectedSAScheduleTupleId tells the backend to again offer a schedule with that ID
* because the EVCC requested it in a previous charging session that has now been resumed after pausing.
*
* @param maxEntriesSAScheduleTuple The maximum number of PMaxEntries and SalesTariff entries allowed by EVCC
* @param departureTime The departure time provided by the EV
* @param xmlSignatureRefElements Signature reference parameter provided to put sales tariff IDs and sales tariffs in
* @param selectedSAScheduleTupleId The SAScheduleTupleID which the EVCC chose in a previous charging session (optional)
*
* @return An SASchedulesType element with a list of secondary actor schedules
*/
public SAScheduleListType getSAScheduleList(
int maxEntriesSAScheduleTuple,
long departureTime,
HashMap<String, byte[]> xmlSignatureRefElements,
short selectedSAScheduleTupleId);
/**
* Provides a certificate chain coming from a secondary actor with the leaf certificate being
* the contract certificate and possible intermediate certificates (Sub-CAs) included.
*
* This interface is to be used for the CertificateUpdate
*
* @param oldContractCertificateChain The to-be-updated contract certificate chain
* @return Certificate chain for contract certificate
*/
public CertificateChainType getContractCertificateChain(CertificateChainType oldContractCertChain);
/**
* Provides a certificate chain coming from a secondary actor with the leaf certificate being
* the contract certificate and possible intermediate certificates (Sub-CAs) included.
*
* This interface is to be used for the CertificateInstallation
*
* @param oemProvisioningCert The OEM provisioning certificate
* @return Certificate chain for contract certificate
*/
public CertificateChainType getContractCertificateChain(X509Certificate oemProvisioningCert);
/**
* Provides the private key belonging to the contract certificate.
*
* @return PrivateKey of the contract certificate
*/
public ECPrivateKey getContractCertificatePrivateKey();
/**
* Provides a certificate chain coming from a secondary actor with the leaf certificate being
* the provisioning certificate and possible intermediate certificates (sub CAs) included.
*
* @return Certificate chain for provisioning certificate
*/
public CertificateChainType getCPSCertificateChain();
/**
* Provides the private key belonging to the SA provisioning certificate.
*
* @return PrivateKey of the SA provisioning certificate
*/
public ECPrivateKey getCPSLeafPrivateKey();
/**
* Provides the private key belonging to the MO Sub-CA 2 certificate (signature of SalesTariff).
*
* @return PrivateKey of the MO Sub-CA 2 certificate
*/
public ECPrivateKey getMOSubCA2PrivateKey();
}

View File

@@ -0,0 +1,117 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.evseController;
import java.math.BigInteger;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import com.v2gclarity.risev2g.shared.utils.ByteUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ACEVSEChargeParameterType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ACEVSEStatusType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.MeterInfoType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PhysicalValueType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.UnitSymbolType;
public class DummyACEVSEController implements IACEVSEController {
@SuppressWarnings("unused")
private V2GCommunicationSessionSECC commSessionContext;
public DummyACEVSEController() {
}
@Override
public String getEvseID() {
return "DE*V2G*E12345";
}
@Override
public JAXBElement<ACEVSEChargeParameterType> getACEVSEChargeParameter() {
ACEVSEChargeParameterType acEVSEChargeParameter = new ACEVSEChargeParameterType();
PhysicalValueType evseNominalVoltage = new PhysicalValueType();
evseNominalVoltage.setMultiplier((byte) 0);
evseNominalVoltage.setUnit(UnitSymbolType.V);
evseNominalVoltage.setValue((short) 230);
acEVSEChargeParameter.setEVSENominalVoltage(evseNominalVoltage);
PhysicalValueType evseMaxCurrent = new PhysicalValueType();
evseMaxCurrent.setMultiplier(ByteUtils.toByteFromHexString("00"));
evseMaxCurrent.setUnit(UnitSymbolType.A);
evseMaxCurrent.setValue((short) 32);
acEVSEChargeParameter.setEVSEMaxCurrent(evseMaxCurrent);
acEVSEChargeParameter.setACEVSEStatus(getACEVSEStatus(EVSENotificationType.NONE));
return new JAXBElement<ACEVSEChargeParameterType>(
new QName("urn:iso:15118:2:2013:MsgDataTypes", "AC_EVSEChargeParameter"),
ACEVSEChargeParameterType.class,
acEVSEChargeParameter);
}
@Override
public ACEVSEStatusType getACEVSEStatus(EVSENotificationType notification) {
ACEVSEStatusType acEVSEStatus = new ACEVSEStatusType();
acEVSEStatus.setEVSENotification((notification != null) ? notification : EVSENotificationType.NONE);
acEVSEStatus.setNotificationMaxDelay(0);
acEVSEStatus.setRCD(false);
return acEVSEStatus;
}
@Override
public void setCommSessionContext(V2GCommunicationSessionSECC commSessionContext) {
this.commSessionContext = commSessionContext;
}
@Override
public boolean closeContactor() {
// A check for CP state B would be necessary
return true;
}
@Override
public boolean openContactor() {
return true;
}
@Override
public MeterInfoType getMeterInfo() {
MeterInfoType meterInfo = new MeterInfoType();
meterInfo.setMeterID("1");
meterInfo.setMeterReading(BigInteger.valueOf(32000));
meterInfo.setTMeter(System.currentTimeMillis() / 1000);
return meterInfo;
}
}

View File

@@ -0,0 +1,255 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.evseController;
import java.math.BigInteger;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVSEChargeParameterType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVSEStatusCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVSEStatusType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.IsolationLevelType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.MeterInfoType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PhysicalValueType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.UnitSymbolType;
public class DummyDCEVSEController implements IDCEVSEController {
private V2GCommunicationSessionSECC commSessionContext;
private PhysicalValueType targetCurrent;
private PhysicalValueType targetVoltage;
@SuppressWarnings("unused")
private PhysicalValueType maximumEVVoltageLimit;
@SuppressWarnings("unused")
private PhysicalValueType maximumEVCurrentLimit;
@SuppressWarnings("unused")
private PhysicalValueType maximumEVPowerLimit;
private IsolationLevelType isolationLevel;
public DummyDCEVSEController() {
setIsolationLevel(IsolationLevelType.INVALID);
}
@Override
public String getEvseID() {
return "DE*V2G*E12345";
}
@Override
public JAXBElement<DCEVSEChargeParameterType> getDCEVSEChargeParameter() {
DCEVSEChargeParameterType dcEVSEChargeParameter = new DCEVSEChargeParameterType();
dcEVSEChargeParameter.setDCEVSEStatus(getDCEVSEStatus(EVSENotificationType.NONE));
dcEVSEChargeParameter.setEVSEMaximumCurrentLimit(getEVSEMaximumCurrentLimit());
dcEVSEChargeParameter.setEVSEMaximumPowerLimit(getEVSEMaximumPowerLimit());
dcEVSEChargeParameter.setEVSEMaximumVoltageLimit(getEVSEMaximumVoltageLimit());
dcEVSEChargeParameter.setEVSEMinimumCurrentLimit(getEVSEMinimumCurrentLimit());
dcEVSEChargeParameter.setEVSEMinimumVoltageLimit(getEVSEMinimumVoltageLimit());
dcEVSEChargeParameter.setEVSEPeakCurrentRipple(getEVSEPeakCurrentRipple());
return new JAXBElement<DCEVSEChargeParameterType>(
new QName("urn:iso:15118:2:2013:MsgDataTypes", "DC_EVSEChargeParameter"),
DCEVSEChargeParameterType.class,
dcEVSEChargeParameter);
}
public V2GCommunicationSessionSECC getCommSessionContext() {
return commSessionContext;
}
@Override
public void setCommSessionContext(V2GCommunicationSessionSECC commSessionContext) {
this.commSessionContext = commSessionContext;
}
@Override
public boolean closeContactor() {
// A check for CP state B would be necessary
return true;
}
@Override
public boolean openContactor() {
return true;
}
@Override
public DCEVSEStatusType getDCEVSEStatus(EVSENotificationType notification) {
DCEVSEStatusType dcEvseStatus = new DCEVSEStatusType();
dcEvseStatus.setNotificationMaxDelay(0);
dcEvseStatus.setEVSENotification((notification != null) ? notification : EVSENotificationType.NONE);
dcEvseStatus.setEVSEStatusCode(DCEVSEStatusCodeType.EVSE_READY);
dcEvseStatus.setEVSEIsolationStatus(getIsolationLevel());
return dcEvseStatus;
}
@Override
public void setTargetVoltage(PhysicalValueType targetVoltage) {
this.targetVoltage = targetVoltage;
}
@Override
public void setTargetCurrent(PhysicalValueType targetCurrent) {
this.targetCurrent = targetCurrent;
}
@Override
public PhysicalValueType getPresentVoltage() {
return this.targetVoltage;
}
@Override
public PhysicalValueType getPresentCurrent() {
return this.targetCurrent;
}
@Override
public void setEVMaximumVoltageLimit(PhysicalValueType maximumVoltageLimit) {
this.maximumEVVoltageLimit = maximumVoltageLimit;
}
@Override
public void setEVMaximumCurrentLimit(PhysicalValueType maximumCurrentLimit) {
this.maximumEVCurrentLimit = maximumCurrentLimit;
}
@Override
public void setEVMaximumPowerLimit(PhysicalValueType maximumPowerLimit) {
this.maximumEVPowerLimit = maximumPowerLimit;
}
@Override
public PhysicalValueType getEVSEMaximumVoltageLimit() {
PhysicalValueType evseMaxVoltageLimit = new PhysicalValueType();
evseMaxVoltageLimit.setMultiplier(new Byte("0"));
evseMaxVoltageLimit.setUnit(UnitSymbolType.V);
evseMaxVoltageLimit.setValue((short) 400);
return evseMaxVoltageLimit;
}
@Override
public PhysicalValueType getEVSEMinimumVoltageLimit() {
PhysicalValueType evseMinVoltageLimit = new PhysicalValueType();
evseMinVoltageLimit.setMultiplier(new Byte("0"));
evseMinVoltageLimit.setUnit(UnitSymbolType.V);
evseMinVoltageLimit.setValue((short) 230);
return evseMinVoltageLimit;
}
@Override
public PhysicalValueType getEVSEMaximumCurrentLimit() {
PhysicalValueType evseMaxCurrentLimit = new PhysicalValueType();
evseMaxCurrentLimit.setMultiplier(new Byte("0"));
evseMaxCurrentLimit.setUnit(UnitSymbolType.A);
evseMaxCurrentLimit.setValue((short) 32);
return evseMaxCurrentLimit;
}
@Override
public PhysicalValueType getEVSEMinimumCurrentLimit() {
PhysicalValueType evseMinCurrentLimit = new PhysicalValueType();
evseMinCurrentLimit.setMultiplier(new Byte("0"));
evseMinCurrentLimit.setUnit(UnitSymbolType.A);
evseMinCurrentLimit.setValue((short) 16);
return evseMinCurrentLimit;
}
@Override
public PhysicalValueType getEVSEMaximumPowerLimit() {
PhysicalValueType evseMaxPowerLimit = new PhysicalValueType();
evseMaxPowerLimit.setMultiplier(new Byte("3"));
evseMaxPowerLimit.setUnit(UnitSymbolType.W);
evseMaxPowerLimit.setValue((short) 63);
return evseMaxPowerLimit;
}
@Override
public boolean isEVSECurrentLimitAchieved() {
return false;
}
@Override
public boolean isEVSEVoltageLimitAchieved() {
return false;
}
@Override
public boolean isEVSEPowerLimitAchieved() {
return false;
}
@Override
public MeterInfoType getMeterInfo() {
MeterInfoType meterInfo = new MeterInfoType();
meterInfo.setMeterID("1");
meterInfo.setMeterReading(BigInteger.valueOf(32000));
meterInfo.setTMeter(System.currentTimeMillis() / 1000);
return meterInfo;
}
@Override
public PhysicalValueType getEVSEPeakCurrentRipple() {
PhysicalValueType peakCurrentRipple = new PhysicalValueType();
peakCurrentRipple.setMultiplier(new Byte("0"));
peakCurrentRipple.setUnit(UnitSymbolType.A);
peakCurrentRipple.setValue((short) 0); // what is a peak-to-peak current ripple??
return peakCurrentRipple;
}
@Override
public IsolationLevelType getIsolationLevel() {
return isolationLevel;
}
@Override
public void setIsolationLevel(IsolationLevelType isolationLevel) {
this.isolationLevel = isolationLevel;
}
}

View File

@@ -0,0 +1,47 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.evseController;
import javax.xml.bind.JAXBElement;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ACEVSEChargeParameterType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ACEVSEStatusType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
public interface IACEVSEController extends IEVSEController {
/**
* Returns the charge parameter for AC charging
* @return The EVSE specific charge parameter for the current charging session
*/
public JAXBElement<ACEVSEChargeParameterType> getACEVSEChargeParameter();
/**
* Returns the EVSE status for AC charging comprising notification, maxDelay and RCD
* @param evseNotification An evse notification can optionally be set for testing purposes
* @return The EVSE specific status
*/
public ACEVSEStatusType getACEVSEStatus(EVSENotificationType evseNotification);
}

View File

@@ -0,0 +1,175 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.evseController;
import javax.xml.bind.JAXBElement;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVSEChargeParameterType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVSEStatusType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.IsolationLevelType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PhysicalValueType;
public interface IDCEVSEController extends IEVSEController {
/**
* Returns the charge parameter for DC charging
* @return The EVSE specific charge parameter for the current charging session
*/
public JAXBElement<DCEVSEChargeParameterType> getDCEVSEChargeParameter();
/**
* Sets the target voltage communicated by the EV for the DC charging process
* @param targetVoltage The target voltage encapsulated in a PhysicalValueType
*/
public void setTargetVoltage(PhysicalValueType targetVoltage);
/**
* Sets the target voltage communicated by the EV for the DC charging process
* @param targetVoltage The target voltage encapsulated in a PhysicalValueType
*/
public void setTargetCurrent(PhysicalValueType targetCurrent);
/**
* Sets the maximum voltage communicated by the EV for the DC charging process
* @param maximumVoltage The maximum voltage encapsulated in a PhysicalValueType
*/
public void setEVMaximumVoltageLimit(PhysicalValueType maximumVoltage);
/**
* Sets the maximum current communicated by the EV for the DC charging process
* @param maximumCurrent The maximum current encapsulated in a PhysicalValueType
*/
public void setEVMaximumCurrentLimit(PhysicalValueType maximumCurrent);
/**
* Sets the maximum power communicated by the EV for the DC charging process
* @param maximumPower The maximum power encapsulated in a PhysicalValueType
*/
public void setEVMaximumPowerLimit(PhysicalValueType maximumPower);
/**
* Returns the present voltage at the EVSE
* @return Present voltage given as a PhyiscalValueType
*/
public PhysicalValueType getPresentVoltage();
/**
* Returns the present current at the EVSE
* @return Present current given as a PhyiscalValueType
*/
public PhysicalValueType getPresentCurrent();
/**
* Returns the maximum voltage limit of the EVSE for DC charging
* @return Maximum voltage limit given as a PhyiscalValueType
*/
public PhysicalValueType getEVSEMaximumVoltageLimit();
/**
* Returns the minimum voltage limit of the EVSE for DC charging
* @return Minimum voltage limit given as a PhyiscalValueType
*/
public PhysicalValueType getEVSEMinimumVoltageLimit();
/**
* Returns the maximum current limit of the EVSE for DC charging
* @return Maximum current limit given as a PhyiscalValueType
*/
public PhysicalValueType getEVSEMaximumCurrentLimit();
/**
* Returns the minimum current limit of the EVSE for DC charging
* @return Minimum current limit given as a PhyiscalValueType
*/
public PhysicalValueType getEVSEMinimumCurrentLimit();
/**
* Returns the maximum power limit of the EVSE for DC charging
* @return Maximum power limit given as a PhyiscalValueType
*/
public PhysicalValueType getEVSEMaximumPowerLimit();
/**
* Returns TRUE, if the EVSE has reached its current limit.
* @return TRUE, if the EVSE has reached its current limit, false otherwise
*/
public boolean isEVSECurrentLimitAchieved();
/**
* Returns TRUE, if the EVSE has reached its voltage limit.
* @return TRUE, if the EVSE has reached its voltage limit, false otherwise
*/
public boolean isEVSEVoltageLimitAchieved();
/**
* Returns TRUE, if the EVSE has reached its power limit.
* @return TRUE, if the EVSE has reached its power limit, false otherwise
*/
public boolean isEVSEPowerLimitAchieved();
/**
* Returns the peak-to-peak magnitude of the current ripple of the EVSE
* @return Peak given as a PhyiscalValueType
*/
public PhysicalValueType getEVSEPeakCurrentRipple();
/**
* Returns the EVSE status for DC charging comprising notification, maxDelay and RCD
* @return The EVSE specific status
*/
public DCEVSEStatusType getDCEVSEStatus(EVSENotificationType notification);
/**
* Returns the peak-to-peak magnitude of the current ripple of the EVSE
* @return Peak given as a PhyiscalValueType
*/
public IsolationLevelType getIsolationLevel();
/**
* Sets the IsolationLevel DC charging
* @param isolationLevel The IsolationLevel status of the charge equipment
*/
public void setIsolationLevel(IsolationLevelType isolationLevel);
}

View File

@@ -0,0 +1,71 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.evseController;
import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.MeterInfoType;
public interface IEVSEController {
/**
* Provides a reference to the current communication session for
* this controller instance.
* @param commSessionContext The active communication session
*/
public void setCommSessionContext(V2GCommunicationSessionSECC commSessionContext);
/**
* The EVSEID is formatted according to Annex H of ISO/IEC 15118 and consists of minimum 7, max 37
* characters.
*
* @return ID given as a string that uniquely identifies the EVSE and the power outlet the
* vehicle is connected to
*/
public String getEvseID();
/**
* Closes the contactor if CP state C was measured (which is a prerequisite for power transfer)
* upon receipt of PowerDeliveryReq with ChargeProgress set to START. A timeout of 3s is allowed.
* @return True, if contactor is closed, false otherwise
*/
public boolean closeContactor();
/**
* Opens the contactor if CP state B was measured upon receipt of PowerDeliveryReq with
* ChargeProgress set to STOP. A timeout of 3s is allowed.
* @return True, if contactor is opened, false otherwise
*/
public boolean openContactor();
/**
* Returns the MeterInfo record containing the latest meter reading and other meter relevant data.
* @return Meter reading and other meter data contained in MeterInfoType
*/
public MeterInfoType getMeterInfo();
}

View File

@@ -0,0 +1,71 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.main;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionHandlerSECC;
import com.v2gclarity.risev2g.secc.transportLayer.TCPServer;
import com.v2gclarity.risev2g.secc.transportLayer.TLSServer;
import com.v2gclarity.risev2g.secc.transportLayer.UDPServer;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.utils.MiscUtils;
public class StartSECC {
public static void main(String[] args) {
final Logger logger = LogManager.getLogger(StartSECC.class.getSimpleName());
MiscUtils.loadProperties(GlobalValues.SECC_CONFIG_PROPERTIES_PATH.toString());
UDPServer udpServer = UDPServer.getInstance();
TCPServer tcpServer = TCPServer.getInstance();
TLSServer tlsServer = TLSServer.getInstance();
if (!udpServer.initialize() || !tlsServer.initialize() || !tcpServer.initialize()) {
logger.fatal("Unable to start SECC because UDP, TCP or TLS server could not be initialized");
} else {
Thread udpServerThread = new Thread(udpServer);
udpServerThread.setName("UDPServerThread");
Thread tcpServerThread = new Thread(tcpServer);
tcpServerThread.setName("TCPServerThread");
Thread tlsServerThread = new Thread(tlsServer);
tlsServerThread.setName("TLSServerThread");
// All transport layer threads need to be initialized before initializing the SECC session handler.
new V2GCommunicationSessionHandlerSECC();
/*
* To avoid possible race conditions, the transport layer threads need to be started AFTER the SECC
* session handler has been initialized. Otherwise the situation might occur that the UDPServer is
* receiving a UDP client packet and tries to access the MessageHandler object before this object has
* been created by the SECC session handler.
*/
udpServerThread.start();
tcpServerThread.start();
tlsServerThread.start();
}
}
}

View File

@@ -0,0 +1,84 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.misc;
import com.v2gclarity.risev2g.secc.backend.DummyBackendInterface;
import com.v2gclarity.risev2g.secc.backend.IBackendInterface;
import com.v2gclarity.risev2g.secc.evseController.DummyACEVSEController;
import com.v2gclarity.risev2g.secc.evseController.DummyDCEVSEController;
import com.v2gclarity.risev2g.secc.evseController.IACEVSEController;
import com.v2gclarity.risev2g.secc.evseController.IDCEVSEController;
import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import com.v2gclarity.risev2g.shared.misc.V2GImplementationFactory;
/**
* Implementation factory for the SECC controllers and for the backend interface
*
*/
public class SECCImplementationFactory extends V2GImplementationFactory {
/**
* Creates the backend interface for the SECC application
* @param commSessionContext the session the backend will be connected to
* @return
*/
public static IBackendInterface createBackendInterface(V2GCommunicationSessionSECC commSessionContext) {
IBackendInterface instance = buildFromProperties("implementation.secc.backend", IBackendInterface.class);
if (instance == null) {
instance = new DummyBackendInterface();
}
instance.setCommSessionContext(commSessionContext);
return instance;
}
/**
* Creates the AC EVSE controller for the SECC application
* @param commSessionContext the session the backend will be connected to
* @return
*/
public static IACEVSEController createACEVSEController(V2GCommunicationSessionSECC commSessionContext) {
IACEVSEController instance = buildFromProperties("implementation.secc.acevsecontroller", IACEVSEController.class);
if (instance == null) {
instance = new DummyACEVSEController();
}
instance.setCommSessionContext(commSessionContext);
return instance;
}
/**
* Creates the DC EVSE controller for the SECC application
* @param commSessionContext the session the backend will be connected to
* @return
*/
public static IDCEVSEController createDCEVSEController(V2GCommunicationSessionSECC commSessionContext) {
IDCEVSEController instance = buildFromProperties("implementation.secc.dcevsecontroller", IDCEVSEController.class);
if (instance == null) {
instance = new DummyDCEVSEController();
}
instance.setCommSessionContext(commSessionContext);
return instance;
}
}

View File

@@ -0,0 +1,273 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.session;
import java.net.DatagramPacket;
import java.net.Inet6Address;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.v2gclarity.risev2g.secc.transportLayer.ConnectionHandler;
import com.v2gclarity.risev2g.secc.transportLayer.TCPServer;
import com.v2gclarity.risev2g.secc.transportLayer.TLSServer;
import com.v2gclarity.risev2g.secc.transportLayer.UDPServer;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.messageHandling.MessageHandler;
import com.v2gclarity.risev2g.shared.messageHandling.PauseSession;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.misc.V2GTPMessage;
import com.v2gclarity.risev2g.shared.utils.ByteUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.SECCDiscoveryReq;
import com.v2gclarity.risev2g.shared.v2gMessages.SECCDiscoveryRes;
public class V2GCommunicationSessionHandlerSECC implements Observer {
private Logger logger = LogManager.getLogger(this.getClass().getSimpleName());
private HashMap<String, V2GCommunicationSessionSECC> v2gCommunicationSessions;
/*
* Keeps a list of all ConnectionHandlers and their respective running Threads.
* The V2GCommunicationSessionHandlerSECC needs a ConnectionHandler (with its TCP/TLS client socket)
* in order to associate it with a V2GCommunicationSessionSECC. Handing over a Thread instead brings
* up the problem that you can't access the Thread's runnable object (ConnectionHandler).
*/
private static HashMap<ConnectionHandler, Thread> connectionHandlerMap;
private MessageHandler messageHandler;
private V2GTPMessage v2gTpMessage;
private byte security;
public V2GCommunicationSessionHandlerSECC() {
// Tell the respective transport layer Observables to notify this session handler
UDPServer.getInstance().addObserver(this);
TCPServer.getInstance().addObserver(this);
TLSServer.getInstance().addObserver(this);
// Maps IP addresses of the clients given as a String to V2GCommunicationSessionSECC objects
setV2gCommunicationSessions(new HashMap<String, V2GCommunicationSessionSECC>());
// Maps ConnectionHandlers to their respective running threads
setConnectionHandlerMap(new HashMap<ConnectionHandler, Thread>());
setMessageHandler(MessageHandler.getInstance());
}
@Override
public void update(Observable obs, Object obj) {
if (obs instanceof UDPServer && obj instanceof DatagramPacket) {
processSECCDiscoveryReq((DatagramPacket) obj);
} else if ((obs instanceof TCPServer || obs instanceof TLSServer) && obj instanceof ConnectionHandler) {
String ipAddress = ((ConnectionHandler) obj).getAddress();
if (getV2gCommunicationSessions().containsKey(ipAddress)) {
/*
* Assign the new ConnectionHandler to the respective existing V2GCommunicationSessionSECC.
* This way the V2GCommunicationSessionSECC knows to which socket to write to when
* sending messages and from which socket to read from when receiving messages.
*
* This if-clause is executed as soon as an EV resumes a previously paused charging
* session. Before pausing, the TCP/TLS socket has been closed, but the charging session
* data object (V2GCommunicationSessionSECC) needed to be kept alive in order to later
* on continue a charging session with the saved data.
* Important!
* The connectionHandler thread must not be started (will start reading the incoming bytes)
* before the V2GCommunicationSessionSECC object is instantiated, otherwise it may lead to
* race conditions.
*/
getLogger().debug("Resuming previous communication session ...");
V2GCommunicationSessionSECC continuedSession = getV2gCommunicationSessions().get(ipAddress);
// Reset charging session state from previous session (namely ChargingSessionType.PAUSE) to avoid confusion in the algorithm
continuedSession.setChargingSession(null);
continuedSession.setConnectionHandler((ConnectionHandler) obj);
continuedSession.setTlsConnection((obs instanceof TLSServer) ? true : false);
((ConnectionHandler) obj).addObserver(getV2gCommunicationSessions().get(ipAddress));
manageConnectionHandlers((ConnectionHandler) obj);
} else {
getLogger().debug("Initiating a new communication session ...");
V2GCommunicationSessionSECC newSession = new V2GCommunicationSessionSECC((ConnectionHandler) obj);
newSession.setTlsConnection((obs instanceof TLSServer) ? true : false);
newSession.addObserver(this);
getV2gCommunicationSessions().put(ipAddress, newSession);
manageConnectionHandlers((ConnectionHandler) obj);
}
} else if (obs instanceof V2GCommunicationSessionSECC && obj instanceof TerminateSession) {
// Remove the V2GCommunicationSessionSECC instance from the hash map
String ipAddress = ((V2GCommunicationSessionSECC) obs).getConnectionHandler().getAddress();
getV2gCommunicationSessions().remove(ipAddress);
stopConnectionHandler(((V2GCommunicationSessionSECC) obs).getConnectionHandler(), false);
} else if (obs instanceof V2GCommunicationSessionSECC && obj instanceof PauseSession) {
// Stop the connection handler, but keep the V2GCommunicationSessionSECC instance in the hash map
stopConnectionHandler(((V2GCommunicationSessionSECC) obs).getConnectionHandler(), true);
} else {
getLogger().warn("Notification received, but sending entity or received object not identifiable");
}
}
private void manageConnectionHandlers(ConnectionHandler connectionHandler) {
Thread connectionHandlerThread = new Thread(connectionHandler);
connectionHandlerThread.setDaemon(true);
connectionHandlerThread.setName("ConnectionThread " + connectionHandler.getAddress());
connectionHandlerThread.start();
getConnectionHandlerMap().put(connectionHandler, connectionHandlerThread);
}
private void processSECCDiscoveryReq(DatagramPacket udpClientPacket) {
setV2gTpMessage(new V2GTPMessage(udpClientPacket.getData()));
try {
if (getMessageHandler().isV2GTPMessageValid(getV2gTpMessage()) &&
Arrays.equals(getV2gTpMessage().getPayloadType(), GlobalValues.V2GTP_PAYLOAD_TYPE_SDP_REQUEST_MESSAGE.getByteArrayValue())) {
SECCDiscoveryReq seccDiscoveryReq = new SECCDiscoveryReq(getV2gTpMessage().getPayload());
setSecurity(seccDiscoveryReq.getSecurity());
getLogger().debug("SECCDiscoveryReq received");
/*
* The TCP and TLS server ports are created upon initialization of the TCP/TLS server and will
* remain the same for every connected EV. Only TCP or TLS are allowed as transport
* protocols for further communication beyond the SECCDiscoveryReq/-Res handshake (not UDP).
*
* One might implement further decision rules for dealing with the security level (TCP or TLS)
* requested by the EVCC (see also Table 3 and 4 of ISO/IEC 15118-2). For now, the requested
* security level of the EVCC will always be accepted.
*/
byte[] seccAddress = (isSecureCommunication()) ? TLSServer.getInstance().getServerAddress().getAddress() : TCPServer.getInstance().getServerAddress().getAddress();
int seccPort = (isSecureCommunication()) ? TLSServer.getInstance().getServerPort() : TCPServer.getInstance().getServerPort();
SECCDiscoveryRes seccDiscoveryRes = new SECCDiscoveryRes(
seccAddress,
ByteUtils.toByteArrayFromInt(seccPort, true),
getSecurity(),
GlobalValues.V2G_TRANSPORT_PROTOCOL_TCP.getByteValue()
);
setV2gTpMessage(new V2GTPMessage(GlobalValues.V2GTP_VERSION_1_IS.getByteValue(),
GlobalValues.V2GTP_PAYLOAD_TYPE_SDP_RESPONSE_MESSAGE.getByteArrayValue(),
seccDiscoveryRes.getPayload()));
getLogger().debug("Preparing to send SECCDiscoveryRes ...");
// The SECCDiscoveryRes must be sent via UDP before the requested TCP/TLS server can be used
UDPServer.getInstance().send(getV2gTpMessage(), (Inet6Address) udpClientPacket.getAddress(), udpClientPacket.getPort());
} else {
getLogger().warn("Incoming DatagramPacket could not be identified as an SECCDiscoveryReq");
}
} catch (NullPointerException e) {
getLogger().error("NullPointerException occurred while processing SECCDiscoveryReq", e);
}
}
/**
* Stops (interrupts) the respective thread running the provided ConnectionHandler and tries
* to close its socket.
* @param connectionHandler The ConnectionHandler whose socket is to be closed and whose thread
* is to be interrupted.
*/
public void stopConnectionHandler(ConnectionHandler connectionHandler, boolean pausingSession) {
if (getConnectionHandlerMap().containsKey(connectionHandler)) {
// Close the socket
connectionHandler.stop();
// Interrupt session thread
Thread connectionThread = getConnectionHandlerMap().get(connectionHandler);
connectionThread.interrupt();
// Remove HashMap entry
getConnectionHandlerMap().remove(connectionHandler);
getLogger().debug("Thread '" + connectionThread.getName() + "' has been interrupted and removed" +
((pausingSession) ? ". Charging session is paused." : "") + "\n\n");
} else {
String address = connectionHandler.getAddress();
int port = connectionHandler.getPort();
getLogger().warn("No active connection to socket with IP address " +
address + " and port " + port + " found.");
}
}
public boolean isSecureCommunication() {
return Byte.compare(getSecurity(), GlobalValues.V2G_SECURITY_WITH_TLS.getByteValue()) == 0 ? true : false;
}
public HashMap<String, V2GCommunicationSessionSECC> getV2gCommunicationSessions() {
return v2gCommunicationSessions;
}
public void setV2gCommunicationSessions(
HashMap<String, V2GCommunicationSessionSECC> v2gCommunicationSessions) {
this.v2gCommunicationSessions = v2gCommunicationSessions;
}
public Logger getLogger() {
return logger;
}
public void setLogger(Logger logger) {
this.logger = logger;
}
public MessageHandler getMessageHandler() {
return messageHandler;
}
public void setMessageHandler(MessageHandler messageHandler) {
this.messageHandler = messageHandler;
}
public V2GTPMessage getV2gTpMessage() {
return v2gTpMessage;
}
public void setV2gTpMessage(V2GTPMessage v2gTpMessage) {
this.v2gTpMessage = v2gTpMessage;
}
public static HashMap<ConnectionHandler, Thread> getConnectionHandlerMap() {
return connectionHandlerMap;
}
public static void setConnectionHandlerMap(HashMap<ConnectionHandler, Thread> connectionHandlerMap) {
V2GCommunicationSessionHandlerSECC.connectionHandlerMap = connectionHandlerMap;
}
public byte getSecurity() {
return security;
}
public void setSecurity(byte security) {
this.security = security;
}
}

View File

@@ -0,0 +1,494 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.session;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Observable;
import java.util.Observer;
import com.v2gclarity.risev2g.secc.backend.IBackendInterface;
import com.v2gclarity.risev2g.secc.evseController.IACEVSEController;
import com.v2gclarity.risev2g.secc.evseController.IDCEVSEController;
import com.v2gclarity.risev2g.secc.evseController.IEVSEController;
import com.v2gclarity.risev2g.secc.misc.SECCImplementationFactory;
import com.v2gclarity.risev2g.secc.states.ForkState;
import com.v2gclarity.risev2g.secc.states.WaitForAuthorizationReq;
import com.v2gclarity.risev2g.secc.states.WaitForCableCheckReq;
import com.v2gclarity.risev2g.secc.states.WaitForCertificateInstallationReq;
import com.v2gclarity.risev2g.secc.states.WaitForCertificateUpdateReq;
import com.v2gclarity.risev2g.secc.states.WaitForChargeParameterDiscoveryReq;
import com.v2gclarity.risev2g.secc.states.WaitForChargingStatusReq;
import com.v2gclarity.risev2g.secc.states.WaitForCurrentDemandReq;
import com.v2gclarity.risev2g.secc.states.WaitForMeteringReceiptReq;
import com.v2gclarity.risev2g.secc.states.WaitForPaymentDetailsReq;
import com.v2gclarity.risev2g.secc.states.WaitForPaymentServiceSelectionReq;
import com.v2gclarity.risev2g.secc.states.WaitForPowerDeliveryReq;
import com.v2gclarity.risev2g.secc.states.WaitForPreChargeReq;
import com.v2gclarity.risev2g.secc.states.WaitForServiceDetailReq;
import com.v2gclarity.risev2g.secc.states.WaitForServiceDiscoveryReq;
import com.v2gclarity.risev2g.secc.states.WaitForSessionSetupReq;
import com.v2gclarity.risev2g.secc.states.WaitForSessionStopReq;
import com.v2gclarity.risev2g.secc.states.WaitForSupportedAppProtocolReq;
import com.v2gclarity.risev2g.secc.states.WaitForWeldingDetectionReq;
import com.v2gclarity.risev2g.secc.transportLayer.ConnectionHandler;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ChangeProcessingState;
import com.v2gclarity.risev2g.shared.messageHandling.PauseSession;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.SendMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.misc.V2GCommunicationSession;
import com.v2gclarity.risev2g.shared.misc.V2GTPMessage;
import com.v2gclarity.risev2g.shared.utils.ByteUtils;
import com.v2gclarity.risev2g.shared.utils.MiscUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.appProtocol.SupportedAppProtocolReq;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ACEVSEStatusType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateChainType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EnergyTransferModeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.MessageHeaderType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.MeterInfoType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PMaxScheduleType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionListType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SAScheduleListType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class V2GCommunicationSessionSECC extends V2GCommunicationSession implements Observer {
private short schemaID;
private ACEVSEStatusType acEVSEStatus;
private ChargingSessionType chargingSession;
private PMaxScheduleType pMaxSchedule;
private short chosenSAScheduleTuple;
private IACEVSEController acEvseController;
private IDCEVSEController dcEvseController;
private IBackendInterface backendInterface;
private boolean oldSessionJoined;
private byte[] incomingV2GTPMessage;
private ConnectionHandler connectionHandler;
private ArrayList<ServiceType> offeredServices;
private byte[] genChallenge;
private SAScheduleListType saSchedules;
private EnergyTransferModeType requestedEnergyTransferMode;
private PaymentOptionType selectedPaymentOption;
private CertificateChainType contractSignatureCertChain;
private MeterInfoType sentMeterInfo;
private boolean chargeProgressStarted; // for checking [V2G2-812]
public V2GCommunicationSessionSECC(ConnectionHandler connectionHandler) {
setConnectionHandler(connectionHandler);
// Tell the respective ConnectionHandler to notify if a new V2GTPMessage has arrived (see update()-method)
connectionHandler.addObserver(this);
getStates().put(V2GMessages.FORK, new ForkState(this));
getStates().put(V2GMessages.SUPPORTED_APP_PROTOCOL_REQ, new WaitForSupportedAppProtocolReq(this));
getStates().put(V2GMessages.SESSION_SETUP_REQ, new WaitForSessionSetupReq(this));
getStates().put(V2GMessages.SERVICE_DISCOVERY_REQ, new WaitForServiceDiscoveryReq(this));
getStates().put(V2GMessages.SERVICE_DETAIL_REQ, new WaitForServiceDetailReq(this));
getStates().put(V2GMessages.PAYMENT_SERVICE_SELECTION_REQ, new WaitForPaymentServiceSelectionReq(this));
getStates().put(V2GMessages.CERTIFICATE_INSTALLATION_REQ, new WaitForCertificateInstallationReq(this));
getStates().put(V2GMessages.CERTIFICATE_UPDATE_REQ, new WaitForCertificateUpdateReq(this));
getStates().put(V2GMessages.PAYMENT_DETAILS_REQ, new WaitForPaymentDetailsReq(this));
getStates().put(V2GMessages.AUTHORIZATION_REQ, new WaitForAuthorizationReq(this));
getStates().put(V2GMessages.CHARGE_PARAMETER_DISCOVERY_REQ, new WaitForChargeParameterDiscoveryReq(this));
getStates().put(V2GMessages.CABLE_CHECK_REQ, new WaitForCableCheckReq(this));
getStates().put(V2GMessages.PRE_CHARGE_REQ, new WaitForPreChargeReq(this));
getStates().put(V2GMessages.POWER_DELIVERY_REQ, new WaitForPowerDeliveryReq(this));
getStates().put(V2GMessages.CHARGING_STATUS_REQ, new WaitForChargingStatusReq(this));
getStates().put(V2GMessages.CURRENT_DEMAND_REQ, new WaitForCurrentDemandReq(this));
getStates().put(V2GMessages.METERING_RECEIPT_REQ, new WaitForMeteringReceiptReq(this));
getStates().put(V2GMessages.WELDING_DETECTION_REQ, new WaitForWeldingDetectionReq(this));
getStates().put(V2GMessages.SESSION_STOP_REQ, new WaitForSessionStopReq(this));
setStartState(getStates().get(V2GMessages.SUPPORTED_APP_PROTOCOL_REQ));
setCurrentState(getStartState());
// Configure which EVSE controller implementation to use
setACEvseController(SECCImplementationFactory.createACEVSEController(this));
setDCEvseController(SECCImplementationFactory.createDCEVSEController(this));
// Configures which backend interface implementation to use for retrieving SASchedules
setBackendInterface(SECCImplementationFactory.createBackendInterface(this));
// ACEVSE notification
setACEVSEStatus(new ACEVSEStatusType());
getACEVSEStatus().setEVSENotification(EVSENotificationType.NONE);
getACEVSEStatus().setNotificationMaxDelay(0);
getACEVSEStatus().setRCD(false);
// Will be set only if a session is to be stopped or paused
setChargingSession(null);
setOfferedServices(new ArrayList<ServiceType>());
getLogger().debug("\n*******************************************" +
"\n* New V2G communication session initialized" +
"\n*******************************************");
}
@Override
public void update(Observable obs, Object obj) {
if (obs instanceof ConnectionHandler && obj instanceof byte[]) {
processIncomingMessage((byte[]) obj);
} else if (obs instanceof ConnectionHandler && obj == null) {
terminateSession("ConnectionHandler has notified an error", false);
}
}
public void processIncomingMessage(Object incomingMessage) {
setV2gTpMessage(new V2GTPMessage((byte[]) incomingMessage));
if (getMessageHandler().isV2GTPMessageValid(getV2gTpMessage()) &&
Arrays.equals(getV2gTpMessage().getPayloadType(), GlobalValues.V2GTP_PAYLOAD_TYPE_EXI_ENCODED_V2G_MESSAGE.getByteArrayValue())) {
/*
* Decide which schema to use for decoding the EXI encoded message.
* Only the SupportedAppProtocolReq/Res message uses a different schema
*/
if (getCurrentState().equals(getStates().get(V2GMessages.SUPPORTED_APP_PROTOCOL_REQ))) {
incomingMessage = (SupportedAppProtocolReq) getMessageHandler().exiToSuppAppProtocolMsg(getV2gTpMessage().getPayload());
} else {
incomingMessage = (V2GMessage) getMessageHandler().exiToV2gMsg(getV2gTpMessage().getPayload());
}
processReaction(getCurrentState().processIncomingMessage(incomingMessage));
} else {
getLogger().warn("Received incoming message is not a valid V2GTPMessage", false);
}
}
private void processReaction(ReactionToIncomingMessage reactionToIncomingMessage) {
// Check the outcome of the processIncomingMessage() of the respective state
if (reactionToIncomingMessage instanceof SendMessage) {
send((SendMessage) reactionToIncomingMessage);
if (getChargingSession() != null && getChargingSession() == ChargingSessionType.TERMINATE)
terminateSession("EVCC indicated request to stop the session or a FAILED response code was sent", true);
if (getChargingSession() != null && getChargingSession() == ChargingSessionType.PAUSE) {
pauseSession(new PauseSession());
}
} else if (reactionToIncomingMessage instanceof ChangeProcessingState) {
setCurrentState(((ChangeProcessingState) reactionToIncomingMessage).getNewState());
processReaction(
getCurrentState().processIncomingMessage(
((ChangeProcessingState) reactionToIncomingMessage).getPayload()));
} else if (reactionToIncomingMessage instanceof TerminateSession) {
/*
* TODO is this really needed? if sth. goes wrong, a negative response code will be sent by
* the respective state anyway, the reaction to this negative response code should only
* instantiate a TerminateSession object.
*/
terminateSession(((TerminateSession) reactionToIncomingMessage));
} else {
terminateSession("Reaction to incoming message is undefined", false);
}
}
/**
* Returns a response code according to 8.4.2
* @param header The header encapsulated in the EVCC request message
* @return The corresponding response code
*/
public ResponseCodeType checkSessionID(MessageHeaderType header) {
if (getCurrentState().equals(getStates().get(V2GMessages.SESSION_SETUP_REQ)) &&
ByteUtils.toHexString(header.getSessionID()).equals("00")) {
// EV wants to start a totally new charging session
setSessionID(generateSessionIDRandomly());
setOldSessionJoined(false);
return ResponseCodeType.OK_NEW_SESSION_ESTABLISHED;
} else if (getCurrentState().equals(getStates().get(V2GMessages.SESSION_SETUP_REQ)) &&
Arrays.equals(header.getSessionID(), getSessionID())) {
// A charging pause has taken place and the EV wants to resume the old charging session
setOldSessionJoined(true);
return ResponseCodeType.OK_OLD_SESSION_JOINED;
} else if (getCurrentState().equals(getStates().get(V2GMessages.SESSION_SETUP_REQ)) &&
!ByteUtils.toHexString(header.getSessionID()).equals("00") &&
!Arrays.equals(header.getSessionID(), getSessionID())) {
// Avoid a "FAILED_..." response code by generating a new SessionID in the response
getLogger().warn("Presented session ID '" + ByteUtils.toHexString(header.getSessionID()) + "' does not match stored session ID '" +
ByteUtils.toHexString(getSessionID()) + "'. Will reassign a new session ID");
setSessionID(generateSessionIDRandomly());
setOldSessionJoined(false);
return ResponseCodeType.OK_NEW_SESSION_ESTABLISHED;
} else if (Arrays.equals(header.getSessionID(), getSessionID())) {
// This should be the routine during a running charging session after a session setup
setOldSessionJoined(false);
return ResponseCodeType.OK;
} else {
// EV sends a SessionID DURING the already running charging session which does not match
setOldSessionJoined(false);
return ResponseCodeType.FAILED_UNKNOWN_SESSION;
}
}
@SuppressWarnings("unchecked")
public PaymentOptionListType getPaymentOptions() {
ArrayList<PaymentOptionType> paymentOptions = new ArrayList<PaymentOptionType>();
if (isOldSessionJoined()) {
paymentOptions.add(selectedPaymentOption);
} else {
paymentOptions.addAll((ArrayList<PaymentOptionType>) (MiscUtils.getPropertyValue("authentication.modes.supported")));
}
// Contract-based payment may only be offered if TLS is used
if (!isTlsConnection())
paymentOptions.remove(PaymentOptionType.CONTRACT);
PaymentOptionListType paymentOptionList = new PaymentOptionListType();
paymentOptionList.getPaymentOption().addAll(paymentOptions);
return paymentOptionList;
}
public void send(SendMessage sendMessage) {
// Only EXI encoded messages will be sent here. Decide whether V2GMessage or SupportedAppProtocolRes
byte[] payload = null;
if (sendMessage.getPayload() instanceof V2GMessage) {
payload = (byte[]) getMessageHandler().v2gMsgToExi(sendMessage.getPayload());
} else {
payload = (byte[]) getMessageHandler().suppAppProtocolMsgToExi(sendMessage.getPayload());
}
setV2gTpMessage(
new V2GTPMessage(GlobalValues.V2GTP_VERSION_1_IS.getByteValue(),
GlobalValues.V2GTP_PAYLOAD_TYPE_EXI_ENCODED_V2G_MESSAGE.getByteArrayValue(),
payload)
);
getConnectionHandler().send(getV2gTpMessage());
if (sendMessage.getNextState() != null) {
setCurrentState(sendMessage.getNextState());
} else {
getLogger().info("State machine has come to an end, no new state provided");
}
}
public short getSchemaID() {
return schemaID;
}
public void setSchemaID(short schemaID) {
this.schemaID = schemaID;
}
public ACEVSEStatusType getACEVSEStatus() {
return acEVSEStatus;
}
public void setACEVSEStatus(ACEVSEStatusType acEVSEStatus) {
this.acEVSEStatus = acEVSEStatus;
}
public PMaxScheduleType getPMaxSchedule() {
return pMaxSchedule;
}
public void setPMaxSchedule(PMaxScheduleType newPMaxSchedule) {
this.pMaxSchedule = newPMaxSchedule;
}
public short getChosenSAScheduleTuple() {
return chosenSAScheduleTuple;
}
public void setChosenSAScheduleTuple(short saScheduleTupleID) {
this.chosenSAScheduleTuple = saScheduleTupleID;
}
public IBackendInterface getBackendInterface() {
return backendInterface;
}
public void setBackendInterface(IBackendInterface backendInterface) {
this.backendInterface = backendInterface;
}
public boolean isOldSessionJoined() {
return oldSessionJoined;
}
public void setOldSessionJoined(boolean oldSessionJoined) {
this.oldSessionJoined = oldSessionJoined;
}
public byte[] getIncomingV2GTPMessage() {
return incomingV2GTPMessage;
}
public void setIncomingV2GTPMessage(byte[] incomingV2GTPMessage) {
this.incomingV2GTPMessage = incomingV2GTPMessage;
}
public ConnectionHandler getConnectionHandler() {
return connectionHandler;
}
public void setConnectionHandler(ConnectionHandler connectionHandler) {
this.connectionHandler = connectionHandler;
}
public ArrayList<ServiceType> getOfferedServices() {
return offeredServices;
}
public void setOfferedServices(ArrayList<ServiceType> offeredServices) {
this.offeredServices = offeredServices;
}
public byte[] getGenChallenge() {
return genChallenge;
}
public void setGenChallenge(byte[] genChallenge) {
this.genChallenge = genChallenge;
}
public SAScheduleListType getSaSchedules() {
return saSchedules;
}
public void setSaSchedules(SAScheduleListType saSchedules) {
this.saSchedules = saSchedules;
}
public EnergyTransferModeType getRequestedEnergyTransferMode() {
return requestedEnergyTransferMode;
}
public void setRequestedEnergyTransferMode(
EnergyTransferModeType requestedEnergyTransferMode) {
this.requestedEnergyTransferMode = requestedEnergyTransferMode;
}
public CertificateChainType getContractSignatureCertChain() {
return contractSignatureCertChain;
}
public void setContractSignatureCertChain(CertificateChainType contractSignatureCertChain) {
this.contractSignatureCertChain = contractSignatureCertChain;
}
public MeterInfoType getSentMeterInfo() {
return sentMeterInfo;
}
public void setSentMeterInfo(MeterInfoType sentMeterInfo) {
this.sentMeterInfo = sentMeterInfo;
}
public IACEVSEController getACEvseController() {
return acEvseController;
}
public void setACEvseController(IACEVSEController acEvseController) {
this.acEvseController = acEvseController;
}
public IDCEVSEController getDCEvseController() {
return dcEvseController;
}
public void setDCEvseController(IDCEVSEController dcEvseController) {
this.dcEvseController = dcEvseController;
}
public IEVSEController getEvseController() {
if (getRequestedEnergyTransferMode() != null) {
if (getRequestedEnergyTransferMode().toString().startsWith("AC"))
return acEvseController;
else if (getRequestedEnergyTransferMode().toString().startsWith("DC"))
return dcEvseController;
else {
getLogger().error("RequestedEnergyTransferMode '" + getRequestedEnergyTransferMode().toString() +
"is neither of type AC nor DC");
return null;
}
} else return acEvseController; // just AC controller as default
}
public PaymentOptionType getSelectedPaymentOption() {
return selectedPaymentOption;
}
public void setSelectedPaymentOption(PaymentOptionType selectedPaymentOption) {
this.selectedPaymentOption = selectedPaymentOption;
}
public boolean isChargeProgressStarted() {
return chargeProgressStarted;
}
public void setChargeProgressStarted(boolean chargeProgressStarted) {
this.chargeProgressStarted = chargeProgressStarted;
}
public ChargingSessionType getChargingSession() {
return chargingSession;
}
public void setChargingSession(ChargingSessionType chargingSession) {
this.chargingSession = chargingSession;
}
}

View File

@@ -0,0 +1,116 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.states;
import java.util.ArrayList;
import java.util.List;
import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ChangeProcessingState;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.misc.State;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class ForkState extends ServerState {
private List<V2GMessages> allowedRequests;
public ForkState(V2GCommunicationSessionSECC commSessionContext,
List<V2GMessages> allowedRequests) {
super(commSessionContext);
this.allowedRequests = allowedRequests;
}
public ForkState(V2GCommunicationSessionSECC commSessionContext) {
super(commSessionContext);
this.allowedRequests = new ArrayList<V2GMessages>();
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
V2GMessage v2gMessageReq = (V2GMessage) message;
V2GMessages incomingMessage = null;
try {
incomingMessage =
V2GMessages.fromValue(v2gMessageReq.getBody().getBodyElement().getValue().getClass().getSimpleName());
} catch (NullPointerException e) {
return new TerminateSession("No valid V2GMessage received");
}
State newState = getCommSessionContext().getStates().get(incomingMessage);
if (newState == null) {
getLogger().error("Error occurred while switching from ForkState to a new state: new state is null");
return new TerminateSession("Invalid message (" + v2gMessageReq.getBody().getBodyElement().getValue().getClass().getSimpleName() +
") at this state (" + this.getClass().getSimpleName() + "). " +
"Allowed messages are: " + this.getAllowedRequests().toString());
}
if (allowedRequests.contains(incomingMessage)) {
// delete all allowedRequests so that they won't be valid anymore
allowedRequests.clear();
return new ChangeProcessingState(message, newState);
} else {
getLogger().error("Invalid message (" + v2gMessageReq.getBody().getBodyElement().getValue().getClass().getSimpleName() +
") at this state (" + this.getClass().getSimpleName() + "). " +
"Allowed messages are: " + this.getAllowedRequests().toString());
BodyBaseType responseMessage = getSequenceErrorResMessage(v2gMessageReq);
ServerState newServerState = (ServerState) newState;
return newServerState.getSendMessage(responseMessage, V2GMessages.NONE, ResponseCodeType.FAILED_SEQUENCE_ERROR);
}
}
public List<V2GMessages> getAllowedRequests() {
return allowedRequests;
}
public void setAllowedRequests(List<V2GMessages> allowedRequests) {
this.allowedRequests = allowedRequests;
}
@Override
public String toString() {
String allowedRequests = "";
for (V2GMessages message : getAllowedRequests()) {
allowedRequests += message.getClass().getSimpleName() + ", ";
}
return allowedRequests;
}
@Override
public BodyBaseType getResponseMessage() {
return null;
}
}

View File

@@ -0,0 +1,505 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.states;
import com.v2gclarity.risev2g.secc.evseController.IACEVSEController;
import com.v2gclarity.risev2g.secc.evseController.IDCEVSEController;
import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.SendMessage;
import com.v2gclarity.risev2g.shared.misc.State;
import com.v2gclarity.risev2g.shared.misc.TimeRestrictions;
import com.v2gclarity.risev2g.shared.v2gMessages.appProtocol.SupportedAppProtocolRes;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.AuthorizationResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CableCheckResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateChainType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateInstallationResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateUpdateResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscoveryResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingStatusResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ContractSignatureEncryptedPrivateKeyType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CurrentDemandResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DiffieHellmanPublickeyType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EMAIDType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSEProcessingType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.MeteringReceiptResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentDetailsResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentServiceSelectionResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PhysicalValueType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PowerDeliveryResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PreChargeResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceDetailResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceDiscoveryResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SessionSetupResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SessionStopResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.UnitSymbolType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.WeldingDetectionResType;
public abstract class ServerState extends State {
public ServerState(V2GCommunicationSessionSECC commSessionContext) {
super(commSessionContext);
}
public V2GCommunicationSessionSECC getCommSessionContext() {
return (V2GCommunicationSessionSECC) super.getCommSessionContext();
}
protected boolean isIncomingMessageValid(
Object incomingMessage,
Class<? extends BodyBaseType> expectedMessage,
BodyBaseType responseMessage) {
V2GMessage v2gMessage = null;
ResponseCodeType responseCode = null;
// Check if incoming request is a V2GMessage
if (incomingMessage instanceof V2GMessage) {
v2gMessage = (V2GMessage) incomingMessage;
// Check if incoming request is expected
if (expectedMessage.isAssignableFrom(v2gMessage.getBody().getBodyElement().getValue().getClass())) {
getLogger().debug(v2gMessage.getBody().getBodyElement().getValue().getClass().getSimpleName().replace("Type", "") + " received");
// Check for correct session ID
responseCode = getCommSessionContext().checkSessionID(v2gMessage.getHeader());
} else {
getLogger().fatal("Invalid message (" + v2gMessage.getBody().getBodyElement().getValue().getClass().getSimpleName() +
") at this state (" + this.getClass().getSimpleName() + ")");
responseCode = ResponseCodeType.FAILED_SEQUENCE_ERROR;
}
} else {
getLogger().fatal("Incoming message is not a V2GMessage");
responseCode = ResponseCodeType.FAILED_SEQUENCE_ERROR;
}
switch (responseMessage.getClass().getSimpleName()) {
case "SessionSetupResType":
((SessionSetupResType) responseMessage).setResponseCode(responseCode);
break;
case "ServiceDiscoveryResType":
((ServiceDiscoveryResType) responseMessage).setResponseCode(responseCode);
break;
case "ServiceDetailResType":
((ServiceDetailResType) responseMessage).setResponseCode(responseCode);
break;
case "PaymentServiceSelectionResType":
((PaymentServiceSelectionResType) responseMessage).setResponseCode(responseCode);
break;
case "PaymentDetailsResType":
((PaymentDetailsResType) responseMessage).setResponseCode(responseCode);
break;
case "CertificateInstallationResType":
((CertificateInstallationResType) responseMessage).setResponseCode(responseCode);
break;
case "CertificateUpdateResType":
((CertificateUpdateResType) responseMessage).setResponseCode(responseCode);
break;
case "AuthorizationResType":
((AuthorizationResType) responseMessage).setResponseCode(responseCode);
break;
case "ChargeParameterDiscoveryResType":
((ChargeParameterDiscoveryResType) responseMessage).setResponseCode(responseCode);
break;
case "CableCheckResType":
((CableCheckResType) responseMessage).setResponseCode(responseCode);
break;
case "PreChargeResType":
((PreChargeResType) responseMessage).setResponseCode(responseCode);
break;
case "PowerDeliveryResType":
((PowerDeliveryResType) responseMessage).setResponseCode(responseCode);
break;
case "ChargingStatusResType":
((ChargingStatusResType) responseMessage).setResponseCode(responseCode);
break;
case "CurrentDemandResType":
((CurrentDemandResType) responseMessage).setResponseCode(responseCode);
break;
case "MeteringReceiptResType":
((MeteringReceiptResType) responseMessage).setResponseCode(responseCode);
break;
case "WeldingDetectionResType":
((WeldingDetectionResType) responseMessage).setResponseCode(responseCode);
break;
case "SessionStopResType":
((SessionStopResType) responseMessage).setResponseCode(responseCode);
break;
default:
getLogger().error("Response message could not be identified");
return false;
}
if (responseCode.toString().startsWith("OK")) return true;
else return false;
}
public SendMessage getSendMessage(
BodyBaseType message,
V2GMessages nextExpectedMessage,
ResponseCodeType responseCode) {
int timeout = getTimeout(message, nextExpectedMessage);
if (!responseCode.value().startsWith("OK")) {
getLogger().error("Response code '" + responseCode.value() + "' will be sent.");
getCommSessionContext().setChargingSession(ChargingSessionType.TERMINATE);
}
return getSendMessage(message, nextExpectedMessage, "", timeout);
}
protected SendMessage getSendMessage(
SupportedAppProtocolRes message,
V2GMessages nextExpectedMessage,
com.v2gclarity.risev2g.shared.v2gMessages.appProtocol.ResponseCodeType responseCode) {
String messageName = message.getClass().getSimpleName();
if (!responseCode.value().substring(0, 2).toUpperCase().equals("OK")) {
getLogger().error("Response code '" + responseCode.value() + "' will be sent.");
getCommSessionContext().setChargingSession(ChargingSessionType.TERMINATE);
}
getLogger().debug("Preparing to send " + messageName);
return new SendMessage(message, getCommSessionContext().getStates().get(nextExpectedMessage), TimeRestrictions.V2G_EVCC_COMMUNICATION_SETUP_TIMEOUT);
}
/**
* In case a FAILED response code is sent, the mandatory fields still need to be set with minimum required values,
* otherwise the EVCC's EXI decoder will raise an error.
*
* @param response The respective response message whose mandatory fields are to be set
*/
protected void setMandatoryFieldsForFailedRes(BodyBaseType responseMessage, ResponseCodeType responseCode) {
switch (responseMessage.getClass().getSimpleName()) {
case "SessionSetupResType":
SessionSetupResType sessionSetupRes = (SessionSetupResType) responseMessage;
sessionSetupRes.setEVSEID(getCommSessionContext().getEvseController().getEvseID());
sessionSetupRes.setResponseCode(responseCode);
break;
case "ServiceDiscoveryResType":
ServiceDiscoveryResType serviceDiscoveryRes = (ServiceDiscoveryResType) responseMessage;
serviceDiscoveryRes.setChargeService((new WaitForServiceDiscoveryReq(getCommSessionContext())).getChargeService());
serviceDiscoveryRes.setPaymentOptionList(getCommSessionContext().getPaymentOptions());
serviceDiscoveryRes.setResponseCode(responseCode);
break;
case "ServiceDetailResType":
ServiceDetailResType serviceDetailRes = (ServiceDetailResType) responseMessage;
serviceDetailRes.setServiceID(1);
serviceDetailRes.setResponseCode(responseCode);
break;
case "PaymentServiceSelectionResType":
PaymentServiceSelectionResType paymentServiceSelectionRes = (PaymentServiceSelectionResType) responseMessage;
paymentServiceSelectionRes.setResponseCode(responseCode);
break;
case "PaymentDetailsResType":
PaymentDetailsResType paymentDetailsRes = (PaymentDetailsResType) responseMessage;
paymentDetailsRes.setEVSETimeStamp(0L);
paymentDetailsRes.setGenChallenge(new byte[1]);
paymentDetailsRes.setResponseCode(responseCode);
break;
case "CertificateInstallationResType":
CertificateInstallationResType certificateInstallationRes = (CertificateInstallationResType) responseMessage;
CertificateChainType saProvisioningCertificateChain = new CertificateChainType();
saProvisioningCertificateChain.setCertificate(new byte[1]);
certificateInstallationRes.setSAProvisioningCertificateChain(saProvisioningCertificateChain);
CertificateChainType contractSignatureCertChain = new CertificateChainType();
contractSignatureCertChain.setCertificate(new byte[1]);
contractSignatureCertChain.setId("ID1");
certificateInstallationRes.setContractSignatureCertChain(contractSignatureCertChain);
ContractSignatureEncryptedPrivateKeyType contractSignatureEncryptedPrivateKey = new ContractSignatureEncryptedPrivateKeyType();
contractSignatureEncryptedPrivateKey.setValue(new byte[1]);
contractSignatureEncryptedPrivateKey.setId("ID2");
certificateInstallationRes.setContractSignatureEncryptedPrivateKey(contractSignatureEncryptedPrivateKey);
DiffieHellmanPublickeyType dhPublicKeyType = new DiffieHellmanPublickeyType();
dhPublicKeyType.setValue(new byte[1]);
dhPublicKeyType.setId("ID3");
certificateInstallationRes.setDHpublickey(dhPublicKeyType);
EMAIDType emaid = new EMAIDType();
emaid.setValue("DEV2G1234512345");
emaid.setId("ID4");
certificateInstallationRes.setEMAID(emaid);
certificateInstallationRes.setResponseCode(responseCode);
break;
case "CertificateUpdateResType":
CertificateUpdateResType certificateUpdateRes = (CertificateUpdateResType) responseMessage;
CertificateChainType saProvisioningCertificateChain2 = new CertificateChainType();
saProvisioningCertificateChain2.setCertificate(new byte[1]);
certificateUpdateRes.setSAProvisioningCertificateChain(saProvisioningCertificateChain2);
CertificateChainType contractSignatureCertChain2 = new CertificateChainType();
contractSignatureCertChain2.setCertificate(new byte[1]);
contractSignatureCertChain2.setId("ID1");
certificateUpdateRes.setContractSignatureCertChain(contractSignatureCertChain2);
ContractSignatureEncryptedPrivateKeyType contractSignatureEncryptedPrivateKey2 = new ContractSignatureEncryptedPrivateKeyType();
contractSignatureEncryptedPrivateKey2.setValue(new byte[1]);
contractSignatureEncryptedPrivateKey2.setId("ID2");
certificateUpdateRes.setContractSignatureEncryptedPrivateKey(contractSignatureEncryptedPrivateKey2);
DiffieHellmanPublickeyType dhPublicKeyType2 = new DiffieHellmanPublickeyType();
dhPublicKeyType2.setValue(new byte[1]);
dhPublicKeyType2.setId("ID3");
certificateUpdateRes.setDHpublickey(dhPublicKeyType2);
EMAIDType emaid2 = new EMAIDType();
emaid2.setValue("DEV2G1234512345");
emaid2.setId("ID4");
certificateUpdateRes.setEMAID(emaid2);
certificateUpdateRes.setRetryCounter((short) 0); // according to [V2G2-696] and [V2G2-928]
certificateUpdateRes.setResponseCode(responseCode);
break;
case "AuthorizationResType":
AuthorizationResType authorizationRes = (AuthorizationResType) responseMessage;
authorizationRes.setEVSEProcessing(EVSEProcessingType.FINISHED);
authorizationRes.setResponseCode(responseCode);
break;
case "ChargeParameterDiscoveryResType":
ChargeParameterDiscoveryResType chargeParameterDiscoveryRes = (ChargeParameterDiscoveryResType) responseMessage;
chargeParameterDiscoveryRes.setEVSEProcessing(EVSEProcessingType.FINISHED);
chargeParameterDiscoveryRes.setEVSEChargeParameter(
((IACEVSEController) getCommSessionContext().getACEvseController()).getACEVSEChargeParameter());
chargeParameterDiscoveryRes.setResponseCode(responseCode);
break;
case "CableCheckResType":
CableCheckResType cableCheckRes = (CableCheckResType) responseMessage;
cableCheckRes.setEVSEProcessing(EVSEProcessingType.FINISHED);
cableCheckRes.setDCEVSEStatus(
((IDCEVSEController) getCommSessionContext().getDCEvseController()).getDCEVSEStatus(EVSENotificationType.NONE)
);
cableCheckRes.setResponseCode(responseCode);
break;
case "PreChargeResType":
PreChargeResType preChargeRes = (PreChargeResType) responseMessage;
IDCEVSEController evseController = getCommSessionContext().getDCEvseController();
preChargeRes.setDCEVSEStatus(evseController.getDCEVSEStatus(EVSENotificationType.NONE));
PhysicalValueType evsePresentVoltage = new PhysicalValueType();
evsePresentVoltage.setMultiplier(new Byte("0"));
evsePresentVoltage.setUnit(UnitSymbolType.V);
evsePresentVoltage.setValue((short) 0);
preChargeRes.setEVSEPresentVoltage(evsePresentVoltage);
preChargeRes.setResponseCode(responseCode);
break;
case "PowerDeliveryResType":
PowerDeliveryResType powerDeliveryRes = (PowerDeliveryResType) responseMessage;
(new WaitForPowerDeliveryReq(getCommSessionContext())).setEVSEStatus(powerDeliveryRes);
powerDeliveryRes.setResponseCode(responseCode);
break;
case "ChargingStatusResType":
ChargingStatusResType chargingStatusRes = (ChargingStatusResType) responseMessage;
chargingStatusRes.setEVSEID(getCommSessionContext().getACEvseController().getEvseID());
chargingStatusRes.setSAScheduleTupleID((short) 1);
chargingStatusRes.setACEVSEStatus(((IACEVSEController) getCommSessionContext().getACEvseController())
.getACEVSEStatus(EVSENotificationType.NONE)
);
chargingStatusRes.setResponseCode(responseCode);
break;
case "CurrentDemandResType":
CurrentDemandResType currentDemandRes = (CurrentDemandResType) responseMessage;
IDCEVSEController evseController2 = (IDCEVSEController) getCommSessionContext().getDCEvseController();
PhysicalValueType physicalValueType = new PhysicalValueType();
physicalValueType.setMultiplier(new Byte("0"));
physicalValueType.setUnit(UnitSymbolType.V); // does not matter which unit symbol if FAILED response is sent
physicalValueType.setValue((short) 1);
currentDemandRes.setDCEVSEStatus(evseController2.getDCEVSEStatus(EVSENotificationType.NONE));
currentDemandRes.setEVSEPresentVoltage(physicalValueType);
currentDemandRes.setEVSEPresentCurrent(physicalValueType);
currentDemandRes.setEVSECurrentLimitAchieved(false);
currentDemandRes.setEVSEVoltageLimitAchieved(false);
currentDemandRes.setEVSEPowerLimitAchieved(false);
currentDemandRes.setEVSEID(evseController2.getEvseID());
currentDemandRes.setSAScheduleTupleID((short) 1);
currentDemandRes.setResponseCode(responseCode);
break;
case "MeteringReceiptResType":
MeteringReceiptResType meteringReceiptRes = (MeteringReceiptResType) responseMessage;
(new WaitForMeteringReceiptReq(getCommSessionContext())).setEVSEStatus(meteringReceiptRes);
meteringReceiptRes.setResponseCode(responseCode);
break;
case "WeldingDetectionResType":
WeldingDetectionResType weldingDetectionRes = (WeldingDetectionResType) responseMessage;
IDCEVSEController evseController3 = (IDCEVSEController) getCommSessionContext().getDCEvseController();
weldingDetectionRes.setDCEVSEStatus(evseController3.getDCEVSEStatus(EVSENotificationType.NONE));
PhysicalValueType evsePresentVoltage2 = new PhysicalValueType();
evsePresentVoltage2.setMultiplier(new Byte("0"));
evsePresentVoltage2.setUnit(UnitSymbolType.V);
evsePresentVoltage2.setValue((short) 0);
weldingDetectionRes.setEVSEPresentVoltage(evsePresentVoltage2);
weldingDetectionRes.setResponseCode(responseCode);
break;
case "SessionStopResType":
SessionStopResType sessionStopRes = (SessionStopResType) responseMessage;
sessionStopRes.setResponseCode(responseCode);
break;
default:
getLogger().error("Response message could not be identified");
}
}
protected BodyBaseType getSequenceErrorResMessage(Object incomingMessage) {
if (incomingMessage instanceof V2GMessage) {
V2GMessage v2gMessage = (V2GMessage) incomingMessage;
String className = v2gMessage.getBody().getBodyElement().getValue().getClass().getSimpleName();
BodyBaseType responseMessage = null;
switch (className) {
case "SessionSetupReqType":
SessionSetupResType sessionSetupRes = new SessionSetupResType();
sessionSetupRes.setResponseCode(ResponseCodeType.FAILED_SEQUENCE_ERROR);
responseMessage = sessionSetupRes;
break;
case "ServiceDiscoveryReqType":
ServiceDiscoveryResType serviceDiscoveryRes = new ServiceDiscoveryResType();
serviceDiscoveryRes.setResponseCode(ResponseCodeType.FAILED_SEQUENCE_ERROR);
responseMessage = serviceDiscoveryRes;
break;
case "ServiceDetailReqType":
ServiceDetailResType serviceDetailRes = new ServiceDetailResType();
serviceDetailRes.setResponseCode(ResponseCodeType.FAILED_SEQUENCE_ERROR);
responseMessage = serviceDetailRes;
break;
case "PaymentServiceSelectionReqType":
PaymentServiceSelectionResType paymentServiceSelectionRes = new PaymentServiceSelectionResType();
paymentServiceSelectionRes.setResponseCode(ResponseCodeType.FAILED_SEQUENCE_ERROR);
responseMessage = paymentServiceSelectionRes;
break;
case "PaymentDetailsReqType":
PaymentDetailsResType paymentDetailsRes = new PaymentDetailsResType();
paymentDetailsRes.setResponseCode(ResponseCodeType.FAILED_SEQUENCE_ERROR);
responseMessage = paymentDetailsRes;
break;
case "CertificateInstallationReqType":
CertificateInstallationResType certificateInstallationRes = new CertificateInstallationResType();
certificateInstallationRes.setResponseCode(ResponseCodeType.FAILED_SEQUENCE_ERROR);
responseMessage = certificateInstallationRes;
break;
case "CertificateUpdateReqType":
CertificateUpdateResType certificateUpdateRes = new CertificateUpdateResType();
certificateUpdateRes.setResponseCode(ResponseCodeType.FAILED_SEQUENCE_ERROR);
responseMessage = certificateUpdateRes;
break;
case "AuthorizationReqType":
AuthorizationResType authorizationRes = new AuthorizationResType();
authorizationRes.setResponseCode(ResponseCodeType.FAILED_SEQUENCE_ERROR);
responseMessage = authorizationRes;
break;
case "ChargeParameterDiscoveryReqType":
ChargeParameterDiscoveryResType chargeParameterDiscoveryRes = new ChargeParameterDiscoveryResType();
chargeParameterDiscoveryRes.setResponseCode(ResponseCodeType.FAILED_SEQUENCE_ERROR);
responseMessage = chargeParameterDiscoveryRes;
break;
case "CableCheckReqType":
CableCheckResType cableCheckRes = new CableCheckResType();
cableCheckRes.setResponseCode(ResponseCodeType.FAILED_SEQUENCE_ERROR);
responseMessage = cableCheckRes;
break;
case "PreChargeReqType":
PreChargeResType preChargeRes = new PreChargeResType();
preChargeRes.setResponseCode(ResponseCodeType.FAILED_SEQUENCE_ERROR);
responseMessage = preChargeRes;
break;
case "PowerDeliveryReqType":
PowerDeliveryResType powerDeliveryResType = new PowerDeliveryResType();
powerDeliveryResType.setResponseCode(ResponseCodeType.FAILED_SEQUENCE_ERROR);
responseMessage = powerDeliveryResType;
break;
case "ChargingStatusReqType":
ChargingStatusResType chargingStatusRes = new ChargingStatusResType();
chargingStatusRes.setResponseCode(ResponseCodeType.FAILED_SEQUENCE_ERROR);
responseMessage = chargingStatusRes;
break;
case "CurrentDemandReqType":
CurrentDemandResType currentDemandRes = new CurrentDemandResType();
currentDemandRes.setResponseCode(ResponseCodeType.FAILED_SEQUENCE_ERROR);
responseMessage = currentDemandRes;
break;
case "MeteringReceiptReqType":
MeteringReceiptResType meteringReceiptRes = new MeteringReceiptResType();
meteringReceiptRes.setResponseCode(ResponseCodeType.FAILED_SEQUENCE_ERROR);
responseMessage = meteringReceiptRes;
break;
case "WeldingDetectionReqType":
WeldingDetectionResType weldingDetectionRes = new WeldingDetectionResType();
weldingDetectionRes.setResponseCode(ResponseCodeType.FAILED_SEQUENCE_ERROR);
responseMessage = weldingDetectionRes;
break;
case "SessionStopReqType":
SessionStopResType sessionStopRes = new SessionStopResType();
sessionStopRes.setResponseCode(ResponseCodeType.FAILED_SEQUENCE_ERROR);
responseMessage = sessionStopRes;
break;
default:
getLogger().error("Response message could not be identified");
}
setMandatoryFieldsForFailedRes(responseMessage, ResponseCodeType.FAILED_SEQUENCE_ERROR);
return responseMessage;
} else {
return null;
}
}
protected BodyBaseType getSequenceErrorResMessage(
BodyBaseType currentStateRes,
Object incomingMessage) {
BodyBaseType responseMessage = getSequenceErrorResMessage(incomingMessage);
// Check in case the switch statement did not match a proper ISO 15118 request message
if (responseMessage != null) {
return responseMessage;
} else {
setMandatoryFieldsForFailedRes(currentStateRes, ResponseCodeType.FAILED_SEQUENCE_ERROR);
return currentStateRes;
}
}
/**
* Needed for the ForkState to get the respective response message which can be used to instantiate a
* SendMessage() object in case of a sequence error
*/
public abstract BodyBaseType getResponseMessage();
}

View File

@@ -0,0 +1,146 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.states;
import java.util.Arrays;
import java.util.HashMap;
import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.AuthorizationReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.AuthorizationResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSEProcessingType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SignatureType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForAuthorizationReq extends ServerState {
private AuthorizationResType authorizationRes;
private boolean authorizationFinished;
public WaitForAuthorizationReq(V2GCommunicationSessionSECC commSessionContext) {
super(commSessionContext);
authorizationRes = new AuthorizationResType();
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, AuthorizationReqType.class, authorizationRes)) {
V2GMessage v2gMessageReq = (V2GMessage) message;
AuthorizationReqType authorizationReq =
(AuthorizationReqType) v2gMessageReq.getBody().getBodyElement().getValue();
if (isResponseCodeOK(authorizationReq, v2gMessageReq.getHeader().getSignature())) {
/*
* TODO start a Thread which authenticates the EVCC and sets the class-variable
* authenticationFinished (and remove setAuthorizationFinished(true) here!)
*/
setAuthorizationFinished(true);
if (isAuthorizationFinished()) {
authorizationRes.setEVSEProcessing(EVSEProcessingType.FINISHED);
return getSendMessage(authorizationRes, V2GMessages.CHARGE_PARAMETER_DISCOVERY_REQ);
} else {
authorizationRes.setEVSEProcessing(EVSEProcessingType.ONGOING);
return getSendMessage(authorizationRes, V2GMessages.AUTHORIZATION_REQ);
}
} else {
setMandatoryFieldsForFailedRes(authorizationRes, authorizationRes.getResponseCode());
}
} else {
if (authorizationRes.getResponseCode().equals(ResponseCodeType.FAILED_SEQUENCE_ERROR)) {
BodyBaseType responseMessage = getSequenceErrorResMessage(new AuthorizationReqType(), message);
return getSendMessage(responseMessage, V2GMessages.NONE, authorizationRes.getResponseCode());
} else {
setMandatoryFieldsForFailedRes(authorizationRes, authorizationRes.getResponseCode());
}
}
return getSendMessage(authorizationRes, V2GMessages.NONE, authorizationRes.getResponseCode());
}
public boolean isResponseCodeOK(AuthorizationReqType authorizationReq, SignatureType signature) {
if (getCommSessionContext().getSelectedPaymentOption().equals(PaymentOptionType.EXTERNAL_PAYMENT)) {
if (authorizationReq.getGenChallenge() != null)
getLogger().warn("EVCC sent a challenge parameter but " + PaymentOptionType.EXTERNAL_PAYMENT +
" has been chosen. The challenge parameter should not be present and will be ignored.");
return true;
}
if (!Arrays.equals(authorizationReq.getGenChallenge(), getCommSessionContext().getGenChallenge())) {
authorizationRes.setResponseCode(ResponseCodeType.FAILED_CHALLENGE_INVALID);
return false;
}
/*
* Only try to verify the signature in case we use a TLS connection and 'Contract' has been chosen as payment
* method. If EIM has been chosen, then no contract certificate chain and not challenge will be sent by the EV,
* but TLS is possible with both EIM and PnC.
*/
if (getCommSessionContext().isTlsConnection() &&
getCommSessionContext().getSelectedPaymentOption().equals(PaymentOptionType.CONTRACT)) {
// Verify signature
HashMap<String, byte[]> verifyXMLSigRefElements = new HashMap<String, byte[]>();
verifyXMLSigRefElements.put(
authorizationReq.getId(),
SecurityUtils.generateDigest(
authorizationReq.getId(),
getMessageHandler().getJaxbElement(authorizationReq)));
if (!SecurityUtils.verifySignature(
signature,
getMessageHandler().getJaxbElement(signature.getSignedInfo()),
verifyXMLSigRefElements,
getCommSessionContext().getContractSignatureCertChain().getCertificate())) {
authorizationRes.setResponseCode(ResponseCodeType.FAILED_SIGNATURE_ERROR);
return false;
}
}
return true;
}
public boolean isAuthorizationFinished() {
return authorizationFinished;
}
public void setAuthorizationFinished(boolean authorizationFinished) {
this.authorizationFinished = authorizationFinished;
}
@Override
public BodyBaseType getResponseMessage() {
return authorizationRes;
}
}

View File

@@ -0,0 +1,106 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.states;
import com.v2gclarity.risev2g.secc.evseController.IDCEVSEController;
import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CableCheckReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CableCheckResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSEProcessingType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.IsolationLevelType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForCableCheckReq extends ServerState {
private CableCheckResType cableCheckRes;
private boolean evseProcessingFinished;
public WaitForCableCheckReq(V2GCommunicationSessionSECC commSessionContext) {
super(commSessionContext);
cableCheckRes = new CableCheckResType();
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, CableCheckReqType.class, cableCheckRes)) {
V2GMessage v2gMessageReq = (V2GMessage) message;
CableCheckReqType cableCheckReq =
(CableCheckReqType) v2gMessageReq.getBody().getBodyElement().getValue();
// TODO how to react to failure status of DCEVStatus of cableCheckReq?
/*
* TODO we need a timeout mechanism here so that a response can be sent within 2s
* the DCEVSEStatus should be generated according to already available values
* (if EVSEProcessing == ONGOING, maybe because of EVSE_IsolationMonitoringActive,
* within a certain timeout, then the status must be different)
*/
setEvseProcessingFinished(true);
if (isEvseProcessingFinished()) {
// As soon as EVSEProcessing is set to Finished, the IsolationLevelType should be set to valid
getCommSessionContext().getDCEvseController().setIsolationLevel(IsolationLevelType.VALID);
cableCheckRes.setEVSEProcessing(EVSEProcessingType.FINISHED);
cableCheckRes.setDCEVSEStatus(
((IDCEVSEController) getCommSessionContext().getDCEvseController()).getDCEVSEStatus(EVSENotificationType.NONE)
);
return getSendMessage(cableCheckRes, V2GMessages.PRE_CHARGE_REQ);
} else {
cableCheckRes.setEVSEProcessing(EVSEProcessingType.ONGOING);
return getSendMessage(cableCheckRes, V2GMessages.CABLE_CHECK_REQ);
}
} else {
if (cableCheckRes.getResponseCode().equals(ResponseCodeType.FAILED_SEQUENCE_ERROR)) {
BodyBaseType responseMessage = getSequenceErrorResMessage(new CableCheckResType(), message);
return getSendMessage(responseMessage, V2GMessages.NONE, cableCheckRes.getResponseCode());
} else {
setMandatoryFieldsForFailedRes(cableCheckRes, cableCheckRes.getResponseCode());
}
}
return getSendMessage(cableCheckRes, V2GMessages.NONE, cableCheckRes.getResponseCode());
}
public boolean isEvseProcessingFinished() {
return evseProcessingFinished;
}
public void setEvseProcessingFinished(boolean evseProcessingFinished) {
this.evseProcessingFinished = evseProcessingFinished;
}
@Override
public BodyBaseType getResponseMessage() {
return cableCheckRes;
}
}

View File

@@ -0,0 +1,198 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.states;
import java.security.KeyPair;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.util.HashMap;
import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import com.v2gclarity.risev2g.shared.enumerations.PKI;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateChainType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateInstallationReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateInstallationResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ContractSignatureEncryptedPrivateKeyType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SignatureType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForCertificateInstallationReq extends ServerState {
private CertificateInstallationResType certificateInstallationRes;
public WaitForCertificateInstallationReq(V2GCommunicationSessionSECC commSessionContext) {
super(commSessionContext);
certificateInstallationRes = new CertificateInstallationResType();
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, CertificateInstallationReqType.class, certificateInstallationRes)) {
V2GMessage v2gMessageReq = (V2GMessage) message;
CertificateInstallationReqType certificateInstallationReq = (CertificateInstallationReqType) v2gMessageReq.getBody().getBodyElement().getValue();
CertificateChainType saContractCertificateChain =
getCommSessionContext().getBackendInterface().getContractCertificateChain(
SecurityUtils.getCertificate(certificateInstallationReq.getOEMProvisioningCert())
);
if (isResponseCodeOK(
certificateInstallationReq,
saContractCertificateChain,
v2gMessageReq.getHeader().getSignature())) {
// The EC key pair is also needed for the generation of the shared secret
KeyPair ecKeyPair = SecurityUtils.getECKeyPair();
if (ecKeyPair == null) {
getLogger().error("EC keypair could not be generated");
return null;
}
// Encrypt private key
ContractSignatureEncryptedPrivateKeyType encryptedContractCertPrivateKey =
SecurityUtils.encryptContractCertPrivateKey(
(ECPublicKey) SecurityUtils.getCertificate(certificateInstallationReq.getOEMProvisioningCert()).getPublicKey(),
(ECPrivateKey) ecKeyPair.getPrivate(),
getCommSessionContext().getBackendInterface().getContractCertificatePrivateKey());
certificateInstallationRes.setContractSignatureCertChain(saContractCertificateChain);
/*
* Experience from the test symposium in San Diego (April 2016):
* The Id element of the signature is not restricted in size by the standard itself. But on embedded
* systems, the memory is very limited which is why we should not use long IDs for the signature reference
* element. A good size would be 3 characters max (like the example in the ISO 15118-2 annex J)
*/
certificateInstallationRes.getContractSignatureCertChain().setId("id1"); // contractSignatureCertChain
certificateInstallationRes.setContractSignatureEncryptedPrivateKey(encryptedContractCertPrivateKey);
certificateInstallationRes.getContractSignatureEncryptedPrivateKey().setId("id2"); // contractSignatureEncryptedPrivateKey
certificateInstallationRes.setDHpublickey(SecurityUtils.getDHPublicKey(ecKeyPair));
certificateInstallationRes.getDHpublickey().setId("id3"); // dhPublicKey
certificateInstallationRes.setEMAID(SecurityUtils.getEMAID(saContractCertificateChain));
certificateInstallationRes.getEMAID().setId("id4"); // emaid
certificateInstallationRes.setSAProvisioningCertificateChain(
getCommSessionContext().getBackendInterface().getCPSCertificateChain());
// Set xml reference elements
getXMLSignatureRefElements().put(
certificateInstallationRes.getContractSignatureCertChain().getId(),
SecurityUtils.generateDigest(
certificateInstallationRes.getContractSignatureCertChain().getId(),
getMessageHandler().getJaxbElement(certificateInstallationRes.getContractSignatureCertChain())));
getXMLSignatureRefElements().put(
certificateInstallationRes.getContractSignatureEncryptedPrivateKey().getId(),
SecurityUtils.generateDigest(
certificateInstallationRes.getContractSignatureEncryptedPrivateKey().getId(),
getMessageHandler().getJaxbElement(certificateInstallationRes.getContractSignatureEncryptedPrivateKey())));
getXMLSignatureRefElements().put(
certificateInstallationRes.getDHpublickey().getId(),
SecurityUtils.generateDigest(
certificateInstallationRes.getDHpublickey().getId(),
getMessageHandler().getJaxbElement(certificateInstallationRes.getDHpublickey())));
getXMLSignatureRefElements().put(
certificateInstallationRes.getEMAID().getId(),
SecurityUtils.generateDigest(
certificateInstallationRes.getEMAID().getId(),
getMessageHandler().getJaxbElement(certificateInstallationRes.getEMAID())));
// Set signing private key
setSignaturePrivateKey(getCommSessionContext().getBackendInterface().getCPSLeafPrivateKey());
} else {
setMandatoryFieldsForFailedRes(certificateInstallationRes, certificateInstallationRes.getResponseCode());
}
} else {
if (certificateInstallationRes.getResponseCode().equals(ResponseCodeType.FAILED_SEQUENCE_ERROR)) {
BodyBaseType responseMessage = getSequenceErrorResMessage(new CertificateInstallationResType(), message);
return getSendMessage(responseMessage, V2GMessages.NONE, certificateInstallationRes.getResponseCode());
} else {
setMandatoryFieldsForFailedRes(certificateInstallationRes, certificateInstallationRes.getResponseCode());
}
}
return getSendMessage(certificateInstallationRes, V2GMessages.PAYMENT_DETAILS_REQ, certificateInstallationRes.getResponseCode());
}
private boolean isResponseCodeOK(
CertificateInstallationReqType certificateInstallationReq,
CertificateChainType saContractCertificateChain,
SignatureType signature) {
ResponseCodeType responseCode = null;
X509Certificate oemProvCert = SecurityUtils.getCertificate(
certificateInstallationReq.getOEMProvisioningCert()
);
// Check for FAILED_NoCertificateAvailable
if (saContractCertificateChain == null || saContractCertificateChain.getCertificate() == null) {
certificateInstallationRes.setResponseCode(ResponseCodeType.FAILED_NO_CERTIFICATE_AVAILABLE);
return false;
}
// Check for FAILED_CertificateExpired
responseCode = SecurityUtils.verifyValidityPeriod(oemProvCert);
if (!responseCode.equals(ResponseCodeType.OK)) {
certificateInstallationRes.setResponseCode(responseCode);
return false;
}
// Check for correct attributes (e.g. correct domain component)
responseCode = SecurityUtils.verifyLeafCertificateAttributes(oemProvCert, PKI.OEM);
if (!responseCode.equals(ResponseCodeType.OK)) {
// FAILED_CertChainError is not defined for CertificateInstallationRes, that's why I set it to FAILED
certificateInstallationRes.setResponseCode(ResponseCodeType.FAILED);
return false;
}
// Check for FAILED_CertificateRevoked
// TODO check for revocation with OCSP
// Verify signature
HashMap<String, byte[]> verifyXMLSigRefElements = new HashMap<String, byte[]>();
verifyXMLSigRefElements.put(
certificateInstallationReq.getId(),
SecurityUtils.generateDigest(
certificateInstallationReq.getId(),
getMessageHandler().getJaxbElement(certificateInstallationReq)));
if (!SecurityUtils.verifySignature(
signature,
getMessageHandler().getJaxbElement(signature.getSignedInfo()),
verifyXMLSigRefElements,
certificateInstallationReq.getOEMProvisioningCert())) {
certificateInstallationRes.setResponseCode(ResponseCodeType.FAILED_SIGNATURE_ERROR);
return false;
}
return true;
}
@Override
public BodyBaseType getResponseMessage() {
return certificateInstallationRes;
}
}

View File

@@ -0,0 +1,195 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.states;
import java.security.KeyPair;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.util.HashMap;
import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.enumerations.PKI;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateChainType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateUpdateReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateUpdateResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ContractSignatureEncryptedPrivateKeyType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SignatureType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForCertificateUpdateReq extends ServerState {
private CertificateUpdateResType certificateUpdateRes;
public WaitForCertificateUpdateReq(V2GCommunicationSessionSECC commSessionContext) {
super(commSessionContext);
certificateUpdateRes = new CertificateUpdateResType();
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, CertificateUpdateReqType.class, certificateUpdateRes)) {
V2GMessage v2gMessageReq = (V2GMessage) message;
CertificateUpdateReqType certificateUpdateReq =
(CertificateUpdateReqType) v2gMessageReq.getBody().getBodyElement().getValue();
if (isResponseCodeOK(
certificateUpdateReq,
v2gMessageReq.getHeader().getSignature())) {
// The ECDH (elliptic curve Diffie Hellman) key pair is also needed for the generation of the shared secret
KeyPair ecdhKeyPair = SecurityUtils.getECKeyPair();
if (ecdhKeyPair == null) {
getLogger().error("ECDH keypair could not be generated");
return null;
}
// Encrypt private key
ContractSignatureEncryptedPrivateKeyType encryptedContractCertPrivateKey =
SecurityUtils.encryptContractCertPrivateKey(
(ECPublicKey) SecurityUtils.getCertificate(certificateUpdateReq.getContractSignatureCertChain().getCertificate()).getPublicKey(),
(ECPrivateKey) ecdhKeyPair.getPrivate(),
getCommSessionContext().getBackendInterface().getContractCertificatePrivateKey());
/*
* Experience from the test symposium in San Diego (April 2016):
* The Id element of the signature is not restricted in size by the standard itself. But on embedded
* systems, the memory is very limited which is why we should not use long IDs for the signature reference
* element. A good size would be 3 characters max (like the example in the ISO 15118-2 annex J)
*/
certificateUpdateRes.getContractSignatureCertChain().setId("id1"); // contractSignatureCertChain
certificateUpdateRes.setContractSignatureEncryptedPrivateKey(encryptedContractCertPrivateKey);
certificateUpdateRes.getContractSignatureEncryptedPrivateKey().setId("id2"); // contractSignatureEncryptedPrivateKey
certificateUpdateRes.setDHpublickey(SecurityUtils.getDHPublicKey(ecdhKeyPair));
certificateUpdateRes.getDHpublickey().setId("id3"); // dhPublicKey
certificateUpdateRes.setEMAID(SecurityUtils.getEMAID(certificateUpdateReq.getContractSignatureCertChain()));
certificateUpdateRes.getEMAID().setId("id4"); // emaid
certificateUpdateRes.setSAProvisioningCertificateChain(getCommSessionContext().getBackendInterface().getCPSCertificateChain());
// In case of negative response code, try at next charging (retryCounter = 0)
if (!certificateUpdateRes.getResponseCode().toString().startsWith("OK"))
certificateUpdateRes.setRetryCounter((short) 0);
// Set xml reference elements
getXMLSignatureRefElements().put(
certificateUpdateRes.getContractSignatureCertChain().getId(),
SecurityUtils.generateDigest(
certificateUpdateRes.getContractSignatureCertChain().getId(),
getMessageHandler().getJaxbElement(certificateUpdateRes.getContractSignatureCertChain())));
getXMLSignatureRefElements().put(
certificateUpdateRes.getContractSignatureEncryptedPrivateKey().getId(),
SecurityUtils.generateDigest(
certificateUpdateRes.getContractSignatureEncryptedPrivateKey().getId(),
getMessageHandler().getJaxbElement(certificateUpdateRes.getContractSignatureEncryptedPrivateKey())));
getXMLSignatureRefElements().put(
certificateUpdateRes.getDHpublickey().getId(),
SecurityUtils.generateDigest(
certificateUpdateRes.getDHpublickey().getId(),
getMessageHandler().getJaxbElement(certificateUpdateRes.getDHpublickey())));
getXMLSignatureRefElements().put(
certificateUpdateRes.getEMAID().getId(),
SecurityUtils.generateDigest(
certificateUpdateRes.getEMAID().getId(),
getMessageHandler().getJaxbElement(certificateUpdateRes.getEMAID())));
// Set signing private key
setSignaturePrivateKey(getCommSessionContext().getBackendInterface().getCPSLeafPrivateKey());
} else {
setMandatoryFieldsForFailedRes(certificateUpdateRes, certificateUpdateRes.getResponseCode());
}
} else {
if (certificateUpdateRes.getResponseCode().equals(ResponseCodeType.FAILED_SEQUENCE_ERROR)) {
BodyBaseType responseMessage = getSequenceErrorResMessage(new CertificateUpdateResType(), message);
return getSendMessage(responseMessage, V2GMessages.NONE, certificateUpdateRes.getResponseCode());
} else {
setMandatoryFieldsForFailedRes(certificateUpdateRes, certificateUpdateRes.getResponseCode());
}
}
return getSendMessage(certificateUpdateRes,
(certificateUpdateRes.getResponseCode().toString().startsWith("OK") ?
V2GMessages.PAYMENT_DETAILS_REQ : V2GMessages.NONE),
certificateUpdateRes.getResponseCode()
);
}
private boolean isResponseCodeOK(
CertificateUpdateReqType certificateUpdateReq,
SignatureType signature) {
// Check for FAILED_NoCertificateAvailable
CertificateChainType saContractCertificateChain =
getCommSessionContext().getBackendInterface().getContractCertificateChain(
certificateUpdateReq.getContractSignatureCertChain()
);
if (saContractCertificateChain == null || saContractCertificateChain.getCertificate() == null) {
certificateUpdateRes.setResponseCode(ResponseCodeType.FAILED_NO_CERTIFICATE_AVAILABLE);
return false;
} else {
certificateUpdateRes.setContractSignatureCertChain(saContractCertificateChain);
}
// Check complete contract certificate chain
ResponseCodeType certChainResponseCode = SecurityUtils.verifyCertificateChain(
certificateUpdateReq.getContractSignatureCertChain(),
GlobalValues.SECC_TRUSTSTORE_FILEPATH.toString(),
PKI.MO);
if (!certChainResponseCode.equals(ResponseCodeType.OK)) {
certificateUpdateRes.setResponseCode(certChainResponseCode);
return false;
}
// Check for FAILED_CertificateRevoked
// TODO check for revocation with OCSP
// Verify signature
HashMap<String, byte[]> verifyXMLSigRefElements = new HashMap<String, byte[]>();
verifyXMLSigRefElements.put(
certificateUpdateReq.getId(),
SecurityUtils.generateDigest(
certificateUpdateReq.getId(),
getMessageHandler().getJaxbElement(certificateUpdateReq)));
if (!SecurityUtils.verifySignature(
signature,
getMessageHandler().getJaxbElement(signature.getSignedInfo()),
verifyXMLSigRefElements,
certificateUpdateReq.getContractSignatureCertChain().getCertificate())) {
certificateUpdateRes.setResponseCode(ResponseCodeType.FAILED_SIGNATURE_ERROR);
return false;
}
return true;
}
@Override
public BodyBaseType getResponseMessage() {
return certificateUpdateRes;
}
}

View File

@@ -0,0 +1,300 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.states;
import java.util.ArrayList;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import com.v2gclarity.risev2g.secc.evseController.IACEVSEController;
import com.v2gclarity.risev2g.secc.evseController.IDCEVSEController;
import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ACEVChargeParameterType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscoveryReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscoveryResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVChargeParameterType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSEProcessingType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EnergyTransferModeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.IsolationLevelType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SAScheduleListType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForChargeParameterDiscoveryReq extends ServerState {
private ChargeParameterDiscoveryResType chargeParameterDiscoveryRes;
private boolean waitingForSchedule;
public WaitForChargeParameterDiscoveryReq(V2GCommunicationSessionSECC commSessionContext) {
super(commSessionContext);
chargeParameterDiscoveryRes = new ChargeParameterDiscoveryResType();
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, ChargeParameterDiscoveryReqType.class, chargeParameterDiscoveryRes)) {
V2GMessage v2gMessageReq = (V2GMessage) message;
ChargeParameterDiscoveryReqType chargeParameterDiscoveryReq =
(ChargeParameterDiscoveryReqType) v2gMessageReq.getBody().getBodyElement().getValue();
if (isResponseCodeOK(chargeParameterDiscoveryReq)) {
getCommSessionContext().setRequestedEnergyTransferMode(
chargeParameterDiscoveryReq.getRequestedEnergyTransferMode());
/*
* Request a new schedule in case of first ChargeParameterDiscoveryReq.
* If EVSEProcessingType.ONGOING was sent in previous ChargeParameterDiscoveryRes
* message, do not request again.
*/
if (!isWaitingForSchedule()) {
// TODO we need a timeout mechanism here so that a response can be sent within 2s
setWaitingForSchedule(true);
// The max. number of PMaxScheduleEntries and SalesTariffEntries is 1024 if not provided otherwise by EVCC
int maxEntriesSAScheduleTuple = (chargeParameterDiscoveryReq.getMaxEntriesSAScheduleTuple() != null) ?
chargeParameterDiscoveryReq.getMaxEntriesSAScheduleTuple() : 1024;
Long departureTime = chargeParameterDiscoveryReq.getEVChargeParameter().getValue().getDepartureTime();
if (getCommSessionContext().isOldSessionJoined()) {
getCommSessionContext().setSaSchedules(
getCommSessionContext().getBackendInterface().getSAScheduleList(
maxEntriesSAScheduleTuple,
(departureTime != null) ? departureTime.longValue() : 0,
getXMLSignatureRefElements(),
getCommSessionContext().getChosenSAScheduleTuple())
);
} else {
getCommSessionContext().setSaSchedules(
getCommSessionContext().getBackendInterface().getSAScheduleList(
maxEntriesSAScheduleTuple,
(departureTime != null) ? departureTime.longValue() : 0,
getXMLSignatureRefElements())
);
}
}
// TODO An integration to a backend system which provides the SalesTariff would be needed here
if (chargeParameterDiscoveryReq.getRequestedEnergyTransferMode().toString().startsWith("AC"))
chargeParameterDiscoveryRes.setEVSEChargeParameter(
((IACEVSEController) getCommSessionContext().getACEvseController()).getACEVSEChargeParameter());
else
chargeParameterDiscoveryRes.setEVSEChargeParameter(
((IDCEVSEController) getCommSessionContext().getDCEvseController()).getDCEVSEChargeParameter());
/*
* TODO The next state depends as well on the EVSENotification:
* - NONE: PowerDeliveryReq (ChargeProgress of ChargeParameterDiscoveryReq = Start)
* - RENEGOTIATION: ChargeParameterDiscoveryReq
* - STOP: PowerDeliveryReq (ChargeProgress of ChargeParameterDiscoveryReq = Stop)
*/
if (getCommSessionContext().getSaSchedules() == null) {
getLogger().debug("No SAScheduleList available yet, setting EVSEProcessingType to ONGOING");
chargeParameterDiscoveryRes.setEVSEProcessing(EVSEProcessingType.ONGOING);
return getSendMessage(chargeParameterDiscoveryRes, V2GMessages.CHARGE_PARAMETER_DISCOVERY_REQ);
} else {
getLogger().debug("SAScheduleList has been provided");
chargeParameterDiscoveryRes.setEVSEProcessing(EVSEProcessingType.FINISHED);
setWaitingForSchedule(false);
chargeParameterDiscoveryRes.setSASchedules(
getSASchedulesAsJAXBElement(getCommSessionContext().getSaSchedules()));
/*
* Note 3 of [V2G2-905] states:
* "If the secondary actor is unaware of which authentication mode is used during EVCC-SECC
* communication (EIM/ PnC), it can simply always sign the SalesTariff."
*
* Therefore, we do not check here if PnC is used but just always sign the SalesTariff.
* Without a real backend functionality, we must sign the SalesTariff by using the SecurityUtils
* class.
*/
//Set signing private key of Mobility Operator Sub-CA 2
setSignaturePrivateKey(getCommSessionContext().getBackendInterface().getMOSubCA2PrivateKey());
if (chargeParameterDiscoveryReq.getRequestedEnergyTransferMode().toString().startsWith("AC"))
return getSendMessage(chargeParameterDiscoveryRes, V2GMessages.POWER_DELIVERY_REQ);
else
return getSendMessage(chargeParameterDiscoveryRes, V2GMessages.CABLE_CHECK_REQ);
}
} else {
setMandatoryFieldsForFailedRes(chargeParameterDiscoveryRes, chargeParameterDiscoveryRes.getResponseCode());
}
} else {
if (chargeParameterDiscoveryRes.getResponseCode().equals(ResponseCodeType.FAILED_SEQUENCE_ERROR)) {
BodyBaseType responseMessage = getSequenceErrorResMessage(new ChargeParameterDiscoveryResType(), message);
return getSendMessage(responseMessage, V2GMessages.NONE, chargeParameterDiscoveryRes.getResponseCode());
} else {
setMandatoryFieldsForFailedRes(chargeParameterDiscoveryRes, chargeParameterDiscoveryRes.getResponseCode());
}
}
return getSendMessage(chargeParameterDiscoveryRes, V2GMessages.NONE, chargeParameterDiscoveryRes.getResponseCode());
}
public boolean isResponseCodeOK(ChargeParameterDiscoveryReqType chargeParameterDiscoveryReq) {
// Check if the EV's requested EnergyTransferModeType is supported
ArrayList<EnergyTransferModeType> evseSupported = getCommSessionContext().getSupportedEnergyTransferModes();
EnergyTransferModeType evRequested = chargeParameterDiscoveryReq.getRequestedEnergyTransferMode();
if (!evseSupported.contains(evRequested)) {
chargeParameterDiscoveryRes.setResponseCode(ResponseCodeType.FAILED_WRONG_ENERGY_TRANSFER_MODE);
return false;
}
// Check as well if evRequested does not fit to the content of attribute EVChargeParameter
if ( (chargeParameterDiscoveryReq.getEVChargeParameter().getValue() instanceof ACEVChargeParameterType &&
evRequested.toString().startsWith("DC")) ||
(chargeParameterDiscoveryReq.getEVChargeParameter().getValue() instanceof DCEVChargeParameterType &&
evRequested.toString().startsWith("AC")) ) {
getLogger().error(chargeParameterDiscoveryReq.getEVChargeParameter().getValue().getClass().getSimpleName() +
" does not fit to EnergyTransferMode '" + evRequested.toString() + "'");
chargeParameterDiscoveryRes.setResponseCode(ResponseCodeType.FAILED_WRONG_ENERGY_TRANSFER_MODE);
return false;
}
if (!verifyChargeParameter(chargeParameterDiscoveryReq)) {
chargeParameterDiscoveryRes.setResponseCode(ResponseCodeType.FAILED_WRONG_CHARGE_PARAMETER);
return false;
}
return true;
}
private boolean verifyChargeParameter(ChargeParameterDiscoveryReqType chargeParameterDiscoveryReq) {
if (chargeParameterDiscoveryReq.getEVChargeParameter() == null) {
getLogger().error("EVChargeParameter is empty (null)");
return false;
}
if (chargeParameterDiscoveryReq.getEVChargeParameter().getValue() instanceof ACEVChargeParameterType) {
ACEVChargeParameterType acEVChargeParameter = (ACEVChargeParameterType) chargeParameterDiscoveryReq.getEVChargeParameter().getValue();
if ( // Check if mandatory charge parameters are null
(acEVChargeParameter.getEAmount() == null ||
acEVChargeParameter.getEVMaxVoltage() == null ||
acEVChargeParameter.getEVMaxCurrent() == null ||
acEVChargeParameter.getEVMinCurrent() == null
) ||
// Check if charge parameters are out of range
( acEVChargeParameter.getEAmount().getValue() < 0 ||
acEVChargeParameter.getEAmount().getValue() * Math.pow(10, acEVChargeParameter.getEAmount().getMultiplier()) > 200000 ||
acEVChargeParameter.getEVMaxVoltage().getValue() < 0 ||
acEVChargeParameter.getEVMaxVoltage().getValue() * Math.pow(10, acEVChargeParameter.getEVMaxVoltage().getMultiplier()) > 1000 ||
acEVChargeParameter.getEVMaxCurrent().getValue() < 0 ||
acEVChargeParameter.getEVMaxCurrent().getValue() * Math.pow(10, acEVChargeParameter.getEVMaxCurrent().getMultiplier()) > 400 ||
acEVChargeParameter.getEVMinCurrent().getValue() < 0 ||
acEVChargeParameter.getEVMinCurrent().getValue() * Math.pow(10, acEVChargeParameter.getEVMinCurrent().getMultiplier()) > 400
)
) {
getLogger().error("One of the AC_EVChargeParameter elements is either null or out of range");
return false;
}
}
if (chargeParameterDiscoveryReq.getEVChargeParameter().getValue() instanceof DCEVChargeParameterType) {
DCEVChargeParameterType dcEVChargeParameter = (DCEVChargeParameterType) chargeParameterDiscoveryReq.getEVChargeParameter().getValue();
if ( // Check if mandatory charge parameters are null
(dcEVChargeParameter.getDCEVStatus() == null ||
dcEVChargeParameter.getEVMaximumCurrentLimit() == null ||
dcEVChargeParameter.getEVMaximumVoltageLimit() == null
) ||
// Check if charge parameters are out of range
( dcEVChargeParameter.getDCEVStatus().getEVRESSSOC() < 0 ||
dcEVChargeParameter.getDCEVStatus().getEVRESSSOC() > 100 ||
dcEVChargeParameter.getEVMaximumCurrentLimit().getValue() < 0 ||
dcEVChargeParameter.getEVMaximumCurrentLimit().getValue() * Math.pow(10, dcEVChargeParameter.getEVMaximumCurrentLimit().getMultiplier()) > 400 ||
dcEVChargeParameter.getEVMaximumVoltageLimit().getValue() < 0 ||
dcEVChargeParameter.getEVMaximumVoltageLimit().getValue() * Math.pow(10, dcEVChargeParameter.getEVMaximumVoltageLimit().getMultiplier()) > 1000 ||
( // EVMaximumPowerLimit is optional
dcEVChargeParameter.getEVMaximumPowerLimit() != null && (
dcEVChargeParameter.getEVMaximumPowerLimit().getValue() < 0 ||
dcEVChargeParameter.getEVMaximumPowerLimit().getValue() * Math.pow(10, dcEVChargeParameter.getEVMaximumPowerLimit().getMultiplier()) > 200000
)
) ||
( // EVEnergyCapacity is optional
dcEVChargeParameter.getEVEnergyCapacity() != null && (
dcEVChargeParameter.getEVEnergyCapacity().getValue() < 0 ||
dcEVChargeParameter.getEVEnergyCapacity().getValue() * Math.pow(10, dcEVChargeParameter.getEVEnergyCapacity().getMultiplier()) > 200000
)
) ||
( // EVEnergyRequest is optional
dcEVChargeParameter.getEVEnergyRequest() != null && (
dcEVChargeParameter.getEVEnergyRequest().getValue() < 0 ||
dcEVChargeParameter.getEVEnergyRequest().getValue() * Math.pow(10, dcEVChargeParameter.getEVEnergyRequest().getMultiplier()) > 200000
)
) ||
( // FullSOC is optional
dcEVChargeParameter.getFullSOC() != null && (
dcEVChargeParameter.getFullSOC() < 0 ||
dcEVChargeParameter.getFullSOC() > 100
)
) ||
( // BulkSOC is optional
dcEVChargeParameter.getBulkSOC() != null && (
dcEVChargeParameter.getBulkSOC() < 0 ||
dcEVChargeParameter.getBulkSOC() > 100
)
)
)
) {
getLogger().error("One of the DC_EVChargeParameter elements is either null or out of range");
return false;
}
}
return true;
}
private JAXBElement<SAScheduleListType> getSASchedulesAsJAXBElement(SAScheduleListType saScheduleList) {
return new JAXBElement<SAScheduleListType>(
new QName("urn:iso:15118:2:2013:MsgDataTypes", "SAScheduleList"),
SAScheduleListType.class,
saScheduleList);
}
public boolean isWaitingForSchedule() {
return waitingForSchedule;
}
private void setWaitingForSchedule(boolean waitingForSchedule) {
this.waitingForSchedule = waitingForSchedule;
}
@Override
public BodyBaseType getResponseMessage() {
return chargeParameterDiscoveryRes;
}
}

View File

@@ -0,0 +1,111 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.states;
import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingStatusReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingStatusResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.MeterInfoType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
public class WaitForChargingStatusReq extends ServerState {
private ChargingStatusResType chargingStatusRes;
public WaitForChargingStatusReq(
V2GCommunicationSessionSECC commSessionContext) {
super(commSessionContext);
chargingStatusRes = new ChargingStatusResType();
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, ChargingStatusReqType.class, chargingStatusRes)) {
chargingStatusRes.setEVSEID(getCommSessionContext().getACEvseController().getEvseID());
chargingStatusRes.setSAScheduleTupleID(getCommSessionContext().getChosenSAScheduleTuple());
/*
* TODO check if a renegotiation is wanted or not
* Change EVSENotificationType to NONE if you want more than one charge loop iteration,
* but then make sure the EV is stopping the charge loop
*/
chargingStatusRes.setACEVSEStatus(
getCommSessionContext().getACEvseController().getACEVSEStatus(EVSENotificationType.NONE)
);
// Optionally indicate that the EVCC is required to send a MeteringReceiptReq message
if (getCommSessionContext().getSelectedPaymentOption().equals(PaymentOptionType.EXTERNAL_PAYMENT)) {
// In EIM, there is never a MeteringReceiptReq/-Res message pair, therefore it is set to false here
chargingStatusRes.setReceiptRequired(false);
} else {
// Only in PnC mode according to [V2G2-691]
chargingStatusRes.setReceiptRequired(false);
}
// Optionally set EVSEMaxCurrent (if NOT in AC PnC mode) -> check with AC station
MeterInfoType meterInfo = getCommSessionContext().getACEvseController().getMeterInfo();
chargingStatusRes.setMeterInfo(meterInfo);
getCommSessionContext().setSentMeterInfo(meterInfo);
/*
* TODO it is unclear how the EV should react if an EVSENotification = Renegotiate/Stop
* is sent as well as ReceiptRequired = true: is a PowerDeliveryReq oder a MeteringReceiptReq
* expected then?
*/
if (chargingStatusRes.isReceiptRequired()) {
return getSendMessage(chargingStatusRes, V2GMessages.METERING_RECEIPT_REQ);
} else {
((ForkState) getCommSessionContext().getStates().get(V2GMessages.FORK))
.getAllowedRequests().add(V2GMessages.CHARGING_STATUS_REQ);
((ForkState) getCommSessionContext().getStates().get(V2GMessages.FORK))
.getAllowedRequests().add(V2GMessages.POWER_DELIVERY_REQ);
return getSendMessage(chargingStatusRes, V2GMessages.FORK);
}
} else {
if (chargingStatusRes.getResponseCode().equals(ResponseCodeType.FAILED_SEQUENCE_ERROR)) {
BodyBaseType responseMessage = getSequenceErrorResMessage(new ChargingStatusResType(), message);
return getSendMessage(responseMessage, V2GMessages.NONE, chargingStatusRes.getResponseCode());
} else {
setMandatoryFieldsForFailedRes(chargingStatusRes, chargingStatusRes.getResponseCode());
}
}
return getSendMessage(chargingStatusRes, V2GMessages.NONE, chargingStatusRes.getResponseCode());
}
@Override
public BodyBaseType getResponseMessage() {
return chargingStatusRes;
}
}

View File

@@ -0,0 +1,121 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.states;
import com.v2gclarity.risev2g.secc.evseController.IDCEVSEController;
import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CurrentDemandReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CurrentDemandResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForCurrentDemandReq extends ServerState {
private CurrentDemandResType currentDemandRes;
public WaitForCurrentDemandReq(V2GCommunicationSessionSECC commSessionContext) {
super(commSessionContext);
currentDemandRes = new CurrentDemandResType();
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, CurrentDemandReqType.class, currentDemandRes)) {
V2GMessage v2gMessageReq = (V2GMessage) message;
CurrentDemandReqType currentDemandReq =
(CurrentDemandReqType) v2gMessageReq.getBody().getBodyElement().getValue();
IDCEVSEController evseController = getCommSessionContext().getDCEvseController();
evseController.setEVMaximumCurrentLimit(currentDemandReq.getEVMaximumCurrentLimit());
evseController.setEVMaximumVoltageLimit(currentDemandReq.getEVMaximumVoltageLimit());
evseController.setEVMaximumPowerLimit(currentDemandReq.getEVMaximumPowerLimit());
evseController.setTargetCurrent(currentDemandReq.getEVTargetCurrent());
evseController.setTargetVoltage(currentDemandReq.getEVTargetVoltage());
// TODO how to deal with the remaining parameters of currentDemandReq?
/*
* TODO check if a renegotiation is wanted or not
* Change EVSENotificationType to NONE if you want more than one charge loop iteration,
* but then make sure the EV is stopping the charge loop
*/
currentDemandRes.setDCEVSEStatus(evseController.getDCEVSEStatus(EVSENotificationType.NONE));
currentDemandRes.setEVSECurrentLimitAchieved(evseController.isEVSECurrentLimitAchieved());
currentDemandRes.setEVSEVoltageLimitAchieved(evseController.isEVSEVoltageLimitAchieved());
currentDemandRes.setEVSEPowerLimitAchieved(evseController.isEVSEPowerLimitAchieved());
currentDemandRes.setEVSEID(evseController.getEvseID());
currentDemandRes.setEVSEMaximumCurrentLimit(evseController.getEVSEMaximumCurrentLimit());
currentDemandRes.setEVSEMaximumVoltageLimit(evseController.getEVSEMaximumVoltageLimit());
currentDemandRes.setEVSEMaximumPowerLimit(evseController.getEVSEMaximumPowerLimit());
currentDemandRes.setEVSEPresentCurrent(evseController.getPresentCurrent());
currentDemandRes.setEVSEPresentVoltage(evseController.getPresentVoltage());
currentDemandRes.setMeterInfo(evseController.getMeterInfo());
getCommSessionContext().setSentMeterInfo(evseController.getMeterInfo());
currentDemandRes.setSAScheduleTupleID(getCommSessionContext().getChosenSAScheduleTuple());
// Optionally indicate that the EVCC is required to send a MeteringReceiptReq message
if (getCommSessionContext().getSelectedPaymentOption().equals(PaymentOptionType.EXTERNAL_PAYMENT)) {
// In EIM, there is never a MeteringReceiptReq/-Res message pair, therefore it is set to false here
currentDemandRes.setReceiptRequired(false);
} else {
// Optionally set to true, but only in PnC mode according to [V2G2-691]
currentDemandRes.setReceiptRequired(false);
}
if (currentDemandRes.isReceiptRequired()) {
return getSendMessage(currentDemandRes, V2GMessages.METERING_RECEIPT_REQ);
} else {
((ForkState) getCommSessionContext().getStates().get(V2GMessages.FORK))
.getAllowedRequests().add(V2GMessages.CURRENT_DEMAND_REQ);
((ForkState) getCommSessionContext().getStates().get(V2GMessages.FORK))
.getAllowedRequests().add(V2GMessages.POWER_DELIVERY_REQ);
return getSendMessage(currentDemandRes, V2GMessages.FORK);
}
} else {
if (currentDemandRes.getResponseCode().equals(ResponseCodeType.FAILED_SEQUENCE_ERROR)) {
BodyBaseType responseMessage = getSequenceErrorResMessage(new CurrentDemandResType(), message);
return getSendMessage(responseMessage, V2GMessages.NONE, currentDemandRes.getResponseCode());
} else {
setMandatoryFieldsForFailedRes(currentDemandRes, currentDemandRes.getResponseCode());
}
}
return getSendMessage(currentDemandRes, V2GMessages.NONE, currentDemandRes.getResponseCode());
}
@Override
public BodyBaseType getResponseMessage() {
return currentDemandRes;
}
}

View File

@@ -0,0 +1,173 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.states;
import java.util.Arrays;
import java.util.HashMap;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ACEVSEStatusType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVSEStatusType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.MeterInfoType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.MeteringReceiptReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.MeteringReceiptResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SignatureType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForMeteringReceiptReq extends ServerState {
private MeteringReceiptResType meteringReceiptRes;
public WaitForMeteringReceiptReq(V2GCommunicationSessionSECC commSessionContext) {
super(commSessionContext);
meteringReceiptRes = new MeteringReceiptResType();
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, MeteringReceiptReqType.class, meteringReceiptRes)) {
V2GMessage v2gMessageReq = (V2GMessage) message;
MeteringReceiptReqType meteringReceiptReq =
(MeteringReceiptReqType) v2gMessageReq.getBody().getBodyElement().getValue();
if (isResponseCodeOK(meteringReceiptReq, v2gMessageReq.getHeader().getSignature())) {
setEVSEStatus(meteringReceiptRes);
((ForkState) getCommSessionContext().getStates().get(V2GMessages.FORK))
.getAllowedRequests().add(V2GMessages.POWER_DELIVERY_REQ);
((ForkState) getCommSessionContext().getStates().get(V2GMessages.FORK))
.getAllowedRequests().add(V2GMessages.CHARGING_STATUS_REQ);
((ForkState) getCommSessionContext().getStates().get(V2GMessages.FORK))
.getAllowedRequests().add(V2GMessages.CURRENT_DEMAND_REQ);
return getSendMessage(meteringReceiptRes, V2GMessages.FORK);
} else {
setMandatoryFieldsForFailedRes(meteringReceiptRes, meteringReceiptRes.getResponseCode());
}
} else {
if (meteringReceiptRes.getResponseCode().equals(ResponseCodeType.FAILED_SEQUENCE_ERROR)) {
BodyBaseType responseMessage = getSequenceErrorResMessage(new MeteringReceiptResType(), message);
return getSendMessage(responseMessage, V2GMessages.NONE, meteringReceiptRes.getResponseCode());
} else {
setMandatoryFieldsForFailedRes(meteringReceiptRes, meteringReceiptRes.getResponseCode());
}
}
return getSendMessage(meteringReceiptRes, V2GMessages.NONE, meteringReceiptRes.getResponseCode());
}
private boolean isResponseCodeOK(
MeteringReceiptReqType meteringReceiptReq,
SignatureType signature) {
/*
* Check if previously sent MeterInfo from ChargingStatusRes (AC charging) /
* CurrentDemandRes (DC charging) is equal to the received MeterInfo
*/
if (!meterInfoEquals(getCommSessionContext().getSentMeterInfo(), meteringReceiptReq.getMeterInfo())) {
getLogger().error("The metering values sent by the EVCC do not match the ones sent previously by the SECC. "
+ "This is not a signature verification error.");
meteringReceiptRes.setResponseCode(ResponseCodeType.FAILED_METERING_SIGNATURE_NOT_VALID);
return false;
}
// Verify signature
HashMap<String, byte[]> verifyXMLSigRefElements = new HashMap<String, byte[]>();
verifyXMLSigRefElements.put(
meteringReceiptReq.getId(),
SecurityUtils.generateDigest(meteringReceiptReq.getId(), getMessageHandler().getJaxbElement(meteringReceiptReq)));
if (!SecurityUtils.verifySignature(
signature,
getMessageHandler().getJaxbElement(signature.getSignedInfo()),
verifyXMLSigRefElements,
getCommSessionContext().getContractSignatureCertChain().getCertificate())) {
meteringReceiptRes.setResponseCode(ResponseCodeType.FAILED_METERING_SIGNATURE_NOT_VALID);
return false;
}
return true;
}
private boolean meterInfoEquals(MeterInfoType meterInfoSentBySECC, MeterInfoType meterInfoReceivedFromEVCC) {
if (meterInfoSentBySECC == null) {
getLogger().error("MeterInfo sent by SECC is not saved in session context, value is null");
return false;
} else if (meterInfoReceivedFromEVCC == null) {
getLogger().error("MeterInfo received from EVCC is null");
return false;
} else {
// Only meterID is mandatory field, thus check for null values as well
if (!meterInfoSentBySECC.getMeterID().equals(meterInfoReceivedFromEVCC.getMeterID()) ||
(meterInfoSentBySECC.getMeterReading() != null && !meterInfoSentBySECC.getMeterReading().equals(meterInfoReceivedFromEVCC.getMeterReading())) ||
(meterInfoSentBySECC.getMeterStatus() != null && !meterInfoSentBySECC.getMeterStatus().equals(meterInfoReceivedFromEVCC.getMeterStatus())) ||
(meterInfoSentBySECC.getSigMeterReading() != null && !Arrays.equals(meterInfoSentBySECC.getSigMeterReading(), meterInfoReceivedFromEVCC.getSigMeterReading())) ||
(meterInfoSentBySECC.getTMeter() != null && !meterInfoSentBySECC.getTMeter().equals(meterInfoReceivedFromEVCC.getTMeter()))
) return false;
else return true;
}
}
protected void setEVSEStatus(MeteringReceiptResType meteringReceiptRes) {
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC")) {
/*
* The MessageHandler method getJAXBElement() cannot be used here because of the difference in the
* class name (ACEVSEStatus) and the name in the XSD (AC_EVSEStatus)
*/
JAXBElement<ACEVSEStatusType> jaxbEVSEStatus = new JAXBElement<>(new QName("urn:iso:15118:2:2013:MsgDataTypes", "AC_EVSEStatus"),
ACEVSEStatusType.class,
getCommSessionContext().getACEvseController().getACEVSEStatus(EVSENotificationType.NONE));
meteringReceiptRes.setEVSEStatus(jaxbEVSEStatus);
} else if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("DC")) {
/*
* The MessageHandler method getJAXBElement() cannot be used here because of the difference in the
* class name (DCEVSEStatus) and the name in the XSD (DC_EVSEStatus)
*/
JAXBElement<DCEVSEStatusType> jaxbACEVSEStatus = new JAXBElement<>(new QName("urn:iso:15118:2:2013:MsgDataTypes", "DC_EVSEStatus"),
DCEVSEStatusType.class,
getCommSessionContext().getDCEvseController().getDCEVSEStatus(EVSENotificationType.NONE));
meteringReceiptRes.setEVSEStatus(jaxbACEVSEStatus);
} else {
getLogger().warn("RequestedEnergyTransferMode '" + getCommSessionContext().getRequestedEnergyTransferMode().toString() +
"is neither of type AC nor DC");
}
}
@Override
public BodyBaseType getResponseMessage() {
return meteringReceiptRes;
}
}

Some files were not shown because too many files have changed in this diff Show More