Compare commits

..

No commits in common. "93ac292f72b3275c26717a9c79227bdb67c4a2ce" and "3efc59444f7dc654df4209744e873bf6664b035a" have entirely different histories.

14 changed files with 339 additions and 899 deletions

View File

@ -5,8 +5,8 @@
*/ */
package pitiupi.GUI; package pitiupi.GUI;
import pitiupi.net.tcp.SocketTCP; import pitiupi.net.SocketTCP;
import pitiupi.net.udp.SocketUDP; import pitiupi.net.SocketUDP;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.swing.JMenu; import javax.swing.JMenu;
@ -44,9 +44,7 @@ public class MainWindow extends javax.swing.JFrame {
private SocketTCP socketTCP; private SocketTCP socketTCP;
private String userName; private String userName;
private int port; private int port;
private String pitiupiIdentifier;
private PluginLoader pluginManager; private PluginLoader pluginManager;
private final List<Plugin> plugins; private final List<Plugin> plugins;
@ -60,7 +58,6 @@ public class MainWindow extends javax.swing.JFrame {
public MainWindow(String userName, int port) { public MainWindow(String userName, int port) {
this.userName = userName; this.userName = userName;
this.port = port; this.port = port;
this.pitiupiIdentifier = "Pitiupi.Alice.2026";
this.menuBar = new MenuBar(this); this.menuBar = new MenuBar(this);
this.plugins = new ArrayList<>(); this.plugins = new ArrayList<>();
this.initComponents(); this.initComponents();
@ -130,10 +127,6 @@ public class MainWindow extends javax.swing.JFrame {
return this.userName; return this.userName;
} }
public String getIdentifier(){
return this.pitiupiIdentifier;
}
public void addMenu(JMenu menu) { public void addMenu(JMenu menu) {
this.menuBar.addPlugin(menu); this.menuBar.addPlugin(menu);
} }

View File

@ -1,5 +1,4 @@
package pitiupi.net; package pitiupi.net;
/** /**
* Mensagem utilizada para sinalizar a presença de um usuário na rede. * Mensagem utilizada para sinalizar a presença de um usuário na rede.
* *

View File

@ -0,0 +1,244 @@
package pitiupi.net;
import pitiupi.GUI.MainWindow;
import pitiupi.control.PeerInfo;
import pitiupi.control.PeerListener;
import pitiupi.plugin.Plugin;
import java.io.*;
import java.net.*;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
/**
* Gerencia a comunicação TCP da aplicação.
*
* <p>Mantém conexões persistentes com os peers e reutiliza essas conexões
* para o envio de mensagens. As mensagens recebidas são encaminhadas aos
* plugins, que são responsáveis por interpretá-las.</p>
*
* <p>Novas conexões são aceitas pelo servidor e cada conexão possui uma
* tarefa própria para receber mensagens, permitindo que múltiplas conexões
* sejam mantidas simultaneamente.</p>
*
* <p>As conexões são associadas aos endereços dos peers e podem ser
* encerradas quando um peer deixa de estar ativo.</p>
*
* @author Gustavo
*/
public class SocketTCP extends Thread implements PeerListener {
private final ExecutorService connectionExecutor = Executors.newCachedThreadPool();
private final Map<InetAddress, TcpConnection> connections;
private ServerSocket serverSocket;
private InetAddress address;
private final MainWindow main;
private volatile boolean running = true;
/**
* Cria o servidor TCP da aplicação.
*
* <p>O servidor utiliza a porta definida pela aplicação e inicia sem
* estabelecer conexões com os peers. As conexões são criadas conforme
* necessário durante o envio ou recebidas de outros peers.</p>
*
* @param main janela principal da aplicação.
*/
public SocketTCP(MainWindow main) {
this.main = main;
connections = new ConcurrentHashMap<>();
try {
serverSocket = new ServerSocket(main.getPort());
address = InetAddress.getByName(InetAddress.getLocalHost().getHostAddress());
} catch (IOException ex) {
System.out.println("There is no socket connection. Sorry.");
System.out.println(ex);
}
}
/**
* Envia uma mensagem TCP para um peer.
*
* <p>Uma conexão existente com o peer é reutilizada. Caso não exista uma
* conexão válida, uma nova conexão é criada e adicionada ao conjunto de
* conexões ativas.</p>
*
* @param msg mensagem serializada a ser enviada.
* @param destinationAddress endereço IP do peer destinatário.
*/
public void send(byte[] msg, InetAddress destinationAddress) {
try {
if (destinationAddress.equals(address)) {
throw new IllegalArgumentException("Destination address cannot be the same as the original address.");
}
TcpConnection connec tion;
synchronized (connections) {
connection = connections.get(destinationAddress);
if (connection == null || connection.isClosed()) {
Socket socket = new Socket(destinationAddress, main.getPort());
connection = new TcpConnection(socket);
connections.put(destinationAddress, connection);
startReceiver(destinationAddress, connection);
}
}
connection.send(msg);
} catch (IOException ex) {
System.out.println("Could not send TCP message.");
System.out.println(ex);
}
}
/**
* Aguarda e aceita novas conexões TCP.
*
* <p>Cada conexão aceita é associada ao endereço do peer e registrada para
* que possa ser utilizada tanto para recepção quanto para envio de
* mensagens.</p>
*/
private void receive() {
while (running) {
try {
Socket socket = serverSocket.accept();
InetAddress address = socket.getInetAddress();
TcpConnection connection = new TcpConnection(socket);
TcpConnection oldConnection = connections.put(address, connection);
if (oldConnection != null) {
oldConnection.close();
}
startReceiver(address, connection);
} catch (IOException ex) {
if (!running) {
break;
}
System.out.println("Error accepting TCP connection.");
System.out.println(ex);
}
}
}
/**
* Inicia a recepção de mensagens de uma conexão TCP.
*
* <p>A recepção é executada de forma independente para que uma conexão
* não impeça a aceitação ou o processamento de outras conexões.</p>
*
* <p>Cada mensagem recebida é encaminhada aos plugins da aplicação.</p>
*
* @param address endereço do peer associado à conexão.
* @param connection conexão TCP utilizada para a comunicação.
*/
private void startReceiver(InetAddress address, TcpConnection connection) {
connectionExecutor.submit(() -> {
try {
while (running) {
byte[] message = connection.receive();
for (Plugin plugin : main.getPlugins()) {
plugin.receiveMessage(message);
}
}
} catch (IOException ex) {
System.out.println(
"Connection with " + address.getHostAddress() + " closed."
);
} finally {
connections.remove(address, connection);
try {
connection.close();
} catch (IOException ignored) {
}
}
});
}
/**
* Inicia o processo de recepção de conexões TCP.
*/
@Override
public void run() {
receive();
}
/**
* Encerra o servidor TCP e as conexões associadas.
*
* <p>As conexões existentes são encerradas e o servidor deixa
* de aceitar novas conexões.</p>
*/
public void close() {
running = false;
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException _) {
}
}
for (TcpConnection connection : connections.values()) {
try {
connection.close();
} catch (IOException _) {
}
}
connections.clear();
connectionExecutor.shutdownNow();
}
/**
* Atualiza as conexões de acordo com os peers atualmente ativos.
*
* <p>Conexões associadas a peers que não estão mais ativos são encerradas
* e removidas.</p>
*
* @param peers lista atualizada de peers ativos.
*/
@Override
public void onPeersChanged(List<PeerInfo> peers) {
Set<InetAddress> activePeers = peers.stream()
.map(PeerInfo::getAddress)
.collect(Collectors.toSet());
for (InetAddress address : connections.keySet()) {
if (!activePeers.contains(address)) {
TcpConnection connection = connections.remove(address);
if (connection != null) {
try {
connection.close();
} catch (IOException e) {
}
}
}
}
}
}

View File

@ -3,7 +3,7 @@
* To change this template file, choose Tools | Templates * To change this template file, choose Tools | Templates
* and open the template in the editor. * and open the template in the editor.
*/ */
package pitiupi.net.udp; package pitiupi.net;
import pitiupi.GUI.MainWindow; import pitiupi.GUI.MainWindow;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@ -13,9 +13,6 @@ import java.io.ObjectInputStream;
import java.net.*; import java.net.*;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import pitiupi.net.HeartbeatMessage;
import pitiupi.net.Message;
import pitiupi.plugin.Plugin; import pitiupi.plugin.Plugin;
/** /**
@ -148,15 +145,9 @@ public class SocketUDP extends Thread {
main.getHeartbeatManager().receiveHeartbeat(heartbeat, msgPacket.getAddress()); main.getHeartbeatManager().receiveHeartbeat(heartbeat, msgPacket.getAddress());
continue; continue;
} }
Message message = tryParseMessage(msgPacket.getData());
if (message == null) {
continue;
}
for (Plugin plugin : main.getPlugins()) { for (Plugin plugin : main.getPlugins()) {
System.out.println(msgPacket.getAddress().toString()); System.out.println(msgPacket.getAddress().toString());
plugin.receiveMessage(message); plugin.receiveMessage(msgPacket.getData());
} }
} }
} }
@ -179,20 +170,6 @@ public class SocketUDP extends Thread {
} }
} }
private Message tryParseMessage(byte[] data) {
try {
ByteArrayInputStream bis = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(bis);
Object object = ois.readObject();
if (object instanceof Message message) return message;
return null;
} catch (IOException | ClassNotFoundException ex) {
return null;
}
}
/** /**
* Inicia a recepção de mensagens UDP. * Inicia a recepção de mensagens UDP.
*/ */

View File

@ -0,0 +1,88 @@
package pitiupi.net;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
/**
* Representa uma conexão TCP persistente com um peer.
*
* <p>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.</p>
*
* @author Gustavo
*/
class TcpConnection {
private final Socket socket;
private final DataInputStream input;
private final DataOutputStream output;
/**
* Cria uma conexão a partir de um socket existente.
*
* @param socket socket TCP utilizado pela conexão.
* @throws IOException caso não seja possível obter os streams do socket.
*/
public TcpConnection(Socket socket) throws IOException {
this.socket = socket;
this.input = new DataInputStream(socket.getInputStream());
this.output = new DataOutputStream(socket.getOutputStream());
}
/**
* Envia uma mensagem pela conexão.
*
* <p>A mensagem é precedida por seu tamanho para permitir que o receptor
* identifique o limite entre mensagens transmitidas pela mesma conexão.</p>
*
* @param message mensagem serializada a ser enviada.
* @throws IOException caso ocorra um erro durante o envio.
*/
public void send(byte[] message) throws IOException {
synchronized (output) {
output.writeInt(message.length);
output.write(message);
output.flush();
}
}
/**
* Recebe uma mensagem completa da conexão.
*
* <p>O tamanho da mensagem é lido primeiro e, em seguida, os dados
* correspondentes são recebidos.</p>
*
* @return mensagem recebida.
* @throws IOException caso ocorra um erro durante a recepção.
*/
public byte[] receive() throws IOException {
int length = input.readInt();
if (length < 0) {
throw new IOException("Invalid message length.");
}
return input.readNBytes(length);
}
/**
* Verifica se o socket da conexão está fechado.
*
* @return {@code true} caso o socket esteja fechado.
*/
public boolean isClosed() {
return socket.isClosed();
}
/**
* Encerra a conexão TCP.
*
* @throws IOException caso ocorra um erro ao fechar o socket.
*/
public void close() throws IOException {
socket.close();
}
}

View File

@ -1,138 +0,0 @@
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;
}
}

