6 Commits

Author SHA1 Message Date
Finn
a223c659a5 Made Classic JavaDoc English 2026-01-19 19:32:18 +01:00
Finn
9081b2b644 Made Classic JavaDoc English 2026-01-19 19:32:06 +01:00
Finn
7321d06ee9 Made Classic JavaDoc English 2026-01-19 19:31:50 +01:00
Finn
d08af1001c Made Classic JavaDoc English 2026-01-19 19:31:14 +01:00
Finn
aba21f5fb3 Changed version name 2025-12-11 11:57:19 +01:00
Finn
435d6c3eea Moved classic version to an own branch 2025-12-11 10:40:37 +01:00
105 changed files with 1384 additions and 9058 deletions

2
.idea/misc.xml generated
View File

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

70
LICENSE
View File

@@ -1,2 +1,68 @@
Please read the license here: https://open-autonomous-connection.org/license.html Open Autonomous Public License (OAPL) v1.0
Download all third parties licenses here: https://open-autonomous-connection.org/assets/licenses.zip 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.

105
README.MD
View File

@@ -1,35 +1,35 @@
# Open Autonomous Connection Protocol # Open Autonomous Connection Protocol
> [!IMPORTANT]
> This is the classic version!
> Classic version is not longer maintained or supported please switch the Branch to master
> The new Protocol has a Backwards compatibility and is supporting the classic Version
This is the Protocol for our Open Autonomous Connection project.<br /> This is the Protocol for our Open Autonomous Connection project.<br />
You can easily implement this Protocol via Maven.<br /> You can easily implement this Protocol via Maven.<br />
Feel free to join our Discord. Feel free to join our Discord.
<br /> <br />
**Third-party components:**
<br /> <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.
### Take a look into [Certificate Generation](https://repo.unlegitdqrk.dev/UnlegitDqrk/unlegitlibrary/src/branch/master#certificate-generation-for-networksystem). # Bugs/Problems
# In progress
- Cleanup Code
# TODO
- Subdomains
- Fragments
# Maven # Maven
### pom.xml ### pom.xml
``` ```
<dependency> <dependency>
<groupId>org.openautonomousconnection</groupId> <groupId>me.openautonomousconnection</groupId>
<artifactId>Protocol</artifactId> <artifactId>protocol</artifactId>
<version>VERSION</version> <version>1.0.0-Classic</version>
<scope>compile</scope>
</dependency> </dependency>
``` ```
### Repository: ### Repository:
``` ```
<repository> <repository>
<id>oac</id> <id>oac</id>
@@ -38,4 +38,79 @@ Download all license here: https://open-autonomous-connection.org/assets/license
<enabled>true</enabled> <enabled>true</enabled>
</snapshots> </snapshots>
</repository> </repository>
```
# Examples
#### Note: These examples are very basic
### Server
```java
import me.finn.unlegitlibrary.network.system.server.ConnectionHandler;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.ProtocolSettings;
import org.openautonomousconnection.protocol.ProtocolVersion;
import side.org.openautonomousconnection.protocol.ProtocolServer;
public class Server extends ProtocolServer {
public Server() throws IOException, InterruptedException {
super(10);
}
public static void main(String[] args) {
try {
ProtocolBridge protocolBridge = new ProtocolBridge(ProtocolVersion.PV_1_0_0, new ProtocolSettings(), new Server());
protocolBridge.getProtocolServer().setProtocolBridge(protocolBridge);
protocolBridge.getProtocolServer().startServer();
} catch (IOException | InterruptedException | InvocationTargetException | InstantiationException |
IllegalAccessException | NoSuchMethodException exception) {
exception.printStackTrace();
}
}
@Override
public List<Domain> getDomains() throws SQLException {
return List.of(); // Your method here to get all registered domains
}
@Override
public void handleMessage(ConnectionHandler connectionHandler, String message) {
System.out.println("Received message: " + message + " from client: " + connectionHandler.getClientID());
}
}
```
### Client
```java
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.ProtocolSettings;
import org.openautonomousconnection.protocol.ProtocolVersion;
import domain.org.openautonomousconnection.protocol.Domain;
import side.org.openautonomousconnection.protocol.ProtocolClient;
import utils.org.openautonomousconnection.protocol.SiteType;
public class Client extends ProtocolClient {
public static void main(String[] args) {
try {
ProtocolBridge protocolBridge = new ProtocolBridge(ProtocolVersion.PV_1_0_0, new ProtocolSettings(), new Client());
protocolBridge.getProtocolClient().setProtocolBridge(protocolBridge);
protocolBridge.getProtocolServer().startClient();
} catch (IOException | InterruptedException | InvocationTargetException | InstantiationException |
IllegalAccessException | NoSuchMethodException exception) {
exception.printStackTrace();
}
}
@Override
public void handleHTMLContent(SiteType siteType, Domain domain, String htmlContent) {
System.out.println("Website html content received. This site is " + siteType.name);
System.out.println(htmlContent); // Render content in a webview for example
}
@Override
public void handleMessage(String message) {
System.out.println("Received message: " + message);
}
}
``` ```

74
pom.xml
View File

@@ -6,7 +6,7 @@
<groupId>org.openautonomousconnection</groupId> <groupId>org.openautonomousconnection</groupId>
<artifactId>Protocol</artifactId> <artifactId>Protocol</artifactId>
<version>1.0.1-BETA.0.6</version> <version>1.0.0-CLASSIC.1.0</version>
<organization> <organization>
<name>Open Autonomous Connection</name> <name>Open Autonomous Connection</name>
<url>https://open-autonomous-connection.org/</url> <url>https://open-autonomous-connection.org/</url>
@@ -15,8 +15,8 @@
<description>The Protocol for Server and Client</description> <description>The Protocol for Server and Client</description>
<properties> <properties>
<maven.compiler.source>25</maven.compiler.source> <maven.compiler.source>23</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target> <maven.compiler.target>23</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
@@ -56,13 +56,6 @@
<enabled>true</enabled> <enabled>true</enabled>
</snapshots> </snapshots>
</repository> </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> </repositories>
<distributionManagement> <distributionManagement>
@@ -75,8 +68,9 @@
<licenses> <licenses>
<license> <license>
<name>Open Autonomous Public License (OAPL)</name> <name>Open Autonomous Public License</name>
<url>https://open-autonomous-connection.org/license.html</url> <url>https://open-autonomous-connection.org/license.html</url>
<distribution>repo</distribution>
</license> </license>
</licenses> </licenses>
@@ -84,12 +78,12 @@
<dependency> <dependency>
<groupId>dev.unlegitdqrk</groupId> <groupId>dev.unlegitdqrk</groupId>
<artifactId>unlegitlibrary</artifactId> <artifactId>unlegitlibrary</artifactId>
<version>1.8.3</version> <version>1.6.7</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>1.18.42</version> <version>1.18.38</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -97,40 +91,47 @@
<artifactId>gson</artifactId> <artifactId>gson</artifactId>
<version>2.13.2</version> <version>2.13.2</version>
</dependency> </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> <dependency>
<groupId>net.bytebuddy</groupId> <groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId> <artifactId>byte-buddy</artifactId>
<version>1.18.5</version> <version>LATEST</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.bytebuddy</groupId> <groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId> <artifactId>byte-buddy-agent</artifactId>
<version>1.18.5</version> <version>LATEST</version>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <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> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId> <artifactId>maven-source-plugin</artifactId>
@@ -149,9 +150,6 @@
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>
<version>3.6.3</version> <version>3.6.3</version>
<configuration> <configuration>
<failOnError>false</failOnError>
<failOnWarnings>false</failOnWarnings>
<doclint>none</doclint>
<locale>en_US</locale> <locale>en_US</locale>
<encoding>UTF-8</encoding> <encoding>UTF-8</encoding>
<docencoding>UTF-8</docencoding> <docencoding>UTF-8</docencoding>

View File

