Compare commits

...

52 Commits

Author SHA1 Message Date
UnlegitDqrk
700cff0294 Bug fix 2026-02-28 15:34:05 +01:00
UnlegitDqrk
8ef2f0291f Bug fix 2026-02-27 20:30:24 +01:00
UnlegitDqrk
d5b880d70c Small bug fix 2026-02-22 18:30:57 +01:00
UnlegitDqrk
e20ee03682 Updated license info 2026-02-22 16:16:14 +01:00
UnlegitDqrk
d715c0758d Implemented InfoNameLib 2026-02-22 16:00:57 +01:00
UnlegitDqrk
abc989a675 Small changes 2026-02-22 14:49:10 +01:00
UnlegitDqrk
632e71707d Introduce 1.0.1-BETA packet model with 1.0.0-BETA backwards compatibility 2026-02-21 23:21:51 +01:00
UnlegitDqrk
dc757d4fcb Introduce 1.0.1-BETA packet model with 1.0.0-BETA backwards compatibility 2026-02-21 23:15:49 +01:00
UnlegitDqrk
a7ce9982c9 Introduce 1.0.1-BETA packet model with 1.0.0-BETA backwards compatibility 2026-02-21 23:12:43 +01:00
UnlegitDqrk
0841992363 Removed unused licenses 2026-02-14 19:15:15 +01:00
UnlegitDqrk
6107cf85be Removed unused licenses 2026-02-14 19:14:46 +01:00
UnlegitDqrk
9e54fe4b46 INS bug fix (resolving default InfoNames) 2026-02-14 17:23:33 +01:00
UnlegitDqrk
cd58d37ea2 INS bug fix (resolving default InfoNames) 2026-02-14 17:22:34 +01:00
UnlegitDqrk
fb8ff372d3 License download bug fix 2026-02-14 15:01:17 +01:00
UnlegitDqrk
ae98225043 Finished protocol 2026-02-11 23:11:33 +01:00
UnlegitDqrk
23a3293060 Bug fixes 2026-02-08 22:01:19 +01:00
UnlegitDqrk
c2e47a8a1e Bug fixes 2026-02-08 19:04:01 +01:00
UnlegitDqrk
8f00dcb5df Added default tcp web port (1028) 2026-02-08 18:23:40 +01:00
UnlegitDqrk
c855877530 Fixing trust issue 2026-02-08 14:47:30 +01:00
UnlegitDqrk
566cddc33f Bug fixes 2026-02-06 22:39:26 +01:00
UnlegitDqrk
0e5e0b5668 Reformatted using IntelliJ 2026-02-06 17:59:04 +01:00
UnlegitDqrk
68aa9c1df2 Some small changes (maven version deployed yet) 2026-02-06 17:53:01 +01:00
UnlegitDqrk
cdf83958c9 Security fix 2026-02-06 17:29:41 +01:00
Finn
e7954cfb0b Updated to latest UnlegitLibrary Version 2026-02-01 19:05:57 +01:00
Finn
238214c1e9 Updated to latest UnlegitLibrary Version 2026-02-01 19:03:51 +01:00
Finn
3ea6723cf9 Updated to latest UnlegitLibrary Version 2026-02-01 17:13:33 +01:00
Finn
0a0618a680 Changed JavaDoc to English 2026-01-19 18:59:59 +01:00
Finn
9958306572 Bug fix 2026-01-19 14:21:46 +01:00
Finn
b244633131 Bug fix 2026-01-18 22:38:59 +01:00
Finn
a2c5d67e8a Bug fix 2026-01-18 22:38:22 +01:00
Finn
a345b81846 Bug fix 2026-01-18 22:36:53 +01:00
Finn
fb56d47b16 Bug fix 2026-01-18 21:58:42 +01:00
Finn
50cd7b57ac Updated to latest UnlegitLibrary version and implemented UDP 2026-01-18 21:48:43 +01:00
Finn
da254a6c8e Added builtin ClassicHandler 2026-01-18 18:17:35 +01:00
Finn
50eac7dbd5 Fixed NullPointerException 2026-01-18 15:28:19 +01:00
Finn
c408c94288 Fixed NullPointerException 2026-01-18 15:27:05 +01:00
Finn
5d028a45a6 Version changing 2026-01-18 14:58:34 +01:00
Tinglyyy
3f58bf2c01 Fixed NullPointerException on Client Bridge initialization that occurred because of a copy & paste mistake (wrongly assumed the protocolINSServer was set for the client) 2026-01-18 12:24:31 +01:00
5e596a4cf7 Update LICENSE 2026-01-16 22:14:21 +00:00
Finn
bf978f48b0 Bug fixes 2025-12-13 16:47:30 +01:00
Finn
6cb9f55804 Added licenses 2025-12-13 15:53:23 +01:00
Finn
0347274af3 Added licenses 2025-12-13 15:51:43 +01:00
Finn
4fb1488c9c Added missing option to connect to WebServer 2025-12-12 20:57:24 +01:00
Finn
ebccdf5b36 Added missing option to connect to WebServer 2025-12-12 20:57:01 +01:00
Finn
a00a3b319f Updated listener to Getters 2025-12-12 19:20:27 +01:00
Finn
d5b5a7d8b0 Added Getter to Packets 2025-12-12 19:17:22 +01:00
Finn
e37a76af56 Finished up WebServer-Protocol 2025-12-12 18:19:56 +01:00
Finn
4108b04582 Changed version name 2025-12-11 11:58:37 +01:00
Finn
c2b72da849 Lowered POM Version 2025-12-11 11:54:26 +01:00
Finn
fb3086c28e Constructor fixes 2025-12-11 11:50:47 +01:00
Finn
95dd467634 Added some Record Tools 2025-12-11 11:32:52 +01:00
Finn
8c64f67538 Updated version of UnlegitLibrary 2025-12-11 10:44:41 +01:00
102 changed files with 7249 additions and 3324 deletions

2
.idea/misc.xml generated
View File

@@ -13,7 +13,7 @@
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_23" default="true" project-jdk-name="23" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" default="true" project-jdk-name="25" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

70
LICENSE
View File

@@ -1,68 +1,2 @@
Open Autonomous Public License (OAPL) v1.0
Copyright (C) 2024-2025 Open Autonomous Connection (OAC)
Projekt-URL: https://open-autonomous-connection.org/
---
1. Nutzungsrechte
Diese Software darf sowohl für private als auch kommerzielle Zwecke genutzt werden. Die Nutzung ist unter den Bedingungen dieser Lizenz gestattet.
---
2. Verkaufsverbot
Es ist nicht gestattet, diese Software oder abgeleitete Werke davon zu verkaufen oder kommerziell zu vertreiben.
Dies umfasst auch jede Form der direkten oder indirekten Monetarisierung der Software selbst.
Es ist gestattet, die Software im Rahmen von Dienstleistungen kommerziell zu nutzen, solange der Quellcode und die Originaldateien kostenlos verfügbar bleiben und nicht gegen Entgelt verkauft oder monetarisiert werden.
Jede Form der Monetarisierung der Software selbst, wie der Verkauf oder die Lizenzierung der Software, ist untersagt.
---
3. Offenlegung des Quellcodes
Die Software ist dauerhaft quelloffen. Der vollständige Quellcode muss bei jeder Verbreitung, auch in geänderter Form, mitgeliefert oder öffentlich zugänglich gemacht werden.
Jede Version, auch veränderte, muss einen klar sichtbaren Verweis auf das Originalprojekt enthalten:
→ https://github.com/Open-Autonomous-Connection/
---
4. Weitergabe & Lizenzvererbung
Die Software darf frei kopiert, verteilt und verändert werden, unter folgenden Bedingungen:
- Die Original-Lizenz (OAPL v1.0) muss vollständig und unverändert mitgeliefert werden.
- Alle Änderungen am Quellcode müssen klar kenntlich gemacht werden (z.B. durch Kommentare oder Änderungsprotokolle).
- Abgeleitete Werke müssen unter derselben Lizenz (OAPL v1.0) veröffentlicht werden, es sei denn, die Lizenzierung erfolgt in einem Kontext,
in dem dies durch geltendes Recht oder technische Einschränkungen nicht möglich ist. In diesem Fall muss der Quellcode der Änderungen weiterhin offen und zugänglich gemacht werden.
- Es dürfen keine zusätzlichen Einschränkungen oder Bedingungen auferlegt werden, die den Bedingungen dieser Lizenz widersprechen.
---
5. Keine proprietäre Nutzung
Die Software oder ihre abgeleiteten Werke dürfen nicht in proprietäre Software integriert oder unter einer Lizenz weitergegeben werden, die die Bedingungen dieser Lizenz einschränkt oder die Offenlegung des Quellcodes unterlässt.
---
6. Keine Veränderung dieser Lizenz
Diese Lizenz darf nicht verändert oder durch andere Lizenzen ersetzt werden. Eine Modifikation oder Re-Lizenzierung ist ausdrücklich untersagt.
---
7. Haftungsausschluss ("as-is")
DIE SOFTWARE WIRD 'WIE BESEHEN' BEREITGESTELLT, OHNE AUSDRÜCKLICHE ODER IMPLIZIERTE GARANTIEN, EINSCHLIESSLICH, ABER NICHT BESCHRÄNKT AUF GARANTIEN DER MARKTGÄNGIGKEIT,
DER EIGNUNG FÜR EINEN BESTIMMTEN ZWECK UND DER NICHTVERLETZUNG VON RECHTEN DRITTER. SOWEIT ES GESCHÄFTSRECHTLICH ZULÄSSIG IST,
WIRD DIE HAFTUNG DER AUTOREN FÜR SCHÄDEN ODER VERLUSTE AUFGRUND DER NUTZUNG DER SOFTWARE AUSGESCHLOSSEN.
---
8. Salvatorische Klausel
Sollte eine Bestimmung dieser Lizenz als unwirksam, undurchsetzbar oder nicht durchsetzbar erklärt werden,
bleibt die Gültigkeit der übrigen Bestimmungen davon unberührt. In diesem Fall wird die unwirksame Klausel durch eine wirksame und durchsetzbare Bestimmung ersetzt,
die dem ursprünglichen wirtschaftlichen Zweck der unwirksamen Bestimmung am nächsten kommt.
---
Ende der Lizenzbedingungen.
Additional Notice regarding UnlegitLibrary:
UnlegitLibrary is primarily distributed under the GNU GPLv3.
For the purposes of the Open Autonomous Connection (OAC) project,
the author has also licensed UnlegitLibrary under the Open Autonomous Public License (OAPL).
Within OAC, the OAPL terms apply to UnlegitLibrary.
Please read the license here: https://open-autonomous-connection.org/license.html
Download all third parties licenses here: https://open-autonomous-connection.org/assets/licenses.zip

View File

@@ -5,30 +5,17 @@ You can easily implement this Protocol via Maven.<br />
Feel free to join our Discord.
<br />
## License Notice
This project (OAC) is licensed under
the [Open Autonomous Public License (OAPL)](https://open-autonomous-connection.org/license.html).
**Third-party components:**
<br />
Download all license here: https://open-autonomous-connection.org/assets/licenses.zip
- *UnlegitLibrary* is authored by the same copyright holder and is used here under a special agreement:
While [UnlegitLibrary](https://repo.unlegitdqrk.dev/UnlegitDqrk/unlegitlibrary/) is generally distributed under
the [GNU GPLv3](https://repo.unlegitdqrk.dev/UnlegitDqrk/unlegitlibrary/src/branch/master/LICENSE),
it is additionally licensed under OAPL **exclusively for the OAC project**.
Therefore, within OAC, the OAPL terms apply to UnlegitLibrary as well.<br/>
<br/>
Therefore, within OAC, the OAPL terms apply to UnlegitLibrary as well.
### Take a look into [Certificate Generation](https://repo.unlegitdqrk.dev/UnlegitDqrk/unlegitlibrary/src/branch/master#certificate-generation-for-networksystem).
# Bugs/Problems
- Project not tested yet
# In progress
- WebServer
# TODO
- Finishing WebServer
- Writing INS Backend and Frontend
- Writing WebClient
- Writing WebServer
- Documentation / Wiki
# Maven
### pom.xml
@@ -36,7 +23,7 @@ the [Open Autonomous Public License (OAPL)](https://open-autonomous-connection.o
```
<dependency>
<groupId>org.openautonomousconnection</groupId>
<artifactId>protocol</artifactId>
<artifactId>Protocol</artifactId>
<version>VERSION</version>
</dependency>
```

91
pom.xml
View File

@@ -5,8 +5,8 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.openautonomousconnection</groupId>
<artifactId>protocol</artifactId>
<version>1.0.0-BETA.11</version>
<artifactId>Protocol</artifactId>
<version>1.0.1-BETA.0.6</version>
<organization>
<name>Open Autonomous Connection</name>
<url>https://open-autonomous-connection.org/</url>
@@ -15,20 +15,11 @@
<description>The Protocol for Server and Client</description>
<properties>
<maven.compiler.source>23</maven.compiler.source>
<maven.compiler.target>23</maven.compiler.target>
<maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<profiles>
<profile>
<id>github</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
</profiles>
<developers>
<developer>
<name>UnlegitDqrk</name>
@@ -65,6 +56,13 @@
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>oac</id>
<url>https://repo.open-autonomous-connection.org/api/packages/open-autonomous-connection/maven</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<distributionManagement>
@@ -77,9 +75,8 @@
<licenses>
<license>
<name>Open Autonomous Public License</name>
<name>Open Autonomous Public License (OAPL)</name>
<url>https://open-autonomous-connection.org/license.html</url>
<distribution>repo</distribution>
</license>
</licenses>
@@ -87,12 +84,12 @@
<dependency>
<groupId>dev.unlegitdqrk</groupId>
<artifactId>unlegitlibrary</artifactId>
<version>1.6.6</version>
<version>1.8.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.38</version>
<version>1.18.42</version>
<scope>provided</scope>
</dependency>
<dependency>
@@ -100,47 +97,40 @@
<artifactId>gson</artifactId>
<version>2.13.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-fileupload2-jakarta-servlet6</artifactId>
<version>2.0.0-M4</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-fileupload2</artifactId>
<version>2.0.0-M4</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-fileupload2-core</artifactId>
<version>2.0.0-M4</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.20.0</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>LATEST</version>
<version>1.18.5</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>LATEST</version>
<version>1.18.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<compilerArgs>
<arg>-proc:full</arg>
</compilerArgs>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.42</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
@@ -158,6 +148,15 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.6.3</version>
<configuration>
<failOnError>false</failOnError>
<failOnWarnings>false</failOnWarnings>
<doclint>none</doclint>
<locale>en_US</locale>
<encoding>UTF-8</encoding>
<docencoding>UTF-8</docencoding>
<charset>UTF-8</charset>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>

View File

@@ -1,32 +1,43 @@
package org.openautonomousconnection.protocol;
import dev.unlegitdqrk.unlegitlibrary.utils.Logger;
import dev.unlegitdqrk.unlegitlibrary.file.FileUtils;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.ClientAuthMode;
import lombok.Getter;
import lombok.Setter;
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
import org.openautonomousconnection.protocol.listeners.ClientListener;
import org.openautonomousconnection.protocol.listeners.INSServerListener;
import org.openautonomousconnection.protocol.listeners.WebServerListener;
import org.openautonomousconnection.protocol.listeners.CustomServerListener;
import org.openautonomousconnection.protocol.packets.OACPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.AuthPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSQueryPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSResponsePacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.UnsupportedClassicPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.classic.Classic_DomainPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.classic.Classic_MessagePacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.classic.Classic_PingPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamChunkPacket_v1_0_0_B;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamEndPacket_v1_0_0_B;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamStartPacket_v1_0_0_B;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentApplyRequestPacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentApplyResponsePacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentSnapshotEventPacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate.WebNavigateAckPacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate.WebNavigateRequestPacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceRequestPacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamChunkPacket_v1_0_1_B;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamEndPacket_v1_0_1_B;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamStartPacket_v1_0_1_B;
import org.openautonomousconnection.protocol.side.client.ProtocolClient;
import org.openautonomousconnection.protocol.side.ins.ProtocolINSServer;
import org.openautonomousconnection.protocol.side.server.ProtocolCustomServer;
import org.openautonomousconnection.protocol.side.web.ProtocolWebServer;
import org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta.OacWebUrlInstaller_v1_0_0_B;
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.LibClientImpl_v1_0_1_B;
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.OacUrlHandlerInstaller_v1_0_1_B;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.ClassicHandlerClient;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.ClassicHandlerINSServer;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.ClassicHandlerWebServer;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.utils.Classic_ClientListener;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.ProtocolWebServer_1_0_0_B;
import java.io.File;
import java.io.IOException;
import java.net.Proxy;
import java.util.function.Supplier;
/**
* The main bridge class for the protocol connection.
@@ -37,25 +48,7 @@ public final class ProtocolBridge {
* The protocol settings for the current connection
*/
@Getter
private final ProtocolSettings protocolSettings;
/**
* The protocol version for the current connection
*/
@Getter
private final ProtocolVersion protocolVersion;
/**
* The logger instance for logging events and errors
*/
@Getter
private Logger logger;
/**
* The protocol side instances
*/
@Getter
private ProtocolINSServer protocolINSServer;
private final ProtocolValues protocolValues;
/**
* The protocol side instances
@@ -63,84 +56,32 @@ public final class ProtocolBridge {
@Getter
private ProtocolClient protocolClient;
/**
* The protocol side instances
*/
@Getter
private ProtocolWebServer protocolWebServer;
private ProtocolCustomServer protocolServer;
/**
* The classic protocol handlers for INS server side
*/
@Getter
@Setter
private ClassicHandlerINSServer classicHandlerINSServer;
/**
* The classic protocol handlers for web server side
*/
@Getter
@Setter
private ClassicHandlerWebServer classicHandlerWebServer;
/**
* The classic protocol handlers for client side
*/
@Getter
@Setter
private ClassicHandlerClient classicHandlerClient;
/**
* The proxy for client side
*/
@Getter
@Setter
private Proxy proxy;
/**
* Initialize the ProtocolBridge instance for the INS server side
* Initialize the ProtocolBridge instance for the client side
*
* @param protocolINSServer The ProtocolINSServer instance
* @param protocolSettings The ProtocolSettings instance
* @param protocolVersion The ProtocolVersion instance
* @param logFolder The folder to store the log files
* @param protocolServer The ProtocolCustomServer instance
* @param protocolValues The ProtocolSettings instance
* @throws Exception if an error occurs while initializing the ProtocolBridge
*/
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.INS)
public ProtocolBridge(ProtocolINSServer protocolINSServer, ProtocolSettings protocolSettings, ProtocolVersion protocolVersion, File logFolder) throws Exception {
public ProtocolBridge(ProtocolCustomServer protocolServer, ProtocolValues protocolValues) throws Exception {
// Assign the parameters to the class fields
this.protocolINSServer = protocolINSServer;
this.protocolSettings = protocolSettings;
this.protocolVersion = protocolVersion;
this.protocolServer = protocolServer;
this.protocolValues = protocolValues;
if (protocolServer instanceof ProtocolINSServer)
protocolServer.attachBridge(this, null, false, ClientAuthMode.NONE);
else
protocolServer.attachBridge(this, protocolValues.keyPass, protocolValues.ssl, protocolValues.authMode);
// Initialize the logger and protocol version
initializeLogger(logFolder);
initializeProtocolVersion();
// Register the appropriate listeners and packets
registerListeners();
registerPackets();
}
/**
* Initialize the ProtocolBridge instance for the web server side
*
* @param protocolWebServer The ProtocolWebServer instance
* @param protocolSettings The ProtocolSettings instance
* @param protocolVersion The ProtocolVersion instance
* @param logFolder The folder to store the log files
* @throws Exception if an error occurs while initializing the ProtocolBridge
*/
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
public ProtocolBridge(ProtocolWebServer protocolWebServer, ProtocolSettings protocolSettings, ProtocolVersion protocolVersion, File logFolder) throws Exception {
// Assign the parameters to the class fields
this.protocolWebServer = protocolWebServer;
this.protocolSettings = protocolSettings;
this.protocolVersion = protocolVersion;
// Initialize the logger and protocol version
initializeLogger(logFolder);
initializeProtocolVersion();
downloadLicenses();
// Register the appropriate listeners and packets
registerListeners();
@@ -150,54 +91,109 @@ public final class ProtocolBridge {
/**
* Initialize the ProtocolBridge instance for the client side
*
* @param protocolClient The ProtocolClient instance
* @param protocolSettings The ProtocolSettings instance
* @param protocolVersion The ProtocolVersion instance
* @param logFolder The folder to store the log files
* @param protocolClient The ProtocolClient instance
* @param protocolValues The ProtocolSettings instance
* @throws Exception if an error occurs while initializing the ProtocolBridge
*/
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.CLIENT)
public ProtocolBridge(ProtocolClient protocolClient, ProtocolSettings protocolSettings, ProtocolVersion protocolVersion, File logFolder) throws Exception {
public ProtocolBridge(ProtocolClient protocolClient, LibClientImpl_v1_0_1_B libClientImpl,
ProtocolValues protocolValues) throws Exception {
// Assign the parameters to the class fields
this.protocolClient = protocolClient;
this.protocolSettings = protocolSettings;
this.protocolVersion = protocolVersion;
this.protocolValues = protocolValues;
// Initialize the logger and protocol version
initializeLogger(logFolder);
protocolClient.attachBridge(this);
initializeProtocolVersion();
downloadLicenses();
// Register the appropriate listeners and packets
registerListeners();
registerPackets();
installUrl(libClientImpl);
}
private void installUrl(LibClientImpl_v1_0_1_B libClientImpl) {
if (protocolValues.protocolVersion == ProtocolVersion.PV_1_0_0_BETA) {
OacWebUrlInstaller_v1_0_0_B.installOnce(this, libClientImpl);
}
if (protocolValues.protocolVersion == ProtocolVersion.PV_1_0_1_BETA) {
OacUrlHandlerInstaller_v1_0_1_B.installOnce(this, libClientImpl, libClientImpl, libClientImpl);
}
}
private void downloadLicenses() throws IOException {
File licensesFolder = new File("licenses");
if (!licensesFolder.exists() || !licensesFolder.isDirectory()) {
if (licensesFolder.exists()) licensesFolder.delete();
File output = new File("licenses.zip");
output.createNewFile();
FileUtils.downloadFile("https://open-autonomous-connection.org/assets/licenses.zip", output);
FileUtils.unzip(output, licensesFolder.getParent());
output.deleteOnExit();
}
}
/**
* Register the appropriate packets based on the current protocol version
* Register the appropriate packets
*
* <p>Overview of all Packets
*
* <table border="2">
* <tr><th>ID</th><th>Packet</th><th>ProtocolVersion</th></tr>
*
* <tr><td>8</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_0.beta.AuthPacket AuthPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
* <tr><td>7</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSQueryPacket INSQueryPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
* <tr><td>6</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSResponsePacket INSResponsePacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
* <tr><td>10</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket WebRequestPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
* <tr><td>9</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket WebResponsePacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
* <tr><td>11</td><td>{@link WebStreamStartPacket_v1_0_0_B v1_0_0.beta.web.stream.WebStreamStartPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
* <tr><td>13</td><td>{@link WebStreamChunkPacket_v1_0_0_B v1_0_0.beta.web.stream.WebStreamChunkPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
* <tr><td>12</td><td>{@link WebStreamEndPacket_v1_0_0_B v1_0_0.beta.web.stream.WebStreamEndPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_0_BETA PV_1_0_0_BETA}</td></tr>
*
* <tr><td>1</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate.WebNavigateRequestPacket WebNavigateRequestPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
* <tr><td>2</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate.WebNavigateAckPacket WebNavigateAckPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
* <tr><td>3</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceRequestPacket WebResourceRequestPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
* <tr><td>4</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket WebResourceResponsePacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
* <tr><td>14</td><td>{@link WebStreamStartPacket_v1_0_1_B v1_0_1.beta.web.impl.stream.WebStreamStartPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
* <tr><td>15</td><td>{@link WebStreamChunkPacket_v1_0_1_B v1_0_1.beta.web.impl.stream.WebStreamChunkPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
* <tr><td>16</td><td>{@link WebStreamEndPacket_v1_0_1_B v1_0_1.beta.web.impl.stream.WebStreamEndPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
* <tr><td>17</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentSnapshotEventPacket WebDocumentSnapshotEventPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
* <tr><td>5</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentApplyResponsePacket WebDocumentApplyResponsePacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
* <tr><td>18</td><td>{@link org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentApplyRequestPacket WebDocumentApplyRequestPacket}</td><td>{@link org.openautonomousconnection.protocol.versions.ProtocolVersion#PV_1_0_1_BETA PV_1_0_1_BETA}</td></tr>
* </table>
*
* @see org.openautonomousconnection.protocol.versions.ProtocolVersion
*/
private void registerPackets() {
// Classic packets
Classic_DomainPacket cDomainPacket = new Classic_DomainPacket();
Classic_MessagePacket cMessagePacket = new Classic_MessagePacket();
Classic_PingPacket cPingPacket = new Classic_PingPacket();
if (isPacketSupported(cDomainPacket)) protocolSettings.packetHandler.registerPacket(cDomainPacket);
if (isPacketSupported(cMessagePacket)) protocolSettings.packetHandler.registerPacket(cMessagePacket);
if (isPacketSupported(cPingPacket)) protocolSettings.packetHandler.registerPacket(cPingPacket);
// 1.0.0-BETA packets
AuthPacket v100bAuthPath = new AuthPacket();
UnsupportedClassicPacket v100bUnsupportedClassicPacket = new UnsupportedClassicPacket();
INSQueryPacket v100BINSQueryPacket = new INSQueryPacket();
INSResponsePacket v100BINSResponsePacket = new INSResponsePacket(this);
registerPacket(() -> new AuthPacket(this));
registerPacket(INSQueryPacket::new);
registerPacket(() -> new INSResponsePacket(this));
registerPacket(WebRequestPacket::new);
registerPacket(WebResponsePacket::new);
registerPacket(WebStreamStartPacket_v1_0_0_B::new);
registerPacket(WebStreamChunkPacket_v1_0_0_B::new);
registerPacket(WebStreamEndPacket_v1_0_0_B::new);
if (isPacketSupported(v100bAuthPath)) protocolSettings.packetHandler.registerPacket(v100bAuthPath);
if (isPacketSupported(v100bUnsupportedClassicPacket))
protocolSettings.packetHandler.registerPacket(v100bUnsupportedClassicPacket);
if (isPacketSupported(v100BINSQueryPacket))
protocolSettings.packetHandler.registerPacket(v100BINSQueryPacket);
if (isPacketSupported(v100BINSResponsePacket))
protocolSettings.packetHandler.registerPacket(v100BINSResponsePacket);
// 1.0.1-BETA Packets
registerPacket(() -> new WebDocumentApplyRequestPacket(this));
registerPacket(WebDocumentApplyResponsePacket::new);
registerPacket(WebDocumentSnapshotEventPacket::new);
registerPacket(() -> new WebNavigateRequestPacket(this));
registerPacket(WebNavigateAckPacket::new);
registerPacket(() -> new WebResourceRequestPacket(this));
registerPacket(WebResourceResponsePacket::new);
registerPacket(WebStreamStartPacket_v1_0_1_B::new);
registerPacket(WebStreamChunkPacket_v1_0_1_B::new);
registerPacket(WebStreamEndPacket_v1_0_1_B::new);
}
private void registerPacket(Supplier<? extends OACPacket> factory) {
if (isPacketSupported(factory.get())) protocolValues.packetHandler.registerPacket(factory);
}
/**
@@ -206,60 +202,17 @@ public final class ProtocolBridge {
* @throws Exception if an error occurs while registering the listeners
*/
private void registerListeners() throws Exception {
// Classic listeners
if (isClassicSupported()) {
Classic_ClientListener classicListener = new Classic_ClientListener();
classicListener.setProtocolBridge(this);
protocolSettings.eventManager.registerListener(classicListener.getClass());
} else protocolSettings.eventManager.unregisterListener(Classic_ClientListener.class);
// INS Listeners
if (isRunningAsINSServer()) {
INSServerListener serverListener = new INSServerListener();
serverListener.setINSServer(protocolINSServer);
protocolSettings.eventManager.registerListener(serverListener.getClass());
protocolSettings.eventManager.unregisterListener(WebServerListener.class);
protocolSettings.eventManager.unregisterListener(ClientListener.class);
}
// Web Listeners
if (isRunningAsWebServer()) {
WebServerListener serverListener = new WebServerListener();
serverListener.setWebServer(protocolWebServer);
protocolSettings.eventManager.registerListener(serverListener.getClass());
protocolSettings.eventManager.unregisterListener(INSServerListener.class);
protocolSettings.eventManager.unregisterListener(ClientListener.class);
}
// Client Listeners
if (isRunningAsClient()) {
ClientListener clientListener = new ClientListener();
clientListener.setClient(protocolClient);
protocolSettings.eventManager.registerListener(clientListener.getClass());
protocolSettings.eventManager.unregisterListener(INSServerListener.class);
protocolSettings.eventManager.unregisterListener(WebServerListener.class);
}
}
/**
* Initialize the logger instance
*
* @param logFolder The folder to store the log files
*/
private void initializeLogger(File logFolder) {
// Create a temporary logger instance to avoid final field issues
Logger tmpLogger = null;
try {
// Initialize temporary logger
tmpLogger = new Logger(logFolder, false, true);
} catch (IOException | NoSuchFieldException | IllegalAccessException exception) {
exception.printStackTrace();
System.exit(1);
protocolValues.eventManager.registerListener(new ClientListener(protocolClient));
protocolValues.eventManager.unregisterListener(CustomServerListener.class);
}
// Assign the temporary logger to the final field
this.logger = tmpLogger;
// Server Listeners
if (isRunningAsServer()) {
protocolValues.eventManager.registerListener(new CustomServerListener(protocolServer));
protocolValues.eventManager.unregisterListener(ClientListener.class);
}
}
/**
@@ -271,7 +224,7 @@ public final class ProtocolBridge {
// Check if the protocol version is valid for the current side
// If not, log an error and exit the application
if (!validateProtocolSide()) {
this.logger.error("Invalid protocol version '" + protocolVersion.toString() + "'!");
protocolValues.logger.error("Invalid protocol version '" + protocolValues.protocolVersion.toString() + "'!");
System.exit(1);
}
}
@@ -283,14 +236,14 @@ public final class ProtocolBridge {
*/
public boolean isClassicSupported() {
boolean yes = false;
for (ProtocolVersion compatibleVersion : protocolVersion.getCompatibleVersions()) {
for (ProtocolVersion compatibleVersion : protocolValues.protocolVersion.getCompatibleVersions()) {
// Check if the compatible version is classic
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
if (yes) break;
}
// Check if the current protocol version is classic or if it is supported by any of the compatible versions
return protocolVersion.getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC || yes;
return protocolValues.protocolVersion.getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC || yes;
}
/**
@@ -301,14 +254,15 @@ public final class ProtocolBridge {
*/
public boolean isProtocolSupported(ProtocolVersion.Protocol protocol) {
boolean yes = false;
for (ProtocolVersion compatibleVersion : protocolVersion.getCompatibleVersions()) {
for (ProtocolVersion compatibleVersion : protocolValues.protocolVersion.getCompatibleVersions()) {
// Check if the compatible version supports the target protocol
yes = compatibleVersion.getSupportedProtocols().contains(protocol);
if (yes) break;
}
// Check if the current protocol version supports the target protocol or if it is supported by any of the compatible versions
return protocolVersion.getSupportedProtocols().contains(protocol) || yes;
return protocolValues.protocolVersion.getSupportedProtocols().contains(protocol) || yes;
}
/**
@@ -318,7 +272,13 @@ public final class ProtocolBridge {
* @return true if the target packet is supported, false otherwise
*/
public boolean isPacketSupported(OACPacket packet) {
return isVersionSupported(packet.getProtocolVersion());
boolean compatible = false;
for (ProtocolVersion compatibleVersion : packet.getCompatibleVersions()) {
if (!compatible) compatible = isVersionSupported(compatibleVersion);
}
return compatible;
}
/**
@@ -329,7 +289,7 @@ public final class ProtocolBridge {
*/
public boolean isVersionSupported(ProtocolVersion targetVersion) {
// Check if the target protocol version is the same as the current protocol version or if it is in the list of compatible versions
return protocolVersion == targetVersion || protocolVersion.getCompatibleVersions().contains(targetVersion);
return protocolValues.protocolVersion == targetVersion || protocolValues.protocolVersion.getCompatibleVersions().contains(targetVersion);
}
/**
@@ -339,20 +299,22 @@ public final class ProtocolBridge {
*/
private boolean validateProtocolSide() {
return
(isRunningAsClient() && protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.CLIENT) ||
(isRunningAsClient() && protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.CLIENT_WEB) ||
(isRunningAsClient() && protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.CLIENT_INS) ||
(isRunningAsClient() && protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.ALL) ||
(isRunningAsClient() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.CLIENT) ||
(isRunningAsClient() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.CLIENT_WEB) ||
(isRunningAsClient() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.CLIENT_INS) ||
(isRunningAsClient() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.ALL) ||
(isRunningAsWebServer() && protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.WEB) ||
(isRunningAsWebServer() && protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.CLIENT_WEB) ||
(isRunningAsWebServer() && protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.WEB_INS) ||
(isRunningAsWebServer() && protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.ALL) ||
(isRunningAsWebServer() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.WEB) ||
(isRunningAsWebServer() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.CLIENT_WEB) ||
(isRunningAsWebServer() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.WEB_INS) ||
(isRunningAsWebServer() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.ALL) ||
(isRunningAsINSServer() && protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.INS) ||
(isRunningAsINSServer() && protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.WEB_INS) ||
(isRunningAsINSServer() && protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.CLIENT_INS) ||
(isRunningAsINSServer() && protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.ALL);
(isRunningAsServer() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.ALL) ||
(isRunningAsINSServer() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.INS) ||
(isRunningAsINSServer() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.WEB_INS) ||
(isRunningAsINSServer() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.CLIENT_INS) ||
(isRunningAsINSServer() && protocolValues.protocolVersion.getProtocolSide() == ProtocolVersion.ProtocolSide.ALL);
}
/**
@@ -361,7 +323,7 @@ public final class ProtocolBridge {
* @return true if the current instance is running as a INS server, false otherwise
*/
public boolean isRunningAsINSServer() {
return protocolINSServer != null;
return isRunningAsServer() && protocolServer instanceof ProtocolINSServer;
}
/**
@@ -379,6 +341,17 @@ public final class ProtocolBridge {
* @return true if the current instance is running as a web server, false otherwise
*/
public boolean isRunningAsWebServer() {
return protocolWebServer != null;
if (protocolValues.protocolVersion == ProtocolVersion.PV_1_0_0_BETA)
return isRunningAsServer() && protocolServer instanceof ProtocolWebServer_1_0_0_B;
return isRunningAsServer() && protocolServer instanceof ProtocolWebServer;
}
/**
* Check if the current instance is running as a custom server
*
* @return true if the current instance is running as a custom server, false otherwise
*/
public boolean isRunningAsServer() {
return protocolServer != null;
}
}

View File

@@ -1,32 +0,0 @@
package org.openautonomousconnection.protocol;
import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
/**
* Settings for the protocol connection.
*/
public final class ProtocolSettings extends DefaultMethodsOverrider {
/**
* The host to connect to.
*/
public String host;
/**
* The port to connect to.
*/
public int port;
/**
* The protocol version to use.
*/
public PacketHandler packetHandler;
/**
* The event manager to use.
*/
public EventManager eventManager;
}

View File

@@ -0,0 +1,36 @@
package org.openautonomousconnection.protocol;
import dev.unlegitdqrk.unlegitlibrary.addon.AddonLoader;
import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.ClientAuthMode;
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
import dev.unlegitdqrk.unlegitlibrary.utils.Logger;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
/**
* Settings for the protocol connection.
*/
public final class ProtocolValues extends DefaultMethodsOverrider {
/**
* The packet handler to use.
*/
public PacketHandler packetHandler;
/**
* The event manager to use.
*/
public EventManager eventManager;
public AddonLoader addonLoader;
public Logger logger;
public String keyPass = null;
public ProtocolVersion protocolVersion;
public boolean ssl = true;
public ClientAuthMode authMode = ClientAuthMode.NONE;
}

View File

@@ -8,6 +8,7 @@ import java.lang.annotation.RetentionPolicy;
/**
* Annotation to provide metadata about protocol handlers or classes.
*/
@Deprecated(forRemoval = false, since = "1.0.1-BETA.0.5")
@Retention(RetentionPolicy.RUNTIME)
public @interface ProtocolInfo {

View File

@@ -5,7 +5,6 @@ import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import javax.annotation.Nullable;
import java.lang.annotation.Annotation;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;
@@ -73,6 +72,6 @@ public class CallTracker<A extends Annotation> extends GenericReflectClass<A> {
/**
* Code executed on any method call
*/
public abstract void onCall(Method method, @Nullable StackTraceElement callerMethod);
public abstract void onCall(Method method, StackTraceElement callerMethod);
}
}

View File

@@ -8,7 +8,6 @@ import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
import org.openautonomousconnection.protocol.exceptions.IncompatibleProtocolSideException;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import javax.annotation.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@@ -36,7 +35,7 @@ public class ProtocolInfoProcessing extends AnnotationProcessor<ProtocolInfo> {
this.tracker = new CallTracker<>(new CallTracker.CallInterceptor() {
@Override
public void onCall(Method method, @Nullable StackTraceElement callerMethod) {
public void onCall(Method method, StackTraceElement callerMethod) {
ProtocolVersion.ProtocolSide side, callerSide;
Object o;

View File

@@ -1,17 +1,16 @@
package org.openautonomousconnection.protocol.listeners;
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
import dev.unlegitdqrk.unlegitlibrary.event.EventPriority;
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.ClientConnectedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.ClientDisconnectedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.ClientConnectedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import lombok.Getter;
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.AuthPacket;
import org.openautonomousconnection.protocol.side.client.ProtocolClient;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import java.io.IOException;
/**
* Listener for client-side events such as connection and disconnection.
*/
@@ -22,15 +21,14 @@ public final class ClientListener extends EventListener {
* The reference to the ProtocolClient object
*/
@Getter
private ProtocolClient client;
private final ProtocolClient client;
/**
* Sets the client variable
*
* @param client The Instance of the ProtocolClient
*/
public void setClient(ProtocolClient client) {
if (this.client != null) return;
public ClientListener(ProtocolClient client) {
this.client = client;
}
@@ -40,25 +38,14 @@ public final class ClientListener extends EventListener {
*
* @param event The client connected event.
*/
@Listener
@Listener(priority = EventPriority.HIGHEST)
public void onConnect(ClientConnectedEvent event) {
try {
event.getClient().sendPacket(new AuthPacket(client.getProtocolBridge()));
} catch (IOException | ClassNotFoundException exception) {
event.getClient().getLogger().exception("Failed to send auth packet", exception);
event.getClient().sendPacket(new AuthPacket(client.getProtocolBridge()), TransportProtocol.TCP);
} catch (Exception exception) {
client.getProtocolBridge().getProtocolValues().logger.exception("Failed to send auth packet", exception);
event.getClient().disconnect();
}
}
/**
* Handles the event when a client disconnects.
* Notifies the protocol client of the disconnection.
*
* @param event The client disconnected event.
*/
@Listener
public void onDisconnect(ClientDisconnectedEvent event) {
client.onINSDisconnect(event);
}
}

View File

@@ -0,0 +1,146 @@
package org.openautonomousconnection.protocol.listeners;
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
import dev.unlegitdqrk.unlegitlibrary.event.EventPriority;
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.client.S_ClientConnectedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.S_PacketReadEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import lombok.Getter;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSQueryPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSResponsePacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket;
import org.openautonomousconnection.protocol.side.ins.ProtocolINSServer;
import org.openautonomousconnection.protocol.side.server.CustomConnectedClient;
import org.openautonomousconnection.protocol.side.server.ProtocolCustomServer;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecord;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.ProtocolWebServer_1_0_0_B;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Listener for web server connection events.
*/
public final class CustomServerListener extends EventListener {
/**
* The reference to the CustomProtocolServer object
*/
@Getter
private final ProtocolCustomServer server;
/**
* Sets the webServer variable
*
* @param server The Instance of the CustomProtocolServer
*/
public CustomServerListener(ProtocolCustomServer server) {
this.server = server;
}
/**
* Handles the event when a connection is established.
* Adds the connected client to the protocol web server's client list.
*
* @param event The connection handler connected event.
*/
@Listener(priority = EventPriority.HIGHEST)
public void onConnect(S_ClientConnectedEvent event) {
try {
server.getClients().add(new CustomConnectedClient(event.getClient(), server));
} catch (Exception e) {
server.getProtocolBridge().getProtocolValues().logger.exception("Failed to add client to server", e);
event.getClient().disconnect();
}
}
@Listener(priority = EventPriority.HIGHEST)
public void onPacketWeb(S_PacketReadEvent event) {
if (!server.getProtocolBridge().isRunningAsWebServer()) return;
if (event.getPacket() instanceof WebRequestPacket) {
try {
event.getClient().sendPacket(
((ProtocolWebServer_1_0_0_B) server.getProtocolBridge().getProtocolServer()).
onWebRequest(server.getClientByID(event.getClient().getUniqueID()), (WebRequestPacket) event.getPacket()),
TransportProtocol.TCP);
} catch (IOException e) {
server.getProtocolBridge().getProtocolValues().logger.exception("Failed to send web response", e);
}
}
}
/**
* Handles incoming INS query packets from connected clients.
* <p>
* This method performs the following steps:
* <ul>
* <li>Checks whether the received packet is an {@link INSQueryPacket}</li>
* <li>Notifies the INS server through {@code onQueryReceived}</li>
* <li>Resolves the request via {@code resolve()}</li>
* <li>Wraps the result in an {@link INSResponsePacket}</li>
* <li>Sends the response back to the requesting client</li>
* </ul>
* If sending the response fails, the INS server receives an
* {@code onResponseSentFailed(...)} callback.
*
* @param event The packet event received by the network system.
*/
@Listener(priority = EventPriority.HIGHEST)
public void onPacketINS(S_PacketReadEvent event) {
if (!(event.getPacket() instanceof INSQueryPacket q)) return;
if (!server.getProtocolBridge().isRunningAsINSServer()) return;
ProtocolINSServer insServer = (ProtocolINSServer) server;
insServer.onQueryReceived(q.getTLN(), q.getName(), q.getSub(), q.getType());
List<INSRecord> resolved = new ArrayList<>();
INSResponseStatus status = null;
if (q.getSub() == null && q.getTLN().equalsIgnoreCase("oac")) {
if (q.getName().equalsIgnoreCase("info")) {
// Return INS server info site
String insInfo = insServer.getInsInfoSite();
if (!insInfo.contains(":")) insInfo = insInfo + ":1028";
String[] hostPort = insInfo.split(":");
resolved = List.of(new INSRecord(q.getType(), hostPort[0], -1, -1, Integer.parseInt(hostPort[1]), 0));
} else if (q.getName().equalsIgnoreCase("register")) {
// Return INS frontend site
String insFrontend = insServer.getInsFrontendSite();
if (!insFrontend.contains(":")) insFrontend = insFrontend + ":1028";
String[] hostPort = insFrontend.split(":");
resolved = List.of(new INSRecord(q.getType(), hostPort[0], -1, -1, Integer.parseInt(hostPort[1]), 0));
} else {
// Not a special name → use normal resolving
resolved = insServer.resolve(q.getTLN(), q.getName(), q.getName(), q.getType());
}
} else if (q.getSub() == null && q.getName().equalsIgnoreCase("info")) {
// Return TLN server info site
String resolve = insServer.resolveTLNInfoSite(q.getTLN());
if (resolve == null) status = INSResponseStatus.INVALID_REQUEST;
else {
if (!resolve.contains(":")) resolve = resolve + ":1028";
String[] hostPort = resolve.split(":");
resolved = List.of(new INSRecord(q.getType(), hostPort[0], -1, -1, Integer.parseInt(hostPort[1]), 0));
}
} else {
// Normal resolving
resolved = insServer.resolve(q.getTLN(), q.getName(), q.getSub(), q.getType());
}
status = status == null && resolved.isEmpty() ? INSResponseStatus.NOT_FOUND : INSResponseStatus.OK;
INSResponsePacket response = new INSResponsePacket(status, resolved, q.getClientId(), insServer.getProtocolBridge());
try {
event.getClient().sendPacket(response, TransportProtocol.TCP);
insServer.onResponseSent(q.getTLN(), q.getName(), q.getSub(), q.getType(), resolved);
} catch (Exception e) {
insServer.onResponseSentFailed(q.getTLN(), q.getName(), q.getSub(), q.getType(), resolved, e);
}
}
}

View File

@@ -1,128 +0,0 @@
package org.openautonomousconnection.protocol.listeners;
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.ConnectionHandlerConnectedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.ConnectionHandlerDisconnectedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.S_PacketReceivedEvent;
import lombok.Getter;
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSQueryPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSResponsePacket;
import org.openautonomousconnection.protocol.side.ins.ConnectedProtocolClient;
import org.openautonomousconnection.protocol.side.ins.ProtocolINSServer;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecord;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecordType;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Listener for INS server connection events.
*/
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.INS)
public final class INSServerListener extends EventListener {
/**
* The reference to the INSServer object
*/
@Getter
private ProtocolINSServer insServer;
/**
* Sets the insServer variable
*
* @param insServer The Instance of the INSServer
*/
public void setINSServer(ProtocolINSServer insServer) {
if (this.insServer != null) return;
this.insServer = insServer;
}
/**
* Handles the event when a connection handler connects to the INS server.
* Adds the connected client to the ProtocolBridge's INS server client list.
*
* @param event The connection handler connected event.
*/
@Listener
public void onConnect(ConnectionHandlerConnectedEvent event) {
insServer.getClients().add(new ConnectedProtocolClient(event.getConnectionHandler(), insServer));
}
/**
* Handles the event when a connection handler disconnects from the INS server.
* Removes the disconnected client from the ProtocolBridge's INS server client list.
*
* @param event The connection handler disconnected event.
*/
@Listener
public void onDisconnect(ConnectionHandlerDisconnectedEvent event) {
insServer.getClients().removeIf(client -> client.getConnectionHandler().getClientID() == -1);
}
/**
* Handles incoming INS query packets from connected clients.
* <p>
* This method performs the following steps:
* <ul>
* <li>Checks whether the received packet is an {@link INSQueryPacket}</li>
* <li>Notifies the INS server through {@code onQueryReceived}</li>
* <li>Resolves the request via {@code resolve()}</li>
* <li>Wraps the result in an {@link INSResponsePacket}</li>
* <li>Sends the response back to the requesting client</li>
* </ul>
* If sending the response fails, the INS server receives an
* {@code onResponseSentFailed(...)} callback.
*
* @param event The packet event received by the network system.
*/
@Listener
public void onPacket(S_PacketReceivedEvent event) {
if (!(event.getPacket() instanceof INSQueryPacket q)) return;
insServer.onQueryReceived(q.tln, q.name, q.sub, q.type);
List<INSRecord> resolved = new ArrayList<>();
INSResponseStatus status = null;
if (q.sub == null && q.tln.equalsIgnoreCase("oac")) {
if (q.name.equalsIgnoreCase("info")) {
// Return INS server info site
String[] hostPort = insServer.getINSInfoSite().split(":");
resolved = List.of(new INSRecord(q.type, hostPort[0], -1, -1, Integer.parseInt(hostPort[1]), 0));
} else if (q.name.equalsIgnoreCase("register")) {
// Return INS frontend site
String[] hostPort = insServer.getINSFrontendSite().split(":");
resolved = List.of(new INSRecord(q.type, hostPort[0], -1, -1, Integer.parseInt(hostPort[1]), 0));
} else {
// Not a special name → use normal resolving
resolved = insServer.resolve(q.tln, q.name, q.sub, q.type);
}
} else if (q.sub == null && q.name.equalsIgnoreCase("info")) {
// Return TLN server info site
String resolve = insServer.resolveTLNInfoSite(q.tln);
if (resolve == null) status = INSResponseStatus.INVALID_REQUEST;
else {
String[] hostPort = resolve.split(":");
resolved = List.of(new INSRecord(q.type, hostPort[0], -1, -1, Integer.parseInt(hostPort[1]), 0));
}
} else {
// Normal resolving
resolved = insServer.resolve(q.tln, q.name, q.sub, q.type);
}
status = status == null && resolved.isEmpty() ? INSResponseStatus.NOT_FOUND : INSResponseStatus.OK;
INSResponsePacket response = new INSResponsePacket(status, resolved, q.clientId, insServer.getProtocolBridge());
try {
event.getConnectionHandler().sendPacket(response);
insServer.onResponseSent(q.tln, q.name, q.sub, q.type, resolved);
} catch (IOException | ClassNotFoundException e) {
insServer.onResponseSentFailed(q.tln, q.name, q.sub, q.type, resolved, e);
}
}
}

View File

@@ -1,57 +0,0 @@
package org.openautonomousconnection.protocol.listeners;
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.ConnectionHandlerConnectedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.ConnectionHandlerDisconnectedEvent;
import lombok.Getter;
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
import org.openautonomousconnection.protocol.side.web.ConnectedWebClient;
import org.openautonomousconnection.protocol.side.web.ProtocolWebServer;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
/**
* Listener for web server connection events.
*/
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
public final class WebServerListener extends EventListener {
/**
* The reference to the ProtocolWebServer object
*/
@Getter
private ProtocolWebServer webServer;
/**
* Sets the webServer variable
*
* @param webServer The Instance of the ProtocolWebServer
*/
public void setWebServer(ProtocolWebServer webServer) {
if (this.webServer != null) return;
this.webServer = webServer;
}
/**
* Handles the event when a connection is established.
* Adds the connected client to the protocol web server's client list.
*
* @param event The connection handler connected event.
*/
@Listener
public void onConnect(ConnectionHandlerConnectedEvent event) {
webServer.getClients().add(new ConnectedWebClient(event.getConnectionHandler()));
}
/**
* Handles the event when a connection is disconnected.
* Removes the disconnected client from the protocol web server's client list.
*
* @param event The connection handler disconnected event.
*/
@Listener
public void onDisconnect(ConnectionHandlerDisconnectedEvent event) {
webServer.getClients().removeIf(client -> client.getPipelineConnection().getClientID() == -1);
}
}

View File

@@ -1,14 +1,14 @@
package org.openautonomousconnection.protocol.packets;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
import lombok.Getter;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.*;
/**
* Abstract class representing a packet in the Open Autonomous Connection (OAC) protocol.
@@ -20,8 +20,8 @@ public abstract class OACPacket extends Packet {
* The protocol version associated with this packet.
*/
@Getter
private final ProtocolVersion protocolVersion;
private final List<ProtocolVersion> compatibleVersions;
private final int id;
/**
* The response code for the packet, defaulting to RESPONSE_NOT_REQUIRED.
*/
@@ -31,11 +31,16 @@ public abstract class OACPacket extends Packet {
* Constructor for OACPacket.
*
* @param id The unique identifier for the packet.
* @param protocolVersion The protocol version associated with this packet.
* @param supportedVersions The protocol version associated with this packet.
*/
public OACPacket(int id, ProtocolVersion protocolVersion) {
super(id);
this.protocolVersion = protocolVersion;
public OACPacket(int id, ProtocolVersion... supportedVersions) {
this.id = id;
this.compatibleVersions = List.of(supportedVersions);
}
@Override
public int getPacketID() {
return id;
}
/**
@@ -59,61 +64,100 @@ public abstract class OACPacket extends Packet {
/**
* Writes the packet data to the output stream.
*
* @param packetHandler The packet handler managing the packet.
* @param objectOutputStream The output stream to write the packet data to.
* @throws IOException If an I/O error occurs.
* @throws ClassNotFoundException If a class cannot be found during serialization.
* @param outputStream The output stream to write the packet data to.
* @throws IOException If an I/O error occurs.
*/
@Override
public final void write(PacketHandler packetHandler, ObjectOutputStream objectOutputStream) throws IOException, ClassNotFoundException {
public final void write(DataOutputStream outputStream) throws IOException {
// Write the specific packet data
onWrite(packetHandler, objectOutputStream);
onWrite(outputStream);
// Write the response code if the protocol version is not classic
if (protocolVersion != ProtocolVersion.PV_1_0_0_CLASSIC) objectOutputStream.writeObject(responseCode);
if (!compatibleVersions.contains(ProtocolVersion.PV_1_0_0_CLASSIC)) outputStream.writeUTF(responseCode.name());
}
@Override
public final void read(PacketHandler packetHandler, ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
public final void read(DataInputStream inputStream, UUID clientID) throws IOException {
// Read the specific packet data
onRead(packetHandler, objectInputStream);
onRead(inputStream, clientID);
// Read the response code if the protocol version is not classic
if (protocolVersion != ProtocolVersion.PV_1_0_0_CLASSIC)
responseCode = (INSResponseStatus) objectInputStream.readObject();
if (!compatibleVersions.contains(ProtocolVersion.PV_1_0_0_CLASSIC))
responseCode = INSResponseStatus.valueOf(inputStream.readUTF());
else responseCode = INSResponseStatus.RESPONSE_NOT_REQUIRED;
// Call the response code read handler
onResponseCodeRead(packetHandler, objectInputStream);
onResponseCodeRead(inputStream, clientID);
}
/**
* Abstract method to be implemented by subclasses for writing specific packet data.
*
* @param packetHandler The packet handler managing the packet.
* @param objectOutputStream The output stream to write the packet data to.
* @throws IOException If an I/O error occurs.
* @throws ClassNotFoundException If a class cannot be found during serialization.
* @param outputStream The output stream to write the packet data to.
* @throws IOException If an I/O error occurs.
*/
public abstract void onWrite(PacketHandler packetHandler, ObjectOutputStream objectOutputStream) throws IOException, ClassNotFoundException;
public abstract void onWrite(DataOutputStream outputStream) throws IOException;
/**
* Abstract method to be implemented by subclasses for reading specific packet data.
*
* @param packetHandler The packet handler managing the packet.
* @param objectInputStream The input stream to read the packet data from.
* @throws IOException If an I/O error occurs.
* @throws ClassNotFoundException If a class cannot be found during deserialization.
* @param inputStream The input stream to read the packet data from.
* @throws IOException If an I/O error occurs.
*/
public abstract void onRead(PacketHandler packetHandler, ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException;
public abstract void onRead(DataInputStream inputStream, UUID clientID) throws IOException;
/**
* Method called after the response code has been read from the input stream.
* Subclasses can override this method to handle any additional logic based on the response code.
*
* @param packetHandler The packet handler managing the packet.
* @param objectInputStream The input stream from which the response code was read.
* @param inputStream The input stream from which the response code was read.
*/
protected void onResponseCodeRead(PacketHandler packetHandler, ObjectInputStream objectInputStream) {
protected void onResponseCodeRead(DataInputStream inputStream, UUID clientID) {
}
/**
* Writes a string map in a deterministic way (no Java object serialization).
*
* @param out output stream
* @param map map to write (may be null)
* @throws IOException on I/O errors
*/
protected final void writeStringMap(DataOutputStream out, Map<String, String> map) throws IOException {
if (map == null || map.isEmpty()) {
out.writeInt(0);
return;
}
out.writeInt(map.size());
for (Map.Entry<String, String> e : map.entrySet()) {
// Null keys/values are normalized to empty strings to keep the wire format stable.
out.writeUTF((e.getKey() != null) ? e.getKey() : "");
out.writeUTF((e.getValue() != null) ? e.getValue() : "");
}
}
/**
* Reads a string map in a deterministic way (no Java object serialization).
*
* @param in input stream
* @return headers map (never null)
* @throws IOException on I/O errors / invalid sizes
*/
protected final Map<String, String> readStringMap(DataInputStream in) throws IOException {
int size = in.readInt();
if (size < 0) {
throw new IOException("Negative map size");
}
if (size == 0) {
return Collections.emptyMap();
}
Map<String, String> map = new LinkedHashMap<>(Math.max(16, size * 2));
for (int i = 0; i < size; i++) {
String key = in.readUTF();
String value = in.readUTF();
map.put(key, value);
}
return map;
}
}

View File

@@ -1,30 +1,33 @@
package org.openautonomousconnection.protocol.packets.v1_0_0.beta;
import dev.unlegitdqrk.unlegitlibrary.file.FileUtils;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectedClient;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import dev.unlegitdqrk.unlegitlibrary.network.utils.NetworkUtils;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.packets.OACPacket;
import org.openautonomousconnection.protocol.side.client.events.ConnectedToProtocolINSServerEvent;
import org.openautonomousconnection.protocol.side.ins.ConnectedProtocolClient;
import org.openautonomousconnection.protocol.side.ins.events.ConnectedProtocolClientEvent;
import org.openautonomousconnection.protocol.side.web.ConnectedWebClient;
import org.openautonomousconnection.protocol.side.client.events.ConnectedToProtocolServerEvent;
import org.openautonomousconnection.protocol.side.server.CustomConnectedClient;
import org.openautonomousconnection.protocol.side.server.events.S_CustomClientConnectedEvent;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;
/**
* Authentication packet used between client and INS/Web servers.
* <p>
* Responsibilities:
*
* <p>Responsibilities:
* <ul>
* <li>Client → Server: Sends client ID and protocol version</li>
* <li>Server → Client: Sends CA key, CA certificate and CA serial files</li>
* <li>Client → Server: Sends client connection id and protocol version</li>
* <li>INS Server → Client: Sends CA key, CA certificate and CA serial files</li>
* <li>Performs version compatibility validation</li>
* <li>Triggers authentication callbacks on both sides</li>
* </ul>
@@ -39,134 +42,182 @@ public final class AuthPacket extends OACPacket {
* @param protocolBridge The protocol context of the current instance.
*/
public AuthPacket(ProtocolBridge protocolBridge) {
this();
super(8, ProtocolVersion.PV_1_0_0_BETA, ProtocolVersion.PV_1_0_1_BETA);
this.protocolBridge = protocolBridge;
}
/**
* Registration constructor
*/
public AuthPacket() {
super(4, ProtocolVersion.PV_1_0_0_BETA);
}
/**
* Writes authentication data to the output stream.
* <p>
* Behavior differs based on the running side:
* <ul>
* <li>INS Server → sends CA bundle to the client</li>
* <li>Client → sends client ID + protocol version</li>
* </ul>
*/
@Override
public void onWrite(PacketHandler packetHandler, ObjectOutputStream objectOutputStream) throws IOException, ClassNotFoundException {
public void onWrite(DataOutputStream objectOutputStream) throws IOException {
if (protocolBridge.isRunningAsINSServer()) {
objectOutputStream.writeObject(protocolBridge.getProtocolVersion());
objectOutputStream.writeBoolean(true);
objectOutputStream.writeUTF(protocolBridge.getProtocolValues().protocolVersion.name());
// Read ca files
String caKey = "N/A";
String caPem = "N/A";
String caSrl = "N/A";
try {
objectOutputStream.writeUTF(protocolBridge.getProtocolINSServer().getFolderStructure().caPrefix + NetworkUtils.getPublicIPAddress());
caKey = FileUtils.readFileFull(new File(
protocolBridge.getProtocolINSServer().getFolderStructure().privateCAFolder,
protocolBridge.getProtocolINSServer().getFolderStructure().caPrefix + NetworkUtils.getPublicIPAddress() + ".key"));
try {
String caPrefix = protocolBridge.getProtocolServer().getFolderStructure().getCaPrefix()
+ NetworkUtils.getPublicIPAddress();
objectOutputStream.writeUTF(caPrefix);
caPem = FileUtils.readFileFull(new File(
protocolBridge.getProtocolINSServer().getFolderStructure().publicCAFolder,
protocolBridge.getProtocolINSServer().getFolderStructure().caPrefix + NetworkUtils.getPublicIPAddress() + ".pem"));
caSrl = FileUtils.readFileFull(new File(
protocolBridge.getProtocolINSServer().getFolderStructure().publicCAFolder,
protocolBridge.getProtocolINSServer().getFolderStructure().caPrefix + NetworkUtils.getPublicIPAddress() + ".srl"));
protocolBridge.getProtocolServer().getFolderStructure().publicCAFolder,
caPrefix + ".pem"));
} catch (Exception exception) {
protocolBridge.getLogger().exception("Failed to read ca-files", exception);
protocolBridge.getProtocolValues().logger.exception("Failed to read ca-files", exception);
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
}
// Send ca data
objectOutputStream.writeUTF(caKey);
objectOutputStream.writeUTF(caPem);
objectOutputStream.writeUTF(caSrl);
} else if (protocolBridge.isRunningAsClient()) {
objectOutputStream.writeInt(protocolBridge.getProtocolClient().getClientINSConnection().getClientID());
objectOutputStream.writeObject(protocolBridge.getProtocolVersion());
return;
}
}
/**
* Reads authentication data and updates protocol state.
* <p>
* Behavior:
* <ul>
* <li>Server validates version and registers new connected client</li>
* <li>Client saves received CA files and completes TLS initialization</li>
* </ul>
*/
@Override
public void onRead(PacketHandler packetHandler, ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
if (protocolBridge.isRunningAsINSServer() || protocolBridge.isRunningAsWebServer()) {
int clientID = objectInputStream.readInt();
ProtocolVersion clientVersion = (ProtocolVersion) objectInputStream.readObject();
ConnectionHandler connectionHandler = protocolBridge.getProtocolINSServer().getNetworkServer().getConnectionHandlerByID(clientID);
if (protocolBridge.isRunningAsServer()) {
objectOutputStream.writeBoolean(false);
objectOutputStream.writeUTF(protocolBridge.getProtocolValues().protocolVersion.name());
return;
}
if (!protocolBridge.isVersionSupported(clientVersion)) {
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
connectionHandler.disconnect();
return;
} else setResponseCode(INSResponseStatus.RESPONSE_AUTH_SUCCESS);
if (protocolBridge.isRunningAsClient()) {
UUID clientConnectionId = null;
if (protocolBridge.isRunningAsINSServer()) {
ConnectedProtocolClient client = protocolBridge.getProtocolINSServer().getClientByID(clientID);
client.setClientVersion(clientVersion);
protocolBridge.getProtocolSettings().eventManager.executeEvent(new ConnectedProtocolClientEvent(client));
} else {
ConnectedWebClient client = protocolBridge.getProtocolWebServer().getClientByID(clientID);
client.setClientVersion(clientVersion);
}
} else if (protocolBridge.isRunningAsClient()) {
ProtocolVersion serverVersion = (ProtocolVersion) objectInputStream.readObject();
if (!protocolBridge.isVersionSupported(serverVersion)) {
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
protocolBridge.getProtocolClient().getClientINSConnection().disconnect();
return;
} else setResponseCode(INSResponseStatus.RESPONSE_AUTH_SUCCESS);
String caPrefix = objectInputStream.readUTF();
String caKey = objectInputStream.readUTF();
String caPem = objectInputStream.readUTF();
String caSrl = objectInputStream.readUTF();
if (caKey.equalsIgnoreCase("N/A") || caPem.equalsIgnoreCase("N/A") || caSrl.equalsIgnoreCase("N/A"))
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
else {
File caPemFile = new File(protocolBridge.getProtocolClient().getFolderStructure().publicCAFolder, caPrefix + ".pem");
File caSrlFile = new File(protocolBridge.getProtocolClient().getFolderStructure().publicCAFolder, caPrefix + ".srl");
File caKeyFile = new File(protocolBridge.getProtocolClient().getFolderStructure().privateCAFolder, caPrefix + ".key");
try {
if (!caPemFile.exists()) caPemFile.createNewFile();
if (!caSrlFile.exists()) caSrlFile.createNewFile();
if (!caKeyFile.exists()) caKeyFile.createNewFile();
FileUtils.writeFile(caPemFile, caPem);
FileUtils.writeFile(caSrlFile, caKey);
FileUtils.writeFile(caKeyFile, caSrl);
} catch (Exception exception) {
protocolBridge.getLogger().exception("Failed to create/save ca-files", exception);
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
if (protocolBridge.getProtocolClient() != null) {
if (protocolBridge.getProtocolClient().getClientINSConnection() != null && (protocolBridge.getProtocolClient().getClientServerConnection() == null)) {
clientConnectionId = protocolBridge.getProtocolClient().getClientINSConnection().getUniqueID();
} else if (protocolBridge.getProtocolClient().getClientServerConnection() != null) {
clientConnectionId = protocolBridge.getProtocolClient().getClientServerConnection().getUniqueID();
} else if (protocolBridge.getProtocolClient().getClientINSConnection() != null) {
clientConnectionId = protocolBridge.getProtocolClient().getClientINSConnection().getUniqueID();
}
}
protocolBridge.getProtocolClient().setServerVersion(serverVersion);
protocolBridge.getProtocolSettings().eventManager.executeEvent(new ConnectedToProtocolINSServerEvent(protocolBridge.getProtocolClient()));
objectOutputStream.writeUTF(clientConnectionId.toString());
objectOutputStream.writeUTF(protocolBridge.getProtocolValues().protocolVersion.name());
}
}
@Override
public void onRead(DataInputStream objectInputStream, UUID id) throws IOException {
if (protocolBridge.isRunningAsServer()) {
UUID clientID = UUID.fromString(objectInputStream.readUTF());
ProtocolVersion clientVersion = ProtocolVersion.valueOf(objectInputStream.readUTF());
ConnectedClient connectionHandler = getConnection(protocolBridge.getProtocolServer().getNetwork(), clientID);
if (connectionHandler == null) {
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
return;
}
if (!protocolBridge.isVersionSupported(clientVersion)) {
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
try {
connectionHandler.disconnect();
} catch (Exception ignored) {
}
return;
}
setResponseCode(INSResponseStatus.RESPONSE_AUTH_SUCCESS);
CustomConnectedClient client = protocolBridge.getProtocolServer().getClientByID(clientID);
client.setClientVersion(clientVersion);
protocolBridge.getProtocolValues().eventManager.executeEvent(new S_CustomClientConnectedEvent(client));
client.getConnection().sendPacket(new AuthPacket(protocolBridge), TransportProtocol.TCP);
return;
}
if (protocolBridge.isRunningAsClient()) {
boolean fromINS = objectInputStream.readBoolean();
ProtocolVersion serverVersion = ProtocolVersion.valueOf(objectInputStream.readUTF());
if (!fromINS) {
protocolBridge.getProtocolClient().setServerVersion(serverVersion);
protocolBridge.getProtocolValues().eventManager.executeEvent(
new ConnectedToProtocolServerEvent(protocolBridge.getProtocolClient())
);
} else {
if (!protocolBridge.isVersionSupported(serverVersion)) {
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
if (protocolBridge.getProtocolClient() != null && protocolBridge.getProtocolClient().getClientINSConnection() != null) {
protocolBridge.getProtocolClient().getClientINSConnection().disconnect();
}
return;
}
setResponseCode(INSResponseStatus.RESPONSE_AUTH_SUCCESS);
String caPrefix = objectInputStream.readUTF();
String caPem = objectInputStream.readUTF();
if (caPem.equalsIgnoreCase("N/A")) {
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
protocolBridge.getProtocolClient().getClientINSConnection().disconnect();
return;
}
byte[] caBytes = caPem.getBytes(java.nio.charset.StandardCharsets.UTF_8);
String fp = "N/A";
try {
java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256");
fp = java.util.HexFormat.of().formatHex(md.digest(caBytes));
} catch (NoSuchAlgorithmException ignored) {
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
protocolBridge.getProtocolClient().getClientINSConnection().disconnect();
return;
}
File caPemFile = new File(protocolBridge.getProtocolClient().getFolderStructure().publicCAFolder, caPrefix + ".pem");
File fpFile = new File(
protocolBridge.getProtocolClient().getFolderStructure().publicCAFolder,
caPrefix + ".fp");
if (!fpFile.exists()) {
if (!protocolBridge.getProtocolClient().trustINS(fp)) {
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
protocolBridge.getProtocolClient().getClientINSConnection().disconnect();
return;
}
} else {
String existing = FileUtils.readFileLines(fpFile).getFirst();
if (!existing.equalsIgnoreCase(fp)) {
if (!protocolBridge.getProtocolClient().trustNewINSFingerprint(existing, fp)) {
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
protocolBridge.getProtocolClient().getClientINSConnection().disconnect();
return;
}
}
}
FileUtils.writeFile(fpFile, fp + System.lineSeparator());
try {
FileUtils.writeFile(caPemFile, caPem);
} catch (Exception exception) {
protocolBridge.getProtocolValues().logger.exception("Failed to create/save ca-files", exception);
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
}
protocolBridge.getProtocolClient().setInsVersion(serverVersion);
protocolBridge.getProtocolValues().eventManager.executeEvent(
new ConnectedToProtocolINSServerEvent(protocolBridge.getProtocolClient())
);
}
}
}
private ConnectedClient getConnection(NetworkServer server, UUID connectionId) {
if (server == null || connectionId == null) return null;
for (ConnectedClient connection : server.getConnectedClients()) {
if (connection != null && connection.getUniqueID().equals(connectionId)) {
return connection;
}
}
return null;
}
}

View File

@@ -1,13 +1,14 @@
package org.openautonomousconnection.protocol.packets.v1_0_0.beta;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
import lombok.Getter;
import org.openautonomousconnection.protocol.packets.OACPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecordType;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.UUID;
/**
* Packet used by clients to query INS records from an INS server.
@@ -23,11 +24,16 @@ import java.io.ObjectOutputStream;
*/
public final class INSQueryPacket extends OACPacket {
public String tln;
public String name;
public String sub;
public INSRecordType type;
public int clientId;
@Getter
private String TLN;
@Getter
private String name;
@Getter
private String sub;
@Getter
private INSRecordType type;
@Getter
private UUID clientId;
/**
* Creates a new INS query packet with all required parameters.
@@ -38,9 +44,9 @@ public final class INSQueryPacket extends OACPacket {
* @param type Record type requested.
* @param clientId Sender client ID for routing.
*/
public INSQueryPacket(String tln, String name, String sub, INSRecordType type, int clientId) {
super(6, ProtocolVersion.PV_1_0_0_BETA);
this.tln = tln;
public INSQueryPacket(String tln, String name, String sub, INSRecordType type, UUID clientId) {
this();
this.TLN = tln;
this.name = name;
this.sub = sub;
this.type = type;
@@ -51,36 +57,36 @@ public final class INSQueryPacket extends OACPacket {
* Registration constructor
*/
public INSQueryPacket() {
super(6, ProtocolVersion.PV_1_0_0_BETA);
super(7, ProtocolVersion.PV_1_0_0_BETA, ProtocolVersion.PV_1_0_1_BETA);
}
/**
* Serializes the INS query into the stream.
*/
@Override
public void onWrite(PacketHandler handler, ObjectOutputStream out) throws IOException {
out.writeUTF(tln);
public void onWrite(DataOutputStream out) throws IOException {
out.writeUTF(TLN);
out.writeUTF(name);
out.writeBoolean(sub != null);
if (sub != null) out.writeUTF(sub);
out.writeObject(type);
out.writeInt(clientId);
out.writeUTF(type.name());
out.writeUTF(clientId.toString());
}
/**
* Deserializes the INS query from the stream.
*/
@Override
public void onRead(PacketHandler handler, ObjectInputStream in) throws IOException, ClassNotFoundException {
tln = in.readUTF();
public void onRead(DataInputStream in, UUID clientID) throws IOException {
TLN = in.readUTF();
name = in.readUTF();
boolean hasSub = in.readBoolean();
sub = hasSub ? in.readUTF() : null;
type = (INSRecordType) in.readObject();
clientId = in.readInt();
type = INSRecordType.valueOf(in.readUTF());
clientId = UUID.fromString(in.readUTF());
}
}

View File

@@ -1,17 +1,18 @@
package org.openautonomousconnection.protocol.packets.v1_0_0.beta;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
import lombok.Getter;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.packets.OACPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecord;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* Response packet returned by an INS server after resolving a query.
@@ -27,10 +28,14 @@ import java.util.List;
*/
public final class INSResponsePacket extends OACPacket {
@Getter
private final ProtocolBridge bridge;
public INSResponseStatus status;
public List<INSRecord> records;
public int clientId;
@Getter
private INSResponseStatus status;
@Getter
private List<INSRecord> records;
@Getter
private UUID clientId;
/**
* Creates a populated response packet.
@@ -40,12 +45,11 @@ public final class INSResponsePacket extends OACPacket {
* @param clientId ID of requesting client.
* @param bridge Protocol runtime context.
*/
public INSResponsePacket(INSResponseStatus status, List<INSRecord> records, int clientId, ProtocolBridge bridge) {
super(7, ProtocolVersion.PV_1_0_0_BETA);
public INSResponsePacket(INSResponseStatus status, List<INSRecord> records, UUID clientId, ProtocolBridge bridge) {
this(bridge);
this.status = status;
this.records = records;
this.clientId = clientId;
this.bridge = bridge;
}
/**
@@ -54,7 +58,7 @@ public final class INSResponsePacket extends OACPacket {
* @param bridge Protocol runtime context.
*/
public INSResponsePacket(ProtocolBridge bridge) {
super(7, ProtocolVersion.PV_1_0_0_BETA);
super(6, ProtocolVersion.PV_1_0_0_BETA, ProtocolVersion.PV_1_0_1_BETA);
this.bridge = bridge;
}
@@ -62,32 +66,32 @@ public final class INSResponsePacket extends OACPacket {
* Serializes the response status, records and client ID.
*/
@Override
public void onWrite(PacketHandler handler, ObjectOutputStream out) throws IOException {
out.writeObject(status);
public void onWrite(DataOutputStream out) throws IOException {
out.writeUTF(status.name());
out.writeInt(records.size());
for (INSRecord rec : records) {
out.writeObject(rec);
writeObject(out, rec);
}
out.writeInt(clientId);
out.writeUTF(clientId.toString());
}
/**
* Deserializes the response, reconstructing the record list.
*/
@Override
public void onRead(PacketHandler handler, ObjectInputStream in) throws IOException, ClassNotFoundException {
status = (INSResponseStatus) in.readObject();
public void onRead(DataInputStream in, UUID clientID) throws IOException {
status = INSResponseStatus.valueOf(in.readUTF());
int size = in.readInt();
records = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
records.add((INSRecord) in.readObject());
records.add((INSRecord) readObject(in));
}
clientId = in.readInt();
clientId = UUID.fromString(in.readUTF());
}
/**
@@ -96,7 +100,7 @@ public final class INSResponsePacket extends OACPacket {
* If running on a client, forwards the result to the client-side API.
*/
@Override
protected void onResponseCodeRead(PacketHandler handler, ObjectInputStream in) {
protected void onResponseCodeRead(DataInputStream in, UUID clientID) {
if (bridge.isRunningAsClient()) {
bridge.getProtocolClient().onResponse(status, records);
}

View File

@@ -1,88 +0,0 @@
package org.openautonomousconnection.protocol.packets.v1_0_0.beta;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.packets.OACPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* Internal compatibility packet used when a classic-protocol packet is received
* but not supported by the current protocol version.
* <p>
* Instead of rejecting the packet entirely, the content and class name are forwarded
* to the appropriate compatibility handler:
* <ul>
* <li>{@link org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.ClassicHandlerINSServer}</li>
* <li>{@link org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.ClassicHandlerClient}</li>
* <li>{@link org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.ClassicHandlerWebServer}</li>
* </ul>
*/
public final class UnsupportedClassicPacket extends OACPacket {
private Class<? extends OACPacket> unsupportedClassicPacket;
private Object[] content;
private ProtocolBridge protocolBridge;
/**
* Constructs a packet describing the unsupported classic packet and its content.
*
* @param unsupportedClassicPacket The packet class that was not understood.
* @param content Serialized field values.
* @param protocolBridge The protocol context.
*/
public UnsupportedClassicPacket(Class<? extends OACPacket> unsupportedClassicPacket, Object[] content, ProtocolBridge protocolBridge) {
this();
this.unsupportedClassicPacket = unsupportedClassicPacket;
this.content = content;
this.protocolBridge = protocolBridge;
}
/**
* Registration Constructor
*/
public UnsupportedClassicPacket() {
super(5, ProtocolVersion.PV_1_0_0_BETA);
}
/**
* Writes the class name and serialized object fields to the stream.
*/
@Override
public void onWrite(PacketHandler packetHandler, ObjectOutputStream objectOutputStream) throws IOException, ClassNotFoundException {
if (protocolBridge.isRunningAsClient())
objectOutputStream.writeInt(protocolBridge.getProtocolClient().getClientINSConnection().getClientID());
objectOutputStream.writeUTF(unsupportedClassicPacket.getName());
objectOutputStream.writeInt(content.length);
for (Object o : content) objectOutputStream.writeObject(o);
setResponseCode(INSResponseStatus.RESPONSE_NOT_REQUIRED);
}
/**
* Reads the unsupported packet data and forwards it to the appropriate compatibility handler.
*/
@Override
public void onRead(PacketHandler packetHandler, ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
int clientID = 0;
if (protocolBridge.isRunningAsINSServer()) clientID = objectInputStream.readInt();
String className = objectInputStream.readUTF();
int size = objectInputStream.readInt();
content = new Object[size];
for (int i = 0; i < size; i++) {
content[i] = objectInputStream.readObject();
}
if (protocolBridge.isRunningAsINSServer())
protocolBridge.getClassicHandlerINSServer().unsupportedClassicPacket(className, content, protocolBridge.getProtocolINSServer().getClientByID(clientID));
else if (protocolBridge.isRunningAsClient())
protocolBridge.getClassicHandlerClient().unsupportedClassicPacket(className, content);
else if (protocolBridge.isRunningAsWebServer())
protocolBridge.getClassicHandlerWebServer().unsupportedClassicPacket(className, content, protocolBridge.getProtocolINSServer().getClientByID(clientID));
}
}

View File

@@ -0,0 +1,84 @@
package org.openautonomousconnection.protocol.packets.v1_0_0.beta.web;
import lombok.Getter;
import org.openautonomousconnection.protocol.packets.OACPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.WebRequestMethod;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
/**
* Represents a web request in OAC Web protocol.
*
* <p>Important: This packet encodes headers using a deterministic UTF-based map format
* (int size, then size times: UTF key, UTF value) instead of Java object serialization.</p>
*/
public final class WebRequestPacket extends OACPacket {
@Getter
private String path;
@Getter
private WebRequestMethod method;
@Getter
private Map<String, String> headers;
@Getter
private byte[] body;
/**
* Creates an empty request packet (used by PacketHandler factory).
*/
public WebRequestPacket() {
super(10, ProtocolVersion.PV_1_0_0_BETA);
}
/**
* Creates a request packet.
*
* @param path request path (e.g. "/index.html")
* @param method request method
* @param headers request headers (may be null)
* @param body request body (may be null)
*/
public WebRequestPacket(String path, WebRequestMethod method, Map<String, String> headers, byte[] body) {
this();
this.path = (path != null) ? path : "/";
this.method = (method != null) ? method : WebRequestMethod.GET;
this.headers = (headers != null) ? new LinkedHashMap<>(headers) : Collections.emptyMap();
this.body = (body != null) ? body : new byte[0];
}
@Override
public void onWrite(DataOutputStream out) throws IOException {
out.writeUTF((path != null) ? path : "/");
out.writeUTF((method != null) ? method.name() : WebRequestMethod.GET.name());
writeStringMap(out, headers);
byte[] b = (body != null) ? body : new byte[0];
out.writeInt(b.length);
out.write(b);
}
@Override
public void onRead(DataInputStream in, UUID clientID) throws IOException {
this.path = in.readUTF();
this.method = WebRequestMethod.valueOf(in.readUTF());
this.headers = readStringMap(in);
int len = in.readInt();
if (len < 0) {
throw new IOException("Negative body length in WebRequestPacket");
}
this.body = in.readNBytes(len);
}
}

View File

@@ -0,0 +1,83 @@
package org.openautonomousconnection.protocol.packets.v1_0_0.beta.web;
import lombok.Getter;
import org.openautonomousconnection.protocol.packets.OACPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
/**
* Represents a web response in OAC Web protocol.
*
* <p>Important: This packet encodes headers using a deterministic UTF-based map format
* (int size, then size times: UTF key, UTF value) instead of Java object serialization.</p>
*/
public final class WebResponsePacket extends OACPacket {
@Getter
private int statusCode;
@Getter
private String contentType;
@Getter
private Map<String, String> headers;
@Getter
private byte[] body;
/**
* Creates an empty response packet (used by PacketHandler factory).
*/
public WebResponsePacket() {
super(9, ProtocolVersion.PV_1_0_0_BETA);
}
/**
* Creates a response packet.
*
* @param statusCode HTTP-like status code (e.g. 200, 404, 500)
* @param contentType MIME type (e.g. "text/html")
* @param headers response headers (may be null)
* @param body response body (may be null)
*/
public WebResponsePacket(int statusCode, String contentType, Map<String, String> headers, byte[] body) {
this();
this.statusCode = statusCode;
this.contentType = (contentType != null) ? contentType : "text/plain";
this.headers = (headers != null) ? new LinkedHashMap<>(headers) : Collections.emptyMap();
this.body = (body != null) ? body : new byte[0];
}
@Override
public void onWrite(DataOutputStream out) throws IOException {
out.writeInt(statusCode);
out.writeUTF((contentType != null) ? contentType : "text/plain");
writeStringMap(out, headers);
byte[] b = (body != null) ? body : new byte[0];
out.writeInt(b.length);
out.write(b);
}
@Override
public void onRead(DataInputStream in, UUID clientID) throws IOException {
this.statusCode = in.readInt();
this.contentType = in.readUTF();
this.headers = readStringMap(in);
int len = in.readInt();
if (len < 0) {
throw new IOException("Negative body length in WebResponsePacket");
}
this.body = in.readNBytes(len);
}
}

View File

@@ -0,0 +1,43 @@
package org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream;
import lombok.Getter;
import org.openautonomousconnection.protocol.packets.OACPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.UUID;
public final class WebStreamChunkPacket_v1_0_0_B extends OACPacket {
@Getter
private int seq;
@Getter
private byte[] data;
public WebStreamChunkPacket_v1_0_0_B() {
super(13, ProtocolVersion.PV_1_0_0_BETA);
}
public WebStreamChunkPacket_v1_0_0_B(int seq, byte[] data) {
this();
this.seq = seq;
this.data = (data != null) ? data : new byte[0];
}
@Override
public void onWrite(DataOutputStream out) throws IOException {
out.writeInt(seq);
out.writeInt(data.length);
out.write(data);
}
@Override
public void onRead(DataInputStream in, UUID clientID) throws IOException {
seq = in.readInt();
int len = in.readInt();
if (len < 0) throw new IOException("Negative chunk length");
data = in.readNBytes(len);
}
}

View File

@@ -0,0 +1,35 @@
package org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream;
import lombok.Getter;
import org.openautonomousconnection.protocol.packets.OACPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.UUID;
public final class WebStreamEndPacket_v1_0_0_B extends OACPacket {
@Getter
private boolean ok;
public WebStreamEndPacket_v1_0_0_B() {
super(13, ProtocolVersion.PV_1_0_0_BETA);
}
public WebStreamEndPacket_v1_0_0_B(boolean ok) {
this();
this.ok = ok;
}
@Override
public void onWrite(DataOutputStream out) throws IOException {
out.writeBoolean(ok);
}
@Override
public void onRead(DataInputStream in, UUID clientID) throws IOException {
ok = in.readBoolean();
}
}

View File

@@ -0,0 +1,77 @@
package org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream;
import lombok.Getter;
import org.openautonomousconnection.protocol.packets.OACPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
/**
* Starts a streaming response.
*
* <p>Important: This packet encodes headers using a deterministic UTF-based map format
* (int size, then size times: UTF key, UTF value) instead of Java object serialization.</p>
*/
public final class WebStreamStartPacket_v1_0_0_B extends OACPacket {
@Getter
private int statusCode;
@Getter
private String contentType;
@Getter
private Map<String, String> headers;
@Getter
private long totalLength;
/**
* Creates an empty start packet (used by PacketHandler factory).
*/
public WebStreamStartPacket_v1_0_0_B() {
super(11, ProtocolVersion.PV_1_0_0_BETA);
}
/**
* Creates a start packet.
*
* @param statusCode status code
* @param contentType content type
* @param headers headers (may be null)
* @param totalLength total length of the stream (may be -1 if unknown)
*/
public WebStreamStartPacket_v1_0_0_B(int statusCode, String contentType, Map<String, String> headers, long totalLength) {
this();
this.statusCode = statusCode;
this.contentType = (contentType != null) ? contentType : "application/octet-stream";
this.headers = (headers != null) ? new LinkedHashMap<>(headers) : Collections.emptyMap();
this.totalLength = totalLength;
}
@Override
public void onWrite(DataOutputStream out) throws IOException {
out.writeInt(statusCode);
out.writeUTF((contentType != null) ? contentType : "application/octet-stream");
writeStringMap(out, headers);
out.writeLong(totalLength);
}
@Override
public void onRead(DataInputStream in, UUID clientID) throws IOException {
this.statusCode = in.readInt();
this.contentType = in.readUTF();
this.headers = readStringMap(in);
this.totalLength = in.readLong();
}
}

View File

@@ -1,87 +0,0 @@
package org.openautonomousconnection.protocol.packets.v1_0_0.classic;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.packets.OACPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.UnsupportedClassicPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.events.Classic_DomainPacketReceivedEvent;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_Domain;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_RequestDomain;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.utils.Classic_ProtocolVersion;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.sql.SQLException;
public final class Classic_DomainPacket extends OACPacket {
private Classic_RequestDomain requestDomain;
private Classic_Domain domain;
private int clientID;
private ProtocolBridge bridge;
public Classic_DomainPacket(int toClient, Classic_RequestDomain requestDomain, Classic_Domain domain, ProtocolBridge protocolBridge) {
this();
this.clientID = toClient;
this.bridge = protocolBridge;
this.requestDomain = requestDomain;
this.domain = domain;
}
// Registration constructor
public Classic_DomainPacket() {
super(2, ProtocolVersion.PV_1_0_0_CLASSIC);
}
@Override
public void onWrite(PacketHandler packetHandler, ObjectOutputStream objectOutputStream) throws IOException, ClassNotFoundException {
if (bridge.isRunningAsINSServer()) {
objectOutputStream.writeInt(clientID);
objectOutputStream.writeObject(requestDomain);
objectOutputStream.writeObject(domain);
} else if (bridge.isRunningAsClient()) {
clientID = bridge.getProtocolClient().getClientINSConnection().getClientID();
objectOutputStream.writeInt(clientID);
objectOutputStream.writeObject(requestDomain);
}
objectOutputStream.writeObject(Classic_ProtocolVersion.PV_1_0_0);
}
@Override
public void onRead(PacketHandler packetHandler, ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
if (bridge.isRunningAsINSServer()) {
clientID = objectInputStream.readInt();
requestDomain = (Classic_RequestDomain) objectInputStream.readObject();
Classic_ProtocolVersion protocolVersion = (Classic_ProtocolVersion) objectInputStream.readObject();
try {
domain = bridge.getClassicHandlerINSServer().getDomain(requestDomain);
} catch (SQLException exception) {
exception.printStackTrace();
}
bridge.getProtocolINSServer().getNetworkServer().getEventManager().executeEvent(new Classic_DomainPacketReceivedEvent(protocolVersion, domain, requestDomain, clientID));
if (bridge.getProtocolINSServer().getClientByID(clientID).supportClientClassic())
bridge.getProtocolINSServer().getNetworkServer().getConnectionHandlerByID(clientID).sendPacket(new Classic_DomainPacket(clientID, requestDomain, domain, bridge));
else
bridge.getProtocolINSServer().getNetworkServer().getConnectionHandlerByID(clientID).sendPacket(new UnsupportedClassicPacket(Classic_PingPacket.class, new Object[]{clientID, requestDomain, domain}, bridge));
} else if (bridge.isRunningAsClient()) {
clientID = objectInputStream.readInt();
requestDomain = (Classic_RequestDomain) objectInputStream.readObject();
domain = (Classic_Domain) objectInputStream.readObject();
Classic_ProtocolVersion protocolVersion = (Classic_ProtocolVersion) objectInputStream.readObject();
bridge.getProtocolClient().getClientINSConnection().getEventManager().executeEvent(new Classic_DomainPacketReceivedEvent(protocolVersion, domain, requestDomain, clientID));
} else if (bridge.isRunningAsWebServer()) {
clientID = objectInputStream.readInt();
requestDomain = (Classic_RequestDomain) objectInputStream.readObject();
Classic_ProtocolVersion protocolVersion = (Classic_ProtocolVersion) objectInputStream.readObject();
bridge.getProtocolWebServer().getPipelineServer().getConnectionHandlerByID(clientID).sendPacket(new UnsupportedClassicPacket(Classic_PingPacket.class, new Object[]{clientID, requestDomain, domain}, bridge));
}
}
}

View File

@@ -1,65 +0,0 @@
package org.openautonomousconnection.protocol.packets.v1_0_0.classic;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.packets.OACPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.utils.Classic_ProtocolVersion;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public final class Classic_MessagePacket extends OACPacket {
private String message;
private int clientID;
private ProtocolBridge bridge;
// Constructor with message and client id
public Classic_MessagePacket(String message, int toClient, ProtocolBridge bridge) {
this();
this.message = message;
this.clientID = toClient;
this.bridge = bridge;
}
public Classic_MessagePacket() {
super(3, ProtocolVersion.PV_1_0_0_CLASSIC);
}
@Override
public void onWrite(PacketHandler packetHandler, ObjectOutputStream objectOutputStream) throws IOException, ClassNotFoundException {
if (bridge.isRunningAsINSServer() || bridge.isRunningAsWebServer())
objectOutputStream.writeInt(clientID);
else if (bridge.isRunningAsClient()) {
clientID = bridge.getProtocolClient().getClientINSConnection().getClientID();
objectOutputStream.writeInt(clientID);
}
objectOutputStream.writeUTF(message);
objectOutputStream.writeObject(Classic_ProtocolVersion.PV_1_0_0);
}
@Override
public void onRead(PacketHandler packetHandler, ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
if (bridge.isRunningAsINSServer()) {
clientID = objectInputStream.readInt();
String message = objectInputStream.readUTF();
Classic_ProtocolVersion protocolVersion = (Classic_ProtocolVersion) objectInputStream.readObject();
bridge.getClassicHandlerINSServer().handleMessage(bridge.getProtocolINSServer().getClientByID(clientID), message, protocolVersion);
} else if (bridge.isRunningAsClient()) {
clientID = objectInputStream.readInt();
String message = objectInputStream.readUTF();
Classic_ProtocolVersion protocolVersion = (Classic_ProtocolVersion) objectInputStream.readObject();
bridge.getClassicHandlerClient().handleMessage(message, protocolVersion);
} else if (bridge.isRunningAsWebServer()) {
clientID = objectInputStream.readInt();
String message = objectInputStream.readUTF();
Classic_ProtocolVersion protocolVersion = (Classic_ProtocolVersion) objectInputStream.readObject();
bridge.getClassicHandlerWebServer().handleMessage(bridge.getProtocolINSServer().getClientByID(clientID), message, protocolVersion);
}
}
}

View File

@@ -1,93 +0,0 @@
package org.openautonomousconnection.protocol.packets.v1_0_0.classic;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.packets.OACPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.UnsupportedClassicPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.events.Classic_PingPacketReceivedEvent;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_Domain;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_RequestDomain;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.utils.Classic_ProtocolVersion;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.sql.SQLException;
public final class Classic_PingPacket extends OACPacket {
private Classic_RequestDomain requestDomain;
private Classic_Domain domain;
private int clientID;
private boolean reachable;
private Classic_ProtocolVersion protocolVersion;
private ProtocolBridge bridge;
public Classic_PingPacket(Classic_RequestDomain requestDomain, Classic_Domain domain, boolean reachable, ProtocolBridge bridge) {
this();
this.bridge = bridge;
this.requestDomain = requestDomain;
this.domain = domain;
this.reachable = reachable;
this.protocolVersion = Classic_ProtocolVersion.PV_1_0_0;
}
public Classic_PingPacket() {
super(1, ProtocolVersion.PV_1_0_0_CLASSIC);
}
@Override
public void onWrite(PacketHandler packetHandler, ObjectOutputStream objectOutputStream) throws IOException, ClassNotFoundException {
if (bridge.isRunningAsINSServer()) {
objectOutputStream.writeInt(clientID);
objectOutputStream.writeObject(requestDomain);
objectOutputStream.writeObject(domain);
objectOutputStream.writeBoolean(reachable);
} else if (bridge.isRunningAsClient()) {
clientID = bridge.getProtocolClient().getClientINSConnection().getClientID();
objectOutputStream.writeInt(clientID);
objectOutputStream.writeObject(requestDomain);
}
objectOutputStream.writeObject(protocolVersion);
}
@Override
public void onRead(PacketHandler packetHandler, ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
if (bridge.isRunningAsINSServer()) {
clientID = objectInputStream.readInt();
requestDomain = (Classic_RequestDomain) objectInputStream.readObject();
protocolVersion = (Classic_ProtocolVersion) objectInputStream.readObject();
try {
domain = bridge.getClassicHandlerINSServer().ping(requestDomain);
} catch (SQLException exception) {
exception.printStackTrace();
}
reachable = domain != null;
bridge.getProtocolINSServer().getNetworkServer().getEventManager().executeEvent(new Classic_PingPacketReceivedEvent(protocolVersion, domain, requestDomain, reachable, clientID));
if (bridge.getProtocolINSServer().getClientByID(clientID).supportClientClassic())
bridge.getProtocolINSServer().getNetworkServer().getConnectionHandlerByID(clientID).sendPacket(new Classic_PingPacket(requestDomain, domain, reachable, bridge));
else
bridge.getProtocolINSServer().getNetworkServer().getConnectionHandlerByID(clientID).sendPacket(new UnsupportedClassicPacket(Classic_PingPacket.class, new Object[]{requestDomain, domain, reachable}, bridge));
} else if (bridge.isRunningAsClient()) {
clientID = objectInputStream.readInt();
requestDomain = (Classic_RequestDomain) objectInputStream.readObject();
domain = (Classic_Domain) objectInputStream.readObject();
boolean reachable = objectInputStream.readBoolean();
Classic_ProtocolVersion protocolVersion = (Classic_ProtocolVersion) objectInputStream.readObject();
bridge.getClassicHandlerClient().validationCompleted(domain, reachable ? INSResponseStatus.OK : INSResponseStatus.NOT_FOUND);
bridge.getProtocolClient().getClientINSConnection().getEventManager().executeEvent(new Classic_PingPacketReceivedEvent(protocolVersion, domain, requestDomain, reachable, clientID));
} else if (bridge.isRunningAsWebServer()) {
clientID = objectInputStream.readInt();
requestDomain = (Classic_RequestDomain) objectInputStream.readObject();
protocolVersion = (Classic_ProtocolVersion) objectInputStream.readObject();
bridge.getProtocolWebServer().getPipelineServer().getConnectionHandlerByID(clientID).sendPacket(new UnsupportedClassicPacket(Classic_PingPacket.class, new Object[]{requestDomain}, bridge));
}
}
}

View File

@@ -0,0 +1,67 @@
package org.openautonomousconnection.protocol.packets.v1_0_1.beta.web;
import lombok.Getter;
import org.openautonomousconnection.protocol.packets.OACPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.UUID;
/**
* Base class for all Web v1.0.1-BETA packets.
*
* <p>Ensures the {@link WebPacketHeader} is always serialized first.</p>
*/
@Getter
public abstract class WebPacket extends OACPacket {
private WebPacketHeader header;
protected WebPacket(int packetId, ProtocolVersion version) {
super(packetId, version);
}
/**
* Sets the header before sending.
*
* @param header header (required)
*/
public void setHeader(WebPacketHeader header) {
this.header = header;
}
@Override
public final void onWrite(DataOutputStream out) throws IOException {
if (header == null) {
throw new IOException("WebPacketHeader is required");
}
header.write(out);
writeBody(out);
}
@Override
public final void onRead(DataInputStream in, UUID clientID) throws IOException {
this.header = WebPacketHeader.read(in);
readBody(in, clientID);
}
/**
* Writes packet-specific payload after the header.
*
* @param out stream
* @throws IOException on IO error
*/
protected abstract void writeBody(DataOutputStream out) throws IOException;
/**
* Reads packet-specific payload after the header.
*
* @param in stream
* @param clientID sender client id
* @throws IOException on IO error
*/
protected abstract void readBody(DataInputStream in, UUID clientID) throws IOException;
}

View File

@@ -0,0 +1,54 @@
package org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import lombok.Getter;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.WebPacket;
import org.openautonomousconnection.protocol.side.server.CustomConnectedClient;
import org.openautonomousconnection.protocol.side.web.ProtocolWebServer;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.UUID;
/**
* Request: replace the document content with full HTML.
*/
@Getter
public final class WebDocumentApplyRequestPacket extends WebPacket {
private String fullHtml;
private final ProtocolBridge protocolBridge;
/**
* Registration constructor.
*/
public WebDocumentApplyRequestPacket(ProtocolBridge protocolBridge) {
super(18, ProtocolVersion.PV_1_0_1_BETA);
this.protocolBridge = protocolBridge;
}
public WebDocumentApplyRequestPacket(WebPacketHeader header, String fullHtml, ProtocolBridge protocolBridge) {
this(protocolBridge);
setHeader(header);
this.fullHtml = (fullHtml != null) ? fullHtml : "";
}
@Override
protected void writeBody(DataOutputStream out) throws IOException {
out.writeUTF((fullHtml != null) ? fullHtml : "");
}
@Override
protected void readBody(DataInputStream in, UUID clientID) throws IOException {
this.fullHtml = in.readUTF();
if (protocolBridge.isRunningAsWebServer()) {
ProtocolWebServer server = (ProtocolWebServer) protocolBridge.getProtocolServer();
CustomConnectedClient client = server.getClientByID(clientID);
client.getConnection().sendPacket(server.handleDocumentApply(client, this), TransportProtocol.TCP);
}
}
}

View File

@@ -0,0 +1,48 @@
package org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document;
import lombok.Getter;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.WebPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.UUID;
/**
* Response: result of applying document changes.
*/
@Getter
public final class WebDocumentApplyResponsePacket extends WebPacket {
private boolean ok;
private String error;
/**
* Registration constructor.
*/
public WebDocumentApplyResponsePacket() {
super(5, ProtocolVersion.PV_1_0_1_BETA);
}
public WebDocumentApplyResponsePacket(WebPacketHeader header, boolean ok, String error) {
this();
setHeader(header);
this.ok = ok;
this.error = error;
}
@Override
protected void writeBody(DataOutputStream out) throws IOException {
out.writeBoolean(ok);
out.writeBoolean(error != null);
if (error != null) out.writeUTF(error);
}
@Override
protected void readBody(DataInputStream in, UUID clientID) throws IOException {
this.ok = in.readBoolean();
boolean hasErr = in.readBoolean();
this.error = hasErr ? in.readUTF() : null;
}
}

View File

@@ -0,0 +1,46 @@
package org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document;
import lombok.Getter;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.WebPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.UUID;
/**
* Event: sends the current document snapshot (HTML) for a page.
*/
@Getter
public final class WebDocumentSnapshotEventPacket extends WebPacket {
private String baseUrl;
private String html;
/**
* Registration constructor.
*/
public WebDocumentSnapshotEventPacket() {
super(17, ProtocolVersion.PV_1_0_1_BETA);
}
public WebDocumentSnapshotEventPacket(WebPacketHeader header, String baseUrl, String html) {
this();
setHeader(header);
this.baseUrl = (baseUrl != null) ? baseUrl : "";
this.html = (html != null) ? html : "";
}
@Override
protected void writeBody(DataOutputStream out) throws IOException {
out.writeUTF((baseUrl != null) ? baseUrl : "");
out.writeUTF((html != null) ? html : "");
}
@Override
protected void readBody(DataInputStream in, UUID clientID) throws IOException {
this.baseUrl = in.readUTF();
this.html = in.readUTF();
}
}

View File

@@ -0,0 +1,48 @@
package org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate;
import lombok.Getter;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.WebPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.UUID;
/**
* Acknowledges (or rejects) a navigation request.
*/
@Getter
public final class WebNavigateAckPacket extends WebPacket {
private boolean accepted;
private String reason;
/**
* Registration constructor.
*/
public WebNavigateAckPacket() {
super(2, ProtocolVersion.PV_1_0_1_BETA);
}
public WebNavigateAckPacket(WebPacketHeader header, boolean accepted, String reason) {
this();
setHeader(header);
this.accepted = accepted;
this.reason = reason;
}
@Override
protected void writeBody(DataOutputStream out) throws IOException {
out.writeBoolean(accepted);
out.writeBoolean(reason != null);
if (reason != null) out.writeUTF(reason);
}
@Override
protected void readBody(DataInputStream in, UUID clientID) throws IOException {
this.accepted = in.readBoolean();
boolean hasReason = in.readBoolean();
this.reason = hasReason ? in.readUTF() : null;
}
}

View File

@@ -0,0 +1,85 @@
package org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import lombok.Getter;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.WebPacket;
import org.openautonomousconnection.protocol.side.server.CustomConnectedClient;
import org.openautonomousconnection.protocol.side.web.ProtocolWebServer;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebCacheMode;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebTransitionType;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Objects;
import java.util.UUID;
/**
* Navigation request for a tab/page.
*/
@Getter
public final class WebNavigateRequestPacket extends WebPacket {
private String url;
private String referrer;
private WebTransitionType transitionType;
private long headerProfileId;
private WebCacheMode cacheMode;
private final ProtocolBridge protocolBridge;
/**
* Registration constructor.
*/
public WebNavigateRequestPacket(ProtocolBridge protocolBridge) {
super(1, ProtocolVersion.PV_1_0_1_BETA);
this.protocolBridge = Objects.requireNonNull(protocolBridge, "protocolBridge");
}
public WebNavigateRequestPacket(
WebPacketHeader header,
String url,
String referrer,
WebTransitionType transitionType,
long headerProfileId,
WebCacheMode cacheMode, ProtocolBridge protocolBridge
) {
this(protocolBridge);
setHeader(header);
this.url = (url != null) ? url : "";
this.referrer = referrer;
this.transitionType = (transitionType != null) ? transitionType : WebTransitionType.TYPED;
this.headerProfileId = headerProfileId;
this.cacheMode = (cacheMode != null) ? cacheMode : WebCacheMode.DEFAULT;
}
@Override
protected void writeBody(DataOutputStream out) throws IOException {
out.writeUTF((url != null) ? url : "");
out.writeBoolean(referrer != null);
if (referrer != null) out.writeUTF(referrer);
out.writeUTF((transitionType != null) ? transitionType.name() : WebTransitionType.TYPED.name());
out.writeLong(headerProfileId);
out.writeUTF((cacheMode != null) ? cacheMode.name() : WebCacheMode.DEFAULT.name());
}
@Override
protected void readBody(DataInputStream in, UUID clientID) throws IOException {
this.url = in.readUTF();
boolean hasRef = in.readBoolean();
this.referrer = hasRef ? in.readUTF() : null;
this.transitionType = WebTransitionType.valueOf(in.readUTF());
this.headerProfileId = in.readLong();
this.cacheMode = WebCacheMode.valueOf(in.readUTF());
if (protocolBridge.isRunningAsWebServer()) {
ProtocolWebServer server = (ProtocolWebServer) protocolBridge.getProtocolServer();
CustomConnectedClient client = server.getClientByID(clientID);
client.getConnection().sendPacket(server.handleNavigate(client, this), TransportProtocol.TCP);
}
}
}

View File

@@ -0,0 +1,113 @@
package org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import lombok.Getter;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.WebPacket;
import org.openautonomousconnection.protocol.side.server.CustomConnectedClient;
import org.openautonomousconnection.protocol.side.web.ProtocolWebServer;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebCacheMode;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebInitiatorType;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
/**
* Resource request
*/
@Getter
public final class WebResourceRequestPacket extends WebPacket {
private String url;
private String method;
private Map<String, String> headers;
private byte[] body;
private String bodyContentType;
private WebInitiatorType initiatorType;
private WebCacheMode cacheMode;
private final ProtocolBridge protocolBridge;
/**
* Registration constructor.
*/
public WebResourceRequestPacket(ProtocolBridge protocolBridge) {
super(3, ProtocolVersion.PV_1_0_1_BETA);
this.protocolBridge = protocolBridge;
}
public WebResourceRequestPacket(
WebPacketHeader header,
String url,
String method,
Map<String, String> headers,
byte[] body,
String bodyContentType,
WebInitiatorType initiatorType,
WebCacheMode cacheMode, ProtocolBridge protocolBridge
) {
this(protocolBridge);
setHeader(header);
this.url = (url != null) ? url : "";
this.method = (method != null) ? method : "GET";
this.headers = (headers != null) ? new LinkedHashMap<>(headers) : Collections.emptyMap();
this.body = (body != null) ? body : new byte[0];
this.bodyContentType = bodyContentType;
this.initiatorType = (initiatorType != null) ? initiatorType : WebInitiatorType.OTHER;
this.cacheMode = (cacheMode != null) ? cacheMode : WebCacheMode.DEFAULT;
}
@Override
protected void writeBody(DataOutputStream out) throws IOException {
out.writeUTF((url != null) ? url : "");
out.writeUTF((method != null) ? method : "GET");
writeStringMap(out, headers);
out.writeBoolean(bodyContentType != null);
if (bodyContentType != null) out.writeUTF(bodyContentType);
out.writeUTF((initiatorType != null) ? initiatorType.name() : WebInitiatorType.OTHER.name());
out.writeUTF((cacheMode != null) ? cacheMode.name() : WebCacheMode.DEFAULT.name());
byte[] b = (body != null) ? body : new byte[0];
out.writeInt(b.length);
out.write(b);
}
@Override
protected void readBody(DataInputStream in, UUID clientID) throws IOException {
this.url = in.readUTF();
this.method = in.readUTF();
this.headers = readStringMap(in);
boolean hasBct = in.readBoolean();
this.bodyContentType = hasBct ? in.readUTF() : null;
try {
this.initiatorType = WebInitiatorType.valueOf(in.readUTF());
} catch (IllegalArgumentException e) {
getProtocolBridge().getProtocolValues().logger.exception("Invalid initiator type: " + initiatorType, e);
this.initiatorType = WebInitiatorType.OTHER;
}
this.cacheMode = WebCacheMode.valueOf(in.readUTF());
int len = in.readInt();
if (len < 0) throw new IOException("Negative body length in WebResourceRequestPacket");
this.body = in.readNBytes(len);
if (protocolBridge.isRunningAsWebServer()) {
ProtocolWebServer server = (ProtocolWebServer) protocolBridge.getProtocolServer();
CustomConnectedClient client = server.getClientByID(clientID);
if (client == null) return;
client.getConnection().sendPacket(server.handleResource(client, this), TransportProtocol.TCP);
}
}
}

View File

@@ -0,0 +1,83 @@
package org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource;
import lombok.Getter;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.WebPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
/**
* Resource response
*
* <p>If the response body is streamed, send this packet with an empty body and set STREAM flag in the header.</p>
*/
@Getter
public final class WebResourceResponsePacket extends WebPacket {
private int statusCode;
private String contentType;
private Map<String, String> headers;
private byte[] body;
private String finalUrl;
/**
* Registration constructor.
*/
public WebResourceResponsePacket() {
super(4, ProtocolVersion.PV_1_0_1_BETA);
}
public WebResourceResponsePacket(
WebPacketHeader header,
int statusCode,
String contentType,
Map<String, String> headers,
byte[] body,
String finalUrl
) {
this();
setHeader(header);
this.statusCode = statusCode;
this.contentType = (contentType != null) ? contentType : "application/octet-stream";
this.headers = (headers != null) ? new LinkedHashMap<>(headers) : Collections.emptyMap();
this.body = (body != null) ? body : new byte[0];
this.finalUrl = finalUrl;
}
@Override
protected void writeBody(DataOutputStream out) throws IOException {
out.writeInt(statusCode);
out.writeUTF((contentType != null) ? contentType : "application/octet-stream");
writeStringMap(out, headers);
out.writeBoolean(finalUrl != null);
if (finalUrl != null) out.writeUTF(finalUrl);
byte[] b = (body != null) ? body : new byte[0];
out.writeInt(b.length);
out.write(b);
}
@Override
protected void readBody(DataInputStream in, UUID clientID) throws IOException {
this.statusCode = in.readInt();
this.contentType = in.readUTF();
this.headers = readStringMap(in);
boolean hasFinal = in.readBoolean();
this.finalUrl = hasFinal ? in.readUTF() : null;
int len = in.readInt();
if (len < 0) throw new IOException("Negative body length in WebResourceResponsePacket");
this.body = in.readNBytes(len);
}
}

View File

@@ -0,0 +1,52 @@
package org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream;
import lombok.Getter;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.WebPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.UUID;
/**
* Stream body chunk.
*
* <p>Correlation is done via {@link WebPacketHeader#getRequestId()}.</p>
*/
@Getter
public final class WebStreamChunkPacket_v1_0_1_B extends WebPacket {
private int seq;
private byte[] data;
/**
* Registration constructor.
*/
public WebStreamChunkPacket_v1_0_1_B() {
super(15, ProtocolVersion.PV_1_0_1_BETA);
}
public WebStreamChunkPacket_v1_0_1_B(WebPacketHeader header, int seq, byte[] data) {
this();
setHeader(header);
this.seq = seq;
this.data = (data != null) ? data : new byte[0];
}
@Override
protected void writeBody(DataOutputStream out) throws IOException {
out.writeInt(seq);
byte[] b = (data != null) ? data : new byte[0];
out.writeInt(b.length);
out.write(b);
}
@Override
protected void readBody(DataInputStream in, UUID clientID) throws IOException {
this.seq = in.readInt();
int len = in.readInt();
if (len < 0) throw new IOException("Negative chunk length in WebStreamChunkPacket");
this.data = in.readNBytes(len);
}
}

View File

@@ -0,0 +1,50 @@
package org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream;
import lombok.Getter;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.WebPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.UUID;
/**
* Ends a streamed body transfer.
*
* <p>Correlation is done via {@link WebPacketHeader#getRequestId()}.</p>
*/
@Getter
public final class WebStreamEndPacket_v1_0_1_B extends WebPacket {
private boolean ok;
private String error;
/**
* Registration constructor.
*/
public WebStreamEndPacket_v1_0_1_B() {
super(16, ProtocolVersion.PV_1_0_1_BETA);
}
public WebStreamEndPacket_v1_0_1_B(WebPacketHeader header, boolean ok, String error) {
this();
setHeader(header);
this.ok = ok;
this.error = error;
}
@Override
protected void writeBody(DataOutputStream out) throws IOException {
out.writeBoolean(ok);
out.writeBoolean(error != null);
if (error != null) out.writeUTF(error);
}
@Override
protected void readBody(DataInputStream in, UUID clientID) throws IOException {
this.ok = in.readBoolean();
boolean hasErr = in.readBoolean();
this.error = hasErr ? in.readUTF() : null;
}
}

View File

@@ -0,0 +1,59 @@
package org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream;
import lombok.Getter;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.WebPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
/**
* Starts a streamed response body.
*
* <p>Correlation is done via {@link WebPacketHeader#getRequestId()}.</p>
*/
@Getter
public final class WebStreamStartPacket_v1_0_1_B extends WebPacket {
private int statusCode;
private String contentType;
private Map<String, String> headers;
private long totalLength;
/**
* Registration constructor.
*/
public WebStreamStartPacket_v1_0_1_B() {
super(14, ProtocolVersion.PV_1_0_1_BETA);
}
public WebStreamStartPacket_v1_0_1_B(WebPacketHeader header, int statusCode, String contentType, Map<String, String> headers, long totalLength) {
this();
setHeader(header);
this.statusCode = statusCode;
this.contentType = (contentType != null) ? contentType : "application/octet-stream";
this.headers = (headers != null) ? new LinkedHashMap<>(headers) : Collections.emptyMap();
this.totalLength = totalLength;
}
@Override
protected void writeBody(DataOutputStream out) throws IOException {
out.writeInt(statusCode);
out.writeUTF((contentType != null) ? contentType : "application/octet-stream");
writeStringMap(out, headers);
out.writeLong(totalLength);
}
@Override
protected void readBody(DataInputStream in, UUID clientID) throws IOException {
this.statusCode = in.readInt();
this.contentType = in.readUTF();
this.headers = readStringMap(in);
this.totalLength = in.readLong();
}
}

View File

@@ -1,9 +1,13 @@
package org.openautonomousconnection.protocol.side.client;
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
import dev.unlegitdqrk.unlegitlibrary.event.EventPriority;
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
import dev.unlegitdqrk.unlegitlibrary.file.FileUtils;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.ClientDisconnectedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.utils.NetworkUtils;
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.ClientDisconnectedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import dev.unlegitdqrk.unlegitlibrary.network.utils.PemUtils;
import lombok.Getter;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
@@ -17,291 +21,341 @@ import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseSta
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Abstract class defining the client-side protocol operations and interactions with INS and web servers.
* Abstract class defining the client-side protocol operations and interactions with INS and servers.
*/
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.INS)
public abstract class ProtocolClient extends DefaultMethodsOverrider {
/**
* Handles everything with INS-Connection.
*/
private final NetworkClient clientToINS;
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.CLIENT)
public abstract class ProtocolClient extends EventListener {
/**
* Manages the folder structure for client certificates.
*/
@Getter
private final ClientCertificateFolderStructure folderStructure;
/**
* The reference to the ProtocolBridge Object
*/
private NetworkClient clientToINS;
private NetworkClient clientToServer;
@Getter
private final ProtocolBridge protocolBridge;
/**
* Stores the protocol version of the connected server.
*/
private ProtocolBridge protocolBridge;
private ProtocolVersion insVersion = null;
private ProtocolVersion serverVersion = null;
/**
* Initializes the ProtocolClient, setting up certificate folders and the INS client connection.
*
* @throws CertificateException if there are issues with the certificates.
* @throws IOException if there are I/O issues during initialization.
*/
public ProtocolClient(ProtocolBridge protocolBridge) throws CertificateException, IOException {
this.protocolBridge = protocolBridge;
// Initialize and verify certificate folders and files
public ProtocolClient() {
folderStructure = new ClientCertificateFolderStructure();
// Initialize connection to INS server
clientToINS = new NetworkClient.ClientBuilder().setLogger(protocolBridge.getLogger()).setProxy(protocolBridge.getProxy()).
setHost(protocolBridge.getProtocolSettings().host).setPort(protocolBridge.getProtocolSettings().port).
setPacketHandler(protocolBridge.getProtocolSettings().packetHandler).setEventManager(protocolBridge.getProtocolSettings().eventManager).
setRootCAFolder(folderStructure.publicCAFolder).setClientCertificatesFolder(folderStructure.publicClientFolder, folderStructure.privateClientFolder).
build();
}
/**
* Gets the INS connection client.
*
* @return the NetworkClient handling the INS connection.
*/
public final void buildINSConnection() {
if (!protocolBridge.isRunningAsClient())
throw new IllegalStateException("Not running as client");
if (clientToINS != null &&
(clientToINS.isConnected() || clientToINS.isTCPConnected() || clientToINS.isUDPConnected()))
return;
clientToINS = new NetworkClient.Builder().sslEnabled(false).
packetHandler(protocolBridge.getProtocolValues().packetHandler)
.eventManager(protocolBridge.getProtocolValues().eventManager)
.build();
}
public final void buildServerConnection(String keyPassword, boolean ssl) throws Exception {
if (!protocolBridge.isRunningAsClient())
throw new IllegalStateException("Not running as client");
if (clientToServer != null &&
(clientToServer.isConnected() || clientToServer.isTCPConnected() || clientToServer.isUDPConnected()))
return;
if (ssl) {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
int caIndex = 0;
for (File caPem : FileUtils.listFiles(folderStructure.publicCAFolder, ".pem", ".crt", ".cer")) {
if (caPem.getName().endsWith(".srl")) continue;
X509Certificate caCert = PemUtils.loadCertificate(caPem);
trustStore.setCertificateEntry("root-ca-" + (caIndex++), caCert);
}
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
char[] keyPass = new char[0];
if (keyPassword != null) keyPass = keyPassword.toCharArray();
Map<String, File> keyFiles = new HashMap<>();
Map<String, File> certFiles = new HashMap<>();
for (File f : FileUtils.listFiles(folderStructure.privateClientFolder, ".key", ".pem")) {
if (f.getName().endsWith(".srl")) continue;
String base = FileUtils.stripExt(f.getName());
if (f.getName().endsWith(".key")) keyFiles.put(base, f);
if (f.getName().endsWith(".pem")) certFiles.put(base, f);
}
int clientIndex = 0;
for (String base : keyFiles.keySet()) {
File keyFile = keyFiles.get(base);
File certFile = certFiles.get(base);
if (certFile == null) {
File alt = new File(folderStructure.publicClientFolder, base + ".pem");
if (alt.exists()) certFile = alt;
}
if (certFile == null) continue;
PrivateKey key = PemUtils.loadPrivateKey(keyFile);
X509Certificate cert = PemUtils.loadCertificate(certFile);
String alias = "client-" + (clientIndex++);
keyStore.setKeyEntry(alias, key, keyPass, new X509Certificate[]{cert});
}
clientToServer = new NetworkClient.Builder().sslEnabled(ssl).
packetHandler(protocolBridge.getProtocolValues().packetHandler)
.eventManager(protocolBridge.getProtocolValues().eventManager)
.keyStore(keyStore, keyPass)
.trustStore(trustStore)
.build();
} else {
clientToServer = new NetworkClient.Builder().sslEnabled(ssl).
packetHandler(protocolBridge.getProtocolValues().packetHandler)
.eventManager(protocolBridge.getProtocolValues().eventManager)
.build();
}
}
public final NetworkClient getClientServerConnection() {
return clientToServer;
}
public final void attachBridge(ProtocolBridge bridge) throws Exception {
if (this.protocolBridge != null)
throw new IllegalStateException("ProtocolBridge already attached!");
this.protocolBridge = bridge;
protocolBridge.getProtocolValues().eventManager.registerListener(this);
}
public final NetworkClient getClientINSConnection() {
return clientToINS;
}
/**
* Checks if the required certificate files exist in the specified folder.
*
* @param folder the folder to check for certificate files.
* @param prefix the prefix of the certificate files.
* @param extension the extension of the certificate files.
* @throws CertificateException if any required certificate file is missing or invalid.
* @throws IOException if there are I/O issues during the check.
*/
private final void checkFileExists(File folder, String prefix, String extension) throws CertificateException, IOException {
boolean found = false;
// Check if folder exists
private void checkFileExists(File folder, String prefix, String extension) throws CertificateException, IOException {
if (folder == null) throw new FileNotFoundException("Folder does not exist");
// List files in the folder
File[] files = folder.listFiles();
// Check if folder is empty
if (files == null || files.length == 0)
throw new FileNotFoundException("Folder " + folder.getAbsolutePath() + " is empty");
// Validate each file in the folder
for (File file : files) {
if (!file.getName().startsWith(prefix))
throw new CertificateException(file.getAbsolutePath() + " is not valid");
// Check for specific files
if (!found) found = file.getName().equalsIgnoreCase(prefix + NetworkUtils.getPublicIPAddress() + extension);
}
// If the specific file is not found, throw an exception
if (!found) throw new CertificateException("Missing " + prefix + NetworkUtils.getPublicIPAddress() + extension);
}
/**
* Gets the protocol version of the connected server.
*
* @return the ProtocolVersion of the server, or PV_1_0_0_CLASSIC if not set.
*/
public final ProtocolVersion getServerVersion() {
return serverVersion == null ? ProtocolVersion.PV_1_0_0_CLASSIC : serverVersion;
}
/**
* Sets the protocol version of the connected server.
*
* @param serverVersion the ProtocolVersion to set for the server.
*/
public final void setServerVersion(ProtocolVersion serverVersion) {
if (serverVersion == null) this.serverVersion = serverVersion;
if (this.serverVersion == null) this.serverVersion = serverVersion;
}
/**
* Handles INS disconnection events, resetting the server version and closing the web client connection if necessary.
*
* @param event the ClientDisconnectedEvent triggered on INS disconnection.
*/
public final void onINSDisconnect(ClientDisconnectedEvent event) {
// Reset server version on INS disconnect
serverVersion = null;
public final ProtocolVersion getInsVersion() {
return insVersion == null ? ProtocolVersion.PV_1_0_0_CLASSIC : insVersion;
}
/**
* Checks if the connected server is a stable server.
*
* @return true if the server is stable, false otherwise.
*/
public final boolean isStableServer() {
// Check if the server version is stable
return !isBetaServer() && !isClassicServer();
public final void setInsVersion(ProtocolVersion insVersion) {
if (this.insVersion == null) this.insVersion = insVersion;
}
/**
* Checks if the connected server or its compatible versions support stable protocol.
*
* @return true if stable protocol is supported, false otherwise.
*/
public final boolean supportServerStable() {
@Listener(priority = EventPriority.HIGHEST)
public final void onDisconnect(ClientDisconnectedEvent event) {
if (clientToINS == null || !clientToINS.isConnected()) {
insVersion = null;
clientToINS = null;
disconnectFromServer();
}
if (clientToServer == null || !clientToServer.isConnected()) {
serverVersion = null;
clientToServer = null;
}
}
public final boolean isINSStableServer() {
return !isINSBetaServer() && !isINSClassicServer();
}
public final boolean supportINSServerStable() {
boolean yes = false;
for (ProtocolVersion compatibleVersion : getServerVersion().getCompatibleVersions()) {
// Check if compatible version is stable
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.STABLE;
if (yes) break;
}
// Check if the server version is stable
return isStableServer() || yes;
return isINSStableServer() || yes;
}
/**
* Checks if the connected server is a beta server.
*
* @return true if the server is beta, false otherwise.
*/
public final boolean isBetaServer() {
// Check if the server version is beta
return getServerVersion().getProtocolType() == ProtocolVersion.ProtocolType.BETA;
public final boolean isINSBetaServer() {
return getInsVersion().getProtocolType() == ProtocolVersion.ProtocolType.BETA;
}
/**
* Checks if the connected server or its compatible versions support beta protocol.
*
* @return true if beta protocol is supported, false otherwise.
*/
public final boolean supportServerBeta() {
public final boolean supportINSServerBeta() {
boolean yes = false;
for (ProtocolVersion compatibleVersion : getServerVersion().getCompatibleVersions()) {
// Check if compatible version is beta
for (ProtocolVersion compatibleVersion : getInsVersion().getCompatibleVersions()) {
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.BETA;
if (yes) break;
}
// Check if the server version is beta
return isBetaServer() || yes;
return isINSBetaServer() || yes;
}
/**
* Checks if the connected server is a classic server.
*
* @return true if the server is classic, false otherwise.
*/
public final boolean isClassicServer() {
// Check if the server version is classic
return getServerVersion().getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
public final boolean isINSClassicServer() {
return getInsVersion().getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
}
/**
* Checks if the connected server or its compatible versions support classic protocol.
*
* @return true if classic protocol is supported, false otherwise.
*/
public final boolean supportServerClassic() {
public final boolean supportINSServerClassic() {
boolean yes = false;
for (ProtocolVersion compatibleVersion : getServerVersion().getCompatibleVersions()) {
// Check if compatible version is classic
for (ProtocolVersion compatibleVersion : getInsVersion().getCompatibleVersions()) {
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
if (yes) break;
}
// Check if the server version is classic
return isClassicServer() || yes;
return isINSClassicServer() || yes;
}
/**
* Checks if the connected server supports the protocol version of the given packet.
*
* @param packet the OACPacket to check against the server's supported protocol version.
* @return true if the server supports the packet's protocol version, false otherwise.
*/
public final boolean supportServerPacket(OACPacket packet) {
// Check if the server supports the protocol version of the packet
return supportServerVersion(packet.getProtocolVersion());
public final boolean supportINSServerPacket(OACPacket packet) {
boolean compatible = false;
for (ProtocolVersion compatibleVersion : packet.getCompatibleVersions()) {
if (!compatible) compatible = supportINSServerVersion(compatibleVersion);
}
return compatible;
}
/**
* Checks if the connected server or its compatible versions support the specified protocol version.
*
* @param targetVersion the ProtocolVersion to check for support.
* @return true if the server or its compatible versions support the target version, false otherwise.
*/
public final boolean supportServerVersion(ProtocolVersion targetVersion) {
// Directly check if the server version matches or is in the list of compatible versions
return getServerVersion() == targetVersion || getServerVersion().getCompatibleVersions().contains(targetVersion);
public final boolean supportINSServerVersion(ProtocolVersion targetVersion) {
return getInsVersion() == targetVersion || getInsVersion().getCompatibleVersions().contains(targetVersion);
}
/**
* Checks if the connected server or its compatible versions support the specified protocol.
*
* @param protocol the Protocol to check for support.
* @return true if the server or its compatible versions support the protocol, false otherwise.
*/
public final boolean supportServerProtocol(ProtocolVersion.Protocol protocol) {
public final boolean supportINSServerProtocol(ProtocolVersion.Protocol protocol) {
boolean yes = false;
for (ProtocolVersion compatibleVersion : getServerVersion().getCompatibleVersions()) {
// Check if compatible version supports the protocol
for (ProtocolVersion compatibleVersion : getInsVersion().getCompatibleVersions()) {
yes = compatibleVersion.getSupportedProtocols().contains(protocol);
if (yes) break;
}
return getInsVersion().getSupportedProtocols().contains(protocol) || yes;
}
// Check if the server version supports the protocol
public final boolean isStableServer() {
return !isBetaServer() && !isClassicServer();
}
public final boolean supportServerStable() {
boolean yes = false;
for (ProtocolVersion compatibleVersion : getServerVersion().getCompatibleVersions()) {
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.STABLE;
if (yes) break;
}
return isStableServer() || yes;
}
public final boolean isBetaServer() {
return getServerVersion().getProtocolType() == ProtocolVersion.ProtocolType.BETA;
}
public final boolean supportServerBeta() {
boolean yes = false;
for (ProtocolVersion compatibleVersion : getServerVersion().getCompatibleVersions()) {
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.BETA;
if (yes) break;
}
return isBetaServer() || yes;
}
public final boolean isClassicServer() {
return getServerVersion().getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
}
public final boolean supportServerClassic() {
boolean yes = false;
for (ProtocolVersion compatibleVersion : getServerVersion().getCompatibleVersions()) {
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
if (yes) break;
}
return isClassicServer() || yes;
}
public final boolean supportServerPacket(OACPacket packet) {
boolean compatible = false;
for (ProtocolVersion compatibleVersion : packet.getCompatibleVersions()) {
if (!compatible) compatible = supportServerVersion(compatibleVersion);
}
return compatible;
}
public final boolean supportServerVersion(ProtocolVersion targetVersion) {
return getServerVersion() == targetVersion || getServerVersion().getCompatibleVersions().contains(targetVersion);
}
public final boolean supportServerProtocol(ProtocolVersion.Protocol protocol) {
boolean yes = false;
for (ProtocolVersion compatibleVersion : getServerVersion().getCompatibleVersions()) {
yes = compatibleVersion.getSupportedProtocols().contains(protocol);
if (yes) break;
}
return getServerVersion().getSupportedProtocols().contains(protocol) || yes;
}
/**
* Sends an INS query to the INS server.
* <p>
* The client requests a specific record type for a given TLN, name and optional subname.
* After sending the packet, {@link #onQuerySent(String, String, String, INSRecordType)} is invoked.
*
* @param tln The TLN.
* @param name The InfoName.
* @param sub Optional subname or {@code null}.
* @param type The requested record type.
* @throws IOException If sending the packet fails.
* @throws ClassNotFoundException If packet serialization fails.
*/
public final void sendINSQuery(String tln, String name, String sub, INSRecordType type) throws IOException, ClassNotFoundException {
public final void sendINSQuery(String tln, String name, String sub, INSRecordType type) throws Exception {
if (!protocolBridge.isRunningAsClient()) return;
getClientINSConnection().sendPacket(new INSQueryPacket(tln, name, sub, type, getClientINSConnection().getClientID()));
getClientINSConnection().sendPacket(
new INSQueryPacket(tln, name, sub, type, getClientINSConnection().getUniqueID()),
TransportProtocol.TCP
);
onQuerySent(tln, name, sub, type);
}
/**
* Called when the client receives an INS response from the server.
* <p>
*
* @param status The response status from the server.
* @param records The list of records returned by the server.
*/
public abstract void onResponse(INSResponseStatus status, List<INSRecord> records);
private final void disconnectFromServer() {
if (clientToServer != null) {
if (clientToINS == null || !clientToINS.isConnected())
protocolBridge.getProtocolValues().eventManager.unregisterListener(this);
clientToServer.disconnect();
clientToServer = null;
}
}
public void onResponse(INSResponseStatus status, List<INSRecord> records) {
}
/**
* Called after a query was sent to the INS server.
* <p>
*
* @param tln The TLN requested.
* @param name The InfoName.
* @param sub Optional subname.
* @param type The requested record type.
*/
public void onQuerySent(String tln, String name, String sub, INSRecordType type) {
}
/**
* Manages the folder structure for client certificates.
* Called when no stored INS fingerprint exists yet (trust-on-first-use).
*
* @param caFingerprint received fingerprint of the INS CA certificate
* @return {@code true} to allow the connection; {@code false} to reject it
*/
public final class ClientCertificateFolderStructure {
public abstract boolean trustINS(String caFingerprint);
/**
* Called when a stored INS fingerprint does not match the received one.
*
* @param oldCAFingerprint previously stored fingerprint
* @param newCAFingerprint received fingerprint of the INS CA certificate
* @return {@code true} to accept the new fingerprint; {@code false} to reject it
*/
public abstract boolean trustNewINSFingerprint(String oldCAFingerprint, String newCAFingerprint);
public static final class ClientCertificateFolderStructure {
public final File certificatesFolder;
public final File publicFolder;
@@ -338,3 +392,4 @@ public abstract class ProtocolClient extends DefaultMethodsOverrider {
}
}
}

View File

@@ -0,0 +1,407 @@
package org.openautonomousconnection.protocol.side.client;
import dev.unlegitdqrk.unlegitlibrary.event.EventPriority;
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.C_PacketReadEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import lombok.Getter;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamChunkPacket_v1_0_0_B;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamEndPacket_v1_0_0_B;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamStartPacket_v1_0_0_B;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.WebPacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentApplyRequestPacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentApplyResponsePacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentSnapshotEventPacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate.WebNavigateAckPacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate.WebNavigateRequestPacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceRequestPacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamChunkPacket_v1_0_1_B;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamEndPacket_v1_0_1_B;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamStartPacket_v1_0_1_B;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.WebRequestMethod;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.*;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebCompatMapper;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
/**
* Web-capable protocol client built on top of {@link ProtocolClient}.
*
* <p>Compatibility strategy:</p>
* <ul>
* <li>In Web v1.0.0 mode, correlation is only possible if there is exactly ONE in-flight request.</li>
* <li>This client therefore enforces single in-flight request and maps responses/streams
* onto that single active {@link WebPacketHeader}.</li>
* </ul>
*/
public abstract class ProtocolWebClient extends ProtocolClient {
/**
* Web header factory bound to this client instance.
*/
@Getter
private final WebHeaderFactory webHeaderFactory = new WebHeaderFactory();
/**
* Tab contexts by tabId (stable).
*/
private final Map<Long, WebHeaderFactory.WebTabContext> tabs = new ConcurrentHashMap<>();
/**
* Single in-flight correlation header.
*
* <p>v1.0.0 has no correlation fields; without changing the v1.0.0 server this is the only deterministic mapping.</p>
*/
private final AtomicReference<WebPacketHeader> v100BInFlight = new AtomicReference<>();
/**
* True once we observed a 1.0.0 stream start for the current in-flight request.
*/
private volatile boolean v100BStreaming;
/**
* Creates and registers a new tab context.
*/
public final WebHeaderFactory.WebTabContext createTab() {
WebHeaderFactory.WebTabContext tab = webHeaderFactory.createTab();
tabs.put(tab.getTabId(), tab);
return tab;
}
/**
* Removes a tab context.
*/
public final void closeTab(long tabId) {
tabs.remove(tabId);
}
/**
* Returns the tab context for a tab id.
*/
public final WebHeaderFactory.WebTabContext getTab(long tabId) {
return tabs.get(tabId);
}
/**
* Begins a new navigation for the given tab.
*/
public final long beginNavigation(WebHeaderFactory.WebTabContext tab) {
Objects.requireNonNull(tab, "tab");
return webHeaderFactory.beginNavigation(tab);
}
public final void sendNavigate(
WebHeaderFactory.WebTabContext tab,
String url,
String referrer,
WebTransitionType transition,
long headerProfileId,
WebCacheMode cacheMode,
boolean noStore
) throws Exception {
Objects.requireNonNull(tab, "tab");
ensureServerConnected();
WebPacketHeader header = webHeaderFactory.navigation(tab, noStore);
header = new WebPacketHeader(
header.getRequestId(),
header.getTabId(),
header.getPageId(),
header.getFrameId(),
header.getFlags() | WebPacketFlags.NAVIGATION,
header.getTimestampMs()
);
if (is100BServer()) {
begin100BRequest(header);
WebRequestPacket packet = new WebRequestPacket(
url,
WebRequestMethod.GET,
Collections.emptyMap(),
null
);
getClientServerConnection().sendPacket(packet, TransportProtocol.TCP);
onWebRequestSent(null);
return;
}
WebNavigateRequestPacket pkt = new WebNavigateRequestPacket(
header,
url,
referrer,
transition,
headerProfileId,
cacheMode, getProtocolBridge()
);
getClientServerConnection().sendPacket(pkt, TransportProtocol.TCP);
onWebRequestSent(pkt);
}
public final long sendResource(
WebHeaderFactory.WebTabContext tab,
long frameId,
String url,
String method,
Map<String, String> headers,
byte[] body,
String bodyContentType,
WebInitiatorType initiator,
WebCacheMode cacheMode,
boolean noStore
) throws Exception {
Objects.requireNonNull(tab, "tab");
ensureServerConnected();
WebPacketHeader base = webHeaderFactory.resource(tab, frameId, noStore);
WebPacketHeader header = new WebPacketHeader(
base.getRequestId(),
base.getTabId(),
base.getPageId(),
base.getFrameId(),
base.getFlags() | WebPacketFlags.RESOURCE,
base.getTimestampMs()
);
if (is100BServer()) {
begin100BRequest(header);
WebRequestPacket packet = new WebRequestPacket(
url,
WebCompatMapper.map100BMethod(method),
headers != null ? headers : Collections.emptyMap(),
body
);
getClientServerConnection().sendPacket(packet, TransportProtocol.TCP);
onWebRequestSent(null);
return header.getRequestId();
}
WebResourceRequestPacket pkt = new WebResourceRequestPacket(
header,
url,
method,
headers,
body,
bodyContentType,
initiator,
cacheMode, getProtocolBridge()
);
getClientServerConnection().sendPacket(pkt, TransportProtocol.TCP);
onWebRequestSent(pkt);
return header.getRequestId();
}
public final long sendDocumentApply(WebHeaderFactory.WebTabContext tab, long frameId, String fullHtml) throws Exception {
Objects.requireNonNull(tab, "tab");
ensureServerConnected();
if (is100BServer()) {
throw new UnsupportedOperationException("Document apply is not supported by Web v1.0.0 servers.");
}
WebPacketHeader base = webHeaderFactory.documentApply(tab, frameId);
WebPacketHeader header = new WebPacketHeader(
base.getRequestId(),
base.getTabId(),
base.getPageId(),
base.getFrameId(),
base.getFlags(),
base.getTimestampMs()
);
WebDocumentApplyRequestPacket pkt = new WebDocumentApplyRequestPacket(header, fullHtml, getProtocolBridge());
getClientServerConnection().sendPacket(pkt, TransportProtocol.TCP);
onWebRequestSent(pkt);
return pkt.getHeader().getRequestId();
}
/**
* Stale detection: drops packets that refer to an old pageId for the same tab.
*/
public final boolean isStale(WebPacketHeader header) {
if (header == null) return false;
WebHeaderFactory.WebTabContext tab = tabs.get(header.getTabId());
if (tab == null) return false;
return webHeaderFactory.isStale(tab, header);
}
/**
* Hook invoked after sending a web request packet (may be null in 1.0.0 mode).
*/
protected void onWebRequestSent(WebPacket packet) {
}
protected void onNavigateAck(WebNavigateAckPacket packet) {
}
protected void onResourceResponse(WebResourceResponsePacket packet) {
}
protected void onStreamStart(WebStreamStartPacket_v1_0_1_B packet) {
}
protected void onStreamChunk(WebStreamChunkPacket_v1_0_1_B packet) {
}
protected void onStreamEnd(WebStreamEndPacket_v1_0_1_B packet) {
}
protected void onDocumentSnapshot(WebDocumentSnapshotEventPacket packet) {
}
protected void onDocumentApplyResponse(WebDocumentApplyResponsePacket packet) {
}
@Listener(priority = EventPriority.HIGHEST)
public final void onPacket(C_PacketReadEvent event) {
Packet p = event.getPacket();
// Native v1.0.1 packets
if (p instanceof WebPacket packet) {
handleIncoming(packet);
return;
}
if (!is100BServer()) {
return;
}
// 1.0.0 mapping requires an active correlation
WebPacketHeader corr = v100BInFlight.get();
if (corr == null) {
// Deterministic mapping is impossible without correlation.
// Dropping is safer than misrouting.
return;
}
// v1.0.0 response -> v1.0.1 resource response
if (p instanceof WebResponsePacket resp) {
WebResourceResponsePacket mapped = WebCompatMapper.toV101ResourceResponse(corr, resp);
onResourceResponse(mapped);
// If this response is not part of a stream, release correlation.
if (!looksLike100BStreamHandshake(resp) && !v100BStreaming) {
end100BRequest();
}
return;
}
// v1.0.0 stream start/chunk/end -> v1.0.1 stream packets
if (p instanceof WebStreamStartPacket_v1_0_0_B start) {
v100BStreaming = true;
WebStreamStartPacket_v1_0_1_B mapped = WebCompatMapper.toV101StreamStart(corr, start);
onStreamStart(mapped);
return;
}
if (p instanceof WebStreamChunkPacket_v1_0_0_B chunk) {
WebStreamChunkPacket_v1_0_1_B mapped = WebCompatMapper.toV101StreamChunk(corr, chunk);
onStreamChunk(mapped);
return;
}
if (p instanceof WebStreamEndPacket_v1_0_0_B end) {
WebStreamEndPacket_v1_0_1_B mapped = WebCompatMapper.toV101StreamEnd(corr, end);
onStreamEnd(mapped);
// stream finished -> release correlation
end100BRequest();
}
}
/**
* Central dispatcher for incoming v1.0.1 web packets.
*/
public final void handleIncoming(WebPacket packet) {
if (packet == null) return;
WebPacketHeader h = packet.getHeader();
if (isStale(h)) return;
switch (packet) {
case WebNavigateAckPacket p -> onNavigateAck(p);
case WebResourceResponsePacket p -> onResourceResponse(p);
case WebStreamStartPacket_v1_0_1_B p -> onStreamStart(p);
case WebStreamChunkPacket_v1_0_1_B p -> onStreamChunk(p);
case WebStreamEndPacket_v1_0_1_B p -> onStreamEnd(p);
case WebDocumentSnapshotEventPacket p -> onDocumentSnapshot(p);
case WebDocumentApplyResponsePacket p -> onDocumentApplyResponse(p);
default -> {
}
}
}
private void ensureServerConnected() {
if (getClientServerConnection() == null) throw new IllegalStateException("Server connection is not built");
if (!getClientServerConnection().isConnected() && !getClientServerConnection().isTCPConnected()) {
throw new IllegalStateException("Server connection is not connected");
}
}
private boolean is100BServer() {
return getServerVersion().equals(ProtocolVersion.PV_1_0_0_BETA);
}
/**
* Begins a deterministic 1.0.0 request mapping.
*
* <p>Enforces single in-flight 1.0.0 request.</p>
*
* @param correlation correlation header to use for mapping 1.0.0 responses
*/
private void begin100BRequest(WebPacketHeader correlation) {
Objects.requireNonNull(correlation, "correlation");
v100BStreaming = false;
if (!v100BInFlight.compareAndSet(null, correlation)) {
throw new IllegalStateException(
"Web v1.0.0 mode supports only 1 in-flight request. " +
"Wait for response/stream end before sending the next request."
);
}
}
/**
* Releases 1.0.0 correlation state.
*/
private void end100BRequest() {
v100BStreaming = false;
v100BInFlight.set(null);
}
/**
* Heuristic: checks if a 1.0.0 response indicates streaming will follow.
*
* <p>Your v1.0.0 WebServer uses: header 'x-oac-stream' = '1' for the 202 response before streaming.</p>
*
* @param resp 1.0.0 response
* @return true if likely stream handshake
*/
private boolean looksLike100BStreamHandshake(WebResponsePacket resp) {
if (resp == null) return false;
Map<String, String> h = resp.getHeaders();
if (h == null) return false;
// Case-insensitive lookup without allocating
for (Map.Entry<String, String> e : h.entrySet()) {
if (e.getKey() != null && e.getValue() != null
&& e.getKey().equalsIgnoreCase("x-oac-stream")
&& e.getValue().equals("1")) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,25 @@
package org.openautonomousconnection.protocol.side.client.events;
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
import lombok.Getter;
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
import org.openautonomousconnection.protocol.side.client.ProtocolClient;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
/**
* Event triggered when a client successfully connects to a INS protocol server.
*/
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.INS)
public final class ConnectedToProtocolServerEvent extends Event {
/**
* Reference to the ProtocolClient object.
*/
@Getter
private final ProtocolClient client;
public ConnectedToProtocolServerEvent(ProtocolClient client) {
this.client = client;
}
}

View File

@@ -1,164 +1,71 @@
package org.openautonomousconnection.protocol.side.ins;
import dev.unlegitdqrk.unlegitlibrary.file.ConfigurationManager;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
import dev.unlegitdqrk.unlegitlibrary.file.FileUtils;
import dev.unlegitdqrk.unlegitlibrary.network.utils.NetworkUtils;
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
import lombok.Getter;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
import org.openautonomousconnection.protocol.side.server.ProtocolCustomServer;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecord;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecordType;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;
/**
* Abstract class representing a INS server in the protocol.
*/
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.INS)
public abstract class ProtocolINSServer extends DefaultMethodsOverrider {
/**
* The network server instance.
*/
@Getter
private final NetworkServer networkServer;
/**
* The configuration manager for handling server configurations.
*/
private final ConfigurationManager configurationManager;
/**
* The reference to the ProtocolBridge Object
*/
@Getter
private final ProtocolBridge protocolBridge;
/**
* List of connected protocol clients.
*/
@Getter
private List<ConnectedProtocolClient> clients;
/**
* The folder structure for server certificates.
*/
@Getter
private ServerCertificateFolderStructure folderStructure;
/**
* Constructs a ProtocolINSServer with the specified configuration file.
*
* @param configFile The configuration file for the INS server.
* @throws IOException If an I/O error occurs.
* @throws CertificateException If a certificate error occurs.
*/
public ProtocolINSServer(File configFile, ProtocolBridge protocolBridge) throws IOException, CertificateException {
this.protocolBridge = protocolBridge;
// Ensure the configuration file exists
if (!configFile.exists()) configFile.createNewFile();
// Load the configuration properties
configurationManager = new ConfigurationManager(configFile);
configurationManager.loadProperties();
// Set default values for configuration properties if not already set
if (!configurationManager.isSet("server.site.info")) {
configurationManager.set("server.site.info", "INS-SERVER INFO SITE IP:PORT");
configurationManager.saveProperties();
}
if (!configurationManager.isSet("server.site.frontend")) {
configurationManager.set("server.site.frontend", "SERVER IP TO INS-FRONTEND:PORT");
configurationManager.saveProperties();
}
// Initialize the folder structure
folderStructure = new ServerCertificateFolderStructure();
// Check for the existence of necessary certificate files
checkFileExists(folderStructure.publicCAFolder, folderStructure.caPrefix, ".pem");
checkFileExists(folderStructure.publicCAFolder, folderStructure.caPrefix, ".srl");
checkFileExists(folderStructure.privateCAFolder, folderStructure.caPrefix, ".key");
checkFileExists(folderStructure.publicServerFolder, folderStructure.certPrefix, ".crt");
checkFileExists(folderStructure.privateServerFolder, folderStructure.certPrefix, ".key");
// Define the certificate and key files based on the public IP address
File certFile = new File(folderStructure.publicServerFolder, folderStructure.certPrefix + NetworkUtils.getPublicIPAddress() + ".crt");
File keyFile = new File(folderStructure.privateServerFolder, folderStructure.certPrefix + NetworkUtils.getPublicIPAddress() + ".key");
// Initialize the protocol bridge and clients list
this.clients = new ArrayList<>();
// Build the network server with the specified settings
this.networkServer = new NetworkServer.ServerBuilder().
setLogger(protocolBridge.getLogger()).
setEventManager(protocolBridge.getProtocolSettings().eventManager).
setPacketHandler(protocolBridge.getProtocolSettings().packetHandler).
setPort(protocolBridge.getProtocolSettings().port).
setRequireClientCertificate(false).setRootCAFolder(folderStructure.publicCAFolder).setServerCertificate(certFile, keyFile).
build();
}
/**
* Checks if the required certificate files exist in the specified folder.
*
* @param folder The folder to check for certificate files.
* @param prefix The prefix of the certificate files.
* @param extension The extension of the certificate files.
* @throws CertificateException If a certificate error occurs.
* @throws IOException If an I/O error occurs.
*/
private final void checkFileExists(File folder, String prefix, String extension) throws CertificateException, IOException {
boolean found = false;
// Check if the folder exists
if (folder == null) throw new FileNotFoundException("Folder does not exist");
// List all files in the folder
File[] files = folder.listFiles();
// Check if the folder is empty
if (files == null || files.length == 0)
throw new FileNotFoundException("Folder " + folder.getAbsolutePath() + " is empty");
// Validate each file in the folder
for (File file : files) {
if (!file.getName().startsWith(prefix))
throw new CertificateException(file.getAbsolutePath() + " is not valid");
// Check if the file matches the expected naming convention
if (!found) found = file.getName().equalsIgnoreCase(prefix + NetworkUtils.getPublicIPAddress() + extension);
}
// If the required file is not found, throw an exception
if (!found) throw new CertificateException("Missing " + prefix + NetworkUtils.getPublicIPAddress() + extension);
}
/**
* Retrieves a connected protocol client by its client ID.
*
* @param clientID The ID of the client to retrieve.
* @return The ConnectedProtocolClient with the specified ID, or null if not found.
*/
public final ConnectedProtocolClient getClientByID(int clientID) {
for (ConnectedProtocolClient client : clients)
if (client.getConnectionHandler().getClientID() == clientID) return client;
return null;
}
public abstract class ProtocolINSServer extends ProtocolCustomServer {
/**
* Gets the INS information site URL from the configuration.
*
* @return The INS information site URL.
*/
public final String getINSInfoSite() {
return configurationManager.getString("server.site.info");
private final String insInfoSite;
/**
* Gets the INS registration site URL from the configuration.
*
* @return The INS registration site URL.
*/
private final String insFrontendSite;
/**
* Constructs a ProtocolINSServer with the specified configuration file.
*
* @param insInfoSite The INS-InfoSize (IP:PORT)
* @param insFrontendSite The INS-InfoSize (IP:PORT)
* @throws IOException If an I/O error occurs.
* @throws CertificateException If a certificate error occurs.
*/
public ProtocolINSServer(String insInfoSite, String insFrontendSite) throws Exception {
super("ins", "ins");
this.insInfoSite = insInfoSite;
this.insFrontendSite = insFrontendSite;
}
public final String getInsInfoSite() {
return insInfoSite;
}
public final String getInsFrontendSite() {
return insFrontendSite;
}
public void start(int tcpPort) throws IOException, InterruptedException, NoSuchAlgorithmException {
getNetwork().start(tcpPort, -1);
String caPrefix = getFolderStructure().getCaPrefix() + NetworkUtils.getPublicIPAddress();
File caPemFile = new File(getFolderStructure().publicCAFolder, caPrefix + ".pem");
byte[] caBytes = FileUtils.readFileFull(caPemFile).getBytes(java.nio.charset.StandardCharsets.UTF_8);
java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256");
String fp = java.util.HexFormat.of().formatHex(md.digest(caBytes));
getProtocolBridge().getProtocolValues().logger.info("CA Fingerprint: " + fp);
}
/**
@@ -188,7 +95,8 @@ public abstract class ProtocolINSServer extends DefaultMethodsOverrider {
* @param sub An optional subname, or {@code null}.
* @param type The record type requested.
*/
public void onQueryReceived(String tln, String name, String sub, INSRecordType type) {}
public void onQueryReceived(String tln, String name, String sub, INSRecordType type) {
}
/**
* Callback fired after an INS response was successfully sent to the client.
@@ -199,7 +107,8 @@ public abstract class ProtocolINSServer extends DefaultMethodsOverrider {
* @param type The requested record type.
* @param results The records returned to the client.
*/
public void onResponseSent(String tln, String name, String sub, INSRecordType type, List<INSRecord> results) {}
public void onResponseSent(String tln, String name, String sub, INSRecordType type, List<INSRecord> results) {
}
/**
* Callback fired when an INS response could not be delivered to the client.
@@ -211,7 +120,8 @@ public abstract class ProtocolINSServer extends DefaultMethodsOverrider {
* @param results The records that would have been sent.
* @param exception The exception describing the failure.
*/
public void onResponseSentFailed(String tln, String name, String sub, INSRecordType type, List<INSRecord> results, Exception exception) {}
public void onResponseSentFailed(String tln, String name, String sub, INSRecordType type, List<INSRecord> results, Exception exception) {
}
/**
* Resolves the information endpoint for a given Top-Level Name (TLN).
@@ -239,60 +149,8 @@ public abstract class ProtocolINSServer extends DefaultMethodsOverrider {
*
* @param tln the top-level name for which the info site should be resolved.
* Must not be null. Case-insensitive.
*
* @return a string in <code>"host:port"</code> format representing the TLN's info endpoint,
* or <code>null</code> if the TLN has no registered info site.
* or <code>null</code> if the TLN has no registered info site.
*/
public abstract String resolveTLNInfoSite(String tln);
/**
* Gets the INS registration site URL from the configuration.
*
* @return The INS registration site URL.
*/
public final String getINSFrontendSite() {
return configurationManager.getString("server.site.frontend");
}
/**
* Class representing the folder structure for server certificates.
*/
public final class ServerCertificateFolderStructure {
public final File certificatesFolder;
public final File publicFolder;
public final File privateFolder;
public final File privateCAFolder;
public final File privateServerFolder;
public final File publicCAFolder;
public final File publicServerFolder;
public final String caPrefix = "ca_ins_";
public final String certPrefix = "cert_ins_";
public ServerCertificateFolderStructure() {
certificatesFolder = new File("certificates");
publicFolder = new File(certificatesFolder, "public");
privateFolder = new File(certificatesFolder, "private");
privateCAFolder = new File(privateFolder, "ca");
privateServerFolder = new File(privateFolder, "server");
publicCAFolder = new File(publicFolder, "ca");
publicServerFolder = new File(publicFolder, "server");
if (!certificatesFolder.exists()) certificatesFolder.mkdirs();
if (!publicFolder.exists()) publicFolder.mkdirs();
if (!privateFolder.exists()) privateFolder.mkdirs();
if (!privateCAFolder.exists()) privateCAFolder.mkdirs();
if (!privateServerFolder.exists()) privateServerFolder.mkdirs();
if (!publicCAFolder.exists()) publicCAFolder.mkdirs();
if (!publicServerFolder.exists()) publicServerFolder.mkdirs();
}
}
}

View File

@@ -1,21 +0,0 @@
package org.openautonomousconnection.protocol.side.ins.events;
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
import lombok.Getter;
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
import org.openautonomousconnection.protocol.side.ins.ConnectedProtocolClient;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
/**
* Event triggered when a protocol client connects to the INS server.
*/
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.INS)
public final class ConnectedProtocolClientEvent extends Event {
@Getter
private final ConnectedProtocolClient protocolClient;
public ConnectedProtocolClientEvent(ConnectedProtocolClient protocolClient) {
this.protocolClient = protocolClient;
}
}

View File

@@ -1,44 +1,56 @@
package org.openautonomousconnection.protocol.side.ins;
package org.openautonomousconnection.protocol.side.server;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler;
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
import dev.unlegitdqrk.unlegitlibrary.event.EventPriority;
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectedClient;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.client.S_ClientDisconnectedEvent;
import lombok.Getter;
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
import org.openautonomousconnection.protocol.packets.OACPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
/**
* Represents a connected protocol client on the INS server side.
*/
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.INS)
public final class ConnectedProtocolClient {
public class CustomConnectedClient extends EventListener {
/**
* The connection handler associated with this protocol client.
*/
@Getter
private final ConnectionHandler connectionHandler;
private final ConnectedClient connection;
/**
* The Protocol Server associated with this protocol client.
*/
@Getter
private final ProtocolINSServer protocolINSServer;
private final ProtocolCustomServer server;
/**
* The protocol version of the connected client.
*/
private ProtocolVersion clientVersion = null;
public ConnectedProtocolClient(ConnectionHandler connectionHandler, ProtocolINSServer protocolINSServer) {
this.connectionHandler = connectionHandler;
this.protocolINSServer = protocolINSServer;
@Getter
private boolean clientVersionLoaded = false;
public CustomConnectedClient(ConnectedClient connection, ProtocolCustomServer protocolServer) throws Exception {
this.connection = connection;
this.server = protocolServer;
protocolServer.getProtocolBridge().getProtocolValues().eventManager.registerListener(this);
}
public synchronized void disconnect() {
try {
connection.disconnect();
} catch (Exception ignored) {
}
clientVersion = null;
}
@Listener(priority = EventPriority.LOWEST)
public final void onDisconnect(S_ClientDisconnectedEvent event) {
if (event.getClient().getUniqueID().equals(this.connection.getUniqueID())) {
server.getProtocolBridge().getProtocolValues().eventManager.unregisterListener(this);
clientVersion = null;
}
}
/**
* Gets the protocol version of the connected client.
* Defaults to PV_1_0_0_CLASSIC if not set.
*
* @return The protocol version of the client.
* @return The protocol version of the client, defaults to PV_1_0_0_CLASSIC if not set.
*/
public ProtocolVersion getClientVersion() {
return clientVersion == null ? ProtocolVersion.PV_1_0_0_CLASSIC : clientVersion;
@@ -50,7 +62,11 @@ public final class ConnectedProtocolClient {
* @param clientVersion The protocol version to set.
*/
public void setClientVersion(ProtocolVersion clientVersion) {
if (clientVersion == null) this.clientVersion = clientVersion;
if (clientVersionLoaded) return;
if (this.clientVersion != null) return;
this.clientVersion = clientVersion;
this.clientVersionLoaded = true;
}
/**
@@ -113,11 +129,9 @@ public final class ConnectedProtocolClient {
* @return True if the client is classic, false otherwise.
*/
public boolean isClassicClient() {
// Check if the server version is classic
return getClientVersion().getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
}
/**
* Checks if the connected client supports classic protocol versions.
*
@@ -136,30 +150,36 @@ public final class ConnectedProtocolClient {
}
/**
* Checks if the connected client supports the given packet's protocol version.
* Checks if the connected client supports the protocol version of the given packet.
*
* @param packet The packet to check.
* @param packet The packet to check support for.
* @return True if the client supports the packet's protocol version, false otherwise.
*/
public boolean supportClientPacket(OACPacket packet) {
return supportClientVersion(packet.getProtocolVersion());
boolean compatible = false;
for (ProtocolVersion compatibleVersion : packet.getCompatibleVersions()) {
if (!compatible) compatible = supportClientVersion(compatibleVersion);
}
return compatible;
}
/**
* Checks if the connected client supports the given protocol version.
*
* @param targetVersion The protocol version to check.
* @param targetVersion The protocol version to check support for.
* @return True if the client supports the target version, false otherwise.
*/
public boolean supportClientVersion(ProtocolVersion targetVersion) {
// Check if the client version matches or is compatible with the target version
// Check if the client version matches the target version or is compatible
return getClientVersion() == targetVersion || getClientVersion().getCompatibleVersions().contains(targetVersion);
}
/**
* Checks if the connected client supports the given protocol.
*
* @param protocol The protocol to check.
* @param protocol The protocol to check support for.
* @return True if the client supports the protocol, false otherwise.
*/
public boolean supportClientProtocol(ProtocolVersion.Protocol protocol) {
@@ -174,3 +194,4 @@ public final class ConnectedProtocolClient {
return getClientVersion().getSupportedProtocols().contains(protocol) || yes;
}
}

View File

@@ -0,0 +1,332 @@
package org.openautonomousconnection.protocol.side.server;
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
import dev.unlegitdqrk.unlegitlibrary.event.EventPriority;
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
import dev.unlegitdqrk.unlegitlibrary.file.FileUtils;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.client.S_ClientDisconnectedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.ServerStoppedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.ClientAuthMode;
import dev.unlegitdqrk.unlegitlibrary.network.utils.NetworkUtils;
import dev.unlegitdqrk.unlegitlibrary.network.utils.PemUtils;
import lombok.Getter;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.side.server.events.S_CustomClientDisconnectedEvent;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.*;
public abstract class ProtocolCustomServer extends EventListener {
/**
* Structure for server.
*/
private final ServerCertificateFolderStructure folderStructure;
/**
* Certificate files for SSL.
*/
private final File certFile;
/**
* Certificate files for SSL.
*/
private final File keyFile;
/**
* List of connected web clients.
*/
private final List<CustomConnectedClient> clients;
/**
* The reference to the ProtocolBridge Object
*/
private ProtocolBridge protocolBridge;
/**
* The network server handling pipeline connections.
*/
private NetworkServer network;
/**
* Initializes the web server with the given configuration, authentication, and rules files.
*
* @throws Exception If an error occurs during initialization.
*/
public ProtocolCustomServer(String caPrefix, String certPrefix) throws Exception {
// Initialize the list of connected clients
this.clients = new ArrayList<>();
// Set up folder structure for certificates
folderStructure = new ServerCertificateFolderStructure(caPrefix, certPrefix);
// Check for necessary certificate files
checkFileExists(folderStructure.publicServerFolder, folderStructure.certPrefix, ".crt");
checkFileExists(folderStructure.privateServerFolder, folderStructure.certPrefix, ".key");
// Set up certificate files based on public IP address
this.certFile = new File(folderStructure.publicServerFolder, folderStructure.certPrefix + NetworkUtils.getPublicIPAddress() + ".crt");
this.keyFile = new File(folderStructure.privateServerFolder, folderStructure.certPrefix + NetworkUtils.getPublicIPAddress() + ".key");
}
public final ServerCertificateFolderStructure getFolderStructure() {
return folderStructure;
}
public final ProtocolBridge getProtocolBridge() {
return protocolBridge;
}
public final List<CustomConnectedClient> getClients() {
return clients;
}
public final NetworkServer getNetwork() {
return network;
}
protected final void setNetwork(NetworkServer network) {
this.network = network;
}
/**
* Injects the ProtocolBridge.
*
* @param bridge the Bridge instance.
* @throws IOException when NetworkServer failed to create.
*/
public final void attachBridge(ProtocolBridge bridge, String keyPassword, boolean ssl, ClientAuthMode authMode) throws Exception {
if (this.protocolBridge != null)
throw new IllegalStateException("ProtocolBridge already attached!");
this.protocolBridge = bridge;
bridge.getProtocolValues().eventManager.registerListener(this);
if (ssl) {
char[] keyPass = new char[0];
if (keyPassword != null) keyPass = keyPassword.toCharArray();
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
int caIndex = 0;
for (File caPem : FileUtils.listFiles(folderStructure.publicCAFolder, ".pem", ".crt", ".cer")) {
if (caPem.getName().endsWith(".srl")) continue;
if (!caPem.getName().startsWith(folderStructure.getCaPrefix())) continue;
X509Certificate caCert = PemUtils.loadCertificate(caPem);
trustStore.setCertificateEntry("root-ca-" + (caIndex++), caCert);
}
// Build server key material (private key + certificate chain)
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
Map<String, File> keyFiles = new HashMap<>();
Map<String, File> certFiles = new HashMap<>();
for (File f : FileUtils.listFiles(folderStructure.privateServerFolder, ".key")) {
if (f.getName().endsWith(".srl")) continue;
if (!f.getName().startsWith(folderStructure.getCertPrefix())) continue;
keyFiles.put(FileUtils.stripExt(f.getName()), f);
}
// Allow cert files in public server folder as .pem/.crt/.cer
for (File f : FileUtils.listFiles(folderStructure.publicServerFolder, ".pem", ".crt", ".cer")) {
if (f.getName().endsWith(".srl")) continue;
if (!f.getName().startsWith(folderStructure.getCertPrefix())) continue;
certFiles.put(FileUtils.stripExt(f.getName()), f);
}
// Load all CA certs once (for chain building)
List<X509Certificate> caCerts = new ArrayList<>();
for (File caPem : FileUtils.listFiles(folderStructure.publicCAFolder, ".pem", ".crt", ".cer")) {
if (caPem.getName().endsWith(".srl")) continue;
if (!caPem.getName().startsWith(folderStructure.getCaPrefix())) continue;
caCerts.add(PemUtils.loadCertificate(caPem));
}
int serverIndex = 0;
for (Map.Entry<String, File> e : keyFiles.entrySet()) {
String base = e.getKey();
File keyFile = e.getValue();
File certFile = certFiles.get(base);
if (certFile == null) {
// If your naming differs between private key and public cert, log it and skip
continue;
}
PrivateKey privateKey = PemUtils.loadPrivateKey(keyFile);
X509Certificate leaf = PemUtils.loadCertificate(certFile);
// Build a minimal chain: leaf + issuer CA if found (or all CAs as fallback)
List<X509Certificate> chain = new ArrayList<>();
chain.add(leaf);
// Try to find matching issuer CA(s) by Subject/Issuer DN
X509Certificate current = leaf;
for (int depth = 0; depth < 5; depth++) {
X509Certificate issuer = findIssuer(current, caCerts);
if (issuer == null) break;
chain.add(issuer);
if (issuer.getSubjectX500Principal().equals(issuer.getIssuerX500Principal())) {
// self-signed root reached
break;
}
current = issuer;
}
String alias = "server-" + (serverIndex++);
keyStore.setKeyEntry(alias, privateKey, keyPass, chain.toArray(new X509Certificate[0]));
}
// If still empty, fail fast with a clear error instead of handshake_failure later
if (!keyStore.aliases().hasMoreElements()) {
throw new IllegalStateException(
"No server key entries loaded. Ensure private .key and public .crt/.pem names match and start with prefix "
+ folderStructure.getCertPrefix()
);
}
network = new NetworkServer.Builder()
.packetHandler(bridge.getProtocolValues().packetHandler)
.eventManager(bridge.getProtocolValues().eventManager)
.sslEnabled(true)
.clientAuthMode(authMode)
.keyStore(keyStore, keyPass)
.trustStore(trustStore)
.build();
} else {
network = new NetworkServer.Builder()
.packetHandler(bridge.getProtocolValues().packetHandler)
.eventManager(bridge.getProtocolValues().eventManager)
.sslEnabled(false)
.clientAuthMode(ClientAuthMode.NONE)
.build();
}
}
@Listener(priority = EventPriority.HIGHEST)
public final void onStop(ServerStoppedEvent event) {
if (event.getServer() == network) {
protocolBridge.getProtocolValues().eventManager.unregisterListener(this);
}
}
/**
* Retrieves a connected web client by its client ID.
*
* @param clientID The client ID to search for.
* @return The connected web client with the specified ID, or null if not found.
*/
public final CustomConnectedClient getClientByID(UUID clientID) {
for (CustomConnectedClient client : clients)
if (client.getConnection().getUniqueID().equals(clientID)) return client;
return null;
}
/**
* Attempts to find the issuer certificate of {@code cert} within {@code candidates}.
*
* @param cert the certificate whose issuer should be found
* @param candidates possible issuer certificates
* @return issuer certificate if found, otherwise null
*/
private X509Certificate findIssuer(X509Certificate cert, List<X509Certificate> candidates) {
for (X509Certificate ca : candidates) {
if (cert.getIssuerX500Principal().equals(ca.getSubjectX500Principal())) {
return ca;
}
}
return null;
}
/**
* Checks if the required certificate files exist in the specified folder.
*
* @param folder The folder to check for certificate files.
* @param prefix The prefix of the certificate files.
* @param extension The extension of the certificate files.
* @throws CertificateException If a required certificate file is missing or invalid.
* @throws IOException If an I/O error occurs while checking the files.
*/
private void checkFileExists(File folder, String prefix, String extension) throws CertificateException, IOException {
// Ensure the folder exists
if (folder == null) throw new FileNotFoundException("Folder does not exist");
// List all files in the folder
File[] files = folder.listFiles();
if (files == null || files.length == 0)
throw new FileNotFoundException("Folder " + folder.getAbsolutePath() + " is empty");
// Check for the required certificate file
for (File file : files) {
if (!file.getName().startsWith(prefix))
throw new CertificateException(file.getAbsolutePath() + " is not valid");
}
}
@Listener(priority = EventPriority.HIGHEST)
public final void clientDisconnected(S_ClientDisconnectedEvent event) {
for (CustomConnectedClient client : new ArrayList<>(clients)) {
if (client.getConnection().getUniqueID().equals(event.getClient().getUniqueID())) {
protocolBridge.getProtocolValues().eventManager.executeEvent(new S_CustomClientDisconnectedEvent(client));
clients.remove(client);
}
}
}
/**
* Represents the folder structure for server certificates.
*/
public final class ServerCertificateFolderStructure {
public final File certificatesFolder;
public final File publicFolder;
public final File privateFolder;
public final File privateCAFolder;
public final File privateServerFolder;
public final File publicCAFolder;
public final File publicServerFolder;
@Getter
private String caPrefix = "ca_server_";
@Getter
private String certPrefix = "cert_server_";
public ServerCertificateFolderStructure(String caPrefix, String certPrefix) {
this.caPrefix = "ca_" + caPrefix + "_";
this.certPrefix = "cert_" + certPrefix + "_";
certificatesFolder = new File("certificates");
publicFolder = new File(certificatesFolder, "public");
privateFolder = new File(certificatesFolder, "private");
privateCAFolder = new File(privateFolder, "ca");
privateServerFolder = new File(privateFolder, "server");
publicCAFolder = new File(publicFolder, "ca");
publicServerFolder = new File(publicFolder, "server");
if (!certificatesFolder.exists()) certificatesFolder.mkdirs();
if (!publicFolder.exists()) publicFolder.mkdirs();
if (!privateFolder.exists()) privateFolder.mkdirs();
if (!privateCAFolder.exists()) privateCAFolder.mkdirs();
if (!privateServerFolder.exists()) privateServerFolder.mkdirs();
if (!publicCAFolder.exists()) publicCAFolder.mkdirs();
if (!publicServerFolder.exists()) publicServerFolder.mkdirs();
}
}
}

View File

@@ -0,0 +1,21 @@
package org.openautonomousconnection.protocol.side.server.events;
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
import lombok.Getter;
import org.openautonomousconnection.protocol.side.server.CustomConnectedClient;
/**
* Event triggered when a web client connects to the web server.
*/
public final class S_CustomClientConnectedEvent extends Event {
/**
* The connected web client.
*/
@Getter
private final CustomConnectedClient client;
public S_CustomClientConnectedEvent(CustomConnectedClient client) {
this.client = client;
}
}

View File

@@ -0,0 +1,21 @@
package org.openautonomousconnection.protocol.side.server.events;
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
import lombok.Getter;
import org.openautonomousconnection.protocol.side.server.CustomConnectedClient;
/**
* Event triggered when a web client connects to the web server.
*/
public final class S_CustomClientDisconnectedEvent extends Event {
/**
* The connected web client.
*/
@Getter
private final CustomConnectedClient client;
public S_CustomClientDisconnectedEvent(CustomConnectedClient client) {
this.client = client;
}
}

View File

@@ -1,734 +0,0 @@
package org.openautonomousconnection.protocol.side.web;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler;
import lombok.Getter;
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
import org.openautonomousconnection.protocol.packets.OACPacket;
import org.openautonomousconnection.protocol.side.web.managers.AuthManager;
import org.openautonomousconnection.protocol.side.web.managers.RuleManager;
import org.openautonomousconnection.protocol.side.web.managers.SessionManager;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import javax.net.ssl.SSLSocket;
import java.io.*;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
/**
* Represents a connected web client.
* Manages the connection, handles HTTP requests, and serves files.
*/
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
public final class ConnectedWebClient {
/**
* The connection handler associated with this web client.
*/
@Getter
private final ConnectionHandler pipelineConnection;
/**
* The SSL socket for the web client connection.
*/
@Getter
private SSLSocket webSocket;
/**
* The output stream for sending data to the client.
*/
private ObjectOutputStream outputStream;
/**
* The input stream for receiving data from the client.
*/
private ObjectInputStream inputStream;
/**
* The protocol version of the connected client.
*/
private ProtocolVersion clientVersion = null;
/**
* Indicates if the client version has been loaded.
*/
@Getter
private boolean clientVersionLoaded = false;
/**
* The reference to the ProtocolWebServer Object
*/
@Getter
private ProtocolWebServer protocolWebServer;
/**
* Constructs a ConnectedWebClient with the given connection handler.
*
* @param pipelineConnection The connection handler for the web client.
*/
public ConnectedWebClient(ConnectionHandler pipelineConnection) {
this.pipelineConnection = pipelineConnection;
}
/**
* Sends an HTTP redirect response to the client.
*
* @param out The output stream to send the response to.
* @param location The URL to redirect to.
* @param cookies Optional cookies to set in the response.
* @throws IOException If an I/O error occurs.
*/
private static void sendRedirect(OutputStream out, String location, Map<String, String> cookies) throws IOException {
// Send HTTP 302 Found response with Location header
out.write(("OAC 302 Found\r\n").getBytes());
out.write(("Location: " + location + "\r\n").getBytes());
// Set cookies if provided
if (cookies != null) {
for (var entry : cookies.entrySet()) {
out.write((entry.getKey() + ": " + entry.getValue() + "\r\n").getBytes());
}
}
// End of headers
out.write("\r\n".getBytes());
out.flush();
} /**
* Thread for receiving data from the client.
*/
private final Thread receiveThread = new Thread(this::receive);
/**
* Parses POST parameters from the input stream.
*
* @param in The input stream to read from.
* @return A map of POST parameter names to values.
* @throws IOException If an I/O error occurs.
*/
private static Map<String, String> parsePostParams(InputStream in) throws IOException {
// Read the entire input stream into a string
BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
while (reader.ready()) {
sb.append((char) reader.read());
}
// Split the string into key-value pairs and decode them
Map<String, String> map = new HashMap<>();
String[] pairs = sb.toString().split("&");
for (String p : pairs) {
// Split each pair into key and value
String[] kv = p.split("=", 2);
if (kv.length == 2)
// Decode and store in the map
map.put(URLDecoder.decode(kv[0], StandardCharsets.UTF_8), URLDecoder.decode(kv[1], StandardCharsets.UTF_8));
}
return map;
}
/**
* Normalizes a file path to prevent directory traversal attacks.
*
* @param path The raw file path.
* @return The normalized file path.
*/
private static String normalizePath(String path) {
// Replace backslashes with forward slashes and remove ".." segments
path = path.replace("/", File.separator).replace("\\", "/");
// Remove any ".." segments to prevent directory traversal
while (path.contains("..")) path = path.replace("..", "");
// Remove leading slashes
if (path.startsWith("/")) path = path.substring(1);
return path;
}
/**
* Parses query parameters from a raw URL path.
*
* @param rawPath The raw URL path containing query parameters.
* @return A map of query parameter names to values.
*/
private static Map<String, String> parseQueryParams(String rawPath) {
// Extract query parameters from the URL path
Map<String, String> map = new HashMap<>();
if (rawPath.contains("?")) {
// Split the query string into key-value pairs
String[] params = rawPath.substring(rawPath.indexOf("?") + 1).split("&");
for (String p : params) {
// Split each pair into key and value
String[] kv = p.split("=");
if (kv.length == 2) map.put(kv[0], kv[1]);
}
}
return map;
}
/**
* Checks if the request is a multipart/form-data request.
*
* @param headers The HTTP headers of the request.
* @return True if the request is multipart/form-data, false otherwise.
*/
private static boolean isMultipart(Map<String, String> headers) {
String contentType = headers.get("content-type");
return contentType != null && contentType.startsWith("multipart/form-data");
}
/**
* Handles a multipart/form-data request, saving uploaded files to the specified directory.
*
* @param in The input stream to read the request body from.
* @param headers The HTTP headers of the request.
* @param uploadDir The directory to save uploaded files to.
* @throws IOException If an I/O error occurs.
*/
private static void handleMultipart(InputStream in, Map<String, String> headers, File uploadDir) throws IOException {
// Ensure the upload directory exists
if (!uploadDir.exists()) uploadDir.mkdirs();
// Extract the boundary from the Content-Type header
String contentType = headers.get("content-type");
String boundary = "--" + contentType.split("boundary=")[1];
// Read the entire request body into a buffer
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] lineBuffer = new byte[8192];
int read;
while ((read = in.read(lineBuffer)) != -1) {
buffer.write(lineBuffer, 0, read);
if (buffer.size() > 10 * 1024 * 1024) break; // 10 MB max
}
// Parse the multipart data
String data = buffer.toString(StandardCharsets.UTF_8);
String[] parts = data.split(boundary);
// Process each part
for (String part : parts) {
if (part.contains("Content-Disposition")) {
String name = null;
String filename = null;
// Extract headers from the part
for (String headerLine : part.split("\r\n")) {
if (headerLine.startsWith("Content-Disposition")) {
if (headerLine.contains("filename=\"")) {
int start = headerLine.indexOf("filename=\"") + 10;
int end = headerLine.indexOf("\"", start);
filename = headerLine.substring(start, end);
}
if (headerLine.contains("name=\"")) {
int start = headerLine.indexOf("name=\"") + 6;
int end = headerLine.indexOf("\"", start);
name = headerLine.substring(start, end);
}
}
}
// Save the file if a filename is provided
if (filename != null && !filename.isEmpty()) {
int headerEnd = part.indexOf("\r\n\r\n");
byte[] fileData = part.substring(headerEnd + 4).getBytes(StandardCharsets.UTF_8);
File outFile = new File(uploadDir, filename);
Files.write(outFile.toPath(), fileData);
}
}
}
}
/**
* Sends an response to the client.
*
* @param out
* @param code
* @param file
* @param headers
* @throws IOException
*/
private static void sendResponse(OutputStream out, int code, File file, Map<String, String> headers) throws IOException {
byte[] body = Files.readAllBytes(file.toPath());
sendResponse(out, code, body, "text/html", headers);
}
/**
* Sends an response to the client.
*
* @param out The output stream to send the response to.
* @param code The HTTP status code.
* @param file The file to read the response body from.
* @throws IOException If an I/O error occurs.
*/
private static void sendResponse(OutputStream out, int code, File file) throws IOException {
sendResponse(out, code, Files.readString(file.toPath()), "text/html");
}
/**
* Sends an response to the client.
*
* @param out The output stream to send the response to.
* @param code The HTTP status code.
* @param body The response body as a string.
* @param contentType The content type of the response.
* @throws IOException If an I/O error occurs.
*/
private static void sendResponse(OutputStream out, int code, String body, String contentType) throws IOException {
sendResponse(out, code, body.getBytes(StandardCharsets.UTF_8), contentType, null);
}
/**
* Sends an response to the client.
*
* @param out The output stream to send the response to.
* @param code The HTTP status code.
* @param file The file to read the response body from.
* @param contentType The content type of the response.
* @throws IOException If an I/O error occurs.
*/
private static void sendResponse(OutputStream out, int code, File file, String contentType) throws IOException {
byte[] bytes = Files.readAllBytes(file.toPath());
sendResponse(out, code, bytes, contentType, null);
}
/**
* Sends an response to the client.
*
* @param out The output stream to send the response to.
* @param code The HTTP status code.
* @param body The response body as a byte array.
* @param contentType The content type of the response.
* @param headers Additional headers to include in the response.
* @throws IOException If an I/O error occurs.
*/
private static void sendResponse(OutputStream out, int code, byte[] body, String contentType, Map<String, String> headers) throws IOException {
// Send response status line and headers
out.write(("OAC " + code + " " + getStatusText(code) + "\r\n").getBytes());
out.write(("Content-Type: " + contentType + "\r\n").getBytes());
out.write(("Content-Length: " + body.length + "\r\n").getBytes());
// Write additional headers if provided
if (headers != null) headers.forEach((k, v) -> {
try {
out.write((k + ": " + v + "\r\n").getBytes());
} catch (IOException ignored) {
}
});
// End of headers
out.write("\r\n".getBytes());
out.write(body);
out.flush();
}
/**
* Returns the standard status text for a given status code.
*
* @param code The status code.
* @return The corresponding status text.
*/
private static String getStatusText(int code) {
return switch (code) {
case 200 -> "OK";
case 301 -> "Moved Permanently";
case 302 -> "Found";
case 400 -> "Bad Request";
case 401 -> "Unauthorized";
case 403 -> "Forbidden";
case 404 -> "Not Found";
case 500 -> "Internal Server Error";
default -> "Unknown";
};
}
/**
* Returns the content type based on the file extension.
*
* @param name The file name.
* @return The corresponding content type.
*/
private static String getContentType(String name) {
return switch (name.substring(name.lastIndexOf('.') + 1).toLowerCase()) {
case "html", "php" -> "text/html";
case "js" -> "text/javascript";
case "css" -> "text/css";
case "json" -> "application/json";
case "png" -> "image/png";
case "jpg", "jpeg" -> "image/jpeg";
case "mp4" -> "video/mp4";
case "mp3" -> "audio/mpeg3";
case "wav" -> "audio/wav";
case "pdf" -> "application/pdf";
default -> "text/plain";
};
}
/**
* Renders a PHP file by executing it with the PHP interpreter and captures cookies.
*
* @param file The PHP file to render.
* @return A PHPResponse containing the output and cookies.
* @throws IOException If an I/O error occurs.
* @throws InterruptedException If the process is interrupted.
*/
private static PHPResponse renderPHPWithCookies(File file) throws IOException, InterruptedException {
// Execute the PHP file using the PHP interpreter
ProcessBuilder pb = new ProcessBuilder("php", file.getAbsolutePath());
pb.redirectErrorStream(true);
Process p = pb.start();
// Capture the output of the PHP process
ByteArrayOutputStream output = new ByteArrayOutputStream();
InputStream processIn = p.getInputStream();
byte[] buf = new byte[8192];
int read;
while ((read = processIn.read(buf)) != -1) {
output.write(buf, 0, read);
}
p.waitFor();
// Parse the output to separate headers and body, and extract cookies
String fullOutput = output.toString(StandardCharsets.UTF_8);
Map<String, String> cookies = new HashMap<>();
// Split headers and body
String[] parts = fullOutput.split("\r\n\r\n", 2);
String body;
if (parts.length == 2) {
// Get headers and body
String headers = parts[0];
body = parts[1];
// Extract cookies from headers
for (String headerLine : headers.split("\r\n")) {
if (headerLine.toLowerCase().startsWith("set-cookie:")) {
String cookie = headerLine.substring("set-cookie:".length()).trim();
String[] kv = cookie.split(";", 2);
String[] pair = kv[0].split("=", 2);
if (pair.length == 2) cookies.put(pair[0], pair[1]);
}
}
} else {
// No headers, only body
body = fullOutput;
}
return new PHPResponse(body, cookies);
}
/**
* Sets the SSL socket for the web client and starts the receive thread.
*
* @param webSocket The SSL socket to set.
*/
public void setWebSocket(SSLSocket webSocket) {
if (webSocket != null) this.webSocket = webSocket;
this.receiveThread.start();
}
/**
* Checks if the web client is currently connected.
*
* @return True if connected, false otherwise.
*/
public boolean isConnected() {
return this.webSocket != null && this.webSocket.isConnected() && !this.webSocket.isClosed() && this.receiveThread.isAlive() && pipelineConnection.isConnected();
}
/**
* Disconnects the web client, closing streams and the socket.
*
* @return True if disconnection was successful, false if already disconnected.
*/
public synchronized boolean disconnect() {
if (!this.isConnected()) {
return false;
} else {
// Disconnect the underlying connection handler
pipelineConnection.disconnect();
// Interrupt the receive thread if it's still alive
if (this.receiveThread.isAlive()) {
this.receiveThread.interrupt();
}
try {
// Close streams and the socket
this.outputStream.close();
this.inputStream.close();
this.webSocket.close();
} catch (IOException var2) {
}
// Nullify references
this.webSocket = null;
this.outputStream = null;
this.inputStream = null;
return true;
}
}
/**
* Gets the protocol version of the connected client.
*
* @return The protocol version of the client, defaults to PV_1_0_0_CLASSIC if not set.
*/
public ProtocolVersion getClientVersion() {
return clientVersion == null ? ProtocolVersion.PV_1_0_0_CLASSIC : clientVersion;
}
/**
* Sets the protocol version of the connected client.
*
* @param clientVersion The protocol version to set.
*/
public void setClientVersion(ProtocolVersion clientVersion) {
if (!clientVersionLoaded) clientVersionLoaded = true;
if (clientVersion == null) this.clientVersion = clientVersion;
}
/**
* Checks if the connected client is a stable client.
*
* @return True if the client is stable, false otherwise.
*/
public boolean isStableClient() {
// Check if the server version is stable
return !isBetaClient() && !isClassicClient();
}
/**
* Checks if the connected client supports stable protocol versions.
*
* @return True if the client supports stable versions, false otherwise.
*/
public boolean supportClientStable() {
boolean yes = false;
for (ProtocolVersion compatibleVersion : getClientVersion().getCompatibleVersions()) {
// Check if compatible version is stable
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.STABLE;
if (yes) break;
}
// Check if the client version is stable
return isStableClient() || yes;
}
/**
* Checks if the connected client is a beta client.
*
* @return True if the client is beta, false otherwise.
*/
public boolean isBetaClient() {
// Check if the server version is beta
return getClientVersion().getProtocolType() == ProtocolVersion.ProtocolType.BETA;
}
/**
* Checks if the connected client supports beta protocol versions.
*
* @return True if the client supports beta versions, false otherwise.
*/
public boolean supportClientBeta() {
boolean yes = false;
for (ProtocolVersion compatibleVersion : getClientVersion().getCompatibleVersions()) {
// Check if compatible version is beta
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.BETA;
if (yes) break;
}
// Check if the client version is beta
return isBetaClient() || yes;
}
/**
* Checks if the connected client is a classic client.
*
* @return True if the client is classic, false otherwise.
*/
public boolean isClassicClient() {
return getClientVersion().getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
}
/**
* Checks if the connected client supports classic protocol versions.
*
* @return True if the client supports classic versions, false otherwise.
*/
public boolean supportClientClassic() {
boolean yes = false;
for (ProtocolVersion compatibleVersion : getClientVersion().getCompatibleVersions()) {
// Check if compatible version is classic
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
if (yes) break;
}
// Check if the client version is classic
return isClassicClient() || yes;
}
/**
* Checks if the connected client supports the protocol version of the given packet.
*
* @param packet The packet to check support for.
* @return True if the client supports the packet's protocol version, false otherwise.
*/
public boolean supportClientPacket(OACPacket packet) {
return supportClientVersion(packet.getProtocolVersion());
}
/**
* Checks if the connected client supports the given protocol version.
*
* @param targetVersion The protocol version to check support for.
* @return True if the client supports the target version, false otherwise.
*/
public boolean supportClientVersion(ProtocolVersion targetVersion) {
// Check if the client version matches the target version or is compatible
return getClientVersion() == targetVersion || getClientVersion().getCompatibleVersions().contains(targetVersion);
}
/**
* Checks if the connected client supports the given protocol.
*
* @param protocol The protocol to check support for.
* @return True if the client supports the protocol, false otherwise.
*/
public boolean supportClientProtocol(ProtocolVersion.Protocol protocol) {
boolean yes = false;
for (ProtocolVersion compatibleVersion : getClientVersion().getCompatibleVersions()) {
// Check if compatible version supports the protocol
yes = compatibleVersion.getSupportedProtocols().contains(protocol);
if (yes) break;
}
// Check if the client version supports the protocol
return getClientVersion().getSupportedProtocols().contains(protocol) || yes;
}
/**
* Receives and processes requests from the client.
* Handles authentication, file serving, and PHP rendering.
*/
private void receive() {
try {
while (this.isConnected()) {
Object received = this.inputStream.readObject();
try (InputStream in = webSocket.getInputStream(); OutputStream out = webSocket.getOutputStream()) {
BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
String line;
String path = "/main.html";
Map<String, String> headers = new HashMap<>();
while ((line = reader.readLine()) != null && !line.isEmpty()) {
if (line.toLowerCase().startsWith("get") || line.toLowerCase().startsWith("post")) {
path = line.split(" ")[1];
}
if (line.contains(":")) {
String[] parts = line.split(":", 2);
headers.put(parts[0].trim().toLowerCase(), parts[1].trim());
}
}
path = URLDecoder.decode(path, StandardCharsets.UTF_8);
path = normalizePath(path);
File file = new File(protocolWebServer.getProtocolBridge().getProtocolWebServer().getContentFolder(), path);
String sessionId = null;
if (headers.containsKey("cookie")) {
for (String cookie : headers.get("cookie").split(";")) {
cookie = cookie.trim();
if (cookie.startsWith("SESSIONID=")) {
sessionId = cookie.substring("SESSIONID=".length());
}
}
}
if (!file.exists() || !file.isFile()) {
sendResponse(out, 404, new File(protocolWebServer.getProtocolBridge().getProtocolWebServer().getErrorsFolder(), "404.html"));
return;
}
String clientIp = webSocket.getInetAddress().getHostAddress();
String userAgent = headers.getOrDefault("user-agent", null);
boolean loggedIn = sessionId != null && SessionManager.isValid(sessionId, clientIp, userAgent, protocolWebServer);
if (path.equals("/403-login") && headers.getOrDefault("content-type", "").startsWith("application/x-www-form-urlencoded")) {
Map<String, String> postParams = parsePostParams(in);
String login = postParams.get("login");
String password = postParams.get("password");
if (AuthManager.checkAuth(login, password)) {
String newSessionId = SessionManager.create(login, clientIp, userAgent, protocolWebServer);
Map<String, String> cookies = Map.of("Set-Cookie", "SESSIONID=" + newSessionId + "; HttpOnly; Path=/");
sendRedirect(out, "/main.html", cookies);
return;
} else {
sendRedirect(out, "/403.php", null);
return;
}
}
if (isMultipart(headers)) {
handleMultipart(in, headers, new File(protocolWebServer.getProtocolBridge().getProtocolWebServer().getContentFolder(), "uploads"));
}
if (RuleManager.requiresAuth(path) && !loggedIn) {
PHPResponse phpResp = renderPHPWithCookies(new File(protocolWebServer.getProtocolBridge().getProtocolWebServer().getContentFolder(), "403.php"));
sendResponse(out, 200, phpResp.body.getBytes(StandardCharsets.UTF_8), "text/html", phpResp.cookies);
return;
}
if (RuleManager.isDenied(path) && !RuleManager.isAllowed(path)) {
sendResponse(out, 403, new File(protocolWebServer.getProtocolBridge().getProtocolWebServer().getErrorsFolder(), "403.php"));
return;
}
if (path.endsWith(".php")) {
PHPResponse phpResp = renderPHPWithCookies(file);
sendResponse(out, 200, phpResp.body.getBytes(StandardCharsets.UTF_8), "text/html", phpResp.cookies);
} else {
sendResponse(out, 200, Files.readAllBytes(file.toPath()), getContentType(path), null);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
disconnect();
}
}
} catch (Exception var2) {
this.disconnect();
}
}
/**
* Set protocol bridge
*
* @param protocolWebServer The ProtocolWebServer object
*/
public void setProtocolWebServer(ProtocolWebServer protocolWebServer) {
if (this.protocolWebServer == null) this.protocolWebServer = protocolWebServer;
}
/**
* Represents the response from a PHP script, including body and cookies.
*/
private static class PHPResponse {
String body;
Map<String, String> cookies;
public PHPResponse(String body, Map<String, String> cookies) {
this.body = body;
this.cookies = cookies;
}
}
}

View File

@@ -1,377 +1,256 @@
package org.openautonomousconnection.protocol.side.web;
import dev.unlegitdqrk.unlegitlibrary.file.ConfigurationManager;
import dev.unlegitdqrk.unlegitlibrary.file.FileUtils;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
import dev.unlegitdqrk.unlegitlibrary.network.utils.NetworkUtils;
import dev.unlegitdqrk.unlegitlibrary.string.RandomString;
import lombok.Getter;
import org.openautonomousconnection.protocol.ProtocolBridge;
import dev.unlegitdqrk.unlegitlibrary.event.EventPriority;
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.C_PacketReadEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
import org.openautonomousconnection.protocol.side.web.managers.AuthManager;
import org.openautonomousconnection.protocol.side.web.managers.RuleManager;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentApplyRequestPacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.document.WebDocumentApplyResponsePacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate.WebNavigateAckPacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate.WebNavigateRequestPacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceRequestPacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamChunkPacket_v1_0_1_B;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamEndPacket_v1_0_1_B;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamStartPacket_v1_0_1_B;
import org.openautonomousconnection.protocol.side.server.CustomConnectedClient;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.ProtocolWebServer_1_0_0_B;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketFlags;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebCompatMapper;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLSocket;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
* Represents the web server for the protocol.
* Protocol Web Server base for Web v1.0.1 (beta) with built-in v1.0.0 compatibility.
*
* <p>This class keeps the v1.0.0 entry point ({@link #onWebRequest(CustomConnectedClient, WebRequestPacket)})
* and adapts it to the v1.0.1 resource pipeline by mapping v1.0.0 requests to a synthetic
* {@link WebResourceRequestPacket} and mapping v1.0.1 responses back to {@link WebResponsePacket}.</p>
*
* <p>Important:
* <ul>
* <li>This class does NOT perform network packet dispatch automatically. Your server packet-receive listener
* must call the appropriate {@code handle*} methods (v1.0.1) or let the v1.0.0 pipeline call {@code onWebRequest}.</li>
* <li>Responses/streams must preserve correlation via {@link WebPacketHeader#getRequestId()} for v1.0.1 clients.</li>
* <li>v1.0.0 streaming packets cannot carry request correlation. If you stream, you must
* send 1.0.0 stream packets to 1.0.0 clients and v1.0.1 stream packets to v1.0.1 clients.</li>
* </ul>
* </p>
*/
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
public final class ProtocolWebServer {
/**
* Folder for web content.
*/
@Getter
private final File contentFolder;
public abstract class ProtocolWebServer extends ProtocolWebServer_1_0_0_B {
/**
* Folder for error pages.
*/
@Getter
private final File errorsFolder;
/**
* Structure for server.
*/
@Getter
private final ServerCertificateFolderStructure folderStructure;
/**
* Configuration manager for server settings.
*/
private final ConfigurationManager configurationManager;
/**
* Certificate files for SSL.
*/
private final File certFile;
/**
* Certificate files for SSL.
*/
private final File keyFile;
/**
* The configuration file for the web server.
*/
private final File configFile;
/**
* The reference to the ProtocolBridge Object
*/
@Getter
private final ProtocolBridge protocolBridge;
/**
* The network server handling pipeline connections.
*/
@Getter
private NetworkServer pipelineServer;
/**
* The SSL server socket for web connections.
*/
@Getter
private SSLServerSocket webServer;
/**
* List of connected web clients.
*/
@Getter
private List<ConnectedWebClient> clients;
/**
* A unique secret for session management.
*/
@Getter
private String uniqueSessionString;
private static final long LEGACY_TAB_ID = 1L;
private static final long LEGACY_PAGE_ID = 1L;
/**
* Initializes the web server with the given configuration, authentication, and rules files.
*
* @param configFile The configuration file.
* @param authFile The authentication file.
* @param rulesFile The rules file.
* @param authFile The authentication file.
* @param rulesFile The rules file.
* @param sessionExpire The expiration time of a Session in minutes.
* @param uploadSize The max upload size in MB.
* @throws Exception If an error occurs during initialization.
*/
public ProtocolWebServer(File configFile, File authFile, File rulesFile, ProtocolBridge protocolBridge) throws Exception {
this.protocolBridge = protocolBridge;
// Initialize the list of connected clients
this.clients = new ArrayList<>();
// Store the configuration file
this.configFile = configFile;
// Set up folder structure for certificates
folderStructure = new ServerCertificateFolderStructure();
// Check for necessary certificate files
checkFileExists(folderStructure.publicServerFolder, folderStructure.certPrefix, ".crt");
checkFileExists(folderStructure.privateServerFolder, folderStructure.certPrefix, ".key");
// Load configuration settings
this.configurationManager = getConfigurationManager(configFile);
// Set up content and error folders
contentFolder = new File("content");
errorsFolder = new File("errors");
// Create folders if they don't exist
if (!contentFolder.exists()) contentFolder.mkdir();
if (!errorsFolder.exists()) errorsFolder.mkdir();
// Set up certificate files based on public IP address
this.certFile = new File(folderStructure.publicServerFolder, folderStructure.certPrefix + NetworkUtils.getPublicIPAddress() + ".crt");
this.keyFile = new File(folderStructure.privateServerFolder, folderStructure.certPrefix + NetworkUtils.getPublicIPAddress() + ".key");
// Create auth and rules files with default content if they don't exist
if (!authFile.exists()) {
authFile.createNewFile();
FileUtils.writeFile(authFile, """
admin:5e884898da28047151d0e56f8dc6292773603d0d6aabbddab8f91d8e5f99f6c7
user:e99a18c428cb38d5f260853678922e03abd8335f
""");
}
// Create default rules file if it doesn't exist
if (!rulesFile.exists()) {
rulesFile.createNewFile();
FileUtils.writeFile(rulesFile, """
{
"allow": [
"index.html",
"css/*",
"private/info.php"
],
"deny": [
"private/*"
],
"auth": [
"private/*",
"admin/*"
]
}
""");
}
// Load authentication and rules
uniqueSessionString = AuthManager.sha256(new RandomString(new Random(System.currentTimeMillis()).nextInt(10, 20)).nextString());
AuthManager.loadAuthFile(authFile);
RuleManager.loadRules(rulesFile);
// Initialize the pipeline server
pipelineServer = new NetworkServer.ServerBuilder().
setPort(configurationManager.getInt("port.pipeline")).setTimeout(0).
setPacketHandler(protocolBridge.getProtocolSettings().packetHandler).setEventManager(protocolBridge.getProtocolSettings().eventManager).
setLogger(protocolBridge.getLogger()).
setServerCertificate(certFile, keyFile).setRootCAFolder(folderStructure.publicCAFolder).
build();
public ProtocolWebServer(File authFile, File rulesFile, int sessionExpire, int uploadSize) throws Exception {
super(authFile, rulesFile, sessionExpire, uploadSize);
}
/**
* Retrieves a connected web client by its client ID.
*
* @param clientID The client ID to search for.
* @return The connected web client with the specified ID, or null if not found.
*/
public ConnectedWebClient getClientByID(int clientID) {
for (ConnectedWebClient client : clients)
if (client.getPipelineConnection().getClientID() == clientID) return client;
return null;
}
@Listener(priority = EventPriority.HIGHEST)
public final void onPacketRead(C_PacketReadEvent event) {
Objects.requireNonNull(event, "event");
/**
* Starts the web server to accept and handle client connections.
*
* @throws Exception If an error occurs while starting the server.
*/
public void startWebServer() throws Exception {
// Start the pipeline server
pipelineServer.start();
Object packet = event.getPacket();
if (packet == null) return;
// Create the SSL server socket for web connections
webServer = (SSLServerSocket) NetworkServer.ServerBuilder.
createSSLServerSocketFactory(folderStructure.publicCAFolder, certFile, keyFile).
createServerSocket(configurationManager.getInt("port"));
webServer.setSoTimeout(0);
webServer.setEnabledProtocols(pipelineServer.getServerSocket().getEnabledProtocols());
CustomConnectedClient client = getClientByID(event.getClient().getUniqueID());
if (client == null) return;
// Stop WebServer if pipelineServer dies
new Thread(() -> {
while (true) {
// Check if the pipeline server is still running
if (pipelineServer == null || !pipelineServer.getServerSocket().isBound()) {
try {
// Stop the web server
onPipelineStop();
} catch (IOException e) {
pipelineServer.getLogger().exception("Failed to stop WebServer", e);
}
Thread.currentThread().interrupt();
break;
}
try {
// Sleep for a while before checking again
Thread.sleep(1000);
} catch (InterruptedException ignored) {
}
try {
if (packet instanceof WebNavigateRequestPacket nav) {
WebNavigateAckPacket ack = handleNavigate(client, nav);
client.getConnection().sendPacket(ack, TransportProtocol.TCP);
return;
}
}).start();
// Handle every client
new Thread(() -> {
while (true) {
try {
// Accept incoming client connections
SSLSocket client = (SSLSocket) webServer.accept();
for (ConnectedWebClient connectedWebClient : clients) {
if (connectedWebClient.getPipelineConnection().getClientID() != -1 && connectedWebClient.isClientVersionLoaded()) {
// Assign socket to an existing connected client
connectedWebClient.setWebSocket(client);
connectedWebClient.setProtocolWebServer(this);
}
}
} catch (IOException e) {
pipelineServer.getLogger().exception("Failed to accept WebClient", e);
}
if (packet instanceof WebResourceRequestPacket req) {
WebResourceResponsePacket resp = handleResource(client, req);
client.getConnection().sendPacket(resp, TransportProtocol.TCP);
return;
}
}).start();
if (packet instanceof WebDocumentApplyRequestPacket apply) {
WebDocumentApplyResponsePacket resp = handleDocumentApply(client, apply);
client.getConnection().sendPacket(resp, TransportProtocol.TCP);
}
} catch (Exception e) {
getProtocolBridge().getProtocolValues().logger.exception("Failed to handle packet", e);
}
}
/**
* Handles the shutdown of the web server when the pipeline server stops.
* Server-side dispatcher entry point for a navigation request (packet id 01).
*
* @throws IOException If an I/O error occurs while closing the web server.
* @param client connected client
* @param request navigation request
* @return navigation ack to send back
*/
private void onPipelineStop() throws IOException {
webServer.close();
public final WebNavigateAckPacket handleNavigate(CustomConnectedClient client, WebNavigateRequestPacket request) {
Objects.requireNonNull(client, "client");
Objects.requireNonNull(request, "request");
WebNavigateAckPacket ack = onNavigateRequest(client, request);
if (ack == null) {
ack = new WebNavigateAckPacket(
mirrorHeader(request.getHeader(), WebPacketFlags.NAVIGATION),
false,
"onNavigateRequest returned null"
);
}
return ack;
}
/**
* Checks if the required certificate files exist in the specified folder.
* Server-side dispatcher entry point for a resource request (packet id 03).
*
* @param folder The folder to check for certificate files.
* @param prefix The prefix of the certificate files.
* @param extension The extension of the certificate files.
* @throws CertificateException If a required certificate file is missing or invalid.
* @throws IOException If an I/O error occurs while checking the files.
*/
private void checkFileExists(File folder, String prefix, String extension) throws CertificateException, IOException {
boolean found = false;
// Ensure the folder exists
if (folder == null) throw new FileNotFoundException("Folder does not exist");
// List all files in the folder
File[] files = folder.listFiles();
if (files == null || files.length == 0)
throw new FileNotFoundException("Folder " + folder.getAbsolutePath() + " is empty");
// Check for the required certificate file
for (File file : files) {
if (!file.getName().startsWith(prefix))
throw new CertificateException(file.getAbsolutePath() + " is not valid");
// Check for file matching the public IP address
if (!found) found = file.getName().equalsIgnoreCase(prefix + NetworkUtils.getPublicIPAddress() + extension);
}
// Throw exception if the required file is not found
if (!found) throw new CertificateException("Missing " + prefix + NetworkUtils.getPublicIPAddress() + extension);
}
/**
* Retrieves the configuration manager for the web server.
* <p>Streaming:
* <ul>
* <li>If you want to stream the body, return a {@link WebResourceResponsePacket} with an empty body,
* and then send {@link WebStreamStartPacket_v1_0_1_B}/{@link WebStreamChunkPacket_v1_0_1_B}/{@link WebStreamEndPacket_v1_0_1_B}
* using the SAME {@code requestId}.</li>
* </ul>
* </p>
*
* @return The configuration manager.
* @throws IOException If an I/O error occurs while loading or saving the configuration.
* @param client connected client
* @param request resource request
* @return resource response packet to send back
*/
public ConfigurationManager getConfigurationManager() throws IOException {
return getConfigurationManager(configFile);
public final WebResourceResponsePacket handleResource(CustomConnectedClient client, WebResourceRequestPacket request) {
Objects.requireNonNull(client, "client");
Objects.requireNonNull(request, "request");
WebResourceResponsePacket resp = onResourceRequest(client, request);
if (resp == null) {
resp = new WebResourceResponsePacket(
mirrorHeader(request.getHeader(), WebPacketFlags.RESOURCE),
500,
"text/plain; charset=utf-8",
java.util.Collections.emptyMap(),
"onResourceRequest returned null".getBytes(StandardCharsets.UTF_8),
null
);
}
return resp;
}
/**
* Loads and initializes the configuration manager with default settings if necessary.
* Server-side dispatcher entry point for a document apply request (packet id 20).
*
* @param configFile The configuration file to load.
* @return The initialized configuration manager.
* @throws IOException If an I/O error occurs while loading or saving the configuration.
* @param client connected client
* @param request apply request
* @return apply response
*/
private ConfigurationManager getConfigurationManager(File configFile) throws IOException {
if (!configFile.exists()) configFile.createNewFile();
public final WebDocumentApplyResponsePacket handleDocumentApply(CustomConnectedClient client, WebDocumentApplyRequestPacket request) {
Objects.requireNonNull(client, "client");
Objects.requireNonNull(request, "request");
ConfigurationManager configurationManager = new ConfigurationManager(configFile);
configurationManager.loadProperties();
if (!configurationManager.isSet("port.webserver")) {
configurationManager.set("port.webserver", 9824);
configurationManager.saveProperties();
WebDocumentApplyResponsePacket resp = onDocumentApplyRequest(client, request);
if (resp == null) {
resp = new WebDocumentApplyResponsePacket(
mirrorHeader(request.getHeader(), 0),
false,
"onDocumentApplyRequest returned null"
);
}
if (!configurationManager.isSet("port.pipeline")) {
configurationManager.set("port.pipeline", 9389);
configurationManager.saveProperties();
}
if (!configurationManager.isSet("filemaxuploadmb")) {
configurationManager.set("filemaxuploadmb", 1000);
configurationManager.saveProperties();
}
if (!configurationManager.isSet("sessionexpireminutes")) {
configurationManager.set("sessionexpireminutes", 60);
configurationManager.saveProperties();
}
return configurationManager;
return resp;
}
/**
* Represents the folder structure for server certificates.
* Hook: called when the server receives a navigation request.
*
* @param client connected client
* @param request navigation request
* @return ack packet (must include mirrored requestId/tabId/pageId/frameId)
*/
public final class ServerCertificateFolderStructure {
public final File certificatesFolder;
protected abstract WebNavigateAckPacket onNavigateRequest(CustomConnectedClient client, WebNavigateRequestPacket request);
public final File publicFolder;
public final File privateFolder;
/**
* Hook: called when the server receives a resource request.
*
* @param client connected client
* @param request resource request
* @return response packet (must include mirrored requestId/tabId/pageId/frameId)
*/
protected abstract WebResourceResponsePacket onResourceRequest(CustomConnectedClient client, WebResourceRequestPacket request);
public final File privateCAFolder;
public final File privateServerFolder;
/**
* Hook: called when the server receives a document apply request.
*
* @param client connected client
* @param request apply request
* @return apply response packet (must include mirrored requestId/tabId/pageId/frameId)
*/
protected abstract WebDocumentApplyResponsePacket onDocumentApplyRequest(CustomConnectedClient client, WebDocumentApplyRequestPacket request);
public final File publicCAFolder;
public final File publicServerFolder;
public final String caPrefix = "ca_server_";
public final String certPrefix = "cert_server_";
/**
* v1.0.0 entry point.
*
* <p>This method is invoked by the v1.0.0 server pipeline when a 1.0.0 client sends a 1.0.0 request packet.
* We adapt it into the v1.0.1 resource pipeline.</p>
*
* @param client connected client
* @param request 1.0.0 request packet
* @return 1.0.0 response packet
*/
@Deprecated(forRemoval = false, since = "1.0.1-BETA.0.1")
@Override
public WebResponsePacket onWebRequest(CustomConnectedClient client, WebRequestPacket request) {
Objects.requireNonNull(client, "client");
Objects.requireNonNull(request, "request");
public ServerCertificateFolderStructure() {
certificatesFolder = new File("certificates");
// 1.0.0 requests have no correlation fields. Create synthetic ones.
long requestId = WebCompatMapper.nextCompatRequestId();
long tabId = LEGACY_TAB_ID;
long pageId = LEGACY_PAGE_ID;
publicFolder = new File(certificatesFolder, "public");
privateFolder = new File(certificatesFolder, "private");
WebResourceRequestPacket mapped = WebCompatMapper.toResourceRequest(requestId, tabId, pageId, request, getProtocolBridge());
WebResourceResponsePacket resp = onResourceRequest(client, mapped);
privateCAFolder = new File(privateFolder, "ca");
privateServerFolder = new File(privateFolder, "server");
publicCAFolder = new File(publicFolder, "ca");
publicServerFolder = new File(publicFolder, "server");
if (!certificatesFolder.exists()) certificatesFolder.mkdirs();
if (!publicFolder.exists()) publicFolder.mkdirs();
if (!privateFolder.exists()) privateFolder.mkdirs();
if (!privateCAFolder.exists()) privateCAFolder.mkdirs();
if (!privateServerFolder.exists()) privateServerFolder.mkdirs();
if (!publicCAFolder.exists()) publicCAFolder.mkdirs();
if (!publicServerFolder.exists()) publicServerFolder.mkdirs();
if (resp == null) {
return new WebResponsePacket(
500,
"text/plain; charset=utf-8",
java.util.Collections.emptyMap(),
"onResourceRequest returned null".getBytes(StandardCharsets.UTF_8)
);
}
return WebCompatMapper.to100BResponse(resp);
}
/**
* Creates a response header that mirrors correlation fields from an incoming header.
*
* @param incoming incoming header
* @param extraFlags extra flags to OR into the mirrored header
* @return mirrored header
*/
protected final WebPacketHeader mirrorHeader(WebPacketHeader incoming, int extraFlags) {
Objects.requireNonNull(incoming, "incoming");
return new WebPacketHeader(
incoming.getRequestId(),
incoming.getTabId(),
incoming.getPageId(),
incoming.getFrameId(),
incoming.getFlags() | extraFlags,
System.currentTimeMillis()
);
}
}

View File

@@ -1,24 +0,0 @@
package org.openautonomousconnection.protocol.side.web.events;
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
import lombok.Getter;
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
import org.openautonomousconnection.protocol.side.web.ConnectedWebClient;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
/**
* Event triggered when a web client connects to the web server.
*/
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
public final class ConnectedWebClientEvent extends Event {
/**
* The connected web client.
*/
@Getter
private final ConnectedWebClient webClient;
public ConnectedWebClientEvent(ConnectedWebClient webClient) {
this.webClient = webClient;
}
}

View File

@@ -6,95 +6,168 @@ import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Manages access rules for web resources.
* Loads allow, deny, and auth rules from a JSON file and provides methods to check access.
*
* <p>Rules are loaded from a JSON file with the structure:
* <pre>
* {
* "allow": ["index.html", "css/*"],
* "deny": ["private/*"],
* "auth": ["private/*", "admin/*"]
* }
* </pre>
*
* <p>Matching is performed against normalized web paths (forward slashes).
* Patterns are treated as glob patterns where '*' matches any sequence.</p>
*/
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
public final class RuleManager {
/**
* Lists of path patterns for allow, deny, and auth rules
*/
private static List<String> allow;
private static List<Pattern> allow = List.of();
private static List<Pattern> deny = List.of();
private static List<Pattern> auth = List.of();
private RuleManager() {
}
/**
* Lists of path patterns for allow, deny, and auth rules
*/
private static List<String> deny;
/**
* Lists of path patterns for allow, deny, and auth rules
*/
private static List<String> auth;
/**
* Loads rules from a JSON file.
* The JSON should have the structure:
* {
* "allow": ["pattern1", "pattern2", ...],
* "deny": ["pattern1", "pattern2", ...],
* "auth": ["pattern1", "pattern2", ...]
* }
* Loads rules from a JSON file and compiles patterns.
*
* @param rulesFile The JSON file containing the rules.
* @throws Exception If an error occurs reading the file or parsing JSON.
* @param rulesFile JSON rules file
* @throws Exception if reading/parsing fails
*/
public static void loadRules(File rulesFile) throws Exception {
// Load and parse the JSON file
String json = new String(Files.readAllBytes(rulesFile.toPath()));
Map<String, List<String>> map = new Gson().fromJson(json, new TypeToken<Map<String, List<String>>>() {
}.getType());
String json = Files.readString(rulesFile.toPath(), StandardCharsets.UTF_8);
// Default to empty lists if keys are missing
allow = map.getOrDefault("allow", List.of());
deny = map.getOrDefault("deny", List.of());
auth = map.getOrDefault("auth", List.of());
Map<String, List<String>> map = new Gson().fromJson(
json,
new TypeToken<Map<String, List<String>>>() {
}.getType()
);
allow = compileList(map.getOrDefault("allow", List.of()));
deny = compileList(map.getOrDefault("deny", List.of()));
auth = compileList(map.getOrDefault("auth", List.of()));
}
/**
* Checks if the given path is allowed based on the allow rules.
* Returns true if the path is allowed by allow rules.
*
* @param path The path to check.
* @return True if the path is allowed, false otherwise.
* <p>Important: If allow list is empty, everything is allowed (default-open).</p>
*
* @param path web path (e.g. "/index.html" or "index.html")
* @return true if allowed
*/
public static boolean isAllowed(String path) {
return allow.stream().anyMatch(p -> pathMatches(path, p));
String p = normalizePath(path);
// Default-open behavior if allow list is empty
if (allow.isEmpty()) return true;
return matchesAny(p, allow);
}
/**
* Checks if the given path is denied based on the deny rules.
* Returns true if the path is denied.
*
* @param path The path to check.
* @return True if the path is denied, false otherwise.
* @param path web path
* @return true if denied
*/
public static boolean isDenied(String path) {
return deny.stream().anyMatch(p -> pathMatches(path, p));
String p = normalizePath(path);
return matchesAny(p, deny);
}
/**
* Checks if the given path requires authentication based on the auth rules.
* Returns true if the path requires authentication.
*
* @param path The path to check.
* @return True if the path requires authentication, false otherwise.
* @param path web path
* @return true if auth required
*/
public static boolean requiresAuth(String path) {
return auth.stream().anyMatch(p -> pathMatches(path, p));
String p = normalizePath(path);
return matchesAny(p, auth);
}
private static boolean matchesAny(String normalizedPath, List<Pattern> patterns) {
for (Pattern pat : patterns) {
if (pat.matcher(normalizedPath).matches()) return true;
}
return false;
}
private static List<Pattern> compileList(List<String> globs) {
if (globs == null || globs.isEmpty()) return List.of();
return globs.stream()
.map(RuleManager::compileGlob)
.toList();
}
/**
* Helper method to check if a path matches a pattern.
* Patterns can include '*' as a wildcard.
* Compiles a glob pattern to a full-match regex Pattern.
*
* @param path The path to check.
* @param pattern The pattern to match against.
* @return True if the path matches the pattern, false otherwise.
* <p>Rules:
* <ul>
* <li>'*' matches any sequence of characters (including '/')</li>
* <li>All other regex meta chars are escaped</li>
* <li>Matching is performed against the entire normalized path</li>
* </ul>
*
* @param glob glob pattern, e.g. "css/*" or "private/*"
* @return compiled Pattern
*/
private static boolean pathMatches(String path, String pattern) {
pattern = pattern.replace("/", File.separator).replace("*", ".*");
return path.matches(pattern);
private static Pattern compileGlob(String glob) {
String g = normalizePath(glob);
StringBuilder regex = new StringBuilder();
regex.append("^");
for (int i = 0; i < g.length(); i++) {
char c = g.charAt(i);
if (c == '*') {
regex.append(".*");
continue;
}
// Escape regex metacharacters
if ("\\.[]{}()+-^$|?".indexOf(c) >= 0) {
regex.append("\\");
}
regex.append(c);
}
regex.append("$");
return Pattern.compile(regex.toString());
}
/**
* Normalizes a web path:
* <ul>
* <li>null -> ""</li>
* <li>backslashes -> forward slashes</li>
* <li>leading '/' removed</li>
* <li>no URL decoding (must be done at parser level if needed)</li>
* </ul>
*
* @param path input path
* @return normalized path
*/
private static String normalizePath(String path) {
if (path == null) return "";
String p = path.trim().replace('\\', '/');
while (p.startsWith("/")) {
p = p.substring(1);
}
return p;
}
}

View File

@@ -128,7 +128,7 @@ public final class SessionManager {
this.login = login;
this.ip = ip;
this.userAgent = userAgent;
this.expiresAt = System.currentTimeMillis() + (long) protocolWebServer.getConfigurationManager().getInt("sessionexpireminutes") * 60 * 1000;
this.expiresAt = System.currentTimeMillis() + (long) protocolWebServer.getSessionExpire() * 60 * 1000;
}
/**
@@ -158,7 +158,7 @@ public final class SessionManager {
* @throws IOException If an I/O error occurs.
*/
void refresh(ProtocolWebServer protocolWebServer) throws IOException {
this.expiresAt = System.currentTimeMillis() + (long) protocolWebServer.getConfigurationManager().getInt("sessionexpireminutes") * 60 * 1000;
this.expiresAt = System.currentTimeMillis() + (long) protocolWebServer.getSessionExpire() * 60 * 1000;
}
}
}

View File

@@ -0,0 +1,15 @@
package org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta;
/**
* Installs the "web://" protocol handler using java.protocol.handler.pkgs.
* Recommended to use Version v1.0.0-BETA or newer
*/
@Deprecated(forRemoval = false, since = "1.0.1-BETA.0.3")
public abstract class LibClientImpl_v1_0_0_B {
/**
* Called when connecting to the resolved server endpoint fails.
*
* @param exception the connection failure
*/
public abstract void serverConnectionFailed(Exception exception);
}

View File

@@ -0,0 +1,29 @@
package org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta;
/**
* Stores the current session token across multiple HttpURLConnection instances.
*
* <p>JavaFX WebView creates a new connection per navigation, so headers must be re-injected
* for every request.</p>
*/
public final class OacSessionJar {
private volatile String session;
/**
* Stores a session token (e.g. from response header "session").
*
* @param session session token
*/
public void store(String session) {
String s = (session == null) ? null : session.trim();
this.session = (s == null || s.isEmpty()) ? null : s;
}
/**
* @return stored session token or null
*/
public String get() {
return session;
}
}

View File

@@ -0,0 +1,288 @@
package org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.WebRequestMethod;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URL;
import java.util.*;
/**
* HttpURLConnection implementation that maps "web://" URLs to OAC WebRequestPacket/WebResponsePacket.
*
* <p>Important semantics for JavaFX WebView:</p>
* <ul>
* <li>WebView may call {@link #connect()} before {@link #getOutputStream()}.</li>
* <li>WebView creates a NEW connection instance per navigation; therefore persistent headers
* (like session) must be injected from an external store per request.</li>
* </ul>
*/
public final class OacWebHttpURLConnection extends HttpURLConnection {
private static final int MAX_REDIRECTS = 8;
private final OacWebRequestBroker broker;
private final OacSessionJar sessionJar;
private final Map<String, String> requestHeaders = new LinkedHashMap<>();
private final ByteArrayOutputStream requestBody = new ByteArrayOutputStream(1024);
private boolean connected;
private boolean requestSent;
private OacWebResponse response;
/**
* Creates a new OAC HttpURLConnection.
*
* @param url the web:// URL
* @param broker request broker
* @param sessionJar shared session store (must be shared across connections)
*/
public OacWebHttpURLConnection(URL url, OacWebRequestBroker broker, OacSessionJar sessionJar) {
super(url);
this.broker = Objects.requireNonNull(broker, "broker");
this.sessionJar = Objects.requireNonNull(sessionJar, "sessionJar");
this.method = "GET";
setInstanceFollowRedirects(true);
}
private static String headerValue(Map<String, String> headers, String nameLower) {
if (headers == null || headers.isEmpty() || nameLower == null) return null;
String needle = nameLower.trim().toLowerCase(Locale.ROOT);
for (Map.Entry<String, String> e : headers.entrySet()) {
if (e.getKey() == null) continue;
if (e.getKey().trim().toLowerCase(Locale.ROOT).equals(needle)) {
return e.getValue();
}
}
return null;
}
@Override
public void setRequestProperty(String key, String value) {
if (key == null) return;
// DO NOT block after connect(): WebView may call connect() early.
// Only block once the request was actually sent.
if (requestSent) throw new IllegalStateException("Request already sent");
if (value == null) requestHeaders.remove(key);
else requestHeaders.put(key, value);
}
@Override
public String getRequestProperty(String key) {
if (key == null) return null;
for (Map.Entry<String, String> e : requestHeaders.entrySet()) {
if (e.getKey() != null && e.getKey().equalsIgnoreCase(key)) return e.getValue();
}
return null;
}
@Override
public Map<String, List<String>> getRequestProperties() {
Map<String, List<String>> out = new LinkedHashMap<>();
for (Map.Entry<String, String> e : requestHeaders.entrySet()) {
out.put(e.getKey(), e.getValue() == null ? List.of() : List.of(e.getValue()));
}
return Collections.unmodifiableMap(out);
}
@Override
public void setRequestMethod(String method) throws ProtocolException {
if (method == null) throw new ProtocolException("method is null");
if (requestSent) throw new ProtocolException("Request already sent");
String m = method.trim().toUpperCase(Locale.ROOT);
if (!m.equals("GET") && !m.equals("POST")) {
throw new ProtocolException("Unsupported method: " + method);
}
this.method = m;
}
@Override
public OutputStream getOutputStream() throws IOException {
// WebView may call connect() first, so do NOT throw "Already connected".
if (requestSent) throw new IllegalStateException("Request already sent");
setDoOutput(true);
return requestBody;
}
@Override
public void connect() {
// MUST NOT send here. WebView may call connect() before writing the POST body.
connected = true;
}
private void ensureResponse() throws IOException {
if (requestSent) return;
URL cur = this.url;
String methodStr = (this.method == null) ? "GET" : this.method.trim().toUpperCase(Locale.ROOT);
WebRequestMethod reqMethod = "POST".equals(methodStr) ? WebRequestMethod.POST : WebRequestMethod.GET;
// Snapshot headers/body at send time.
Map<String, String> carryHeaders = new LinkedHashMap<>(requestHeaders);
// Each navigation creates a new connection, so we re-add the session for every request.
String session = sessionJar.get();
if (session != null && !session.isBlank() && headerValue(carryHeaders, "session") == null) {
carryHeaders.put("session", session);
}
byte[] carryBody = null;
if (getDoOutput()) {
carryBody = requestBody.toByteArray();
if (reqMethod == WebRequestMethod.POST && carryBody == null) carryBody = new byte[0];
}
if (reqMethod == WebRequestMethod.POST && headerValue(carryHeaders, "content-type") == null) {
carryHeaders.put("content-type", "application/x-www-form-urlencoded; charset=utf-8");
}
OacWebResponse resp = null;
WebRequestMethod carryMethod = reqMethod;
for (int i = 0; i <= MAX_REDIRECTS; i++) {
resp = broker.fetch(cur, carryMethod, carryHeaders, carryBody);
String newSession = headerValue(resp.headers(), "session");
if (newSession != null && !newSession.isBlank()) {
sessionJar.store(newSession);
// keep it for the next request in this redirect chain too
carryHeaders.put("session", newSession);
}
int code = resp.statusCode();
if (!getInstanceFollowRedirects()) break;
if (code == 301 || code == 302 || code == 303 || code == 307 || code == 308) {
String loc = headerValue(resp.headers(), "location");
if (loc == null || loc.isBlank()) break;
try {
cur = new URL(cur, loc);
} catch (Exception ex) {
break;
}
if (code == 303) {
carryMethod = WebRequestMethod.GET;
carryBody = null;
} else if ((code == 301 || code == 302) && carryMethod == WebRequestMethod.POST) {
carryMethod = WebRequestMethod.GET;
carryBody = null;
}
continue;
}
break;
}
this.response = resp;
this.requestSent = true;
this.connected = true;
}
@Override
public InputStream getInputStream() throws IOException {
ensureResponse();
if (response == null) return new ByteArrayInputStream(new byte[0]);
return response.bodyStream();
}
@Override
public InputStream getErrorStream() {
try {
ensureResponse();
if (response == null) return null;
int code = response.statusCode();
return (code >= 400) ? response.bodyStream() : null;
} catch (IOException e) {
return null;
}
}
@Override
public int getResponseCode() throws IOException {
ensureResponse();
return response == null ? -1 : response.statusCode();
}
@Override
public String getContentType() {
try {
ensureResponse();
} catch (IOException e) {
return "application/octet-stream";
}
String ct = (response == null) ? null : response.contentType();
return (ct == null || ct.isBlank()) ? "application/octet-stream" : ct;
}
@Override
public int getContentLength() {
try {
ensureResponse();
} catch (IOException e) {
return -1;
}
long len = (response == null) ? -1L : response.contentLength();
return (len <= 0 || len > Integer.MAX_VALUE) ? -1 : (int) len;
}
@Override
public long getContentLengthLong() {
try {
ensureResponse();
} catch (IOException e) {
return -1L;
}
return (response == null) ? -1L : response.contentLength();
}
@Override
public Map<String, List<String>> getHeaderFields() {
try {
ensureResponse();
} catch (IOException e) {
return Map.of();
}
if (response == null) return Map.of();
Map<String, List<String>> out = new LinkedHashMap<>();
for (Map.Entry<String, String> e : response.headers().entrySet()) {
String k = e.getKey();
String v = e.getValue();
if (k == null) continue;
out.put(k, v == null ? List.of() : List.of(v));
}
return out;
}
@Override
public String getHeaderField(String name) {
if (name == null) return null;
try {
ensureResponse();
} catch (IOException e) {
return null;
}
if (response == null) return null;
return headerValue(response.headers(), name.trim().toLowerCase(Locale.ROOT));
}
@Override
public void disconnect() {
// No persistent socket owned by this object.
connected = false;
}
@Override
public boolean usingProxy() {
return false;
}
}

View File

@@ -0,0 +1,153 @@
package org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta;
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
import dev.unlegitdqrk.unlegitlibrary.event.EventPriority;
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.C_PacketReadEvent;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSResponsePacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamChunkPacket_v1_0_0_B;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamEndPacket_v1_0_0_B;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamStartPacket_v1_0_0_B;
import org.openautonomousconnection.protocol.side.client.ProtocolClient;
import org.openautonomousconnection.protocol.side.client.events.ConnectedToProtocolServerEvent;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecord;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
import java.util.List;
import java.util.Objects;
/**
* Receives incoming OAC web response packets and forwards them into the request broker.
*
* <p>Important:</p>
* <ul>
* <li>The shown protocol types do not contain any correlation id.</li>
* <li>Therefore, the broker must treat the connection as single-flight (one in-flight request).</li>
* </ul>
*/
public final class OacWebPacketListener extends EventListener {
private final OacWebRequestBroker broker;
private final ProtocolClient client;
private final LibClientImpl_v1_0_0_B impl;
/**
* Creates a new listener bound to the given broker.
*
* @param broker broker instance
* @param client protocol client
*/
public OacWebPacketListener(OacWebRequestBroker broker, ProtocolClient client, LibClientImpl_v1_0_0_B impl) {
this.broker = Objects.requireNonNull(broker, "broker");
this.client = Objects.requireNonNull(client, "client");
this.impl = Objects.requireNonNull(impl, "impl");
}
/**
* Notifies the broker that the server connection is established.
*
* @param event connected event
*/
@Listener(priority = EventPriority.HIGHEST)
public void onConnected(ConnectedToProtocolServerEvent event) {
broker.notifyServerConnected();
}
/**
* Handles packets coming from INS and the web server side.
*
* @param event packet event
*/
@Listener(priority = EventPriority.HIGHEST)
public void onPacketRead(C_PacketReadEvent event) {
Object p = event.getPacket();
if (p instanceof INSResponsePacket resp) {
onInsResponse(resp);
return;
}
if (p instanceof WebResponsePacket resp) {
broker.onWebResponse(resp.getStatusCode(), resp.getContentType(), resp.getHeaders(), resp.getBody());
return;
}
if (p instanceof WebStreamStartPacket_v1_0_0_B start) {
broker.onStreamStart(start.getStatusCode(), start.getContentType(), start.getHeaders(), start.getTotalLength());
return;
}
if (p instanceof WebStreamChunkPacket_v1_0_0_B chunk) {
broker.onStreamChunk(chunk.getSeq(), chunk.getData());
return;
}
if (p instanceof WebStreamEndPacket_v1_0_0_B end) {
broker.onStreamEnd(end.isOk());
}
}
private void onInsResponse(INSResponsePacket resp) {
INSResponseStatus status = resp.getStatus();
List<INSRecord> records = resp.getRecords();
if (status != INSResponseStatus.OK) {
broker.invalidateCurrentInfoName();
throw new IllegalStateException("INS resolution failed: " + status);
}
if (records == null || records.isEmpty() || records.getFirst() == null || records.getFirst().value == null) {
broker.invalidateCurrentInfoName();
throw new IllegalStateException("INS resolution returned no usable records");
}
String host = records.getFirst().value.trim();
if (host.isEmpty()) {
broker.invalidateCurrentInfoName();
throw new IllegalStateException("INS record value is empty");
}
String hostname;
int port;
if (!host.contains(":")) {
hostname = host;
if (records.getFirst().port == 0) port = 1028;
else port = records.getFirst().port;
} else {
String[] split = host.split(":", 2);
hostname = split[0].trim();
String p1 = split[1].trim();
if (hostname.isEmpty() || p1.isEmpty()) {
broker.invalidateCurrentInfoName();
throw new IllegalStateException("Invalid INS host:port value: " + host);
}
try {
port = Integer.parseInt(p1);
} catch (NumberFormatException e) {
broker.invalidateCurrentInfoName();
throw new IllegalStateException("Invalid port in INS record: " + host, e);
}
}
Thread t = new Thread(() -> connectServer(hostname, port), "oac-web-server-connect");
t.setDaemon(true);
t.start();
}
private void connectServer(String hostname, int port) {
try {
if (client.getClientServerConnection() != null && client.getClientServerConnection().isConnected()) {
client.getClientServerConnection().disconnect();
}
client.buildServerConnection(null, client.getProtocolBridge().getProtocolValues().ssl);
client.getClientServerConnection().connect(hostname, port);
} catch (Exception e) {
broker.invalidateCurrentInfoName();
impl.serverConnectionFailed(e);
}
}
}

View File

@@ -0,0 +1,520 @@
package org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSQueryPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket;
import org.openautonomousconnection.protocol.side.client.ProtocolClient;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecordType;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.WebRequestMethod;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Central broker that translates {@code web://} URLs into OAC protocol traffic.
*
* <p>Protocol limitation: no correlation id -> single-flight (one in-flight request at a time).</p>
*
* <p>UDP streaming semantics (best-effort):</p>
* <ul>
* <li>Chunks may arrive out of order or be lost.</li>
* <li>We accept gaps and assemble what we have after {@code WebStreamEndPacket}.</li>
* <li>We wait a short grace window after stream end to allow late UDP packets.</li>
* </ul>
*/
public final class OacWebRequestBroker {
private static final OacWebRequestBroker INSTANCE = new OacWebRequestBroker();
private static final long CONNECT_TIMEOUT_SECONDS = 10;
private static final long RESPONSE_TIMEOUT_SECONDS = 25;
/**
* Grace time after receiving WebStreamEndPacket to allow late UDP packets (reordering).
*/
private static final long UDP_END_GRACE_MILLIS = 150;
private final Object responseLock = new Object();
private volatile ProtocolClient client;
private volatile CountDownLatch connectionLatch;
private volatile String currentInfoName;
private volatile ResponseState responseState;
private OacWebRequestBroker() {
}
/**
* Returns the singleton broker.
*
* @return broker
*/
public static OacWebRequestBroker get() {
return INSTANCE;
}
private static String safeContentType(String ct) {
return (ct == null || ct.isBlank()) ? "application/octet-stream" : ct;
}
private static Map<String, String> safeHeaders(Map<String, String> headers) {
return (headers == null || headers.isEmpty()) ? Map.of() : Map.copyOf(headers);
}
private static String normalizePathWithQuery(String path, String query) {
String p;
if (path == null || path.isBlank() || "/".equals(path)) {
p = "index.html";
} else {
p = path.startsWith("/") ? path.substring(1) : path;
if (p.isBlank()) p = "index.html";
}
if (query != null && !query.isBlank()) {
return p + "?" + query;
}
return p;
}
/**
* Assembles the response body from received chunks in ascending seq order.
*
* <p>Best-effort UDP behavior: gaps are ignored.</p>
*/
private static void assembleBestEffortLocked(ResponseState st) {
st.body.reset();
if (st.chunkBuffer.isEmpty() || st.maxSeqSeen < 0) {
return;
}
for (int seq = 0; seq <= st.maxSeqSeen; seq++) {
byte[] chunk = st.chunkBuffer.get(seq);
if (chunk == null) continue; // gap accepted
st.body.write(chunk, 0, chunk.length);
}
}
private static void sleepSilently(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
/**
* Attaches the client used to send INS/Web packets.
*
* @param client protocol client
*/
public void attachClient(ProtocolClient client) {
this.client = Objects.requireNonNull(client, "client");
}
/**
* Legacy overload (GET with no body).
*
* @param url web:// URL
* @param headers request headers
* @return response
*/
public OacWebResponse fetch(URL url, Map<String, String> headers) {
return fetch(url, WebRequestMethod.GET, headers, null);
}
/**
* Fetches a URL via OAC protocol (used by {@link java.net.URLConnection}).
*
* @param url web:// URL
* @param method request method
* @param headers request headers
* @param body request body (may be null)
* @return response
*/
public OacWebResponse fetch(URL url, WebRequestMethod method, Map<String, String> headers, byte[] body) {
Objects.requireNonNull(url, "url");
Objects.requireNonNull(method, "method");
ProtocolClient c = this.client;
if (c == null) {
throw new IllegalStateException("ProtocolClient not attached. Call OacWebUrlInstaller.installOnce(..., client) first.");
}
Response r = openAndAwait(c, url, method, headers, body);
byte[] respBody = (r.body() == null) ? new byte[0] : r.body();
long len = respBody.length;
return new OacWebResponse(
r.statusCode(),
r.contentType(),
OacWebResponse.safeHeaders(r.headers()),
new ByteArrayInputStream(respBody),
len
);
}
/**
* Opens a resource and blocks until the current single-flight response completes.
*/
public Response openAndAwait(ProtocolClient client, URL url, WebRequestMethod method, Map<String, String> headers, byte[] body) {
Objects.requireNonNull(client, "client");
Objects.requireNonNull(url, "url");
Objects.requireNonNull(method, "method");
open(client, url, method, headers, body);
return awaitResponse(RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
}
/**
* Sends required packets for a {@code web://} URL.
*/
public synchronized void open(ProtocolClient client, URL url, WebRequestMethod method, Map<String, String> headers, byte[] body) {
Objects.requireNonNull(client, "client");
Objects.requireNonNull(url, "url");
Objects.requireNonNull(method, "method");
if (!"web".equalsIgnoreCase(url.getProtocol())) {
throw new IllegalArgumentException("Unsupported protocol: " + url.getProtocol());
}
String infoName = url.getHost();
if (infoName == null || infoName.isBlank()) {
throw new IllegalArgumentException("Missing InfoName in URL: " + url);
}
String path = normalizePathWithQuery(url.getPath(), url.getQuery());
beginNewResponse();
if (!infoName.equals(currentInfoName)) {
resolveAndConnect(client, infoName);
currentInfoName = infoName;
} else {
awaitConnectionIfPending();
}
sendWebRequest(client, path, method, headers, body);
}
/**
* Called by packet listener when server connection is established.
*/
public void notifyServerConnected() {
CountDownLatch latch = connectionLatch;
if (latch != null) {
latch.countDown();
}
}
/**
* Invalidates cached InfoName, forcing a new INS resolution on next request.
*/
public synchronized void invalidateCurrentInfoName() {
currentInfoName = null;
}
/**
* Handles a non-streamed WebResponsePacket.
*
* @param statusCode status code
* @param contentType content-type
* @param headers headers
* @param body body
*/
public void onWebResponse(int statusCode, String contentType, Map<String, String> headers, byte[] body) {
ResponseState st = responseState;
if (st == null) return;
synchronized (responseLock) {
if (st.completed) return;
st.statusCode = statusCode;
st.contentType = safeContentType(contentType);
st.headers = safeHeaders(headers);
byte[] b = (body == null) ? new byte[0] : body;
st.body.reset();
st.body.write(b, 0, b.length);
st.completed = true;
st.success = true;
st.done.countDown();
}
}
/**
* Handles the beginning of a streamed response.
*
* @param statusCode status code
* @param contentType content-type
* @param headers headers
* @param totalLength total length (may be -1)
*/
public void onStreamStart(int statusCode, String contentType, Map<String, String> headers, long totalLength) {
ResponseState st = responseState;
if (st == null) return;
synchronized (responseLock) {
if (st.completed) return;
st.statusCode = statusCode;
st.contentType = safeContentType(contentType);
st.headers = safeHeaders(headers);
st.totalLength = totalLength;
st.streamStarted = true;
st.maxSeqSeen = -1;
st.endReceived = false;
st.endReceivedAtMillis = 0L;
// Streaming body will be assembled on end (best-effort UDP)
st.body.reset();
}
}
/**
* Handles a streamed chunk.
*
* <p>UDP best-effort: store by seq and assemble later; accept gaps.</p>
*
* @param seq chunk sequence number
* @param data chunk bytes
*/
public void onStreamChunk(int seq, byte[] data) {
ResponseState st = responseState;
if (st == null) return;
if (data == null || data.length == 0) return;
synchronized (responseLock) {
if (st.completed) return;
if (!st.streamStarted) {
failLocked(st, "Stream chunk received before stream start");
return;
}
if (seq < 0) return;
st.chunkBuffer.put(seq, Arrays.copyOf(data, data.length));
if (seq > st.maxSeqSeen) st.maxSeqSeen = seq;
}
}
/**
* Handles stream end.
*
* <p>UDP best-effort: do not complete immediately; allow late UDP packets and assemble after grace.</p>
*
* @param ok end status
*/
public void onStreamEnd(boolean ok) {
ResponseState st = responseState;
if (st == null) return;
synchronized (responseLock) {
if (st.completed) return;
if (!st.streamStarted) {
st.streamStarted = true;
}
st.success = ok;
st.endReceived = true;
st.endReceivedAtMillis = System.currentTimeMillis();
// completion + assembly happens in awaitResponse() after grace window
}
}
/**
* Waits for the current response (single-flight).
*
* @param timeout timeout
* @param unit unit
* @return response
*/
public Response awaitResponse(long timeout, TimeUnit unit) {
Objects.requireNonNull(unit, "unit");
ResponseState st = responseState;
if (st == null) {
throw new IllegalStateException("No in-flight request");
}
long deadlineNanos = System.nanoTime() + unit.toNanos(timeout);
for (; ; ) {
synchronized (responseLock) {
if (st.completed) {
break;
}
// Non-stream response already completed by onWebResponse()
if (!st.streamStarted && st.done.getCount() == 0) {
st.completed = true;
break;
}
if (st.endReceived) {
long now = System.currentTimeMillis();
if (now - st.endReceivedAtMillis >= UDP_END_GRACE_MILLIS) {
// Assemble best-effort body from received chunks
assembleBestEffortLocked(st);
st.completed = true;
if (!st.success) {
st.errorMessage = (st.errorMessage == null) ? "Streaming failed" : st.errorMessage;
}
st.done.countDown();
break;
}
}
}
if (System.nanoTime() >= deadlineNanos) {
throw new IllegalStateException("Timeout while waiting for Web response");
}
sleepSilently(10);
}
synchronized (responseLock) {
if (!st.success) {
throw new IllegalStateException(st.errorMessage == null ? "Request failed" : st.errorMessage);
}
return new Response(
st.statusCode,
st.contentType,
st.headers,
st.body.toByteArray()
);
}
}
private void resolveAndConnect(ProtocolClient client, String infoName) {
if (client.getClientINSConnection() == null || !client.getClientINSConnection().isConnected()) return;
String[] parts = infoName.split("\\.");
if (parts.length < 2 || parts.length > 3) {
throw new IllegalArgumentException(
"Invalid INS address format: " + infoName + " (expected name.tln or sub.name.tln)"
);
}
String tln = parts[parts.length - 1];
String name = parts[parts.length - 2];
String sub = (parts.length == 3) ? parts[0] : null;
connectionLatch = new CountDownLatch(1);
try {
client.sendINSQuery(tln, name, sub, INSRecordType.A);
awaitConnectionIfPending();
} catch (Exception e) {
throw new IllegalStateException("Failed to send INSQueryPacket for " + infoName, e);
}
}
private void awaitConnectionIfPending() {
CountDownLatch latch = connectionLatch;
if (latch == null || client == null || client.getClientServerConnection() == null || client.getClientINSConnection() == null) {
return;
}
try {
if (!latch.await(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
throw new IllegalStateException("Timeout while waiting for ServerConnection");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Interrupted while waiting for ServerConnection", e);
}
}
private void sendWebRequest(ProtocolClient client, String path, WebRequestMethod method, Map<String, String> headers, byte[] body) {
Objects.requireNonNull(client, "client");
Objects.requireNonNull(path, "path");
Objects.requireNonNull(method, "method");
if (client.getClientServerConnection() == null || !client.getClientServerConnection().isConnected()) {
awaitConnectionIfPending();
}
if (client.getClientServerConnection() == null || !client.getClientServerConnection().isConnected()) {
throw new IllegalStateException("ServerConnection is not connected after waiting");
}
WebRequestPacket packet = new WebRequestPacket(
path,
method,
(headers == null ? Map.of() : headers),
body
);
try {
client.getClientServerConnection().sendPacket(packet, TransportProtocol.TCP);
} catch (Exception e) {
throw new IllegalStateException("Failed to send WebRequestPacket for path " + path, e);
}
}
private void beginNewResponse() {
synchronized (responseLock) {
responseState = new ResponseState();
}
}
private void failLocked(ResponseState st, String message) {
st.completed = true;
st.success = false;
st.errorMessage = message;
st.done.countDown();
}
/**
* Response DTO.
*
* @param statusCode status code
* @param contentType content type
* @param headers headers
* @param body body bytes
*/
public record Response(int statusCode, String contentType, Map<String, String> headers, byte[] body) {
}
/**
* In-flight state for a single request (single-flight).
*/
private static final class ResponseState {
private final CountDownLatch done = new CountDownLatch(1);
private final ByteArrayOutputStream body = new ByteArrayOutputStream(64 * 1024);
private final Map<Integer, byte[]> chunkBuffer = new HashMap<>();
private int statusCode = 0;
private String contentType = "application/octet-stream";
private Map<String, String> headers = Map.of();
private boolean streamStarted;
private long totalLength;
private int maxSeqSeen = -1;
private boolean endReceived;
private long endReceivedAtMillis;
private boolean completed;
private boolean success;
private String errorMessage;
}
}

View File

@@ -0,0 +1,32 @@
package org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta;
import java.io.InputStream;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
/**
* Represents a resolved web response for the JavaFX WebView.
*
* @param statusCode HTTP-like status code
* @param contentType response content-type (as sent by server)
* @param headers response headers
* @param bodyStream body stream (may be streaming)
* @param contentLength content length if known, else -1
*/
public record OacWebResponse(
int statusCode,
String contentType,
Map<String, String> headers,
InputStream bodyStream,
long contentLength
) {
public OacWebResponse {
Objects.requireNonNull(headers, "headers");
Objects.requireNonNull(bodyStream, "bodyStream");
}
public static Map<String, String> safeHeaders(Map<String, String> h) {
return (h == null) ? Collections.emptyMap() : Collections.unmodifiableMap(h);
}
}

View File

@@ -0,0 +1,32 @@
package org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta;
import org.openautonomousconnection.protocol.ProtocolBridge;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Installs the "web://" protocol handler using java.protocol.handler.pkgs.
* Recommended to use Version v1.0.0-BETA or newer
*/
@Deprecated(forRemoval = false, since = "1.0.1-BETA.0.3")
public final class OacWebUrlInstaller_v1_0_0_B {
private static final AtomicBoolean INSTALLED = new AtomicBoolean(false);
private OacWebUrlInstaller_v1_0_0_B() {
}
public static void installOnce(ProtocolBridge protocolBridge, LibClientImpl_v1_0_0_B impl) {
Objects.requireNonNull(protocolBridge, "protocolBridge");
Objects.requireNonNull(impl, "impl");
if (!INSTALLED.compareAndSet(false, true)) return;
OacWebRequestBroker.get().attachClient(protocolBridge.getProtocolClient());
protocolBridge.getProtocolValues().eventManager.
registerListener(new OacWebPacketListener(OacWebRequestBroker.get(), protocolBridge.getProtocolClient(), impl));
ProtocolHandlerPackages.installPackage("org.openautonomousconnection.urlhandler");
}
}

View File

@@ -0,0 +1,42 @@
package org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
/**
* Utility to safely append packages to "java.protocol.handler.pkgs".
*/
public final class ProtocolHandlerPackages {
private static final String KEY = "java.protocol.handler.pkgs";
private ProtocolHandlerPackages() {
}
/**
* Appends a package prefix to the protocol handler search path.
*
* @param pkg package prefix (e.g. "com.example.protocols")
*/
public static void installPackage(String pkg) {
Objects.requireNonNull(pkg, "pkg");
String p = pkg.trim();
if (p.isEmpty()) return;
String existing = System.getProperty(KEY, "");
Set<String> parts = new LinkedHashSet<>();
if (!existing.isBlank()) {
for (String s : existing.split("\\|")) {
String t = s.trim();
if (!t.isEmpty()) parts.add(t);
}
}
parts.add(p);
String merged = String.join("|", parts);
System.setProperty(KEY, merged);
}
}

View File

@@ -0,0 +1,22 @@
package org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta.web;
import org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta.OacSessionJar;
import org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta.OacWebHttpURLConnection;
import org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta.OacWebRequestBroker;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
/**
* URLStreamHandler for the "web" protocol (loaded via java.protocol.handler.pkgs).
*/
public final class Handler extends URLStreamHandler {
private static final OacSessionJar SESSION_JAR = new OacSessionJar();
@Override
protected URLConnection openConnection(URL u) throws IOException {
return new OacWebHttpURLConnection(u, OacWebRequestBroker.get(), SESSION_JAR);
}
}

View File

@@ -0,0 +1,326 @@
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta;
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
import dev.unlegitdqrk.unlegitlibrary.event.EventPriority;
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.C_PacketReadEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.ClientDisconnectedEvent;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSResponsePacket;
import org.openautonomousconnection.protocol.side.client.ProtocolClient;
import org.openautonomousconnection.protocol.side.client.events.ConnectedToProtocolINSServerEvent;
import org.openautonomousconnection.protocol.side.client.events.ConnectedToProtocolServerEvent;
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web.OacWebRequestBroker;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecord;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecordType;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
import java.util.List;
import java.util.Objects;
/**
* Coordinates INS resolution for WEB requests and connects to the resolved server endpoint.
*
* <p>No {@code sendIns()} exists. This coordinator sends INS queries directly:</p>
* <ul>
* <li>{@link #requestResolve(String)} is called on every WEB request.</li>
* <li>Requests for the currently connected InfoName reuse the active server connection.</li>
* <li>If INS is connected, unresolved hosts immediately trigger {@code sendINSQuery(tln,name,sub,A)}.</li>
* <li>If INS is not connected yet, it stores the pending host and sends on {@link ConnectedToProtocolINSServerEvent}.</li>
* <li>On {@link INSResponsePacket} it connects the server connection.</li>
* <li>On {@link ConnectedToProtocolServerEvent} it opens the broker gate.</li>
* </ul>
*/
public final class InsResolutionCoordinator_v1_0_1_B extends EventListener {
private static final InsResolutionCoordinator_v1_0_1_B INSTANCE = new InsResolutionCoordinator_v1_0_1_B();
private final Object lock = new Object();
private volatile ProtocolBridge bridge;
private volatile ProtocolClient insClient; // from ConnectedToProtocolINSServerEvent
private volatile boolean insConnected;
private volatile Object activeServerConnection;
private volatile String activeInfoName;
private volatile String pendingInfoName;
private InsResolutionCoordinator_v1_0_1_B() {
}
/**
* @return global singleton instance
*/
public static InsResolutionCoordinator_v1_0_1_B get() {
return INSTANCE;
}
/**
* Attaches runtime dependencies.
*
* @param protocolBridge protocol bridge
*/
public synchronized void attach(ProtocolBridge protocolBridge) {
Objects.requireNonNull(protocolBridge, "protocolBridge");
if (this.bridge == null) {
this.bridge = protocolBridge;
return;
}
if (this.bridge == protocolBridge) return;
throw new IllegalStateException("InsResolutionCoordinator runtime already initialized with different instances");
}
/**
* Called by the WEB broker for EVERY outgoing request.
*
* <p>If the requested InfoName is already connected, the current server connection is reused.
* Otherwise an INS query is triggered for the URL host. If INS is not connected yet, the host is stored
* and will be sent once INS connects.</p>
*
* @param infoName host from {@code web://<infoName>/...}
*/
public void requestResolve(String infoName) {
String in = (infoName == null) ? "" : infoName.trim();
if (in.isEmpty()) {
throw new IllegalArgumentException("InfoName is empty");
}
if (canReuseCurrentServerConnection(in)) {
OacWebRequestBroker.get().notifyServerConnected();
return;
}
synchronized (lock) {
pendingInfoName = in;
}
// Unresolved or changed hosts reset the server gate and trigger INS -> connect -> request.
OacWebRequestBroker.get().beginServerConnectAttempt();
// If INS already connected, send immediately
trySendPendingInsNow();
}
/**
* INS is now connected -> we can send pending INS immediately.
*
* @param event ins connected event
*/
@Listener(priority = EventPriority.HIGHEST)
public void onConnectedToIns(ConnectedToProtocolINSServerEvent event) {
Objects.requireNonNull(event, "event");
this.insClient = Objects.requireNonNull(event.getClient(), "event.getClient()");
this.insConnected = true;
// Send pending (if any) now that INS is connected
trySendPendingInsNow();
}
/**
* Receives INS resolution response and starts the WEB server connect attempt.
*
* @param event packet read event
*/
@Listener(priority = EventPriority.HIGHEST)
public void onInsResponse(C_PacketReadEvent event) {
Objects.requireNonNull(event, "event");
if (!(event.getPacket() instanceof INSResponsePacket packet)) return;
final String infoName;
final String hostname;
final int port;
try {
synchronized (lock) {
infoName = pendingInfoName;
}
if (infoName == null || infoName.isBlank()) {
throw new IllegalStateException("INS response received without pending info name");
}
INSResponseStatus status = packet.getStatus();
List<INSRecord> records = packet.getRecords();
if (status != INSResponseStatus.OK) {
throw new IllegalStateException("INS resolution failed: " + status);
}
if (records == null || records.isEmpty() || records.getFirst() == null || records.getFirst().value == null) {
throw new IllegalStateException("INS resolution returned no usable records");
}
String host = records.getFirst().value.trim();
if (host.isEmpty()) {
throw new IllegalStateException("INS record value is empty");
}
if (!host.contains(":")) {
hostname = host;
int recordPort = records.getFirst().port;
port = (recordPort == 0) ? 1028 : recordPort;
} else {
String[] split = host.split(":", 2);
String h = split[0].trim();
String p1 = split[1].trim();
if (h.isEmpty() || p1.isEmpty()) {
throw new IllegalStateException("Invalid INS host:port value: " + host);
}
try {
port = Integer.parseInt(p1);
} catch (NumberFormatException e) {
throw new IllegalStateException("Invalid port in INS record: " + host, e);
}
hostname = h;
}
} catch (Exception e) {
OacWebRequestBroker.get().notifyServerConnectionFailed(e);
return;
}
Thread t = new Thread(() -> connectServer(infoName, hostname, port), "oac-web-server-connect");
t.setDaemon(true);
t.start();
}
/**
* Server connected -> open broker gate.
*
* @param event server connected event
*/
@Listener(priority = EventPriority.HIGHEST)
public void onConnectedToServer(ConnectedToProtocolServerEvent event) {
Objects.requireNonNull(event, "event");
ProtocolClient client = event.getClient();
if (client != null) {
this.activeServerConnection = client.getClientServerConnection();
}
OacWebRequestBroker.get().notifyServerConnected();
}
@Listener(priority = EventPriority.HIGHEST)
public void onDisconnected(ClientDisconnectedEvent event) {
Objects.requireNonNull(event, "event");
Object disconnected = event.getClient();
if (disconnected == null) return;
ProtocolBridge b = this.bridge;
ProtocolClient client = (b == null) ? null : b.getProtocolClient();
if (disconnected == activeServerConnection) {
activeServerConnection = null;
activeInfoName = null;
OacWebRequestBroker.get().notifyServerConnectionFailed(
new IllegalStateException("Server connection was lost")
);
}
if (client != null && disconnected == client.getClientINSConnection()) {
this.insConnected = false;
this.pendingInfoName = null;
}
}
private boolean canReuseCurrentServerConnection(String infoName) {
ProtocolBridge b = this.bridge;
if (b == null) return false;
ProtocolClient client = b.getProtocolClient();
if (client == null || client.getClientServerConnection() == null || !client.getClientServerConnection().isConnected()) {
return false;
}
return infoName.equalsIgnoreCase(activeInfoName);
}
private void trySendPendingInsNow() {
ProtocolClient c = this.insClient;
if (c == null) return;
final String infoName;
synchronized (lock) {
infoName = pendingInfoName;
}
if (infoName == null || infoName.isBlank()) return;
InsParts parts = InsParts.parse(infoName);
try {
c.sendINSQuery(parts.tln(), parts.name(), parts.sub(), INSRecordType.A);
} catch (Exception e) {
OacWebRequestBroker.get().notifyServerConnectionFailed(e);
}
}
private void connectServer(String infoName, String hostname, int port) {
ProtocolBridge b = this.bridge;
if (b == null) return;
if (canReuseCurrentServerConnection(infoName)) {
OacWebRequestBroker.get().notifyServerConnected();
return;
}
try {
ProtocolClient client = b.getProtocolClient();
if (client.getClientServerConnection() != null && client.getClientServerConnection().isConnected()) {
client.getClientServerConnection().disconnect();
}
client.buildServerConnection(
null,
client.getProtocolBridge().getProtocolValues().ssl
);
client.getClientServerConnection().connect(hostname, port);
this.activeServerConnection = client.getClientServerConnection();
this.activeInfoName = infoName;
// Broker gate is opened by ConnectedToProtocolServerEvent.
} catch (Exception e) {
e.printStackTrace();
this.activeServerConnection = null;
this.activeInfoName = null;
OacWebRequestBroker.get().notifyServerConnectionFailed(e);
}
}
/**
* Parses name.tln or sub.name.tln.
*/
public record InsParts(String tln, String name, String sub) {
/**
* Parses an InfoName into INS parts.
*
* @param infoName infoName (name.tln or sub.name.tln)
* @return parts
*/
public static InsParts parse(String infoName) {
String in = Objects.requireNonNull(infoName, "infoName").trim();
if (in.isEmpty()) throw new IllegalArgumentException("infoName is empty");
String[] parts = in.split("\\.");
if (parts.length < 2 || parts.length > 3) {
throw new IllegalArgumentException(
"Invalid INS address format: " + infoName + " (expected name.tln or sub.name.tln)"
);
}
String tln = parts[parts.length - 1];
String name = parts[parts.length - 2];
String sub = (parts.length == 3) ? parts[0] : null;
return new InsParts(tln, name, sub);
}
}
}

View File

@@ -0,0 +1,55 @@
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta;
import org.openautonomousconnection.protocol.urlhandler.v1_0_0.beta.LibClientImpl_v1_0_0_B;
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web.WebFlagInspector;
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web.WebRequestContextProvider;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
import java.util.Map;
/**
* Callback surface for URL-handler related streaming events (v1.0.1-BETA).
*/
public abstract class LibClientImpl_v1_0_1_B extends LibClientImpl_v1_0_0_B implements WebRequestContextProvider, WebFlagInspector {
/**
* Called when a streamed response begins.
*/
public void streamStart(
WebPacketHeader header,
int statusCode,
String contentType,
Map<String, String> headers,
long totalLength
) {
}
/**
* Called for each streamed chunk.
*/
public void streamChunk(
WebPacketHeader header,
int seq,
byte[] data
) {
}
/**
* Called when the stream ends.
*/
public void streamEnd(
WebPacketHeader header,
boolean ok,
String error
) {
}
/**
* Called after full best-effort assembly (only if ok=true).
*/
public void streamFinish(
WebPacketHeader header,
byte[] content
) {
}
}

View File

@@ -0,0 +1,68 @@
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web.OacWebProtocolModule_v1_0_1_B;
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web.OacWebRequestBroker;
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web.WebFlagInspector;
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web.WebRequestContextProvider;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Installs OAC URL protocol modules for v1.0.1-BETA.
*
* <p>Runtime behavior required by this installer:</p>
* <ul>
* <li>Every WEB request triggers an INS resolve attempt (via {@link InsResolutionCoordinator_v1_0_1_B}).</li>
* <li>Once an INS response arrives, the client connects to the resolved server endpoint.</li>
* <li>Only after the server connection is established, WEB packets may be sent (broker gate).</li>
* </ul>
*
* <p>Call once during startup (before creating {@link java.net.URL} instances).</p>
*/
public final class OacUrlHandlerInstaller_v1_0_1_B {
private static final AtomicBoolean INSTALLED = new AtomicBoolean(false);
private OacUrlHandlerInstaller_v1_0_1_B() {
}
/**
* Installs the URL handler package path and registers the WEB module.
*
* @param protocolBridge protocol bridge
* @param impl callback implementation
* @param ctxProvider provides tab/page/frame correlation for each request
* @param flagInspector interprets {@code WebPacketHeader.flags} (e.g. stream bit)
*/
public static void installOnce(
ProtocolBridge protocolBridge,
LibClientImpl_v1_0_1_B impl,
WebRequestContextProvider ctxProvider,
WebFlagInspector flagInspector
) {
Objects.requireNonNull(protocolBridge, "protocolBridge");
Objects.requireNonNull(impl, "impl");
Objects.requireNonNull(ctxProvider, "ctxProvider");
Objects.requireNonNull(flagInspector, "flagInspector");
if (!INSTALLED.compareAndSet(false, true)) return;
ProtocolHandlerPackages.installPackage("org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta");
// WEB module (URLConnection + web packet listener -> broker)
OacWebProtocolModule_v1_0_1_B web = new OacWebProtocolModule_v1_0_1_B(ctxProvider, flagInspector, protocolBridge);
OacUrlProtocolRegistry.register(web);
web.install(protocolBridge, impl);
// INS coordination (request -> send INS -> connect server -> open broker gate)
InsResolutionCoordinator_v1_0_1_B coordinator = InsResolutionCoordinator_v1_0_1_B.get();
coordinator.attach(protocolBridge);
protocolBridge.getProtocolValues().eventManager.registerListener(coordinator);
// Broker needs to wait for "server connected" gate. Coordinator will open/fail this gate.
OacWebRequestBroker.get().attachCoordinator(coordinator);
}
}

View File

@@ -0,0 +1,38 @@
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta;
import org.openautonomousconnection.protocol.ProtocolBridge;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
/**
* Pluggable module for a URL protocol scheme (e.g. "web", "ftp").
*
* <p>Each module is responsible for providing {@link URLConnection} instances for its scheme and
* wiring packet listeners into the {@link ProtocolBridge} runtime.</p>
*/
public interface OacUrlProtocolModule {
/**
* @return the URL scheme handled by this module (e.g. "web", "ftp")
*/
String scheme();
/**
* Opens a connection for the given URL.
*
* @param url the URL
* @return the connection
* @throws IOException if opening fails
*/
URLConnection openConnection(URL url) throws IOException;
/**
* Installs the module into the given protocol bridge (listeners, brokers, etc.).
*
* @param protocolBridge protocol bridge
* @param impl callback implementation
*/
void install(ProtocolBridge protocolBridge, LibClientImpl_v1_0_1_B impl);
}

View File

@@ -0,0 +1,52 @@
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
* Global registry for URL protocol modules.
*/
public final class OacUrlProtocolRegistry {
private static final ConcurrentHashMap<String, OacUrlProtocolModule> MODULES = new ConcurrentHashMap<>();
private OacUrlProtocolRegistry() {
}
/**
* Registers a module for its scheme.
*
* @param module the module
* @throws IllegalStateException if a different module is already registered for the scheme
*/
public static void register(OacUrlProtocolModule module) {
Objects.requireNonNull(module, "module");
String scheme = normalizeScheme(module.scheme());
OacUrlProtocolModule prev = MODULES.putIfAbsent(scheme, module);
if (prev != null && prev != module) {
throw new IllegalStateException(
"Module already registered for scheme '" + scheme + "': " + prev.getClass().getName()
);
}
}
/**
* Returns the module for the given scheme.
*
* @param scheme the scheme
* @return module or null if not registered
*/
public static OacUrlProtocolModule get(String scheme) {
if (scheme == null) return null;
return MODULES.get(normalizeScheme(scheme));
}
private static String normalizeScheme(String scheme) {
String s = Objects.requireNonNull(scheme, "scheme").trim();
if (s.isEmpty()) throw new IllegalArgumentException("scheme is empty");
return s.toLowerCase(Locale.ROOT);
}
}

View File

@@ -0,0 +1,44 @@
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
/**
* Utility to safely append packages to "java.protocol.handler.pkgs".
*
* <p>JVM lookup rule: it searches {@code <pkgprefix>.<protocol>.Handler}.</p>
*/
public final class ProtocolHandlerPackages {
private static final String KEY = "java.protocol.handler.pkgs";
private ProtocolHandlerPackages() {
}
/**
* Appends a package prefix to the protocol handler search path.
*
* @param pkg package prefix (e.g. "org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta")
*/
public static void installPackage(String pkg) {
Objects.requireNonNull(pkg, "pkg");
String p = pkg.trim();
if (p.isEmpty()) return;
String existing = System.getProperty(KEY, "");
Set<String> parts = new LinkedHashSet<>();
if (!existing.isBlank()) {
for (String s : existing.split("\\|")) {
String t = s.trim();
if (!t.isEmpty()) parts.add(t);
}
}
parts.add(p);
String merged = String.join("|", parts);
System.setProperty(KEY, merged);
}
}

View File

@@ -0,0 +1,71 @@
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Objects;
/**
* InputStream wrapper that deletes one or more paths (files/directories) on close.
*
* <p>Useful for temporary downloads/streams that must be cleaned up automatically.</p>
*/
final class DeleteOnCloseInputStream extends FilterInputStream {
private final Path[] deletePaths;
private volatile boolean closed;
DeleteOnCloseInputStream(InputStream in, Path... deletePaths) {
super(Objects.requireNonNull(in, "in"));
this.deletePaths = (deletePaths == null) ? new Path[0] : deletePaths.clone();
}
@Override
public void close() throws IOException {
if (closed) return;
closed = true;
IOException io = null;
try {
super.close();
} catch (IOException e) {
io = e;
}
for (Path p : deletePaths) {
if (p == null) continue;
try {
deleteRecursivelyIfExists(p);
} catch (IOException e) {
if (io == null) io = e;
}
}
if (io != null) throw io;
}
private static void deleteRecursivelyIfExists(Path path) throws IOException {
if (!Files.exists(path)) return;
if (Files.isDirectory(path)) {
Files.walkFileTree(path, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.deleteIfExists(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.deleteIfExists(dir);
return FileVisitResult.CONTINUE;
}
});
return;
}
Files.deleteIfExists(path);
}
}

View File

@@ -0,0 +1,26 @@
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web;
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.OacUrlProtocolModule;
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.OacUrlProtocolRegistry;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
/**
* URLStreamHandler for the "web" protocol.
*
* <p>Loaded by JVM via {@code java.protocol.handler.pkgs} and delegates to the registered module.</p>
*/
public final class Handler extends URLStreamHandler {
@Override
protected URLConnection openConnection(URL u) throws IOException {
OacUrlProtocolModule module = OacUrlProtocolRegistry.get("web");
if (module == null) {
throw new IOException("No module registered for scheme 'web'. Did you call OacUrlHandlerInstaller_v1_0_1_B.installOnce(...)?");
}
return module.openConnection(u);
}
}

View File

@@ -0,0 +1,26 @@
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web;
/**
* Stores the current session token across multiple URLConnection instances.
*/
public final class OacSessionJar {
private volatile String session;
/**
* Stores a session token (e.g. from response header "session").
*
* @param session session token
*/
public void store(String session) {
String s = (session == null) ? null : session.trim();
this.session = (s == null || s.isEmpty()) ? null : s;
}
/**
* @return stored session token or null
*/
public String get() {
return session;
}
}

View File

@@ -0,0 +1,274 @@
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web;
import org.openautonomousconnection.protocol.ProtocolBridge;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URL;
import java.util.*;
/**
* HttpURLConnection implementation that maps "web://" URLs to WEB v1.0.1 protocol requests.
*
* <p>This implementation is designed for JavaFX WebView semantics.</p>
*/
public final class OacWebHttpURLConnection extends HttpURLConnection {
private static final int MAX_REDIRECTS = 8;
private final OacWebRequestBroker broker;
private final OacSessionJar sessionJar;
private final WebRequestContextProvider ctxProvider;
private final ProtocolBridge bridge;
private final Map<String, String> requestHeaders = new LinkedHashMap<>();
private final ByteArrayOutputStream requestBody = new ByteArrayOutputStream(1024);
private boolean requestSent;
private OacWebResponse response;
public OacWebHttpURLConnection(URL url, OacWebRequestBroker broker, OacSessionJar sessionJar, WebRequestContextProvider ctxProvider, ProtocolBridge protocolBridge) {
super(url);
this.broker = Objects.requireNonNull(broker, "broker");
this.sessionJar = Objects.requireNonNull(sessionJar, "sessionJar");
this.ctxProvider = Objects.requireNonNull(ctxProvider, "ctxProvider");
this.bridge = Objects.requireNonNull(protocolBridge, "bridge");
this.method = "GET";
setInstanceFollowRedirects(true);
}
private static String headerValue(Map<String, String> headers, String nameLower) {
if (headers == null || headers.isEmpty() || nameLower == null) return null;
String needle = nameLower.trim().toLowerCase(Locale.ROOT);
for (Map.Entry<String, String> e : headers.entrySet()) {
if (e.getKey() == null) continue;
if (e.getKey().trim().toLowerCase(Locale.ROOT).equals(needle)) {
return e.getValue();
}
}
return null;
}
@Override
public void setRequestProperty(String key, String value) {
if (key == null) return;
if (requestSent) throw new IllegalStateException("Request already sent");
if (value == null) requestHeaders.remove(key);
else requestHeaders.put(key, value);
}
@Override
public String getRequestProperty(String key) {
if (key == null) return null;
for (Map.Entry<String, String> e : requestHeaders.entrySet()) {
if (e.getKey() != null && e.getKey().equalsIgnoreCase(key)) return e.getValue();
}
return null;
}
@Override
public Map<String, List<String>> getRequestProperties() {
Map<String, List<String>> out = new LinkedHashMap<>();
for (Map.Entry<String, String> e : requestHeaders.entrySet()) {
out.put(e.getKey(), e.getValue() == null ? List.of() : List.of(e.getValue()));
}
return Collections.unmodifiableMap(out);
}
@Override
public void setRequestMethod(String method) throws ProtocolException {
if (method == null) throw new ProtocolException("method is null");
if (requestSent) throw new ProtocolException("Request already sent");
String m = method.trim().toUpperCase(Locale.ROOT);
if (!m.equals("GET") && !m.equals("POST")) {
throw new ProtocolException("Unsupported method: " + method);
}
this.method = m;
}
@Override
public OutputStream getOutputStream() {
if (requestSent) throw new IllegalStateException("Request already sent");
setDoOutput(true);
return requestBody;
}
@Override
public void connect() {
// Intentionally no-op: JavaFX WebView may call connect() before writing POST body.
}
private void ensureResponse() throws IOException {
if (requestSent) return;
URL cur = this.url;
String methodStr = (this.method == null) ? "GET" : this.method.trim().toUpperCase(Locale.ROOT);
Map<String, String> carryHeaders = new LinkedHashMap<>(requestHeaders);
String session = sessionJar.get();
if (session != null && !session.isBlank() && headerValue(carryHeaders, "session") == null) {
carryHeaders.put("session", session);
}
byte[] carryBody = null;
if (getDoOutput()) {
carryBody = requestBody.toByteArray();
if ("POST".equals(methodStr) && carryBody == null) carryBody = new byte[0];
}
if ("POST".equals(methodStr) && headerValue(carryHeaders, "content-type") == null) {
carryHeaders.put("content-type", "application/x-www-form-urlencoded; charset=utf-8");
}
OacWebResponse resp = null;
for (int i = 0; i <= MAX_REDIRECTS; i++) {
WebRequestContextProvider.WebRequestContext ctx = ctxProvider.contextFor(cur);
resp = broker.fetch(
cur,
methodStr,
carryHeaders,
carryBody,
ctx.tabId(),
ctx.pageId(),
ctx.frameId(), bridge
);
String newSession = broker.extractSession(resp.headers());
if (newSession != null && !newSession.isBlank()) {
sessionJar.store(newSession);
carryHeaders.put("session", newSession);
}
int code = resp.statusCode();
if (!getInstanceFollowRedirects()) break;
if (code == 301 || code == 302 || code == 303 || code == 307 || code == 308) {
String loc = headerValue(resp.headers(), "location");
if (loc == null || loc.isBlank()) break;
try {
cur = new URL(cur, loc);
} catch (Exception ex) {
break;
}
if (code == 303) {
methodStr = "GET";
carryBody = null;
} else if ((code == 301 || code == 302) && "POST".equals(methodStr)) {
methodStr = "GET";
carryBody = null;
}
continue;
}
break;
}
this.response = resp;
this.requestSent = true;
}
@Override
public InputStream getInputStream() throws IOException {
ensureResponse();
if (response == null) return new ByteArrayInputStream(new byte[0]);
return response.bodyStream();
}
@Override
public InputStream getErrorStream() {
try {
ensureResponse();
if (response == null) return null;
return (response.statusCode() >= 400) ? response.bodyStream() : null;
} catch (IOException e) {
return null;
}
}
@Override
public int getResponseCode() throws IOException {
ensureResponse();
return response == null ? -1 : response.statusCode();
}
@Override
public String getContentType() {
try {
ensureResponse();
} catch (IOException e) {
return "application/octet-stream";
}
String ct = (response == null) ? null : response.contentType();
return (ct == null || ct.isBlank()) ? "application/octet-stream" : ct;
}
@Override
public int getContentLength() {
try {
ensureResponse();
} catch (IOException e) {
return -1;
}
long len = (response == null) ? -1L : response.contentLength();
return (len <= 0 || len > Integer.MAX_VALUE) ? -1 : (int) len;
}
@Override
public long getContentLengthLong() {
try {
ensureResponse();
} catch (IOException e) {
return -1L;
}
return (response == null) ? -1L : response.contentLength();
}
@Override
public Map<String, List<String>> getHeaderFields() {
try {
ensureResponse();
} catch (IOException e) {
return Map.of();
}
if (response == null) return Map.of();
Map<String, List<String>> out = new LinkedHashMap<>();
for (Map.Entry<String, String> e : response.headers().entrySet()) {
String k = e.getKey();
String v = e.getValue();
if (k == null) continue;
out.put(k, v == null ? List.of() : List.of(v));
}
return out;
}
@Override
public String getHeaderField(String name) {
if (name == null) return null;
try {
ensureResponse();
} catch (IOException e) {
return null;
}
if (response == null) return null;
return headerValue(response.headers(), name.trim().toLowerCase(Locale.ROOT));
}
@Override
public void disconnect() {
// No persistent socket owned by this object.
}
@Override
public boolean usingProxy() {
return false;
}
}

View File

@@ -0,0 +1,71 @@
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web;
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
import dev.unlegitdqrk.unlegitlibrary.event.EventPriority;
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.C_PacketReadEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamChunkPacket_v1_0_1_B;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamEndPacket_v1_0_1_B;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamStartPacket_v1_0_1_B;
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.LibClientImpl_v1_0_1_B;
import java.util.Map;
import java.util.Objects;
/**
* Receives incoming WEB packets and forwards them into the broker and client callbacks.
*/
public final class OacWebPacketListener extends EventListener {
private final OacWebRequestBroker broker;
private final LibClientImpl_v1_0_1_B impl;
/**
* Creates a listener bound to the given broker.
*
* @param broker broker instance
* @param impl callback implementation
*/
public OacWebPacketListener(OacWebRequestBroker broker, LibClientImpl_v1_0_1_B impl) {
this.broker = Objects.requireNonNull(broker, "broker");
this.impl = Objects.requireNonNull(impl, "impl");
}
@Listener(priority = EventPriority.HIGHEST)
public void onPacketRead(C_PacketReadEvent event) {
Packet p = event.getPacket();
if (p instanceof WebResourceResponsePacket resp) {
broker.onResourceResponse(resp);
return;
}
if (p instanceof WebStreamStartPacket_v1_0_1_B start) {
impl.streamStart(
start.getHeader(),
start.getStatusCode(),
start.getContentType(),
start.getHeaders() == null ? Map.of() : start.getHeaders(),
start.getTotalLength()
);
broker.onStreamStart(start);
return;
}
if (p instanceof WebStreamChunkPacket_v1_0_1_B chunk) {
byte[] data = (chunk.getData() == null) ? new byte[0] : chunk.getData();
impl.streamChunk(chunk.getHeader(), chunk.getSeq(), data);
broker.onStreamChunk(chunk);
return;
}
if (p instanceof WebStreamEndPacket_v1_0_1_B end) {
impl.streamEnd(end.getHeader(), end.isOk(), end.getError());
byte[] full = broker.onStreamEndAndAssemble(end);
if (full != null) {
impl.streamFinish(end.getHeader(), full);
}
}
}
}

View File

@@ -0,0 +1,53 @@
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.LibClientImpl_v1_0_1_B;
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.OacUrlProtocolModule;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Objects;
/**
* WEB protocol module implementation for v1.0.1-BETA.
*/
public final class OacWebProtocolModule_v1_0_1_B implements OacUrlProtocolModule {
private final OacSessionJar sessionJar = new OacSessionJar();
private final WebRequestContextProvider ctxProvider;
private final WebFlagInspector flagInspector;
private final ProtocolBridge bridge;
public OacWebProtocolModule_v1_0_1_B(WebRequestContextProvider ctxProvider, WebFlagInspector flagInspector, ProtocolBridge protocolBridge) {
this.ctxProvider = Objects.requireNonNull(ctxProvider, "ctxProvider");
this.flagInspector = Objects.requireNonNull(flagInspector, "flagInspector");
this.bridge = Objects.requireNonNull(protocolBridge, "protocolBridge");
}
@Override
public String scheme() {
return "web";
}
@Override
public URLConnection openConnection(URL url) throws IOException {
Objects.requireNonNull(url, "url");
if (!"web".equalsIgnoreCase(url.getProtocol())) {
throw new IOException("Unsupported scheme for this module: " + url.getProtocol());
}
return new OacWebHttpURLConnection(url, OacWebRequestBroker.get(), sessionJar, ctxProvider, bridge);
}
@Override
public void install(ProtocolBridge protocolBridge, LibClientImpl_v1_0_1_B impl) {
Objects.requireNonNull(protocolBridge, "protocolBridge");
Objects.requireNonNull(impl, "impl");
OacWebRequestBroker.get().attachRuntime(protocolBridge.getProtocolClient(), flagInspector);
protocolBridge.getProtocolValues().eventManager.registerListener(
new OacWebPacketListener(OacWebRequestBroker.get(), impl)
);
}
}

View File

@@ -0,0 +1,614 @@
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceRequestPacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamChunkPacket_v1_0_1_B;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamEndPacket_v1_0_1_B;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamStartPacket_v1_0_1_B;
import org.openautonomousconnection.protocol.side.client.ProtocolClient;
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.InsResolutionCoordinator_v1_0_1_B;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketFlags;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
import java.io.*;
import java.net.URL;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
/**
* Multi-flight broker for WEB v1.0.1-BETA with best-effort streaming and temp-file assembly.
*
* <p><b>Gate behavior:</b> Before any WEB packet is sent, the broker triggers INS resolve (every request),
* then blocks until server connection is established (opened by {@link #notifyServerConnected()}).</p>
*/
public final class OacWebRequestBroker {
private static final OacWebRequestBroker INSTANCE = new OacWebRequestBroker();
private static final long RESPONSE_TIMEOUT_SECONDS = 30;
private static final long CONNECT_TIMEOUT_SECONDS = 30;
private final ConcurrentHashMap<Long, ResponseState> inFlight = new ConcurrentHashMap<>();
private final AtomicLong requestCounter = new AtomicLong(1);
private final Object connectLock = new Object();
private volatile CompletableFuture<Void> serverReady = new CompletableFuture<>();
private volatile Throwable serverFailure;
private volatile ProtocolClient client;
private volatile WebFlagInspector flagInspector;
private volatile InsResolutionCoordinator_v1_0_1_B coordinator;
private OacWebRequestBroker() {
}
/**
* @return global singleton instance
*/
public static OacWebRequestBroker get() {
return INSTANCE;
}
/**
* Attaches runtime dependencies required for dispatching requests and interpreting flags.
*
* @param client protocol client used to send packets
* @param flagInspector inspector for header flags (STREAM bit)
*/
public synchronized void attachRuntime(ProtocolClient client, WebFlagInspector flagInspector) {
Objects.requireNonNull(client, "client");
Objects.requireNonNull(flagInspector, "flagInspector");
if (this.client == null && this.flagInspector == null) {
this.client = client;
this.flagInspector = flagInspector;
return;
}
if (this.client == client && this.flagInspector == flagInspector) return;
throw new IllegalStateException("OacWebRequestBroker runtime already initialized with different instances");
}
/**
* Attaches the INS coordinator used for per-request INS resolution.
*
* @param coordinator coordinator
*/
public synchronized void attachCoordinator(InsResolutionCoordinator_v1_0_1_B coordinator) {
Objects.requireNonNull(coordinator, "coordinator");
if (this.coordinator == null) {
this.coordinator = coordinator;
return;
}
if (this.coordinator == coordinator) return;
throw new IllegalStateException("Coordinator already attached with different instance");
}
/**
* Resets the server connection gate to "not ready".
*/
public void beginServerConnectAttempt() {
synchronized (connectLock) {
this.serverFailure = null;
this.serverReady = new CompletableFuture<>();
}
}
/**
* Opens the server connection gate.
*/
public void notifyServerConnected() {
synchronized (connectLock) {
this.serverFailure = null;
CompletableFuture<Void> f = this.serverReady;
if (!f.isDone()) f.complete(null);
}
}
/**
* Fails the server connection gate and unblocks waiters.
*
* @param t failure cause
*/
public void notifyServerConnectionFailed(Throwable t) {
Objects.requireNonNull(t, "t");
synchronized (connectLock) {
this.serverFailure = t;
CompletableFuture<Void> f = this.serverReady;
if (!f.isDone()) f.completeExceptionally(t);
}
}
private void awaitServerConnection() throws IOException {
ProtocolClient c = this.client;
if (c == null) {
throw new IllegalStateException("ProtocolClient not attached. Call OacWebRequestBroker.attachRuntime(...) during install.");
}
if (c.getClientServerConnection() != null && c.getClientServerConnection().isConnected()) {
return;
}
CompletableFuture<Void> f = this.serverReady;
try {
f.get(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
} catch (TimeoutException e) {
throw new IOException("Timeout waiting for server connection after " + CONNECT_TIMEOUT_SECONDS + "s", e);
} catch (ExecutionException e) {
Throwable cause = (e.getCause() == null) ? e : e.getCause();
throw new IOException("Server connection failed: " + cause.getMessage(), cause);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IOException("Interrupted while waiting for server connection", e);
}
if (c.getClientServerConnection() == null || !c.getClientServerConnection().isConnected()) {
Throwable fail = this.serverFailure;
if (fail != null) throw new IOException("Server connection failed: " + fail.getMessage(), fail);
throw new IOException("Server gate opened but connection is not connected");
}
}
/**
* Sends a resource request and blocks until completion.
*
* <p><b>Required flow:</b></p>
* <ol>
* <li>Trigger INS query for URL host (EVERY request).</li>
* <li>Wait for INS response -> connect -> server connected gate.</li>
* <li>Send WEB request packet.</li>
* </ol>
*/
public OacWebResponse fetch(
URL url,
String method,
Map<String, String> headers,
byte[] body,
long tabId,
long pageId,
long frameId, ProtocolBridge bridge
) throws IOException {
Objects.requireNonNull(url, "url");
String host = url.getHost();
if (host == null || host.isBlank()) {
throw new IOException("Missing InfoName in URL host: " + url);
}
InsResolutionCoordinator_v1_0_1_B coord = this.coordinator;
if (coord == null) {
throw new IllegalStateException("INS coordinator not attached. Ensure installer was called.");
}
// Requirement: trigger INS on EVERY request.
coord.requestResolve(host);
// Block until server is connected
awaitServerConnection();
ProtocolClient c = this.client;
if (c == null) {
throw new IllegalStateException("ProtocolClient not attached. Call OacWebRequestBroker.attachRuntime(...) during install.");
}
String m = (method == null || method.isBlank()) ? "GET" : method.trim().toUpperCase(Locale.ROOT);
long requestId = requestCounter.getAndIncrement();
int flags = WebPacketFlags.RESOURCE;
WebPacketHeader header = new WebPacketHeader(
requestId,
tabId,
pageId,
frameId,
flags,
System.currentTimeMillis()
);
ResponseState st = new ResponseState(requestId);
inFlight.put(requestId, st);
Map<String, String> safeHeaders = (headers == null) ? Map.of() : new LinkedHashMap<>(headers);
byte[] safeBody = (body == null) ? new byte[0] : body;
WebResourceRequestPacket packet = new WebResourceRequestPacket(
header,
url.toString(),
m,
safeHeaders,
safeBody,
safeHeaders.get("content-type"),
null,
null, bridge
);
try {
if (c.getClientServerConnection() == null || !c.getClientServerConnection().isConnected()) {
inFlight.remove(requestId);
throw new IOException("ServerConnection is not connected");
}
c.getClientServerConnection().sendPacket(packet, TransportProtocol.TCP);
} catch (Exception e) {
inFlight.remove(requestId);
throw new IOException("Failed to send WebResourceRequestPacket", e);
}
return awaitAndBuildResponse(st);
}
public String extractSession(Map<String, String> headers) {
return headerValue(headers, "session");
}
public void onResourceResponse(WebResourceResponsePacket p) {
if (p == null || p.getHeader() == null) return;
ResponseState st = inFlight.get(p.getHeader().getRequestId());
if (st == null) return;
synchronized (st.lock) {
if (st.completed) return;
st.statusCode = p.getStatusCode();
st.contentType = safeContentType(p.getContentType());
st.headers = safeHeaders(p.getHeaders());
boolean stream = false;
WebFlagInspector inspector = this.flagInspector;
if (inspector != null) {
stream = inspector.isStream(p.getHeader());
}
if (!stream) {
byte[] b = (p.getBody() == null) ? new byte[0] : p.getBody();
st.memoryBody = b;
st.success = true;
st.completed = true;
st.done.countDown();
return;
}
st.streamExpected = true;
}
}
public void onStreamStart(WebStreamStartPacket_v1_0_1_B p) {
if (p == null || p.getHeader() == null) return;
ResponseState st = inFlight.get(p.getHeader().getRequestId());
if (st == null) return;
synchronized (st.lock) {
if (st.completed) return;
st.statusCode = p.getStatusCode();
st.contentType = safeContentType(p.getContentType());
st.headers = safeHeaders(p.getHeaders());
st.totalLength = p.getTotalLength();
st.streamExpected = true;
if (st.spooler == null) {
try {
st.spooler = TempChunkSpooler.create(st.requestId);
st.spoolDir = st.spooler.dir();
} catch (IOException e) {
failLocked(st, "Failed to create stream spooler: " + e.getMessage());
}
}
}
}
public void onStreamChunk(WebStreamChunkPacket_v1_0_1_B p) {
if (p == null || p.getHeader() == null) return;
ResponseState st = inFlight.get(p.getHeader().getRequestId());
if (st == null) return;
int seq = p.getSeq();
if (seq < 0) return;
byte[] data = (p.getData() == null) ? new byte[0] : p.getData();
if (data.length == 0) return;
synchronized (st.lock) {
if (st.completed) return;
st.streamExpected = true;
if (st.spooler == null) {
try {
st.spooler = TempChunkSpooler.create(st.requestId);
st.spoolDir = st.spooler.dir();
} catch (IOException e) {
failLocked(st, "Failed to create stream spooler: " + e.getMessage());
return;
}
}
try {
st.spooler.writeChunk(seq, data);
if (seq > st.maxSeqSeen) st.maxSeqSeen = seq;
} catch (IOException e) {
failLocked(st, "Failed to spool stream chunk: " + e.getMessage());
}
}
}
public byte[] onStreamEndAndAssemble(WebStreamEndPacket_v1_0_1_B p) {
if (p == null || p.getHeader() == null) return null;
ResponseState st = inFlight.get(p.getHeader().getRequestId());
if (st == null) return null;
synchronized (st.lock) {
if (st.completed) return null;
st.success = p.isOk();
st.errorMessage = p.getError();
if (!st.success) {
st.completed = true;
st.done.countDown();
return null;
}
if (st.spooler == null) {
st.finalFile = null;
st.completed = true;
st.done.countDown();
return new byte[0];
}
st.spoolDir = st.spooler.dir();
try {
st.finalFile = st.spooler.assembleBestEffort(st.maxSeqSeen);
} catch (IOException e) {
failLocked(st, "Failed to assemble stream: " + e.getMessage());
return null;
} finally {
try {
st.spooler.close();
} catch (IOException ignored) {
}
}
st.completed = true;
st.done.countDown();
if (st.finalFile == null) return new byte[0];
try (InputStream in = Files.newInputStream(st.finalFile)) {
return in.readAllBytes();
} catch (IOException e) {
return null;
}
}
}
private OacWebResponse awaitAndBuildResponse(ResponseState st) throws IOException {
try {
if (!st.done.await(RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
removeAndCleanup(st);
throw new IOException("Timeout waiting for web response (requestId=" + st.requestId + ")");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
removeAndCleanup(st);
throw new IOException("Interrupted while waiting for web response (requestId=" + st.requestId + ")", e);
}
inFlight.remove(st.requestId);
synchronized (st.lock) {
if (!st.success) {
cleanupLocked(st);
String msg = (st.errorMessage == null || st.errorMessage.isBlank()) ? "Request failed" : st.errorMessage;
throw new IOException(msg);
}
if (!st.streamExpected && st.finalFile == null) {
byte[] b = (st.memoryBody == null) ? new byte[0] : st.memoryBody;
return new OacWebResponse(
st.statusCode,
safeContentType(st.contentType),
OacWebResponse.safeHeaders(st.headers),
new ByteArrayInputStream(b),
b.length
);
}
if (st.finalFile == null) {
return new OacWebResponse(
st.statusCode,
safeContentType(st.contentType),
OacWebResponse.safeHeaders(st.headers),
new ByteArrayInputStream(new byte[0]),
0L
);
}
InputStream fileIn = Files.newInputStream(st.finalFile, StandardOpenOption.READ);
return new OacWebResponse(
st.statusCode,
safeContentType(st.contentType),
OacWebResponse.safeHeaders(st.headers),
new DeleteOnCloseInputStream(fileIn, st.finalFile, st.spoolDir),
safeFileSize(st.finalFile)
);
}
}
private void removeAndCleanup(ResponseState st) {
inFlight.remove(st.requestId);
synchronized (st.lock) {
cleanupLocked(st);
}
}
private static void cleanupLocked(ResponseState st) {
if (st.spooler != null) {
try {
st.spooler.close();
} catch (IOException ignored) {
}
st.spooler = null;
}
if (st.finalFile != null) {
try {
Files.deleteIfExists(st.finalFile);
} catch (IOException ignored) {
}
st.finalFile = null;
}
if (st.spoolDir != null) {
try {
if (Files.exists(st.spoolDir)) {
try (DirectoryStream<Path> ds = Files.newDirectoryStream(st.spoolDir)) {
for (Path p : ds) {
try {
Files.deleteIfExists(p);
} catch (IOException ignored2) {
}
}
}
try {
Files.deleteIfExists(st.spoolDir);
} catch (IOException ignored2) {
}
}
} catch (IOException ignored) {
}
st.spoolDir = null;
}
}
private static void failLocked(ResponseState st, String message) {
st.success = false;
st.errorMessage = message;
st.completed = true;
st.done.countDown();
}
private static long safeFileSize(Path p) {
try {
return Files.size(p);
} catch (IOException e) {
return -1L;
}
}
private static String safeContentType(String ct) {
return (ct == null || ct.isBlank()) ? "application/octet-stream" : ct;
}
private static Map<String, String> safeHeaders(Map<String, String> headers) {
return (headers == null || headers.isEmpty()) ? Map.of() : Map.copyOf(headers);
}
private static String headerValue(Map<String, String> headers, String nameLower) {
if (headers == null || headers.isEmpty() || nameLower == null) return null;
String needle = nameLower.trim().toLowerCase(Locale.ROOT);
for (Map.Entry<String, String> e : headers.entrySet()) {
if (e.getKey() == null) continue;
if (e.getKey().trim().toLowerCase(Locale.ROOT).equals(needle)) {
return e.getValue();
}
}
return null;
}
private static final class ResponseState {
private final long requestId;
private final Object lock = new Object();
private final CountDownLatch done = new CountDownLatch(1);
private int statusCode = 0;
private String contentType = "application/octet-stream";
private Map<String, String> headers = Map.of();
private boolean streamExpected;
private long totalLength = -1L;
private int maxSeqSeen = -1;
private boolean completed;
private boolean success;
private String errorMessage;
private byte[] memoryBody;
private TempChunkSpooler spooler;
private Path spoolDir;
private Path finalFile;
private ResponseState(long requestId) {
this.requestId = requestId;
}
}
private static final class TempChunkSpooler implements Closeable {
private final Path dir;
private TempChunkSpooler(Path dir) {
this.dir = dir;
}
static TempChunkSpooler create(long requestId) throws IOException {
Path dir = Files.createTempDirectory("oac-web-stream-" + requestId + "-");
return new TempChunkSpooler(dir);
}
void writeChunk(int seq, byte[] data) throws IOException {
Path p = dir.resolve(seq + ".chunk");
Files.write(p, data, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
}
Path assembleBestEffort(int maxSeqSeen) throws IOException {
Path out = Files.createTempFile("oac-web-stream-final-", ".bin");
try (OutputStream os = Files.newOutputStream(out, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) {
for (int seq = 0; seq <= maxSeqSeen; seq++) {
Path p = dir.resolve(seq + ".chunk");
if (!Files.exists(p)) continue;
try (InputStream in = Files.newInputStream(p, StandardOpenOption.READ)) {
in.transferTo(os);
}
Files.deleteIfExists(p);
}
}
return out;
}
Path dir() {
return dir;
}
@Override
public void close() throws IOException {
if (!Files.exists(dir)) return;
try (DirectoryStream<Path> ds = Files.newDirectoryStream(dir)) {
for (Path p : ds) {
try {
Files.deleteIfExists(p);
} catch (IOException ignored) {
}
}
}
try {
Files.deleteIfExists(dir);
} catch (IOException ignored) {
}
}
}
}

View File

@@ -0,0 +1,32 @@
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web;
import java.io.InputStream;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
/**
* Represents a resolved web response for the JavaFX WebView.
*
* @param statusCode HTTP-like status code
* @param contentType response content-type
* @param headers response headers
* @param bodyStream body stream
* @param contentLength content length if known, else -1
*/
public record OacWebResponse(
int statusCode,
String contentType,
Map<String, String> headers,
InputStream bodyStream,
long contentLength
) {
public OacWebResponse {
Objects.requireNonNull(headers, "headers");
Objects.requireNonNull(bodyStream, "bodyStream");
}
public static Map<String, String> safeHeaders(Map<String, String> h) {
return (h == null) ? Collections.emptyMap() : Collections.unmodifiableMap(h);
}
}

View File

@@ -0,0 +1,30 @@
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketFlags;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
import java.util.Objects;
/**
* Interprets {@link WebPacketHeader#getFlags()} into semantic booleans.
*/
public interface WebFlagInspector {
/**
* @param header web packet header
* @return true if the packet is part of a streamed body transfer
*/
boolean isStream(WebPacketHeader header);
/**
* Default implementation based on {@link WebPacketFlags#STREAM}.
*/
final class Default implements WebFlagInspector {
@Override
public boolean isStream(WebPacketHeader header) {
Objects.requireNonNull(header, "header");
return WebPacketFlags.has(header.getFlags(), WebPacketFlags.STREAM);
}
}
}

View File

@@ -0,0 +1,37 @@
package org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web;
import java.net.URL;
/**
* Provides per-request WEB correlation context (tab/page/frame).
*/
public interface WebRequestContextProvider {
/**
* Returns the context to use for this URL request.
*
* @param url requested URL
* @return context (never null)
*/
WebRequestContext contextFor(URL url);
/**
* Immutable context DTO.
*
* @param tabId stable tab id
* @param pageId navigation instance id
* @param frameId frame id (0 = main frame)
*/
record WebRequestContext(long tabId, long pageId, long frameId) {
}
/**
* Default context provider (tab/page/frame = 0).
*/
final class Default implements WebRequestContextProvider {
@Override
public WebRequestContext contextFor(URL url) {
return new WebRequestContext(0L, 0L, 0L);
}
}
}

View File

@@ -12,14 +12,20 @@ import java.util.List;
*/
public enum ProtocolVersion implements Serializable {
/**
* Support for old OAC-Project => <a href="https://repo.open-autonomous-connection.org/Open-Autonomous-Connection/">*_old</a>
* For classic OAC-Project => <a href="https://repo.open-autonomous-connection.org/Open-Autonomous-Connection/">"classic"-branch</a>
*/
@Deprecated(forRemoval = false, since = "1.0.1-BETA.0.1")
PV_1_0_0_CLASSIC("1.0.0", ProtocolType.CLASSIC, ProtocolSide.WEB_INS, List.of(Protocol.HTTP)),
/**
* First Beta Version of OAC-Protocol
* Version {@link ProtocolVersion#PV_1_0_0_BETA} has many bugs and does not work as expected (occurred by incompleted packets).
* Use {@link ProtocolVersion#PV_1_0_1_BETA} or newer.
*/
PV_1_0_0_BETA("1.0.0", ProtocolType.BETA, ProtocolSide.ALL, List.of(Protocol.OAC));
@Deprecated(forRemoval = false, since = "1.0.1-BETA.0.1")
PV_1_0_0_BETA("1.0.0", ProtocolType.BETA, ProtocolSide.ALL, List.of(Protocol.OAC)),
PV_1_0_1_BETA("1.0.1", ProtocolType.BETA, ProtocolSide.ALL, List.of(Protocol.OAC), PV_1_0_0_BETA),
;
/**
* The version string of the protocol version.

View File

@@ -0,0 +1,172 @@
package org.openautonomousconnection.protocol.versions.v1_0_0.beta;
import org.openautonomousconnection.protocol.side.ins.ProtocolINSServer;
import java.util.*;
/**
* Utility methods for working with {@link INSRecord} instances.
* <p>
* Features:
* <ul>
* <li>Recursive CNAME resolution with loop protection</li>
* <li>Basic record validation helpers</li>
* </ul>
*/
public final class INSRecordTools {
/**
* Maximum number of CNAME hops before resolution is aborted.
*/
public static final int MAX_CNAME_DEPTH = 10;
/**
* Resolves records for the given name and type, following CNAME chains if necessary.
* <p>
* Resolution strategy:
* <ol>
* <li>Try to resolve the requested {@code type} directly via {@link ProtocolINSServer#resolve}</li>
* <li>If no records are found:
* <ul>
* <li>Resolve {@link INSRecordType#CNAME} for the same (tln, name, sub)</li>
* <li>For each CNAME target, follow the chain up to {@link #MAX_CNAME_DEPTH}</li>
* </ul>
* </li>
* <li>Return the first non-empty resolution result</li>
* </ol>
*
* @param server The INS server implementation to use for lookups.
* @param tln Top-level-name.
* @param name InfoName.
* @param sub Optional sub-name, may be {@code null}.
* @param type Desired record type.
* @return A list of resolved records. May be empty if nothing could be resolved.
*/
public static List<INSRecord> resolveWithCNAME(ProtocolINSServer server,
String tln,
String name,
String sub,
INSRecordType type) {
// If we explicitly ask for CNAME, just return raw CNAME records
if (type == INSRecordType.CNAME) return server.resolve(tln, name, sub, INSRecordType.CNAME);
// Try direct resolution first
List<INSRecord> direct = server.resolve(tln, name, sub, type);
if (!direct.isEmpty()) return direct;
// Otherwise follow CNAME chain
Set<String> visited = new HashSet<>();
return followCNAME(server, tln, name, sub, type, 0, visited);
}
private static List<INSRecord> followCNAME(ProtocolINSServer server, String tln, String name, String sub, INSRecordType targetType, int depth, Set<String> visited) {
if (depth > MAX_CNAME_DEPTH) {
server.getProtocolBridge().getProtocolValues().logger.warn("Max CNAME depth exceeded for " + fqdn(tln, name, sub));
return Collections.emptyList();
}
String key = fqdn(tln, name, sub);
if (!visited.add(key)) {
// Loop detected
server.getProtocolBridge().getProtocolValues().logger.warn("CNAME loop detected for " + key);
return Collections.emptyList();
}
List<INSRecord> cnames = server.resolve(tln, name, sub, INSRecordType.CNAME);
if (cnames.isEmpty()) return Collections.emptyList();
for (INSRecord cname : cnames) {
if (!isValidRecord(cname)) continue;
// Target might be "host.tln" or full "sub.name.tln"
ParsedName target = parseTargetName(cname.value);
if (target == null) continue;
List<INSRecord> resolved = server.resolve(target.tln, target.name, target.sub, targetType);
if (!resolved.isEmpty()) return resolved;
// Try next CNAME hop
List<INSRecord> deeper = followCNAME(server, target.tln, target.name, target.sub, targetType, depth + 1, visited);
if (!deeper.isEmpty()) return deeper;
}
return Collections.emptyList();
}
private static String fqdn(String tln, String name, String sub) {
if (sub == null || sub.isEmpty()) return name + "." + tln;
return sub + "." + name + "." + tln;
}
/**
* Parses a CNAME target into (tln, name, sub) assuming the last label is the TLN
* and the one before that is the InfoName.
*
* @param target FQDN string.
* @return ParsedName or {@code null} if it cannot be parsed.
*/
private static ParsedName parseTargetName(String target) {
if (target == null) return null;
String hostname = target.trim();
if (hostname.endsWith(".")) hostname = hostname.substring(0, hostname.length() - 1);
String[] parts = hostname.split("\\.");
if (parts.length < 2) return null;
String tln = parts[parts.length - 1];
String name = parts[parts.length - 2];
String sub = null;
if (parts.length > 2) sub = String.join(".", Arrays.copyOfRange(parts, 0, parts.length - 2));
return new ParsedName(tln, name, sub);
}
/**
* Performs basic validation on a single {@link INSRecord}.
* <p>
* Checks:
* <ul>
* <li>type is non-null</li>
* <li>value is non-null and non-empty</li>
* <li>port is in range 065535</li>
* <li>ttl is non-negative</li>
* </ul>
*
* @param record The record to validate.
* @return {@code true} if the record passes all checks, {@code false} otherwise.
*/
public static boolean isValidRecord(INSRecord record) {
if (record == null) return false;
if (record.type == null) return false;
if (record.value == null || record.value.isEmpty()) return false;
if (record.port < 0 || record.port > 65535) return false;
return record.ttl >= 0;
}
// Validation helpers
/**
* Validates a collection of records by applying {@link #isValidRecord(INSRecord)}
* to each element.
*
* @param records Records to validate.
* @return A new list containing only valid records.
*/
public static List<INSRecord> filterValid(List<INSRecord> records) {
if (records == null || records.isEmpty()) return Collections.emptyList();
List<INSRecord> out = new ArrayList<>(records.size());
for (INSRecord r : records) {
if (isValidRecord(r)) out.add(r);
}
return out;
}
private record ParsedName(String tln, String name, String sub) {
}
}

View File

@@ -0,0 +1,133 @@
package org.openautonomousconnection.protocol.versions.v1_0_0.beta;
import dev.unlegitdqrk.unlegitlibrary.file.FileUtils;
import dev.unlegitdqrk.unlegitlibrary.string.RandomString;
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket;
import org.openautonomousconnection.protocol.side.server.CustomConnectedClient;
import org.openautonomousconnection.protocol.side.server.ProtocolCustomServer;
import org.openautonomousconnection.protocol.side.web.managers.AuthManager;
import org.openautonomousconnection.protocol.side.web.managers.RuleManager;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import java.io.File;
import java.util.Random;
/**
* Represents the web server for the protocol.
*/
@Deprecated(forRemoval = false, since = "1.0.1-BETA.0.1")
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
public abstract class ProtocolWebServer_1_0_0_B extends ProtocolCustomServer {
/**
* Folder for web content.
*/
private final File contentFolder;
/**
* Folder for error pages.
*/
private final File errorsFolder;
/**
* A unique secret for session management.
*/
private final String uniqueSessionString;
/**
* The expiration time of a Session in minutes
*/
private final int sessionExpire;
/**
* The max upload size in MB
*/
private final int maxUploadSize;
/**
* Initializes the web server with the given configuration, authentication, and rules files.
*
* @param authFile The authentication file.
* @param rulesFile The rules file.
* @param sessionExpire The expiration time of a Session in minutes
* @param uploadSize The max upload size in MB
* @throws Exception If an error occurs during initialization.
*/
public ProtocolWebServer_1_0_0_B(File authFile, File rulesFile, int sessionExpire, int uploadSize) throws Exception {
super("server", "server");
this.sessionExpire = sessionExpire;
this.maxUploadSize = uploadSize;
// Set up content and error folders
contentFolder = new File("content");
errorsFolder = new File("errors");
// Create folders if they don't exist
if (!contentFolder.exists()) contentFolder.mkdir();
if (!errorsFolder.exists()) errorsFolder.mkdir();
// Create auth and rules files with default content if they don't exist
if (!authFile.exists()) {
authFile.createNewFile();
FileUtils.writeFile(authFile, """
admin:5e884898da28047151d0e56f8dc6292773603d0d6aabbddab8f91d8e5f99f6c7
user:e99a18c428cb38d5f260853678922e03abd8335f
""");
}
// Create default rules file if it doesn't exist
if (!rulesFile.exists()) {
rulesFile.createNewFile();
FileUtils.writeFile(rulesFile, """
{
"allow": [
"index.html",
"css/*",
"private/info.php"
],
"deny": [
"private/*"
],
"auth": [
"private/*",
"admin/*"
]
}
""");
}
// Load authentication and rules
uniqueSessionString = AuthManager.sha256(new RandomString(new Random(System.currentTimeMillis()).nextInt(10, 20)).nextString());
AuthManager.loadAuthFile(authFile);
RuleManager.loadRules(rulesFile);
}
public final File getContentFolder() {
return contentFolder;
}
public final File getErrorsFolder() {
return errorsFolder;
}
public final String getUniqueSessionString() {
return uniqueSessionString;
}
public final int getSessionExpire() {
return sessionExpire;
}
public final int getMaxUploadSize() {
return maxUploadSize;
}
/**
* Called when the server receives a WebRequestPacket from the client.
*
* @param client The connected web client (pipeline + web socket).
* @param request The full decoded request packet.
* @return The response packet that should be sent back to the client.
*/
@Deprecated(forRemoval = false, since = "1.0.1-BETA.0.1")
public abstract WebResponsePacket onWebRequest(CustomConnectedClient client, WebRequestPacket request);
}

View File

@@ -0,0 +1,5 @@
package org.openautonomousconnection.protocol.versions.v1_0_0.beta;
public enum WebRequestMethod {
GET, POST, EXECUTE, SCRIPT
}

View File

@@ -1,11 +1,11 @@
package org.openautonomousconnection.protocol.versions.v1_0_0.classic.utils;
package org.openautonomousconnection.protocol.versions.v1_0_0.classic;
import java.io.Serializable;
/**
* Enum representing the protocol versions for the Classic protocol.
*/
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
@Deprecated(forRemoval = true, since = "1.0.1-BETA.0.1")
public enum Classic_ProtocolVersion implements Serializable {
PV_1_0_0("1.0.0");
public final String version;

View File

@@ -1,51 +0,0 @@
package org.openautonomousconnection.protocol.versions.v1_0_0.classic.events;
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.ClassicHandlerINSServer;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_Domain;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_RequestDomain;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.utils.Classic_ProtocolVersion;
/**
* This event is fired when a classic domain packet is received.
* This event is deprecated and will be marked for removal in future versions.
*
* @see ClassicHandlerINSServer
* @see org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.ClassicHandlerClient
* @see org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.ClassicHandlerWebServer
*/
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
public final class Classic_DomainPacketReceivedEvent extends Event {
public final Classic_ProtocolVersion protocolVersion;
public final Classic_Domain domain;
public final Classic_RequestDomain requestDomain;
public final int clientID;
public Classic_DomainPacketReceivedEvent(Classic_ProtocolVersion protocolVersion, Classic_Domain domain, Classic_RequestDomain requestDomain, int clientID) {
this.protocolVersion = protocolVersion;
this.domain = domain;
this.requestDomain = requestDomain;
this.clientID = clientID;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
@Override
public String toString() {
return super.toString();
}
@Override
public int hashCode() {
return super.hashCode();
}
}

View File

@@ -1,52 +0,0 @@
package org.openautonomousconnection.protocol.versions.v1_0_0.classic.events;
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.ClassicHandlerINSServer;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_Domain;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_RequestDomain;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.utils.Classic_ProtocolVersion;
/**
* This event is fired when a classic ping packet is received.
* This event is deprecated and will be marked for removal in future versions.
*
* @see ClassicHandlerINSServer
* @see org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.ClassicHandlerClient
* @see org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.ClassicHandlerWebServer
*/
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
public final class Classic_PingPacketReceivedEvent extends Event {
public final Classic_ProtocolVersion protocolVersion;
public final Classic_Domain domain;
public final Classic_RequestDomain requestDomain;
public final boolean reachable;
public final int clientID;
public Classic_PingPacketReceivedEvent(Classic_ProtocolVersion protocolVersion, Classic_Domain domain, Classic_RequestDomain requestDomain, boolean reachable, int clientID) {
this.protocolVersion = protocolVersion;
this.domain = domain;
this.requestDomain = requestDomain;
this.reachable = reachable;
this.clientID = clientID;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
@Override
public String toString() {
return super.toString();
}
@Override
public int hashCode() {
return super.hashCode();
}
}

View File

@@ -1,47 +0,0 @@
package org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers;
import lombok.Getter;
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
import org.openautonomousconnection.protocol.packets.v1_0_0.classic.Classic_MessagePacket;
import org.openautonomousconnection.protocol.side.client.ProtocolClient;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_Domain;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.site.Classic_SiteType;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.utils.Classic_ProtocolVersion;
import java.io.IOException;
/**
* Abstract class defining the client-side handler for Classic protocol operations.
*/
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.CLIENT)
public abstract class ClassicHandlerClient {
/**
* Reference to the ProtocolClient
*/
@Getter
private final ProtocolClient client;
/**
* Sets the client variable
*
* @param client The ProtocolClient Object
*/
public ClassicHandlerClient(ProtocolClient client) {
this.client = client;
}
public abstract void unsupportedClassicPacket(String classicPacketClassName, Object[] content);
public abstract void handleHTMLContent(Classic_SiteType siteType, Classic_Domain domain, String html);
public abstract void handleMessage(String message, Classic_ProtocolVersion protocolVersion);
public final void sendMessage(String message) throws IOException, ClassNotFoundException {
client.getClientINSConnection().sendPacket(new Classic_MessagePacket(message, 0, client.getProtocolBridge()));
}
public abstract void validationCompleted(Classic_Domain domain, INSResponseStatus insResponseStatus);
}

View File

@@ -1,24 +0,0 @@
package org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers;
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
import org.openautonomousconnection.protocol.side.ins.ConnectedProtocolClient;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_Domain;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_RequestDomain;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.utils.Classic_ProtocolVersion;
import java.sql.SQLException;
/**
* Abstract class defining the INS server-side handler for Classic protocol operations.
*/
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.INS)
public abstract class ClassicHandlerINSServer {
public abstract void handleMessage(ConnectedProtocolClient client, String message, Classic_ProtocolVersion protocolVersion);
public abstract Classic_Domain getDomain(Classic_RequestDomain requestDomain) throws SQLException;
public abstract Classic_Domain ping(Classic_RequestDomain requestDomain) throws SQLException;
public abstract void unsupportedClassicPacket(String className, Object[] content, ConnectedProtocolClient client);
}

View File

@@ -1,16 +0,0 @@
package org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers;
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
import org.openautonomousconnection.protocol.side.ins.ConnectedProtocolClient;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.utils.Classic_ProtocolVersion;
/**
* Abstract class defining the web server-side handler for Classic protocol operations.
*/
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
public abstract class ClassicHandlerWebServer {
public abstract void handleMessage(ConnectedProtocolClient client, String message, Classic_ProtocolVersion protocolVersion);
public abstract void unsupportedClassicPacket(String className, Object[] content, ConnectedProtocolClient client);
}

View File

@@ -1,70 +0,0 @@
package org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects;
import lombok.Getter;
import org.openautonomousconnection.protocol.ProtocolBridge;
import java.io.Serializable;
/**
* Classic_Domain is an old representation of a InfoName, maintained for backward compatibility.
* It encapsulates the InfoName's name, top-level name, path, and destination.
* This class is deprecated and users are encouraged to use the InfoName class instead.
*/
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
public class Classic_Domain implements Serializable {
/**
* The name of the domain.
*/
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
public final String name;
/**
* The top-level domain.
*/
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
public final String topLevelDomain;
/**
* The path component of the domain.
*/
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
public final String path;
/**
* The destination of the domain, typically the full URL or address.
*/
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
public final String destination;
/**
* The ProtocolBridge reference.
*/
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
public final ProtocolBridge protocolBridge;
public Classic_Domain(String name, String topLevelDomain, String destination, String path, ProtocolBridge bridge) {
this.protocolBridge = bridge;
this.name = name;
this.topLevelDomain = topLevelDomain;
this.destination = destination;
this.path = path;
}
@Override
protected final Object clone() throws CloneNotSupportedException {
return new Classic_Domain(name, topLevelDomain, destination, path, protocolBridge);
}
@Override
public final boolean equals(Object obj) {
if (!(obj instanceof Classic_Domain other)) return false;
return other.name.equalsIgnoreCase(name) && other.topLevelDomain.equalsIgnoreCase(topLevelDomain);
}
@Override
public final int hashCode() {
return super.hashCode();
}
}

View File

@@ -1,14 +0,0 @@
package org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects;
import org.openautonomousconnection.protocol.ProtocolBridge;
/**
* Class representing a local domain in the Classic protocol.
* This class extends Classic_Domain and is used for local domain representation.
*/
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
public final class Classic_LocalDomain extends Classic_Domain {
public Classic_LocalDomain(String name, String endName, String path, ProtocolBridge protocolBridge) {
super(name, endName, null, path, protocolBridge);
}
}

View File

@@ -1,17 +0,0 @@
package org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects;
import org.openautonomousconnection.protocol.ProtocolBridge;
import java.io.Serializable;
/**
* Class representing a request for a domain in the Classic protocol.
* This class extends Classic_Domain and is used for requesting domain information.
*/
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
public final class Classic_RequestDomain extends Classic_Domain implements Serializable {
public Classic_RequestDomain(String name, String topLevelDomain, String path, ProtocolBridge protocolBridge) {
super(name, topLevelDomain, null, path, protocolBridge);
}
}

View File

@@ -1,44 +0,0 @@
package org.openautonomousconnection.protocol.versions.v1_0_0.classic.site;
import java.io.Serializable;
/**
* Enum representing different types of sites in the Classic protocol.
*/
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
public enum Classic_SiteType implements Serializable {
/**
* Client site type.
*/
CLIENT("oac-client"),
/**
* Web server site type.
*/
SERVER("oac-server"),
/**
* INS server site type.
*/
PUBLIC("oac"),
/**
* Protocol site type.
*/
PROTOCOL("oac-protocol"),
/**
* Local site type.
*/
LOCAL("oac-local");
/**
* The name of the site type.
*/
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
public final String name;
Classic_SiteType(String name) {
this.name = name;
}
}

View File

@@ -1,71 +0,0 @@
package org.openautonomousconnection.protocol.versions.v1_0_0.classic.site;
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
/**
* This class contains predefined HTML content for various website responses in the Classic protocol.
*/
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
public final class Classic_WebsitesContent extends DefaultMethodsOverrider {
public static final String DOMAIN_NOT_FOUND = """
<html>
<head>
<title>404 - Domain not found</title>
<meta content="UTF-8" name="charset"/>
<meta content="Open Autonomous Connection" name="author"/>
<meta content="Domain not found" name="description"/>
</head>
<body>
<h1>404 - This infoName was not found</h1>
</body>
</html>
""";
public static final String FILE_NOT_FOUND = """
<html>
<head>
<title>404 - File not found</title>
<meta content="UTF-8" name="charset"/>
<meta content="Open Autonomous Connection" name="author"/>
<meta content="File not found" name="description"/>
</head>
<body>
<h1>404 - This file was not found</h1>
</body>
</html>
""";
public static final String DOMAIN_NOT_REACHABLE = """
<html>
<head>
<title>504 - Site not reachable</title>
<meta content="UTF-8" name="charset"/>
<meta content="Open Autonomous Connection" name="author"/>
<meta content="Site not reached" name="description"/>
</head>
<body>
<h1>504 - This site is currently not reachable</h1>
</body>
</html>
""";
public static String ERROR_OCCURRED = ERROR_OCCURRED("No specified details!");
public static String ERROR_OCCURRED(String errorDetails) {
return """
<html>
<head>
<title>500 - Error occurred</title>
<meta content="UTF-8" name="charset"/>
<meta content="Open Autonomous Connection" name="author"/>
<meta content="Site not reached" name="description"/>
</head>
<body>
<h1>500 - Error occured while resolving infoName!</h1>
<h4>Details:</h2>
<h5>""" + errorDetails + "</h5>" + """
</body>
</html>
""";
}
}

View File

@@ -1,131 +0,0 @@
package org.openautonomousconnection.protocol.versions.v1_0_0.classic.utils;
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
import lombok.Getter;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.packets.v1_0_0.classic.Classic_PingPacket;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.events.Classic_DomainPacketReceivedEvent;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.events.Classic_PingPacketReceivedEvent;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_LocalDomain;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.site.Classic_SiteType;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.site.Classic_WebsitesContent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* This class listens for events related to Classic protocol operations on the client side.
* It handles domain resolution and ping responses, facilitating communication with the INS server
* and web content retrieval.
*/
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
public final class Classic_ClientListener extends EventListener {
/**
* Reference to the ProtocolBridge
*/
@Getter
private ProtocolBridge protocolBridge;
/**
* Set protocol bridge
*
* @param protocolBridge The ProtocolBridge object
*/
public void setProtocolBridge(ProtocolBridge protocolBridge) {
if (this.protocolBridge != null) return;
this.protocolBridge = protocolBridge;
}
/**
* Handles the event when a domain packet is received.
* It checks if the domain exists and sends a ping request to the INS server.
* If the domain does not exist, it handles the error accordingly.
*
* @param event The event containing domain information.
*/
@Listener
public void onDomain(Classic_DomainPacketReceivedEvent event) {
// Check if the domain exists
boolean exists = event.domain != null;
if (exists) {
try {
// Send a ping request to the INS server
if (!protocolBridge.getProtocolClient().getClientINSConnection().sendPacket(new Classic_PingPacket(event.requestDomain, event.domain, false, protocolBridge))) {
// If sending the packet fails, handle the error
protocolBridge.getClassicHandlerClient().handleHTMLContent(Classic_SiteType.PROTOCOL, new Classic_LocalDomain("error-occurred", "html", "", protocolBridge),
Classic_WebsitesContent.ERROR_OCCURRED(event.domain + "/" + event.domain.path));
}
} catch (IOException | ClassNotFoundException e) {
// Handle any exceptions that occur during the process
protocolBridge.getClassicHandlerClient().handleHTMLContent(Classic_SiteType.PROTOCOL, new Classic_LocalDomain("error-occurred", "html", "", protocolBridge),
Classic_WebsitesContent.ERROR_OCCURRED(event.domain + "/" + event.domain.path + ":\n" + e.getMessage()));
}
} else
// If the domain does not exist, handle the error
protocolBridge.getClassicHandlerClient().handleHTMLContent(Classic_SiteType.PROTOCOL, new Classic_LocalDomain("domain-not-found", "html", "", protocolBridge), Classic_WebsitesContent.DOMAIN_NOT_FOUND);
}
/**
* Handles the event when a ping packet is received.
* If the domain is reachable, it fetches the HTML content from the domain.
* If not reachable, it handles the error accordingly.
*
* @param event The event containing ping response information.
*/
@Listener
public void onPing(Classic_PingPacketReceivedEvent event) {
// If the domain is reachable, fetch the HTML content
if (event.reachable) {
String destination = event.domain.destination;
try {
// Create a URL object
URL url = new URL(destination);
HttpURLConnection connection2 = (HttpURLConnection) url.openConnection();
connection2.setRequestMethod("GET");
// Read the response
StringBuilder content = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection2.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) content.append(line);
}
// Handle the HTML content
protocolBridge.getClassicHandlerClient().handleHTMLContent(Classic_SiteType.PUBLIC, event.domain, content.toString());
} catch (IOException exception) {
// Handle any exceptions that occur during the process
protocolBridge.getClassicHandlerClient().handleHTMLContent(Classic_SiteType.PROTOCOL, new Classic_LocalDomain("error-occurred", "html", "", protocolBridge),
Classic_WebsitesContent.ERROR_OCCURRED(exception.getMessage().replace(event.domain.destination, event.domain + "/" + event.domain.path)));
}
} else
// If the domain is not reachable, handle the error
protocolBridge.getClassicHandlerClient().handleHTMLContent(Classic_SiteType.PROTOCOL, new Classic_LocalDomain("error-not-reached", "html", "", protocolBridge), Classic_WebsitesContent.DOMAIN_NOT_REACHABLE);
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
@Override
public String toString() {
return super.toString();
}
@Override
public int hashCode() {
return super.hashCode();
}
}

View File

@@ -1,100 +0,0 @@
package org.openautonomousconnection.protocol.versions.v1_0_0.classic.utils;
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.site.Classic_SiteType;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
/**
* Utility class for domain-related operations in the Classic protocol.
*/
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
class Classic_DomainUtils extends DefaultMethodsOverrider {
/**
* Extracts the top-level domain (TLD) from a given URL.
*
* @param url The URL from which to extract the TLD.
* @return The top-level domain as a string.
* @throws MalformedURLException If the URL is malformed.
*/
public static String getTopLevelDomain(String url) throws MalformedURLException {
URL uri = null;
String tldString = null;
if (url.startsWith(Classic_SiteType.PUBLIC.name + "://"))
url = url.substring((Classic_SiteType.PUBLIC.name + "://").length());
if (url.startsWith(Classic_SiteType.CLIENT.name + "://"))
url = url.substring((Classic_SiteType.CLIENT.name + "://").length());
if (url.startsWith(Classic_SiteType.SERVER.name + "://"))
url = url.substring((Classic_SiteType.SERVER.name + "://").length());
if (url.startsWith(Classic_SiteType.PROTOCOL.name + "://"))
url = url.substring((Classic_SiteType.PROTOCOL.name + "://").length());
if (url.startsWith(Classic_SiteType.LOCAL.name + "://"))
url = url.substring((Classic_SiteType.LOCAL.name + "://").length());
if (!url.startsWith("https://") && !url.startsWith("http://")) url = "https://" + url;
uri = new URL(url);
String[] domainNameParts = uri.getHost().split("\\.");
tldString = domainNameParts[domainNameParts.length - 1];
return tldString;
}
/**
* Extracts the domain name (excluding the TLD) from a given URL.
*
* @param url The URL from which to extract the domain name.
* @return The domain name as a string.
* @throws URISyntaxException If the URL syntax is incorrect.
* @throws MalformedURLException If the URL is malformed.
*/
public static String getDomainName(String url) throws URISyntaxException, MalformedURLException {
if (url.startsWith(Classic_SiteType.PUBLIC.name + "://"))
url = url.substring((Classic_SiteType.PUBLIC.name + "://").length());
if (url.startsWith(Classic_SiteType.CLIENT.name + "://"))
url = url.substring((Classic_SiteType.CLIENT.name + "://").length());
if (url.startsWith(Classic_SiteType.SERVER.name + "://"))
url = url.substring((Classic_SiteType.SERVER.name + "://").length());
if (url.startsWith(Classic_SiteType.PROTOCOL.name + "://"))
url = url.substring((Classic_SiteType.PROTOCOL.name + "://").length());
if (url.startsWith(Classic_SiteType.LOCAL.name + "://"))
url = url.substring((Classic_SiteType.LOCAL.name + "://").length());
if (!url.startsWith("https://") && !url.startsWith("http://")) url = "https://" + url;
URI uri = new URI(url);
return uri.getHost().replace("." + getTopLevelDomain(url), "");
}
/**
* Extracts the path component from a given URL.
*
* @param url The URL from which to extract the path.
* @return The path as a string.
*/
public static String getPath(String url) {
if (!url.startsWith(Classic_SiteType.PUBLIC.name + "://") && !url.startsWith(Classic_SiteType.CLIENT.name + "://") &&
!url.startsWith(Classic_SiteType.SERVER.name + "://") && !url.startsWith(Classic_SiteType.PROTOCOL.name + "://") &&
!url.startsWith(Classic_SiteType.LOCAL.name + "://") && !url.startsWith("http") && !url.startsWith("https")) {
url = Classic_SiteType.PUBLIC.name + "://" + url;
}
String[] split = url.split("/");
if (split.length <= 3) return "";
StringBuilder path = new StringBuilder();
for (int i = 3; i < split.length; i++) path.append(split[i]).append("/");
String pathStr = path.toString();
if (pathStr.startsWith("/")) pathStr = pathStr.substring("/".length());
if (pathStr.endsWith("/")) pathStr = pathStr.substring(0, pathStr.length() - "/".length());
return pathStr;
}
}

View File

@@ -0,0 +1,46 @@
package org.openautonomousconnection.protocol.versions.v1_0_1.beta;
import java.util.Objects;
/**
* Parsed INS address components.
*
* <p>Supported formats:</p>
* <ul>
* <li>{@code name.tln}</li>
* <li>{@code sub.name.tln}</li>
* </ul>
*/
public record InsParts(String tln, String name, String sub) {
/**
* Parses an InfoName into INS query parts.
*
* @param infoName InfoName host string
* @return parsed parts
* @throws IllegalArgumentException if format is invalid
*/
public static InsParts parse(String infoName) {
String in = Objects.requireNonNull(infoName, "infoName").trim();
if (in.isEmpty()) {
throw new IllegalArgumentException("InfoName is empty");
}
String[] parts = in.split("\\.");
if (parts.length < 2 || parts.length > 3) {
throw new IllegalArgumentException(
"Invalid INS address format: " + infoName + " (expected name.tln or sub.name.tln)"
);
}
String tln = parts[parts.length - 1].trim();
String name = parts[parts.length - 2].trim();
String sub = (parts.length == 3) ? parts[0].trim() : null;
if (tln.isEmpty() || name.isEmpty() || (sub != null && sub.isEmpty())) {
throw new IllegalArgumentException("Invalid INS address parts in: " + infoName);
}
return new InsParts(tln, name, sub);
}
}

View File

@@ -0,0 +1,10 @@
package org.openautonomousconnection.protocol.versions.v1_0_1.beta;
/**
* Cache behavior for navigation/resource requests.
*/
public enum WebCacheMode {
DEFAULT,
BYPASS,
ONLY_CACHE
}

View File

@@ -0,0 +1,279 @@
package org.openautonomousconnection.protocol.versions.v1_0_1.beta;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamChunkPacket_v1_0_0_B;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamEndPacket_v1_0_0_B;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamStartPacket_v1_0_0_B;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceRequestPacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamChunkPacket_v1_0_1_B;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamEndPacket_v1_0_1_B;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamStartPacket_v1_0_1_B;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.WebRequestMethod;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;
/**
* Compatibility mapper between Web protocol v1.0.0
* and Web protocol v1.0.1 (beta).
*
* <p>This class provides pure mapping logic and contains no networking code.</p>
*
* <p>Usage:
* <ul>
* <li>Server-side: map 1.0.0 {@link WebRequestPacket} to {@link WebResourceRequestPacket}</li>
* <li>Server-side: map {@link WebResourceResponsePacket} back to {@link WebResponsePacket}</li>
* <li>Client-side: map 1.0.0 responses/streams to v1.0.1 packets so v1.0.1 hooks can be reused</li>
* </ul>
* </p>
*/
public final class WebCompatMapper {
/**
* Synthetic request id generator for 1.0.0 requests.
*/
private static final AtomicLong COMPAT_REQUEST_ID = new AtomicLong(1L);
private WebCompatMapper() {
}
/**
* Generates a monotonic synthetic request id.
*
* @return next request id
*/
public static long nextCompatRequestId() {
return COMPAT_REQUEST_ID.getAndIncrement();
}
/**
* Maps a v1.0.0 {@link WebRequestPacket} to a v1.0.1 {@link WebResourceRequestPacket}.
*
* <p>Because v1.0.0 has no tab/page/frame/request correlation model,
* synthetic identifiers must be provided.</p>
*
* @param requestId synthetic request id
* @param tabId synthetic tab id
* @param pageId synthetic page id
* @param requestPacket 1.0.0 request
* @return mapped v1.0.1 resource request
*/
public static WebResourceRequestPacket toResourceRequest(
long requestId,
long tabId,
long pageId,
WebRequestPacket requestPacket, ProtocolBridge bridge
) {
Objects.requireNonNull(requestPacket, "requestPacket");
String path = requestPacket.getPath();
String method = mapMethod(requestPacket.getMethod());
Map<String, String> headers = requestPacket.getHeaders() != null ? requestPacket.getHeaders() : Collections.emptyMap();
byte[] body = requestPacket.getBody() != null ? requestPacket.getBody() : new byte[0];
WebPacketHeader header = new WebPacketHeader(
requestId,
tabId,
pageId,
0L,
WebPacketFlags.RESOURCE,
System.currentTimeMillis()
);
return new WebResourceRequestPacket(
header,
path,
method,
headers,
body,
null,
WebInitiatorType.OTHER,
WebCacheMode.DEFAULT, bridge
);
}
/**
* Maps a v1.0.1 {@link WebResourceResponsePacket} back to a 1.0.0 {@link WebResponsePacket}.
*
* <p>Correlation information is lost because v1.0.0 does not support it.</p>
*
* @param response v1.0.1 resource response
* @return 1.0.0 response packet
*/
public static WebResponsePacket to100BResponse(WebResourceResponsePacket response) {
Objects.requireNonNull(response, "response");
return new WebResponsePacket(
response.getStatusCode(),
response.getContentType(),
response.getHeaders(),
response.getBody()
);
}
/**
* Maps v1.0.1 resource response to 1.0.0 response with overridden body.
*
* <p>Useful when buffering streamed responses for 1.0.0 clients.</p>
*
* @param response original v1.0.1 response
* @param body buffered full body
* @return 1.0.0 response
*/
public static WebResponsePacket to100BResponse(WebResourceResponsePacket response, byte[] body) {
Objects.requireNonNull(response, "response");
return new WebResponsePacket(
response.getStatusCode(),
response.getContentType(),
response.getHeaders(),
body != null ? body : new byte[0]
);
}
/**
* Maps a v1.0.0 {@link WebResponsePacket} to a v1.0.1 {@link WebResourceResponsePacket}
* using the provided synthetic correlation header.
*
* @param correlationHeader synthetic correlation header (requestId/tabId/pageId/frameId)
* @param responsePacket 1.0.0 response packet
* @return v1.0.1 resource response packet
*/
public static WebResourceResponsePacket toV101ResourceResponse(WebPacketHeader correlationHeader, WebResponsePacket responsePacket) {
Objects.requireNonNull(correlationHeader, "correlationHeader");
Objects.requireNonNull(responsePacket, "responsePacket");
WebPacketHeader header = mirrorCorrelation(correlationHeader, WebPacketFlags.RESOURCE);
return new WebResourceResponsePacket(
header,
responsePacket.getStatusCode(),
responsePacket.getContentType(),
responsePacket.getHeaders(),
responsePacket.getBody(),
null
);
}
/**
* Maps a v1.0.0 {@link WebStreamStartPacket_v1_0_0_B} to a v1.0.1 {@link WebStreamStartPacket_v1_0_1_B}
* using the provided synthetic correlation header.
*
* @param correlationHeader synthetic correlation header
* @param streamPacket 1.0.0 stream start
* @return v1.0.1 stream start
*/
public static WebStreamStartPacket_v1_0_1_B toV101StreamStart(WebPacketHeader correlationHeader, WebStreamStartPacket_v1_0_0_B streamPacket) {
Objects.requireNonNull(correlationHeader, "correlationHeader");
Objects.requireNonNull(streamPacket, "streamPacket");
WebPacketHeader header = mirrorCorrelation(correlationHeader, WebPacketFlags.RESOURCE);
return new WebStreamStartPacket_v1_0_1_B(
header,
streamPacket.getStatusCode(),
streamPacket.getContentType(),
streamPacket.getHeaders(),
streamPacket.getTotalLength()
);
}
/**
* Maps a v1.0.0 {@link WebStreamChunkPacket_v1_0_0_B} to a v1.0.1 {@link WebStreamChunkPacket_v1_0_1_B}
* using the provided synthetic correlation header.
*
* @param correlationHeader synthetic correlation header
* @param streamPacket 1.0.0 stream chunk
* @return v1.0.1 stream chunk
*/
public static WebStreamChunkPacket_v1_0_1_B toV101StreamChunk(WebPacketHeader correlationHeader, WebStreamChunkPacket_v1_0_0_B streamPacket) {
Objects.requireNonNull(correlationHeader, "correlationHeader");
Objects.requireNonNull(streamPacket, "streamPacket");
WebPacketHeader header = mirrorCorrelation(correlationHeader, WebPacketFlags.RESOURCE);
return new WebStreamChunkPacket_v1_0_1_B(
header,
streamPacket.getSeq(),
streamPacket.getData()
);
}
/**
* Maps a v1.0.0 {@link WebStreamEndPacket_v1_0_0_B} to a v1.0.1 {@link WebStreamEndPacket_v1_0_1_B}
* using the provided synthetic correlation header.
*
* @param correlationHeader synthetic correlation header
* @param streamPacket v1.0.0 stream end packet
* @return v1.0.1 stream end packet
*/
public static WebStreamEndPacket_v1_0_1_B toV101StreamEnd(
WebPacketHeader correlationHeader,
WebStreamEndPacket_v1_0_0_B streamPacket
) {
Objects.requireNonNull(correlationHeader, "correlationHeader");
Objects.requireNonNull(streamPacket, "streamPacket");
WebPacketHeader header = mirrorCorrelation(correlationHeader, WebPacketFlags.RESOURCE);
// v1.0.0 has no error string -> null
return new WebStreamEndPacket_v1_0_1_B(
header,
streamPacket.isOk(),
null
);
}
/**
* Creates a header that mirrors correlation fields from the provided header and updates timestamp/flags.
*
* @param correlationHeader correlation header to mirror
* @param extraFlags flags to OR
* @return mirrored header
*/
public static WebPacketHeader mirrorCorrelation(WebPacketHeader correlationHeader, int extraFlags) {
Objects.requireNonNull(correlationHeader, "correlationHeader");
return new WebPacketHeader(
correlationHeader.getRequestId(),
correlationHeader.getTabId(),
correlationHeader.getPageId(),
correlationHeader.getFrameId(),
correlationHeader.getFlags() | extraFlags,
System.currentTimeMillis()
);
}
/**
* Maps 1.0.0 {@link WebRequestMethod}.
*
* @param method 1.0.0 method
* @return method string
*/
public static String mapMethod(WebRequestMethod method) {
if (method == null) return "GET";
return switch (method) {
case GET -> "GET";
case POST, EXECUTE, SCRIPT -> "POST";
default -> "GET";
};
}
/**
* Maps 1.0.1 to 1.0.0 {@link WebRequestMethod}.
*
* @param method 1.0.0 method
* @return method string
*/
public static WebRequestMethod map100BMethod(String method) {
if (method == null) return WebRequestMethod.GET;
return switch (method.toUpperCase()) {
case "POST", "SCRIPT", "EXECUTE" -> WebRequestMethod.POST;
default -> WebRequestMethod.GET;
};
}
}

View File

@@ -0,0 +1,250 @@
package org.openautonomousconnection.protocol.versions.v1_0_1.beta;
import java.security.SecureRandom;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;
/**
* Factory for creating {@link WebPacketHeader} instances with stable tab/page identifiers and
* monotonic request identifiers.
*
* <p>This class is intended to be used by the WebClient/WebServer integration layer to ensure
* every packet carries a valid header and consistent correlation metadata.</p>
*
* <p>Thread-safety:
* <ul>
* <li>Request id generation is thread-safe.</li>
* <li>Tab/page allocation methods are thread-safe.</li>
* <li>{@link WebTabContext} is thread-safe for reads; writes are synchronized.</li>
* </ul>
* </p>
*/
public final class WebHeaderFactory {
private static final long MIN_ID = 1L;
private final AtomicLong requestIdSeq;
private final AtomicLong tabIdSeq;
private final AtomicLong pageIdSeq;
/**
* Creates a factory with randomized id bases to reduce collisions across process restarts.
*/
public WebHeaderFactory() {
SecureRandom rnd = new SecureRandom();
this.requestIdSeq = new AtomicLong(Math.max(MIN_ID, rnd.nextLong() & Long.MAX_VALUE));
this.tabIdSeq = new AtomicLong(Math.max(MIN_ID, rnd.nextLong() & Long.MAX_VALUE));
this.pageIdSeq = new AtomicLong(Math.max(MIN_ID, rnd.nextLong() & Long.MAX_VALUE));
}
/**
* Creates a new tab context with a fresh tab id and an initial page id.
*
* @return new tab context
*/
public WebTabContext createTab() {
long tabId = nextTabId();
long pageId = nextPageId();
return new WebTabContext(tabId, pageId);
}
/**
* Starts a new navigation by allocating a new page id for the given tab context.
*
* <p>Call this when navigation is initiated (typed URL, link click, reload, back/forward).</p>
*
* @param tab tab context
* @return the new page id
*/
public long beginNavigation(WebTabContext tab) {
Objects.requireNonNull(tab, "tab");
long newPageId = nextPageId();
tab.setPageId(newPageId);
return newPageId;
}
/**
* Creates a header for a navigation request.
*
* @param tab tab context
* @param noStore if true, sets {@link WebPacketFlags#NO_STORE}
* @return header
*/
public WebPacketHeader navigation(WebTabContext tab, boolean noStore) {
Objects.requireNonNull(tab, "tab");
int flags = WebPacketFlags.NAVIGATION;
if (noStore) flags |= WebPacketFlags.NO_STORE;
return header(nextRequestId(), tab.getTabId(), tab.getPageId(), 0L, flags);
}
/**
* Creates a header for a resource request.
*
* @param tab tab context
* @param frameId frame id (0 = main)
* @param noStore if true, sets {@link WebPacketFlags#NO_STORE}
* @return header
*/
public WebPacketHeader resource(WebTabContext tab, long frameId, boolean noStore) {
Objects.requireNonNull(tab, "tab");
int flags = WebPacketFlags.RESOURCE;
if (noStore) flags |= WebPacketFlags.NO_STORE;
return header(nextRequestId(), tab.getTabId(), tab.getPageId(), frameId, flags);
}
/**
* Creates a header for a resource response correlated to a request id.
*
* @param requestId request id to correlate
* @param tabId tab id
* @param pageId page id
* @param frameId frame id
* @return header
*/
public WebPacketHeader resourceResponse(long requestId, long tabId, long pageId, long frameId) {
return header(requestId, tabId, pageId, frameId, WebPacketFlags.RESOURCE);
}
/**
* Creates a header for a streamed payload associated with a request id.
*
* @param requestId correlated request id
* @param tabId tab id
* @param pageId page id
* @param frameId frame id
* @return header
*/
public WebPacketHeader stream(long requestId, long tabId, long pageId, long frameId) {
return header(requestId, tabId, pageId, frameId, WebPacketFlags.STREAM);
}
/**
* Creates an event header (server->client or client->server).
*
* @param tabId tab id
* @param pageId page id
* @param frameId frame id
* @return header
*/
public WebPacketHeader event(long tabId, long pageId, long frameId) {
return header(nextRequestId(), tabId, pageId, frameId, WebPacketFlags.EVENT);
}
/**
* Creates an event header correlated to an existing request id.
*
* @param requestId request id
* @param tabId tab id
* @param pageId page id
* @param frameId frame id
* @return header
*/
public WebPacketHeader correlatedEvent(long requestId, long tabId, long pageId, long frameId) {
return header(requestId, tabId, pageId, frameId, WebPacketFlags.EVENT);
}
/**
* Creates a header for a document snapshot event.
*
* @param tab tab context
* @param frameId frame id
* @return header
*/
public WebPacketHeader documentSnapshotEvent(WebTabContext tab, long frameId) {
Objects.requireNonNull(tab, "tab");
return header(nextRequestId(), tab.getTabId(), tab.getPageId(), frameId, WebPacketFlags.EVENT);
}
/**
* Creates a header for a document apply request.
*
* @param tab tab context
* @param frameId frame id
* @return header
*/
public WebPacketHeader documentApply(WebTabContext tab, long frameId) {
Objects.requireNonNull(tab, "tab");
return header(nextRequestId(), tab.getTabId(), tab.getPageId(), frameId, WebPacketFlags.RESOURCE);
}
/**
* Checks whether an incoming packet is stale for a given tab context.
*
* <p>Stale means: the packet refers to an older pageId than the current tab pageId.</p>
*
* @param tab tab context
* @param incoming incoming header
* @return true if stale, false otherwise
*/
public boolean isStale(WebTabContext tab, WebPacketHeader incoming) {
Objects.requireNonNull(tab, "tab");
Objects.requireNonNull(incoming, "incoming");
return incoming.getTabId() == tab.getTabId() && incoming.getPageId() != tab.getPageId();
}
private WebPacketHeader header(long requestId, long tabId, long pageId, long frameId, int flags) {
long ts = System.currentTimeMillis();
return new WebPacketHeader(requestId, tabId, pageId, frameId, flags, ts);
}
private long nextRequestId() {
long v = requestIdSeq.incrementAndGet();
return (v < MIN_ID) ? MIN_ID : v;
}
private long nextTabId() {
long v = tabIdSeq.incrementAndGet();
return (v < MIN_ID) ? MIN_ID : v;
}
private long nextPageId() {
long v = pageIdSeq.incrementAndGet();
return (v < MIN_ID) ? MIN_ID : v;
}
/**
* Represents per-tab state used for header creation and stale detection.
*/
public static final class WebTabContext {
private final long tabId;
private volatile long pageId;
/**
* Creates a tab context.
*
* @param tabId tab id
* @param pageId initial page id
*/
public WebTabContext(long tabId, long pageId) {
if (tabId < MIN_ID) throw new IllegalArgumentException("tabId must be >= 1");
if (pageId < MIN_ID) throw new IllegalArgumentException("pageId must be >= 1");
this.tabId = tabId;
this.pageId = pageId;
}
/**
* @return stable tab id
*/
public long getTabId() {
return tabId;
}
/**
* @return current page id
*/
public long getPageId() {
return pageId;
}
/**
* Sets the current page id.
*
* @param pageId new page id
*/
public synchronized void setPageId(long pageId) {
if (pageId < MIN_ID) throw new IllegalArgumentException("pageId must be >= 1");
this.pageId = pageId;
}
}
}

View File

@@ -0,0 +1,16 @@
package org.openautonomousconnection.protocol.versions.v1_0_1.beta;
/**
* Identifies the initiator of a resource request.
*/
public enum WebInitiatorType {
DOCUMENT,
SCRIPT,
STYLE,
IMAGE,
MEDIA,
FONT,
XHR,
FETCH,
OTHER
}

View File

@@ -0,0 +1,46 @@
package org.openautonomousconnection.protocol.versions.v1_0_1.beta;
/**
* Bitset flags used in WebPacketHeader.
*/
public final class WebPacketFlags {
/**
* Packet describes a top-level navigation action.
*/
public static final int NAVIGATION = 1 << 0;
/**
* Packet describes a resource request/response.
*/
public static final int RESOURCE = 1 << 1;
/**
* Packet is part of a streamed body transfer.
*/
public static final int STREAM = 1 << 2;
/**
* Packet is devtools-related.
*/
public static final int DEVTOOLS = 1 << 3;
/**
* Packet is an event (server->client / client->server).
*/
public static final int EVENT = 1 << 4;
/**
* Disable caching / do not store.
*/
public static final int NO_STORE = 1 << 5;
private WebPacketFlags() {
}
/**
* Checks if a flag is present.
*
* @param flags bitset
* @param flag flag constant
* @return true if present
*/
public static boolean has(int flags, int flag) {
return (flags & flag) == flag;
}
}

Some files were not shown because too many files have changed in this diff Show More