add files
This commit is contained in:
23
temp/RISE-V2G/RISE-V2G-SECC/LICENSE.txt
Normal file
23
temp/RISE-V2G/RISE-V2G-SECC/LICENSE.txt
Normal 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.
|
||||
*******************************************************************************/
|
||||
130
temp/RISE-V2G/RISE-V2G-SECC/SECCConfig.properties
Normal file
130
temp/RISE-V2G/RISE-V2G-SECC/SECCConfig.properties
Normal 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
|
||||
87
temp/RISE-V2G/RISE-V2G-SECC/pom.xml
Normal file
87
temp/RISE-V2G/RISE-V2G-SECC/pom.xml
Normal 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>
|
||||
18
temp/RISE-V2G/RISE-V2G-SECC/src/assembly/bin.xml
Normal file
18
temp/RISE-V2G/RISE-V2G-SECC/src/assembly/bin.xml
Normal 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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
14
temp/RISE-V2G/RISE-V2G-SECC/src/main/resources/log4j2.xml
Normal file
14
temp/RISE-V2G/RISE-V2G-SECC/src/main/resources/log4j2.xml
Normal 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 -->
|
||||
Reference in New Issue
Block a user