initial commit for RISE V2G

This commit is contained in:
Marc Mültin 2015-06-01 17:19:09 +02:00
parent 653a2f11e7
commit 505e772f08
270 changed files with 32929 additions and 204 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
*.class
**/bin
*/bin
.settings
.project

204
LICENSE
View File

@ -1,204 +0,0 @@
Eclipse Public License - v 1.0
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
1. DEFINITIONS
"Contribution" means:
a) in the case of the initial Contributor, the initial code and documentation
distributed under this Agreement, and
b) in the case of each subsequent Contributor:
i) changes to the Program, and
ii) additions to the Program;
where such changes and/or additions to the Program originate from and are
distributed by that particular Contributor. A Contribution 'originates'
from a Contributor if it was added to the Program by such Contributor
itself or anyone acting on such Contributor's behalf. Contributions do not
include additions to the Program which: (i) are separate modules of
software distributed in conjunction with the Program under their own
license agreement, and (ii) are not derivative works of the Program.
"Contributor" means any person or entity that distributes the Program.
"Licensed Patents" mean patent claims licensable by a Contributor which are
necessarily infringed by the use or sale of its Contribution alone or when
combined with the Program.
"Program" means the Contributions distributed in accordance with this
Agreement.
"Recipient" means anyone who receives the Program under this Agreement,
including all Contributors.
2. GRANT OF RIGHTS
a) Subject to the terms of this Agreement, each Contributor hereby grants
Recipient a non-exclusive, worldwide, royalty-free copyright license to
reproduce, prepare derivative works of, publicly display, publicly
perform, distribute and sublicense the Contribution of such Contributor,
if any, and such derivative works, in source code and object code form.
b) Subject to the terms of this Agreement, each Contributor hereby grants
Recipient a non-exclusive, worldwide, royalty-free patent license under
Licensed Patents to make, use, sell, offer to sell, import and otherwise
transfer the Contribution of such Contributor, if any, in source code and
object code form. This patent license shall apply to the combination of
the Contribution and the Program if, at the time the Contribution is
added by the Contributor, such addition of the Contribution causes such
combination to be covered by the Licensed Patents. The patent license
shall not apply to any other combinations which include the Contribution.
No hardware per se is licensed hereunder.
c) Recipient understands that although each Contributor grants the licenses
to its Contributions set forth herein, no assurances are provided by any
Contributor that the Program does not infringe the patent or other
intellectual property rights of any other entity. Each Contributor
disclaims any liability to Recipient for claims brought by any other
entity based on infringement of intellectual property rights or
otherwise. As a condition to exercising the rights and licenses granted
hereunder, each Recipient hereby assumes sole responsibility to secure
any other intellectual property rights needed, if any. For example, if a
third party patent license is required to allow Recipient to distribute
the Program, it is Recipient's responsibility to acquire that license
before distributing the Program.
d) Each Contributor represents that to its knowledge it has sufficient
copyright rights in its Contribution, if any, to grant the copyright
license set forth in this Agreement.
3. REQUIREMENTS
A Contributor may choose to distribute the Program in object code form under
its own license agreement, provided that:
a) it complies with the terms and conditions of this Agreement; and
b) its license agreement:
i) effectively disclaims on behalf of all Contributors all warranties
and conditions, express and implied, including warranties or
conditions of title and non-infringement, and implied warranties or
conditions of merchantability and fitness for a particular purpose;
ii) effectively excludes on behalf of all Contributors all liability for
damages, including direct, indirect, special, incidental and
consequential damages, such as lost profits;
iii) states that any provisions which differ from this Agreement are
offered by that Contributor alone and not by any other party; and
iv) states that source code for the Program is available from such
Contributor, and informs licensees how to obtain it in a reasonable
manner on or through a medium customarily used for software exchange.
When the Program is made available in source code form:
a) it must be made available under this Agreement; and
b) a copy of this Agreement must be included with each copy of the Program.
Contributors may not remove or alter any copyright notices contained
within the Program.
Each Contributor must identify itself as the originator of its Contribution,
if
any, in a manner that reasonably allows subsequent Recipients to identify the
originator of the Contribution.
4. COMMERCIAL DISTRIBUTION
Commercial distributors of software may accept certain responsibilities with
respect to end users, business partners and the like. While this license is
intended to facilitate the commercial use of the Program, the Contributor who
includes the Program in a commercial product offering should do so in a manner
which does not create potential liability for other Contributors. Therefore,
if a Contributor includes the Program in a commercial product offering, such
Contributor ("Commercial Contributor") hereby agrees to defend and indemnify
every other Contributor ("Indemnified Contributor") against any losses,
damages and costs (collectively "Losses") arising from claims, lawsuits and
other legal actions brought by a third party against the Indemnified
Contributor to the extent caused by the acts or omissions of such Commercial
Contributor in connection with its distribution of the Program in a commercial
product offering. The obligations in this section do not apply to any claims
or Losses relating to any actual or alleged intellectual property
infringement. In order to qualify, an Indemnified Contributor must:
a) promptly notify the Commercial Contributor in writing of such claim, and
b) allow the Commercial Contributor to control, and cooperate with the
Commercial Contributor in, the defense and any related settlement
negotiations. The Indemnified Contributor may participate in any such claim at
its own expense.
For example, a Contributor might include the Program in a commercial product
offering, Product X. That Contributor is then a Commercial Contributor. If
that Commercial Contributor then makes performance claims, or offers
warranties related to Product X, those performance claims and warranties are
such Commercial Contributor's responsibility alone. Under this section, the
Commercial Contributor would have to defend claims against the other
Contributors related to those performance claims and warranties, and if a
court requires any other Contributor to pay any damages as a result, the
Commercial Contributor must pay those damages.
5. NO WARRANTY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE,
NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each
Recipient is solely responsible for determining the appropriateness of using
and distributing the Program and assumes all risks associated with its
exercise of rights under this Agreement , including but not limited to the
risks and costs of program errors, compliance with applicable laws, damage to
or loss of data, programs or equipment, and unavailability or interruption of
operations.
6. DISCLAIMER OF LIABILITY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGES.
7. GENERAL
If any provision of this Agreement is invalid or unenforceable under
applicable law, it shall not affect the validity or enforceability of the
remainder of the terms of this Agreement, and without further action by the
parties hereto, such provision shall be reformed to the minimum extent
necessary to make such provision valid and enforceable.
If Recipient institutes patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Program itself
(excluding combinations of the Program with other software or hardware)
infringes such Recipient's patent(s), then such Recipient's rights granted
under Section 2(b) shall terminate as of the date such litigation is filed.
All Recipient's rights under this Agreement shall terminate if it fails to
comply with any of the material terms or conditions of this Agreement and does
not cure such failure in a reasonable period of time after becoming aware of
such noncompliance. If all Recipient's rights under this Agreement terminate,
Recipient agrees to cease use and distribution of the Program as soon as
reasonably practicable. However, Recipient's obligations under this Agreement
and any licenses granted by Recipient relating to the Program shall continue
and survive.
Everyone is permitted to copy and distribute copies of this Agreement, but in
order to avoid inconsistency the Agreement is copyrighted and may only be
modified in the following manner. The Agreement Steward reserves the right to
publish new versions (including revisions) of this Agreement from time to
time. No one other than the Agreement Steward has the right to modify this
Agreement. The Eclipse Foundation is the initial Agreement Steward. The
Eclipse Foundation may assign the responsibility to serve as the Agreement
Steward to a suitable separate entity. Each new version of the Agreement will
be given a distinguishing version number. The Program (including
Contributions) may always be distributed subject to the version of the
Agreement under which it was received. In addition, after a new version of the
Agreement is published, Contributor may elect to distribute the Program
(including its Contributions) under the new version. Except as expressly
stated in Sections 2(a) and 2(b) above, Recipient receives no rights or
licenses to the intellectual property of any Contributor under this Agreement,
whether expressly, by implication, estoppel or otherwise. All rights in the
Program not expressly granted under this Agreement are reserved.
This Agreement is governed by the laws of the State of New York and the
intellectual property laws of the United States of America. No party to this
Agreement will bring a legal action under this Agreement more than one year
after the cause of action arose. Each party waives its rights to a jury trial in
any resulting litigation.

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 = CPOSub1CA
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 = CPOSub2CA
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 = 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 = MOSub1CA
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 = MOSub2CA
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 = OEMSub1CA
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 = OEMSub2CA
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 = ProvServiceCert
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 = ProvSub1CA
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 = ProvSub2CA
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 = SECCCert
organizationName = RISE V2G Project
countryName = DE
domainComponent = CPO
[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 = 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,167 @@
# 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.
# Previously created certificates should have been provided with the respective release of the RISE V2G project for testing purposes. However, certain certificates might not be valid any more in which case you need to create new certificates.
# 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/IEC 15118. For example, OpenSSL 0.9.8 does not come with SHA-2 for SHA-256 signature algorithms.
#
# Author: Marc Mültin (marc.mueltin@chargepartner.com)
# 1) Create a self-signed V2GRootCA certificate
# 1.1) Create a
# - private key -> -genkey
# - with elliptic curve parameters -> ecparam
# - for key of length 256 bit to be used for digital signatures -> -name secp256r1
# - with symmetric encryption AES 128 bit -> -aes128
# - and the passphrase for the private key provided in a file -> -passout file:passphrase.txt
openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys/v2gRootCA.key -aes128 -passout file:passphrase.txt
# 1.2) Create a
# - new -> -new
# - self-signed certificate -> -new -x509 (and -out v2gRootCA.pem)
# - valid for 40 years -> -days 14600
# - with signature algorithm sha256 -> -sha256
# - with previously created private key -> -key privateKeys/v2gRootCA.key
# - and configuration data provided -> -config configs/v2gRootCA.cnf
# - with extensions specified in section [ext] -> -extensions ext
openssl req -new -x509 -days 14600 -sha256 -key privateKeys/v2gRootCA.key -set_serial 01 -passin file:passphrase.txt -config configs/v2gRootCA.cnf -extensions ext -out certs/v2gRootCA.pem
# 2) Create an intermediate CPO sub-CA certificate which is directly signed by the V2GRootCA certificate
# 2.1) Create a private key (same procedure as for V2GRootCA certificate)
openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys/cpoSub1CA.key -aes128 -passout file:passphrase.txt
# 2.2) Create a
# - new Certificate Signing Request (CSR) -> -new (and -out cpoSub1CA.csr)
# - with previously created private key -> -key privateKeys/cpoSub1CA.key
# - and configuration data provided -> -config configs/cpoSub1CA.cnf
# - with extensions specified in section [ext] -> -extensions ext
openssl req -new -key privateKeys/cpoSub1CA.key -passin file:passphrase.txt -config configs/cpoSub1CA.cnf -extensions ext -out csrs/cpoSub1CA.csr
# 2.3) Create a
# - certificate for the CPOSub1CA -> x509
# - with the previously created CSR -> -in csrs/cpoSub1CA.csr
# - signed by the V2GRootCA's private key -> -signkey privateKeys/v2gRootCA.key
# - with a validity of 4 years -> -days 1460
openssl x509 -req -in csrs/cpoSub1CA.csr -extfile configs/cpoSub1CA.cnf -extensions ext -CA certs/v2gRootCA.pem -CAkey privateKeys/v2gRootCA.key -set_serial 02 -passin file:passphrase.txt -days 1460 -out certs/cpoSub1CA.pem
# 3) Create a second intermediate CPO sub-CA certificate just the way the previous intermedia certificate was created which is directly signed by the CPOSub1CA
# Differences to CPOSub1CA
# - basicConstraints in config file sets pathlength to 0 (meaning that no further sub CA's certificate 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/IEC 15118)
openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys/cpoSub2CA.key -aes128 -passout file:passphrase.txt
openssl req -new -key privateKeys/cpoSub2CA.key -passin file:passphrase.txt -config configs/cpoSub2CA.cnf -extensions ext -out csrs/cpoSub2CA.csr
openssl x509 -req -in csrs/cpoSub2CA.csr -extfile configs/cpoSub2CA.cnf -extensions ext -CA certs/cpoSub1CA.pem -CAkey privateKeys/cpoSub1CA.key -set_serial 03 -passin file:passphrase.txt -days 365 -out certs/cpoSub2CA.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 CPOSub2CA certificate
# Differences to CPOSub1CA and CPOSub2CA
# - 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/IEC 15118)
openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys/seccCert.key -aes128 -passout file:passphrase.txt
openssl req -new -key privateKeys/seccCert.key -passin file:passphrase.txt -config configs/seccCert.cnf -extensions ext -out csrs/seccCert.csr
openssl x509 -req -in csrs/seccCert.csr -extfile configs/seccCert.cnf -extensions ext -CA certs/cpoSub2CA.pem -CAkey privateKeys/cpoSub2CA.key -set_serial 04 -passin file:passphrase.txt -days 60 -out certs/seccCert.pem
# Concatenate the intermediate CAs 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 intermediate CA's certificate which signs the leaf certificate (in this case cpoSub2CA.pem). Otherwise the Java method getCertificateChain() which is called on the keystore will only return the leaf certificate!
cat certs/cpoSub2CA.pem certs/cpoSub1CA.pem > certs/intermediateCPOCAs.pem
# Put the seccCertificate, the private key of the seccCertificate as well as the intermediate CAs in a pkcs12 container.
# IMPORTANT: It is necessary to put all necessary intermediate CAs directly into the PKCS12 container (with the -certfile switch), instead of later on mporting the PKCS12 containter only holding the leaf certificate (seccCert) and its private key and additionally importing the intermediate CAs via the keytool command (TLS handshake will fail).
# This is the reason why we need two password files (passphrase.txt and passphrase2.txt). Possibly the passphrase.txt file resource is locked before being accessed a second time within the same command? See also http://rt.openssl.org/Ticket/Display.html?id=3168&user=guest&pass=guest
# The -name switch corresponds to the -alias switch in the keytool command later on
openssl pkcs12 -export -inkey privateKeys/seccCert.key -in certs/seccCert.pem -certfile certs/intermediateCPOCAs.pem -aes128 -passin file:passphrase.txt -passout file:passphrase2.txt -name secc_cert -out certs/seccCert.p12
# 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 secp256r1 | openssl ec -out privateKeys/oemRootCA.key -aes128 -passout file:passphrase.txt
openssl req -new -x509 -days 14600 -sha256 -key privateKeys/oemRootCA.key -set_serial 05 -passin file:passphrase.txt -config configs/oemRootCA.cnf -extensions ext -out certs/oemRootCA.pem
# 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 CPOSub1CA)
openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys/oemSub1CA.key -aes128 -passout file:passphrase.txt
openssl req -new -key privateKeys/oemSub1CA.key -passin file:passphrase.txt -config configs/oemSub1CA.cnf -extensions ext -out csrs/oemSub1CA.csr
openssl x509 -req -in csrs/oemSub1CA.csr -extfile configs/oemSub1CA.cnf -extensions ext -CA certs/oemRootCA.pem -CAkey privateKeys/oemRootCA.key -set_serial 06 -passin file:passphrase.txt -days 1460 -out certs/oemSub1CA.pem
# 7) Create a second intermediate OEM sub-CA certificate which is directly signed by the OEMSub1CA certificate (validity is up to the OEM, this example applies the same validity as the CPOSub2CA)
openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys/oemSub2CA.key -aes128 -passout file:passphrase.txt
openssl req -new -key privateKeys/oemSub2CA.key -passin file:passphrase.txt -config configs/oemSub2CA.cnf -extensions ext -out csrs/oemSub2CA.csr
openssl x509 -req -in csrs/oemSub2CA.csr -extfile configs/oemSub2CA.cnf -extensions ext -CA certs/oemSub1CA.pem -CAkey privateKeys/oemSub1CA.key -set_serial 07 -passin file:passphrase.txt -days 1460 -out certs/oemSub2CA.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 secp256r1 | openssl ec -out privateKeys/oemProvCert.key -aes128 -passout file:passphrase.txt
openssl req -new -key privateKeys/oemProvCert.key -passin file:passphrase.txt -config configs/oemProvCert.cnf -extensions ext -out csrs/oemProvCert.csr
openssl x509 -req -in csrs/oemProvCert.csr -extfile configs/oemProvCert.cnf -extensions ext -CA certs/oemSub2CA.pem -CAkey privateKeys/oemSub2CA.key -set_serial 08 -passin file:passphrase.txt -days 60 -out certs/oemProvCert.pem
cat certs/oemSub2CA.pem certs/oemSub1CA.pem > certs/intermediateOEMCAs.pem
openssl pkcs12 -export -inkey privateKeys/oemProvCert.key -in certs/oemProvCert.pem -certfile certs/intermediateOEMCAs.pem -aes128 -passin file:passphrase.txt -passout file:passphrase2.txt -name oem_prov_cert -out certs/oemProvCert.p12
# 9) Create a self-signed MORootCA (mobility operator) certificate (validity is up to the OEM, this example applies the same validity as the V2GRootCA)
openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys/moRootCA.key -aes128 -passout file:passphrase.txt
openssl req -new -x509 -days 14600 -sha256 -key privateKeys/moRootCA.key -set_serial 09 -passin file:passphrase.txt -config configs/moRootCA.cnf -extensions ext -out certs/moRootCA.pem
# 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 CPOSub1CA)
openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys/moSub1CA.key -aes128 -passout file:passphrase.txt
openssl req -new -key privateKeys/moSub1CA.key -passin file:passphrase.txt -config configs/moSub1CA.cnf -extensions ext -out csrs/moSub1CA.csr
openssl x509 -req -in csrs/moSub1CA.csr -extfile configs/moSub1CA.cnf -extensions ext -CA certs/moRootCA.pem -CAkey privateKeys/moRootCA.key -set_serial 10 -passin file:passphrase.txt -days 1460 -out certs/moSub1CA.pem
# 11) Create a second intermediate MO sub-CA certificate which is directly signed by the MOSub1CA certificate (validity is up to the MO, this example applies the same validity as the CPOSub2CA)
openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys/moSub2CA.key -aes128 -passout file:passphrase.txt
openssl req -new -key privateKeys/moSub2CA.key -passin file:passphrase.txt -config configs/moSub2CA.cnf -extensions ext -out csrs/moSub2CA.csr
openssl x509 -req -in csrs/moSub2CA.csr -extfile configs/moSub2CA.cnf -extensions ext -CA certs/moSub1CA.pem -CAkey privateKeys/moSub1CA.key -set_serial 11 -passin file:passphrase.txt -days 1460 -out certs/moSub2CA.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 secp256r1 | openssl ec -out privateKeys/contractCert.key -aes128 -passout file:passphrase.txt
openssl req -new -key privateKeys/contractCert.key -passin file:passphrase.txt -config configs/contractCert.cnf -extensions ext -out csrs/contractCert.csr
openssl x509 -req -in csrs/contractCert.csr -extfile configs/contractCert.cnf -extensions ext -CA certs/moSub2CA.pem -CAkey privateKeys/moSub2CA.key -set_serial 12 -passin file:passphrase.txt -days 730 -out certs/contractCert.pem
cat certs/moSub2CA.pem certs/moSub1CA.pem > certs/intermediateMOCAs.pem
openssl pkcs12 -export -inkey privateKeys/contractCert.key -in certs/contractCert.pem -certfile certs/intermediateMOCAs.pem -aes128 -passin file:passphrase.txt -passout file:passphrase2.txt -name contract_cert -out certs/contractCert.p12
# 13) Create an intermediate provisioning service sub-CA certificate which is directly signed by the V2GRootCA certificate
openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys/provSub1CA.key -aes128 -passout file:passphrase.txt
openssl req -new -key privateKeys/provSub1CA.key -passin file:passphrase.txt -config configs/provSub1CA.cnf -extensions ext -out csrs/provSub1CA.csr
openssl x509 -req -in csrs/provSub1CA.csr -extfile configs/provSub1CA.cnf -extensions ext -CA certs/v2gRootCA.pem -CAkey privateKeys/v2gRootCA.key -set_serial 13 -passin file:passphrase.txt -days 1460 -out certs/provSub1CA.pem
# 14) Create a second intermediate provisioning sub-CA certificate which is directly signed by the ProvSub1CA certificate (validity 1 - 2 years, we make it 2 years)
openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys/provSub2CA.key -aes128 -passout file:passphrase.txt
openssl req -new -key privateKeys/provSub2CA.key -passin file:passphrase.txt -config configs/provSub2CA.cnf -extensions ext -out csrs/provSub2CA.csr
openssl x509 -req -in csrs/provSub2CA.csr -extfile configs/provSub2CA.cnf -extensions ext -CA certs/provSub1CA.pem -CAkey privateKeys/provSub1CA.key -set_serial 14 -passin file:passphrase.txt -days 730 -out certs/provSub2CA.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 secp256r1 | openssl ec -out privateKeys/provServiceCert.key -aes128 -passout file:passphrase.txt
openssl req -new -key privateKeys/provServiceCert.key -passin file:passphrase.txt -config configs/provServiceCert.cnf -extensions ext -out csrs/provServiceCert.csr
openssl x509 -req -in csrs/provServiceCert.csr -extfile configs/provServiceCert.cnf -extensions ext -CA certs/provSub2CA.pem -CAkey privateKeys/provSub2CA.key -set_serial 15 -passin file:passphrase.txt -days 90 -out certs/provServiceCert.pem
cat certs/provSub2CA.pem certs/provSub1CA.pem > certs/intermediateProvCAs.pem
openssl pkcs12 -export -inkey privateKeys/provServiceCert.key -in certs/provServiceCert.pem -certfile certs/intermediateProvCAs.pem -aes128 -passin file:passphrase.txt -passout file:passphrase2.txt -name prov_service_cert -out certs/provServiceCert.p12
# XX) Finally we need to convert the certificates from PEM format to DER format (PEM is the default format, but ISO/IEC 15118 only allows DER format)
openssl x509 -inform PEM -in certs/v2gRootCA.pem -outform DER -out certs/v2gRootCA.crt
openssl x509 -inform PEM -in certs/oemRootCA.pem -outform DER -out certs/oemRootCA.crt
openssl x509 -inform PEM -in certs/moRootCA.pem -outform DER -out certs/moRootCA.crt
# 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.
# XX) Create the initial Java truststores and keystores
# XX.1) truststore for the EVCC which needs to hold the V2GRootCA certificate (the EVCC does not verify the received certificate chain, therefore no MORootCA needs to be imported in evccTruststore.jks )
keytool -import -keystore keystores/evccTruststore.jks -alias v2g_root_ca -file certs/v2gRootCA.crt -storepass:file passphrase.txt -noprompt
# XX.2) truststore for the SECC which needs to hold the V2GRootCA certificate and the MORootCA which signed the MOSub1CA (needed for verifying the contract certificate signature chain which will be sent from the EVCC to the SECC with PaymentDetailsReq message). According to ISO/IEC 15118-2, MORootCA is not necessarily needed as the MOSub1CA could instead be signed by a V2GRootCA.
keytool -import -keystore keystores/seccTruststore.jks -alias v2g_root_ca -file certs/v2gRootCA.crt -storepass:file passphrase.txt -noprompt
keytool -import -keystore keystores/seccTruststore.jks -alias mo_root_ca -file certs/moRootCA.crt -storepass:file passphrase.txt -noprompt
# XX.3) keystore for the SECC which needs to hold the CPOSub1CA, CPOSub1CA and SECCCert certificates
keytool -importkeystore -srckeystore certs/seccCert.p12 -srcstoretype pkcs12 -srcstorepass:file passphrase.txt -srcalias secc_cert -destalias secc_cert -destkeystore keystores/seccKeystore.jks -storepass:file passphrase.txt -noprompt
# XX.4) keystore for the EVCC which needs to hold the OEMSub1CA, OEMSub2CA and OEMProvCert certificates
keytool -importkeystore -srckeystore certs/oemProvCert.p12 -srcstoretype pkcs12 -srcstorepass:file passphrase.txt -srcalias oem_prov_cert -destalias oem_prov_cert -destkeystore keystores/evccKeystore.jks -storepass:file passphrase.txt -noprompt
# Side notes for OCSP stapling in Java: see http://openjdk.java.net/jeps/8046321

View File

@ -0,0 +1 @@
123456

View File

@ -0,0 +1 @@
123456

1
RISE-V2G-EVCC/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

View File

@ -0,0 +1,71 @@
###############################################################################
# Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
#
# Contributors:
# Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
###############################################################################
# ===========================================================
# Configuration properties for a unique electric vehicle (EV)
# ===========================================================
# Network interface
#------------------
#
# The network interface (see also scope id from IPv6-address%scope_id) given as a String on which
# to communicate with the EVSE
NetworkInterface = lo0
# 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
TLSSecurity = true
# Contract certificate update timespan
#-------------------------------------
#
# Integer value defining the timespan in days which precedes the expiration of a contract certificate
# and during which an update of the contract certificate needs to be performed
ContractCertificateUpdateTimespan = 14
# SessionID
#----------
#
# If this value is unequal to zero, then it represents a previously
# paused V2G communication session
SessionID = 0
# 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
RequestedPaymentOption =
# 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
RequestedEnergyTransferMode = AC_three_phase_core

Binary file not shown.

Binary file not shown.

78
RISE-V2G-EVCC/pom.xml Normal file
View File

@ -0,0 +1,78 @@
<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>org.eclipse.risev2g</groupId>
<artifactId>parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<url>http://maven.apache.org</url>
<artifactId>evcc</artifactId>
<name>risev2g.evcc</name>
<dependencies>
<dependency>
<groupId>org.eclipse.risev2g</groupId>
<artifactId>shared</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>org.eclipse.risev2g.evcc.main.StartEVCC</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<appendAssemblyId>false</appendAssemblyId>
</configuration>
<executions>
<execution>
<id>make-my-jar-with-dependencies</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<descriptor>${project.baseUri}src/assembly/bin.xml</descriptor>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,39 @@
<assembly>
<id>bin</id>
<formats>
<format>tar.gz</format>
<format>tar.bz2</format>
<format>zip</format>
</formats>
<fileSets>
<fileSet>
<includes>
<include>README*</include>
</includes>
</fileSet>
<fileSet>
<directory>target</directory>
<outputDirectory>.</outputDirectory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>
<fileSet>
<directory>.</directory>
<outputDirectory>.</outputDirectory>
<includes>
<include>*.p12</include>
<include>*.jks</include>
</includes>
</fileSet>
<fileSet>
<directory>.</directory>
<outputDirectory>.</outputDirectory>
<includes>
<include>*.properties</include>
</includes>
</fileSet>
</fileSets>
</assembly>

View File

@ -0,0 +1,285 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.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 org.eclipse.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import org.eclipse.risev2g.shared.enumerations.CPStates;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ACEVChargeParameterType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargingProfileType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.DCEVChargeParameterType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.DCEVErrorCodeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.DCEVStatusType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EnergyTransferModeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PMaxScheduleEntryType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PMaxScheduleType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentOptionListType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PhysicalValueType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ProfileEntryType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.RelativeTimeIntervalType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SAScheduleListType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SAScheduleTupleType;
import org.eclipse.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;
public DummyEVController(V2GCommunicationSessionEVCC commSessionContext) {
setCommSessionContext(commSessionContext);
setCPState(CPStates.STATE_B); // should be signaled before ISO/IEC 15118 stack initializes
}
@Override
public PaymentOptionType getPaymentOption(PaymentOptionListType paymentOptionsOffered) {
if (paymentOptionsOffered.getPaymentOption().contains(PaymentOptionType.CONTRACT))
return PaymentOptionType.CONTRACT;
else
return PaymentOptionType.EXTERNAL_PAYMENT;
}
@Override
public EnergyTransferModeType getRequestedEnergyTransferMode() {
return EnergyTransferModeType.AC_SINGLE_PHASE_CORE;
}
@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(new Byte("0"));
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;
}
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) 32);
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");
}
}