View File

@ -1,160 +0,0 @@
package pitiupi.net.tcp;
import pitiupi.control.PeerInfo;
import pitiupi.control.PeerListener;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
public class ConnectionManager implements PeerListener {
private final Logger logger;
private final Map<InetAddress, TcpConnection> publicConnections;
private final Map<InetAddress, List<TcpConnection>> privateConnections;
public ConnectionManager() {
publicConnections = new ConcurrentHashMap<>();
privateConnections = new ConcurrentHashMap<>();
logger = Logger.getLogger(getClass().getName());
}
public TcpConnection getPublicConnection(InetAddress address) {
return publicConnections.get(address);
}
public void addPrivateConnection(InetAddress address, TcpConnection connection) {
List<TcpConnection> connections =
privateConnections.computeIfAbsent(connection.getAddress(), _ -> new ArrayList<>());
synchronized (connections) {
connections.add(connection);
}
}
public void addPublicConnection(InetAddress address, TcpConnection connection) {
TcpConnection oldConnection = publicConnections.put(connection.getAddress(), connection);
if (oldConnection != null && oldConnection != connection) {
closeConnection(oldConnection);
}
}
public void closeConnection(TcpConnection connection, String reason) {
SysInfoMessage.sendSystemInfo(connection, SysInfoType.CONNECTION_CLOSED, reason);
closeConnection(connection);
}
public void closeConnection(TcpConnection connection) {
if (connection == null) return;
if (connection.isPrivate()) {
List<TcpConnection> connections = privateConnections.get(connection.getAddress());
if (connections != null) {
synchronized (connections) {
connections.remove(connection);
if (connections.isEmpty()) privateConnections.remove(connection.getAddress(), connections);
}
}
}
else publicConnections.remove(connection.getAddress(), connection);
try {
if (!connection.isClosed()) {
connection.close();
}
} catch (IOException ex) {
logger.log(Level.SEVERE, "Error closing TCP connection", ex);
}
}
public void closeByAdress(InetAddress address, String reason) {
List<TcpConnection> connections = privateConnections.get(address);
for (TcpConnection connection : connections) {
SysInfoMessage.sendSystemInfo(connection, SysInfoType.CONNECTION_CLOSED, reason);
}
closeByAdress(address);
}
public void closeByAdress(InetAddress address) {
TcpConnection publicConnection = publicConnections.remove(address);
if (publicConnection != null) {
try {
publicConnection.close();
}
catch (IOException ex) {
logger.log(Level.SEVERE, "Error closing TCP connection", ex);
}
}
List<TcpConnection> connections = privateConnections.remove(address);
if (connections != null) {
synchronized (connections) {
for (TcpConnection connection : connections) {
try {
connection.close();
}
catch (IOException ex) {
logger.log(Level.SEVERE, "Error closing TCP connection", ex);
}
}
connections.clear();
}
}
}
public void closeAllConnections(String reason) {
for (List<TcpConnection> connections : privateConnections.values()) {
for (TcpConnection connection : connections) {
SysInfoMessage.sendSystemInfo(connection, SysInfoType.CONNECTION_CLOSED, reason);
}
}
closeAllConnections();
}
public void closeAllConnections() {
for (TcpConnection connection : publicConnections.values()) {
closeConnection(connection);
}
synchronized (privateConnections) {
for (List<TcpConnection> connectionsList : privateConnections.values()) {
for (TcpConnection connection : connectionsList) {
closeConnection(connection);
}
privateConnections.clear();
}
}
}
/**
* Atualiza as conexões de acordo com os peers atualmente ativos.
*
* <p>Conexões associadas a peers que não estão mais ativos são encerradas
* e removidas.</p>
*
* @param peers lista atualizada de peers ativos.
*/
@Override
public void onPeersChanged(List<PeerInfo> peers) {
Set<InetAddress> activePeers = peers.stream().map(PeerInfo::getAddress).collect(Collectors.toSet());
for (InetAddress address : publicConnections.keySet()) {
if (!activePeers.contains(address)) {
closeByAdress(address);
}
}
for (InetAddress address : privateConnections.keySet()) {
if (!activePeers.contains(address)) {
closeByAdress(address);
}
}
}
}