@@ -1,357 +1,136 @@
/*
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
*
* You are unauthorized to remove this copyright.
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
* See LICENSE-File if exists
*/
package org.openautonomousconnection.protocol; package org.openautonomousconnection.protocol;
import dev.unlegitdqrk.unlegitlibrary.file.FileUtils; import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.ClientAuthMode; import org.openautonomousconnection.protocol.packets.v1_0_0.DomainPacket;
import lombok.Getter; import org.openautonomousconnection.protocol.packets.v1_0_0.MessagePacket;
import org.openautonomousconnection.protocol.annotations.ProtocolInfo; import org.openautonomousconnection.protocol.packets.v1_0_0.PingPacket;
import org.openautonomousconnection.protocol.listeners.ClientListener; import org.openautonomousconnection.protocol.side.ProtocolClient;
import org.openautonomousconnection.protocol.listeners.CustomServerListener; import org.openautonomousconnection.protocol.side.ProtocolServer;
import org.openautonomousconnection.protocol.packets.OACPacket; import org.openautonomousconnection.protocol.utils.APIInformation;
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.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.beta.ProtocolWebServer_1_0_0_B;
import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.function.Supplier; import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.nio.file.Files;
/** public class ProtocolBridge extends DefaultMethodsOverrider {
* The main bridge class for the protocol connection. private APIInformation apiInformation;
* It manages the protocol settings, version, and side instances. private final ProtocolSettings protocolSettings;
*/ private final ProtocolVersion protocolVersion;
public final class ProtocolBridge {
/**
* The protocol settings for the current connection
*/
@Getter
private final ProtocolValues protocolValues;
/** private final ProtocolServer protocolServer;
* The protocol side instances private final ProtocolClient protocolClient;
*/
@Getter
private ProtocolClient protocolClient;
public final boolean isRunningAsServer() {
/**
* The protocol side instances
*/
@Getter
private ProtocolCustomServer protocolServer;
/**
* Initialize the ProtocolBridge instance for the client side
*
* @param protocolServer The ProtocolCustomServer instance
* @param protocolValues The ProtocolSettings instance
* @throws Exception if an error occurs while initializing the ProtocolBridge
*/
public ProtocolBridge(ProtocolCustomServer protocolServer, ProtocolValues protocolValues) throws Exception {
// Assign the parameters to the class fields
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);
initializeProtocolVersion();
downloadLicenses();
// Register the appropriate listeners and packets
registerListeners();
registerPackets();
}
/**
* Initialize the ProtocolBridge instance for the client side
*
* @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, LibClientImpl_v1_0_1_B libClientImpl,
ProtocolValues protocolValues) throws Exception {
// Assign the parameters to the class fields
this.protocolClient = protocolClient;
this.protocolValues = protocolValues;
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
*
* <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() {
// 1.0.0-BETA packets
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);
// 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);
}
/**
* Register the appropriate listeners based on the current side
*
* @throws Exception if an error occurs while registering the listeners
*/
private void registerListeners() throws Exception {
// Client Listeners
if (isRunningAsClient()) {
protocolValues.eventManager.registerListener(new ClientListener(protocolClient));
protocolValues.eventManager.unregisterListener(CustomServerListener.class);
}
// Server Listeners
if (isRunningAsServer()) {
protocolValues.eventManager.registerListener(new CustomServerListener(protocolServer));
protocolValues.eventManager.unregisterListener(ClientListener.class);
}
}
/**
* Initialize the protocol version
* Validate if the protocol version is valid for the current side
* If not, log an error and exit the application
*/
private void initializeProtocolVersion() {
// Check if the protocol version is valid for the current side
// If not, log an error and exit the application
if (!validateProtocolSide()) {
protocolValues.logger.error("Invalid protocol version '" + protocolValues.protocolVersion.toString() + "'!");
System.exit(1);
}
}
/**
* Check if the classic protocol is supported by the current protocol version
*
* @return true if the classic protocol is supported, false otherwise
*/
public boolean isClassicSupported() {
boolean yes = false;
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 protocolValues.protocolVersion.getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC || yes;
}
/**
* Check if the target protocol is supported by the current protocol version
*
* @param protocol The target protocol to check
* @return true If the target protocol is supported, false otherwise
*/
public boolean isProtocolSupported(ProtocolVersion.Protocol protocol) {
boolean yes = false;
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 protocolValues.protocolVersion.getSupportedProtocols().contains(protocol) || yes;
}
/**
* Check if the target packet is supported by the current protocol version
*
* @param packet The target packet to check
* @return true if the target packet is supported, false otherwise
*/
public boolean isPacketSupported(OACPacket packet) {
boolean compatible = false;
for (ProtocolVersion compatibleVersion : packet.getCompatibleVersions()) {
if (!compatible) compatible = isVersionSupported(compatibleVersion);
}
return compatible;
}
/**
* Check if the target protocol version is supported by the current protocol version
*
* @param targetVersion The target protocol version to check
* @return true if the target protocol version is supported, false otherwise
*/
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 protocolValues.protocolVersion == targetVersion || protocolValues.protocolVersion.getCompatibleVersions().contains(targetVersion);
}
/**
* Validate if the protocol version is valid for the current side
*
* @return true if the protocol version is valid for the current side, false otherwise
*/
private boolean validateProtocolSide() {
return
(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() && 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) ||
(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);
}
/**
* Check if the current instance is running as a INS server
*
* @return true if the current instance is running as a INS server, false otherwise
*/
public boolean isRunningAsINSServer() {
return isRunningAsServer() && protocolServer instanceof ProtocolINSServer;
}
/**
* Check if the current instance is running as a client
*
* @return true if the current instance is running as a client, false otherwise
*/
public boolean isRunningAsClient() {
return protocolClient != null;
}
/**
* Check if the current instance is running as a web server
*
* @return true if the current instance is running as a web server, false otherwise
*/
public boolean isRunningAsWebServer() {
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; return protocolServer != null;
} }
public final ProtocolClient getProtocolClient() {
return protocolClient;
}
public final ProtocolSettings getProtocolSettings() {
return protocolSettings;
}
public final ProtocolServer getProtocolServer() {
return protocolServer;
}
public final ProtocolVersion getProtocolVersion() {
return protocolVersion;
}
private static ProtocolBridge instance;
public static ProtocolBridge getInstance() {
return instance;
}
public ProtocolBridge(ProtocolVersion protocolVersion, ProtocolSettings protocolSettings, ProtocolClient protocolClient, APIInformation apiInformation) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
this(protocolVersion, protocolSettings, protocolClient);
this.apiInformation = apiInformation;
instance = this;
}
public ProtocolBridge(ProtocolVersion protocolVersion, ProtocolSettings protocolSettings, ProtocolClient protocolClient) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
checkUpdates();
protocolSettings.packetHandler.registerPacket(new DomainPacket());
protocolSettings.packetHandler.registerPacket(new PingPacket());
protocolSettings.packetHandler.registerPacket(new MessagePacket());
this.protocolServer = null;
this.protocolVersion = protocolVersion;
this.protocolSettings = protocolSettings;
this.apiInformation = null;
this.protocolClient = protocolClient;
instance = this;
}
public ProtocolBridge(ProtocolVersion protocolVersion, ProtocolSettings protocolSettings, ProtocolServer protocolServer) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
checkUpdates();
this.apiInformation = null;
this.protocolClient = null;
this.protocolVersion = protocolVersion;
this.protocolSettings = protocolSettings;
this.protocolServer = protocolServer;
protocolSettings.packetHandler.registerPacket(new DomainPacket());
protocolSettings.packetHandler.registerPacket(new PingPacket());
protocolSettings.packetHandler.registerPacket(new MessagePacket());
instance = this;
}
public ProtocolBridge(ProtocolVersion protocolVersion, ProtocolSettings protocolSettings, ProtocolServer protocolServer, APIInformation apiInformation) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
this(protocolVersion, protocolSettings, protocolServer);
this.apiInformation = apiInformation;
instance = this;
}
private final void checkUpdates() {
try {
URL oracle = new URL("https://raw.githubusercontent.com/Open-Autonomous-Connection/Protocol/master/src/main/java/me/openautonomousconnection/protocol/version.txt");
BufferedReader in = new BufferedReader(new InputStreamReader(oracle.openStream()));
String version = "";
String inputLine;
while ((inputLine = in.readLine()) != null) version += inputLine;
if (!version.equalsIgnoreCase(Files.readString(new File("version.txt").toPath()))) {
System.out.println();
System.out.println("========================================================");
System.out.println("IMPORTANT: A NEW PROTOCOL VERSION IS PUBLISHED ON GITHUB");
System.out.println("========================================================");
System.out.println();
}
} catch (IOException exception) {
System.out.println();
System.out.println("=======================================================================");
System.out.println("IMPORTANT: PROTOCOL VERSION CHECK COULD NOT COMPLETED! VISIT OUR GITHUB");
System.out.println(" https://github.com/Open-Autonomous-Connection ");
System.out.println("=======================================================================");
System.out.println();
}
}
public final APIInformation getApiInformation() {
return apiInformation;
}
} }

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
*
* You are unauthorized to remove this copyright.
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
* See LICENSE-File if exists
*/
package org.openautonomousconnection.protocol;
import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
public class ProtocolSettings extends DefaultMethodsOverrider {
public String host = "45.155.173.50";
public int port = 8345;
public PacketHandler packetHandler = new PacketHandler();
public EventManager eventManager = new EventManager();
}

View File

@@ -1,36 +0,0 @@
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

@@ -0,0 +1,21 @@
/*
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
*
* You are unauthorized to remove this copyright.
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
* See LICENSE-File if exists
*/
package org.openautonomousconnection.protocol;
import java.io.Serializable;
public enum ProtocolVersion implements Serializable {
PV_1_0_0("1.0.0");
;
public final String version;
ProtocolVersion(String version) {
this.version = version;
}
}

View File

@@ -1,23 +0,0 @@
package org.openautonomousconnection.protocol.annotations;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import java.lang.annotation.Retention;
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 {
/**
* Specifies the side of the protocol that the annotated class or method is associated with.
* Default is ALL, indicating that it can be used on any side.
*
* @return The protocol side.
*/
ProtocolVersion.ProtocolSide protocolSide() default ProtocolVersion.ProtocolSide.ALL;
}

View File

@@ -1,77 +0,0 @@
package org.openautonomousconnection.protocol.annotations.processing;
import dev.unlegitdqrk.unlegitlibrary.reflections.GenericReflectClass;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import java.lang.annotation.Annotation;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import static net.bytebuddy.matcher.ElementMatchers.any;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
public class CallTracker<A extends Annotation> extends GenericReflectClass<A> {
private static final AtomicReference<Class<? extends Annotation>> atomicClass = new AtomicReference<>();
public CallTracker(CallInterceptor interceptor) {
super();
atomicClass.set(this.persistentClass);
}
public static void premain(String agentArgs, Instrumentation inst) {
ByteBuddyAgent.install();
new AgentBuilder.Default()
.type(any()) // instrument all classes, you can restrict here
.transform((builder, type, classLoader, module, protectionDomain) ->
builder.visit(Advice.to(CallInterceptor.class).on(isMethod()))).installOn(inst);
}
public abstract static class CallInterceptor {
private static final Set<CallInterceptor> interceptors = new HashSet<>();
public CallInterceptor() {
interceptors.add(this);
}
@Advice.OnMethodEnter
static void intercept(@Advice.Origin Method method) {
for (CallInterceptor interceptor : interceptors) {
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
if (stack.length <= 3) return;
StackTraceElement caller = stack[3];
interceptor.onCall(method, caller);
}
// if (method.isAnnotationPresent(atomicClass.get())) {
// StackTraceElement[] stack = Thread.currentThread().getStackTrace();
// // stack[0] = getStackTrace
// // stack[1] = intercept
// // stack[2] = Advice dispatcher
// // stack[3+] = your actual caller
// if (stack.length <= 3)
// return;
//
//
//
// StackTraceElement caller = stack[3];
//
// System.out.println("Annotated method " + method.getName()
// + " was called by " + caller.getClassName() + "." + caller.getMethodName());
//
// }
}
/**
* Code executed on any method call
*/
public abstract void onCall(Method method, StackTraceElement callerMethod);
}
}

View File

