add files

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,129 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.states;
import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.enumerations.PKI;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentDetailsReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentDetailsResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.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() / 1000L);
byte[] genChallenge = SecurityUtils.generateRandomNumber(16);
getCommSessionContext().setGenChallenge(genChallenge);
paymentDetailsRes.setGenChallenge(genChallenge);
} else {
setMandatoryFieldsForFailedRes(paymentDetailsRes, paymentDetailsRes.getResponseCode());
}
} else {
if (paymentDetailsRes.getResponseCode().equals(ResponseCodeType.FAILED_SEQUENCE_ERROR)) {
BodyBaseType responseMessage = getSequenceErrorResMessage(new PaymentDetailsResType(), message);
return getSendMessage(responseMessage, V2GMessages.NONE, paymentDetailsRes.getResponseCode());
} else {
setMandatoryFieldsForFailedRes(paymentDetailsRes, paymentDetailsRes.getResponseCode());
}
}
return getSendMessage(paymentDetailsRes,
(paymentDetailsRes.getResponseCode().toString().startsWith("OK") ?
V2GMessages.AUTHORIZATION_REQ : V2GMessages.NONE),
paymentDetailsRes.getResponseCode()
);
}
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;
}
// Check complete contract certificate chain
ResponseCodeType certChainResponseCode = SecurityUtils.verifyCertificateChain(
paymentDetailsReq.getContractSignatureCertChain(),
GlobalValues.SECC_TRUSTSTORE_FILEPATH.toString(),
PKI.MO);
if (!certChainResponseCode.equals(ResponseCodeType.OK)) {
paymentDetailsRes.setResponseCode(certChainResponseCode);
return false;
}
// Check if certificate expires soon (in 21 days or fewer) according to V2G2-690
// A check for general validity has already been done above and does not need to be checked again here
if (SecurityUtils.getValidityPeriod(
SecurityUtils.getCertificate(paymentDetailsReq.getContractSignatureCertChain().getCertificate())
) <= GlobalValues.CERTIFICATE_EXPIRES_SOON_PERIOD.getShortValue()) {
paymentDetailsRes.setResponseCode(ResponseCodeType.OK_CERTIFICATE_EXPIRES_SOON);
}
// Check for FAILED_ContractCancelled
// TODO how to check if the EMAID provided by EVCC is not accepted by secondary actor?
if (!SecurityUtils.isEMAIDSyntaxValid(
SecurityUtils.getCertificate(
paymentDetailsReq.getContractSignatureCertChain().getCertificate())
)
) {
// There is no good FAILED response code for this situation, but ContractCanceled is still better than FAILED
paymentDetailsRes.setResponseCode(ResponseCodeType.FAILED_CONTRACT_CANCELED);
return false;
}
return true;
}
@Override
public BodyBaseType getResponseMessage() {
return paymentDetailsRes;
}
}

View File

@@ -0,0 +1,140 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.states;
import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentServiceSelectionReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentServiceSelectionResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SelectedServiceType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceType;
import com.v2gclarity.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();
getLogger().info("Payment option " + paymentServiceSelectionReq.getSelectedPaymentOption().toString() +
" has been chosen by EVCC");
getCommSessionContext().setSelectedPaymentOption(paymentServiceSelectionReq.getSelectedPaymentOption());
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 {
setMandatoryFieldsForFailedRes(paymentServiceSelectionRes, paymentServiceSelectionRes.getResponseCode());
}
} else {
if (paymentServiceSelectionRes.getResponseCode().equals(ResponseCodeType.FAILED_SEQUENCE_ERROR)) {
BodyBaseType responseMessage = getSequenceErrorResMessage(new PaymentServiceSelectionResType(), message);
return getSendMessage(responseMessage, V2GMessages.NONE, paymentServiceSelectionRes.getResponseCode());
} else {
setMandatoryFieldsForFailedRes(paymentServiceSelectionRes, paymentServiceSelectionRes.getResponseCode());
}
}
return getSendMessage(paymentServiceSelectionRes, V2GMessages.NONE, paymentServiceSelectionRes.getResponseCode());
}
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;
}
@Override
public BodyBaseType getResponseMessage() {
return paymentServiceSelectionRes;
}
}

