/******************************************************************************* * The MIT License (MIT) * * Copyright 2017 Dr.-Ing. 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 final int MASK = 0x80; 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 most significant bit of v2gTPHeader[4] should never be set. * If it was set, then this would mean that a V2GTP message of a size of at least 2 GB * was intended to be transferred ... and this cannot be, no V2G message has this size. * Since the most significant bit should never be set, we do not need to care about * signed integers in Java at this point! */ if ((getV2gTpHeader()[4] & MASK) == MASK) { stopAndNotify("Payload length of V2GTP message is inappropiately high! There must be " + "an error in the V2GTP message header!", null); break; } else { setPayloadLength(ByteUtils.toIntFromByteArray(Arrays.copyOfRange(getV2gTpHeader(), 4, 8))); setV2gTPPayload(new byte[payloadLength]); 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() { 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; } }