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

267 lines
12 KiB
Java
Raw Normal View History

2015-06-01 08:19:09 -07:00
/*******************************************************************************
* The MIT License (MIT)
2015-06-01 08:19:09 -07:00
*
* 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.
2015-06-01 08:19:09 -07:00
*******************************************************************************/
package com.v2gclarity.risev2g.secc.session;
2015-06-01 08:19:09 -07:00
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;
2015-06-01 08:19:09 -07:00
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 {
- Replaced System.currentTimeMillis() by System.nanoTime() for timing tasks. - Changed the way how JAXBElements are created due to particularities with the creation of the XML Reference elements for the CertificateInstallationRes and CertificateUpdateRes messages. The JAXBContext is now set exactly for the message or field that is to be marshaled into XML. As a result, the JAXBContext is no more set in the EXI codec, but in the MessageHandler - SecurityUtils.java has been partly rewritten with regards to the verification of certificate chains. See also verifyCertificateChain(…) method. - Requirement [V2G2-812] is not implemented. - When the EVCC sends a message out of the correct order which would induce a FAILED_SequenceError message, the SECC now sends the response message corresponding to the request message sent by the EVCC instead of the response message corresponding to the message the SECC would expect. As a result, ServerState.java has partly been rewritten and all SECC states have been adapted to it. - Charge parameter provided by ChargeParameterDisoveryReq message are now thoroughly checked by the SECC. See also verifyChargeParameter(…) method. - Charging profile provided by PowerDeliveryReq message is now thoroughly checked by SECC. See also isChargingProfileValid(…) method. - New enum class PKI is added to shared.enumerations package. Needed for certificate chain checks in SecurityUtils.java. - Some bugfixes in V2GTPMessage so that headers are not checked correctly.
2017-08-20 14:26:22 -07:00
getLogger().warn("Incoming DatagramPacket could not be identified as an SECCDiscoveryReq");
}
} catch (NullPointerException e) {
getLogger().error("NullPointerException occurred while processing SECCDiscoveryReq", e);
2015-06-01 08:19:09 -07:00
}
}
/**
* 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" );
2015-06-01 08:19:09 -07:00
} 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;
}
}