View File

@@ -0,0 +1,272 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.states;
import java.util.ArrayList;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ACEVSEStatusType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeProgressType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingProfileType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVSEStatusCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVSEStatusType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PMaxScheduleEntryType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PowerDeliveryReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PowerDeliveryResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ProfileEntryType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.RelativeTimeIntervalType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SAScheduleTupleType;
import com.v2gclarity.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());
// TODO regard [V2G2-866]
setEVSEStatus(powerDeliveryRes);
if (powerDeliveryReq.getChargeProgress().equals(ChargeProgressType.START)) {
getCommSessionContext().setChargeProgressStarted(true); // see [V2G2-812]
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 {
setMandatoryFieldsForFailedRes(powerDeliveryRes, powerDeliveryRes.getResponseCode());
}
} else {
if (powerDeliveryRes.getResponseCode().equals(ResponseCodeType.FAILED_SEQUENCE_ERROR)) {
BodyBaseType responseMessage = getSequenceErrorResMessage(new PowerDeliveryResType(), message);
return getSendMessage(responseMessage, V2GMessages.NONE, powerDeliveryRes.getResponseCode());
} else {
setMandatoryFieldsForFailedRes(powerDeliveryRes, powerDeliveryRes.getResponseCode());
}
}
return getSendMessage(powerDeliveryRes, V2GMessages.NONE, powerDeliveryRes.getResponseCode());
}
public boolean isResponseCodeOK(PowerDeliveryReqType powerDeliveryReq) {
SAScheduleTupleType chosenSASchedule = getChosenSASCheduleTuple(powerDeliveryReq.getSAScheduleTupleID());
// This debug message is helpful to determine why the EV might not send a ChargingProfile
// (parameter is optional and should only be left out if ChargeProgress is set to Stop)
getLogger().debug("ChargeProgress of PowerDeliveryReq set to '" + powerDeliveryReq.getChargeProgress().toString() + "'");
if (powerDeliveryReq.getChargeProgress().equals(ChargeProgressType.RENEGOTIATE) &&
!getCommSessionContext().isChargeProgressStarted()) {
getLogger().error("EVCC wants to renegotiate, but charge progress has not started yet (no "
+ "PowerDeliveryReq with ChargeProgress=START has been received before)");
powerDeliveryRes.setResponseCode(ResponseCodeType.FAILED);
return false;
}
if (chosenSASchedule == null) {
getLogger().warn("Chosen SAScheduleTupleID in PowerDeliveryReq is null, but parameter is mandatory");
powerDeliveryRes.setResponseCode(ResponseCodeType.FAILED_TARIFF_SELECTION_INVALID);
return false;
}
// Important to call this AFTER checking for valid tariff selection because of possible null-value!
// Check ChargingProfile only if EV wants to start (not stop or renegotiate) the charging process
if (powerDeliveryReq.getChargeProgress().equals(ChargeProgressType.START) &&
!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().warn("EVSE status code is '" + dcEVSEStatusCode.toString() + "'");
if (!powerDeliveryReq.getChargeProgress().equals(ChargeProgressType.STOP)) {
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;
}
protected void setEVSEStatus(PowerDeliveryResType powerDeliveryRes) {
// In case the SECC received a PowerDeliveryReq before a PaymentServiceSelectionReq, the field requestedEnergyTransferMode will be null. So we need to check for it.
if (getCommSessionContext().getRequestedEnergyTransferMode() != null && 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<ACEVSEStatusType> 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() != null && 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<DCEVSEStatusType> jaxbACEVSEStatus = new JAXBElement<>(new QName("urn:iso:15118:2:2013:MsgDataTypes", "DC_EVSEStatus"),
DCEVSEStatusType.class,
getCommSessionContext().getDCEvseController().getDCEVSEStatus(EVSENotificationType.NONE));
powerDeliveryRes.setEVSEStatus(jaxbACEVSEStatus);
} else {
getLogger().warn("RequestedEnergyTransferMode '" + getCommSessionContext().getRequestedEnergyTransferMode().toString() +
"is neither of type AC nor DC");
}
}
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) {
long profileEntryStart = 0;
double profileEntryPower = 0;
long pMaxScheduleIntervalStart = 0;
long pMaxScheduleIntervalEnd = 0;
double pMaxScheduleIntervalPMax = 0;
ArrayList<PMaxScheduleEntryType> limit = (ArrayList<PMaxScheduleEntryType>) chosenSAScheduleTuple.getPMaxSchedule().getPMaxScheduleEntry();
if (chargingProfile == null) {
getLogger().error("ChargingProfile is empty (null)");
return false;
}
for (ProfileEntryType profileEntry : chargingProfile.getProfileEntry()) {
if (profileEntry.getChargingProfileEntryMaxNumberOfPhasesInUse() != null && profileEntry.getChargingProfileEntryMaxNumberOfPhasesInUse() == 2) {
getLogger().error("Parameter MaxNumberOfPhasesInUse of one ChargingProfile entry element is 2 which is not allowed. Only 1 or 3 are valid values.");
return false;
}
profileEntryStart = profileEntry.getChargingProfileEntryStart();
profileEntryPower = profileEntry.getChargingProfileEntryMaxPower().getValue() *
Math.pow(10, profileEntry.getChargingProfileEntryMaxPower().getMultiplier());
for (int i=0; i < limit.size(); i++) {
pMaxScheduleIntervalStart = ((RelativeTimeIntervalType) limit.get(i).getTimeInterval().getValue()).getStart();
try {
pMaxScheduleIntervalEnd = ((RelativeTimeIntervalType) limit.get(i+1).getTimeInterval().getValue()).getStart();
} catch (IndexOutOfBoundsException e) {
if ( ((RelativeTimeIntervalType) limit.get(i).getTimeInterval().getValue()).getDuration() != 0)
pMaxScheduleIntervalEnd = pMaxScheduleIntervalStart + ((RelativeTimeIntervalType) limit.get(i).getTimeInterval().getValue()).getDuration();
else
pMaxScheduleIntervalEnd = Long.MAX_VALUE;
}
pMaxScheduleIntervalPMax = limit.get(i).getPMax().getValue() * Math.pow(10, limit.get(i).getPMax().getMultiplier());
// TODO Find out how to deal with grace time period defined by [V2G2-833] and [V2G2-834] that contradicts [V2G2-777]
if (profileEntryStart >= pMaxScheduleIntervalStart && profileEntryStart < pMaxScheduleIntervalEnd) {
if (profileEntryPower > pMaxScheduleIntervalPMax) {
getLogger().error("ChargingProfile entry element starting at " + profileEntryStart +
"s exceeds power limit. Limit is " + pMaxScheduleIntervalPMax +
" W, ChargingProfile entry's max power value is " + profileEntryPower + " W" );
return false;
} else
break;
} else
continue;
}
}
return true;
}
@Override
public BodyBaseType getResponseMessage() {
return powerDeliveryRes;
}
}

