WIP: Refatoração pronta, falta testes

This commit is contained in:
GustavoHMDS committed 2026-10-09 03:18:04 -03:00
1 parent a628490fcb
commit e25d3a425d
41 files changed
+870 -1363

No files matched your search

-1
View File
@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="openjdk-27" project-jdk-type="JavaSDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="openjdk-27" project-jdk-type="JavaSDK" />
</project> </project>
Generated
+1
View File
@@ -2,5 +2,6 @@
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" /> <mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$/udpTest" vcs="Git" />
</component> </component>
</project> </project>
+85
View File
@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="e2cdacc6-69cd-4190-bef1-27719e32140a" name="Changes" comment="">
<change beforePath="$PROJECT_DIR$/../.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../.idea/misc.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../.idea/vcs.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../.idea/vcs.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/GUI/PrivateChatWindow.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/GUI/PrivateChatWindow.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/GUI/PublicChatWindow.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/GUI/PublicChatWindow.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/plugin/MultiProtocolChat.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/plugin/MultiProtocolChat.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/.idea/misc.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/build/built-jar.properties" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/build/built-jar.properties" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/build/classes/pitiupi/screenCast/GUI/ScreenCastMenu.class" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/build/classes/pitiupi/screenCast/GUI/ScreenCastMenu.class" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/build/classes/pitiupi/screenCast/GUI/ScreenCastWindow.class" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/build/classes/pitiupi/screenCast/GUI/ScreenCastWindow.class" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/build/classes/pitiupi/screenCast/net/ScreenCastMessage.class" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/build/classes/pitiupi/screenCast/net/ScreenCastMessage.class" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/build/classes/pitiupi/screenCast/plugin/ScreenCastPlugin.class" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/build/classes/pitiupi/screenCast/plugin/ScreenCastPlugin.class" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi.screenCast.iml" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi.screenCast.iml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi/screenCast/GUI/ScreenCastMenu.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi/screenCast/GUI/ScreenCastMenu.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi/screenCast/GUI/ScreenCastWindow.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi/screenCast/GUI/ScreenCastWindow.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi/screenCast/net/ScreenCastMessage.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi/screenCast/net/ScreenCastMessage.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi/screenCast/plugin/ScreenCastPlugin.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi/screenCast/plugin/ScreenCastPlugin.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../pitiupi/src/pitiupi/control/plugins/PluginManager.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi/src/pitiupi/control/plugins/PluginManager.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../pitiupi/src/pitiupi/net/tcp/SocketTCP.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi/src/pitiupi/net/tcp/SocketTCP.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../pitiupi/src/pitiupi/net/tcp/TcpConnection.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi/src/pitiupi/net/tcp/TcpConnection.java" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Class" />
</list>
</option>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 6
}</component>
<component name="ProjectId" id="3K7MYfvmfmI7sWODbOakShpPyuR" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;ModuleVcsDetector.initialDetectionPerformed&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252&quot;: &quot;true&quot;,
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;update/mpChat&quot;,
&quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;,
&quot;kotlin-language-version-configured&quot;: &quot;true&quot;,
&quot;last_opened_file_path&quot;: &quot;C:/Users/extre/OneDrive/Documentos/Faculdade/2026_02/Monitoria/Pitiupi/pitiupi.mpChat&quot;,
&quot;project.structure.last.edited&quot;: &quot;Modules&quot;,
&quot;project.structure.proportion&quot;: &quot;0.15&quot;,
&quot;project.structure.side.proportion&quot;: &quot;0.2&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;preferences.keymap&quot;
}
}</component>
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-jdk-9823dce3aa75-bf35d07a577b-intellij.indexing.shared.core-IU-252.28539.97" />
</set>
</attachedChunks>
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="e2cdacc6-69cd-4190-bef1-27719e32140a" name="Changes" comment="" />
<created>1790904201925</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1790904201925</updated>
</task>
<servers />
</component>
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/pitiupi.screenCast.iml" filepath="$PROJECT_DIR$/.idea/pitiupi.screenCast.iml" />
</modules>
</component>
</project>
+16 -18
View File
@@ -5,10 +5,9 @@
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="083e72ce-fa9b-4ddf-a0ef-24105aa3eabb" name="Changes" comment=""> <list default="true" id="083e72ce-fa9b-4ddf-a0ef-24105aa3eabb" name="Changes" comment="">
<change afterPath="$PROJECT_DIR$/../pitiupi.testSocket/Makefile" afterDir="false" /> <change beforePath="$PROJECT_DIR$/../.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../.idea/misc.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/../.idea/vcs.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../.idea/vcs.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../pitiupi/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi/.idea/misc.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../pitiupi/src/pitiupi/net/tcp/ConnectionFactory.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi/src/pitiupi/net/tcp/ConnectionFactory.java" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -34,7 +33,7 @@
"RunOnceActivity.git.unshallow": "true", "RunOnceActivity.git.unshallow": "true",
"RunOnceActivity.typescript.service.memoryLimit.init": "true", "RunOnceActivity.typescript.service.memoryLimit.init": "true",
"codeWithMe.voiceChat.enabledByDefault": "false", "codeWithMe.voiceChat.enabledByDefault": "false",
"git-widget-placeholder": "feature/separacao-gerenciadores", "git-widget-placeholder": "update/screenCast",
"ignore.virus.scanning.warn.message": "true", "ignore.virus.scanning.warn.message": "true",
"kotlin-language-version-configured": "true", "kotlin-language-version-configured": "true",
"last_opened_file_path": "C:/Users/Gustavo/OneDrive/Documentos/Faculdade/2026_2/Monitoria/pitiupi/pitiupi.screenCast", "last_opened_file_path": "C:/Users/Gustavo/OneDrive/Documentos/Faculdade/2026_2/Monitoria/pitiupi/pitiupi.screenCast",
@@ -45,6 +44,17 @@
} }
}]]></component> }]]></component>
<component name="RunManager"> <component name="RunManager">
<configuration default="true" type="JetRunConfigurationType">
<module name="pitiupi.screenCast" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
<configuration default="true" type="KotlinStandaloneScriptRunConfigurationType">
<module name="pitiupi.screenCast" />
<option name="filePath" />
<method v="2" />
</configuration>
<configuration default="true" type="PythonConfigurationType" factoryName="Python"> <configuration default="true" type="PythonConfigurationType" factoryName="Python">
<module name="pitiupi.screenCast" /> <module name="pitiupi.screenCast" />
<option name="ENV_FILES" value="" /> <option name="ENV_FILES" value="" />
@@ -79,23 +89,11 @@
<option name="ADD_SOURCE_ROOTS" value="true" /> <option name="ADD_SOURCE_ROOTS" value="true" />
<method v="2" /> <method v="2" />
</configuration> </configuration>
<configuration default="true" type="JetRunConfigurationType">
<module name="pitiupi.screenCast" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
<configuration default="true" type="KotlinStandaloneScriptRunConfigurationType">
<module name="pitiupi.screenCast" />
<option name="filePath" />
<method v="2" />
</configuration>
</component> </component>
<component name="SharedIndexes"> <component name="SharedIndexes">
<attachedChunks> <attachedChunks>
<set> <set>
<option value="bundled-jdk-30f59d01ecdd-37e91769500f-intellij.indexing.shared.core-IU-261.26222.65" /> <option value="bundled-jdk-9823dce3aa75-bf35d07a577b-intellij.indexing.shared.core-IU-252.28539.97" />
<option value="bundled-js-predefined-d6986cc7102b-31caf2ab9e3c-JavaScript-IU-261.26222.65" />
</set> </set>
</attachedChunks> </attachedChunks>
</component> </component>
@@ -1,6 +1,6 @@
#Tue, 29 Sep 2026 11:04:58 -0300 #Wed, 07 Oct 2026 15:36:37 -0300
C\:\\Users\\Gustavo\\OneDrive\\Documentos\\Faculdade\\2026_2\\Monitoria\\pitiupi\\pitiupi.screenCast= C\:\\Users\\extre\\OneDrive\\Documentos\\Faculdade\\2026_02\\Monitoria\\Pitiupi\\pitiupi.screenCast=
C\:\\Users\\Gustavo\\OneDrive\\Documentos\\Faculdade\\2026_2\\Monitoria\\pitiupi\\pitiupi= C\:\\Users\\extre\\OneDrive\\Documentos\\Faculdade\\2026_02\\Monitoria\\Pitiupi\\pitiupi=
+5
View File
@@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_X" default="true" project-jdk-name="openjdk-26" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/pitiupi.screenCast.iml" filepath="$PROJECT_DIR$/pitiupi.screenCast.iml" />
</modules>
</component>
</project>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
</component>
</project>
@@ -6,6 +6,7 @@
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module-library"> <orderEntry type="module-library">
<library> <library>
<CLASSES> <CLASSES>
@@ -15,6 +16,5 @@
<SOURCES /> <SOURCES />
</library> </library>
</orderEntry> </orderEntry>
<orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>
@@ -4,28 +4,29 @@
*/ */
package pitiupi.screenCast.GUI; package pitiupi.screenCast.GUI;
import pitiupi.models.Menus.MulticastMenus.MulticastActionMenu;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import javax.swing.JMenu;
/** /**
* *
* @author flavio * @author flavio
*/ */
public class ScreenCastMenu extends JMenu { public class ScreenCastMenu extends MulticastActionMenu {
private final ScreenCastWindow screenCastWindow; private final ScreenCastWindow screenCastWindow;
private final javax.swing.JMenuItem menuItem;
public ScreenCastMenu(ScreenCastWindow screenCastWindow) { public ScreenCastMenu(ScreenCastWindow screenCastWindow) {
this.screenCastWindow = screenCastWindow; this.screenCastWindow = screenCastWindow;
this.setText("Screen Cast");
menuItem = new javax.swing.JMenuItem();
menuItem.setText("ScreenCast Window");
menuItem.addActionListener(this::chatMenuActionPerformed);
this.add(menuItem);
} }
private void chatMenuActionPerformed(ActionEvent ae) { @Override
public void onClick() {
this.screenCastWindow.setVisible(true); this.screenCastWindow.setVisible(true);
} }
@Override
public String getText() {
return "Screen Cast";
}
} }
@@ -7,6 +7,7 @@ package pitiupi.screenCast.GUI;
import pitiupi.control.ConnectionManager; import pitiupi.control.ConnectionManager;
import pitiupi.control.GuiManager; import pitiupi.control.GuiManager;
import pitiupi.plugin.Pitiupi;
import pitiupi.screenCast.control.AlarmClock; import pitiupi.screenCast.control.AlarmClock;
import pitiupi.screenCast.control.ImageTransformation; import pitiupi.screenCast.control.ImageTransformation;
import pitiupi.screenCast.control.ScreenShot; import pitiupi.screenCast.control.ScreenShot;
@@ -22,8 +23,6 @@ import java.util.logging.Logger;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
import javax.swing.JLabel; import javax.swing.JLabel;
import pitiupi.GUI.MainWindow;
import pitiupi.net.Message;
/** /**
* *
@@ -40,13 +39,11 @@ public class ScreenCastWindow extends javax.swing.JFrame {
public final static int TIME = 1000; public final static int TIME = 1000;
private float compressRatio; private float compressRatio;
private final ConnectionManager connectionManager;
private final GuiManager guiManager;
public ScreenCastWindow(ConnectionManager connectionManager, GuiManager guiManager) { private final Pitiupi pitiupi;
this.connectionManager = connectionManager;
this.guiManager = guiManager;
public ScreenCastWindow(Pitiupi pitiupi) {
this.pitiupi = pitiupi;
this.compressRatio = 0.5f; this.compressRatio = 0.5f;
this.initComponents(); this.initComponents();
} }
@@ -96,9 +93,9 @@ public class ScreenCastWindow extends javax.swing.JFrame {
BufferedImage image = ScreenShot.takeAShot(); BufferedImage image = ScreenShot.takeAShot();
BufferedImage rescaled = ImageTransformation.resize(image, 1024, 768); BufferedImage rescaled = ImageTransformation.resize(image, 1024, 768);
byte[] byteImage = ImageTransformation.compress(rescaled, compressRatio); byte[] byteImage = ImageTransformation.compress(rescaled, compressRatio);
ScreenCastMessage message = new ScreenCastMessage(this.connectionManager.getUserName(), byteImage); ScreenCastMessage message = new ScreenCastMessage(pitiupi.getUserName(), byteImage);
if (!connectionManager.sendUdpMulticast(message)) this.compressRatio -= 0.1f; if (!pitiupi.sendUdpMulticast(message)) this.compressRatio -= 0.1f;
} catch (AWTException | IOException ex) { } catch (AWTException | IOException ex) {
Logger.getLogger(AlarmClock.class.getName()).log(Level.SEVERE, null, ex); Logger.getLogger(AlarmClock.class.getName()).log(Level.SEVERE, null, ex);
} }
@@ -5,7 +5,8 @@
*/ */
package pitiupi.screenCast.net; package pitiupi.screenCast.net;
import pitiupi.net.Message;
import pitiupi.models.Messages.Message;
/** /**
* *
@@ -4,14 +4,12 @@
*/ */
package pitiupi.screenCast.plugin; package pitiupi.screenCast.plugin;
import java.io.ByteArrayInputStream; import java.util.List;
import java.io.IOException;
import java.io.ObjectInputStream; import pitiupi.models.Menus.MenuCategory;
import pitiupi.GUI.MainWindow; import pitiupi.models.Messages.Message;
import pitiupi.control.ConnectionManager; import pitiupi.models.Plugin;
import pitiupi.control.GuiManager; import pitiupi.plugin.Pitiupi;
import pitiupi.net.Message;
import pitiupi.plugin.Plugin;
import pitiupi.screenCast.GUI.ScreenCastMenu; import pitiupi.screenCast.GUI.ScreenCastMenu;
import pitiupi.screenCast.GUI.ScreenCastWindow; import pitiupi.screenCast.GUI.ScreenCastWindow;
import pitiupi.screenCast.net.ScreenCastMessage; import pitiupi.screenCast.net.ScreenCastMessage;
@@ -20,7 +18,7 @@ import pitiupi.screenCast.net.ScreenCastMessage;
* *
* @author flavio * @author flavio
*/ */
public class ScreenCastPlugin implements Plugin { public class ScreenCastPlugin extends Plugin {
private ScreenCastWindow screenCastWindow; private ScreenCastWindow screenCastWindow;
@@ -45,28 +43,20 @@ public class ScreenCastPlugin implements Plugin {
} }
@Override @Override
public void createPlugin(ConnectionManager connectionManager, GuiManager guiManager) { public void createPlugin(Pitiupi pitiupi) {
this.screenCastWindow = new ScreenCastWindow(connectionManager, guiManager); this.screenCastWindow = new ScreenCastWindow(pitiupi);
guiManager.addMenu(new ScreenCastMenu(screenCastWindow)); pitiupi.createMenu(MenuCategory.Streaming, new ScreenCastMenu(screenCastWindow));
} }
@Override @Override
public void receiveMessage(byte[] message) { public List<Class<? extends Message>> getMessages() {
ByteArrayInputStream bis = new ByteArrayInputStream(message); return List.of(ScreenCastMessage.class);
ObjectInputStream ois; }
try {
ois = new ObjectInputStream(bis); @Override
if (ois.readObject() instanceof ScreenCastMessage msg) { public void receiveMessage(Message message) {
this.screenCastWindow.receiveScreenCastMessage(msg); if (message instanceof ScreenCastMessage msg) {
} this.screenCastWindow.receiveScreenCastMessage(msg);
} catch (IOException | ClassNotFoundException ex) {
System.out.println(ex.toString());
} }
} }
@Override
public Message getMessage() {
return new ScreenCastMessage();
}
} }
+4 -4
View File
@@ -66,15 +66,15 @@ A classe `Pitiupi` é a interface que o seu plugin vai receber quando for criado
Envia uma mensagem multicast, retorna `boolean` indicando se teve sucesso. Envia uma mensagem multicast, retorna `boolean` indicando se teve sucesso.
### boolean sendUdpUnicast(Message message, InetAddress address) ### boolean sendUdpUnicast(Message message, InetAddress receiverAddress)
Envia uma mensagem unicast via udp para o endereço fornecido, retorna `boolean` indicando se teve sucesso. Envia uma mensagem unicast via udp para o endereço fornecido, retorna `boolean` indicando se teve sucesso.
### boolean sendTcp(Message message, InetAddress address) ### boolean sendTcp(Message message, InetAddress receiverAddress)
Envia uma mensagem unicast via tcp para o endereço fornecido, retorna `boolean` indicando se teve sucesso. Envia uma mensagem unicast via tcp para o endereço fornecido, retorna `boolean` indicando se teve sucesso.
### <span style="color: red;">[REF]</span> TcpConnection connectTcp(InetAddress address, String senderId, String receiverId) ### <span style="color: red;">[REF]</span> TcpConnection connectTcp(InetAddress receiverAddress, String senderId, String receiverId)
Pede uma conexão tcp privada para o seu plugin com outro usuário, para utilizar essa funcionalidade é necessário implementar alguns métodos opcionais em Plugin. Maisinformações serão discutidas na seção TcpConnection. Pede uma conexão tcp privada para o seu plugin com outro usuário, para utilizar essa funcionalidade é necessário implementar alguns métodos opcionais em Plugin. Maisinformações serão discutidas na seção TcpConnection.
@@ -157,7 +157,7 @@ Esse método é chamado quando o Pitiupi precisa entregar uma `TcpConnection` ao
### <span style="color: red;">[REF]</span> receiveBytes(TcpConnection connection, byte[] bytes) ### <span style="color: red;">[REF]</span> receiveBytes(TcpConnection connection, byte[] bytes)
Esse método é chamado para entregar as mensagens recebidas por um `TcpConnection`, o tcpConnection no parametro indica qual a fonte da mensagem. Esse método é chamado para entregar as mensagens recebidas por um `TcpConnection`, o tcpPitiupi no parametro indica qual a fonte da mensagem.
### <span style="color: red;">[REF]</span> onSocketClosed(TcpConnection connection) ### <span style="color: red;">[REF]</span> onSocketClosed(TcpConnection connection)
+4 -4
View File
@@ -700,7 +700,7 @@ is divided into following sections:
<sequential> <sequential>
<j2seproject3:junit excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}"> <j2seproject3:junit excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
<customize> <customize>
<jvmarg value="-agentlib:jdwp=transport=${debug-transport},address=${jpda.address}"/> <jvmarg value="-agentlib:jdwp=transport=${debug-transport},receiverAddress=${jpda.receiverAddress}"/>
<customizeDebuggee/> <customizeDebuggee/>
</customize> </customize>
</j2seproject3:junit> </j2seproject3:junit>
@@ -843,7 +843,7 @@ is divided into following sections:
<attribute default="${debug.classpath}" name="classpath"/> <attribute default="${debug.classpath}" name="classpath"/>
<attribute default="" name="stopclassname"/> <attribute default="" name="stopclassname"/>
<sequential> <sequential>
<nbjpdastart addressproperty="jpda.address" name="@{name}" stopclassname="@{stopclassname}" transport="${debug-transport}"> <nbjpdastart addressproperty="jpda.receiverAddress" name="@{name}" stopclassname="@{stopclassname}" transport="${debug-transport}">
<modulepath> <modulepath>
<path path="@{modulepath}"/> <path path="@{modulepath}"/>
</modulepath> </modulepath>
@@ -882,7 +882,7 @@ is divided into following sections:
<sequential> <sequential>
<j2seproject1:java classname="@{classname}" classpath="@{classpath}" modulename="@{modulename}" modulepath="@{modulepath}"> <j2seproject1:java classname="@{classname}" classpath="@{classpath}" modulename="@{modulename}" modulepath="@{modulepath}">
<customize> <customize>
<jvmarg value="-agentlib:jdwp=transport=${debug-transport},address=${jpda.address}"/> <jvmarg value="-agentlib:jdwp=transport=${debug-transport},receiverAddress=${jpda.receiverAddress}"/>
<customizeDebuggee/> <customizeDebuggee/>
</customize> </customize>
</j2seproject1:java> </j2seproject1:java>
@@ -1434,7 +1434,7 @@ is divided into following sections:
</nbprofiledirect> </nbprofiledirect>
<j2seproject3:junit excludes="${excludes}" includes="${includes}" testincludes="${profile.class}" testmethods=""> <j2seproject3:junit excludes="${excludes}" includes="${includes}" testincludes="${profile.class}" testmethods="">
<customize> <customize>
<jvmarg value="-agentlib:jdwp=transport=${debug-transport},address=${jpda.address}"/> <jvmarg value="-agentlib:jdwp=transport=${debug-transport},receiverAddress=${jpda.receiverAddress}"/>
<env key="${profiler.info.pathvar}" path="${profiler.info.agentpath}:${profiler.current.path}"/> <env key="${profiler.info.pathvar}" path="${profiler.info.agentpath}:${profiler.current.path}"/>
<jvmarg value="${profiler.info.jvmargs.agent}"/> <jvmarg value="${profiler.info.jvmargs.agent}"/>
<jvmarg line="${profiler.info.jvmargs}"/> <jvmarg line="${profiler.info.jvmargs}"/>
+26 -12
View File
@@ -12,6 +12,7 @@ import pitiupi.control.plugins.PluginManager;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.net.UnknownHostException;
/** /**
* Janela principal da aplicação. * Janela principal da aplicação.
@@ -33,9 +34,9 @@ public class MainWindow extends JFrame {
private JLabel status; private JLabel status;
OnlineUsersPanel onlineUsersPanel; OnlineUsersPanel onlineUsersPanel;
private final ConnectionManager connectionManager; private ConnectionManager connectionManager;
private final PluginManager pluginManager; private PluginManager pluginManager;
private final GuiManager guiManager; private GuiManager guiManager;
/** /**
* Cria a janela principal da aplicação. * Cria a janela principal da aplicação.
@@ -44,21 +45,34 @@ public class MainWindow extends JFrame {
* @param port porta utilizada na comunicação multicast. * @param port porta utilizada na comunicação multicast.
*/ */
public MainWindow(String userName, int port) { public MainWindow(String userName, int port) {
initMenu(); try {
initMenu();
pluginManager = new PluginManager(); pluginManager = new PluginManager();
connectionManager = new ConnectionManager(this, userName, pluginManager, port); connectionManager = new ConnectionManager(userName, pluginManager, port);
initUserPanel(); initUserPanel();
initStatusPanel(); initStatusPanel();
guiManager = new GuiManager(this, menuBar, onlineUsersPanel); guiManager = new GuiManager(this, menuBar, onlineUsersPanel);
loadPlugins(); loadPlugins();
this.pack(); this.pack();
this.setSize(new java.awt.Dimension(1024, 768)); this.setSize(new java.awt.Dimension(1024, 768));
}
catch (UnknownHostException e) {
JOptionPane.showMessageDialog(
null,
"Ocorreu um erro abrindo a conexão TCP, verifique sua rede. \n" +
"Se o problema persistir entre em contato com a equipe do Pitiupi.",
"Erro na conexão TCP",
JOptionPane.ERROR_MESSAGE
);
System.exit(0);
}
} }
private void initMenu() { private void initMenu() {
@@ -2,46 +2,54 @@ package pitiupi.control;
import pitiupi.GUI.MainWindow; import pitiupi.GUI.MainWindow;
import pitiupi.control.plugins.PluginManager; import pitiupi.control.plugins.PluginManager;
import pitiupi.models.ConnectionData;
import pitiupi.models.Messages.HeartbeatMessage; import pitiupi.models.Messages.HeartbeatMessage;
import pitiupi.models.Messages.Message; import pitiupi.models.Messages.Message;
import pitiupi.net.tcp.SocketTCP; import pitiupi.net.tcp.TcpClient;
import pitiupi.net.tcp.TcpConnection; import pitiupi.net.tcp.TcpManager;
import pitiupi.net.tcp.TcpServer;
import pitiupi.net.udp.SocketUDP; import pitiupi.net.udp.SocketUDP;
import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.logging.Level;
import java.util.logging.Logger;
public class ConnectionManager { public class ConnectionManager {
private final String pitiupiIdentifier;
private final PluginManager pluginManager; private final PluginManager pluginManager;
private final HeartbeatManager heartbeatManager; private final HeartbeatManager heartbeatManager;
private SocketTCP socketTCP; private TcpManager tcpManager;
private SocketUDP socketUDP; private SocketUDP socketUDP;
private String username; private String username;
private InetAddress address;
private int port; private int port;
public ConnectionManager(MainWindow main, String username, PluginManager pluginManager, int port) { public ConnectionManager(String username, PluginManager pluginManager, int port) throws UnknownHostException {
this.pluginManager = pluginManager; this.pluginManager = pluginManager;
this.username = username; this.username = username;
this.address = InetAddress.getLocalHost();
this.port = port; this.port = port;
pitiupiIdentifier = "Pitiupi.Alice.2026";
reconnect(); reconnect();
heartbeatManager = new HeartbeatManager(this); heartbeatManager = new HeartbeatManager(this);
heartbeatManager.start(); heartbeatManager.start();
} }
public TcpConnection connectPluginTcp(InetAddress destinationAdress, String senderId, String receiverId) { public TcpClient connectToTcpServer(ConnectionData serverData, String pluginId) {
return socketTCP.createPrivateConnection(destinationAdress, senderId, receiverId); return tcpManager.getClient(serverData, pluginId, username);
}
public TcpServer getTcpServer(String pluginId) {
return tcpManager.getServer(pluginId, username, address, port);
} }
public boolean sendTcp(Message message, InetAddress destinationAddress) { public boolean sendTcp(Message message, InetAddress destinationAddress) {
return socketTCP.send(message, destinationAddress); return tcpManager.send(message, destinationAddress, port);
} }
public boolean sendUdpUnicast(Message message, InetAddress destinationAddress) { public boolean sendUdpUnicast(Message message, InetAddress destinationAddress) {
@@ -63,7 +71,7 @@ public class ConnectionManager {
return false; return false;
} }
public void broadcastLocal(byte[] message) { public void receiveMessage(byte[] message) {
pluginManager.handleMessage(message); pluginManager.handleMessage(message);
} }
@@ -84,30 +92,35 @@ public class ConnectionManager {
return port; return port;
} }
public String getIdentifier(){
return this.pitiupiIdentifier;
}
private void reconnect() { private void reconnect() {
if (this.socketUDP != null) { try {
this.socketUDP.close(); if (this.socketUDP != null) {
} this.socketUDP.close();
if (this.socketTCP != null) { }
this.socketTCP.close(); if (this.tcpManager != null) {
} this.tcpManager.closeManager();
}
this.socketUDP = new SocketUDP(this, pluginManager); this.socketUDP = new SocketUDP(this, pluginManager);
this.socketTCP = new SocketTCP(this, pluginManager); this.tcpManager = new TcpManager(port, this::receiveMessage);
this.socketUDP.start(); this.socketUDP.start();
this.socketTCP.start(); this.tcpManager.start();
}
catch (Exception ex) {
Logger.getLogger(ConnectionManager.class.getName()).log(Level.SEVERE, null, ex);
}
} }
public String getUserName() { public String getUserName() {
return this.username; return this.username;
} }
public InetAddress getAddress() {
return this.address;
}
public void setUserName(String userName) { public void setUserName(String userName) {
this.username = userName; this.username = userName;
} }
} }
@@ -5,7 +5,6 @@ import pitiupi.Main;
import pitiupi.control.ConnectionManager; import pitiupi.control.ConnectionManager;
import pitiupi.control.GuiManager; import pitiupi.control.GuiManager;
import pitiupi.models.Messages.Message; import pitiupi.models.Messages.Message;
import pitiupi.net.tcp.TcpConnection;
import pitiupi.models.Plugin; import pitiupi.models.Plugin;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@@ -67,29 +66,6 @@ public class PluginManager {
return null; return null;
} }
public void handleMessageFromConnection(TcpConnection connection, byte[] msg) {
Plugin plugin = getPlugin(connection.getReceiver());
if (plugin == null) return;
plugin.receiveBytes(connection, msg);
}
public boolean handleConnection(TcpConnection connection, String receiverId) {
Plugin plugin = getPlugin(receiverId);
if (plugin == null) return false;
plugin.receivePrivateConnection(connection);
return false;
}
public void handleDisconnection(TcpConnection connection, String receiverId) {
Plugin plugin = getPlugin(receiverId);
if (plugin == null) return;
plugin.onSocketClosed(connection);
}
public void loadPlugins(ConnectionManager connectionManager, GuiManager guiManager) { public void loadPlugins(ConnectionManager connectionManager, GuiManager guiManager) {
File pluginDir = new File("plugins"); File pluginDir = new File("plugins");
File[] jarFiles = pluginDir.listFiles((dir, name) -> name.endsWith(".jar")); File[] jarFiles = pluginDir.listFiles((dir, name) -> name.endsWith(".jar"));
@@ -0,0 +1,44 @@
package pitiupi.models;
import java.io.Serializable;
import java.net.InetAddress;
import java.util.UUID;
public class ConnectionData implements Serializable {
private final UUID uuid;
private final int port;
private final String ownerName;
private final InetAddress ownerAddress;
private final String plugin;
private final boolean isServer;
public ConnectionData(String ownerName, InetAddress ownerAddress, String plugin, int port, boolean isServer) {
uuid = UUID.randomUUID();
this.port = port;
this.ownerName = ownerName;
this.ownerAddress = ownerAddress;
this.plugin = plugin;
this.isServer = isServer;
}
public UUID getUuid() {
return uuid;
}
public int getPort() {
return port;
}
public String getOwnerName() {
return ownerName;
}
public InetAddress getOwnerAddress() {
return ownerAddress;
}
public String getPlugin() {
return plugin;
}
public boolean isServer() {
return isServer;
}
}
@@ -1,50 +0,0 @@
package pitiupi.models.Messages;
import pitiupi.net.tcp.TcpConnection;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
public class InfoMessage extends Message {
private final InfoType type;
private final String message;
public InfoMessage(String message, InfoType type) {
this.message = message;
this.type = type;
}
public String getMessage() {
return message;
}
public InfoType getType() {
return type;
}
public static void sendSystemInfo(TcpConnection connection, InfoType type, String description) {
try {
InfoMessage error = new InfoMessage(description, type);
connection.send(error.toByteArray());
} catch (IOException ex) {
Logger.getLogger(InfoMessage.class.getName()).log(Level.WARNING, null, ex);
}
}
public static InfoMessage parseSystemInfo(byte[] bytes) {
try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis)) {
Object object = ois.readObject();
if (object instanceof InfoMessage) {
return (InfoMessage) object;
}
} catch (IOException | ClassNotFoundException ex) {
Logger.getLogger(InfoMessage.class.getName()).log(Level.WARNING, null, ex);
}
return null;
}
}
@@ -1,9 +0,0 @@
package pitiupi.models.Messages;
public enum InfoType {
CONNECTION_REJECTED,
RECEIVER_NOT_FOUND,
MESSAGE_REJECTED,
INTERNAL_ERROR,
CONNECTION_CLOSED,
}
+1 -10
View File
@@ -5,7 +5,6 @@
package pitiupi.models; package pitiupi.models;
import pitiupi.plugin.Pitiupi; import pitiupi.plugin.Pitiupi;
import pitiupi.models.Messages.Message; import pitiupi.models.Messages.Message;
import pitiupi.net.tcp.TcpConnection;
import java.util.List; import java.util.List;
@@ -50,13 +49,5 @@ public abstract class Plugin {
public abstract void receiveMessage(Message message); public abstract void receiveMessage(Message message);
public boolean receiveAllTypes() {return false;} public boolean receiveAllTypes() {return false;}
public boolean receivePrivateConnection(TcpConnection connection) {
return false;
}
public boolean receiveBytes(TcpConnection connection, byte[] bytes) {
return false;
}
public boolean onSocketClosed(TcpConnection connection) {
return false;
}
} }
@@ -1,229 +0,0 @@
package pitiupi.net.tcp;
import pitiupi.models.Messages.Message;
import pitiupi.models.Messages.InfoMessage;
import pitiupi.models.Messages.InfoType;
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;
/**
* Fábrica responsável pela criação e configuração de conexões TCP.
*
* <p>Esta classe centraliza o estabelecimento de conexões de saída e a
* configuração de conexões recebidas. Antes de uma conexão ser considerada
* válida, é realizado um handshake entre os peers para identificar o remetente,
* o destinatário e se a conexão é privada.</p>
*
* <p>Durante o handshake é utilizado um timeout menor, definido por
* {@link #HANDSHAKE_TIMEOUT}. Após a validação bem-sucedida, a conexão passa
* a utilizar o timeout padrão definido por {@link #STANDARD_TIMEOUT}.</p>
*/
public class ConnectionFactory {
private static final int HANDSHAKE_TIMEOUT = 1000;
private static final int STANDARD_TIMEOUT = 300_000;
/**
* Cria uma conexão TCP de saída com o peer de destino e realiza o handshake
* necessário para validá-la.
*
* <p>A conexão é configurada com os identificadores do remetente e do
* destinatário e com o tipo indicado pelo parâmetro {@code isPrivate}.
* Após o estabelecimento do socket, o método envia um
* {@link HandshakeMessage} e aguarda uma resposta válida.</p>
*
* <p>Se o handshake for validado com sucesso, o timeout da conexão é
* alterado de {@link #HANDSHAKE_TIMEOUT} para {@link #STANDARD_TIMEOUT}
* e a conexão é retornada. Caso contrário, {@code null} é retornado.</p>
*
* @param address endereço IP do peer de destino.
* @param port porta TCP na qual o peer de destino está aguardando conexões.
* @param sender identificador do remetente da conexão.
* @param receiver identificador do destinatário da conexão.
* @param isPrivate {@code true} se a conexão deve ser privada;
* {@code false} se deve ser pública.
* @return a conexão TCP estabelecida e validada ou {@code null} caso
* não seja possível estabelecer ou validar a conexão.
*/
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;
}
/**
* Configura uma conexão TCP recebida e valida o handshake enviado pelo
* peer remoto.
*
* <p>O handshake recebido é utilizado para obter e configurar os
* identificadores do remetente e do destinatário, bem como o tipo da
* conexão. Após a validação bem-sucedida, o timeout da conexão é alterado
* para {@link #STANDARD_TIMEOUT}.</p>
*
* <p>O {@link Socket} recebido deve representar uma conexão TCP já aceita
* pelo servidor.</p>
*
* @param socket socket TCP correspondente à conexão recebida.
* @return a conexão TCP configurada e validada ou {@code null} caso
* o handshake seja inválido ou não possa ser processado.
*/
public static TcpConnection receiveConnection(Socket socket) {
try {
TcpConnection connection;
connection = new TcpConnection(socket);
HandshakeMessage handshake = validateConnection(connection);
if (handshake == null) return null;
connection.send(handshake.createResponse().toByteArray());
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;
}
/**
* Aguarda e valida a mensagem de handshake de uma conexão TCP.
*
* <p>Durante a validação, a conexão utiliza {@link #HANDSHAKE_TIMEOUT}
* como limite para o recebimento do handshake. A mensagem recebida deve
* ser uma instância de {@link Message} e, especificamente, de
* {@link HandshakeMessage}. Mensagens de sistema ou mensagens de outros
* tipos são rejeitadas.</p>
*
* <p>Quando o handshake é recebido, seus campos de remetente e destinatário
* são validados por {@link #validateHandshake(HandshakeMessage, TcpConnection)}.
* Caso a validação seja bem-sucedida, o próprio handshake é retornado.</p>
*
* <p>Em caso de timeout, mensagem inválida ou erro durante o processamento,
* uma mensagem de sistema apropriada pode ser enviada ao peer e
* {@code null} é retornado.</p>
*
* @param connection conexão TCP da qual o handshake será recebido.
* @return o {@link HandshakeMessage} validado ou {@code null} caso o
* handshake não seja recebido ou seja inválido.
*/
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) {
InfoMessage sysInfo = InfoMessage.parseSystemInfo(bytes);
if (sysInfo != null) return null;
if (message instanceof HandshakeMessage handshake) {
return (validateHandshake(handshake, connection)) ? handshake : null;
}
}
InfoMessage.sendSystemInfo(
connection,
InfoType.CONNECTION_REJECTED,
"The message was not from an allowed type."
);
}
catch (SocketTimeoutException ex) {
InfoMessage.sendSystemInfo(
connection,
InfoType.CONNECTION_REJECTED,
"No handshake was received within the allowed time."
);
}
catch (ClassNotFoundException | IOException ex) {
Logger.getLogger(ConnectionFactory.class.getName()).log(Level.SEVERE, null, ex);
InfoMessage.sendSystemInfo(
connection,
InfoType.INTERNAL_ERROR,
"The server could not process the handshake."
);
}
return null;
}
/**
* Valida os identificadores presentes em uma mensagem de handshake.
*
* <p>O remetente e o destinatário devem estar presentes e não podem ser
* vazios ou conter apenas espaços em branco. Quando um dos identificadores
* é inválido, uma mensagem de rejeição é enviada ao peer e a validação
* falha.</p>
*
* @param handshake mensagem de handshake a ser validada.
* @param connection conexão associada ao handshake, utilizada para enviar
* uma mensagem de rejeição quando necessário.
* @return {@code true} se os identificadores do remetente e do destinatário
* forem válidos; {@code false} caso contrário.
*/
private static boolean validateHandshake(HandshakeMessage handshake, TcpConnection connection) {
String sender = handshake.getSender();
String receiver = handshake.getReceiver();
if (sender == null || sender.isBlank()) {
InfoMessage.sendSystemInfo(
connection,
InfoType.CONNECTION_REJECTED,
"Invalid Sender: " + sender + "."
);
return false;
}
if (receiver == null || receiver.isBlank()) {
InfoMessage.sendSystemInfo(
connection,
InfoType.CONNECTION_REJECTED,
"Invalid Receiver: " + receiver + "."
);
return false;
}
return true;
}
}
@@ -1,30 +0,0 @@
package pitiupi.net.tcp;
import pitiupi.models.Messages.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);
}
}
-456
View File
@@ -1,456 +0,0 @@
package pitiupi.net.tcp;
import pitiupi.control.ConnectionManager;
import pitiupi.control.plugins.PluginManager;
import pitiupi.models.Messages.Message;
import pitiupi.models.Messages.InfoMessage;
import pitiupi.models.Messages.InfoType;
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 final PluginManager pluginManager;
private final TcpConnectionManager tcpConnectionManager;
private ServerSocket serverSocket;
private InetAddress address;
private volatile boolean running = true;
/**
* Cria o servidor TCP da aplicação.
*
* <p>O servidor é criado na porta definida pela aplicação. As conexões
* com os peers são estabelecidas sob demanda durante o envio ou aceitas
* quando iniciadas por outros peers.</p>
*
* @param connectionManager interface de conexão da aplicação, usada para obter a porta,
* o identificador local e os plugins disponíveis.
*/
public SocketTCP(ConnectionManager connectionManager, PluginManager pluginManager) {
this.connectionManager = connectionManager;
this.pluginManager = pluginManager;
logger = Logger.getLogger(getClass().getName());
tcpConnectionManager = new TcpConnectionManager();
try {
serverSocket = new ServerSocket(connectionManager.getPort());
address = InetAddress.getByName(InetAddress.getLocalHost().getHostAddress());
} catch (IOException ex) {
System.out.println("There is no socket connection. Sorry.");
System.out.println(ex.toString());
}
}
/**
* Envia uma mensagem por meio de uma conexão TCP pública com o peer de destino.
*
* <p>A conexão pública existente para o endereço informado é reutilizada.
* Caso não exista uma conexão válida, uma nova conexão é estabelecida antes
* do envio. Falhas durante o estabelecimento ou envio são registradas na
* saída padrão e não são propagadas ao chamador.</p>
*
* @param msg mensagem que será enviada.
* @param destinationAddress endereço IP do peer que receberá a mensagem.
*/
public boolean send(Message msg, InetAddress destinationAddress) {
try {
TcpConnection connection = getPublicConnection(
destinationAddress,
connectionManager.getIdentifier(),
connectionManager.getIdentifier()
);
if (connection == null) {
System.out.println("Could not establish TCP connection.");
return false;
}
connection.send(msg.toByteArray());
} catch (Exception ex) {
System.out.println("Could not send TCP message.");
System.out.println(ex.toString());
return false;
}
return true;
}
/**
* Estabelece uma conexão TCP privada com um plugin específico do peer de destino.
*
* <p>A conexão é criada como privada e passa a ser gerenciada pelo
* {@link TcpConnectionManager}. O método também inicia a tarefa responsável
* por receber os dados dessa conexão.</p>
*
* @param destinationAddress endereço IP do peer de destino.
* @param senderId identificador do remetente da conexão.
* @param receiverId identificador do plugin que receberá os dados.
* @return a conexão privada estabelecida ou {@code null} quando a criação
* da conexão falhar.
* @throws IllegalArgumentException se o endereço de destino for o endereço
* local desta instância.
*/
public TcpConnection createPrivateConnection(InetAddress destinationAddress, String senderId, String receiverId) {
return establishOutgoingConnection(destinationAddress, senderId, receiverId, true);
}
/**
* Obtém uma conexão TCP pública existente com o peer de destino ou estabelece
* uma nova caso não exista uma conexão válida.
*
* <p>As conexões públicas são gerenciadas pelo {@link TcpConnectionManager}.
* O método primeiro verifica se já existe uma conexão pública registrada
* para o endereço de destino e, caso ela exista e não esteja fechada,
* reutiliza-a. Caso contrário, uma nova conexão pública é estabelecida,
* registrada no {@link TcpConnectionManager} e disponibilizada para uso.</p>
*
* <p>O acesso ao método é sincronizado para evitar que múltiplas threads
* estabeleçam simultaneamente conexões públicas para o mesmo peer.</p>
*
* @param destinationAddress endereço IP do peer de destino.
* @param senderId identificador usado como remetente no estabelecimento da conexão.
* @param receiverId identificador usado como destinatário no estabelecimento da conexão.
* @return uma conexão pública válida ou {@code null} caso não seja possível
* estabelecê-la.
*/
private synchronized TcpConnection getPublicConnection(InetAddress destinationAddress, String senderId, String receiverId) {
TcpConnection connection;
connection = tcpConnectionManager.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 socket aceito é encaminhado para processamento assíncrono,
* interpretado como uma {@link TcpConnection}, registrado pelo
* {@link TcpConnectionManager} e associado ao receptor adequado.</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());
}
}
}
/**
* Estabelece uma conexão TCP de saída com outro peer.
*
* <p>O método impede conexões com o próprio endereço local, cria a conexão
* por meio da {@link ConnectionFactory}, registra-a no gerenciador de
* conexões e inicia uma tarefa para receber dados continuamente.</p>
*
* @param destinationAddress endereço IP do peer de destino.
* @param sender identificador do remetente.
* @param receiver identificador do destinatário.
* @param isPrivate {@code true} para uma conexão destinada a um plugin
* específico; {@code false} para uma conexão pública.
* @return a conexão estabelecida ou {@code null} se a
* {@link ConnectionFactory} não conseguir criá-la.
* @throws IllegalArgumentException se o destino for o endereço desta máquina.
*/
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, connectionManager.getPort(), sender, receiver, isPrivate);
if (connection == null) return null;
saveConnection(connection);
connectionExecutor.submit(() -> {
startReceiver(connection);
});
return connection;
}
/**
* Agenda o processamento de uma conexão TCP recebida.
*
* <p>A conexão é interpretada pela {@link ConnectionFactory}, registrada
* no gerenciador e associada a uma tarefa de recepção. O processamento é
* executado de forma assíncrona pelo executor compartilhado.</p>
*
* @param socket socket aceito pelo {@link ServerSocket}.
*/
private void acceptIncomingConnection(Socket socket) {
connectionExecutor.submit(() -> {
TcpConnection connection = ConnectionFactory.receiveConnection(socket);
if (connection == null) return;
saveConnection(connection);
startReceiver(connection);
});
}
/**
* Registra uma conexão no gerenciador de acordo com seu tipo.
*
* <p>Conexões privadas são armazenadas na coleção de conexões privadas;
* as demais são armazenadas na coleção de conexões públicas.</p>
*
* @param connection conexão que será registrada.
*/
private void saveConnection(TcpConnection connection) {
if (connection.isPrivate()) {
tcpConnectionManager.addPrivateConnection(connection);
}
else {
tcpConnectionManager.addPublicConnection(connection);
}
}
/**
* Seleciona e inicia o receptor adequado para uma conexão.
*
* <p>Conexões públicas são tratadas pelo receptor de mensagens públicas.
* Conexões privadas destinadas ao próprio sistema são tratadas
* separadamente; as demais são encaminhadas ao plugin identificado como
* destinatário. Quando solicitado, o plugin também é notificado sobre o
* estabelecimento da conexão.</p>
*
* @param connection conexão cujo fluxo de entrada será processado.
*/
private void startReceiver(TcpConnection connection) {
if (!connection.isPrivate()) {
publicConnectionReceiver(connection);
return;
}
if (connection.getReceiver().equals(connectionManager.getIdentifier())) {
systemConnectionReceiver(connection);
return;
}
if (pluginManager.handleConnection(connection, connection.getReceiver()))
privateConnectionReceiver(connection);
else {
InfoMessage.sendSystemInfo(
connection,
InfoType.RECEIVER_NOT_FOUND,
"This peer does not have the plugin: " + connection.getReceiver()
);
}
}
/**
* Processa continuamente as mensagens recebidas em uma conexão pública.
*
* <p>As mensagens são desserializadas e, quando são instâncias de
* {@link Message}, encaminhadas a todos os plugins da aplicação. Mensagens
* de controle do sistema podem encerrar a conexão ou registrar uma
* condição de erro.</p>
*
* <p>O método também trata timeouts e erros de I/O, removendo a conexão
* do {@link TcpConnectionManager} quando ela deixa de ser utilizável.</p>
*
* @param connection conexão pública da qual as mensagens serão recebidas.
*/
private void publicConnectionReceiver(TcpConnection connection) {
while (running) {
try {
byte[] bytes = connection.receive();
InfoMessage sysMessage = InfoMessage.parseSystemInfo(bytes);
if (sysMessage == null) {
pluginManager.handleMessage(bytes);
continue;
}
switch (sysMessage.getType()) {
case CONNECTION_CLOSED, INTERNAL_ERROR -> {
tcpConnectionManager.closeConnection(connection);
return;
}
case MESSAGE_REJECTED -> {
logger.log(Level.SEVERE, "Somehow sent something that was not an Message");
}
default -> {}
}
}
catch (SocketTimeoutException ex) {
if (connection.hasTimedOut()) {
tcpConnectionManager.closeConnection(connection, "Connection timeout");
return;
}
}
catch (IOException ex) {
tcpConnectionManager.closeConnection(connection);
}
}
}
/**
* Processa os dados recebidos em uma conexão privada e os encaminha
* ao plugin destinatário.
*
* <p>Dados que não representam mensagens de controle são entregues
* diretamente ao {@link PrivateConnectionPlugin}. Mensagens de controle
* relacionadas ao encerramento ou a erros internos provocam a notificação
* do plugin e o fechamento da conexão.</p>
*
* @param connection conexão privada da qual os dados serão recebidos.
*/
private void privateConnectionReceiver(TcpConnection connection) {
while (running) {
try {
byte[] message = connection.receive();
InfoMessage sysMessage = InfoMessage.parseSystemInfo(message);
if (sysMessage == null) {
pluginManager.handleMessageFromConnection(connection, message);
continue;
}
switch (sysMessage.getType()) {
case CONNECTION_CLOSED, INTERNAL_ERROR -> {
pluginManager.handleDisconnection(connection, connection.getReceiver());
tcpConnectionManager.closeConnection(connection);
}
default -> {
continue;
}
}
}
catch (SocketTimeoutException ex) {
if (connection.hasTimedOut()) {
tcpConnectionManager.closeConnection(connection, "Connection timeout");
return;
}
}
catch (IOException ex) {
tcpConnectionManager.closeConnection(connection);
}
}
}
/**
* Processa mensagens recebidas em uma conexão destinada ao próprio sistema.
*
* <p>Atualmente, mensagens destinadas ao sistema são apenas verificadas
* quanto ao seu formato de controle. Não há processamento adicional de
* mensagens de sistema neste método.</p>
*
* @param connection conexão destinada ao identificador da própria aplicação.
*/
private void systemConnectionReceiver(TcpConnection connection) {
while (running) {
try {
byte[] message = connection.receive();
try (ByteArrayInputStream bis = new ByteArrayInputStream(message);
ObjectInputStream ois = new ObjectInputStream(bis)) {
InfoMessage sysMessage = InfoMessage.parseSystemInfo(message);
if (sysMessage != null) continue;
//TODO: Ainda não existem mensagens trocadas entre o sistema.
}
}
catch (SocketTimeoutException ex) {
if (connection.hasTimedOut()) return;
}
catch (IOException ex) {
tcpConnectionManager.closeConnection(connection);
}
}
}
/**
* Encerra uma conexão TCP e registra o motivo informado.
*
* <p>Quando nenhuma descrição é fornecida, é utilizado um motivo padrão.
* O fechamento efetivo da conexão é delegado ao {@link TcpConnectionManager}.</p>
*
* @param connection conexão que será encerrada.
* @param description motivo do encerramento; pode ser {@code null}.
*/
public void disconnect(TcpConnection connection, String description) {
tcpConnectionManager.closeConnection(
connection,
Objects.requireNonNullElse(description, "No reason provided")
);
}
@Override
public void run() {
receive();
}
/**
* Encerra o servidor TCP e libera os recursos associados.
*
* <p>Interrompe o laço de aceitação de conexões, fecha o
* {@link ServerSocket}, encerra todas as conexões gerenciadas e interrompe
* as tarefas do executor.</p>
*
* <p>Após a chamada, esta instância não deve ser reutilizada para aceitar
* novas conexões.</p>
*/
public void close() {
running = false;
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException ex) {
logger.log(Level.SEVERE, null, ex);
}
}
tcpConnectionManager.closeAllConnections("The peer must have closed the application or changed ports");
connectionExecutor.shutdownNow();
}
}
@@ -0,0 +1,34 @@
package pitiupi.net.tcp;
import pitiupi.models.ConnectionData;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
public final class TcpClient extends TcpPitiupi {
private final ConnectionData clientData;
private final ConnectionData serverData;
TcpClient(
ConnectionData clientData,
ConnectionData serverData,
Socket socket,
DataInputStream input,
DataOutputStream output)
{
super(socket, input, output);
this.clientData = clientData;
this.serverData = serverData;
}
public ConnectionData getServerData() {
return serverData;
}
public ConnectionData getClientData() {
return clientData;
}
}
@@ -1,171 +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 ativa com um peer remoto.
*
* <p>A conexão permite o envio e recebimento de mensagens, além de manter
* informações sobre o endereço remoto e metadados da conexão, como remetente,
* destinatário e se a conexão é privada.</p>
*
* @author Gustavo
*/
public class TcpConnection {
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;
private long timeout;
/**
* Cria uma conexão TCP a partir de um socket existente.
*
* @param socket socket utilizado para a comunicação
* @throws IOException se não for possível obter os fluxos de entrada ou saída
* do socket
*/
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();
this.timeout = 300_000; // 5 minutos
}
/**
* Envia uma mensagem pela conexão.
*
* <p>A mensagem é enviada precedida pelo seu tamanho, permitindo que o
* receptor determine quantos bytes devem ser lidos.</p>
*
* @param message mensagem a ser enviada
* @throws IOException se ocorrer um erro durante o envio
*/
public synchronized void send(byte[] message) throws IOException {
output.writeInt(message.length);
output.write(message);
output.flush();
updateLastActivity();
}
/**
* Recebe uma mensagem da conexão.
*
* <p>Primeiro lê o tamanho da mensagem e, em seguida, lê aquantidade
* correspondente de bytes.</p>
*
* @return mensagem recebida
* @throws IOException se o tamanho da mensagem for inválido ou se a conexão
* for encerrada antes que a mensagem seja completamente recebida
*/
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;
}
/**
* Verifica se o socket da conexão está fechado.
*
* @return {@code true} se o socket estiver fechado;
* {@code false} caso contrário
*/
public boolean isClosed() {
return socket.isClosed();
}
/**
* Fecha o socket da conexão.
*
* @throws IOException se ocorrer um erro ao fechar o socket
*/
void close() throws IOException {
socket.close();
}
/**
* Retorna o endereço do peer remoto conectado.
*
* @return endereço IP do peer remoto
*/
public InetAddress getAddress() {
return address;
}
/**
* Atualiza o instante da última atividade da conexão.
*/
private void updateLastActivity() {
lastActivity = System.currentTimeMillis();
}
/**
* Define o tempo limite de leitura do socket.
*
* @param timeout tempo limite, em milissegundos
* @throws SocketException se ocorrer um erro ao definir o tempo limite
*/
void setTimeout(int timeout) throws SocketException {
socket.setSoTimeout(timeout);
this.timeout = timeout;
}
/**
* Verifica se a conexão ficou inativa pelo tempo limite configurado.
*
* <p>A verificação é baseada no instante da última atividade registrada
* durante o envio ou recebimento de uma mensagem.</p>
*
* @return {@code true} se a conexão estiver inativa há pelo menos 5 minutos;
* {@code false} caso contrário
*/
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;
}
}
@@ -1,264 +0,0 @@
package pitiupi.net.tcp;
import pitiupi.models.Messages.InfoType;
import pitiupi.models.User;
import pitiupi.control.PeerListener;
import pitiupi.models.Messages.InfoMessage;
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;
/**
* Gerencia as conexões TCP estabelecidas com os peers da aplicação.
*
* <p>As conexões públicas são armazenadas individualmente por endereço IP,
* enquanto as conexões privadas são agrupadas por endereço, permitindo que
* um mesmo peer mantenha múltiplas conexões privadas simultaneamente.</p>
*
* <p>Além de manter o registro das conexões ativas, esta classe é responsável
* por encerrá-las quando necessário e por remover do gerenciamento as
* conexões que forem fechadas. Também acompanha as alterações na lista de
* peers ativos para encerrar conexões associadas a peers que deixaram de
* estar disponíveis.</p>
*/
class TcpConnectionManager implements PeerListener {
private final Logger logger;
private final Map<InetAddress, TcpConnection> publicConnections;
private final Map<InetAddress, List<TcpConnection>> privateConnections;
/**
* Cria um gerenciador de conexões TCP vazio.
*/
public TcpConnectionManager() {
publicConnections = new ConcurrentHashMap<>();
privateConnections = new ConcurrentHashMap<>();
logger = Logger.getLogger(getClass().getName());
}
/**
* Obtém a conexão pública registrada para um determinado peer.
*
* @param address endereço IP do peer.
* @return a conexão pública registrada para o endereço ou {@code null}
* caso não exista uma conexão pública registrada.
*/
public TcpConnection getPublicConnection(InetAddress address) {
return publicConnections.get(address);
}
/**
* Registra uma conexão privada associada a um peer.
*
* <p>Um mesmo peer pode possuir múltiplas conexões privadas. Por isso,
* as conexões são armazenadas em uma lista associada ao endereço do
* peer.</p>
*
* @param connection conexão privada a ser registrada.
*/
public void addPrivateConnection(TcpConnection connection) {
List<TcpConnection> connections =
privateConnections.computeIfAbsent(connection.getAddress(), e -> new ArrayList<>());
synchronized (connections) {
connections.add(connection);
}
}
/**
* Registra uma conexão pública associada a um peer.
*
* <p>Cada peer pode possuir apenas uma conexão pública registrada.
* Quando já existe uma conexão pública para o endereço da nova conexão,
* a conexão anterior é encerrada e substituída pela nova.</p>
*
* @param connection nova conexão pública a ser registrada.
*/
public void addPublicConnection(TcpConnection connection) {
TcpConnection oldConnection = publicConnections.put(connection.getAddress(), connection);
if (oldConnection != null && oldConnection != connection) {
closeConnection(oldConnection);
}
}
/**
* Envia uma mensagem de encerramento ao peer e fecha a conexão.
*
* <p>A mensagem enviada informa o motivo do encerramento antes que a
* conexão seja removida do gerenciamento e fechada.</p>
*
* @param connection conexão TCP a ser encerrada.
* @param reason motivo do encerramento que será informado ao peer.
*/
public void closeConnection(TcpConnection connection, String reason) {
InfoMessage.sendSystemInfo(connection, InfoType.CONNECTION_CLOSED, reason);
closeConnection(connection);
}
/**
* Remove uma conexão do gerenciamento e encerra seu socket.
*
* <p>Para conexões privadas, a conexão é removida da lista associada ao
* endereço do peer. Caso essa seja a última conexão privada do endereço,
* a lista também é removida do mapa.</p>
*
* <p>Para conexões públicas, a conexão é removida do mapa somente se
* ainda for a conexão registrada para seu endereço.</p>
*
* <p>Se a conexão já estiver fechada ou for {@code null}, nenhuma ação
* adicional de fechamento é realizada.</p>
*
* @param connection conexão TCP a ser removida e encerrada.
*/
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);
}
}
/**
* Envia uma mensagem de encerramento para todas as conexões de um peer
* e, em seguida, fecha essas conexões.
*
* @param address endereço IP do peer cujas conexões serão encerradas.
* @param reason motivo do encerramento informado ao peer.
*/
public void closeByAdress(InetAddress address, String reason) {
List<TcpConnection> connections = privateConnections.get(address);
for (TcpConnection connection : connections) {
InfoMessage.sendSystemInfo(connection, InfoType.CONNECTION_CLOSED, reason);
}
closeByAdress(address);
}
/**
* Fecha e remove todas as conexões associadas a um determinado endereço.
*
* <p>Tanto a conexão pública quanto todas as conexões privadas associadas
* ao endereço são encerradas e removidas do gerenciamento.</p>
*
* @param address endereço IP do peer cujas conexões serão encerradas.
*/
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();
}
}
}
/**
* Envia uma mensagem de encerramento para todas as conexões privadas
* gerenciadas e, em seguida, fecha todas elas.
*
* @param reason motivo do encerramento informado aos peers.
*/
public void closeAllConnections(String reason) {
for (List<TcpConnection> connections : privateConnections.values()) {
for (TcpConnection connection : connections) {
InfoMessage.sendSystemInfo(connection, InfoType.CONNECTION_CLOSED, reason);
}
}
closeAllConnections();
}
/**
* Fecha e remove todas as conexões públicas e privadas gerenciadas.
*
* <p>Após a execução, não devem permanecer conexões registradas nos
* mapas de conexões deste gerenciador.</p>
*/
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>Os endereços presentes na lista de peers são utilizados como
* referência para determinar quais conexões ainda devem permanecer
* ativas. Conexões públicas e privadas associadas a endereços que não
* estão mais presentes na lista de peers são encerradas e removidas.</p>
*
* @param peers lista atualizada de peers ativos.
*/
@Override
public void onPeersChanged(List<User> peers) {
Set<InetAddress> activePeers = peers.stream().map(User::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);
}
}
}
}
+188
View File
@@ -0,0 +1,188 @@
package pitiupi.net.tcp;
import pitiupi.models.ConnectionData;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;
final class TcpFactory {
private static final int HANDSHAKE_TIMEOUT = 1000;
private static final int DISABLE_TIMEOUT = 0;
TcpManager manager;
TcpFactory(TcpManager manager) {
this.manager = manager;
}
TcpPitiupi createPitiupiConnection(InetAddress receiverAddress, int port) {
Socket socket = null;
try {
socket = new Socket(receiverAddress, port);
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
sendHandshake(dataOutputStream);
socket.setSoTimeout(HANDSHAKE_TIMEOUT);
TcpHandshakeResponse response = receiveHandshakeResponse(dataInputStream);
socket.setSoTimeout(DISABLE_TIMEOUT);
if (response.getSucess()) return new TcpPitiupi(socket, dataInputStream, dataOutputStream);
}
catch (IOException | ClassNotFoundException e) {
Logger.getLogger(TcpFactory.class.getName()).log(Level.SEVERE, null, e);
}
if (socket != null) tryClose(socket);
return null;
}
TcpClient createClient(ConnectionData serverData, String pluginIdentifier, String username, InetAddress receiverAddress, int port) {
Socket socket = null;
try {
socket = new Socket(receiverAddress, port);
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
ConnectionData clientData = new ConnectionData(
username,
receiverAddress,
pluginIdentifier,
port,
false);
sendHandshake(dataOutputStream, serverData, clientData);
socket.setSoTimeout(HANDSHAKE_TIMEOUT);
TcpHandshakeResponse response = receiveHandshakeResponse(dataInputStream);
socket.setSoTimeout(DISABLE_TIMEOUT);
if (response.getSucess()) return new TcpClient(clientData, serverData, socket, dataInputStream, dataOutputStream);
}
catch (IOException | ClassNotFoundException e) {
Logger.getLogger(TcpFactory.class.getName()).log(Level.SEVERE, null, e);
}
if (socket != null) tryClose(socket);
return null;
}
TcpServer createServer(String pluginIdentifier, String username, InetAddress receiverAddress, int port) {
ConnectionData data = new ConnectionData(
username,
receiverAddress,
pluginIdentifier,
port,
true);
return new TcpServer(data);
}
TcpPitiupi createFromReceiver(Socket socket) throws IOException {
DataOutputStream dataOutputStream = null;
DataInputStream dataInputStream = null;
try {
dataOutputStream = new DataOutputStream(socket.getOutputStream());
dataInputStream = new DataInputStream(socket.getInputStream());
socket.setSoTimeout(HANDSHAKE_TIMEOUT);
TcpHandshake tcpHandshake = receiveHandshake(dataInputStream);
socket.setSoTimeout(DISABLE_TIMEOUT);
if (tcpHandshake.isPrivate()) {
ConnectionData clientData = tcpHandshake.getClientData();
ConnectionData serverData = tcpHandshake.getServerData();
if (manager.findServer(serverData) == null) {
sendHandshakeResponse(false, dataOutputStream);
tryClose(socket);
return null;
}
sendHandshakeResponse(true, dataOutputStream);
return new TcpClient(clientData, serverData, socket, dataInputStream, dataOutputStream);
}
else {
sendHandshakeResponse(true, dataOutputStream);
return new TcpPitiupi(socket, dataInputStream, dataOutputStream);
}
}
catch (IOException | ClassNotFoundException e) {
Logger.getLogger(TcpFactory.class.getName()).log(Level.SEVERE, null, e);
}
sendHandshakeResponse(false, dataOutputStream);
tryClose(socket);
return null;
}
private void sendHandshake(DataOutputStream output, ConnectionData serverData, ConnectionData clientData) throws IOException {
TcpHandshake handshake = TcpHandshake.createPrivate(serverData, clientData);
byte[] message = handshake.toByteArray();
sendMessage(message, output);
}
private void sendHandshake(DataOutputStream output) throws IOException {
TcpHandshake handshake = TcpHandshake.createPublic();
byte[] message = handshake.toByteArray();
sendMessage(message, output);
}
private void sendHandshakeResponse(boolean success, DataOutputStream output) throws IOException {
TcpHandshakeResponse response = new TcpHandshakeResponse(success);
byte[] message = response.toByteArray();
sendMessage(message, output);
}
private void sendMessage(byte[] message, DataOutputStream output) throws IOException {
output.writeInt(message.length);
output.write(message);
output.flush();
}
private TcpHandshake receiveHandshake(DataInputStream input) throws IOException, ClassNotFoundException {
byte[] msg = receiveMessage(input);
if (byteToObject(msg) instanceof TcpHandshake handshake) return handshake;
else throw new IOException("Not a handshake");
}
private TcpHandshakeResponse receiveHandshakeResponse(DataInputStream input) throws IOException, ClassNotFoundException {
byte[] msg = receiveMessage(input);
if (byteToObject(msg) instanceof TcpHandshakeResponse response) return response;
else throw new IOException("Not a handshake");
}
private byte[] receiveMessage(DataInputStream input) throws IOException {
int length = input.readInt();
if (length < 0) throw new IOException("Invalid length");
byte[] msg = input.readNBytes(length);
if (msg.length != length) throw new IOException("Socket closed while receiving data");
return msg;
}
private Object byteToObject(byte[] bytes) throws IOException, ClassNotFoundException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInput in = new ObjectInputStream(bais);
return in.readObject();
}
public void tryClose(Socket socket) {
try {
socket.close();
}
catch (IOException ex) {
Logger.getLogger(TcpFactory.class.getName()).log(Level.WARNING, null, ex);
}
}
}
@@ -0,0 +1,36 @@
package pitiupi.net.tcp;
import pitiupi.models.ConnectionData;
import pitiupi.models.Messages.Message;
final class TcpHandshake extends Message {
private final boolean isPrivate;
private final ConnectionData serverData;
private final ConnectionData clientData;
private TcpHandshake(boolean isPrivate, ConnectionData serverData, ConnectionData clientData) {
this.isPrivate = isPrivate;
this.serverData = serverData;
this.clientData = clientData;
}
static TcpHandshake createPrivate(ConnectionData serverData, ConnectionData clientData) {
return new TcpHandshake(true, serverData, clientData);
}
static TcpHandshake createPublic() {
return new TcpHandshake(false, null, null);
}
public boolean isPrivate() {
return isPrivate;
}
public ConnectionData getServerData() {
return serverData;
}
public ConnectionData getClientData() {
return clientData;
}
}
@@ -0,0 +1,15 @@
package pitiupi.net.tcp;
import pitiupi.models.Messages.Message;
class TcpHandshakeResponse extends Message {
private final boolean sucess;
TcpHandshakeResponse(boolean sucess) {
this.sucess = sucess;
}
public boolean getSucess() {
return sucess;
}
}
+163
View File
@@ -0,0 +1,163 @@
package pitiupi.net.tcp;
import pitiupi.models.ConnectionData;
import pitiupi.models.Messages.Message;
import java.io.*;
import java.net.*;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
public final class TcpManager extends Thread{
private final TcpFactory tcpFactory;
private final Map<ConnectionData, TcpServer> servers;
private final Map<InetAddress, TcpPitiupi> connections;
private final ExecutorService connectionExecutor;
private final ServerSocket serverSocket;
private final Consumer<byte[]> handleMessage;
private boolean isRunning;
public TcpManager(int port, Consumer<byte[]> handleMessage) throws IOException {
servers = new ConcurrentHashMap<>();
connections = new ConcurrentHashMap<>();
tcpFactory = new TcpFactory(this);
connectionExecutor = Executors.newCachedThreadPool();
serverSocket = new ServerSocket(port);
this.handleMessage = handleMessage;
}
public TcpServer getServer(String pluginIdentifier, String username, InetAddress userAddress, int port) {
TcpServer tcpServer = tcpFactory.createServer(pluginIdentifier, username, userAddress, port);
servers.put(tcpServer.getConnectionData(), tcpServer);
return tcpServer;
}
public TcpClient getClient(ConnectionData serverData, String pluginIdentifier, String username) {
return tcpFactory.createClient(serverData, pluginIdentifier, username, serverData.getOwnerAddress(), serverData.getPort());
}
private TcpPitiupi createConnection(InetAddress receiverAddress, int port) {
TcpPitiupi tcpPitiupi = tcpFactory.createPitiupiConnection(receiverAddress, port);
manageConnectionList(tcpPitiupi, receiverAddress);
startConnectionReceiver(tcpPitiupi);
return tcpPitiupi;
}
public boolean send(Message message, InetAddress receiverAddress, int port) {
TcpPitiupi tcpPitiupi = findConnection(receiverAddress);
if (tcpPitiupi == null || tcpPitiupi.isClosed()) tcpPitiupi = createConnection(receiverAddress, port);
try {
tcpPitiupi.send(message.toByteArray());
return true;
}
catch (IOException ex) {
Logger.getLogger(TcpManager.class.getName()).log(Level.WARNING, null, ex);
return false;
}
}
private void receive() {
while (isRunning) {
Socket socket = null;
try {
socket = serverSocket.accept();
processConnection(socket);
}
catch (SocketException e) {
if (isRunning) {
Logger.getLogger(TcpManager.class.getName()).log(Level.SEVERE, "Erro no servidor TCP.", e);
}
}
catch (IOException e) {
Logger.getLogger(TcpManager.class.getName()).log(Level.WARNING, "Erro ao aceitar conexão.", e);
}
}
}
private void processConnection(Socket socket) {
try {
TcpPitiupi tcpPitiupi = tcpFactory.createFromReceiver(socket);
if (tcpPitiupi == null) return;
if (tcpPitiupi instanceof TcpClient client) {
ConnectionData serverData = client.getServerData();
TcpServer tcpServer = servers.get(serverData);
tcpServer.addClient(client);
}
else {
manageConnectionList(tcpPitiupi, socket.getInetAddress());
startConnectionReceiver(tcpPitiupi);
}
} catch (IOException e) {
Logger.getLogger(TcpClient.class.getName()).log(Level.SEVERE, null, e);
}
}
public void closeManager() {
try {
isRunning = false;
serverSocket.close();
}
catch (IOException e) {
Logger.getLogger(TcpManager.class.getName()).log(Level.WARNING, null, e);
}
servers.clear();
connections.clear();
}
public void closeServer(TcpServer tcpServer) {
ConnectionData serverData = tcpServer.getConnectionData();
if (serverData.isServer()) servers.remove(serverData);
tcpServer.close();
}
private TcpPitiupi findConnection(InetAddress receiverAddress) {
return connections.get(receiverAddress);
}
public TcpServer findServer(ConnectionData serverData) {
return servers.get(serverData);
}
private void manageConnectionList(TcpPitiupi tcpPitiupi, InetAddress receiverAddress) {
TcpPitiupi oldPitiupiConnection = connections.put(receiverAddress, tcpPitiupi);
if (oldPitiupiConnection != null) {
tcpFactory.tryClose(oldPitiupiConnection.getSocket());
}
}
private void startConnectionReceiver(TcpPitiupi tcpPitiupi) {
connectionExecutor.submit(() -> {
while (!tcpPitiupi.isClosed()) {
try {
handleMessage.accept(tcpPitiupi.receive());
} catch (IOException e) {
Logger.getLogger(TcpManager.class.getName()).log(Level.SEVERE, null, e);
break;
}
}
});
}
@Override
public void run() {
isRunning = true;
receive();
}
}
@@ -0,0 +1,70 @@
package pitiupi.net.tcp;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
class TcpPitiupi {
protected final Socket socket;
protected final DataInputStream input;
protected final DataOutputStream output;
public TcpPitiupi(Socket socket, DataInputStream input, DataOutputStream output) {
this.socket = socket;
this.input = input;
this.output = output;
}
/**
* Envia uma mensagem pela conexão.
*
* <p>A mensagem é enviada precedida pelo seu tamanho, permitindo que o
* receptor determine quantos bytes devem ser lidos.</p>
*
* @param message mensagem a ser enviada
* @throws IOException se ocorrer um erro durante o envio
*/
public void send(byte[] message) throws IOException {
output.writeInt(message.length);
output.write(message);
output.flush();
}
/**
* Recebe uma mensagem da conexão.
*
* <p>Primeiro lê o tamanho da mensagem e, em seguida, lê aquantidade
* correspondente de bytes.</p>
*
* @return mensagem recebida
* @throws IOException se o tamanho da mensagem for inválido ou se a conexão
* for encerrada antes que a mensagem seja completamente recebida
*/
public 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.");
}
return message;
}
public boolean isClosed() {
return socket.isClosed();
}
Socket getSocket() {
return socket;
}
public void close() throws IOException {
socket.close();
}
}
@@ -0,0 +1,61 @@
package pitiupi.net.tcp;
import pitiupi.models.ConnectionData;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
public final class TcpServer {
private final ConnectionData connectionData;
private final BlockingQueue<TcpClient> buffer;
private final Map<ConnectionData, TcpClient> clients;
TcpServer(ConnectionData connectionData) {
this.connectionData = connectionData;
this.buffer = new LinkedBlockingQueue<>();
this.clients = new ConcurrentHashMap<>();
}
void addClient(TcpClient tcpClient) {
buffer.add(tcpClient);
clients.put(tcpClient.getClientData(), tcpClient);
}
public TcpClient receive() {
try {
return buffer.take();
}
catch (InterruptedException e) {
Logger.getLogger(TcpServer.class.getName()).log(Level.SEVERE, null, e);
return null;
}
}
public List<ConnectionData> getClients() {
return clients.keySet().stream().toList();
}
public ConnectionData getConnectionData() {
return connectionData;
}
void close() {
for (TcpClient tcpClient : clients.values()) {
try {
tcpClient.close();
} catch (IOException e) {
Logger.getLogger(TcpServer.class.getName()).log(Level.WARNING, null, e);
}
}
buffer.clear();
clients.clear();
}
}
+9 -3
View File
@@ -2,10 +2,12 @@ package pitiupi.plugin;
import pitiupi.control.ConnectionManager; import pitiupi.control.ConnectionManager;
import pitiupi.control.GuiManager; import pitiupi.control.GuiManager;
import pitiupi.models.ConnectionData;
import pitiupi.models.Menus.Menu; import pitiupi.models.Menus.Menu;
import pitiupi.models.Menus.MenuCategory; import pitiupi.models.Menus.MenuCategory;
import pitiupi.models.Messages.Message; import pitiupi.models.Messages.Message;
import pitiupi.net.tcp.TcpConnection; import pitiupi.net.tcp.TcpClient;
import pitiupi.net.tcp.TcpServer;
import java.net.InetAddress; import java.net.InetAddress;
@@ -31,8 +33,12 @@ public class Pitiupi {
return connectionManager.sendTcp(message, address); return connectionManager.sendTcp(message, address);
} }
public TcpConnection connectTcp(InetAddress address, String senderId, String receiverId) { public TcpServer openTcpServer(String pluginId) {
return connectionManager.connectPluginTcp(address, senderId, receiverId); return connectionManager.getTcpServer(pluginId);
}
public TcpClient connectToTcpServer(ConnectionData connectionData, String pluginId) {
return connectionManager.connectToTcpServer(connectionData, pluginId);
} }
public String getUserName() { public String getUserName() {