@@ -1,86 +0,0 @@
// Author: maple
// date: 9/29/25
package org.openautonomousconnection.protocol.annotations.processing;
import dev.unlegitdqrk.unlegitlibrary.reflections.annotation.processing.AnnotationProcessor;
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
import org.openautonomousconnection.protocol.exceptions.IncompatibleProtocolSideException;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
/**
* Process ProtocolInfo annotation and throw exception on mismatching annotation and ProtocolSide
*/
public class ProtocolInfoProcessing extends AnnotationProcessor<ProtocolInfo> {
private final CallTracker<ProtocolInfo> tracker;
private final AtomicReference<Set<Method>> methodReferences = new AtomicReference<>();
private final AtomicReference<Set<Class<?>>> typeReferences = new AtomicReference<>();
public ProtocolInfoProcessing() {
super("org.openautonomousconnection.protocol");
this.process();
this.methodReferences.set(this.annotatedMethods);
this.typeReferences.set(this.annotatedTypes);
this.tracker = new CallTracker<>(new CallTracker.CallInterceptor() {
@Override
public void onCall(Method method, StackTraceElement callerMethod) {
ProtocolVersion.ProtocolSide side, callerSide;
Object o;
if ((o = methodGetByName(callerMethod.getMethodName())) != null)
callerSide = ((Method) o).getAnnotation(ProtocolInfo.class).protocolSide();
else if ((o = typeHasAnnotation(callerMethod.getClassName())) != null)
callerSide = ((Class<?>) o).getAnnotation(ProtocolInfo.class).protocolSide();
else return;
if (methodReferences.get().contains(method))
side = method.getAnnotation(ProtocolInfo.class).protocolSide();
else if (typeReferences.get().contains(method.getDeclaringClass()))
side = method.getDeclaringClass().getAnnotation(ProtocolInfo.class).protocolSide();
else return;
if (callerSide.equals(ProtocolVersion.ProtocolSide.CLIENT) && !side.equals(callerSide))
throw new IncompatibleProtocolSideException(callerSide, side);
}
});
}
private Method methodGetByName(String methodName) {
for (Method method : this.annotatedMethods) if (method.getName().equals(methodName)) return method;
return null;
}
private Class<?> typeHasAnnotation(String typeName) {
for (Class<?> type : this.annotatedTypes) if (type.getName().equals(typeName)) return type;
return null;
}
@Override
protected void processType(Class<?> type) {
}
@Override
protected void processMethod(Method method) {
}
@Override
protected void processField(Field field) {
}
@Override
protected void processConstructor(Constructor constructor) {
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
*
* You are unauthorized to remove this copyright.
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
* See LICENSE-File if exists
*/
package org.openautonomousconnection.protocol.domain;
import dev.unlegitdqrk.unlegitlibrary.string.StringUtils;
import org.openautonomousconnection.protocol.utils.DomainUtils;
import java.io.Serializable;
public class Domain implements Serializable {
public final String name;
public final String topLevelDomain;
private final String destination;
private final String path;
public Domain(String name, String topLevelDomain, String destination, String path) {
if (path == null) path = "";
this.name = name;
this.topLevelDomain = topLevelDomain;
this.destination = destination;
this.path = path;
}
public final String realDestination() {
String tmpDestination = destination.endsWith("/") ? destination : destination + "/";
String tmpPath = getPath();
if (tmpPath == null) tmpPath = "";
if (tmpPath.startsWith("/")) tmpPath = tmpPath.substring("/".length());
if (tmpPath.endsWith("/")) tmpPath = tmpPath.substring(0, tmpPath.length() - "/".length());
return tmpDestination + tmpPath;
}
public final String getPath() {
if (path.endsWith("/")) return path.substring(0, path.length() - "/".length());
if (path.startsWith("/")) return path.substring("/".length());
return path;
}
public final String parsedDestination() {
if (destination.toLowerCase().startsWith("https://github.com/")) {
String base = "https://raw.githubusercontent.com/";
String username = DomainUtils.getPath(destination).split("/")[0];
String site = DomainUtils.getPath(destination).split("/")[1];
String tmpPath = getPath();
if (tmpPath == null || StringUtils.isEmptyString(tmpPath)) tmpPath = "index.html";
if (tmpPath.startsWith("/")) tmpPath = tmpPath.substring("/".length());
if (tmpPath.endsWith("/")) tmpPath = tmpPath.substring(0, tmpPath.length() - "/".length());
base = base + username + "/" + site + "/main/" + tmpPath;
return base;
}
return realDestination();
}
@Override
protected final Object clone() throws CloneNotSupportedException {
return new Domain(name, topLevelDomain, destination, path);
}
@Override
public final boolean equals(Object obj) {
if (!(obj instanceof Domain other)) return false;
return other.name.equalsIgnoreCase(name) && other.topLevelDomain.equalsIgnoreCase(topLevelDomain);
}
@Override
public final String toString() {
return "{parsed='" + parsedDestination() + "';real='" + realDestination() + "'}";
}
@Override
public final int hashCode() {
return super.hashCode();
}
}

View File

@@ -0,0 +1,15 @@
/*
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
*
* You are unauthorized to remove this copyright.
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
* See LICENSE-File if exists
*/
package org.openautonomousconnection.protocol.domain;
public class LocalDomain extends Domain {
public LocalDomain(String name, String endName, String path) {
super(name, endName, null, path);
}
}

View File

@@ -0,0 +1,18 @@
/*
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
*
* You are unauthorized to remove this copyright.
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
* See LICENSE-File if exists
*/
package org.openautonomousconnection.protocol.domain;
import java.io.Serializable;
public class RequestDomain extends Domain implements Serializable {
public RequestDomain(String name, String topLevelDomain, String path) {
super(name, topLevelDomain, null, path);
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
*
* You are unauthorized to remove this copyright.
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
* See LICENSE-File if exists
*/
package org.openautonomousconnection.protocol.events.v1_0_0;
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
import org.openautonomousconnection.protocol.ProtocolVersion;
import org.openautonomousconnection.protocol.domain.Domain;
import org.openautonomousconnection.protocol.domain.RequestDomain;
public class DomainPacketReceivedEvent extends Event {
public final ProtocolVersion protocolVersion;
public final Domain domain;
public final RequestDomain requestDomain;
public final int clientID;
public DomainPacketReceivedEvent(ProtocolVersion protocolVersion, Domain domain, RequestDomain requestDomain, int clientID) {
this.protocolVersion = protocolVersion;
this.domain = domain;
this.requestDomain = requestDomain;
this.clientID = clientID;
}
@Override
protected final Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public final boolean equals(Object obj) {
return super.equals(obj);
}
@Override
public final String toString() {
return super.toString();
}
@Override
public final int hashCode() {
return super.hashCode();
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
*
* You are unauthorized to remove this copyright.
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
* See LICENSE-File if exists
*/
package org.openautonomousconnection.protocol.events.v1_0_0;
import dev.unlegitdqrk.unlegitlibrary.event.impl.Event;
import org.openautonomousconnection.protocol.ProtocolVersion;
import org.openautonomousconnection.protocol.domain.Domain;
import org.openautonomousconnection.protocol.domain.RequestDomain;
public class PingPacketReceivedEvent extends Event {
public final ProtocolVersion protocolVersion;
public final Domain domain;
public final RequestDomain requestDomain;
public final boolean reachable;
public final int clientID;
public PingPacketReceivedEvent(ProtocolVersion protocolVersion, Domain domain, RequestDomain requestDomain, boolean reachable, int clientID) {
this.protocolVersion = protocolVersion;
this.domain = domain;
this.requestDomain = requestDomain;
this.reachable = reachable;
this.clientID = clientID;
}
@Override
protected final Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public final boolean equals(Object obj) {
return super.equals(obj);
}
@Override
public final String toString() {
return super.toString();
}
@Override
public final int hashCode() {
return super.hashCode();
}
}

View File

@@ -1,12 +0,0 @@
package org.openautonomousconnection.protocol.exceptions;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
/**
* Exception thrown when an unsupported protocol side method is called.
*/
public class IncompatibleProtocolSideException extends RuntimeException {
public IncompatibleProtocolSideException(ProtocolVersion.ProtocolSide callerSide, ProtocolVersion.ProtocolSide side) {
super(callerSide.name() + " is incompatible with called method of ProtocolSide " + side.name());
}
}

View File

@@ -1,27 +0,0 @@
package org.openautonomousconnection.protocol.exceptions;
/**
* Exception thrown when an unsupported protocol is encountered.
*/
public final class UnsupportedProtocolException extends RuntimeException {
public UnsupportedProtocolException() {
this("Selected protocol is not supported!");
}
public UnsupportedProtocolException(String message) {
super(message);
}
public UnsupportedProtocolException(String message, Throwable cause) {
super(message, cause);
}
public UnsupportedProtocolException(Throwable cause) {
super(cause);
}
public UnsupportedProtocolException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -1,51 +1,89 @@
/*
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
*
* You are unauthorized to remove this copyright.
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
* See LICENSE-File if exists
*/
package org.openautonomousconnection.protocol.listeners; package org.openautonomousconnection.protocol.listeners;
import dev.unlegitdqrk.unlegitlibrary.event.EventListener; import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
import dev.unlegitdqrk.unlegitlibrary.event.EventPriority;
import dev.unlegitdqrk.unlegitlibrary.event.Listener; import dev.unlegitdqrk.unlegitlibrary.event.Listener;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.ClientConnectedEvent; import org.openautonomousconnection.protocol.ProtocolBridge;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol; import org.openautonomousconnection.protocol.domain.LocalDomain;
import lombok.Getter; import org.openautonomousconnection.protocol.events.v1_0_0.DomainPacketReceivedEvent;
import org.openautonomousconnection.protocol.annotations.ProtocolInfo; import org.openautonomousconnection.protocol.events.v1_0_0.PingPacketReceivedEvent;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.AuthPacket; import org.openautonomousconnection.protocol.packets.v1_0_0.PingPacket;
import org.openautonomousconnection.protocol.side.client.ProtocolClient; import org.openautonomousconnection.protocol.utils.SiteType;
import org.openautonomousconnection.protocol.versions.ProtocolVersion; import org.openautonomousconnection.protocol.utils.WebsitesContent;
/** import java.io.BufferedReader;
* Listener for client-side events such as connection and disconnection. import java.io.IOException;
*/ import java.io.InputStreamReader;
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.CLIENT) import java.net.HttpURLConnection;
public final class ClientListener extends EventListener { import java.net.URL;
/** public class ClientListener extends EventListener {
* The reference to the ProtocolClient object
*/
@Getter
private final ProtocolClient client;
/** @Listener
* Sets the client variable public void onDomain(DomainPacketReceivedEvent event) {
* boolean exists = event.domain != null;
* @param client The Instance of the ProtocolClient
*/ if (exists) {
public ClientListener(ProtocolClient client) { try {
this.client = client; if (!ProtocolBridge.getInstance().getProtocolClient().getClient().sendPacket(new PingPacket(event.requestDomain, event.domain, false))) {
ProtocolBridge.getInstance().getProtocolClient().handleHTMLContent(SiteType.PROTOCOL, new LocalDomain("error-occurred", "html", ""),
WebsitesContent.ERROR_OCCURRED(event.domain.toString() + "/" + event.domain.getPath()));
}
} catch (IOException | ClassNotFoundException e) {
ProtocolBridge.getInstance().getProtocolClient().handleHTMLContent(SiteType.PROTOCOL, new LocalDomain("error-occurred", "html", ""),
WebsitesContent.ERROR_OCCURRED(event.domain.toString() + "/" + event.domain.getPath() + "\n" + e.getMessage()));
}
} else ProtocolBridge.getInstance().getProtocolClient().handleHTMLContent(SiteType.PROTOCOL, new LocalDomain("domain-not-found", "html", ""), WebsitesContent.DOMAIN_NOT_FOUND);
} }
/** @Listener
* Handles the event when a client connects. public void onPing(PingPacketReceivedEvent event) {
* Sends an authentication packet to the server. if (event.reachable) {
* String destination = event.domain.parsedDestination();
* @param event The client connected event.
*/ try {
@Listener(priority = EventPriority.HIGHEST) URL url = new URL(destination);
public void onConnect(ClientConnectedEvent event) { HttpURLConnection connection2 = (HttpURLConnection) url.openConnection();
try { connection2.setRequestMethod("GET");
event.getClient().sendPacket(new AuthPacket(client.getProtocolBridge()), TransportProtocol.TCP);
} catch (Exception exception) { StringBuilder content = new StringBuilder();
client.getProtocolBridge().getProtocolValues().logger.exception("Failed to send auth packet", exception); try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection2.getInputStream()))) {
event.getClient().disconnect(); String line;
} while ((line = reader.readLine()) != null) content.append(line);
}
ProtocolBridge.getInstance().getProtocolClient().handleHTMLContent(SiteType.PUBLIC, event.domain, content.toString());
} catch (IOException exception) {
ProtocolBridge.getInstance().getProtocolClient().handleHTMLContent(SiteType.PROTOCOL, new LocalDomain("error-occurred", "html", ""),
WebsitesContent.ERROR_OCCURRED(exception.getMessage().replace(event.domain.parsedDestination(), event.domain.toString() + "/" + event.domain.getPath())));
}
} else ProtocolBridge.getInstance().getProtocolClient().handleHTMLContent(SiteType.PROTOCOL, new LocalDomain("error-not-reached", "html", ""), WebsitesContent.DOMAIN_NOT_REACHABLE);
} }
@Override
protected final Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public final boolean equals(Object obj) {
return super.equals(obj);
}
@Override
public final String toString() {
return super.toString();
}
@Override
public final int hashCode() {
return super.hashCode();
}
} }