View File

@@ -0,0 +1,89 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.states;
import com.v2gclarity.risev2g.secc.evseController.IDCEVSEController;
import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PreChargeReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PreChargeResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.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);
} else {
if (preChargeRes.getResponseCode().equals(ResponseCodeType.FAILED_SEQUENCE_ERROR)) {
BodyBaseType responseMessage = getSequenceErrorResMessage(new PreChargeResType(), message);
return getSendMessage(responseMessage, V2GMessages.NONE, preChargeRes.getResponseCode());
} else {
setMandatoryFieldsForFailedRes(preChargeRes, preChargeRes.getResponseCode());
}
}
return getSendMessage(preChargeRes,
(preChargeRes.getResponseCode().toString().startsWith("OK") ?
V2GMessages.FORK : V2GMessages.NONE),
preChargeRes.getResponseCode()
);
}
@Override
public BodyBaseType getResponseMessage() {
return preChargeRes;
}
}

View File

@@ -0,0 +1,204 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.states;
import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ParameterSetType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ParameterType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceDetailReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceDetailResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceParameterListType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceType;
import com.v2gclarity.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());
// The ServiceParameterList itself is optional, but if you send it, it shall not be empty
if (serviceParameterList.getParameterSet().size() > 0) {
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 {
setMandatoryFieldsForFailedRes(serviceDetailRes, serviceDetailRes.getResponseCode());
}
} else {
if (serviceDetailRes.getResponseCode().equals(ResponseCodeType.FAILED_SEQUENCE_ERROR)) {
BodyBaseType responseMessage = getSequenceErrorResMessage(new ServiceDetailResType(), message);
return getSendMessage(responseMessage, V2GMessages.NONE, serviceDetailRes.getResponseCode());
} else {
setMandatoryFieldsForFailedRes(serviceDetailRes, serviceDetailRes.getResponseCode());
}
}
return getSendMessage(serviceDetailRes, V2GMessages.NONE, serviceDetailRes.getResponseCode());
}
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("Service");
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("Service");
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("FTP20");
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("FTP21");
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;
}
@Override
public BodyBaseType getResponseMessage() {
return serviceDetailRes;
}
}