View File

@ -0,0 +1,32 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.evcc.evController;
import javax.xml.bind.JAXBElement;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ACEVChargeParameterType;
import org.eclipse.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,96 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.evcc.evController;
import javax.xml.bind.JAXBElement;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.DCEVChargeParameterType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.DCEVStatusType;
import org.eclipse.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();
}

View File

@ -0,0 +1,66 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.evcc.evController;
import org.eclipse.risev2g.shared.enumerations.CPStates;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargingProfileType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EnergyTransferModeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentOptionListType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
public interface IEVController {
/**
* 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(PaymentOptionListType paymentOptionsOffered);
/**
* 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();
}

View File

@ -0,0 +1,30 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.evcc.main;
import org.eclipse.risev2g.evcc.session.V2GCommunicationSessionHandlerEVCC;
import org.eclipse.risev2g.evcc.transportLayer.UDPClient;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.utils.MiscUtils;
public class StartEVCC {
public static void main(String[] args) {
MiscUtils.setV2gEntityConfig(GlobalValues.EVCC_CONFIG_PROPERTIES_PATH.toString());
UDPClient udpClient = UDPClient.getInstance();
if (udpClient.initialize()) {
V2GCommunicationSessionHandlerEVCC sessionHandler = new V2GCommunicationSessionHandlerEVCC();
}
}
}

View File

@ -0,0 +1,451 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.evcc.session;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import org.eclipse.risev2g.evcc.evController.DummyEVController;
import org.eclipse.risev2g.evcc.evController.IEVController;
import org.eclipse.risev2g.evcc.states.WaitForAuthorizationRes;
import org.eclipse.risev2g.evcc.states.WaitForCableCheckRes;
import org.eclipse.risev2g.evcc.states.WaitForCertificateInstallationRes;
import org.eclipse.risev2g.evcc.states.WaitForCertificateUpdateRes;
import org.eclipse.risev2g.evcc.states.WaitForChargeParameterDiscoveryRes;
import org.eclipse.risev2g.evcc.states.WaitForChargingStatusRes;
import org.eclipse.risev2g.evcc.states.WaitForCurrentDemandRes;
import org.eclipse.risev2g.evcc.states.WaitForMeteringReceiptRes;
import org.eclipse.risev2g.evcc.states.WaitForPaymentDetailsRes;
import org.eclipse.risev2g.evcc.states.WaitForPaymentServiceSelectionRes;
import org.eclipse.risev2g.evcc.states.WaitForPowerDeliveryRes;
import org.eclipse.risev2g.evcc.states.WaitForPreChargeRes;
import org.eclipse.risev2g.evcc.states.WaitForServiceDetailRes;
import org.eclipse.risev2g.evcc.states.WaitForServiceDiscoveryRes;
import org.eclipse.risev2g.evcc.states.WaitForSessionSetupRes;
import org.eclipse.risev2g.evcc.states.WaitForSessionStopRes;
import org.eclipse.risev2g.evcc.states.WaitForSupportedAppProtocolRes;
import org.eclipse.risev2g.evcc.states.WaitForWeldingDetectionRes;
import org.eclipse.risev2g.evcc.transportLayer.StatefulTransportLayerClient;
import org.eclipse.risev2g.evcc.transportLayer.TCPClient;
import org.eclipse.risev2g.evcc.transportLayer.TLSClient;
import org.eclipse.risev2g.shared.enumerations.CPStates;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ChangeProcessingState;
import org.eclipse.risev2g.shared.messageHandling.PauseSession;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.messageHandling.SendMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.misc.V2GCommunicationSession;
import org.eclipse.risev2g.shared.misc.V2GTPMessage;
import org.eclipse.risev2g.shared.v2gMessages.appProtocol.AppProtocolType;
import org.eclipse.risev2g.shared.v2gMessages.appProtocol.SupportedAppProtocolRes;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscoveryReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargingProfileType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EnergyTransferModeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SAScheduleListType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SelectedServiceListType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SelectedServiceType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ServiceListType;
import org.eclipse.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 boolean stopChargingRequested;
private boolean renegotiationRequested;
private boolean pausingV2GCommSession;
private ChargingProfileType chargingProfile;
private ServiceListType offeredServices;
private SelectedServiceListType selectedServices;
private ArrayList<Short> 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;
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(new DummyEVController(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.currentTimeMillis());
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 {
obj = (V2GMessage) getMessageHandler().exiToV2gMsg(getV2gTpMessage().getPayload());
}
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 {
getLogger().warn("Notification received, but sending entity or received object not identifiable");
}
}
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() {
// TODO save respective parameters to properties file
}
private void deleteSessionProperties() {
// TODO delete the respective parameters from properties file
}
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 isStopChargingRequested() {
return stopChargingRequested;
}
public void setStopChargingRequested(boolean stopChargingRequested) {
this.stopChargingRequested = stopChargingRequested;
}
public boolean isRenegotiationRequested() {
return renegotiationRequested;
}
public void setRenegotiationRequested(boolean renegotiationRequested) {
this.renegotiationRequested = renegotiationRequested;
}
public boolean isPausingV2GCommSession() {
return pausingV2GCommSession;
}
public void setPausingV2GCommSession(boolean pausingV2GCommSession) {
this.pausingV2GCommSession = pausingV2GCommSession;
}
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.currentTimeMillis();
}
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<Short> getServiceDetailsToBeRequested() {
if (serviceDetailsToBeRequested == null) {
serviceDetailsToBeRequested = new ArrayList<Short>();
}
return serviceDetailsToBeRequested;
}
public void setServiceDetailsToBeRequested(
ArrayList<Short> serviceDetailsToBeRequested) {
this.serviceDetailsToBeRequested = serviceDetailsToBeRequested;
}
public boolean isCertificateInstallationNeeded() {
for (SelectedServiceType service : getSelectedServices().getSelectedService()) {
if (service.getServiceID() == 2 &&
service.getParameterSetID() != null &&
service.getParameterSetID() == 1)
return true;
}
return false;
}
public boolean isCertificateUpdateNeeded() {
for (SelectedServiceType service : getSelectedServices().getSelectedService()) {
if (service.getServiceID() == 2 &&
service.getParameterSetID() != null &&
service.getParameterSetID() == 2)
return true;
}
return false;
}
}

View File

@ -0,0 +1,388 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.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 org.eclipse.risev2g.evcc.transportLayer.StatefulTransportLayerClient;
import org.eclipse.risev2g.evcc.transportLayer.TCPClient;
import org.eclipse.risev2g.evcc.transportLayer.TLSClient;
import org.eclipse.risev2g.evcc.transportLayer.UDPClient;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.MessageHandler;
import org.eclipse.risev2g.shared.messageHandling.PauseSession;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.misc.TimeRestrictions;
import org.eclipse.risev2g.shared.misc.V2GTPMessage;
import org.eclipse.risev2g.shared.utils.ByteUtils;
import org.eclipse.risev2g.shared.utils.MiscUtils;
import org.eclipse.risev2g.shared.v2gMessages.SECCDiscoveryReq;
import org.eclipse.risev2g.shared.v2gMessages.SECCDiscoveryRes;
import org.eclipse.risev2g.shared.v2gMessages.appProtocol.AppProtocolType;
import org.eclipse.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(new MessageHandler());
setSecurity(
(MiscUtils.getPropertyValue("TLSSecurity") != null ?
(byte) MiscUtils.getPropertyValue("TLSSecurity") :
GlobalValues.V2G_SECURITY_WITHOUT_TLS.getByteValue())
);
setSessionRetryCounter(0);
if (!initialize()) {
// TODO ja, was tun?
};
}
private boolean initialize() {
byte[] udpResponse = null;
SECCDiscoveryRes seccDiscoveryRes = null;
setSessionRetryCounter(getSeccDiscoveryRequestCounter() + 1);
// Create SECCDiscoveryReq and check response
while (udpResponse == null &&
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;
}
}
}
/*
* Establish a new V2GCommunicationSessionEVCC if SECCDiscoveryRes was successful and initiate
* the respective TCP client connection
*/
if (startNewSession(seccDiscoveryRes)) return true;
else return false;
}
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);
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) {
setV2gCommunicationSessionEVCC(null);
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.getV2G_EVCC_Msg_Timeout(V2GMessages.SUPPORTED_APP_PROTOCOL_RES));
} else {
TCPClient.getInstance().send(
getV2gTpMessage(),
TimeRestrictions.getV2G_EVCC_Msg_Timeout(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,450 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.evcc.states;
import java.security.KeyStore;
import java.util.Arrays;
import java.util.ListIterator;
import org.eclipse.risev2g.evcc.evController.IACEVController;
import org.eclipse.risev2g.evcc.evController.IDCEVController;
import org.eclipse.risev2g.evcc.evController.DummyEVController;
import org.eclipse.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import org.eclipse.risev2g.shared.enumerations.CPStates;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.misc.State;
import org.eclipse.risev2g.shared.utils.ByteUtils;
import org.eclipse.risev2g.shared.utils.MiscUtils;
import org.eclipse.risev2g.shared.utils.SecurityUtils;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.AuthorizationReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.AuthorizationResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CableCheckReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CableCheckResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateInstallationResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateUpdateResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscoveryReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscoveryResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargeProgressType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargingProfileType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargingStatusResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CurrentDemandReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CurrentDemandResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EMAIDType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EnergyTransferModeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.MessageHeaderType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.MeteringReceiptResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentDetailsReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentDetailsResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentServiceSelectionReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentServiceSelectionResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PowerDeliveryReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PowerDeliveryResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PreChargeResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ServiceDetailReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ServiceDetailResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ServiceDiscoveryResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SessionSetupResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SessionStopReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SessionStopResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.V2GMessage;
import org.eclipse.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<Short> listIterator = getCommSessionContext().getServiceDetailsToBeRequested().listIterator();
ServiceDetailReqType serviceDetailReq = new ServiceDetailReqType();
serviceDetailReq.setServiceID((short) 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);
authorizationReq.setId("authorizationReq");
}
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(((DummyEVController) 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());
// Optionally set DC_EVPowerDeliveryParameter if in DC charging mode
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("DC")) {
}
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() {
// Check if an EnergyTransferModeType has been requested in a previously paused session
EnergyTransferModeType requestedEnergyTransferMode =
(EnergyTransferModeType) MiscUtils.getPropertyValue("RequestedEnergyTransferMode");
if (requestedEnergyTransferMode == null) {
requestedEnergyTransferMode = getCommSessionContext().getEvController().getRequestedEnergyTransferMode();
getCommSessionContext().setRequestedEnergyTransferMode(requestedEnergyTransferMode);
}
return requestedEnergyTransferMode;
}
protected PaymentOptionType getSelectedPaymentOption() {
// Check if a PaymentOptionType has been requested in a previously paused session
PaymentOptionType selectedPaymentOption = (PaymentOptionType) MiscUtils.getPropertyValue("RequestedPaymentOption");
if (selectedPaymentOption == null) {
selectedPaymentOption = getCommSessionContext().getEvController().getPaymentOption(getCommSessionContext().getPaymentOptions());
getCommSessionContext().setSelectedPaymentOption(selectedPaymentOption);
}
return selectedPaymentOption;
}
}

View File

@ -0,0 +1,69 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.evcc.states;
import org.eclipse.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.utils.SecurityUtils;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.AuthorizationReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.AuthorizationResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscoveryReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EVSEProcessingType;
import org.eclipse.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().equals(EVSEProcessingType.FINISHED)) {
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 {
// Set xml reference element
AuthorizationReqType authorizationReq = getAuthorizationReq(null);
getXMLSignatureRefElements().put(
authorizationReq.getId(),
SecurityUtils.generateDigest(authorizationReq, false));
// 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,56 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.evcc.states;
import org.eclipse.risev2g.evcc.evController.IDCEVController;
import org.eclipse.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CableCheckResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EVSEProcessingType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PreChargeReqType;
import org.eclipse.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());
return getSendMessage(preChargeReq, V2GMessages.PRE_CHARGE_RES);
} else {
getLogger().debug("EVSEProcessing was set to ONGOING");
return getSendMessage(getCableCheckReq(), V2GMessages.CABLE_CHECK_RES);
}
} else {
return new TerminateSession("Incoming message raised an error");
}
}
}