View File

@@ -1,146 +0,0 @@
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

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
*
* You are unauthorized to remove this copyright.
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
* See LICENSE-File if exists
*/
package org.openautonomousconnection.protocol.listeners;
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
public class ServerListener extends EventListener {
@Override
protected final Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public final boolean equals(Object obj) {
return super.equals(obj);
}
@Override
public final String toString() {
return super.toString();
}
@Override
public final int hashCode() {
return super.hashCode();
}
}

View File

@@ -1,163 +0,0 @@
package org.openautonomousconnection.protocol.packets;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
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.util.*;
/**
* Abstract class representing a packet in the Open Autonomous Connection (OAC) protocol.
* This class extends the base Packet class and includes additional functionality specific to OAC.
*/
public abstract class OACPacket extends Packet {
/**
* The protocol version associated with this packet.
*/
@Getter
private final List<ProtocolVersion> compatibleVersions;
private final int id;
/**
* The response code for the packet, defaulting to RESPONSE_NOT_REQUIRED.
*/
private INSResponseStatus responseCode = INSResponseStatus.RESPONSE_NOT_REQUIRED;
/**
* Constructor for OACPacket.
*
* @param id The unique identifier for the packet.
* @param supportedVersions The protocol version associated with this packet.
*/
public OACPacket(int id, ProtocolVersion... supportedVersions) {
this.id = id;
this.compatibleVersions = List.of(supportedVersions);
}
@Override
public int getPacketID() {
return id;
}
/**
* Gets the response code for the packet.
*
* @return The INSResponseCode associated with the packet.
*/
protected final INSResponseStatus getResponseCode() {
return responseCode;
}
/**
* Sets the response code for the packet.
*
* @param responseCode The INSResponseCode to set for the packet.
*/
protected final void setResponseCode(INSResponseStatus responseCode) {
this.responseCode = responseCode;
}
/**
* Writes the packet data to the output stream.
*
* @param outputStream The output stream to write the packet data to.
* @throws IOException If an I/O error occurs.
*/
@Override
public final void write(DataOutputStream outputStream) throws IOException {
// Write the specific packet data
onWrite(outputStream);
// Write the response code if the protocol version is not classic
if (!compatibleVersions.contains(ProtocolVersion.PV_1_0_0_CLASSIC)) outputStream.writeUTF(responseCode.name());
}
@Override
public final void read(DataInputStream inputStream, UUID clientID) throws IOException {
// Read the specific packet data
onRead(inputStream, clientID);
// Read the response code if the protocol version is not classic
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(inputStream, clientID);
}
/**
* Abstract method to be implemented by subclasses for writing specific packet data.
*
* @param outputStream The output stream to write the packet data to.
* @throws IOException If an I/O error occurs.
*/
public abstract void onWrite(DataOutputStream outputStream) throws IOException;
/**
* Abstract method to be implemented by subclasses for reading specific packet data.
*
* @param inputStream The input stream to read the packet data from.
* @throws IOException If an I/O error occurs.
*/
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 inputStream The input stream from which the response code was read.
*/
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

@@ -0,0 +1,102 @@
/*
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
*
* You are unauthorized to remove this copyright.
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
* See LICENSE-File if exists
*/
package org.openautonomousconnection.protocol.packets.v1_0_0;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.ProtocolVersion;
import org.openautonomousconnection.protocol.domain.Domain;
import org.openautonomousconnection.protocol.domain.RequestDomain;
import org.openautonomousconnection.protocol.events.v1_0_0.DomainPacketReceivedEvent;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.sql.SQLException;
public class DomainPacket extends Packet {
private int clientID;
private RequestDomain requestDomain;
private Domain domain;
private ProtocolVersion protocolVersion;
public DomainPacket(RequestDomain requestDomain, Domain domain) {
this();
this.requestDomain = requestDomain;
this.domain = domain;
}
public DomainPacket() {
super(2);
}
@Override
public void write(PacketHandler packetHandler, ObjectOutputStream objectOutputStream) throws IOException, ClassNotFoundException {
protocolVersion = ProtocolBridge.getInstance().getProtocolVersion();
if (ProtocolBridge.getInstance().isRunningAsServer()) {
objectOutputStream.writeInt(clientID);
objectOutputStream.writeObject(requestDomain);
objectOutputStream.writeObject(domain);
} else {
clientID = ProtocolBridge.getInstance().getProtocolClient().getClient().getClientID();
objectOutputStream.writeInt(clientID);
objectOutputStream.writeObject(requestDomain);
}
objectOutputStream.writeObject(protocolVersion);
}
@Override
public void read(PacketHandler packetHandler, ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
if (ProtocolBridge.getInstance().isRunningAsServer()) {
clientID = objectInputStream.readInt();
requestDomain = (RequestDomain) objectInputStream.readObject();
protocolVersion = (ProtocolVersion) objectInputStream.readObject();
try {
domain = ProtocolBridge.getInstance().getProtocolServer().getDomain(requestDomain);
} catch (SQLException exception) {
exception.printStackTrace();
}
ProtocolBridge.getInstance().getProtocolServer().getServer().getEventManager().executeEvent(new DomainPacketReceivedEvent(protocolVersion, domain, requestDomain, clientID));
ProtocolBridge.getInstance().getProtocolServer().getServer().getConnectionHandlerByID(clientID).sendPacket(new DomainPacket(requestDomain, domain));
} else {
clientID = objectInputStream.readInt();
requestDomain = (RequestDomain) objectInputStream.readObject();
domain = (Domain) objectInputStream.readObject();
protocolVersion = (ProtocolVersion) objectInputStream.readObject();
ProtocolBridge.getInstance().getProtocolClient().getClient().getEventManager().executeEvent(new DomainPacketReceivedEvent(protocolVersion, domain, requestDomain, clientID));
}
}
@Override
protected final Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public final boolean equals(Object obj) {
return super.equals(obj);
}
@Override
public final String toString() {
return super.toString();
}
@Override
public final int hashCode() {
return super.hashCode();
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
*
* You are unauthorized to remove this copyright.
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
* See LICENSE-File if exists
*/
package org.openautonomousconnection.protocol.packets.v1_0_0;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.ProtocolVersion;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class MessagePacket extends Packet {
private ProtocolVersion protocolVersion;
private String message;
private int clientID;
public MessagePacket(int id, String message) {
super(id);
this.message = message;
}
public MessagePacket() {
super(3);
}
@Override
public void write(PacketHandler packetHandler, ObjectOutputStream objectOutputStream) throws IOException, ClassNotFoundException {
protocolVersion = ProtocolBridge.getInstance().getProtocolVersion();
if (ProtocolBridge.getInstance().isRunningAsServer()) objectOutputStream.writeInt(clientID);
else {
clientID = ProtocolBridge.getInstance().getProtocolClient().getClient().getClientID();
objectOutputStream.writeInt(clientID);
}
objectOutputStream.writeUTF(message);
objectOutputStream.writeObject(protocolVersion);
}
@Override
public void read(PacketHandler packetHandler, ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
if (ProtocolBridge.getInstance().isRunningAsServer()) {
int clientID = objectInputStream.readInt();
String message = objectInputStream.readUTF();
protocolVersion = (ProtocolVersion) objectInputStream.readObject();
ProtocolBridge.getInstance().getProtocolServer().handleMessage(ProtocolBridge.getInstance().getProtocolServer().getServer().getConnectionHandlerByID(clientID), message);
} else {
int clientID = objectInputStream.readInt();
String message = objectInputStream.readUTF();
protocolVersion = (ProtocolVersion) objectInputStream.readObject();
ProtocolBridge.getInstance().getProtocolClient().handleMessage(message);
}
}
@Override
protected final Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public final boolean equals(Object obj) {
return super.equals(obj);
}
@Override
public final String toString() {
return super.toString();
}
@Override
public final int hashCode() {
return super.hashCode();
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
*
* You are unauthorized to remove this copyright.
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
* See LICENSE-File if exists
*/
package org.openautonomousconnection.protocol.packets.v1_0_0;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.ProtocolVersion;
import org.openautonomousconnection.protocol.domain.Domain;
import org.openautonomousconnection.protocol.domain.RequestDomain;
import org.openautonomousconnection.protocol.events.v1_0_0.PingPacketReceivedEvent;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.sql.SQLException;
public class PingPacket extends Packet {
private RequestDomain requestDomain;
private Domain domain;
private int clientID;
private boolean reachable;
private ProtocolVersion protocolVersion;
public PingPacket(RequestDomain requestDomain, Domain domain, boolean reachable) {
this();
this.requestDomain = requestDomain;
this.domain = domain;
this.reachable = reachable;
}
public PingPacket() {
super(1);
}
@Override
public void write(PacketHandler packetHandler, ObjectOutputStream objectOutputStream) throws IOException, ClassNotFoundException {
protocolVersion = ProtocolBridge.getInstance().getProtocolVersion();
if (ProtocolBridge.getInstance().isRunningAsServer()) {
objectOutputStream.writeInt(clientID);
objectOutputStream.writeObject(requestDomain);
objectOutputStream.writeObject(domain);
objectOutputStream.writeBoolean(reachable);
} else {
clientID = ProtocolBridge.getInstance().getProtocolClient().getClient().getClientID();
objectOutputStream.writeInt(clientID);
objectOutputStream.writeObject(requestDomain);
}
objectOutputStream.writeObject(protocolVersion);
}
@Override
public void read(PacketHandler packetHandler, ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
if (ProtocolBridge.getInstance().isRunningAsServer()) {
clientID = objectInputStream.readInt();
requestDomain = (RequestDomain) objectInputStream.readObject();
protocolVersion = (ProtocolVersion) objectInputStream.readObject();
try {
domain = ProtocolBridge.getInstance().getProtocolServer().ping(requestDomain);
} catch (SQLException exception) {
exception.printStackTrace();
}
reachable = domain != null;
ProtocolBridge.getInstance().getProtocolServer().getServer().getEventManager().executeEvent(new PingPacketReceivedEvent(protocolVersion, domain, requestDomain, reachable, clientID));
ProtocolBridge.getInstance().getProtocolServer().getServer().getConnectionHandlerByID(clientID).sendPacket(new PingPacket(requestDomain, domain, reachable));
} else {
clientID = objectInputStream.readInt();
requestDomain = (RequestDomain) objectInputStream.readObject();
domain = (Domain) objectInputStream.readObject();
boolean reachable = objectInputStream.readBoolean();
protocolVersion = (ProtocolVersion) objectInputStream.readObject();
ProtocolBridge.getInstance().getProtocolClient().getClient().getEventManager().executeEvent(new PingPacketReceivedEvent(protocolVersion, domain, requestDomain, reachable, clientID));
}
}
@Override
protected final Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public final boolean equals(Object obj) {
return super.equals(obj);
}
@Override
public final String toString() {
return super.toString();
}
@Override
public final int hashCode() {
return super.hashCode();
}
}

View File

@@ -1,223 +0,0 @@
package org.openautonomousconnection.protocol.packets.v1_0_0.beta;
import dev.unlegitdqrk.unlegitlibrary.file.FileUtils;
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.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.security.NoSuchAlgorithmException;
import java.util.UUID;
/**
* Authentication packet used between client and INS/Web servers.
*
* <p>Responsibilities:
* <ul>
* <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>
*/
public final class AuthPacket extends OACPacket {
private ProtocolBridge protocolBridge;
/**
* Creates a new authentication packet for sending CA data or client identity.
*
* @param protocolBridge The protocol context of the current instance.
*/
public AuthPacket(ProtocolBridge protocolBridge) {
super(8, ProtocolVersion.PV_1_0_0_BETA, ProtocolVersion.PV_1_0_1_BETA);
this.protocolBridge = protocolBridge;
}
@Override
public void onWrite(DataOutputStream objectOutputStream) throws IOException {
if (protocolBridge.isRunningAsINSServer()) {
objectOutputStream.writeBoolean(true);
objectOutputStream.writeUTF(protocolBridge.getProtocolValues().protocolVersion.name());
String caPem = "N/A";
try {
String caPrefix = protocolBridge.getProtocolServer().getFolderStructure().getCaPrefix()
+ NetworkUtils.getPublicIPAddress();
objectOutputStream.writeUTF(caPrefix);
caPem = FileUtils.readFileFull(new File(
protocolBridge.getProtocolServer().getFolderStructure().publicCAFolder,
caPrefix + ".pem"));
} catch (Exception exception) {
protocolBridge.getProtocolValues().logger.exception("Failed to read ca-files", exception);
setResponseCode(INSResponseStatus.RESPONSE_AUTH_FAILED);
}
objectOutputStream.writeUTF(caPem);
return;
}
if (protocolBridge.isRunningAsServer()) {
objectOutputStream.writeBoolean(false);
objectOutputStream.writeUTF(protocolBridge.getProtocolValues().protocolVersion.name());
return;
}
if (protocolBridge.isRunningAsClient()) {
UUID clientConnectionId = null;
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();
}
}
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,92 +0,0 @@
package org.openautonomousconnection.protocol.packets.v1_0_0.beta;
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.util.UUID;
/**
* Packet used by clients to query INS records from an INS server.
* <p>
* Contains all information required for resolving an InfoName:
* <ul>
* <li>TLN</li>
* <li>Name</li>
* <li>Optional subname </li>
* <li>Record type</li>
* <li>Client ID for routing responses</li>
* </ul>
*/
public final class INSQueryPacket extends OACPacket {
@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.
*
* @param tln The top-level namespace.
* @param name The InfoName.
* @param sub Optional subname ("www") or null.
* @param type Record type requested.
* @param clientId Sender client ID for routing.
*/
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;
this.clientId = clientId;
}
/**
* Registration constructor
*/
public INSQueryPacket() {
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(DataOutputStream out) throws IOException {
out.writeUTF(TLN);
out.writeUTF(name);
out.writeBoolean(sub != null);
if (sub != null) out.writeUTF(sub);
out.writeUTF(type.name());
out.writeUTF(clientId.toString());
}
/**
* Deserializes the INS query from the stream.
*/
@Override
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.valueOf(in.readUTF());
clientId = UUID.fromString(in.readUTF());
}
}

View File

@@ -1,108 +0,0 @@
package org.openautonomousconnection.protocol.packets.v1_0_0.beta;
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.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* Response packet returned by an INS server after resolving a query.
* <p>
* Contains:
* <ul>
* <li>Status code ({@link INSResponseStatus})</li>
* <li>List of resolved {@link INSRecord} entries</li>
* <li>The ID of the requesting client</li>
* </ul>
* On the client side, {@link org.openautonomousconnection.protocol.side.client.ProtocolClient#onResponse(INSResponseStatus, List)}
* is automatically invoked through {@link #onResponseCodeRead}.
*/
public final class INSResponsePacket extends OACPacket {
@Getter
private final ProtocolBridge bridge;
@Getter
private INSResponseStatus status;
@Getter
private List<INSRecord> records;
@Getter
private UUID clientId;
/**
* Creates a populated response packet.
*
* @param status The resolution status.
* @param records List of resolved records.
* @param clientId ID of requesting client.
* @param bridge Protocol runtime context.
*/
public INSResponsePacket(INSResponseStatus status, List<INSRecord> records, UUID clientId, ProtocolBridge bridge) {
this(bridge);
this.status = status;
this.records = records;
this.clientId = clientId;
}
/**
* Registration Constructor
*
* @param bridge Protocol runtime context.
*/
public INSResponsePacket(ProtocolBridge bridge) {
super(6, ProtocolVersion.PV_1_0_0_BETA, ProtocolVersion.PV_1_0_1_BETA);
this.bridge = bridge;
}
/**
* Serializes the response status, records and client ID.
*/
@Override
public void onWrite(DataOutputStream out) throws IOException {
out.writeUTF(status.name());
out.writeInt(records.size());
for (INSRecord rec : records) {
writeObject(out, rec);
}
out.writeUTF(clientId.toString());
}
/**
* Deserializes the response, reconstructing the record list.
*/
@Override
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) readObject(in));
}
clientId = UUID.fromString(in.readUTF());
}
/**
* Called after reading the server's response code.
* <p>
* If running on a client, forwards the result to the client-side API.
*/
@Override
protected void onResponseCodeRead(DataInputStream in, UUID clientID) {
if (bridge.isRunningAsClient()) {
bridge.getProtocolClient().onResponse(status, records);
}
}
}

View File

@@ -1,84 +0,0 @@
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

@@ -1,83 +0,0 @@
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

@@ -1,43 +0,0 @@
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

@@ -1,35 +0,0 @@
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

@@ -1,77 +0,0 @@
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,67 +0,0 @@
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

@@ -1,54 +0,0 @@
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

@@ -1,48 +0,0 @@
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

@@ -1,46 +0,0 @@
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

@@ -1,48 +0,0 @@
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

@@ -1,85 +0,0 @@
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

@@ -1,113 +0,0 @@
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

@@ -1,83 +0,0 @@
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

@@ -1,52 +0,0 @@
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

@@ -1,50 +0,0 @@
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

@@ -1,59 +0,0 @@
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

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
*
* You are unauthorized to remove this copyright.
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
* See LICENSE-File if exists
*/
package org.openautonomousconnection.protocol.side;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.NetworkClient;
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.domain.Domain;
import org.openautonomousconnection.protocol.domain.RequestDomain;
import org.openautonomousconnection.protocol.listeners.ClientListener;
import org.openautonomousconnection.protocol.listeners.ServerListener;
import org.openautonomousconnection.protocol.packets.v1_0_0.DomainPacket;
import org.openautonomousconnection.protocol.utils.SiteType;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
public abstract class ProtocolClient extends DefaultMethodsOverrider {
private NetworkClient client;
private ProtocolBridge protocolBridge;
public abstract void handleHTMLContent(SiteType siteType, Domain domain, String htmlContent);
public abstract void handleMessage(String message);
public final NetworkClient getClient() {
return client;
}
public final ProtocolBridge getProtocolBridge() {
return protocolBridge;
}
public final void setProtocolBridge(ProtocolBridge protocolBridge) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
this.protocolBridge = protocolBridge;
client = new NetworkClient.ClientBuilder()
.setEventManager(protocolBridge.getProtocolSettings().eventManager).setPacketHandler(protocolBridge.getProtocolSettings().packetHandler)
.setPort(protocolBridge.getProtocolSettings().port).setHost(protocolBridge.getProtocolSettings().host).
build();
}
public final void startClient() throws Exception {
client.getEventManager().unregisterListener(ServerListener.class);
client.getEventManager().registerListener(ClientListener.class);
client.connect();
}
public final void disconnectClient() throws IOException {
client.getEventManager().unregisterListener(ClientListener.class);
client.disconnect();
}
public final void resolveSite(RequestDomain requestDomain) throws IOException, ClassNotFoundException {
if (!client.isConnected()) return;
client.sendPacket(new DomainPacket(requestDomain, null));
}
}

View File

@@ -0,0 +1,132 @@
/*
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
*
* You are unauthorized to remove this copyright.
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
* See LICENSE-File if exists
*/
package org.openautonomousconnection.protocol.side;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.ConnectionHandler;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.domain.Domain;
import org.openautonomousconnection.protocol.domain.RequestDomain;
import org.openautonomousconnection.protocol.listeners.ClientListener;
import org.openautonomousconnection.protocol.listeners.ServerListener;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.*;
import java.sql.SQLException;
import java.util.List;
public abstract class ProtocolServer extends DefaultMethodsOverrider {
public abstract List<Domain> getDomains() throws SQLException;
public abstract List<String> getTopLevelDomains() throws SQLException;
public abstract void handleMessage(ConnectionHandler connectionHandler, String message);
public abstract String getDNSServerInfoSite() throws SQLException;
public abstract String getInfoSite(String topLevelDomain) throws SQLException;
public abstract String getInterfaceSite() throws SQLException;
private NetworkServer server;
private ProtocolBridge protocolBridge;
public final void setProtocolBridge(ProtocolBridge protocolBridge) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
this.protocolBridge = protocolBridge;
server = new NetworkServer.ServerBuilder()
.setEventManager(protocolBridge.getProtocolSettings().eventManager).setPacketHandler(protocolBridge.getProtocolSettings().packetHandler)
.setPort(protocolBridge.getProtocolSettings().port).build();
}
public final ProtocolBridge getProtocolBridge() {
return protocolBridge;
}
public final boolean startServer() throws Exception {
server.getEventManager().registerListener(ServerListener.class);
server.getEventManager().unregisterListener(ClientListener.class);
return server.start();
}
public final NetworkServer getServer() {
return server;
}
public final boolean stopServer() throws IOException {
server.getEventManager().unregisterListener(ServerListener.class);
return server.stop();
}
public final Domain ping(RequestDomain requestDomain) throws SQLException {
Domain domain = getDomain(requestDomain);
boolean reachable = false;
String destination = domain.parsedDestination();
if (!destination.startsWith("http://") && !destination.startsWith("https://")) destination = "http://" + destination;
HttpURLConnection connection = null;
try {
URL u = new URL(destination);
connection = (HttpURLConnection) u.openConnection();
connection.setRequestMethod("HEAD");
int code = connection.getResponseCode();
reachable = code == 200;
} catch (IOException exception) {
InetAddress address = null;
try {
InetAddress address1 = InetAddress.getByName(destination);
String ip = address1.getHostAddress();
address = InetAddress.getByName(ip);
reachable = address.isReachable(10000);
} catch (IOException exc) {
reachable = false;
exc.printStackTrace();
}
reachable = false;
exception.printStackTrace();
} finally {
if (connection != null) connection.disconnect();
}
return domain;
}
public final boolean domainExists(RequestDomain domain) throws SQLException {
return getDomain(domain) != null;
}
public final boolean domainExists(Domain domain) throws SQLException {
return domainExists(new RequestDomain(domain.name, domain.topLevelDomain, domain.getPath()));
}
public final Domain getDomain(RequestDomain domain) throws SQLException {
return getDomain(domain.name, domain.topLevelDomain, domain.getPath());
}
public final boolean topLevelDomainExists(String topLevelDomain) throws SQLException {
return topLevelDomain.equalsIgnoreCase("oac") || getTopLevelDomains().contains(topLevelDomain);
}
public final Domain getDomain(String name, String topLevelDomain, String path) throws SQLException {
if (!topLevelDomainExists(topLevelDomain)) return null;
if (name.equalsIgnoreCase("info") && topLevelDomain.equalsIgnoreCase("oac")) return new Domain(name, topLevelDomain, getDNSServerInfoSite(), path);
if (name.equalsIgnoreCase("interface") && topLevelDomain.equalsIgnoreCase("oac")) return new Domain(name, topLevelDomain, getInterfaceSite(), path);
if (name.equalsIgnoreCase("info")) return new Domain(name, topLevelDomain, getInfoSite(topLevelDomain), path);
for (Domain domain : getDomains()) if (domain.name.equals(name) && domain.topLevelDomain.equals(topLevelDomain)) return new Domain(domain.name, domain.topLevelDomain, domain.realDestination(), path);
return null;
}
}

View File

@@ -1,395 +0,0 @@
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.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;
import org.openautonomousconnection.protocol.packets.OACPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSQueryPacket;
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.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 servers.
*/
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.CLIENT)
public abstract class ProtocolClient extends EventListener {
@Getter
private final ClientCertificateFolderStructure folderStructure;
private NetworkClient clientToINS;
private NetworkClient clientToServer;
@Getter
private ProtocolBridge protocolBridge;
private ProtocolVersion insVersion = null;
private ProtocolVersion serverVersion = null;
public ProtocolClient() {
folderStructure = new ClientCertificateFolderStructure();
}
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;
}
private void checkFileExists(File folder, String prefix, String extension) throws CertificateException, IOException {
if (folder == null) throw new FileNotFoundException("Folder does not exist");
File[] files = folder.listFiles();
if (files == null || files.length == 0)
throw new FileNotFoundException("Folder " + folder.getAbsolutePath() + " is empty");
for (File file : files) {
if (!file.getName().startsWith(prefix))
throw new CertificateException(file.getAbsolutePath() + " is not valid");
}
}
public final ProtocolVersion getServerVersion() {
return serverVersion == null ? ProtocolVersion.PV_1_0_0_CLASSIC : serverVersion;
}
public final void setServerVersion(ProtocolVersion serverVersion) {
if (this.serverVersion == null) this.serverVersion = serverVersion;
}
public final ProtocolVersion getInsVersion() {
return insVersion == null ? ProtocolVersion.PV_1_0_0_CLASSIC : insVersion;
}
public final void setInsVersion(ProtocolVersion insVersion) {
if (this.insVersion == null) this.insVersion = insVersion;
}
@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()) {
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.STABLE;
if (yes) break;
}
return isINSStableServer() || yes;
}
public final boolean isINSBetaServer() {
return getInsVersion().getProtocolType() == ProtocolVersion.ProtocolType.BETA;
}
public final boolean supportINSServerBeta() {
boolean yes = false;
for (ProtocolVersion compatibleVersion : getInsVersion().getCompatibleVersions()) {
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.BETA;
if (yes) break;
}
return isINSBetaServer() || yes;
}
public final boolean isINSClassicServer() {
return getInsVersion().getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
}
public final boolean supportINSServerClassic() {
boolean yes = false;
for (ProtocolVersion compatibleVersion : getInsVersion().getCompatibleVersions()) {
yes = compatibleVersion.getProtocolType() == ProtocolVersion.ProtocolType.CLASSIC;
if (yes) break;
}
return isINSClassicServer() || yes;
}
public final boolean supportINSServerPacket(OACPacket packet) {
boolean compatible = false;
for (ProtocolVersion compatibleVersion : packet.getCompatibleVersions()) {
if (!compatible) compatible = supportINSServerVersion(compatibleVersion);
}
return compatible;
}
public final boolean supportINSServerVersion(ProtocolVersion targetVersion) {
return getInsVersion() == targetVersion || getInsVersion().getCompatibleVersions().contains(targetVersion);
}
public final boolean supportINSServerProtocol(ProtocolVersion.Protocol protocol) {
boolean yes = false;
for (ProtocolVersion compatibleVersion : getInsVersion().getCompatibleVersions()) {
yes = compatibleVersion.getSupportedProtocols().contains(protocol);
if (yes) break;
}
return getInsVersion().getSupportedProtocols().contains(protocol) || yes;
}
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;
}
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().getUniqueID()),
TransportProtocol.TCP
);
onQuerySent(tln, name, sub, type);
}
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) {
}
public void onQuerySent(String tln, String name, String sub, INSRecordType type) {
}
/**
* 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 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;
public final File privateFolder;
public final File privateCAFolder;
public final File privateClientFolder;
public final File publicCAFolder;
public final File publicClientFolder;
public ClientCertificateFolderStructure() {
certificatesFolder = new File("certificates");
publicFolder = new File(certificatesFolder, "public");
privateFolder = new File(certificatesFolder, "private");
privateCAFolder = new File(privateFolder, "ca");
privateClientFolder = new File(privateFolder, "client");
publicCAFolder = new File(publicFolder, "ca");
publicClientFolder = new File(publicFolder, "client");
if (!certificatesFolder.exists()) certificatesFolder.mkdirs();
if (!publicFolder.exists()) publicFolder.mkdirs();
if (!privateFolder.exists()) privateFolder.mkdirs();
if (!privateCAFolder.exists()) privateCAFolder.mkdirs();
if (!privateClientFolder.exists()) privateClientFolder.mkdirs();
if (!publicCAFolder.exists()) publicCAFolder.mkdirs();
if (!publicClientFolder.exists()) publicClientFolder.mkdirs();
}
}
}

View File

@@ -1,407 +0,0 @@
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

@@ -1,25 +0,0 @@
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 ConnectedToProtocolINSServerEvent extends Event {
/**
* Reference to the ProtocolClient object.
*/
@Getter
private final ProtocolClient client;
public ConnectedToProtocolINSServerEvent(ProtocolClient client) {
this.client = client;
}
}

