diff --git a/pitiupi/src/pitiupi/net/tcp/ConnectionFactory.java b/pitiupi/src/pitiupi/net/tcp/ConnectionFactory.java new file mode 100644 index 0000000..0a3f098 --- /dev/null +++ b/pitiupi/src/pitiupi/net/tcp/ConnectionFactory.java @@ -0,0 +1,138 @@ +package pitiupi.net.tcp; + +import pitiupi.net.Message; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ConnectionFactory { + private static final int HANDSHAKE_TIMEOUT = 5000; + private static final int STANDARD_TIMEOUT = 300_000; + + public static TcpConnection createConnection(InetAddress address, int port, String sender, String receiver, boolean isPrivate) { + try { + TcpConnection connection; + + Socket socket = new Socket(address, port); + connection = new TcpConnection(socket); + connection.setSender(sender); + connection.setReceiver(receiver); + connection.setPrivate(isPrivate); + + HandshakeMessage handshake = new HandshakeMessage(isPrivate, sender, receiver); + + connection.send(handshake.toByteArray()); + HandshakeMessage handshakeResponse = validateConnection(connection); + + if (handshakeResponse == null) return null; + + connection.setTimeout(STANDARD_TIMEOUT); + + return connection; + } + catch (IOException ex) { + Logger.getLogger(ConnectionFactory.class.getName()).log(Level.SEVERE, null, ex); + } + return null; + } + + public static TcpConnection receiveConnection(Socket socket) { + try { + TcpConnection connection; + + connection = new TcpConnection(socket); + + HandshakeMessage handshake = validateConnection(connection); + + if (handshake == null) return null; + + connection.setSender(handshake.getSender()); + connection.setReceiver(handshake.getReceiver()); + connection.setPrivate(handshake.isPrivate()); + + connection.setTimeout(STANDARD_TIMEOUT); + + return connection; + } + catch (IOException ex) { + Logger.getLogger(ConnectionFactory.class.getName()).log(Level.SEVERE, null, ex); + } + return null; + } + + private static HandshakeMessage validateConnection(TcpConnection connection) { + try { + connection.setTimeout(HANDSHAKE_TIMEOUT); + byte[] bytes = connection.receive(); + + ByteArrayInputStream bis = new ByteArrayInputStream(bytes); + ObjectInputStream ois = new ObjectInputStream(bis); + + Object object = ois.readObject(); + + if (object instanceof Message message) { + if (SysInfoMessage.parseSystemInfo(message) != null) { + //TODO: Tratar erros + return null; + } + + if (message instanceof HandshakeMessage handshake) { + return (validateHandshake(handshake, connection)) ? handshake : null; + } + } + + SysInfoMessage.sendSystemInfo( + connection, + SysInfoType.CONNECTION_REJECTED, + "The message was not from an allowed type." + ); + } + catch (SocketTimeoutException ex) { + SysInfoMessage.sendSystemInfo( + connection, + SysInfoType.CONNECTION_REJECTED, + "No handshake was received within the allowed time." + ); + } + catch (ClassNotFoundException | IOException ex) { + Logger.getLogger(ConnectionFactory.class.getName()).log(Level.SEVERE, null, ex); + + SysInfoMessage.sendSystemInfo( + connection, + SysInfoType.INTERNAL_ERROR, + "The server could not process the handshake." + ); + } + + return null; + } + + private static boolean validateHandshake(HandshakeMessage handshake, TcpConnection connection) { + String sender = handshake.getSender(); + String receiver = handshake.getReceiver(); + + if (sender == null || sender.isBlank()) { + SysInfoMessage.sendSystemInfo( + connection, + SysInfoType.CONNECTION_REJECTED, + "Invalid Sender: " + sender + "." + ); + return false; + } + if (receiver == null || receiver.isBlank()) { + SysInfoMessage.sendSystemInfo( + connection, + SysInfoType.CONNECTION_REJECTED, + "Invalid Receiver: " + receiver + "." + ); + return false; + } + return true; + } +} diff --git a/pitiupi/src/pitiupi/net/tcp/HandshakeMessage.java b/pitiupi/src/pitiupi/net/tcp/HandshakeMessage.java new file mode 100644 index 0000000..d18b593 --- /dev/null +++ b/pitiupi/src/pitiupi/net/tcp/HandshakeMessage.java @@ -0,0 +1,30 @@ +package pitiupi.net.tcp; + +import pitiupi.net.Message; + +class HandshakeMessage extends Message { + private final boolean isPrivate; + private final String sender; + private final String receiver; + + + public HandshakeMessage(boolean isPrivate, String sender, String receiver) { + this.isPrivate = isPrivate; + this.sender = sender; + this.receiver = receiver; + } + + public boolean isPrivate() { + return isPrivate; + } + public String getSender() { + return sender; + } + public String getReceiver() { + return receiver; + } + + public HandshakeMessage createResponse() { + return new HandshakeMessage(isPrivate, receiver, sender); + } +} diff --git a/pitiupi/src/pitiupi/net/tcp/TcpConnection.java b/pitiupi/src/pitiupi/net/tcp/TcpConnection.java new file mode 100644 index 0000000..947cb25 --- /dev/null +++ b/pitiupi/src/pitiupi/net/tcp/TcpConnection.java @@ -0,0 +1,111 @@ +package pitiupi.net.tcp; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketException; + +/** + * Representa uma conexão TCP persistente com um peer. + * + *
Encapsula o socket e os streams utilizados para enviar e receber + * mensagens. Também é responsável pelo framing das mensagens, permitindo + * que várias mensagens sejam transmitidas pela mesma conexão.
+ * + * @author Gustavo + */ +public class TcpConnection { + private static final long TIMEOUT = 300_000; // 5 minutos + + private final Socket socket; + private final DataInputStream input; + private final DataOutputStream output; + private final InetAddress address; + private String sender; + private String receiver; + + private boolean isPrivate; + + private volatile long lastActivity; + + TcpConnection(Socket socket) throws IOException { + this.socket = socket; + this.input = new DataInputStream(socket.getInputStream()); + this.output = new DataOutputStream(socket.getOutputStream()); + this.address = socket.getInetAddress(); + this.lastActivity = System.currentTimeMillis(); + } + + public synchronized void send(byte[] message) throws IOException { + output.writeInt(message.length); + output.write(message); + output.flush(); + updateLastActivity(); + } + + byte[] receive() throws IOException { + int length = input.readInt(); + + if (length < 0) throw new IOException("Invalid message length."); + + byte[] message = input.readNBytes(length); + + if (message.length != length) { + throw new IOException("Connection closed before receiving the complete message."); + } + + updateLastActivity(); + + return message; + } + + public boolean isClosed() { + return socket.isClosed(); + } + + void close() throws IOException { + socket.close(); + } + + public InetAddress getAddress() { + return address; + } + + private void updateLastActivity() { + lastActivity = System.currentTimeMillis(); + } + + void setTimeout(int timeout) throws SocketException { + socket.setSoTimeout(timeout); + } + + boolean hasTimedOut() { + return System.currentTimeMillis() - lastActivity >= TIMEOUT; + } + + public String getReceiver() { + return receiver; + } + + public String getSender() { + return sender; + } + + public boolean isPrivate() { + return isPrivate; + } + + void setSender(String sender) { + this.sender = sender; + } + + void setReceiver(String receiver) { + this.receiver = receiver; + } + + void setPrivate(boolean aPrivate) { + isPrivate = aPrivate; + } +} diff --git a/pitiupi/src/pitiupi/plugin/PrivateConnectionPlugin.java b/pitiupi/src/pitiupi/plugin/PrivateConnectionPlugin.java new file mode 100644 index 0000000..0ddd738 --- /dev/null +++ b/pitiupi/src/pitiupi/plugin/PrivateConnectionPlugin.java @@ -0,0 +1,9 @@ +package pitiupi.plugin; + +import pitiupi.net.tcp.TcpConnection; + +public interface PrivateConnectionPlugin extends Plugin { + public void receivePrivateConnection(TcpConnection connection); + public void receiveBytes(byte[] bytes); + public void onSocketClosed(TcpConnection connection); +}