View File

@ -0,0 +1,92 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.evcc.states;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.util.HashMap;
import org.eclipse.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.utils.SecurityUtils;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateInstallationResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SignatureType;
import org.eclipse.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");
}
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(), false));
verifyXMLSigRefElements.put(
certificateInstallationRes.getContractSignatureEncryptedPrivateKey().getId(),
SecurityUtils.generateDigest(certificateInstallationRes.getContractSignatureEncryptedPrivateKey(), false));
verifyXMLSigRefElements.put(
certificateInstallationRes.getDHpublickey().getId(),
SecurityUtils.generateDigest(certificateInstallationRes.getDHpublickey(), false));
verifyXMLSigRefElements.put(
certificateInstallationRes.getEMAID().getId(),
SecurityUtils.generateDigest(certificateInstallationRes.getEMAID(), false));
ECPublicKey ecPublicKey = (ECPublicKey) SecurityUtils.getCertificate(
certificateInstallationRes.getSAProvisioningCertificateChain().getCertificate())
.getPublicKey();
if (!SecurityUtils.verifySignature(signature, verifyXMLSigRefElements, ecPublicKey)) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,92 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.evcc.states;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.util.HashMap;
import org.eclipse.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.utils.SecurityUtils;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateUpdateResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SignatureType;
import org.eclipse.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");
}
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(), false));
verifyXMLSigRefElements.put(
certificateUpdateRes.getContractSignatureEncryptedPrivateKey().getId(),
SecurityUtils.generateDigest(certificateUpdateRes.getContractSignatureEncryptedPrivateKey(), false));
verifyXMLSigRefElements.put(
certificateUpdateRes.getDHpublickey().getId(),
SecurityUtils.generateDigest(certificateUpdateRes.getDHpublickey(), false));
verifyXMLSigRefElements.put(
certificateUpdateRes.getEMAID().getId(),
SecurityUtils.generateDigest(certificateUpdateRes.getEMAID(), false));
ECPublicKey ecPublicKey = (ECPublicKey) SecurityUtils.getCertificate(
certificateUpdateRes.getSAProvisioningCertificateChain().getCertificate())
.getPublicKey();
if (!SecurityUtils.verifySignature(signature, verifyXMLSigRefElements, ecPublicKey)) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,147 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.evcc.states;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPublicKey;
import java.util.HashMap;
import java.util.List;
import org.eclipse.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import org.eclipse.risev2g.shared.enumerations.CPStates;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.utils.SecurityUtils;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ACEVSEChargeParameterType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscoveryResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargeProgressType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.DCEVSEChargeParameterType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EVSEProcessingType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SAScheduleListType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SAScheduleTupleType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SignatureType;
import org.eclipse.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().equals(EVSEProcessingType.ONGOING)) {
getLogger().debug("EVSEProcessing was set to ONGOING");
return getSendMessage(getCommSessionContext().getChargeParameterDiscoveryReq(), V2GMessages.CHARGE_PARAMETER_DISCOVERY_RES);
} else {
// Check for the EVSENotification
EVSENotificationType evseNotification = null;
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC"))
evseNotification = ((ACEVSEChargeParameterType) chargeParameterDiscoveryRes
.getEVSEChargeParameter().getValue())
.getACEVSEStatus().getEVSENotification();
else
evseNotification = ((DCEVSEChargeParameterType) chargeParameterDiscoveryRes
.getEVSEChargeParameter().getValue())
.getDCEVSEStatus().getEVSENotification();
if (evseNotification.equals(EVSENotificationType.STOP_CHARGING)) {
getLogger().debug("The EVSE requested to stop the charging process");
getCommSessionContext().setStopChargingRequested(true);
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
*/
SAScheduleListType saSchedules = (SAScheduleListType) chargeParameterDiscoveryRes.getSASchedules().getValue();
// Verify each sales tariff with the mobility operator sub 2 certificate
if (saSchedules != null && !verifySalesTariffs(saSchedules, v2gMessageRes.getHeader().getSignature()))
return new TerminateSession("Verification of sales tariffs failed");
// Save the list of SASchedules (saves the time of reception as well)
getCommSessionContext().setSaSchedules(saSchedules);
if (getCommSessionContext().getEvController().getCPState().equals(CPStates.STATE_B)) {
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))
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");
}
}
private boolean verifySalesTariffs(SAScheduleListType saSchedules, SignatureType signature) {
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;
salesTariffCounter++;
verifyXMLSigRefElements.put(
saScheduleTuple.getSalesTariff().getId(),
SecurityUtils.generateDigest(saScheduleTuple.getSalesTariff(), false));
}
if (salesTariffCounter > 0) {
X509Certificate moSub2Certificate = SecurityUtils.getMOSub2Certificate(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString());
if (moSub2Certificate == null) {
getLogger().error("No MOSub2Certificate found");
return false;
} else {
ECPublicKey ecPublicKey = (ECPublicKey) moSub2Certificate.getPublicKey();
if (!SecurityUtils.verifySignature(signature, verifyXMLSigRefElements, ecPublicKey)) {
return false;
}
}
}
return true;
}
}

View File

@ -0,0 +1,90 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.evcc.states;
import org.eclipse.risev2g.evcc.evController.IACEVController;
import org.eclipse.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.utils.SecurityUtils;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargeProgressType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargingStatusResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.MeteringReceiptReqType;
import org.eclipse.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
if (chargingStatusRes.isReceiptRequired()) {
MeteringReceiptReqType meteringReceiptReq = new MeteringReceiptReqType();
meteringReceiptReq.setId("meteringReceiptReq");
meteringReceiptReq.setMeterInfo(chargingStatusRes.getMeterInfo());
meteringReceiptReq.setSAScheduleTupleID(chargingStatusRes.getSAScheduleTupleID());
meteringReceiptReq.setSessionID(getCommSessionContext().getSessionID());
// Set xml reference element
getXMLSignatureRefElements().put(
meteringReceiptReq.getId(),
SecurityUtils.generateDigest(meteringReceiptReq, false));
// 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().setStopChargingRequested(true);
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.STOP),
V2GMessages.POWER_DELIVERY_RES,
" (ChargeProgress = STOP_CHARGING)");
case RE_NEGOTIATION:
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)
// TODO check somehow if charging is stopped by EV, otherwise send new ChargingStatusReq
getCommSessionContext().setStopChargingRequested(true);
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,75 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.evcc.states;
import org.eclipse.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargeProgressType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CurrentDemandResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.DCEVSEStatusType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.MeteringReceiptReqType;
import org.eclipse.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()) {
MeteringReceiptReqType meteringReceiptReq = new MeteringReceiptReqType();
meteringReceiptReq.setId("MeterInfo");
meteringReceiptReq.setMeterInfo(currentDemandRes.getMeterInfo());
meteringReceiptReq.setSAScheduleTupleID(currentDemandRes.getSAScheduleTupleID());
meteringReceiptReq.setSessionID(getCommSessionContext().getSessionID());
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().setStopChargingRequested(true);
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.STOP),
V2GMessages.POWER_DELIVERY_RES,
" (ChargeProgress = STOP_CHARGING)");
case RE_NEGOTIATION:
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)
// TODO check somehow if charging is stopped by EV, otherwise send new CurrentDemandReq
getCommSessionContext().setStopChargingRequested(true);
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,90 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.evcc.states;
import org.eclipse.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ACEVSEStatusType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargeProgressType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargingStatusReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.DCEVSEStatusType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.MeteringReceiptResType;
import org.eclipse.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().setStopChargingRequested(true);
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)
// TODO check somehow if charging is stopped by EV, otherwise send new ChargingStatusReq/CurrentDemandReq
if (isAcCharging()) {
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 boolean isAcCharging() {
return acCharging;
}
private void setAcCharging(boolean acCharging) {
this.acCharging = acCharging;
}
}

View File

@ -0,0 +1,60 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.evcc.states;
import org.eclipse.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.utils.SecurityUtils;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.AuthorizationReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentDetailsResType;
import org.eclipse.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();
if (paymentDetailsRes.getGenChallenge() == null)
return new TerminateSession("GenChallenge not provided in PaymentDetailsRes");
else {
// Set xml reference element
AuthorizationReqType authorizationReq = getAuthorizationReq(paymentDetailsRes.getGenChallenge());
getXMLSignatureRefElements().put(
authorizationReq.getId(),
SecurityUtils.generateDigest(authorizationReq, false));
// 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,112 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.evcc.states;
import java.security.KeyStore;
import org.eclipse.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.utils.SecurityUtils;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateInstallationReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateUpdateReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import org.eclipse.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().isCertificateInstallationNeeded())
return getSendMessage(getCertificateInstallationReq(), V2GMessages.CERTIFICATE_INSTALLATION_RES);
else if (getCommSessionContext().isCertificateUpdateNeeded())
return getSendMessage(getCertificateUpdateReq(), V2GMessages.CERTIFICATE_UPDATE_RES);
else
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();
certInstallationReq.setId("certificateInstallationReq");
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, false));
// 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());
certificateUpdateReq.setId("certificateUpdateReq");
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, false));
// 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,94 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.evcc.states;
import org.eclipse.risev2g.evcc.evController.IDCEVController;
import org.eclipse.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import org.eclipse.risev2g.shared.enumerations.CPStates;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargingStatusReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.DCEVStatusType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PowerDeliveryResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.V2GMessage;
import org.eclipse.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()) {
getCommSessionContext().setRenegotiationRequested(false);
return getSendMessage(getChargeParameterDiscoveryReq(), V2GMessages.CHARGE_PARAMETER_DISCOVERY_RES);
} else if (getCommSessionContext().isStopChargingRequested()) {
return getSendMessage(ChargingSessionType.TERMINATE, true);
} else if (getCommSessionContext().isPausingV2GCommSession()) {
return getSendMessage(ChargingSessionType.PAUSE, false);
} 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, boolean stopChargingRequested) {
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("DC")) {
// CP state C signaling BEFORE sending WeldingDetectionReq message in DC
if (getCommSessionContext().getEvController().setCPState(CPStates.STATE_C)) {
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 C not ready (current state = " +
getCommSessionContext().getEvController().getCPState() +
")");
}
} else {
if (stopChargingRequested) getCommSessionContext().setStopChargingRequested(false);
else getCommSessionContext().setPausingV2GCommSession(false);
return getSendMessage(getSessionStopReq(chargingSessionType),
V2GMessages.SESSION_STOP_RES, "(ChargingSession = " +
chargingSessionType.toString() + ")");
}
}
}

View File

@ -0,0 +1,41 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.evcc.states;
import org.eclipse.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargeProgressType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PreChargeResType;
import org.eclipse.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 and EVSEPresentVoltage?
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.START), V2GMessages.POWER_DELIVERY_RES);
} else {
return new TerminateSession("Incoming message raised an error");
}
}
}

View File

@ -0,0 +1,48 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.evcc.states;
import org.eclipse.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ServiceDetailReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ServiceDetailResType;
import org.eclipse.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,171 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.evcc.states;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.Date;
import org.eclipse.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import org.eclipse.risev2g.evcc.transportLayer.TLSClient;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.utils.MiscUtils;
import org.eclipse.risev2g.shared.utils.SecurityUtils;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateChainType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EnergyTransferModeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SelectedServiceType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ServiceCategoryType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ServiceDiscoveryResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ServiceListType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ServiceType;
import org.eclipse.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 offered charge service and optional value added services
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().setRequestedEnergyTransferMode(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");
}
} else return new TerminateSession("No charge service available");
/*
* 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)
*/
PaymentOptionType userPaymentOption =
getCommSessionContext().getEvController().getPaymentOption(serviceDiscoveryRes.getPaymentOptionList());
getCommSessionContext().setSelectedPaymentOption(userPaymentOption);
// 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");
}
}
/**
* 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 (getCommSessionContext().getTransportLayerClient() instanceof TLSClient) {
// Check if certificate service is needed
if (isCertificateServiceOffered(serviceDiscoveryRes.getServiceList())) {
KeyStore evccKeyStore = SecurityUtils.getKeyStore(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
CertificateChainType contractCertificateChain =
SecurityUtils.getCertificateChain(evccKeyStore, GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString());
if (contractCertificateChain != null) {
if (!SecurityUtils.isCertificateChainValid(contractCertificateChain)) {
addSelectedService(2, (short) 1);
} else {
if (isContractCertificateUpdateNeeded(contractCertificateChain)) {
addSelectedService(2, (short) 2);
}
}
} else {
addSelectedService(2, (short) 1);
}
}
// Optionally, other value added services can be checked for here ...
}
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);
getCommSessionContext().getServiceDetailsToBeRequested().add((short) serviceID);
}
private boolean isCertificateServiceOffered(ServiceListType offeredServiceList) {
for (ServiceType service : offeredServiceList.getService()) {
if (service.getServiceCategory().equals(ServiceCategoryType.CONTRACT_CERTIFICATE))
return true;
}
return false;
}
private boolean isContractCertificateUpdateNeeded(CertificateChainType contractCertificateChain) {
Date today = new Date();
X509Certificate contractCertificate = SecurityUtils.getCertificate(contractCertificateChain.getCertificate());
long validityDays = contractCertificate.getNotAfter().getTime() - today.getTime();
if (contractCertificate != null && validityDays <
( ((long) (int) MiscUtils.getPropertyValue("ContractCertificateUpdateTimespan")) * 24 * 60 * 60 * 1000 )) {
getLogger().info("Contract certificate with distinguished name '" +
contractCertificate.getSubjectX500Principal().getName() +
"' is only valid for " + validityDays / (1000 * 60 * 60 * 24) +
" days and needs to be updated");
return true;
} else return false;
}
}

View File

@ -0,0 +1,77 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.evcc.states;
import org.eclipse.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.utils.ByteUtils;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ServiceDiscoveryReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SessionSetupResType;
import org.eclipse.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)) {
getCommSessionContext().setSessionID(receivedSessionID);
getLogger().debug("Negotiated session ID is " + ByteUtils.toLongFromByteArray(receivedSessionID));
getCommSessionContext().setOldSessionJoined(false);
getCommSessionContext().setEvseID(sessionSetupRes.getEVSEID());
// EVSETimeStamp is optional
if (sessionSetupRes.getEVSETimeStamp() != null) getCommSessionContext().setEvseTimeStamp(sessionSetupRes.getEVSETimeStamp());
} else if (sessionSetupRes.getResponseCode().equals(ResponseCodeType.OK_OLD_SESSION_JOINED)) {
getLogger().debug("Previous charging session joined (session ID = " + ByteUtils.toLongFromByteArray(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);
getCommSessionContext().setEvseID(sessionSetupRes.getEVSEID());
getCommSessionContext().setEvseTimeStamp(sessionSetupRes.getEVSETimeStamp());
} 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");
}
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,32 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.evcc.states;
import org.eclipse.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.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)) {
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,86 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.evcc.states;
import org.eclipse.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.misc.TimeRestrictions;
import org.eclipse.risev2g.shared.utils.MiscUtils;
import org.eclipse.risev2g.shared.v2gMessages.appProtocol.AppProtocolType;
import org.eclipse.risev2g.shared.v2gMessages.appProtocol.ResponseCodeType;
import org.eclipse.risev2g.shared.v2gMessages.appProtocol.SupportedAppProtocolRes;
import org.eclipse.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/IEC 15118 controller is shut down for energy
* saving reasons.
* The initial value for a completely new charging session must be 0.
*/
long sessionID = (long) MiscUtils.getPropertyValue("SessionID");
getCommSessionContext().setSessionID(
getCommSessionContext().generateSessionIDFromValue(sessionID)
);
} else {
return new TerminateSession("No supported appProtocol found (positive response code received, but no valid schemaID)");
}
return getSendMessage(sessionSetupReq, V2GMessages.SESSION_SETUP_RES, (int) Math.min(
TimeRestrictions.getV2G_EVCC_Msg_Timeout(V2GMessages.SESSION_SETUP_RES),
TimeRestrictions.V2G_EVCC_COMMUNICATION_SETUP_TIMEOUT - (System.currentTimeMillis() - getCommSessionContext().getV2gEVCCCommunicationSetupTimer())
));
} else {
return new TerminateSession("Invalid message (" + message.getClass().getSimpleName() +
") at this state (" + this.getClass().getSimpleName() + ")");
}
}
}

