Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a223c659a5 | ||
|
|
9081b2b644 | ||
|
|
7321d06ee9 | ||
|
|
d08af1001c | ||
|
|
aba21f5fb3 | ||
|
|
435d6c3eea |
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -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
70
LICENSE
@@ -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
105
README.MD
@@ -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
74
pom.xml
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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.<tln>
|
|
||||||
* </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);
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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!");
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
1.0
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 record’s 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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 0–65535</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) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package org.openautonomousconnection.protocol.versions.v1_0_0.beta;
|
|
||||||
|
|
||||||
public enum WebRequestMethod {
|
|
||||||
GET, POST, EXECUTE, SCRIPT
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
Reference in New Issue
Block a user