View File

@@ -1,25 +0,0 @@
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,156 +0,0 @@
package org.openautonomousconnection.protocol.side.ins;
import dev.unlegitdqrk.unlegitlibrary.file.FileUtils;
import dev.unlegitdqrk.unlegitlibrary.network.utils.NetworkUtils;
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.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.List;
/**
* Abstract class representing a INS server in the protocol.
*/
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.INS)
public abstract class ProtocolINSServer extends ProtocolCustomServer {
/**
* Gets the INS information site URL from the configuration.
*
* @return The INS information site URL.
*/
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);
}
/**
* Resolves a request for an INS record based on TLN, name, subname and record type.
* <p>
* The implementation should:
* <ul>
* <li>Locate the corresponding InfoName in storage</li>
* <li>Return all matching {@link INSRecord} entries</li>
* <li>Handle CNAME recursion, TTL rules, and filtering for the requested type</li>
* </ul>
*
* @param tln The top-level name.
* @param name The InfoName.
* @param sub The optional subname , may be {@code null}.
* @param type The INS record type being requested.
* @return A list of resolved INS records. May be empty if no record exists.
*/
public abstract List<INSRecord> resolve(String tln, String name, String sub, INSRecordType type);
/**
* Callback fired whenever the INS server receives a query packet.
* <p>This method is optional.</p>
*
* @param tln The top-level name of the request.
* @param name The InfoName being queried.
* @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) {
}
/**
* Callback fired after an INS response was successfully sent to the client.
*
* @param tln The requested TLN.
* @param name The InfoName.
* @param sub Optional subname or {@code null}.
* @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) {
}
/**
* Callback fired when an INS response could not be delivered to the client.
*
* @param tln The requested TLN.
* @param name The InfoName.
* @param sub Optional subname.
* @param type The record type requested.
* @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) {
}
/**
* Resolves the information endpoint for a given Top-Level Name (TLN).
*
* <p>This method is part of the INS server's internal resolution logic.
* Each TLN
* need to define a special "info site" which provides metadata, documentation,
* or administrative information for that TLN.
*
* <p>The returned string must always be in the format:
* <pre>
* host:port
* </pre>
*
* <p>This method is used automatically by the INS protocol handler when a client
* queries an InfoName of the form:
* <pre>
* info.&lt;tln&gt;
* </pre>
*
* <p>If no TLN-specific info endpoint exists or the TLN does not exist, the implementation can:
* <ul>
* <li>return <code>null</code> to signal that no info site is registered</li>
* </ul>
*
* @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.
*/
public abstract String resolveTLNInfoSite(String tln);
}