View File

@ -0,0 +1,58 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.evcc.states;
import org.eclipse.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.V2GMessage;
import org.eclipse.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().isPausingV2GCommSession()) {
getCommSessionContext().setPausingV2GCommSession(false);
return getSendMessage(getSessionStopReq(ChargingSessionType.PAUSE),
V2GMessages.SESSION_STOP_RES, "(ChargingSession = " +
ChargingSessionType.PAUSE.toString() + ")");
} else {
getCommSessionContext().setStopChargingRequested(false);
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,226 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.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 org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.misc.V2GTPMessage;
import org.eclipse.risev2g.shared.utils.ByteUtils;
import org.eclipse.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 final int MASK = 0x80;
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]);
}
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 (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 most significant bit of v2gTPHeader[4] should never be set!
* If it was set, then this would mean that a V2GTP message of a size of at least 2 GB
* was intended to be transferred ... and this cannot be, no V2G message has this size!
* Since the most significant bit should never be set, we do not need to care about
* signed integers in Java at this point!
*/
if ((getV2gTPHeader()[4] & getMASK()) == getMASK()) {
stopAndNotify("Payload length of V2GTP message is inappropiately high! There must be " +
"an error in the V2GTP message header!", null);
return false;
} else {
setPayloadLength(ByteUtils.toIntFromByteArray(Arrays.copyOfRange(getV2gTPHeader(), 4, 8)));
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 int getMASK() {
return MASK;
}
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,152 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.evcc.transportLayer;
import java.io.IOException;
import java.net.Inet6Address;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import org.eclipse.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().debug("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.interrupted()) {
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;
}
}
}
}
@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();
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,178 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.evcc.transportLayer;
import java.io.IOException;
import java.net.Inet6Address;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.misc.V2GTPMessage;
import org.eclipse.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();
setTlsSocketToServer((SSLSocket) sslSocketFactory.createSocket(host, port));
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
*/
String[] enabledCipherSuites = {"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256"};
getTlsSocketToServer().setEnabledCipherSuites(enabledCipherSuites);
// Set the supported TLS protocol
String[] enabledProtocols = {"TLSv1.2"};
getTlsSocketToServer().setEnabledProtocols(enabledProtocols);
getLogger().debug("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("TCP client connection failed (UnknownHostException)!", e);
} catch (IOException e) {
getLogger().error("TCP 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;
}
return false;
}
@Override
public void run() {
while (!Thread.interrupted()) {
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;
}
}
}
}
@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 TLS client ...");
setStopAlreadyInitiated(true);
try {
getInStream().close();
getOutStream().close();
getTlsSocketToServer().close();
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,197 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.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 org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.misc.TimeRestrictions;
import org.eclipse.risev2g.shared.misc.V2GTPMessage;
import org.eclipse.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 final 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 UDPClient getInstance() {
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() {
// the UDPClient is not supposed to be stopped
}
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,22 @@
<?xml version="1.0" encoding="UTF-8" ?>
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appenders>
<console name="Console" target="SYSTEM_OUT">
<patternLayout pattern="%d{ISO8601} %-5p [%t] %c: %m%n" />
</console>
<!-- <param name="threshold" value="debug" />
<param name="Target" value="System.out" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{ISO8601} %-5p [%t] %c: %m%n" />
</layout>-->
</appenders>
<loggers>
<root level="debug">
<appender-ref ref="Console" />
</root>
</loggers>
</log4j:configuration>
<!-- see http://logging.apache.org/log4j/2.x/manual/configuration.html -->

View File

@ -0,0 +1,65 @@
###############################################################################
# Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
#
# Contributors:
# Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
###############################################################################
# ==============================================================================
# Configuration properties for a unique electric vehicle supply equipment (EVSE)
# ==============================================================================
# Network interface
#------------------
#
# The network interface (see also scope id from IPv6-address%scope_id) given as a String on which
# to communicate with the EV
NetworkInterface = lo0
# 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
SupportedEnergyTransferModes = AC_three_phase_core, AC_single_phase_core, DC_core, DC_extended
# Is charging a free service?
#----------------------------
#
# Possible values:
# - true
# - false
ChargingForFree = 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.
SupportedPaymentOptions = 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
PrivateEnvironment = false

Binary file not shown.

67
RISE-V2G-SECC/pom.xml Normal file
View File

@ -0,0 +1,67 @@
<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>org.eclipse.risev2g</groupId>
<artifactId>parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<url>http://maven.apache.org</url>
<artifactId>secc</artifactId>
<name>risev2g.secc</name>
<dependencies>
<dependency>
<groupId>org.eclipse.risev2g</groupId>
<artifactId>shared</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>org.eclipse.risev2g.secc.main.StartSECC</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<appendAssemblyId>false</appendAssemblyId>
</configuration>
<executions>
<execution>
<id>make-my-jar-with-dependencies</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<descriptor>${project.baseUri}src/assembly/bin.xml</descriptor>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,39 @@
<assembly>
<id>bin</id>
<formats>
<format>tar.gz</format>
<format>tar.bz2</format>
<format>zip</format>
</formats>
<fileSets>
<fileSet>
<includes>
<include>README*</include>
</includes>
</fileSet>
<fileSet>
<directory>target</directory>
<outputDirectory>.</outputDirectory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>
<fileSet>
<directory>.</directory>
<outputDirectory>.</outputDirectory>
<includes>
<include>*.p12</include>
<include>*.jks</include>
</includes>
</fileSet>
<fileSet>
<directory>.</directory>
<outputDirectory>.</outputDirectory>
<includes>
<include>*.properties</include>
</includes>
</fileSet>
</fileSets>
</assembly>

View File

@ -0,0 +1,132 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.backend;
import java.security.KeyStore;
import java.security.interfaces.ECPrivateKey;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.risev2g.secc.session.V2GCommunicationSessionSECC;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.utils.SecurityUtils;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateChainType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PMaxScheduleEntryType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PMaxScheduleType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PhysicalValueType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.RelativeTimeIntervalType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SAScheduleListType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SAScheduleTupleType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.UnitSymbolType;
public class DummyBackendInterface implements IBackendInterface {
private V2GCommunicationSessionSECC commSessionContext;
private Logger logger = LogManager.getLogger(this.getClass().getSimpleName());
public DummyBackendInterface(V2GCommunicationSessionSECC commSessionContext) {
setCommSessionContext(commSessionContext);
}
@Override
public SAScheduleListType getSAScheduleList() {
/*
* PMaxSchedule
*/
PhysicalValueType pMaxValue = new PhysicalValueType();
pMaxValue.setMultiplier(new Byte("3"));
pMaxValue.setUnit(UnitSymbolType.W);
pMaxValue.setValue((short) 11);
RelativeTimeIntervalType timeInterval = new RelativeTimeIntervalType();
timeInterval.setStart(0);
timeInterval.setDuration(3600L);
PMaxScheduleEntryType pMaxScheduleEntry = new PMaxScheduleEntryType();
pMaxScheduleEntry.setTimeInterval(new JAXBElement<RelativeTimeIntervalType>(
new QName("urn:iso:15118:2:2013:MsgDataTypes", "RelativeTimeInterval"),
RelativeTimeIntervalType.class,
timeInterval));
pMaxScheduleEntry.setPMax(pMaxValue);
PMaxScheduleType pMaxSchedule = new PMaxScheduleType();
pMaxSchedule.getPMaxScheduleEntry().add(pMaxScheduleEntry);
/*
* 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)
*/
/*
* 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);
return saScheduleList;
}
@Override
public CertificateChainType getContractCertificateChain() {
return SecurityUtils.getCertificateChain("./contractCert.p12");
}
@Override
public ECPrivateKey getContractCertificatePrivateKey() {
KeyStore keyStore = SecurityUtils.getPKCS12KeyStore(
"./contractCert.p12",
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
return SecurityUtils.getPrivateKey(keyStore);
}
@Override
public ECPrivateKey getSAProvisioningCertificatePrivateKey() {
KeyStore keyStore = SecurityUtils.getPKCS12KeyStore(
"./provServiceCert.p12",
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
return SecurityUtils.getPrivateKey(keyStore);
}
@Override
public CertificateChainType getSAProvisioningCertificateChain() {
return SecurityUtils.getCertificateChain("./provServiceCert.p12");
}
public V2GCommunicationSessionSECC getCommSessionContext() {
return commSessionContext;
}
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,56 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.backend;
import java.security.interfaces.ECPrivateKey;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateChainType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SAScheduleListType;
public interface IBackendInterface {
/**
* 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.
* @return An SASchedulesType element with a list of secondary actor schedules
*/
public SAScheduleListType getSAScheduleList();
/**
* Provides a certificate chain coming from a secondary actor with the leaf certificate being
* the contract certificate and possible intermediate certificates (sub CAs) included.
* @return Certificate chain for contract certificate
*/
public CertificateChainType getContractCertificateChain();
/**
* 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 getSAProvisioningCertificateChain();
/**
* Provides the private key belonging to the SA provisioning certificate.
* @return PrivateKey of the SA provisioning certificate
*/
public ECPrivateKey getSAProvisioningCertificatePrivateKey();
}

View File

@ -0,0 +1,108 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.evseController;
import java.math.BigInteger;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import org.eclipse.risev2g.secc.session.V2GCommunicationSessionSECC;
import org.eclipse.risev2g.shared.utils.ByteUtils;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ACEVSEChargeParameterType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ACEVSEStatusType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.MeterInfoType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PhysicalValueType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.UnitSymbolType;
public class DummyACEVSEController implements IACEVSEController {
private V2GCommunicationSessionSECC commSessionContext;
public DummyACEVSEController(V2GCommunicationSessionSECC commSessionContext) {
setCommSessionContext(commSessionContext);
}
@Override
public String getEvseID() {
return "EVSEID-0";
}
@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;
}
public V2GCommunicationSessionSECC getCommSessionContext() {
return commSessionContext;
}
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,226 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.evseController;
import java.math.BigInteger;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import org.eclipse.risev2g.secc.session.V2GCommunicationSessionSECC;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.DCEVSEChargeParameterType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.DCEVSEStatusCodeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.DCEVSEStatusType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.MeterInfoType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PhysicalValueType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.UnitSymbolType;
public class DummyDCEVSEController implements IDCEVSEController {
private V2GCommunicationSessionSECC commSessionContext;
private PhysicalValueType targetCurrent;
private PhysicalValueType targetVoltage;
private PhysicalValueType maximumEVVoltageLimit;
private PhysicalValueType maximumEVCurrentLimit;
private PhysicalValueType maximumEVPowerLimit;
public DummyDCEVSEController(V2GCommunicationSessionSECC commSessionContext) {
setCommSessionContext(commSessionContext);
}
@Override
public String getEvseID() {
return "EVSEID-0";
}
@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;
}
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(IsolationLevelType.INVALID);
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;
}
}

View File

@ -0,0 +1,34 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.evseController;
import javax.xml.bind.JAXBElement;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ACEVSEChargeParameterType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ACEVSEStatusType;
import org.eclipse.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,147 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.evseController;
import javax.xml.bind.JAXBElement;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.DCEVSEChargeParameterType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.DCEVSEStatusType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import org.eclipse.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 AC charging comprising notification, maxDelay and RCD
* @return The EVSE specific status
*/
public DCEVSEStatusType getDCEVSEStatus(EVSENotificationType notification);
}

View File

@ -0,0 +1,50 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.evseController;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.MeterInfoType;
public interface IEVSEController {
/**
* 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,50 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.main;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.risev2g.secc.session.V2GCommunicationSessionHandlerSECC;
import org.eclipse.risev2g.secc.transportLayer.TCPServer;
import org.eclipse.risev2g.secc.transportLayer.TLSServer;
import org.eclipse.risev2g.secc.transportLayer.UDPServer;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.utils.MiscUtils;
public class StartSECC {
public static void main(String[] args) {
final Logger logger = LogManager.getLogger(StartSECC.class.getSimpleName());
MiscUtils.setV2gEntityConfig(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");
udpServerThread.start();
Thread tcpServerThread = new Thread(tcpServer);
tcpServerThread.setName("TCPServerThread");
tcpServerThread.start();
Thread tlsServerThread = new Thread(tlsServer);
tlsServerThread.setName("TLSServerThread");
tlsServerThread.start();
V2GCommunicationSessionHandlerSECC sessionHandler = new V2GCommunicationSessionHandlerSECC();
}
}
}

View File

@ -0,0 +1,251 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.session;
import java.net.DatagramPacket;
import java.net.Inet6Address;
import java.net.InetAddress;
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 org.eclipse.risev2g.secc.transportLayer.ConnectionHandler;
import org.eclipse.risev2g.secc.transportLayer.TCPServer;
import org.eclipse.risev2g.secc.transportLayer.TLSServer;
import org.eclipse.risev2g.secc.transportLayer.UDPServer;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.messageHandling.MessageHandler;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.misc.V2GTPMessage;
import org.eclipse.risev2g.shared.utils.ByteUtils;
import org.eclipse.risev2g.shared.utils.MiscUtils;
import org.eclipse.risev2g.shared.v2gMessages.SECCDiscoveryReq;
import org.eclipse.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 Threads 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(new MessageHandler());
}
@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.
*/
V2GCommunicationSessionSECC continuedSession = getV2gCommunicationSessions().get(ipAddress);
continuedSession.setConnectionHandler((ConnectionHandler) obj);
continuedSession.setTlsConnection((obs instanceof TLSServer) ? true : false);
((ConnectionHandler) obj).addObserver(getV2gCommunicationSessions().get(ipAddress));
manageConnectionHandlers((ConnectionHandler) obj);
} else {
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 hashmap
String ipAddress = ((V2GCommunicationSessionSECC) obs).getConnectionHandler().getAddress();
getV2gCommunicationSessions().remove(ipAddress);
stopConnectionHandler(((V2GCommunicationSessionSECC) obs).getConnectionHandler());
} 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()));
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 a SECCDiscoveryReq");
}
}
/**
* 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) {
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" );
} 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 void removeV2GCommunicationSession(InetAddress requesterAddress) {
getV2gCommunicationSessions().remove(getV2gCommunicationSessions().get(requesterAddress));
}
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,461 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.session;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Observable;
import java.util.Observer;
import org.eclipse.risev2g.secc.backend.DummyBackendInterface;
import org.eclipse.risev2g.secc.backend.IBackendInterface;
import org.eclipse.risev2g.secc.evseController.DummyACEVSEController;
import org.eclipse.risev2g.secc.evseController.DummyDCEVSEController;
import org.eclipse.risev2g.secc.evseController.IACEVSEController;
import org.eclipse.risev2g.secc.evseController.IDCEVSEController;
import org.eclipse.risev2g.secc.evseController.IEVSEController;
import org.eclipse.risev2g.secc.states.ForkState;
import org.eclipse.risev2g.secc.states.WaitForAuthorizationReq;
import org.eclipse.risev2g.secc.states.WaitForCableCheckReq;
import org.eclipse.risev2g.secc.states.WaitForCertificateInstallationReq;
import org.eclipse.risev2g.secc.states.WaitForCertificateUpdateReq;
import org.eclipse.risev2g.secc.states.WaitForChargeParameterDiscoveryReq;
import org.eclipse.risev2g.secc.states.WaitForChargingStatusReq;
import org.eclipse.risev2g.secc.states.WaitForCurrentDemandReq;
import org.eclipse.risev2g.secc.states.WaitForMeteringReceiptReq;
import org.eclipse.risev2g.secc.states.WaitForPaymentDetailsReq;
import org.eclipse.risev2g.secc.states.WaitForPaymentServiceSelectionReq;
import org.eclipse.risev2g.secc.states.WaitForPowerDeliveryReq;
import org.eclipse.risev2g.secc.states.WaitForPreChargeReq;
import org.eclipse.risev2g.secc.states.WaitForServiceDetailReq;
import org.eclipse.risev2g.secc.states.WaitForServiceDiscoveryReq;
import org.eclipse.risev2g.secc.states.WaitForSessionSetupReq;
import org.eclipse.risev2g.secc.states.WaitForSessionStopReq;
import org.eclipse.risev2g.secc.states.WaitForSupportedAppProtocolReq;
import org.eclipse.risev2g.secc.states.WaitForWeldingDetectionReq;
import org.eclipse.risev2g.secc.transportLayer.ConnectionHandler;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ChangeProcessingState;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.messageHandling.SendMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.misc.V2GCommunicationSession;
import org.eclipse.risev2g.shared.misc.V2GTPMessage;
import org.eclipse.risev2g.shared.utils.ByteUtils;
import org.eclipse.risev2g.shared.v2gMessages.appProtocol.SupportedAppProtocolReq;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ACEVSEStatusType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateChainType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EnergyTransferModeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.MessageHeaderType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.MeterInfoType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PMaxScheduleType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SAScheduleListType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ServiceType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class V2GCommunicationSessionSECC extends V2GCommunicationSession implements Observer {
private short schemaID;
private ACEVSEStatusType acEVSEStatus;
private boolean stopV2GCommunicationSession;
private boolean pauseV2GCommunicationSession;
private PMaxScheduleType pMaxSchedule;
private short chosenSAScheduleTuple;
private IACEVSEController acEvseController;
private IDCEVSEController dcEvseController;
private IEVSEController evseController;
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 CertificateChainType contractSignatureCertChain;
private MeterInfoType sentMeterInfo;
private boolean tlsConnection;
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(new DummyACEVSEController(this));
setDCEvseController(new DummyDCEVSEController(this));
// Configures which backend interface implementation to use for retrieving SASchedules
setBackendInterface(new DummyBackendInterface(this));
// ACEVSE notification
setAcEVSEStatus(new ACEVSEStatusType());
getACEVSEStatus().setEVSENotification(EVSENotificationType.NONE);
getACEVSEStatus().setNotificationMaxDelay(0);
getACEVSEStatus().setRCD(false);
setStopV2GCommunicationSession(false);
setPauseV2GCommunicationSession(false);
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("ConnectionHanlder 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 {
terminateSession("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 (isStopV2GCommunicationSession()) {
terminateSession("EVCC indicated request to stop the session", true);
}
} 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 send 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.toLongFromByteArray(header.getSessionID()) == 0L) {
// 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)) &&
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.toLongFromByteArray(header.getSessionID()) != 0L &&
header.getSessionID() != getSessionID()) {
// Avoid a "FAILED_..." response code by generating a new SessionID in the response
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;
}
}
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 boolean isStopV2GCommunicationSession() {
return stopV2GCommunicationSession;
}
public void setStopV2GCommunicationSession(boolean stopV2GCommunicationSession) {
this.stopV2GCommunicationSession = stopV2GCommunicationSession;
}
public boolean isPauseV2GCommunicationSession() {
return pauseV2GCommunicationSession;
}
public void setPauseV2GCommunicationSession(boolean pauseV2GCommunicationSession) {
this.pauseV2GCommunicationSession = pauseV2GCommunicationSession;
}
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 ACEVSEStatusType getAcEVSEStatus() {
return acEVSEStatus;
}
public void setAcEVSEStatus(ACEVSEStatusType acEVSEStatus) {
this.acEVSEStatus = acEVSEStatus;
}
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 void setEvseController(IEVSEController evseController) {
this.evseController = evseController;
}
public boolean isTlsConnection() {
return tlsConnection;
}
public void setTlsConnection(boolean tlsConnection) {
this.tlsConnection = tlsConnection;
}
}

View File

@ -0,0 +1,81 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.states;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.risev2g.secc.session.V2GCommunicationSessionSECC;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ChangeProcessingState;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.misc.State;
import org.eclipse.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 =
V2GMessages.fromValue(v2gMessageReq.getBody().getBodyElement().getValue().getClass().getSimpleName());
if (allowedRequests.contains(incomingMessage)) {
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");
}
// delete all allowedRequests so that they won't be valid anymore
allowedRequests.clear();
return new ChangeProcessingState(message, newState);
} else {
return new TerminateSession("Invalid message (" + v2gMessageReq.getBody().getBodyElement().getValue().getClass().getSimpleName() +
") at this state (" + this.getClass().getSimpleName() + "). " +
"Allowed messages are: " + this.getAllowedRequests().toString());
}
}
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;
}
}

View File

@ -0,0 +1,177 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.states;
import org.eclipse.risev2g.secc.session.V2GCommunicationSessionSECC;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.SendMessage;
import org.eclipse.risev2g.shared.misc.State;
import org.eclipse.risev2g.shared.misc.TimeRestrictions;
import org.eclipse.risev2g.shared.v2gMessages.appProtocol.SupportedAppProtocolRes;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.AuthorizationResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CableCheckResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateInstallationResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateUpdateResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscoveryResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargingStatusResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CurrentDemandResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.MeteringReceiptResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentDetailsResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentServiceSelectionResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PowerDeliveryResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PreChargeResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ServiceDetailResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ServiceDiscoveryResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SessionSetupResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SessionStopResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.V2GMessage;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.WeldingDetectionResType;
public abstract class ServerState extends State {
private ResponseCodeType responseCode;
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 {
getLogger().error("Response code '" + responseCode.toString() + "' will be sent");
return false;
}
}
protected SendMessage getSendMessage(
SupportedAppProtocolRes message,
V2GMessages nextExpectedMessage) {
String messageName = message.getClass().getSimpleName();
getLogger().debug("Preparing to send " + messageName);
return new SendMessage(message, getCommSessionContext().getStates().get(nextExpectedMessage), TimeRestrictions.V2G_SECC_SEQUENCE_TIMEOUT);
}
public ResponseCodeType getResponseCode() {
return responseCode;
}
/**
* Provides additional information about the kind of response code
* @param responseCode The response code to be sent
* @return True if the response code is positive (i.e. containing or starting with "OK"), false otherwise
*/
public boolean setResponseCode(ResponseCodeType responseCode) {
// Only log a negative response code
if (!responseCode.value().substring(0, 2).toUpperCase().equals("OK")) {
getLogger().error("Response code '" + responseCode.value() + "' will be sent.");
getCommSessionContext().setStopV2GCommunicationSession(true);
return false;
}
this.responseCode = responseCode;
return true;
}
}