View File

@@ -0,0 +1,169 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.states;
import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.utils.MiscUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeServiceType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceCategoryType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceDiscoveryReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceDiscoveryResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceListType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SupportedEnergyTransferModeType;
import com.v2gclarity.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());
// The ServiceList itself is optional, but if you send it, it shall not be empty
if (offeredVASList.getService().size() > 0) {
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);
} else {
if (serviceDiscoveryRes.getResponseCode().equals(ResponseCodeType.FAILED_SEQUENCE_ERROR)) {
BodyBaseType responseMessage = getSequenceErrorResMessage(new ServiceDiscoveryResType(), message);
return getSendMessage(responseMessage, V2GMessages.NONE, serviceDiscoveryRes.getResponseCode());
} else {
setMandatoryFieldsForFailedRes(serviceDiscoveryRes, serviceDiscoveryRes.getResponseCode());
}
}
return getSendMessage(serviceDiscoveryRes,
(serviceDiscoveryRes.getResponseCode().toString().startsWith("OK") ?
V2GMessages.FORK : V2GMessages.NONE),
serviceDiscoveryRes.getResponseCode()
);
}
public 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
/*
* Is an optional value, but fill it with a non-empty string if used,
* otherwise an EXI decoding error could occur on the other side!
*/
chargeService.setServiceName("AC_DC_Charging");
/*
* Is an optional value, but fill it with a non-empty string if used,
* otherwise an EXI decoding error could occur on the other side!
*/
chargeService.setServiceScope("chargingServiceScope");
boolean isChargingForFree = ((boolean) MiscUtils.getPropertyValue("charging.free"));
chargeService.setFreeService(isChargingForFree);
return chargeService;
}
private ServiceListType getServiceList(ServiceCategoryType serviceCategoryFilter, String serviceScopeFilter) {
ServiceListType serviceList = new ServiceListType();
if (serviceCategoryFilter != null)
getLogger().debug("EVCC filters offered services by category: " + serviceCategoryFilter.toString());
// Currently no filter based on service scope is applied since its string value is not standardized 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(true);
certificateService.setServiceCategory(ServiceCategoryType.CONTRACT_CERTIFICATE);
certificateService.setServiceID(2); // according to Table 105 ISO/IEC 15118-2
certificateService.setServiceName("Certificate"); // optional value
/*
* Is an optional value, but fill it with a non-empty string if used,
* otherwise an EXI decoding error could occur on the other side!
*/
certificateService.setServiceScope("certificateServiceScope");
return certificateService;
}
@Override
public BodyBaseType getResponseMessage() {
return serviceDiscoveryRes;
}
}

View File

@@ -0,0 +1,73 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.states;
import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SessionSetupReqType;
import com.v2gclarity.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);
} else {
if (sessionSetupRes.getResponseCode().equals(ResponseCodeType.FAILED_SEQUENCE_ERROR)) {
BodyBaseType responseMessage = getSequenceErrorResMessage(new SessionSetupResType(), message);
return getSendMessage(responseMessage, V2GMessages.NONE, sessionSetupRes.getResponseCode());
} else {
setMandatoryFieldsForFailedRes(sessionSetupRes, sessionSetupRes.getResponseCode());
}
}
return getSendMessage(sessionSetupRes,
(sessionSetupRes.getResponseCode().toString().startsWith("OK") ?
V2GMessages.SERVICE_DISCOVERY_REQ : V2GMessages.NONE),
sessionSetupRes.getResponseCode()
);
}
@Override
public BodyBaseType getResponseMessage() {
return sessionSetupRes;
}
}