View File

@@ -1,197 +0,0 @@
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.network.system.server.ConnectedClient;
import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.client.S_ClientDisconnectedEvent;
import lombok.Getter;
import org.openautonomousconnection.protocol.packets.OACPacket;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
public class CustomConnectedClient extends EventListener {
@Getter
private final ConnectedClient connection;
@Getter
private final ProtocolCustomServer server;
private ProtocolVersion clientVersion = null;
@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.
*
* @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) return;
if (this.clientVersion != null) return;
this.clientVersion = clientVersion;
this.clientVersionLoaded = true;
}
/**
* 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) {
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 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;
}
}

View File

@@ -1,332 +0,0 @@
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

@@ -1,21 +0,0 @@
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

@@ -1,21 +0,0 @@
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,256 +0,0 @@
package org.openautonomousconnection.protocol.side.web;
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.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 java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
* 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 abstract class ProtocolWebServer extends ProtocolWebServer_1_0_0_B {
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 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 authFile, File rulesFile, int sessionExpire, int uploadSize) throws Exception {
super(authFile, rulesFile, sessionExpire, uploadSize);
}
@Listener(priority = EventPriority.HIGHEST)
public final void onPacketRead(C_PacketReadEvent event) {
Objects.requireNonNull(event, "event");
Object packet = event.getPacket();
if (packet == null) return;
CustomConnectedClient client = getClientByID(event.getClient().getUniqueID());
if (client == null) return;
try {
if (packet instanceof WebNavigateRequestPacket nav) {
WebNavigateAckPacket ack = handleNavigate(client, nav);
client.getConnection().sendPacket(ack, TransportProtocol.TCP);
return;
}
if (packet instanceof WebResourceRequestPacket req) {
WebResourceResponsePacket resp = handleResource(client, req);
client.getConnection().sendPacket(resp, TransportProtocol.TCP);
return;
}
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);
}
}
/**
* Server-side dispatcher entry point for a navigation request (packet id 01).
*
* @param client connected client
* @param request navigation request
* @return navigation ack to send back
*/
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;
}
/**
* Server-side dispatcher entry point for a resource request (packet id 03).
*
* <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>
*
* @param client connected client
* @param request resource request
* @return resource response packet to send back
*/
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;
}
/**
* Server-side dispatcher entry point for a document apply request (packet id 20).
*
* @param client connected client
* @param request apply request
* @return apply response
*/
public final WebDocumentApplyResponsePacket handleDocumentApply(CustomConnectedClient client, WebDocumentApplyRequestPacket request) {
Objects.requireNonNull(client, "client");
Objects.requireNonNull(request, "request");
WebDocumentApplyResponsePacket resp = onDocumentApplyRequest(client, request);
if (resp == null) {
resp = new WebDocumentApplyResponsePacket(
mirrorHeader(request.getHeader(), 0),
false,
"onDocumentApplyRequest returned null"
);
}
return resp;
}
/**
* 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)
*/
protected abstract WebNavigateAckPacket onNavigateRequest(CustomConnectedClient client, WebNavigateRequestPacket request);
/**
* 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);
/**
* 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);
/**
* 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");
// 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;
WebResourceRequestPacket mapped = WebCompatMapper.toResourceRequest(requestId, tabId, pageId, request, getProtocolBridge());
WebResourceResponsePacket resp = onResourceRequest(client, mapped);
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,87 +0,0 @@
package org.openautonomousconnection.protocol.side.web.managers;
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.Map;
/**
* Manages authentication for web clients.
* Loads user credentials from a file and verifies login attempts.
*/
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
public final class AuthManager {
/**
* Map of usernames to their SHA-256 hashed passwords
*/
private static final Map<String, String> users = new HashMap<>();
/**
* Loads the authentication file and populates the users map.
* The file should contain lines in the format: username:hashed_password
* Lines starting with '#' are treated as comments and ignored.
*
* @param authFile The authentication file to load.
* @throws IOException If an I/O error occurs reading from the file.
*/
public static void loadAuthFile(File authFile) throws IOException {
// Create the file if it doesn't exist
if (!authFile.exists()) authFile.createNewFile();
for (String line : Files.readAllLines(authFile.toPath(), StandardCharsets.UTF_8)) {
// Trim whitespace and ignore comments/empty lines
line = line.trim();
if (line.isEmpty() || line.startsWith("#")) continue;
// Split the line into username and hashed password
String[] parts = line.split(":", 2);
if (parts.length == 2) {
users.put(parts[0], parts[1]);
}
}
}
/**
* Checks if the provided login and password are valid.
*
* @param login The username to check.
* @param password The password to verify.
* @return True if the credentials are valid, false otherwise.
*/
public static boolean checkAuth(String login, String password) {
// Retrieve the stored hashed password for the given username
String storedHash = users.get(login);
if (storedHash == null) return false;
// Hash the provided password and compare it to the stored hash
String hash = sha256(password);
return storedHash.equalsIgnoreCase(hash);
}
/**
* Computes the SHA-256 hash of the given input string.
*
* @param input The input string to hash.
* @return The hexadecimal representation of the SHA-256 hash.
*/
public static String sha256(String input) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8));
// Convert the byte array to a hexadecimal string
StringBuilder sb = new StringBuilder();
for (byte b : digest) sb.append(String.format("%02x", b));
return sb.toString();
} catch (Exception e) {
return "";
}
}
}