View File

@ -0,0 +1,96 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.states;
import java.security.interfaces.ECPublicKey;
import java.util.Arrays;
import java.util.HashMap;
import org.eclipse.risev2g.secc.session.V2GCommunicationSessionSECC;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.utils.SecurityUtils;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.AuthorizationReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.AuthorizationResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EVSEProcessingType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SignatureType;
import org.eclipse.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 {
getLogger().error("Response code '" + authorizationRes.getResponseCode() + "' will be sent");
}
}
return getSendMessage(authorizationRes, V2GMessages.NONE);
}
public boolean isResponseCodeOK(AuthorizationReqType authorizationReq, SignatureType signature) {
if (!Arrays.equals(authorizationReq.getGenChallenge(), getCommSessionContext().getGenChallenge())) {
authorizationRes.setResponseCode(ResponseCodeType.FAILED_CHALLENGE_INVALID);
return false;
}
// Verify signature
HashMap<String, byte[]> verifyXMLSigRefElements = new HashMap<String, byte[]>();
verifyXMLSigRefElements.put(authorizationReq.getId(), SecurityUtils.generateDigest(authorizationReq, false));
ECPublicKey ecPublicKey = (ECPublicKey) SecurityUtils.getCertificate(
getCommSessionContext().getContractSignatureCertChain().getCertificate())
.getPublicKey();
if (!SecurityUtils.verifySignature(signature, verifyXMLSigRefElements, ecPublicKey)) {
authorizationRes.setResponseCode(ResponseCodeType.FAILED_SIGNATURE_ERROR);
return false;
}
return true;
}
public boolean isAuthorizationFinished() {
return authorizationFinished;
}
public void setAuthorizationFinished(boolean authorizationFinished) {
this.authorizationFinished = authorizationFinished;
}
}

View File

@ -0,0 +1,74 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.states;
import org.eclipse.risev2g.secc.evseController.IDCEVSEController;
import org.eclipse.risev2g.secc.session.V2GCommunicationSessionSECC;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CableCheckReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CableCheckResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EVSEProcessingType;
import org.eclipse.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()) {
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);
}
}
return getSendMessage(cableCheckRes, V2GMessages.NONE);
}
public boolean isEvseProcessingFinished() {
return evseProcessingFinished;
}
public void setEvseProcessingFinished(boolean evseProcessingFinished) {
this.evseProcessingFinished = evseProcessingFinished;
}
}

View File

@ -0,0 +1,136 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.states;
import java.security.KeyPair;
import java.security.interfaces.ECPublicKey;
import java.util.HashMap;
import org.eclipse.risev2g.secc.session.V2GCommunicationSessionSECC;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.utils.SecurityUtils;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateChainType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateInstallationReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateInstallationResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ContractSignatureEncryptedPrivateKeyType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SignatureType;
import org.eclipse.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();
if (isResponseCodeOK(
certificateInstallationReq,
saContractCertificateChain,
v2gMessageReq.getHeader().getSignature())) {
// The ECDH (elliptic curve Diffie Hellman) key pair is also needed for the generation of the shared secret
KeyPair ecdhKeyPair = SecurityUtils.getECDHKeyPair();
if (ecdhKeyPair == null) {
getLogger().error("ECDH keypair could not be generated");
return null;
}
// Encrypt private key
ContractSignatureEncryptedPrivateKeyType encryptedContractCertPrivateKey =
SecurityUtils.encryptContractCertPrivateKey(
(ECPublicKey) SecurityUtils.getCertificate(certificateInstallationReq.getOEMProvisioningCert()).getPublicKey(),
ecdhKeyPair,
getCommSessionContext().getBackendInterface().getContractCertificatePrivateKey());
certificateInstallationRes.setContractSignatureCertChain(saContractCertificateChain);
certificateInstallationRes.getContractSignatureCertChain().setId("contractSignatureCertChain");
certificateInstallationRes.setContractSignatureEncryptedPrivateKey(encryptedContractCertPrivateKey);
certificateInstallationRes.getContractSignatureEncryptedPrivateKey().setId("contractSignatureEncryptedPrivateKey");
certificateInstallationRes.setDHpublickey(SecurityUtils.getDHPublicKey(ecdhKeyPair));
certificateInstallationRes.getDHpublickey().setId("dhPublicKey");
certificateInstallationRes.setEMAID(SecurityUtils.getEMAID(saContractCertificateChain));
certificateInstallationRes.getEMAID().setId("emaid");
certificateInstallationRes.setSAProvisioningCertificateChain(
getCommSessionContext().getBackendInterface().getSAProvisioningCertificateChain());
// Set xml reference elements
getXMLSignatureRefElements().put(
certificateInstallationRes.getContractSignatureCertChain().getId(),
SecurityUtils.generateDigest(certificateInstallationRes.getContractSignatureCertChain(), false));
getXMLSignatureRefElements().put(
certificateInstallationRes.getContractSignatureEncryptedPrivateKey().getId(),
SecurityUtils.generateDigest(certificateInstallationRes.getContractSignatureEncryptedPrivateKey(), false));
getXMLSignatureRefElements().put(
certificateInstallationRes.getDHpublickey().getId(),
SecurityUtils.generateDigest(certificateInstallationRes.getDHpublickey(), false));
getXMLSignatureRefElements().put(
certificateInstallationRes.getEMAID().getId(),
SecurityUtils.generateDigest(certificateInstallationRes.getEMAID(), false));
// Set signing private key
setSignaturePrivateKey(getCommSessionContext().getBackendInterface().getSAProvisioningCertificatePrivateKey());
} else {
getLogger().error("Response code '" + certificateInstallationRes.getResponseCode() + "' will be sent");
}
}
return getSendMessage(certificateInstallationRes, V2GMessages.PAYMENT_DETAILS_REQ);
}
private boolean isResponseCodeOK(
CertificateInstallationReqType certificateInstallationReq,
CertificateChainType saContractCertificateChain,
SignatureType signature) {
// Check for FAILED_NoCertificateAvailable
if (saContractCertificateChain == null || saContractCertificateChain.getCertificate() == null) {
certificateInstallationRes.setResponseCode(ResponseCodeType.FAILED_NO_CERTIFICATE_AVAILABLE);
return false;
}
/*
* Check for FAILED_CertificateExpired
* There is no negative response code for a certificate which is neither yet valid nor expired.
* It is thus implicitly expected that a secondary actor would only send already valid certificates.
*/
if (!SecurityUtils.isCertificateChainValid(saContractCertificateChain)) {
certificateInstallationRes.setResponseCode(ResponseCodeType.FAILED_CERTIFICATE_EXPIRED);
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, false));
ECPublicKey ecPublicKey = (ECPublicKey) SecurityUtils.getCertificate(
certificateInstallationReq.getOEMProvisioningCert())
.getPublicKey();
if (!SecurityUtils.verifySignature(signature, verifyXMLSigRefElements, ecPublicKey)) {
certificateInstallationRes.setResponseCode(ResponseCodeType.FAILED_SIGNATURE_ERROR);
return false;
}
return true;
}
}

View File

@ -0,0 +1,154 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.states;
import java.security.KeyPair;
import java.security.interfaces.ECPublicKey;
import java.util.HashMap;
import org.eclipse.risev2g.secc.session.V2GCommunicationSessionSECC;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.utils.SecurityUtils;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateChainType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateUpdateReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateUpdateResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ContractSignatureEncryptedPrivateKeyType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SignatureType;
import org.eclipse.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();
CertificateChainType contractCertificateChain = getCommSessionContext().getBackendInterface().getContractCertificateChain();
if (isResponseCodeOK(
certificateUpdateReq,
contractCertificateChain,
v2gMessageReq.getHeader().getSignature())) {
// The ECDH (elliptic curve Diffie Hellman) key pair is also needed for the generation of the shared secret
KeyPair ecdhKeyPair = SecurityUtils.getECDHKeyPair();
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(),
ecdhKeyPair,
getCommSessionContext().getBackendInterface().getContractCertificatePrivateKey());
certificateUpdateRes.setContractSignatureCertChain(contractCertificateChain);
certificateUpdateRes.getContractSignatureCertChain().setId("contractSignatureCertChain");
certificateUpdateRes.setContractSignatureEncryptedPrivateKey(encryptedContractCertPrivateKey);
certificateUpdateRes.getContractSignatureEncryptedPrivateKey().setId("contractSignatureEncryptedPrivateKey");
certificateUpdateRes.setDHpublickey(SecurityUtils.getDHPublicKey(ecdhKeyPair));
certificateUpdateRes.getDHpublickey().setId("dhPublicKey");
certificateUpdateRes.setEMAID(SecurityUtils.getEMAID(contractCertificateChain));
certificateUpdateRes.getEMAID().setId("emaid");
certificateUpdateRes.setSAProvisioningCertificateChain(getCommSessionContext().getBackendInterface().getSAProvisioningCertificateChain());
// 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(), false));
getXMLSignatureRefElements().put(
certificateUpdateRes.getContractSignatureEncryptedPrivateKey().getId(),
SecurityUtils.generateDigest(certificateUpdateRes.getContractSignatureEncryptedPrivateKey(), false));
getXMLSignatureRefElements().put(
certificateUpdateRes.getDHpublickey().getId(),
SecurityUtils.generateDigest(certificateUpdateRes.getDHpublickey(), false));
getXMLSignatureRefElements().put(
certificateUpdateRes.getEMAID().getId(),
SecurityUtils.generateDigest(certificateUpdateRes.getEMAID(), false));
// Set signing private key
setSignaturePrivateKey(getCommSessionContext().getBackendInterface().getSAProvisioningCertificatePrivateKey());
} else {
getLogger().error("Response code '" + certificateUpdateRes.getResponseCode() + "' will be sent");
}
}
return getSendMessage(certificateUpdateRes,
(certificateUpdateRes.getResponseCode().toString().startsWith("OK") ?
V2GMessages.PAYMENT_DETAILS_REQ : V2GMessages.NONE)
);
}
private boolean isResponseCodeOK(
CertificateUpdateReqType certificateUpdateReq,
CertificateChainType saContractCertificateChain,
SignatureType signature) {
// Check for FAILED_NoCertificateAvailable
if (saContractCertificateChain == null || saContractCertificateChain.getCertificate() == null) {
certificateUpdateRes.setResponseCode(ResponseCodeType.FAILED_NO_CERTIFICATE_AVAILABLE);
return false;
}
/*
* Check for FAILED_CertificateExpired
* There is no negative response code for a certificate which is neither yet valid nor expired.
* It is thus implicitly expected that a secondary actor would only send already valid certificates.
*/
if (!SecurityUtils.isCertificateChainValid(certificateUpdateReq.getContractSignatureCertChain())) {
certificateUpdateRes.setResponseCode(ResponseCodeType.FAILED_CERTIFICATE_EXPIRED);
return false;
}
// Check for FAILED_CertChainError
if (!SecurityUtils.isCertificateChainVerified(
GlobalValues.SECC_TRUSTSTORE_FILEPATH.toString(),
certificateUpdateReq.getContractSignatureCertChain())) {
certificateUpdateRes.setResponseCode(ResponseCodeType.FAILED_CERT_CHAIN_ERROR);
return false;
}
// Check for FAILED_ContractCancelled
// TODO how to check if the EMAID provided by EVCC is not accepted by secondary actor?
// 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, false));
ECPublicKey ecPublicKey = (ECPublicKey) SecurityUtils.getCertificate(
certificateUpdateReq.getContractSignatureCertChain().getCertificate())
.getPublicKey();
if (!SecurityUtils.verifySignature(signature, verifyXMLSigRefElements, ecPublicKey)) {
certificateUpdateRes.setResponseCode(ResponseCodeType.FAILED_SIGNATURE_ERROR);
return false;
}
return true;
}
}

View File

@ -0,0 +1,174 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.states;
import java.util.ArrayList;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import org.eclipse.risev2g.secc.evseController.IACEVSEController;
import org.eclipse.risev2g.secc.evseController.IDCEVSEController;
import org.eclipse.risev2g.secc.session.V2GCommunicationSessionSECC;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.misc.TimeRestrictions;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ACEVChargeParameterType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscoveryReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscoveryResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.DCEVChargeParameterType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EVSEProcessingType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EnergyTransferModeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SAScheduleListType;
import org.eclipse.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 ChargeParameterDiscoveryReq
* response 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);
getCommSessionContext().setSaSchedules(
getCommSessionContext().getBackendInterface().getSAScheduleList());
}
// Wait a bit and check if the schedule has already been provided
// TODO is this the best way?
try {
Thread.sleep(TimeRestrictions.getV2G_EVCC_Msg_Timeout(V2GMessages.CHARGE_PARAMETER_DISCOVERY_RES)-1000);
} catch (InterruptedException e) {
return new TerminateSession("InterruptedException while waiting for schedule");
}
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()));
if (chargeParameterDiscoveryReq.getRequestedEnergyTransferMode().toString().startsWith("AC"))
return getSendMessage(chargeParameterDiscoveryRes, V2GMessages.POWER_DELIVERY_REQ);
else
return getSendMessage(chargeParameterDiscoveryRes, V2GMessages.CABLE_CHECK_REQ);
}
} else {
getLogger().error("Response code '" + chargeParameterDiscoveryRes.getResponseCode() + "' will be sent");
}
}
return getSendMessage(chargeParameterDiscoveryRes, V2GMessages.NONE);
}
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 (chargeParameterDiscoveryReq.getEVChargeParameter() == null ||
(chargeParameterDiscoveryReq.getEVChargeParameter().getValue() instanceof ACEVChargeParameterType &&
(((ACEVChargeParameterType) chargeParameterDiscoveryReq.getEVChargeParameter().getValue()).getEAmount() == null ||
((ACEVChargeParameterType) chargeParameterDiscoveryReq.getEVChargeParameter().getValue()).getEVMaxVoltage() == null ||
((ACEVChargeParameterType) chargeParameterDiscoveryReq.getEVChargeParameter().getValue()).getEVMaxCurrent() == null ||
((ACEVChargeParameterType) chargeParameterDiscoveryReq.getEVChargeParameter().getValue()).getEVMinCurrent() == null
)
) ||
(chargeParameterDiscoveryReq.getEVChargeParameter().getValue() instanceof DCEVChargeParameterType &&
(((DCEVChargeParameterType) chargeParameterDiscoveryReq.getEVChargeParameter().getValue()).getDCEVStatus() == null ||
((DCEVChargeParameterType) chargeParameterDiscoveryReq.getEVChargeParameter().getValue()).getEVMaximumCurrentLimit() == null ||
((DCEVChargeParameterType) chargeParameterDiscoveryReq.getEVChargeParameter().getValue()).getEVMaximumVoltageLimit() == null
)
)
) {
chargeParameterDiscoveryRes.setResponseCode(ResponseCodeType.FAILED_WRONG_CHARGE_PARAMETER);
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;
}
}

