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