RISE-V2G/RISE-V2G-SECC/src/main/java/com/v2gclarity/risev2g/secc/transportLayer/ConnectionHandler.java

331 lines
9.6 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 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.
2015-06-01 08:19:09 -07:00
*******************************************************************************/
package com.v2gclarity.risev2g.secc.transportLayer;
2015-06-01 08:19:09 -07:00
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;
2015-06-01 08:19:09 -07:00
import javax.net.ssl.SSLSocket;
2015-06-01 08:19:09 -07:00
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;
2015-06-01 08:19:09 -07:00
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);
2015-06-01 08:19:09 -07:00
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()) {
- 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().debug("Closing connection to client ...");
2015-06-01 08:19:09 -07:00
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;
}
}