View File

@ -0,0 +1,74 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.states;
import org.eclipse.risev2g.secc.evseController.IACEVSEController;
import org.eclipse.risev2g.secc.session.V2GCommunicationSessionSECC;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargingStatusReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargingStatusResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.MeterInfoType;
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
chargingStatusRes.setACEVSEStatus(
((IACEVSEController) getCommSessionContext().getACEvseController())
.getACEVSEStatus(EVSENotificationType.STOP_CHARGING)
);
// Optionally indicate that the EVCC is required to send a MeteringReceiptReq message
// (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);
}
}
return getSendMessage(chargingStatusRes, V2GMessages.NONE);
}
}

View File

@ -0,0 +1,79 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.states;
import org.eclipse.risev2g.secc.evseController.IDCEVSEController;
import org.eclipse.risev2g.secc.session.V2GCommunicationSessionSECC;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CurrentDemandReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CurrentDemandResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import org.eclipse.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 = (IDCEVSEController) 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?
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());
currentDemandRes.setSAScheduleTupleID(getCommSessionContext().getChosenSAScheduleTuple());
// TODO how to determine if a receipt is required or not?
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);
}
}
return getSendMessage(currentDemandRes, V2GMessages.NONE);
}
}

View File

@ -0,0 +1,127 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.states;
import java.security.interfaces.ECPublicKey;
import java.util.Arrays;
import java.util.HashMap;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import org.eclipse.risev2g.secc.session.V2GCommunicationSessionSECC;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.utils.SecurityUtils;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ACEVSEStatusType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.DCEVSEStatusType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.MeterInfoType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.MeteringReceiptReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.MeteringReceiptResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SignatureType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForMeteringReceiptReq extends ServerState {
private MeteringReceiptResType meteringReceiptRes;
public WaitForMeteringReceiptReq(V2GCommunicationSessionSECC commSessionContext) {
super(commSessionContext);
meteringReceiptRes = new MeteringReceiptResType();
}
@SuppressWarnings("unchecked")
@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())) {
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC")) {
/*
* The MiscUtils method getJAXBElement() cannot be used here because of the difference in the
* class name (ACEVSEStatus) and the name in the XSD (AC_EVSEStatus)
*/
JAXBElement 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 MiscUtils method getJAXBElement() cannot be used here because of the difference in the
* class name (DCEVSEStatus) and the name in the XSD (DC_EVSEStatus)
*/
JAXBElement jaxbACEVSEStatus = new JAXBElement(new QName("urn:iso:15118:2:2013:MsgDataTypes", "DC_EVSEStatus"),
DCEVSEStatusType.class,
getCommSessionContext().getDCEvseController().getDCEVSEStatus(EVSENotificationType.NONE));
meteringReceiptRes.setEVSEStatus(jaxbACEVSEStatus);
} else {
return new TerminateSession("RequestedEnergyTransferMode '" + getCommSessionContext().getRequestedEnergyTransferMode().toString() +
"is neither of type AC nor DC");
}
((ForkState) getCommSessionContext().getStates().get(V2GMessages.FORK))
.getAllowedRequests().add(V2GMessages.POWER_DELIVERY_REQ);
((ForkState) getCommSessionContext().getStates().get(V2GMessages.FORK))
.getAllowedRequests().add(V2GMessages.CHARGING_STATUS_REQ);
return getSendMessage(meteringReceiptRes, V2GMessages.FORK);
} else {
getLogger().error("Response code '" + meteringReceiptRes.getResponseCode() + "' will be sent");
}
}
return getSendMessage(meteringReceiptRes, V2GMessages.NONE);
}
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())) {
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, false));
ECPublicKey ecPublicKey = (ECPublicKey) SecurityUtils.getCertificate(
getCommSessionContext().getContractSignatureCertChain().getCertificate())
.getPublicKey();
if (!SecurityUtils.verifySignature(signature, verifyXMLSigRefElements, ecPublicKey)) {
meteringReceiptRes.setResponseCode(ResponseCodeType.FAILED_METERING_SIGNATURE_NOT_VALID);
return false;
}
return true;
}
private boolean meterInfoEquals(MeterInfoType sentMeterInfo, MeterInfoType receivedMeterInfo) {
// Only meterID is mandatory field, thus check for null values as well
if (!sentMeterInfo.getMeterID().equals(receivedMeterInfo.getMeterID()) ||
(sentMeterInfo.getMeterReading() != null && !sentMeterInfo.getMeterReading().equals(receivedMeterInfo.getMeterReading())) ||
(sentMeterInfo.getMeterStatus() != null && !sentMeterInfo.getMeterStatus().equals(receivedMeterInfo.getMeterStatus())) ||
(sentMeterInfo.getSigMeterReading() != null && !Arrays.equals(sentMeterInfo.getSigMeterReading(), receivedMeterInfo.getSigMeterReading())) ||
(sentMeterInfo.getTMeter() != null && !sentMeterInfo.getTMeter().equals(receivedMeterInfo.getTMeter()))) return false;
else return true;
}
}

View File

@ -0,0 +1,84 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.states;
import org.eclipse.risev2g.secc.session.V2GCommunicationSessionSECC;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.utils.SecurityUtils;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentDetailsReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentDetailsResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForPaymentDetailsReq extends ServerState {
private PaymentDetailsResType paymentDetailsRes;
public WaitForPaymentDetailsReq(V2GCommunicationSessionSECC commSessionContext) {
super(commSessionContext);
paymentDetailsRes = new PaymentDetailsResType();
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, PaymentDetailsReqType.class, paymentDetailsRes)) {
V2GMessage v2gMessageReq = (V2GMessage) message;
PaymentDetailsReqType paymentDetailsReq =
(PaymentDetailsReqType) v2gMessageReq.getBody().getBodyElement().getValue();
if (isResponseCodeOK(paymentDetailsReq)) {
// Save contract certificate chain for certificate and signature verification/validation
getCommSessionContext().setContractSignatureCertChain(paymentDetailsReq.getContractSignatureCertChain());
paymentDetailsRes.setEVSETimeStamp(System.currentTimeMillis());
byte[] genChallenge = SecurityUtils.generateRandomNumber(16);
getCommSessionContext().setGenChallenge(genChallenge);
paymentDetailsRes.setGenChallenge(genChallenge);
} else {
getLogger().error("Response code '" + paymentDetailsRes.getResponseCode() + "' will be sent");
}
}
return getSendMessage(paymentDetailsRes,
(paymentDetailsRes.getResponseCode().toString().startsWith("OK") ?
V2GMessages.AUTHORIZATION_REQ : V2GMessages.NONE)
);
}
public boolean isResponseCodeOK(PaymentDetailsReqType paymentDetailsReq) {
// TODO is Check for FAILED_NoCertificateAvailable and FAILED_CertificateRevoked necessary here?
if (paymentDetailsReq.getContractSignatureCertChain() == null) {
getLogger().error("Certificate chain is NULL");
paymentDetailsRes.setResponseCode(ResponseCodeType.FAILED_CERT_CHAIN_ERROR);
return false;
}
if (!SecurityUtils.isCertificateChainValid(paymentDetailsReq.getContractSignatureCertChain())) {
getLogger().error("Contract certificate chain is not valid");
paymentDetailsRes.setResponseCode(ResponseCodeType.FAILED_CERTIFICATE_EXPIRED);
return false;
}
if (!SecurityUtils.isCertificateChainVerified(
GlobalValues.SECC_TRUSTSTORE_FILEPATH.toString(),
paymentDetailsReq.getContractSignatureCertChain())) {
getLogger().error("Contract certificate chain could not be verified");
paymentDetailsRes.setResponseCode(ResponseCodeType.FAILED_CERT_CHAIN_ERROR);
return false;
}
return true;
}
}

View File

@ -0,0 +1,108 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.states;
import org.eclipse.risev2g.secc.session.V2GCommunicationSessionSECC;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentServiceSelectionReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentServiceSelectionResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SelectedServiceType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ServiceType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForPaymentServiceSelectionReq extends ServerState {
private PaymentServiceSelectionResType paymentServiceSelectionRes;
public WaitForPaymentServiceSelectionReq(V2GCommunicationSessionSECC commSessionContext) {
super(commSessionContext);
paymentServiceSelectionRes = new PaymentServiceSelectionResType();
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, PaymentServiceSelectionReqType.class, paymentServiceSelectionRes)) {
V2GMessage v2gMessageReq = (V2GMessage) message;
PaymentServiceSelectionReqType paymentServiceSelectionReq =
(PaymentServiceSelectionReqType) v2gMessageReq.getBody().getBodyElement().getValue();
if (isResponseCodeOK(paymentServiceSelectionReq)) {
// see [V2G2-551]
if (paymentServiceSelectionReq.getSelectedPaymentOption().equals(PaymentOptionType.CONTRACT)) {
((ForkState) getCommSessionContext().getStates().get(V2GMessages.FORK))
.getAllowedRequests().add(V2GMessages.PAYMENT_DETAILS_REQ);
((ForkState) getCommSessionContext().getStates().get(V2GMessages.FORK))
.getAllowedRequests().add(V2GMessages.CERTIFICATE_INSTALLATION_REQ);
((ForkState) getCommSessionContext().getStates().get(V2GMessages.FORK))
.getAllowedRequests().add(V2GMessages.CERTIFICATE_UPDATE_REQ);
return getSendMessage(paymentServiceSelectionRes, V2GMessages.FORK);
} else {
return getSendMessage(paymentServiceSelectionRes, V2GMessages.AUTHORIZATION_REQ);
}
} else {
getLogger().error("Response code '" + paymentServiceSelectionRes.getResponseCode() + "' will be sent");
}
}
return getSendMessage(paymentServiceSelectionRes, V2GMessages.NONE);
}
public boolean isResponseCodeOK(PaymentServiceSelectionReqType paymentServiceSelectionReq) {
// Check if the charge service was selected and if all selected services were offered before
boolean chargeServiceSelected = false;
boolean selectedServiceOffered;
for (SelectedServiceType selectedService : paymentServiceSelectionReq.getSelectedServiceList().getSelectedService()) {
selectedServiceOffered = false;
for (ServiceType offeredService : getCommSessionContext().getOfferedServices()) {
if (offeredService.getServiceID() == selectedService.getServiceID()) {
selectedServiceOffered = true;
break;
// TODO check for parameterSetID as well
}
}
if (!selectedServiceOffered) {
getLogger().error("Selected service with ID " + selectedService.getServiceID() +
" is not offered");
paymentServiceSelectionRes.setResponseCode(ResponseCodeType.FAILED_SERVICE_SELECTION_INVALID);
return false;
}
if (selectedService.getServiceID() == 1) {
chargeServiceSelected = true;
break;
}
}
if (!chargeServiceSelected) {
paymentServiceSelectionRes.setResponseCode(ResponseCodeType.FAILED_NO_CHARGE_SERVICE_SELECTED);
return false;
}
// Check if selected payment option is supported
if (!getCommSessionContext().getPaymentOptions().getPaymentOption()
.contains(paymentServiceSelectionReq.getSelectedPaymentOption())) {
paymentServiceSelectionRes.setResponseCode(ResponseCodeType.FAILED_PAYMENT_SELECTION_INVALID);
return false;
}
return true;
}
}

View File

@ -0,0 +1,172 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.states;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import org.eclipse.risev2g.secc.session.V2GCommunicationSessionSECC;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ACEVSEStatusType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargeProgressType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargingProfileType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.DCEVSEStatusCodeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.DCEVSEStatusType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PowerDeliveryReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PowerDeliveryResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SAScheduleTupleType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForPowerDeliveryReq extends ServerState {
private PowerDeliveryResType powerDeliveryRes;
public WaitForPowerDeliveryReq(
V2GCommunicationSessionSECC commSessionContext) {
super(commSessionContext);
powerDeliveryRes = new PowerDeliveryResType();
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, PowerDeliveryReqType.class, powerDeliveryRes)) {
V2GMessage v2gMessageReq = (V2GMessage) message;
PowerDeliveryReqType powerDeliveryReq = (PowerDeliveryReqType) v2gMessageReq.getBody().getBodyElement().getValue();
if (isResponseCodeOK(powerDeliveryReq)) {
getCommSessionContext().setChosenSAScheduleTuple(powerDeliveryReq.getSAScheduleTupleID());
// For debugging purposes, log the ChargeProgress value
getLogger().debug("ChargeProgress of PowerDeliveryReq set to '" +
powerDeliveryReq.getChargeProgress().toString() + "'");
// TODO regard [V2G2-866]
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC")) {
/*
* The MiscUtils method getJAXBElement() cannot be used here because of the difference in the
* class name (ACEVSEStatus) and the name in the XSD (AC_EVSEStatus)
*/
JAXBElement jaxbEVSEStatus = new JAXBElement(new QName("urn:iso:15118:2:2013:MsgDataTypes", "AC_EVSEStatus"),
ACEVSEStatusType.class,
getCommSessionContext().getACEvseController().getACEVSEStatus(EVSENotificationType.NONE));
powerDeliveryRes.setEVSEStatus(jaxbEVSEStatus);
} else if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("DC")) {
/*
* The MiscUtils method getJAXBElement() cannot be used here because of the difference in the
* class name (DCEVSEStatus) and the name in the XSD (DC_EVSEStatus)
*/
JAXBElement jaxbACEVSEStatus = new JAXBElement(new QName("urn:iso:15118:2:2013:MsgDataTypes", "DC_EVSEStatus"),
DCEVSEStatusType.class,
getCommSessionContext().getDCEvseController().getDCEVSEStatus(EVSENotificationType.NONE));
powerDeliveryRes.setEVSEStatus(jaxbACEVSEStatus);
} else {
return new TerminateSession("RequestedEnergyTransferMode '" + getCommSessionContext().getRequestedEnergyTransferMode().toString() +
"is neither of type AC nor DC");
}
if (powerDeliveryReq.getChargeProgress().equals(ChargeProgressType.START)) {
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC"))
return getSendMessage(powerDeliveryRes, V2GMessages.CHARGING_STATUS_REQ);
else
return getSendMessage(powerDeliveryRes, V2GMessages.CURRENT_DEMAND_REQ);
} else if (powerDeliveryReq.getChargeProgress().equals(ChargeProgressType.STOP)) {
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC")) {
return getSendMessage(powerDeliveryRes, V2GMessages.SESSION_STOP_REQ);
} else {
((ForkState) getCommSessionContext().getStates().get(V2GMessages.FORK))
.getAllowedRequests().add(V2GMessages.WELDING_DETECTION_REQ);
((ForkState) getCommSessionContext().getStates().get(V2GMessages.FORK))
.getAllowedRequests().add(V2GMessages.SESSION_STOP_REQ);
return getSendMessage(powerDeliveryRes, V2GMessages.FORK);
}
} else {
return getSendMessage(powerDeliveryRes, V2GMessages.CHARGE_PARAMETER_DISCOVERY_REQ);
}
} else {
getLogger().error("Response code '" + powerDeliveryRes.getResponseCode() + "' will be sent");
}
}
return getSendMessage(powerDeliveryRes, V2GMessages.NONE);
}
public boolean isResponseCodeOK(PowerDeliveryReqType powerDeliveryReq) {
SAScheduleTupleType chosenSASchedule = getChosenSASCheduleTuple(powerDeliveryReq.getSAScheduleTupleID());
if (chosenSASchedule == null) {
powerDeliveryRes.setResponseCode(ResponseCodeType.FAILED_TARIFF_SELECTION_INVALID);
return false;
}
// Important to call this AFTER checking for valid tariff selection because of possible null-value!
if (!isChargingProfileValid(chosenSASchedule, powerDeliveryReq.getChargingProfile())) {
powerDeliveryRes.setResponseCode(ResponseCodeType.FAILED_CHARGING_PROFILE_INVALID);
return false;
}
// Not sure if these values are the ones to monitor when checking for FAILED_POWER_DELIVERY_NOT_APPLIED
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC")) {
if (getCommSessionContext().getACEvseController().getACEVSEStatus(null).isRCD()) {
getLogger().error("RCD has detected an error");
powerDeliveryRes.setResponseCode(ResponseCodeType.FAILED_POWER_DELIVERY_NOT_APPLIED);
return false;
}
} else {
DCEVSEStatusCodeType dcEVSEStatusCode =
getCommSessionContext().getDCEvseController().getDCEVSEStatus(null).getEVSEStatusCode();
if (dcEVSEStatusCode.equals(DCEVSEStatusCodeType.EVSE_NOT_READY) ||
dcEVSEStatusCode.equals(DCEVSEStatusCodeType.EVSE_SHUTDOWN) ||
dcEVSEStatusCode.equals(DCEVSEStatusCodeType.EVSE_EMERGENCY_SHUTDOWN) ||
dcEVSEStatusCode.equals(DCEVSEStatusCodeType.EVSE_MALFUNCTION)) {
getLogger().error("EVSE status code is '" + dcEVSEStatusCode.toString() + "'");
powerDeliveryRes.setResponseCode(ResponseCodeType.FAILED_POWER_DELIVERY_NOT_APPLIED);
return false;
}
}
if ((powerDeliveryReq.getChargeProgress().equals(ChargeProgressType.START) &&
!getCommSessionContext().getEvseController().closeContactor()) ||
(powerDeliveryReq.getChargeProgress().equals(ChargeProgressType.STOP) &&
!getCommSessionContext().getEvseController().openContactor())) {
powerDeliveryRes.setResponseCode(ResponseCodeType.FAILED_CONTACTOR_ERROR);
return false;
}
return true;
}
private SAScheduleTupleType getChosenSASCheduleTuple(short chosenSAScheduleTupleID) {
for (SAScheduleTupleType saSchedule : getCommSessionContext().getSaSchedules().getSAScheduleTuple()) {
if (saSchedule.getSAScheduleTupleID() == chosenSAScheduleTupleID) return saSchedule;
}
return null;
}
private boolean isChargingProfileValid(
SAScheduleTupleType chosenSAScheduleTuple,
ChargingProfileType chargingProfile) {
// TODO check for validity of charging profile
return true;
}
}