View File

@ -1,30 +0,0 @@
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);
}
}

View File

@ -1,344 +0,0 @@
package pitiupi.net.tcp;
import pitiupi.GUI.MainWindow;
import pitiupi.net.Message;
import pitiupi.plugin.Plugin;
import pitiupi.plugin.PrivateConnectionPlugin;
import javax.swing.*;
import java.io.*;
import java.net.*;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Gerencia a comunicação TCP da aplicação.
*
* <p>Mantém conexões persistentes com os peers e reutiliza essas conexões
* para o envio de mensagens. As mensagens recebidas são encaminhadas aos
* plugins, que são responsáveis por interpretá-las.</p>
*
* <p>Novas conexões são aceitas pelo servidor e cada conexão possui uma
* tarefa própria para receber mensagens, permitindo que múltiplas conexões
* sejam mantidas simultaneamente.</p>
*
* <p>As conexões são associadas aos endereços dos peers e podem ser
* encerradas quando um peer deixa de estar ativo.</p>
*
* @author Gustavo
*/
public class SocketTCP extends Thread {
private final Logger logger;
private final ExecutorService connectionExecutor = Executors.newCachedThreadPool();
private final ConnectionManager connectionManager;
private ServerSocket serverSocket;
private InetAddress address;
private final MainWindow main;
private volatile boolean running = true;
/**
* Cria o servidor TCP da aplicação.
*
* <p>O servidor utiliza a porta definida pela aplicação e inicia sem
* estabelecer conexões com os peers. As conexões são criadas conforme
* necessário durante o envio ou recebidas de outros peers.</p>
*
* @param main janela principal da aplicação.
*/
public SocketTCP(MainWindow main) {
this.main = main;
logger = Logger.getLogger(getClass().getName());
connectionManager = new ConnectionManager();
try {
serverSocket = new ServerSocket(main.getPort());
address = InetAddress.getByName(InetAddress.getLocalHost().getHostAddress());
} catch (IOException ex) {
System.out.println("There is no socket connection. Sorry.");
System.out.println(ex.toString());
}
}
public void send(byte[] msg, InetAddress destinationAddress) {
try {
TcpConnection connection = getPublicConnection(destinationAddress, main.getIdentifier(), main.getIdentifier());
if (connection == null) {
System.out.println("Could not establish TCP connection.");
return;
}
connection.send(msg);
} catch (Exception ex) {
System.out.println("Could not send TCP message.");
System.out.println(ex.toString());
}
}
public TcpConnection createPrivateConnection(InetAddress destinationAddress, String senderId, String receiverId) {
return establishOutgoingConnection(destinationAddress, senderId, receiverId, true);
}
private synchronized TcpConnection getPublicConnection(InetAddress destinationAddress, String senderId, String receiverId) {
TcpConnection connection;
connection = connectionManager.getPublicConnection(destinationAddress);
if (connection != null && !connection.isClosed()) return connection;
connection = establishOutgoingConnection(destinationAddress, senderId, receiverId, false);
return connection;
}
/**
* Aguarda e aceita novas conexões TCP.
*
* <p>Cada conexão aceita é associada ao endereço do peer e registrada para
* que possa ser utilizada tanto para recepção quanto para envio de
* mensagens.</p>
*/
private void receive() {
while (running) {
try {
Socket socket = serverSocket.accept();
acceptIncomingConnection(socket);
} catch (IOException ex) {
if (!running) {
break;
}
System.out.println("Error accepting TCP connection.");
System.out.println(ex.toString());
}
}
}
private TcpConnection establishOutgoingConnection(InetAddress destinationAddress, String sender, String receiver, boolean isPrivate) {
if (address.equals(destinationAddress)) {
System.out.println("Failed to get socket.");
throw new IllegalArgumentException("Connections to the local host are forbidden.");
}
TcpConnection connection = ConnectionFactory.createConnection(destinationAddress, main.getPort(), sender, receiver, isPrivate);
if (connection == null) return null;
saveConnection(connection);
connectionExecutor.submit(() -> {
startReceiver(connection, false);
});
return connection;
}
private void acceptIncomingConnection(Socket socket) {
connectionExecutor.submit(() -> {
TcpConnection connection = ConnectionFactory.receiveConnection(socket);
if (connection == null) return;
saveConnection(connection);
startReceiver(connection, true);
});
}
private void saveConnection(TcpConnection connection) {
if (connection.isPrivate()) {
connectionManager.addPrivateConnection(connection.getAddress(), connection);
}
else {
connectionManager.addPublicConnection(connection.getAddress(), connection);
}
}
private void startReceiver(TcpConnection connection, boolean notify) {
try {
if (!connection.isPrivate()) {
publicConnectionReceiver(connection);
return;
}
if (connection.getReceiver().equals(main.getIdentifier())) {
systemConnectionReceiver(connection);
return;
}
Plugin receiver = findConnectionReceiver(connection);
if (receiver instanceof PrivateConnectionPlugin plugin) {
if (notify) {
plugin.receivePrivateConnection(connection);
}
privateConnectionReceiver(connection, plugin);
}
}
catch (IOException ex) {
logger.log(Level.SEVERE, null, ex);
}
}
private Plugin findConnectionReceiver(TcpConnection connection) {
Plugin receiver = null;
for(Plugin plugin : main.getPlugins()) {
if (plugin.getIdentifier().equals(connection.getReceiver())) {
receiver = plugin;
break;
}
}
if (receiver == null) {
SysInfoMessage.sendSystemInfo(
connection,
SysInfoType.RECEIVER_NOT_FOUND,
"This peer does not have the plugin: " + connection.getReceiver()
);
}
return receiver;
}
private void publicConnectionReceiver(TcpConnection connection) {
while (running) {
try {
byte[] bytes = connection.receive();
try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis)) {
Object object = ois.readObject();
if (object instanceof Message message) {
SysInfoMessage sysMessage = SysInfoMessage.parseSystemInfo(message);
if (sysMessage == null) {
for (Plugin plugin : main.getPlugins()) {
plugin.receiveMessage(message);
}
continue;
}
switch (sysMessage.getType()) {
case CONNECTION_CLOSED, INTERNAL_ERROR -> {
connectionManager.closeConnection(connection);
return;
}
case MESSAGE_REJECTED -> {
logger.log(Level.SEVERE, "Somehow sent something that was not an Message");
}
default -> {}
}
}
else {
SysInfoMessage.sendSystemInfo(
connection,
SysInfoType.MESSAGE_REJECTED,
"Public messages must be instances of Message"
);
}
}
catch (ClassNotFoundException ex) {
SysInfoMessage.sendSystemInfo(
connection,
SysInfoType.INTERNAL_ERROR,
"class Message not found."
);
logger.log(Level.SEVERE, null, ex);
connectionManager.closeConnection(connection);
}
}
catch (SocketTimeoutException ex) {
if (connection.hasTimedOut()) {
connectionManager.closeConnection(connection, "Connection timeout");
return;
}
}
catch (IOException ex) {
connectionManager.closeConnection(connection);
}
}
}
private void privateConnectionReceiver(TcpConnection connection, Plugin plugin) {
if (plugin instanceof PrivateConnectionPlugin privatePlugin) {
while (running) {
try {
byte[] message = connection.receive();
SysInfoMessage sysMessage = SysInfoMessage.parseSystemInfo(message);
if (sysMessage == null) {
privatePlugin.receiveBytes(message);
continue;
}
switch (sysMessage.getType()) {
case CONNECTION_CLOSED, INTERNAL_ERROR -> {
privatePlugin.onSocketClosed(connection);
connectionManager.closeConnection(connection);
}
default -> {
continue;
}
}
}
catch (SocketTimeoutException ex) {
if (connection.hasTimedOut()) {
connectionManager.closeConnection(connection, "Connection timeout");
return;
}
}
catch (IOException ex) {
connectionManager.closeConnection(connection);
}
}
}
}
private void systemConnectionReceiver(TcpConnection connection) throws IOException {
while (running) {
try {
byte[] message = connection.receive();
try (ByteArrayInputStream bis = new ByteArrayInputStream(message);
ObjectInputStream ois = new ObjectInputStream(bis)) {
SysInfoMessage sysMessage = SysInfoMessage.parseSystemInfo(message);
if (sysMessage != null) continue;
//TODO: Ainda não existem mensagens trocadas entre o sistema.
}
} catch (SocketTimeoutException ex) {
if (connection.hasTimedOut()) return;
}
}
}
public void disconnect(TcpConnection connection, String description) {
connectionManager.closeConnection(
connection,
Objects.requireNonNullElse(description, "No reason provided")
);
}
@Override
public void run() {
receive();
}
public void close() {
running = false;
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException ex) {
logger.log(Level.SEVERE, null, ex);
}
}
connectionManager.closeAllConnections("The peer must have closed the application or changed ports");
connectionExecutor.shutdownNow();
}
}

