RISE-V2G/RISE-V2G-SECC/src/main/java/com/v2gclarity/risev2g/secc/session/V2GCommunicationSessionHand...

267 lines
12 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.secc.session;
import java.net.DatagramPacket;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import 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.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 Threads runnable object (ConnectionHandler).
*/
private static HashMap<ConnectionHandler, Thread> connectionHandlerMap;
private MessageHandler messageHandler;
private V2GTPMessage v2gTpMessage;
private byte security;
public V2GCommunicationSessionHandlerSECC() {
// Tell the respective transport layer Observables to notify this session handler
UDPServer.getInstance().addObserver(this);
TCPServer.getInstance().addObserver(this);
TLSServer.getInstance().addObserver(this);
// Maps IP addresses of the clients given as a String to V2GCommunicationSessionSECC objects
setV2gCommunicationSessions(new HashMap<String, V2GCommunicationSessionSECC>());
// Maps ConnectionHandlers to their respective running threads
setConnectionHandlerMap(new HashMap<ConnectionHandler, Thread>());
setMessageHandler(new MessageHandler());
}
@Override
public void update(Observable obs, Object obj) {
if (obs instanceof UDPServer && obj instanceof DatagramPacket) {
processSECCDiscoveryReq((DatagramPacket) obj);
} else if ((obs instanceof TCPServer || obs instanceof TLSServer) && obj instanceof ConnectionHandler) {
String ipAddress = ((ConnectionHandler) obj).getAddress();
if (getV2gCommunicationSessions().containsKey(ipAddress)) {
/*
* Assign the new ConnectionHandler to the respective existing V2GCommunicationSessionSECC.
* This way the V2GCommunicationSessionSECC knows to which socket to write to when
* sending messages and from which socket to read from when receiving messages.
*
* This if-clause is executed as soon as an EV resumes a previously paused charging
* session. Before pausing, the TCP/TLS socket has been closed, but the charging session
* data object (V2GCommunicationSessionSECC) needed to be kept alive in order to later
* on continue a charging session with the saved data.
* Important!
* The connectionHandler thread must not be started (will start reading the incoming bytes)
* before the V2GCommunicationSessionSECC object is instantiated, otherwise it may lead to
* race conditions.
*/
V2GCommunicationSessionSECC continuedSession = getV2gCommunicationSessions().get(ipAddress);
continuedSession.setConnectionHandler((ConnectionHandler) obj);
continuedSession.setTlsConnection((obs instanceof TLSServer) ? true : false);
((ConnectionHandler) obj).addObserver(getV2gCommunicationSessions().get(ipAddress));
manageConnectionHandlers((ConnectionHandler) obj);
} else {
V2GCommunicationSessionSECC newSession = new V2GCommunicationSessionSECC((ConnectionHandler) obj);
newSession.setTlsConnection((obs instanceof TLSServer) ? true : false);
newSession.addObserver(this);
getV2gCommunicationSessions().put(ipAddress, newSession);
manageConnectionHandlers((ConnectionHandler) obj);
}
} else if (obs instanceof V2GCommunicationSessionSECC && obj instanceof TerminateSession) {
// Remove the V2GCommunicationSessionSECC instance from the hashmap
String ipAddress = ((V2GCommunicationSessionSECC) obs).getConnectionHandler().getAddress();
getV2gCommunicationSessions().remove(ipAddress);
stopConnectionHandler(((V2GCommunicationSessionSECC) obs).getConnectionHandler());
} else {
getLogger().warn("Notification received, but sending entity or received object not identifiable");
}
}
private void manageConnectionHandlers(ConnectionHandler connectionHandler) {
Thread connectionHandlerThread = new Thread(connectionHandler);
connectionHandlerThread.setDaemon(true);
connectionHandlerThread.setName("ConnectionThread " + connectionHandler.getAddress());
connectionHandlerThread.start();
getConnectionHandlerMap().put(connectionHandler, connectionHandlerThread);
}
private void processSECCDiscoveryReq(DatagramPacket udpClientPacket) {
setV2gTpMessage(new V2GTPMessage(udpClientPacket.getData()));
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) {
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\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 void removeV2GCommunicationSession(InetAddress requesterAddress) {
getV2gCommunicationSessions().remove(getV2gCommunicationSessions().get(requesterAddress));
}
public HashMap<String, V2GCommunicationSessionSECC> getV2gCommunicationSessions() {
return v2gCommunicationSessions;
}
public void setV2gCommunicationSessions(
HashMap<String, V2GCommunicationSessionSECC> v2gCommunicationSessions) {
this.v2gCommunicationSessions = v2gCommunicationSessions;
}
public Logger getLogger() {
return logger;
}
public void setLogger(Logger logger) {
this.logger = logger;
}
public MessageHandler getMessageHandler() {
return messageHandler;
}
public void setMessageHandler(MessageHandler messageHandler) {
this.messageHandler = messageHandler;
}
public V2GTPMessage getV2gTpMessage() {
return v2gTpMessage;
}
public void setV2gTpMessage(V2GTPMessage v2gTpMessage) {
this.v2gTpMessage = v2gTpMessage;
}
public static HashMap<ConnectionHandler, Thread> getConnectionHandlerMap() {
return connectionHandlerMap;
}
public static void setConnectionHandlerMap(HashMap<ConnectionHandler, Thread> connectionHandlerMap) {
V2GCommunicationSessionHandlerSECC.connectionHandlerMap = connectionHandlerMap;
}
public byte getSecurity() {
return security;
}
public void setSecurity(byte security) {
this.security = security;
}
}