View File

@ -0,0 +1,59 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.states;
import org.eclipse.risev2g.secc.evseController.IDCEVSEController;
import org.eclipse.risev2g.secc.session.V2GCommunicationSessionSECC;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PreChargeReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PreChargeResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForPreChargeReq extends ServerState {
private PreChargeResType preChargeRes;
public WaitForPreChargeReq(V2GCommunicationSessionSECC commSessionContext) {
super(commSessionContext);
preChargeRes = new PreChargeResType();
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, PreChargeReqType.class, preChargeRes)) {
V2GMessage v2gMessageReq = (V2GMessage) message;
PreChargeReqType preChargeReq =
(PreChargeReqType) v2gMessageReq.getBody().getBodyElement().getValue();
// TODO how to react to failure status of DCEVStatus of cableCheckReq?
IDCEVSEController evseController = (IDCEVSEController) getCommSessionContext().getDCEvseController();
evseController.setTargetCurrent(preChargeReq.getEVTargetCurrent());
evseController.setTargetVoltage(preChargeReq.getEVTargetVoltage());
preChargeRes.setDCEVSEStatus(evseController.getDCEVSEStatus(EVSENotificationType.NONE));
preChargeRes.setEVSEPresentVoltage(evseController.getPresentVoltage());
((ForkState) getCommSessionContext().getStates().get(V2GMessages.FORK))
.getAllowedRequests().add(V2GMessages.PRE_CHARGE_REQ);
((ForkState) getCommSessionContext().getStates().get(V2GMessages.FORK))
.getAllowedRequests().add(V2GMessages.POWER_DELIVERY_REQ);
}
return getSendMessage(preChargeRes,
(preChargeRes.getResponseCode().toString().startsWith("OK") ?
V2GMessages.FORK : V2GMessages.NONE)
);
}
}

View File

@ -0,0 +1,173 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.states;
import org.eclipse.risev2g.secc.session.V2GCommunicationSessionSECC;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ParameterSetType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ParameterType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ServiceDetailReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ServiceDetailResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ServiceParameterListType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ServiceType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForServiceDetailReq extends ServerState {
private ServiceDetailResType serviceDetailRes;
public WaitForServiceDetailReq(V2GCommunicationSessionSECC commSessionContext) {
super(commSessionContext);
serviceDetailRes = new ServiceDetailResType();
}
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, ServiceDetailReqType.class, serviceDetailRes)) {
V2GMessage v2gMessageReq = (V2GMessage) message;
ServiceDetailReqType serviceDetailReq = (ServiceDetailReqType) v2gMessageReq.getBody().getBodyElement().getValue();
if (isResponseCodeOK(serviceDetailReq)) {
ServiceParameterListType serviceParameterList = new ServiceParameterListType();
// The charge service has no parameters and is therefore not checked for here
if (serviceDetailReq.getServiceID() == 2) {
// parameters for certificate service
serviceParameterList.getParameterSet().add(getCertificateInstallationParameters());
serviceParameterList.getParameterSet().add(getCertificateUpdateParameters());
} else if (serviceDetailReq.getServiceID() == 3) {
// Comment out internet access service which will not be available
serviceParameterList.getParameterSet().add(getInternetAccessFTPPort20Parameters());
serviceParameterList.getParameterSet().add(getInternetAccessFTPPort21Parameters());
serviceParameterList.getParameterSet().add(getInternetAccessHTTPParameters());
serviceParameterList.getParameterSet().add(getInternetAccessHTTPSParameters());
}
// Optionally, further service details parameters can be provided (if previously offered)
serviceDetailRes.setServiceID(serviceDetailReq.getServiceID());
serviceDetailRes.setServiceParameterList(serviceParameterList);
((ForkState) getCommSessionContext().getStates().get(V2GMessages.FORK))
.getAllowedRequests().add(V2GMessages.SERVICE_DETAIL_REQ);
((ForkState) getCommSessionContext().getStates().get(V2GMessages.FORK))
.getAllowedRequests().add(V2GMessages.PAYMENT_SERVICE_SELECTION_REQ);
return getSendMessage(serviceDetailRes, V2GMessages.FORK);
} else {
getLogger().error("Response code '" + serviceDetailRes.getResponseCode() + "' will be sent");
}
}
return getSendMessage(serviceDetailRes, V2GMessages.NONE);
}
private boolean isResponseCodeOK(ServiceDetailReqType serviceDetailReq) {
for (ServiceType service : getCommSessionContext().getOfferedServices()) {
if (service.getServiceID() == serviceDetailReq.getServiceID())
return true;
}
serviceDetailRes.setResponseCode(ResponseCodeType.FAILED_SERVICE_ID_INVALID);
return false;
}
private ParameterSetType getCertificateInstallationParameters() {
ParameterSetType parameterSet = new ParameterSetType();
ParameterType certInstallation = new ParameterType();
certInstallation.setName("Certificate Installation");
certInstallation.setStringValue("Installation");
parameterSet.getParameter().add(certInstallation);
parameterSet.setParameterSetID((short) 1);
return parameterSet;
}
private ParameterSetType getCertificateUpdateParameters() {
ParameterSetType parameterSet = new ParameterSetType();
ParameterType certUpdate = new ParameterType();
certUpdate.setName("Certificate Update");
certUpdate.setStringValue("Update");
parameterSet.getParameter().add(certUpdate);
parameterSet.setParameterSetID((short) 2);
return parameterSet;
}
private ParameterSetType getInternetAccessFTPPort20Parameters() {
ParameterSetType parameterSet = new ParameterSetType();
ParameterType ftpPort20 = new ParameterType();
ftpPort20.setName("FTP port 20");
ftpPort20.setStringValue("ftp");
ftpPort20.setIntValue(20);
parameterSet.getParameter().add(ftpPort20);
parameterSet.setParameterSetID((short) 1);
return parameterSet;
}
private ParameterSetType getInternetAccessFTPPort21Parameters() {
ParameterSetType parameterSet = new ParameterSetType();
ParameterType ftpPort21 = new ParameterType();
ftpPort21.setName("FTP port 21");
ftpPort21.setStringValue("ftp");
ftpPort21.setIntValue(21);
parameterSet.getParameter().add(ftpPort21);
parameterSet.setParameterSetID((short) 2);
return parameterSet;
}
private ParameterSetType getInternetAccessHTTPParameters() {
ParameterSetType parameterSet = new ParameterSetType();
ParameterType http = new ParameterType();
http.setName("HTTP port 80");
http.setStringValue("http");
http.setIntValue(80);
parameterSet.getParameter().add(http);
parameterSet.setParameterSetID((short) 3);
return parameterSet;
}
private ParameterSetType getInternetAccessHTTPSParameters() {
ParameterSetType parameterSet = new ParameterSetType();
ParameterType https = new ParameterType();
https.setName("HTTP port 443");
https.setStringValue("https");
https.setIntValue(443);
parameterSet.getParameter().add(https);
parameterSet.setParameterSetID((short) 4);
return parameterSet;
}
}

View File

@ -0,0 +1,114 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.states;
import org.eclipse.risev2g.secc.session.V2GCommunicationSessionSECC;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargeServiceType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ServiceCategoryType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ServiceDiscoveryReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ServiceDiscoveryResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ServiceListType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ServiceType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SupportedEnergyTransferModeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForServiceDiscoveryReq extends ServerState {
private ServiceDiscoveryResType serviceDiscoveryRes;
public WaitForServiceDiscoveryReq(V2GCommunicationSessionSECC commSessionContext) {
super(commSessionContext);
serviceDiscoveryRes = new ServiceDiscoveryResType();
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, ServiceDiscoveryReqType.class, serviceDiscoveryRes)) {
V2GMessage v2gMessageReq = (V2GMessage) message;
ServiceDiscoveryReqType serviceDiscoveryReq = (ServiceDiscoveryReqType) v2gMessageReq.getBody().getBodyElement().getValue();
ServiceListType offeredVASList = getServiceList(
serviceDiscoveryReq.getServiceCategory(),
serviceDiscoveryReq.getServiceScope()
);
serviceDiscoveryRes.setPaymentOptionList(getCommSessionContext().getPaymentOptions());
serviceDiscoveryRes.setChargeService(getChargeService());
serviceDiscoveryRes.setServiceList(offeredVASList);
/*
* When processing PaymentServiceSelectionReq the SECC needs to check if the service
* chosen by the EVCC was previously offered
*/
getCommSessionContext().getOfferedServices().add(getChargeService());
getCommSessionContext().getOfferedServices().addAll(offeredVASList.getService());
((ForkState) getCommSessionContext().getStates().get(V2GMessages.FORK))
.getAllowedRequests().add(V2GMessages.SERVICE_DETAIL_REQ);
((ForkState) getCommSessionContext().getStates().get(V2GMessages.FORK))
.getAllowedRequests().add(V2GMessages.PAYMENT_SERVICE_SELECTION_REQ);
}
return getSendMessage(serviceDiscoveryRes,
(serviceDiscoveryRes.getResponseCode().toString().startsWith("OK") ?
V2GMessages.FORK : V2GMessages.NONE)
);
}
private ChargeServiceType getChargeService() {
SupportedEnergyTransferModeType supportedEnergyTransferModes = new SupportedEnergyTransferModeType();
supportedEnergyTransferModes.getEnergyTransferMode().addAll(
getCommSessionContext().getSupportedEnergyTransferModes());
ChargeServiceType chargeService = new ChargeServiceType();
chargeService.setSupportedEnergyTransferMode(supportedEnergyTransferModes);
chargeService.setServiceCategory(ServiceCategoryType.EV_CHARGING);
chargeService.setServiceID(1); // according to Table 105 ISO/IEC 15118-2
chargeService.setServiceName("EV charging (AC/DC)"); // optional value
chargeService.setServiceScope(""); // optional value
chargeService.setFreeService(false); // it is supposed that charging is by default not for free
return chargeService;
}
private ServiceListType getServiceList(ServiceCategoryType serviceCategoryFilter, String serviceScopeFilter) {
ServiceListType serviceList = new ServiceListType();
// Currently no filter based on service scope is applied since its string value is not standardised somehow
if (getCommSessionContext().isTlsConnection() && (
(serviceCategoryFilter != null && serviceCategoryFilter.equals(ServiceCategoryType.CONTRACT_CERTIFICATE)) ||
serviceCategoryFilter == null)) {
serviceList.getService().add(getCertificateService());
}
/*
* If more VAS (value added service) services beyond the certificate installation/update service
* are to be offered, then they could be listed here.
*/
return serviceList;
}
private ServiceType getCertificateService() {
ServiceType certificateService = new ServiceType();
certificateService.setFreeService(false); // it is supposed that certificate installation is by default not for free
certificateService.setServiceCategory(ServiceCategoryType.CONTRACT_CERTIFICATE);
certificateService.setServiceID(2); // according to Table 105 ISO/IEC 15118-2
certificateService.setServiceName("Contrac certificate installation/update"); // optional value
certificateService.setServiceScope(""); // optional value
return certificateService;
}
}

View File

@ -0,0 +1,43 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.states;
import org.eclipse.risev2g.secc.session.V2GCommunicationSessionSECC;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SessionSetupReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SessionSetupResType;
public class WaitForSessionSetupReq extends ServerState {
private SessionSetupResType sessionSetupRes;
public WaitForSessionSetupReq(V2GCommunicationSessionSECC commSessionContext) {
super(commSessionContext);
sessionSetupRes = new SessionSetupResType();
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, SessionSetupReqType.class, sessionSetupRes)) {
sessionSetupRes.setEVSEID(getCommSessionContext().getEvseController().getEvseID());
// Unix time stamp is needed (seconds instead of milliseconds)
sessionSetupRes.setEVSETimeStamp(System.currentTimeMillis() / 1000L);
}
return getSendMessage(sessionSetupRes,
(sessionSetupRes.getResponseCode().toString().startsWith("OK") ?
V2GMessages.SERVICE_DISCOVERY_REQ : V2GMessages.NONE)
);
}
}

View File

@ -0,0 +1,37 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.states;
import org.eclipse.risev2g.secc.session.V2GCommunicationSessionSECC;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SessionStopReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SessionStopResType;
public class WaitForSessionStopReq extends ServerState {
private SessionStopResType sessionStopRes;
public WaitForSessionStopReq(V2GCommunicationSessionSECC commSessionContext) {
super(commSessionContext);
sessionStopRes = new SessionStopResType();
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, SessionStopReqType.class, sessionStopRes)) {
getCommSessionContext().setStopV2GCommunicationSession(true);
}
return getSendMessage(sessionStopRes, V2GMessages.NONE);
}
}

View File

@ -0,0 +1,114 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.states;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.risev2g.secc.session.V2GCommunicationSessionSECC;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ChangeProcessingState;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.v2gMessages.SECCDiscoveryReq;
import org.eclipse.risev2g.shared.v2gMessages.appProtocol.AppProtocolType;
import org.eclipse.risev2g.shared.v2gMessages.appProtocol.ResponseCodeType;
import org.eclipse.risev2g.shared.v2gMessages.appProtocol.SupportedAppProtocolReq;
import org.eclipse.risev2g.shared.v2gMessages.appProtocol.SupportedAppProtocolRes;
public class WaitForSupportedAppProtocolReq extends ServerState {
private SupportedAppProtocolRes supportedAppProtocolRes;
public WaitForSupportedAppProtocolReq(V2GCommunicationSessionSECC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
supportedAppProtocolRes = new SupportedAppProtocolRes();
if (message instanceof SupportedAppProtocolReq) {
getLogger().debug("SupportedAppProtocolReq received");
boolean match = false;
ResponseCodeType responseCode = ResponseCodeType.FAILED_NO_NEGOTIATION;
SupportedAppProtocolReq supportedAppProtocolReq = (SupportedAppProtocolReq) message;
// The provided appProtocols might not be sorted by priority
Collections.sort(supportedAppProtocolReq.getAppProtocol(), (appProtocol1, appProtocol2) ->
Short.compare(appProtocol1.getPriority(), appProtocol2.getPriority()));
/*
* If protocol and major version matches with more than one supported protocol,
* choose the one with highest priority
*/
for (AppProtocolType evccAppProtocol : supportedAppProtocolReq.getAppProtocol()) {
/*
* A getSupportedAppProtocols().contains(evccAppProtocol) does not work here since
* priority and schemaID are not provided in getSupportedAppProtocols()
*/
for (AppProtocolType seccAppProtocol : getSupportedAppProtocols()) {
if (evccAppProtocol.getProtocolNamespace().equals(seccAppProtocol.getProtocolNamespace()) &&
evccAppProtocol.getVersionNumberMajor() == seccAppProtocol.getVersionNumberMajor()) {
if (evccAppProtocol.getVersionNumberMinor() == seccAppProtocol.getVersionNumberMinor()) {
responseCode = ResponseCodeType.OK_SUCCESSFUL_NEGOTIATION;
} else {
responseCode = ResponseCodeType.OK_SUCCESSFUL_NEGOTIATION_WITH_MINOR_DEVIATION;
}
match = true;
supportedAppProtocolRes.setSchemaID(evccAppProtocol.getSchemaID());
break;
}
}
if (match) break;
}
supportedAppProtocolRes.setResponseCode(responseCode);
} else if (message instanceof SECCDiscoveryReq) {
getLogger().debug("Another SECCDiscoveryReq was received, changing to state WaitForSECCDiscoveryReq");
return new ChangeProcessingState(message, getCommSessionContext().getStates().get(V2GMessages.SECC_DISCOVERY_REQ));
} else {
getLogger().error("Invalid message (" + message.getClass().getSimpleName() +
") at this state (" + this.getClass().getSimpleName() + ")");
supportedAppProtocolRes.setResponseCode(ResponseCodeType.FAILED_NO_NEGOTIATION);
}
return getSendMessage(supportedAppProtocolRes,
(supportedAppProtocolRes.getResponseCode().toString().startsWith("OK") ?
V2GMessages.SESSION_SETUP_REQ : V2GMessages.NONE)
);
}
/**
* 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.
* The values for priority and schema ID do not need to be set since these values are provided by
* the EVCC.
*
* @return A list of supported of AppProtocol entries
*/
private List<AppProtocolType> getSupportedAppProtocols() {
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);
supportedAppProtocols.add(appProtocol1);
return supportedAppProtocols;
}
}

View File

@ -0,0 +1,56 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.states;
import org.eclipse.risev2g.secc.evseController.IDCEVSEController;
import org.eclipse.risev2g.secc.session.V2GCommunicationSessionSECC;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.V2GMessage;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.WeldingDetectionReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.WeldingDetectionResType;
public class WaitForWeldingDetectionReq extends ServerState {
private WeldingDetectionResType weldingDetectionRes;
public WaitForWeldingDetectionReq(V2GCommunicationSessionSECC commSessionContext) {
super(commSessionContext);
weldingDetectionRes = new WeldingDetectionResType();
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, WeldingDetectionReqType.class, weldingDetectionRes)) {
V2GMessage v2gMessageReq = (V2GMessage) message;
WeldingDetectionReqType weldingDetectionReq =
(WeldingDetectionReqType) v2gMessageReq.getBody().getBodyElement().getValue();
// TODO how to react to failure status of DCEVStatus of weldingDetectionReq?
IDCEVSEController evseController = (IDCEVSEController) getCommSessionContext().getDCEvseController();
weldingDetectionRes.setDCEVSEStatus(evseController.getDCEVSEStatus(EVSENotificationType.NONE));
weldingDetectionRes.setEVSEPresentVoltage(evseController.getPresentVoltage());
((ForkState) getCommSessionContext().getStates().get(V2GMessages.FORK))
.getAllowedRequests().add(V2GMessages.WELDING_DETECTION_REQ);
((ForkState) getCommSessionContext().getStates().get(V2GMessages.FORK))
.getAllowedRequests().add(V2GMessages.SESSION_STOP_REQ);
}
return getSendMessage(weldingDetectionRes,
(weldingDetectionRes.getResponseCode().toString().startsWith("OK") ?
V2GMessages.FORK : V2GMessages.NONE)
);
}
}

View File