View File

@@ -1,173 +0,0 @@
package org.openautonomousconnection.protocol.side.web.managers;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
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.
*
* <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 {
private static List<Pattern> allow = List.of();
private static List<Pattern> deny = List.of();
private static List<Pattern> auth = List.of();
private RuleManager() {
}
/**
* Loads rules from a JSON file and compiles patterns.
*
* @param rulesFile JSON rules file
* @throws Exception if reading/parsing fails
*/
public static void loadRules(File rulesFile) throws Exception {
String json = Files.readString(rulesFile.toPath(), StandardCharsets.UTF_8);
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()));
}
/**
* Returns true if the path is allowed by allow rules.
*
* <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) {
String p = normalizePath(path);
// Default-open behavior if allow list is empty
if (allow.isEmpty()) return true;
return matchesAny(p, allow);
}
/**
* Returns true if the path is denied.
*
* @param path web path
* @return true if denied
*/
public static boolean isDenied(String path) {
String p = normalizePath(path);
return matchesAny(p, deny);
}
/**
* Returns true if the path requires authentication.
*
* @param path web path
* @return true if auth required
*/
public static boolean requiresAuth(String path) {
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();
}
/**
* Compiles a glob pattern to a full-match regex Pattern.
*
* <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 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

@@ -1,164 +0,0 @@
package org.openautonomousconnection.protocol.side.web.managers;
import lombok.Getter;
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
import org.openautonomousconnection.protocol.side.web.ProtocolWebServer;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Manages user sessions for web clients.
* Provides methods to create, validate, and invalidate sessions.
*/
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
public final class SessionManager {
/**
* Map of session IDs to Session objects.
*/
private static final Map<String, Session> sessions = new ConcurrentHashMap<>();
/**
* Secure random number generator for session ID creation.
*/
private static final SecureRandom secureRandom = new SecureRandom();
/**
* Creates a new session for the given user.
*
* @param login The username associated with the session.
* @param ip The IP address of the client.
* @param userAgent The User-Agent string of the client.
* @param protocolWebServer The Protocol WebServer for the unique Session
* @return The generated session ID.
* @throws IOException If an I/O error occurs.
*/
public static String create(String login, String ip, String userAgent, ProtocolWebServer protocolWebServer) throws IOException {
// Generate a secure random session ID
byte[] bytes = new byte[32];
secureRandom.nextBytes(bytes);
// Encode the bytes to a URL-safe Base64 string
String sessionId = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes) + protocolWebServer.getUniqueSessionString();
// Create and store the new session
sessions.put(sessionId, new Session(login, ip, userAgent, protocolWebServer));
return sessionId;
}
/**
* Validates a session ID against the provided IP and User-Agent.
*
* @param sessionId The session ID to validate.
* @param ip The IP address of the client.
* @param userAgent The User-Agent string of the client.
* @param protocolWebServer The Protocol WebServer to get the config for refreshing
* @return True if the session is valid, false otherwise.
* @throws IOException If an I/O error occurs.
*/
public static boolean isValid(String sessionId, String ip, String userAgent, ProtocolWebServer protocolWebServer) throws IOException {
// Retrieve the session associated with the session ID
Session session = sessions.get(sessionId);
// Check if the session exists, is not expired, and matches the IP and User-Agent
if (session == null || session.isExpired() || !session.matches(ip, userAgent)) {
sessions.remove(sessionId);
return false;
}
// Refresh the session expiration time
session.refresh(protocolWebServer);
return true;
}
/**
* Invalidates a session, removing it from the active sessions.
*
* @param sessionId The session ID to invalidate.
*/
public static void invalidate(String sessionId) {
sessions.remove(sessionId);
}
/**
* Retrieves the username associated with a valid session ID.
*
* @param sessionId The session ID to look up.
* @return The username if the session is valid, null otherwise.
*/
public static String getUser(String sessionId) {
// Retrieve the session associated with the session ID
Session session = sessions.get(sessionId);
// Check if the session exists and is not expired
if (session == null || session.isExpired()) {
sessions.remove(sessionId);
return null;
}
// Return the username associated with the session
return session.getLogin();
}
/**
* Cleans up expired sessions from the session map.
* This method should be called periodically to prevent memory leaks.
*/
public static void cleanupExpiredSessions() {
long now = System.currentTimeMillis();
sessions.entrySet().removeIf(entry -> entry.getValue().isExpired());
}
/**
* Represents a user session with associated metadata.
*/
private static class Session {
@Getter
String login;
String ip;
String userAgent;
long expiresAt;
Session(String login, String ip, String userAgent, ProtocolWebServer protocolWebServer) throws IOException {
this.login = login;
this.ip = ip;
this.userAgent = userAgent;
this.expiresAt = System.currentTimeMillis() + (long) protocolWebServer.getSessionExpire() * 60 * 1000;
}
/**
* Checks if the session has expired.
*
* @return True if the session is expired, false otherwise.
*/
boolean isExpired() {
return System.currentTimeMillis() > expiresAt;
}
/**
* Checks if the session matches the given IP and User-Agent.
*
* @param ip The IP address to check.
* @param userAgent The User-Agent string to check.
* @return True if both match, false otherwise.
*/
boolean matches(String ip, String userAgent) {
return this.ip.equals(ip) && this.userAgent.equals(userAgent);
}
/**
* Refreshes the session's expiration time.
*
* @param protocolWebServer The Protocol WebServer to get the Config setting
* @throws IOException If an I/O error occurs.
*/
void refresh(ProtocolWebServer protocolWebServer) throws IOException {
this.expiresAt = System.currentTimeMillis() + (long) protocolWebServer.getSessionExpire() * 60 * 1000;
}
}
}

View File

@@ -1,15 +0,0 @@
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

@@ -1,29 +0,0 @@
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

@@ -1,288 +0,0 @@
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

@@ -1,153 +0,0 @@
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

@@ -1,520 +0,0 @@
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

@@ -1,32 +0,0 @@
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

@@ -1,32 +0,0 @@
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

@@ -1,42 +0,0 @@
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

@@ -1,22 +0,0 @@
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

@@ -1,326 +0,0 @@
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

@@ -1,55 +0,0 @@
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

@@ -1,68 +0,0 @@
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

@@ -1,38 +0,0 @@
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

@@ -1,52 +0,0 @@
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

@@ -1,44 +0,0 @@
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

@@ -1,71 +0,0 @@
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

@@ -1,26 +0,0 @@
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

@@ -1,26 +0,0 @@
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

@@ -1,274 +0,0 @@
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

@@ -1,71 +0,0 @@
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

@@ -1,53 +0,0 @@
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

@@ -1,614 +0,0 @@
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

@@ -1,32 +0,0 @@
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

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