View File

@@ -0,0 +1,83 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.states;
import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentServiceSelectionReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SessionStopReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SessionStopResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
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)) {
V2GMessage v2gMessageReq = (V2GMessage) message;
SessionStopReqType sessionStopReq =
(SessionStopReqType) v2gMessageReq.getBody().getBodyElement().getValue();
getLogger().info("EV indicated to " + sessionStopReq.getChargingSession() + " the charging session");
if (sessionStopReq.getChargingSession() == ChargingSessionType.TERMINATE) {
getCommSessionContext().setChargingSession(ChargingSessionType.TERMINATE);
return getSendMessage(sessionStopRes, V2GMessages.NONE, sessionStopRes.getResponseCode());
} else {
// EV indicated to pause the charging session. Next expected request message is SupportedAppProtocolReq
getCommSessionContext().setChargingSession(ChargingSessionType.PAUSE);
return getSendMessage(sessionStopRes, V2GMessages.SUPPORTED_APP_PROTOCOL_REQ, sessionStopRes.getResponseCode());
}
} else {
getCommSessionContext().setChargingSession(ChargingSessionType.TERMINATE);
if (sessionStopRes.getResponseCode().equals(ResponseCodeType.FAILED_SEQUENCE_ERROR)) {
BodyBaseType responseMessage = getSequenceErrorResMessage(new SessionStopResType(), message);
return getSendMessage(responseMessage, V2GMessages.NONE, sessionStopRes.getResponseCode());
} else {
setMandatoryFieldsForFailedRes(sessionStopRes, sessionStopRes.getResponseCode());
return getSendMessage(sessionStopRes, V2GMessages.NONE, sessionStopRes.getResponseCode());
}
}
}
@Override
public BodyBaseType getResponseMessage() {
return sessionStopRes;
}
}

View File