@ -0,0 +1,310 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.transportLayer;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.Arrays;
import java.util.Observable;
import javax.net.ssl.SSLSocket;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.risev2g.shared.misc.TimeRestrictions;
import org.eclipse.risev2g.shared.misc.V2GTPMessage;
import org.eclipse.risev2g.shared.utils.ByteUtils;
public class ConnectionHandler extends Observable implements Runnable {
private Logger logger = LogManager.getLogger(this.getClass().getSimpleName());
private Socket tcpClientSocket;
private SSLSocket tlsClientSocket;
private InputStream inStream;
private OutputStream outStream;
private byte[] v2gTpHeader;
private byte[] v2gTPPayload;
private byte[] v2gTPMessage;
private final int MASK = 0x80;
private int payloadLength;
private int bytesReadFromInputStream;
private boolean stopAlreadyInitiated;
private String address;
private int port;
public ConnectionHandler(Socket tcpClientSocket) {
setTcpClientSocket(tcpClientSocket);
try {
setInStream(getTcpClientSocket().getInputStream());
setOutStream(getTcpClientSocket().getOutputStream());
setV2gTpHeader(new byte[8]);
} catch (Exception e) {
stopAndNotify("An IOException was thrown while creating streams from TCP client socket", e);
}
}
public ConnectionHandler(SSLSocket tlsClientSocket) {
setTlsClientSocket(tlsClientSocket);
try {
setInStream(getTlsClientSocket().getInputStream());
setOutStream(getTlsClientSocket().getOutputStream());
setV2gTpHeader(new byte[8]);
} catch (IOException e) {
stopAndNotify("An IOException was thrown while creating streams from TLS client socket", e);
}
}
@Override
public void run() {
while (!Thread.interrupted()) {
/*
* Read header (8 bytes) of incoming V2GTPMessage to further allocate a byte array with
* the appropriate length.
*/
try {
if (getTcpClientSocket() != null) {
getTcpClientSocket().setSoTimeout(TimeRestrictions.V2G_SECC_SEQUENCE_TIMEOUT);
} else if (getTlsClientSocket() != null) {
getTlsClientSocket().setSoTimeout(TimeRestrictions.V2G_SECC_SEQUENCE_TIMEOUT);
} else {
getLogger().error("Neither TCP nor TLS client socket available");
Thread.currentThread().interrupt();
}
setBytesReadFromInputStream(getInStream().read(getV2gTpHeader()));
if (bytesReadFromInputStream < 0) {
stopAndNotify("No bytes read from input stream, client socket seems to be closed", null);
break;
}
/*
* The payload length is written to the last 4 bytes (v2gTPHeader[4] to v2gTPHeader[7])
* of the V2GTP header. The most significant bit of v2gTPHeader[4] should never be set.
* If it was set, then this would mean that a V2GTP message of a size of at least 2 GB
* was intended to be transferred ... and this cannot be, no V2G message has this size.
* Since the most significant bit should never be set, we do not need to care about
* signed integers in Java at this point!
*/
if ((getV2gTpHeader()[4] & MASK) == MASK) {
stopAndNotify("Payload length of V2GTP message is inappropiately high! There must be " +
"an error in the V2GTP message header!", null);
break;
} else {
setPayloadLength(ByteUtils.toIntFromByteArray(Arrays.copyOfRange(getV2gTpHeader(), 4, 8)));
setV2gTPPayload(new byte[payloadLength]);
getInStream().read(getV2gTPPayload());
setV2gTPMessage(new byte[getV2gTpHeader().length + getV2gTPPayload().length]);
System.arraycopy(getV2gTpHeader(), 0, getV2gTPMessage(), 0, getV2gTpHeader().length);
System.arraycopy(getV2gTPPayload(), 0, getV2gTPMessage(), getV2gTpHeader().length, getV2gTPPayload().length);
getLogger().debug("Message received");
setChanged();
notifyObservers(getV2gTPMessage());
}
} catch (SocketTimeoutException e) {
stopAndNotify("A SocketTimeoutException occurred", null);
break;
} catch (IOException e1) {
stopAndNotify("IOException occurred", e1);
break;
}
}
}
/**
* If an error occurred in the run()-method, the client will be stopped by closing all streams
* and the socket and interrupting the Thread. V2GCommunicationSessionSECC will be notified as well.
* The method's statements will not be executed if a stop of the client has already been
* initiated by the V2GCommunicationSessionSECC (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
*/
private void stopAndNotify(String errorMessage, Exception e) {
if (!isStopAlreadyInitiated()) {
getLogger().error(errorMessage, e);
stop();
// Notify V2GCommunicationSessionEVCC about termination of session
setChanged();
notifyObservers(null);
}
}
public void stop() {
if (!isStopAlreadyInitiated()) {
setStopAlreadyInitiated(true);
try {
getInStream().close();
getOutStream().close();
if (getTcpClientSocket() != null) {
getTcpClientSocket().close();
} else if (getTlsClientSocket() != null) {
getTlsClientSocket().close();
} else {
getLogger().error("Neither TCP nor TLS client socket could be closed");
}
Thread.currentThread().interrupt();
getLogger().debug("Connection to client closed");
} catch (IOException e) {
getLogger().error("Error occurred while trying to close socket to client", e);
}
}
}
public boolean send(V2GTPMessage message) {
try {
getOutStream().write(message.getMessage());
getOutStream().flush();
} catch (IOException e) {
getLogger().error("Error occurred while trying to send V2GTPMessage (IOException)!", e);
}
getLogger().debug("Message sent");
return false;
}
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 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 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 Logger getLogger() {
return logger;
}
public void setLogger(Logger logger) {
this.logger = logger;
}
public byte[] getV2gTpHeader() {
return v2gTpHeader;
}
private void setV2gTpHeader(byte[] v2gTpHeader) {
this.v2gTpHeader = v2gTpHeader;
}
public boolean isStopAlreadyInitiated() {
return stopAlreadyInitiated;
}
public void setStopAlreadyInitiated(boolean stopAlreadyInitiated) {
this.stopAlreadyInitiated = stopAlreadyInitiated;
}
public Socket getTcpClientSocket() {
return tcpClientSocket;
}
public void setTcpClientSocket(Socket tcpClientSocket) {
this.tcpClientSocket = tcpClientSocket;
setAddress(tcpClientSocket.getInetAddress().getHostAddress());
setPort(tcpClientSocket.getPort());
}
public SSLSocket getTlsClientSocket() {
return tlsClientSocket;
}
public void setTlsClientSocket(SSLSocket tlsClientSocket) {
this.tlsClientSocket = tlsClientSocket;
setAddress(tlsClientSocket.getInetAddress().getHostAddress());
setPort(tlsClientSocket.getPort());
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}

View File

@ -0,0 +1,59 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.transportLayer;
import java.net.Inet6Address;
import java.util.Observable;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.risev2g.shared.utils.MiscUtils;
public abstract class StatefulTransportLayerServer extends Observable implements Runnable {
private Logger logger = LogManager.getLogger(this.getClass().getSimpleName());
private int serverPort;
private Inet6Address serverAddress;
protected boolean initialize() {
setServerPort(MiscUtils.getRandomPortNumber());
setServerAddress(MiscUtils.getLinkLocalAddress());
return true;
}
public abstract void stop();
public Logger getLogger() {
return logger;
}
public void setLogger(Logger logger) {
this.logger = logger;
}
public int getServerPort() {
return serverPort;
}
public void setServerPort(int serverPort) {
this.serverPort = serverPort;
}
public Inet6Address getServerAddress() {
return serverAddress;
}
public void setServerAddress(Inet6Address serverAddress) {
this.serverAddress = serverAddress;
}
}

View File

@ -0,0 +1,104 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.transportLayer;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
public final class TCPServer extends StatefulTransportLayerServer {
// Eager instantiation of the Singleton TCPClient.
private static final TCPServer uniqueTCPServerInstance = new TCPServer();
private Socket tcpClientSocket;
private ServerSocket tcpServerSocket;
private TCPServer() {}
public static TCPServer getInstance() {
return uniqueTCPServerInstance;
}
public boolean initialize() {
super.initialize();
try {
setTcpServerSocket(new ServerSocket(getServerPort(), 50, getServerAddress()));
getLogger().debug("TCP server initialized at link-local address " +
getTcpServerSocket().getInetAddress().getHostAddress() +
" and port " + getTcpServerSocket().getLocalPort());
} catch (IOException e) {
getLogger().fatal("IOException while trying to initialize TCP server", e);
return false;
}
return true;
}
@Override
public void run() {
try {
while (!Thread.interrupted()) {
getLogger().debug("Waiting for new TCP client connection ...");
setTcpClientSocket(getTcpServerSocket().accept());
getLogger().debug("TCP client connection with IP address " +
getTcpClientSocket().getInetAddress().getHostAddress() + " and port " +
getTcpClientSocket().getPort());
ConnectionHandler connectionHandler = new ConnectionHandler(tcpClientSocket);
// Notify the V2GCommunicationSessionHandlerSECC about a newly connected TCP client Socket
setChanged();
notifyObservers(connectionHandler);
}
} catch (IOException e) {
getLogger().error(e.getClass().getSimpleName() + " occurred while running TCP server");
} finally {
stop();
}
}
@Override
public void stop() {
try {
getLogger().debug("TCP server will be stopped now");
getTcpServerSocket().close();
} catch (SocketException e) {
getLogger().debug("TCPServerSocket was still active and has been closed now", e);
} catch (IOException e) {
getLogger().error("Error occurred while trying to close TCPServerSocket (IOException)", e);
}
getLogger().debug("TCP server stopped");
}
public ServerSocket getTcpServerSocket() {
return tcpServerSocket;
}
public void setTcpServerSocket(ServerSocket tcpServerSocket) {
this.tcpServerSocket = tcpServerSocket;
}
public Socket getTcpClientSocket() {
return tcpClientSocket;
}
public void setTcpClientSocket(Socket tcpClientSocket) {
this.tcpClientSocket = tcpClientSocket;
}
}

View File

@ -0,0 +1,141 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.transportLayer;
import java.io.IOException;
import java.net.SocketException;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.utils.SecurityUtils;
public final class TLSServer extends StatefulTransportLayerServer {
private static final TLSServer uniqueTLSServerInstance = new TLSServer();
private SSLSocket tlsClientSocket;
private SSLServerSocket tlsServerSocket;
private TLSServer() {}
public static TLSServer getInstance() {
return uniqueTLSServerInstance;
}
/**
* Used to check the correct initialization of a TCP server which is a prerequisite for establishing
* a V2G communication session.
* @return True if the initialization of the TCP server was successful, false otherwise
*/
public boolean initialize() {
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.SECC_KEYSTORE_FILEPATH.toString(),
GlobalValues.SECC_TRUSTSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
SSLServerSocketFactory tlsServerSocketFactory =
(SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
setTlsServerSocket((SSLServerSocket) tlsServerSocketFactory
.createServerSocket(getServerPort(), 50, getServerAddress()));
/*
* 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
*/
String[] enabledCipherSuites = {"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256"};
getTlsServerSocket().setEnabledCipherSuites(enabledCipherSuites);
// Set the supported TLS protocol
String[] enabledProtocols = {"TLSv1.2"};
getTlsServerSocket().setEnabledProtocols(enabledProtocols);
getLogger().debug("TLS server initialized at link-local address " +
getTlsServerSocket().getInetAddress().getHostAddress() +
" and port " + getTlsServerSocket().getLocalPort());
} catch (IOException e) {
getLogger().fatal("IOException while trying to initialize TLS server", e);
return false;
} catch (NullPointerException e) {
getLogger().fatal("NullPointerException while trying to set keystores, resource path to keystore/truststore might be incorrect");
return false;
}
return true;
}
@Override
public void run() {
try {
while (!Thread.interrupted()) {
getLogger().debug("Waiting for new TLS client connection ...");
setTlsClientSocket((SSLSocket) getTlsServerSocket().accept());
getLogger().debug("TLS client connection with IP address " +
getTlsClientSocket().getInetAddress().getHostAddress() + " and port " +
getTlsClientSocket().getPort());
ConnectionHandler connectionHandler = new ConnectionHandler(tlsClientSocket);
// Notify the V2GCommunicationSessionHandlerSECC about a newly connected TCP client Socket
setChanged();
notifyObservers(connectionHandler);
}
} catch (IOException e) {
getLogger().error(e.getClass().getSimpleName() + " occurred while running TLSServer");
} finally {
stop();
}
}
@Override
public void stop() {
try {
getLogger().debug("TLS server will be stopped now");
getTlsServerSocket().close();
} catch (SocketException e) {
getLogger().debug("TLSServerSocket was still active and has been closed now", e);
} catch (IOException e) {
getLogger().error("Error occurred while trying to close TLSServerSocket (IOException)", e);
}
getLogger().debug("TCP server stopped");
}
public SSLSocket getTlsClientSocket() {
return tlsClientSocket;
}
public void setTlsClientSocket(SSLSocket tlsClientSocket) {
this.tlsClientSocket = tlsClientSocket;
}
public SSLServerSocket getTlsServerSocket() {
return tlsServerSocket;
}
public void setTlsServerSocket(SSLServerSocket tlsServerSocket) {
this.tlsServerSocket = tlsServerSocket;
}
}

View File

@ -0,0 +1,191 @@
/*******************************************************************************
* Copyright (c) 2015 Marc Mültin (Chargepartner GmbH).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dr.-Ing. Marc Mültin (Chargepartner GmbH) - initial API and implementation and initial documentation
*******************************************************************************/
package org.eclipse.risev2g.secc.transportLayer;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.Inet6Address;
import java.net.MulticastSocket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Observable;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.misc.V2GTPMessage;
import org.eclipse.risev2g.shared.utils.MiscUtils;
/**
* The UDP server 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 10 bytes
* (8 bytes header of V2GTP message + 2 byte SECCDiscoveryReq payload).
*/
public class UDPServer extends Observable implements Runnable {
/*
* Eager instantiation of the Singleton, since a UDP server is always needed up front.
* 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 final UDPServer uniqueUDPServerInstance = new UDPServer();
private Inet6Address multicastAddress;
private MulticastSocket udpServerSocket;
private byte[] udpClientRequest;
private DatagramPacket udpClientPacket;
private Inet6Address udpServerAddress;
private UDPServer() {}
/**
* Used to check the correct initialization of a UDP server which is a prerequisite for establishing
* a V2G communication session.
* @return True if the initialization of the UDP server was successful, false otherwise
*/
public boolean initialize() {
setUdpClientRequest(new byte[10]);
try {
setUdpServerAddress(MiscUtils.getLinkLocalAddress());
if (getUdpServerAddress() == null) return false;
setMulticastAddress((Inet6Address) Inet6Address.getByName(GlobalValues.SDP_MULTICAST_ADDRESS.toString()));
setUdpServerSocket(new MulticastSocket(GlobalValues.V2G_UDP_SDP_SERVER_PORT.getShortValue()));
getUdpServerSocket().setReuseAddress(true);
// Without setting the interface, the server might not react to client requests
getUdpServerSocket().setInterface(getUdpServerAddress());
getUdpServerSocket().joinGroup(getMulticastAddress());
getLogger().info("UDP server initialized at link-local address " +
getUdpServerAddress().getHostAddress() + " and port 15118");
} catch (UnknownHostException e) {
getLogger().error("Unknown host exception was thrown!", e);
return false;
} catch (IOException e) {
getLogger().error("MulticastSocket creation failed!", e);
return false;
}
return true;
}
public static UDPServer getInstance() {
return uniqueUDPServerInstance;
}
public void run() {
while (!Thread.interrupted()) {
setUdpClientPacket(new DatagramPacket(udpClientRequest, udpClientRequest.length));
try {
getUdpServerSocket().receive(getUdpClientPacket());
getLogger().debug("Message received");
// Notify the session handler about a new incoming SECCDiscoveryReq message
setChanged();
notifyObservers(getUdpClientPacket());
} catch (SocketException e) {
getLogger().error("SocketException", e);
} catch (IOException e) {
getLogger().error("IOException", e);
getUdpServerSocket().close();
}
}
}
public void stop() {
getLogger().debug("UDP server will be stopped now");
try {
getUdpServerSocket().leaveGroup(multicastAddress);
} catch (IOException e) {
getLogger().error("Error occurred while trying to close TCPServerSocket (IOException)", e);
}
getUdpServerSocket().close();
getLogger().debug("UDP server stopped (socket closed)");
}
public boolean send(V2GTPMessage message, Inet6Address udpClientAddress, int udpClientPort) {
byte[] v2gTPMessage = message.getMessage();
// Set up the UDP packet containing the V2GTP message to be sent to the UDP client
DatagramPacket udpServerPacket = new DatagramPacket(v2gTPMessage,
v2gTPMessage.length,
udpClientAddress,
udpClientPort);
// Send the response to the UDP client
try {
udpServerSocket.send(udpServerPacket);
getLogger().debug("Message sent");
return true;
} catch (IOException e) {
getLogger().error("UDP response failed (IOException) while trying to send message!", e);
return false;
}
}
public Logger getLogger() {
return logger;
}
public void setLogger(Logger logger) {
this.logger = logger;
}
public Inet6Address getMulticastAddress() {
return multicastAddress;
}
public void setMulticastAddress(Inet6Address multicastAddress) {
this.multicastAddress = multicastAddress;
}
public MulticastSocket getUdpServerSocket() {
return udpServerSocket;
}
public void setUdpServerSocket(MulticastSocket udpServerSocket) {
this.udpServerSocket = udpServerSocket;
}
public byte[] getUdpClientRequest() {
return udpClientRequest;
}
public void setUdpClientRequest(byte[] udpClientRequest) {
this.udpClientRequest = udpClientRequest;
}
public DatagramPacket getUdpClientPacket() {
return udpClientPacket;
}
public void setUdpClientPacket(DatagramPacket udpClientPacket) {
this.udpClientPacket = udpClientPacket;
}
public Inet6Address getUdpServerAddress() {
return udpServerAddress;
}
private void setUdpServerAddress(Inet6Address udpServerAddress) {
this.udpServerAddress = udpServerAddress;
}
}

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8" ?>
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appenders>
<console name="Console" target="SYSTEM_OUT">
<patternLayout pattern="%d{ISO8601} %-5p [%t] %c: %m%n" />
</console>
<!-- <param name="threshold" value="debug" />
<param name="Target" value="System.out" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{ISO8601} %-5p [%t] %c: %m%n" />
</layout>-->
</appenders>
<loggers>
<root level="debug">
<appender-ref ref="Console" />
</root>
</loggers>
</log4j:configuration>
<!-- see http://logging.apache.org/log4j/2.x/manual/configuration.html -->

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