View File

@ -1,58 +0,0 @@
package pitiupi.net.tcp;
import pitiupi.net.Message;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
public class SysInfoMessage extends Message {
private final SysInfoType type;
private final String message;
public SysInfoMessage(String message, SysInfoType type) {
this.message = message;
this.type = type;
}
public String getMessage() {
return message;
}
public SysInfoType getType() {
return type;
}
public static void sendSystemInfo(TcpConnection connection, SysInfoType type, String description) {
try {
SysInfoMessage error = new SysInfoMessage(description, type);
connection.send(error.toByteArray());
} catch (IOException ex) {
Logger.getLogger(SysInfoMessage.class.getName()).log(Level.WARNING, null, ex);
}
}
public static SysInfoMessage parseSystemInfo(byte[] bytes) {
try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis)) {
Object object = ois.readObject();
if (object instanceof SysInfoMessage) {
return (SysInfoMessage) object;
}
} catch (IOException | ClassNotFoundException ex) {
Logger.getLogger(SysInfoMessage.class.getName()).log(Level.WARNING, null, ex);
}
return null;
}
public static SysInfoMessage parseSystemInfo(Message message) {
if (message instanceof SysInfoMessage) {
return (SysInfoMessage) message;
}
return null;
}
}

View File

@ -1,9 +0,0 @@
package pitiupi.net.tcp;
public enum SysInfoType {
CONNECTION_REJECTED,
RECEIVER_NOT_FOUND,
MESSAGE_REJECTED,
INTERNAL_ERROR,
CONNECTION_CLOSED,
}

View File

@ -1,111 +0,0 @@
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.
*
* <p>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.</p>
*
* @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;
}
}

View File

@ -4,9 +4,9 @@
*/ */
package pitiupi.plugin; package pitiupi.plugin;
import javax.swing.JMenu;
import pitiupi.GUI.MainWindow; import pitiupi.GUI.MainWindow;
import pitiupi.net.Message; import pitiupi.net.Message;
import pitiupi.net.tcp.TcpConnection;
/** /**
* Interface base para plugins da aplicação. * Interface base para plugins da aplicação.
@ -21,7 +21,6 @@ public interface Plugin {
public String getName(); public String getName();
public String getAuthor(); public String getAuthor();
public String getVersion(); public String getVersion();
public String getIdentifier();
/** /**
* Inicializa o plugin. * Inicializa o plugin.
@ -47,6 +46,5 @@ public interface Plugin {
* *
* @param message mensagem serializada recebida. * @param message mensagem serializada recebida.
*/ */
public void receiveMessage(Message message); public void receiveMessage(byte[] message);
} }

View File

@ -1,9 +0,0 @@
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);
}