@@ -0,0 +1,143 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.states;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ChangeProcessingState;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.v2gMessages.SECCDiscoveryReq;
import com.v2gclarity.risev2g.shared.v2gMessages.appProtocol.AppProtocolType;
import com.v2gclarity.risev2g.shared.v2gMessages.appProtocol.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.appProtocol.SupportedAppProtocolReq;
import com.v2gclarity.risev2g.shared.v2gMessages.appProtocol.SupportedAppProtocolRes;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
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 if (message != null) {
/*
* This check has been introduced to make sure the application can deal with incoming messages which rely
* on the DINSPEC 70121 XSD schema (which is different from the ISO 15118-2 schema. Without this check,
* the message.getClass() would throw a NullPointerException and the application would die.
*/
getLogger().error("Invalid message (" + message.getClass().getSimpleName() +
") at this state (" + this.getClass().getSimpleName() + ")");
supportedAppProtocolRes.setResponseCode(ResponseCodeType.FAILED_NO_NEGOTIATION);
} else {
getLogger().error("Invalid message at this state, message seems to be null. Check if same XSD schema is used on EVCC side.");
supportedAppProtocolRes.setResponseCode(ResponseCodeType.FAILED_NO_NEGOTIATION);
}
return getSendMessage(supportedAppProtocolRes,
(supportedAppProtocolRes.getResponseCode().toString().startsWith("OK") ?
V2GMessages.SESSION_SETUP_REQ : V2GMessages.NONE),
supportedAppProtocolRes.getResponseCode()
);
}
/**
* 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;
}
@Override
public BodyBaseType getResponseMessage() {
return null;
}
}

View File

@@ -0,0 +1,86 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.states;
import com.v2gclarity.risev2g.secc.evseController.IDCEVSEController;
import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.WeldingDetectionReqType;
import com.v2gclarity.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);
} else {
if (weldingDetectionRes.getResponseCode().equals(ResponseCodeType.FAILED_SEQUENCE_ERROR)) {
BodyBaseType responseMessage = getSequenceErrorResMessage(new WeldingDetectionResType(), message);
return getSendMessage(responseMessage, V2GMessages.NONE, weldingDetectionRes.getResponseCode());
} else {
setMandatoryFieldsForFailedRes(weldingDetectionRes, weldingDetectionRes.getResponseCode());
}
}
return getSendMessage(weldingDetectionRes,
(weldingDetectionRes.getResponseCode().toString().startsWith("OK") ?
V2GMessages.FORK : V2GMessages.NONE),
weldingDetectionRes.getResponseCode()
);
}
@Override
public BodyBaseType getResponseMessage() {
return weldingDetectionRes;
}
}

View File

@@ -0,0 +1,340 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.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.SSLHandshakeException;
import javax.net.ssl.SSLSocket;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.v2gclarity.risev2g.shared.misc.TimeRestrictions;
import com.v2gclarity.risev2g.shared.misc.V2GTPMessage;
import com.v2gclarity.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 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 biggest ISO 15118 message in size is the CertificateInstallationRes, which in EXI
* consumes usually 3.000 to 4.000 bytes max. Let's use a threshold of 10.000 just to be safe to
* check for unreasonably high payload lengths.
*
* Change this value if you use a V2GTP payload type for a proprietary, manufacturer-specific use (see Table 10 of ISO 15118-2)
* that requires bigger payloads!
*/
setPayloadLength(ByteUtils.toIntFromByteArray(Arrays.copyOfRange(getV2gTpHeader(), 4, 8)));
if (getPayloadLength() > 10000) {
stopAndNotify("Payload length of V2GTP message is inappropiately high (" + getPayloadLength() + " bytes)! " +
"There must be an error in the V2GTP message header!", null);
break;
} else {
getLogger().debug("Length of V2GTP payload in bytes according to V2GTP header: " + getPayloadLength());
setV2gTPPayload(new byte[getPayloadLength()]);
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 (SSLHandshakeException e1) {
stopAndNotify("An SSLHandshakeException occurred", e1);
break;
} catch (IOException e2) {
stopAndNotify("IOException occurred", e2);
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() {
// See ISO 15118 User Group issue http://extmgmt.kn.e-technik.tu-dortmund.de/issues/50
getLogger().debug("Waiting 5 seconds for EVCC to process response and close TCP/TLS connection ...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!isStopAlreadyInitiated()) {
getLogger().debug("Closing connection to client ...");
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,72 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.transportLayer;
import java.net.Inet6Address;
import java.util.Observable;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.v2gclarity.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,117 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.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().info("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.currentThread().isInterrupted()) {
getLogger().info("Waiting for new TCP client connection ...");
setTcpClientSocket(getTcpServerSocket().accept());
getLogger().info("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,168 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.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 com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.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 setSSLContext()
*/
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
*
* In ISO 15118-2, only the named elliptic curve "secp256r1" is allowed for ECDH(E). The jdk.tls.namedGroups property
* contains a comma-separated list within quotation marks of enabled named groups in preference order. The list of default
* named groups varies depending on what JDK release you are using. Set it on your Java command-line as follows:
*
* $ java -Djdk.tls.namedGroups="secp256r1"
*
* As it turns out, "secp256r1" is already the default first entry for Java 8 (and higher versions), but you should deactivate
* the other elliptic curves by reducing the list to this one entry.
*/
String[] enabledCipherSuites = {
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256" // this cipher suite should be avoided, ECDH does not support perfect forward secrecy
};
getTlsServerSocket().setEnabledCipherSuites(enabledCipherSuites);
// Set the supported TLS protocol
String[] enabledProtocols = {"TLSv1.2"};
getTlsServerSocket().setEnabledProtocols(enabledProtocols);
getLogger().info("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.currentThread().isInterrupted()) {
getLogger().info("Waiting for new TLS client connection ...");
setTlsClientSocket((SSLSocket) getTlsServerSocket().accept());
getLogger().info("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 TLS 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("TLS 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,206 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.secc.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 com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.misc.V2GTPMessage;
import com.v2gclarity.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.currentThread().isInterrupted()) {
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();
}
}
stop();
}
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,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Configuration>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{ISO8601} %level [%t] %c: %m%n" />
</Console>
</Appenders>
<Loggers>
<Root level="debug">
<AppenderRef ref="Console" />
</Root>
</Loggers>
</Configuration>
<!-- see http://logging.apache.org/log4j/2.x/manual/configuration.html -->