@@ -1,37 +0,0 @@
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

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
*
* You are unauthorized to remove this copyright.
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
* See LICENSE-File if exists
*/
package org.openautonomousconnection.protocol.utils;
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
import java.io.Serializable;
public class APIInformation extends DefaultMethodsOverrider implements Serializable {
public final String username;
public final String apiApplication;
public final String apiKey;
public APIInformation(String username, String apiApplication, String apiKey) {
this.username = username;
this.apiApplication = apiApplication;
this.apiKey = apiKey;
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
*
* You are unauthorized to remove this copyright.
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
* See LICENSE-File if exists
*/
package org.openautonomousconnection.protocol.utils;
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
public class DomainUtils extends DefaultMethodsOverrider {
public static String getTopLevelDomain(String url) throws MalformedURLException {
URL uri = null;
String tldString = null;
if (url.startsWith(SiteType.PUBLIC.name + "://")) url = url.substring((SiteType.PUBLIC.name + "://").length());
if (url.startsWith(SiteType.CLIENT.name + "://")) url = url.substring((SiteType.CLIENT.name + "://").length());
if (url.startsWith(SiteType.SERVER.name + "://")) url = url.substring((SiteType.SERVER.name + "://").length());
if (url.startsWith(SiteType.PROTOCOL.name + "://")) url = url.substring((SiteType.PROTOCOL.name + "://").length());
if (url.startsWith(SiteType.LOCAL.name + "://")) url = url.substring((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;
}
public static String getDomainName(String url) throws URISyntaxException, MalformedURLException {
if (url.startsWith(SiteType.PUBLIC.name + "://")) url = url.substring((SiteType.PUBLIC.name + "://").length());
if (url.startsWith(SiteType.CLIENT.name + "://")) url = url.substring((SiteType.CLIENT.name + "://").length());
if (url.startsWith(SiteType.SERVER.name + "://")) url = url.substring((SiteType.SERVER.name + "://").length());
if (url.startsWith(SiteType.PROTOCOL.name + "://")) url = url.substring((SiteType.PROTOCOL.name + "://").length());
if (url.startsWith(SiteType.LOCAL.name + "://")) url = url.substring((SiteType.LOCAL.name + "://").length());
if (!url.startsWith("https://") && !url.startsWith("http://")) url = "https://" + url;
URI uri = new URI(url);
String domain = uri.getHost().replace("." + getTopLevelDomain(url), "");
return domain;
}
public static String getPath(String url) {
if (!url.startsWith(SiteType.PUBLIC.name + "://") && !url.startsWith(SiteType.CLIENT.name + "://") &&
!url.startsWith(SiteType.SERVER.name + "://") && !url.startsWith(SiteType.PROTOCOL.name + "://") &&
!url.startsWith(SiteType.LOCAL.name + "://") && !url.startsWith("http") && !url.startsWith("https")) {
url = 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,23 @@
/*
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
*
* You are unauthorized to remove this copyright.
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
* See LICENSE-File if exists
*/
package org.openautonomousconnection.protocol.utils;
import java.io.Serializable;
public enum SiteType implements Serializable {
CLIENT("oac-client"), SERVER("oac-server"),
PUBLIC("oac"), PROTOCOL("oac-protocol"), LOCAL("oac-local");
;
public final String name;
SiteType(String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright (C) 2024 Open Autonomous Connection - All Rights Reserved
*
* You are unauthorized to remove this copyright.
* You have to give Credits to the Author in your project and link this GitHub site: https://github.com/Open-Autonomous-Connection
* See LICENSE-File if exists
*/
package org.openautonomousconnection.protocol.utils;
import dev.unlegitdqrk.unlegitlibrary.utils.DefaultMethodsOverrider;
public class 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 domain 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(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 domain!</h1>
<h4>Details:</h2>
<h5>""" + errorDetails + "</h5>" + """
</body>
</html>
""";
}
public static String ERROR_OCCURRED = ERROR_OCCURRED("No specified details!");
}

View File

@@ -0,0 +1 @@
1.0

View File

@@ -1,204 +0,0 @@
package org.openautonomousconnection.protocol.versions;
import lombok.Getter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Enum representing different protocol versions, their types, sides, and compatibility.
*/
public enum ProtocolVersion implements Serializable {
/**
* 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)),
/**
* 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.
*/
@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.
*/
@Getter
private final String version;
/**
* The type of the protocol version.
*/
@Getter
private final ProtocolType protocolType;
/**
* The side(s) the protocol version is intended for.
*/
@Getter
private final ProtocolSide protocolSide;
/**
* List of protocol versions that are compatible with this version.
*/
@Getter
private final List<ProtocolVersion> compatibleVersions;
/**
* List of supported protocols.
*/
@Getter
private final List<Protocol> supportedProtocols;
/**
* Constructor for ProtocolVersion enum.
*
* @param version The version string.
* @param protocolType The type of the protocol.
* @param protocolSide The side(s) the protocol is intended for.
* @param supportedProtocols List of supported protocols.
* @param compatibleVersions Varargs of compatible protocol versions.
*/
ProtocolVersion(String version, ProtocolType protocolType, ProtocolSide protocolSide, List<Protocol> supportedProtocols, ProtocolVersion... compatibleVersions) {
this.version = version;
this.protocolType = protocolType;
this.protocolSide = protocolSide;
this.compatibleVersions = new ArrayList<>(Arrays.stream(compatibleVersions).toList());
if (!this.compatibleVersions.contains(this)) this.compatibleVersions.add(this);
this.supportedProtocols = supportedProtocols;
}
/**
* Returns a string representation of the protocol version, including its version, type, side, supported protocols, and compatible versions.
*
* @return a string representation of the protocol version.
*/
@Override
public final String toString() {
StringBuilder compatible = new StringBuilder("[");
StringBuilder supported = new StringBuilder("[");
for (ProtocolVersion compatibleVersion : compatibleVersions) compatible.append(compatibleVersion.buildName());
for (Protocol supportedProtocol : supportedProtocols) supported.append(supportedProtocol.toString());
compatible.append("]");
supported.append("]");
return "{version=" + version + ";type=" + protocolType.toString() + ";side=" + protocolSide.toString() + ";supportedProtocols=" + supported + ";compatibleVersions=" + compatible + "}";
}
/**
* Builds a name for the protocol version combining its version and type.
*
* @return a string representing the name of the protocol version.
*/
public final String buildName() {
return version + "-" + protocolType.toString();
}
/**
* Enum representing different protocols.
*/
public enum Protocol implements Serializable {
HTTP,
HTTPS,
OAC;
/**
* Returns the name of the protocol in uppercase.
*
* @return the name of the protocol in uppercase.
*/
@Override
public final String toString() {
return name().toUpperCase();
}
}
/**
* Enum representing different types of protocol versions.
*/
public enum ProtocolType implements Serializable {
/**
* Classic Protocol Type, see old OAC-Project: <a href="https://repo.open-autonomous-connection.org/Open-Autonomous-Connection/">*_old</a>
*/
CLASSIC,
/**
* Beta Protocol Type, may be unstable and subject to change.
*/
BETA,
/**
* Stable Protocol Type, recommended for production use.
*/
STABLE;
/**
* Returns the name of the protocol in uppercase.
*
* @return the name of the protocol in uppercase.
*/
@Override
public final String toString() {
return name().toUpperCase();
}
}
/**
* Enum representing different sides where the protocol version can be used.
*/
public enum ProtocolSide implements Serializable {
/**
* Client Side only
*/
CLIENT,
/**
* INS Server Side only
*/
INS,
/**
* Web Server Side only
*/
WEB,
/**
* Both INS and Web Server Side
*/
WEB_INS,
/**
* Both Client and INS Server Side
*/
CLIENT_INS,
/**
* Both Client and Web Server Side
*/
CLIENT_WEB,
/**
* All Sides
*/
ALL;
/**
* Returns the name of the protocol in uppercase.
*
* @return the name of the protocol in uppercase.
*/
@Override
public final String toString() {
return name().toUpperCase();
}
}
}

View File

@@ -1,51 +0,0 @@
package org.openautonomousconnection.protocol.versions.v1_0_0.beta;
import java.io.Serializable;
/**
* Represents a INS record inside the INS system.
* <p>
* This is the transport format used in responses and packets.
* Each record contains:
* <ul>
* <li>The record type</li>
* <li>A value</li>
* <li>Optional priority and weight fields</li>
* <li>Optional port field (SRV)</li>
* <li>A TTL defining how long the record may be cached</li>
* </ul>
*/
public final class INSRecord implements Serializable {
public INSRecordType type;
public String value;
public int priority;
public int weight;
public int port;
public int ttl;
/**
* Creates an INS record object.
*
* @param type The INS record type.
* @param value The records data (IPv4, IPv6, hostname, text, etc.).
* @param priority MX / SRV priority value (ignored for other types).
* @param weight SRV weight (ignored for other types).
* @param port SRV port (ignored for other types).
* @param ttl Time-to-live for caching.
*/
public INSRecord(INSRecordType type, String value, int priority, int weight, int port, int ttl) {
this.type = type;
this.value = value;
this.priority = priority;
this.weight = weight;
this.port = port;
this.ttl = ttl;
}
@Override
public String toString() {
return type + ":" + value;
}
}

View File

@@ -1,172 +0,0 @@
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

@@ -1,26 +0,0 @@
package org.openautonomousconnection.protocol.versions.v1_0_0.beta;
/**
* Represents INS record types for the INS protocol.
* <p>
* Supported types:
* <ul>
* <li>A — IPv4 address</li>
* <li>AAAA — IPv6 address</li>
* <li>CNAME — Canonical name redirect</li>
* <li>TXT — Arbitrary text</li>
* <li>MX — Mail exchanger</li>
* <li>SRV — Service locator</li>
* <li>NS — Nameserver delegation</li>
* </ul>
*/
public enum INSRecordType {
A,
AAAA,
CNAME,
TXT,
MX,
SRV,
NS,
INFO,
}

View File

@@ -1,41 +0,0 @@
package org.openautonomousconnection.protocol.versions.v1_0_0.beta;
/**
* Defines the possible response states returned by the INS server.
*/
public enum INSResponseStatus {
/**
* Request succeeded and matching records were found.
*/
OK,
/**
* No records exist for the queried TLN/name/sub/type combination.
*/
NOT_FOUND,
/**
* The query was malformed or missing required parameters.
*/
INVALID_REQUEST,
/**
* Internal server error occurred while resolving the request.
*/
SERVER_ERROR,
/**
* Response is not required for the specific request type.
*/
RESPONSE_NOT_REQUIRED,
/**
* Authentication failed.
*/
RESPONSE_AUTH_FAILED,
/**
* Authentication succeeded.
*/
RESPONSE_AUTH_SUCCESS
}

View File

@@ -1,133 +0,0 @@
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

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

View File

@@ -1,16 +0,0 @@
package org.openautonomousconnection.protocol.versions.v1_0_0.classic;
import java.io.Serializable;
/**
* Enum representing the protocol versions for the Classic protocol.
*/
@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;
Classic_ProtocolVersion(String version) {
this.version = version;
}
}

View File

@@ -1,46 +0,0 @@
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

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

View File

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

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